Merged back Obumbrata et Velata engine improvements
authorMartin Read <mpread@chiark.greenend.org.uk>
Sun, 16 Mar 2014 20:43:44 +0000 (20:43 +0000)
committerMartin Read <mpread@chiark.greenend.org.uk>
Sun, 16 Mar 2014 20:43:44 +0000 (20:43 +0000)
42 files changed:
Makefile
cave.cc
combat.cc
combat.hh
combo.cc [new file with mode: 0644]
coord.hh
core.hh
deeds.cc [new file with mode: 0644]
display-nc.cc
display.hh
dungeon.cc [new file with mode: 0644]
fov.cc
fov.hh
log.cc
main.cc
map.cc
map.cc.old [new file with mode: 0644]
map.hh
mapgen.hh
mon2.cc
mon3.cc [new file with mode: 0644]
monsters.cc
monsters.hh
notify-local-tty.cc
notify.hh
obj2.cc [new file with mode: 0644]
objects.cc
objects.hh
permobj.hh
permon.hh
player.hh
pmon2.cc
pmon_comp
pobj_comp
rng.hh
role.cc [new file with mode: 0644]
shrine.cc
skills.cc [new file with mode: 0644]
sorcery.cc
sorcery.hh
u.cc
victrix-abyssi.hh

index 6598895..ca0b39c 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 display-nc.o fov.o log.o main.o map.o monsters.o mon2.o notify-local-tty.o objects.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 dungeon.o fov.o log.o main.o map.o monsters.o mon2.o mon3.o notify-local-tty.o objects.o obj2.o pmon2.o rng.o role.o shrine.o skills.o sorcery.o u.o util.o
 OBJS:=$(GENERATED_OBJS) $(HANDWRITTEN_OBJS)
 GAME:=victrix-abyssi
 MAJVERS:=0
@@ -17,13 +17,13 @@ MINVERS:=9
 PATCHVERS:=1
 COMMON_CFLAGS:=-Wall -Wwrite-strings -Wunreachable-code -Wformat -Werror=format-security -fstack-protector --param=ssp-buffer-size=4 -DMAJVERS=$(MAJVERS) -DMINVERS=$(MINVERS) -DPATCHVERS=$(PATCHVERS)-std=gnu11 -D_FORTIFY_SOURCE=2 -I$(srcdir)
 COMMON_CXXFLAGS:=-Wall -Wwrite-strings -Wno-unused-but-set-variable -Wredundant-decls -Wunreachable-code -Wformat -Werror=format-security -fstack-protector --param=ssp-buffer-size=4 -DMAJVERS=$(MAJVERS) -DMINVERS=$(MINVERS) -DPATCHVERS=$(PATCHVERS) -std=gnu++11 -D_FORTIFY_SOURCE=2 -I$(srcdir)
-PRODUCTION_CFLAGS:=$(COMMON_CFLAGS)
+PRODUCTION_CFLAGS:=$(COMMON_CFLAGS) -O2
 DEVELOPMENT_CFLAGS:=$(COMMON_CFLAGS) -g -Werror
-PRODUCTION_CXXFLAGS:=$(COMMON_CXXFLAGS)
+PRODUCTION_CXXFLAGS:=$(COMMON_CXXFLAGS) -O2
 DEVELOPMENT_CXXFLAGS:=$(COMMON_CXXFLAGS) -g -Werror
 LIBS=-lpanelw -lncursesw -lxdg-basedir -lm
-ARCHIVEDIR:=victrix-abyssi-$(MAJVERS).$(MINVERS).$(PATCHVERS)
-ARCHIVENAME:=victrix-abyssi_$(MAJVERS).$(MINVERS).$(PATCHVERS)
+ARCHIVEDIR:=$(GAME)-$(MAJVERS).$(MINVERS).$(PATCHVERS)
+ARCHIVENAME:=$(GAME)_$(MAJVERS).$(MINVERS).$(PATCHVERS)
 COMMON_LDFLAGS:=-Wl,-z,relro
 DEVELOPMENT_LDFLAGS:=$(COMMON_LDFLAGS) -g
 
@@ -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)
@@ -93,38 +93,53 @@ permobj.cc pobj_id.hh: $(srcdir)/default.permobjs $(srcdir)/pobj_comp
 permons.cc pmon_id.hh: $(srcdir)/default.permons $(srcdir)/pmon_comp
        $(srcdir)/pmon_comp $<
 ## Dependencies for the build
-cave.o: $(srcdir)/cave.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/map.hh $(srcdir)/mapgen.hh
+cave.o: $(srcdir)/cave.cc $(srcdir)/$(GAME).hh $(srcdir)/map.hh $(srcdir)/mapgen.hh
 
-combat.o: $(srcdir)/combat.cc $(srcdir)/combat.hh $(srcdir)/victrix-abyssi.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh $(srcdir)/notify.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+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
 
-display-nc.o: $(srcdir)/display-nc.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/display.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.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
+
+display-nc.o: $(srcdir)/display-nc.cc $(srcdir)/$(GAME).hh $(srcdir)/display.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh $(srcdir)/map.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/permon.hh $(srcdir)/permobj.hh
 
 log.o: $(srcdir)/log.cc $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/player.hh $(srcdir)/map.hh $(srcdir)/util.h
 
-main.o: $(srcdir)/main.cc $(srcdir)/combat.hh $(srcdir)/victrix-abyssi.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+main.o: $(srcdir)/main.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/monsters.hh $(srcdir)/notify.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
 
-map.o: $(srcdir)/map.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/map.hh $(srcdir)/core.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+map.o: $(srcdir)/map.cc $(srcdir)/$(GAME).hh $(srcdir)/map.hh $(srcdir)/core.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
 
-monsters.o: $(srcdir)/monsters.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+monsters.o: $(srcdir)/monsters.cc $(srcdir)/$(GAME).hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
 
-mon2.o: $(srcdir)/mon2.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/sorcery.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+mon2.o: $(srcdir)/mon2.cc $(srcdir)/$(GAME).hh $(srcdir)/sorcery.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
 
-notify-local-tty.o: $(srcdir)/notify-local-tty.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/combat.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/sorcery.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+mon3.o: $(srcdir)/mon3.cc $(srcdir)/$(GAME).hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/map.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+notify-local-tty.o: $(srcdir)/notify-local-tty.cc $(srcdir)/$(GAME).hh $(srcdir)/combat.hh $(srcdir)/monsters.hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/sorcery.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
 
 permobj.o: ./permobj.cc $(srcdir)/core.hh $(srcdir)/permobj.hh
 
 permons.o: ./permons.cc $(srcdir)/core.hh $(srcdir)/permon.hh
 
-pmon2.o: $(srcdir)/pmon2.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+pmon2.o: $(srcdir)/pmon2.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+objects.o: $(srcdir)/objects.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
+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
 
-objects.o: $(srcdir)/objects.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+skills.o: $(srcdir)/skills.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)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/map.hh $(srcdir)/mapgen.hh
-sorcery.o: $(srcdir)/sorcery.cc $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/sorcery.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.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
 
-u.o: $(srcdir)/u.cc $(srcdir)/combat.hh $(srcdir)/victrix-abyssi.hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+u.o: $(srcdir)/u.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
 
 util.o: $(srcdir)/util.c $(srcdir)/util.h
 
diff --git a/cave.cc b/cave.cc
index 04e8660..c3c12df 100644 (file)
--- a/cave.cc
+++ b/cave.cc
 #include "monsters.hh"
 #include "mapgen.hh"
 
-void place_cave_stairs(void)
+void place_cave_stairs(Level *l)
 {
     Coord c;
     Coord d;
     int stair_tries = 0;
     do
     {
-        c = lvl.random_point(1);
-    } while (!(terrain_props[lvl.terrain_at(c)].flags & TFLAG_floor) ||
-             (lvl.flags_at(c) & MAPFLAG_HARDWALL));
-    lvl.add_stairs_at(c, STAIRS_DOWN, lvl.self.naive_next());
+        c = l->random_point(1);
+    } while (!(terrain_props[l->terrain_at(c)].flags & TFLAG_floor) ||
+             (l->flags_at(c) & MAPFLAG_HARDWALL));
+    l->add_stairs_at(c, PORTAL_ONWARD, l->self.naive_next());
     do
     {
-        d = lvl.random_point(1);
+        d = l->random_point(1);
         ++stair_tries;
-    } while (!(terrain_props[lvl.terrain_at(d)].flags & TFLAG_floor) ||
-             (lvl.flags_at(d) & MAPFLAG_HARDWALL) ||
+    } while (!(terrain_props[l->terrain_at(d)].flags & TFLAG_floor) ||
+             (l->flags_at(d) & MAPFLAG_HARDWALL) ||
              (d.dist_cheb(c) < (10 - (stair_tries / 40))));
-    lvl.add_stairs_at(d, STAIRS_UP, lvl.self.naive_prev());
+    l->add_stairs_at(d, PORTAL_LANDING, l->self.naive_prev());
 }
 
 /*! \brief Excavate a cave level.
  *
  *  This algorithm runs two random walks on the map.
  */
-void build_level_cave(void)
+void build_level_cave(Level *l)
 {
     Coord c = { GUIDE_EDGE_SIZE / 2, GUIDE_EDGE_SIZE / 2 };
     int num_pools;
     int walk_data[4] = { 1, FLOOR, WALL, FLOOR };
 
-    initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
-    run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    initialize_chunks(l, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+    try
+    {
+    run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    }
+    catch (Obumb_levgen_excep p)
+    {
+        DEBUG_ARBITRARY(p.str);
+    }
     //run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
-    if ((lvl.theme != THEME_UNDEAD) && (depth > 20) && !zero_die(4))
+    if ((l->theme != THEME_UNDEAD) && (depth > 20) && !zero_die(4))
     {
         num_pools = inc_flat(1, 4);
         walk_data[0] = 2;
@@ -85,29 +92,101 @@ void build_level_cave(void)
     {
         int pool_size = inc_flat(9, 36);
         do {
-            c = lvl.random_point(2);
-        } while (lvl.terrain_at(c) != FLOOR);
-        run_random_walk(c, excavation_write, walk_data, pool_size);
+            c = l->random_point(2);
+        } while (l->terrain_at(c) != FLOOR);
+        try 
+        {
+            run_random_walk(l, c, excavation_write, walk_data, pool_size);
+        }
+        catch (Obumb_levgen_excep p)
+        {
+            DEBUG_ARBITRARY(p.str);
+        }
         --num_pools;
     }
-    place_cave_stairs();
+    place_cave_stairs(l);
 }
 
 /*! \brief Excavate a cave level with intrusions */
-void build_level_intrusions(void)
+void build_level_intrusions(Level *l)
 {
     Coord c = { GUIDE_EDGE_SIZE / 2, GUIDE_EDGE_SIZE / 2 };
     int i;
     int walk_data[4] = { 1, FLOOR, WALL, FLOOR };
 
-    initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+    initialize_chunks(l, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
     for (i = 0; i < 6; ++i)
     {
-        place_random_intrusion(WALL);
+        place_random_intrusion(l, WALL);
+    }
+    try
+    {
+        run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    }
+    catch (Obumb_levgen_excep p)
+    {
+        DEBUG_ARBITRARY(p.str);
     }
-    run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
     /* and now the stairs */
-    place_cave_stairs();
+    place_cave_stairs(l);
+}
+
+/*! \brief Excavate a "pits" level. */
+void build_level_pits(Level *l, Terrain pit_type)
+{
+    Coord c = { GUIDE_EDGE_SIZE / 2, GUIDE_EDGE_SIZE / 2 };
+    int walk_data[4] = { 1, FLOOR, WALL, pit_type };
+
+    initialize_chunks(l, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+    walk_data[1] = pit_type;
+    c.y = GUIDE_EDGE_SIZE / 6;
+    c.x = GUIDE_EDGE_SIZE / 6;
+    try
+    {
+        run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    }
+    catch (Obumb_levgen_excep p)
+    {
+        DEBUG_ARBITRARY(p.str);
+    }
+    c.x = GUIDE_EDGE_SIZE - (GUIDE_EDGE_SIZE / 6);
+    try
+    {
+        run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    }
+    catch (Obumb_levgen_excep p)
+    {
+        DEBUG_ARBITRARY(p.str);
+    }
+    c.y = GUIDE_EDGE_SIZE - (GUIDE_EDGE_SIZE / 6);
+    try
+    {
+    run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    }
+    catch (Obumb_levgen_excep p)
+    {
+        DEBUG_ARBITRARY(p.str);
+    }
+    c.x = (GUIDE_EDGE_SIZE / 6);
+    try
+    {
+    run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    }
+    catch (Obumb_levgen_excep p)
+    {
+        DEBUG_ARBITRARY(p.str);
+    }
+    walk_data[0] = 2;
+    walk_data[1] = FLOOR;
+    try
+    {
+    run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    }
+    catch (Obumb_levgen_excep p)
+    {
+        DEBUG_ARBITRARY(p.str);
+    }
+    place_cave_stairs(l);
 }
 
 /* cave.cc */
index 6e64679..23a3675 100644 (file)
--- a/combat.cc
+++ b/combat.cc
@@ -42,7 +42,7 @@ int player_melee_base(void)
     int damage;
     if (u.weapon != NO_OBJ)
     {
-        dmgbase = permobjs[objects[u.weapon].obj_id].power + (u.body / 10);
+        dmgbase = permobjs[objects[u.weapon].po_ref].power + (u.body / 10);
         damage = dmgbase / 3 + one_die(dmgbase - dmgbase / 3);
     }
     else
@@ -54,37 +54,37 @@ int player_melee_base(void)
 
 /*! \brief Calculate the effect of the player's  damage amplifier ring
  */
-bool ring_effectiveness(int mon, int ring_pobj, int damage, int *bonus_damage, int *vamp_healing)
+bool damage_amplification(Mon_handle mon, int ring_pobj, int damage, int *bonus_damage, int *vamp_healing)
 {
     bool rv = false;
-    int pm = monsters[mon].mon_id;
+    int pm = monsters[mon].pm_ref;
     *vamp_healing = 0;
-    switch (ring_pobj)
+    *bonus_damage = 0;
+    // Fire and frost mutually exclude.
+    if (u.damage_amp[DT_FIRE] && !pmon_resists_fire(pm))
     {
-    case PO_FIRE_RING:
-        if (!pmon_resists_fire(pm))
-        {
-            *bonus_damage = dice(2, 4) + ((damage + 1) / 2);
-            rv = true;
-        }
-        break;
-    case PO_VAMPIRE_RING:
-        if (!pmon_resists_necro(pm))
-        {
-            *bonus_damage = dice(2, 4) + ((damage + 3) / 4);
-            *vamp_healing = std::min(monsters[mon].hpcur, (damage + 5) / 6);
-            rv = true;
-        }
-        break;
-    case PO_FROST_RING:
-        if (!pmon_resists_cold(pm))
-        {
-            *bonus_damage = dice(2, 4) + ((damage + 3) / 4);
-            rv = true;
-        }
-        break;
-    default:
-        break;
+       *bonus_damage += dice(2, 4) + ((damage + 1) / 2);
+       rv = true;
+    }
+    else if (u.damage_amp[DT_COLD] && !pmon_resists_cold(pm))
+    {
+       *bonus_damage += dice(2, 4) + ((damage + 3) / 4);
+       rv = true;
+    }
+    if (u.damage_amp[DT_NECRO] && !pmon_resists_necro(pm))
+    {
+       *bonus_damage += dice(2, 4) + ((damage + 3) / 4);
+       *vamp_healing = std::min(monsters[mon].hpcur, (damage + 5) / 6);
+       rv = true;
+    }
+    // only one "slay" applies
+    if (u.damage_amp[DT_SLAY_DEMON] && pmon_is_demonic(pm))
+    {
+       *bonus_damage += damage;
+    }
+    else if (u.damage_amp[DT_SLAY_UNDEAD] && pmon_is_undead(pm))
+    {
+       *bonus_damage += damage;
     }
     return rv;
 }
@@ -94,17 +94,17 @@ bool ring_effectiveness(int mon, int ring_pobj, int damage, int *bonus_damage, i
  * \param mon Monster hit
  * \param damage Rolled damage before rings are applied
  */
-void resolve_player_melee(int mon, int damage)
+void resolve_player_melee(Mon_handle mon, int damage)
 {
     bool ring_eff;
     int ring_bonus;
     int healing = 0;
     if (u.ring != NO_OBJ)
     {
-        ring_eff = ring_effectiveness(mon, objects[u.ring].obj_id, damage, &ring_bonus, &healing);
+        ring_eff = damage_amplification(mon, objects[u.ring].po_ref, damage, &ring_bonus, &healing);
         if (ring_eff)
         {
-            notify_ring_boost(mon, objects[u.ring].obj_id);
+            notify_ring_boost(mon, objects[u.ring].po_ref);
             damage += ring_bonus;
         }
     }
@@ -116,7 +116,10 @@ void resolve_player_melee(int 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);
+       }
     }
 }
 
@@ -128,7 +131,7 @@ Action_cost player_power_attack(Offset delta)
 {
     Coord c = u.pos + delta;
     int damage;
-    int mon = lvl.mon_at(c);
+    Mon_handle mon = lvl.mon_at(c);
     /* Power Attack: Always hit, do +75% damage. */
     notify_player_combo_powatk(mon);
     damage = (player_melee_base() * 7) / 4;
@@ -155,13 +158,27 @@ Action_cost player_attack(Offset delta)
     return Cost_std;
 }
 
-int uhitm(int mon)
+Action_cost player_charge(Offset step, int power)
+{
+    Mon const *mp;
+    Coord c = u.pos + step;
+    int mon = lvl.mon_at(c);
+    int damage;
+    mp = mon_snap(mon);
+    notify_player_charge_mon(mon);
+    /* +25%, and a further +25% for each step in the charge */
+    damage = (player_melee_base() * (5 + power)) / 4;
+    resolve_player_melee(mon, damage);
+    return Cost_std;   /* Hit. */
+}
+
+int uhitm(Mon_handle mon)
 {
-    Mon *mp;
+    Mon const *mp;
     int tohit;
     int damage;
     int hitbase = u.agility + u.level;
-    mp = monsters + mon;
+    mp = mon_snap(mon);
     tohit = hitbase / 3 + zero_die(hitbase - hitbase / 3);
     if (tohit < mp->defence)
     {
@@ -182,13 +199,13 @@ int ushootm(Offset step)
     int range;
     Coord c = u.pos + step;
     bool done = false;
-    int mon;
+    Mon_handle mon;
     Mon *mptr;
     Obj *wep;
     Permobj *pwep;
     int damage;
-    wep = objects + u.weapon;
-    pwep = permobjs + wep->obj_id;
+    wep = obj_snapv(u.weapon);
+    pwep = permobjs + wep->po_ref;
     damage = one_die(pwep->power);
     for (range = 1; !done; ++range, (c += step))
     {
@@ -196,7 +213,7 @@ int ushootm(Offset step)
         if (mon != NO_MON)
         {
             done = true;
-            mptr = monsters + mon;
+            mptr = mon_snapv(mon);
             tohit = zero_die(u.agility + u.level - range);
             if (range == 1)
             {
@@ -212,7 +229,7 @@ int ushootm(Offset step)
                     notify_player_hurt_mon(mon, damage);
                 }
                 damage_mon(mon, damage, true);
-                if ((mptr->used) && (wep->obj_id == PO_THUNDERBOW))
+                if ((mptr->flags & MF_ALIVE) && (wep->po_ref == PO_THUNDERBOW))
                 {
                     int kb = knockback_mon(mon, step, true, true);
                     switch (kb)
@@ -243,12 +260,12 @@ int ushootm(Offset step)
     return 0;
 }
 
-int mhitu(int mon, Damtyp dtype)
+int mhitu(Mon_handle mon, Damtyp dtype)
 {
     int tohit;
     int damage;
     int unaffected;
-    Mon *mptr = monsters + mon;
+    Mon *mptr = mon_snapv(mon);
     tohit = zero_die(mptr->mtohit + 5);
     if (tohit < u.defence)
     {
@@ -256,7 +273,10 @@ int mhitu(int 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
@@ -307,19 +327,19 @@ test_unaffected:
     else
     {
         notify_player_touch_effect(dtype);
-        if ((mptr->mon_id == PM_VAMPIRE) && !u.resists(DT_NECRO))
+        if ((mptr->pm_ref == PM_VAMPIRE) && !u.resists(DT_NECRO))
         {
             heal_mon(mon, damage * 2 / 5, 1);
-        } else if ((tohit - u.defence >= 5) && (mptr->mon_id == PM_SNAKE))
+        } else if ((tohit - u.defence >= 5) && (mptr->pm_ref == PM_SNAKE))
         {
             drain_body(1, "snake venom", 0);
         }
-        damage_u(damage, DEATH_KILLED_MON, permons[mptr->mon_id].name);
+        damage_u(damage, DEATH_KILLED_MON, permons[mptr->pm_ref].name);
     }
     return 1;
 }
 
-int mshootu(int mon)
+int mshootu(Mon_handle mon)
 {
     Mon *mptr;
     Mon *bystander;
@@ -333,14 +353,14 @@ int mshootu(int mon)
     int evasion;
     int defence;
     Damtyp dtype;
-    mptr = monsters + mon;
+    mptr = mon_snapv(mon);
     c = mptr->pos;
     /* dy, dx == trajectory of missile */
     delta = u.pos.delta(c);
     step = mysign(delta);
     /* Don't get the bonus that applies to melee attacks. */
     tohit = zero_die(mptr->rtohit);
-    dtype = permons[mptr->mon_id].rdtyp;
+    dtype = permons[mptr->pm_ref].rdtyp;
     notify_mon_ranged_attack(mon);
     if ((dtype == DT_NECRO) || (dtype == DT_ELEC))
     {
@@ -360,7 +380,7 @@ int mshootu(int mon)
     /* Move projectile one square before looking for targets. */
     for ((done = 0), (c = mptr->pos + step); !done; c += step)
     {
-        int victim;
+        Mon_handle victim;
         if ((lvl.terrain_at(c) == WALL) || (lvl.terrain_at(c) == DOOR))
         {
             done = 1;
@@ -376,7 +396,7 @@ int mshootu(int mon)
                 if (!unaffected)
                 {
                     damage = one_die(mptr->rdam);
-                    damage_u(damage, DEATH_KILLED_MON, permons[mptr->mon_id].name);
+                    damage_u(damage, DEATH_KILLED_MON, permons[mptr->pm_ref].name);
                 }
                 return 1;
             }
@@ -388,11 +408,11 @@ int mshootu(int mon)
         else if (victim != NO_MON)
         {
             done = 1;
-            bystander = monsters + victim;
+            bystander = mon_snapv(victim);
             switch (dtype)
             {
             case DT_COLD:
-                if (pmon_resists_cold(bystander->mon_id))
+                if (pmon_resists_cold(bystander->pm_ref))
                 {
                     unaffected = 1;
                 }
@@ -402,7 +422,7 @@ int mshootu(int mon)
                 }
                 break;
             case DT_FIRE:
-                if (pmon_resists_fire(bystander->mon_id))
+                if (pmon_resists_fire(bystander->pm_ref))
                 {
                     unaffected = 1;
                 }
@@ -412,7 +432,7 @@ int mshootu(int mon)
                 }
                 break;
             case DT_NECRO:
-                if (pmon_is_undead(bystander->mon_id))
+                if (pmon_is_undead(bystander->pm_ref))
                 {
                     unaffected = 1;
                 }
index fe0699d..743c773 100644 (file)
--- a/combat.hh
+++ b/combat.hh
@@ -29,8 +29,8 @@
 #ifndef COMBAT_HH
 #define COMBAT_HH
 
-#ifndef VICTRIX_ABYSSI_HH
-#include "victrix-abyssi.hh"
+#ifndef CORE_HH
+#include "core.hh"
 #endif
 
 #ifndef MONSTERS_HH
 
 #define agility_modifier() (u.withering ? (u.agility / 10) : (u.agility / 5))
 /* XXX combat.c data and funcs */
-extern Action_cost throw_flask(int obj, Offset step);
-extern Action_cost player_attack(Offset step);
-extern Action_cost player_power_attack(Offset step);
-extern void resolve_player_melee(int mon, int damage);
-extern int mhitu(int mon, Damtyp dtyp);
-extern int uhitm(int mon);
-extern int mshootu(int mon);
-extern int ushootm(Offset step);
+Action_cost player_attack(Offset step);
+Action_cost player_power_attack(Offset step);
+Action_cost player_charge(Offset step, int power);
+void resolve_player_melee(Mon_handle mon, int damage);
+int mhitu(Mon_handle mon, Damtyp dtyp);
+int uhitm(Mon_handle mon);
+int mshootu(Mon_handle mon);
+int ushootm(Offset step);
 
 class Combo_spec
 {
diff --git a/combo.cc b/combo.cc
new file mode 100644 (file)
index 0000000..840943c
--- /dev/null
+++ b/combo.cc
@@ -0,0 +1,369 @@
+/*! \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 "victrix-abyssi.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->known_skill[Skill_power_attack])
+    {
+        Obj const *optr = obj_snap(player->weapon);
+        if (optr && (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
+    {
+        DEBUG_CONTROL_FLOW();
+        return Combo_state_none;
+    }
+}
+
+static bool blade_dance_avail(Player const *player)
+{
+    if (player->known_skill[Skill_blade_dance])
+    {
+        Obj const *optr = obj_snap(player->weapon);
+        if (optr)
+        {
+            if (po_is_sword(optr->po_ref) || po_is_dagger(optr->po_ref))
+            {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+static Combo_state blade_dance_detector(std::deque<Action>* action_list)
+{
+    if ((*action_list)[0].cmd == WALK)
+    {
+        if (action_list->size() == 1)
+        {
+            Offset o = { (int32_t) (*action_list)[0].details[0], (int32_t) (*action_list)[0].details[1] };
+            if (myabs(o.y) == myabs(o.x))
+            {
+                Offset o1 = { o.y, 0 };
+                Offset o2 = { 0, o.x };
+                Coord c1 = u.pos + o1;
+                Coord c2 = u.pos + o2;
+                Mon_handle m1 = lvl.mon_at(c1);
+                Mon_handle m2 = lvl.mon_at(c2);
+                Coord target;
+                if ((m1 != NO_MON) && ((m2 == NO_MON) || (zero_die(2))))
+                {
+                    target = c1;
+                }
+                else if (m2 != NO_MON)
+                {
+                    target = c2;
+                }
+                else
+                {
+                    return Combo_state_none;
+                }
+                action_list->resize(1);
+                (*action_list)[0].cmd = COMBO_BLADE_DANCE;
+                (*action_list)[0].details[0] = o.y;
+                (*action_list)[0].details[1] = o.x;
+                (*action_list)[0].details[2] = target.y;
+                (*action_list)[0].details[3] = target.x;
+                return Combo_state_complete;
+            }
+        }
+        return Combo_state_none;
+    }
+    else
+    {
+        DEBUG_CONTROL_FLOW();
+        return Combo_state_none;
+    }
+}
+
+static bool charge_avail(Player const *player)
+{
+    if (player->known_skill[Skill_charge])
+    {
+        Obj const *optr = obj_snap(player->weapon);
+        if (optr && (permobjs[optr->po_ref].flags[0] & POF_RANGED_WEAPON))
+        {
+            return false;
+        }
+        return true;
+    }
+    return false;
+}
+
+static Combo_state charge_detector(std::deque<Action>* action_list)
+{
+    if ((*action_list)[0].cmd == WALK)
+    {
+        uint32_t size = action_list->size();
+        if (size > 1)
+        {
+            bool valid = true;
+            for (uint32_t i = 1; i < size; ++i)
+            {
+                if (i == (size - 1))
+                {
+                    if (((*action_list)[size - 1].cmd == ATTACK) &&
+                        ((*action_list)[size - 1].details[0] == 
+                         (*action_list)[0].details[0]) &&
+                        ((*action_list)[size - 1].details[1] ==
+                         (*action_list)[0].details[1]))
+                    {
+                        (*action_list)[0].cmd = COMBO_CHARGE;
+                        (*action_list)[0].details[2] = i;
+                        action_list->resize(1);
+                        return Combo_state_complete;
+                    }
+                }
+                if ((*action_list)[i] != (*action_list)[0])
+                {
+                    // Do we have a prefix consisting entirely of identical
+                    // WALK actions?
+                    valid = false;
+                }
+            }
+            if (!valid)
+            {
+                return Combo_state_none;
+            }
+        }
+        return Combo_state_partial;
+    }
+    else
+    {
+        DEBUG_CONTROL_FLOW();
+        return Combo_state_none;
+    }
+}
+
+static bool flying_leap_avail(Player const *player)
+{
+    if (player->known_skill[Skill_flying_leap])
+    {
+        if (u.armour == NO_OBJ)
+        {
+            return true;
+        }
+        Obj const *optr = obj_snap(u.armour);
+        if (po_is_dress(optr->po_ref) || po_is_leatherwear(optr->po_ref) ||
+            po_is_robe(optr->po_ref))
+        {
+            return true;
+        }
+        return false;
+    }
+    return false;
+}
+
+static Combo_state flying_leap_detector(std::deque<Action>* action_list)
+{
+    if ((*action_list)[0].cmd == WALK)
+    {
+        uint32_t size = action_list->size();
+        if (size == 1)
+        {
+            Offset o = { (int32_t) (*action_list)[0].details[0], (int32_t) (*action_list)[0].details[1] };
+            Coord c = u.pos + o;
+            Mon_handle mon = lvl.mon_at(c);
+            if (mon != NO_MON)
+            {
+                return Combo_state_none;
+            }
+            Terrain t = lvl.terrain_at(c);
+            if (terrain_gapes(t) || terrain_drowns(t) || terrain_is_hot(t))
+            {
+                Coord c2 = c + o;
+                Terrain t2 = lvl.terrain_at(c2);
+                Mon_handle mon = lvl.mon_at(c2);
+                if (terrain_is_floor(t2) && (mon == NO_MON))
+                {
+                    (*action_list)[0].cmd = COMBO_FLYING_LEAP;
+                    (*action_list)[0].details[0] = o.y*2;
+                    (*action_list)[0].details[1] = o.x*2;
+                    return Combo_state_complete;
+                }
+            }
+        }
+        return Combo_state_none;
+    }
+    else
+    {
+        DEBUG_CONTROL_FLOW();
+        return Combo_state_none;
+    }
+}
+
+Combo_entry combo_entries[] =
+{
+    { "charge", WALK, charge_avail, charge_detector },
+    { "flying leap", WALK, flying_leap_avail, flying_leap_detector },
+    { "blade dance", WALK, blade_dance_avail, blade_dance_detector },
+    { "power attack", STAND_STILL, powatk_avail, powatk_detector },
+    { "empty fencepost entry", REJECTED_ACTION, nullptr, nullptr }
+};
+
+/* combo.cc */
+// vim:cindent:expandtab
index 1a2016a..3a25f4f 100644 (file)
--- a/coord.hh
+++ b/coord.hh
@@ -30,6 +30,7 @@
 #define COORD_HH
 
 #include <algorithm>
+#include <stdint.h>
 
 template <typename T> T myabs(T val);
 template <typename T> T mysign(T val);
@@ -48,8 +49,8 @@ template <typename T> inline T mysign(T val) { return (val < 0) ? -1 : ((val > 0
 class Offset
 {
 public:
-    int y;
-    int x;
+    int32_t y;
+    int32_t x;
     bool ecardinal(void) const { return ((y && !x) || (x && !y)); }
     bool rcardinal(void) const { return ecardinal() || (myabs(y) == myabs(x)); }
     int len_cheb(void) const { return std::max(myabs(y), myabs(x)); }
@@ -69,7 +70,7 @@ public:
     bool operator <=(Offset const& right) const { return (y < right.y) || ((y == right.y) && (x <= right.x)); }
     bool operator >(Offset const& right) const { return (y > right.y) || ((y == right.y) && (x > right.x)); }
     bool operator >=(Offset const& right) const { return (y > right.y) || ((y == right.y) && (x >= right.x)); }
-    void clamp(int ymin, int xmin, int ymax, int xmax)
+    void clamp(int32_t ymin, int32_t xmin, int32_t ymax, int32_t xmax)
     {
         y = std::min(ymax, std::max(ymin, y));
         x = std::min(xmax, std::max(xmin, x));
@@ -88,14 +89,15 @@ public:
 class Coord
 {
 public:
-    int y;
-    int x;
+    int32_t y;
+    int32_t x;
     int dist_cheb(Coord const& right) const { return std::max(myabs(y - right.y), myabs(x - right.x)); }
     int dist_taxi(Coord const& right) const { return myabs(y - right.y) + myabs(x - right.x); }
     int distsq_eucl(Coord const& right) const { return (y - right.y) * (y - right.y) + (x - right.x) * (x - right.x); }
     Offset delta(Coord const& right) const { Offset d = { y - right.y, x - right.x }; return d; }
     Coord operator +(Offset const& right) const { Coord c = { y + right.y, x + right.x}; return c; }
     Coord operator -(Offset const& right) const { Coord c = { y - right.y, x - right.x}; return c; }
+    Offset operator -(Coord const& right) const { Offset o = { y - right.y, x - right.x}; return o; }
     Coord const& operator +=(Offset const& right) { y += right.y; x += right.x; return *this;}
     Coord const& operator -=(Offset const& right) { y -= right.y; x -= right.x; return *this;}
     bool operator !=(Coord const& right) const { return (y != right.y) || (x != right.x); }
@@ -104,7 +106,7 @@ public:
     bool operator <=(Coord const& right) const { return (y < right.y) || ((y == right.y) && (x <= right.x)); }
     bool operator >(Coord const& right) const { return (y > right.y) || ((y == right.y) && (x > right.x)); }
     bool operator >=(Coord const& right) const { return (y > right.y) || ((y == right.y) && (x >= right.x)); }
-    void clamp(int ymin, int xmin, int ymax, int xmax)
+    void clamp(int32_t ymin, int32_t xmin, int32_t ymax, int32_t xmax)
     {
         y = std::min(ymax, std::max(ymin, y));
         x = std::min(xmax, std::max(xmin, x));
diff --git a/core.hh b/core.hh
index d92ba8f..cecf024 100644 (file)
--- a/core.hh
+++ b/core.hh
@@ -1,5 +1,5 @@
 /*! \file core.hh
- *  \brief Essential predefinitions header for Victrix Abyssi
+ *  \brief Essential predefinitions header
  */
 
 /* Copyright 2014 Martin Read
@@ -38,6 +38,7 @@
 #include <stdbool.h>
 #include <stdio.h>
 #include <arpa/inet.h>
+#include <deque>
 
 #ifndef COORD_HH
 #include "coord.hh"
@@ -84,14 +85,15 @@ enum Noisiness
  */
 enum Gamecolour
 {
+    Gcol_nocolour = -1,
     /* l_cyan is the last "core" colour. Colours after it may alias to one
      * of the core colours depending on the nature of your display. */
-    Gcol_l_grey, Gcol_d_grey, Gcol_red, Gcol_blue,
+    Gcol_l_grey = 0, Gcol_d_grey, Gcol_red, Gcol_blue,
     Gcol_green, Gcol_purple, Gcol_brown, Gcol_cyan,
     Gcol_white, Gcol_l_red, Gcol_l_blue, Gcol_l_green,
     Gcol_l_purple, Gcol_yellow, Gcol_l_cyan,
     /* Fancy colours now! */
-    Gcol_iron, Gcol_gold, Gcol_silver,
+    Gcol_iron, Gcol_gold, Gcol_silver, Gcol_blood, Gcol_pus,
     /* UI customizable colours */
     Gcol_prio_low, Gcol_prio_normal, Gcol_prio_alert, Gcol_prio_warn, Gcol_prio_bug
 };
@@ -101,11 +103,16 @@ enum Gamecolour
 
 /*! \brief Identification code for damage types */
 enum Damtyp {
+    DT_NONE = -1,
     DT_PHYS = 0, DT_COLD, DT_FIRE, DT_NECRO,
     DT_ELEC, DT_HELLFIRE, DT_POISON,
-    DT_KNOCKBACK, DT_DROWNING
+    DT_KNOCKBACK, DT_DROWNING,
+    // Now the special DTs for class bonuses
+    DT_SLAY_DEMON, DT_SLAY_UNDEAD
 };
-#define DT_COUNT (1 + DT_DROWNING)
+#define LAST_DAMTYPE (DT_SLAY_UNDEAD)
+#define NUM_DAMTYPES (1 + LAST_DAMTYPE)
+extern char const *damtype_names[NUM_DAMTYPES];
 
 /*! \brief Identification code for player actions */
 enum Game_cmd {
@@ -113,13 +120,31 @@ 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, COMBO_BLADE_DANCE, COMBO_CHARGE, COMBO_FLYING_LEAP
+};
+#define LAST_COMMAND (COMBO_FLYING_LEAP)
+#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
@@ -131,7 +156,7 @@ enum Death {
     DEATH_LASH, DEATH_RIBBONS
 };
 
-/*! \brief Fell powers that might influence the Princess's fate
+/*! \brief Fell powers from forbidden places
  */
 enum Fell_powers {
     FePo_iron,
@@ -142,10 +167,12 @@ enum Fell_powers {
 
 #define TOTAL_FELL_POWERS (1 + FePo_flesh)
 
-#define RESIST_MASK_TEMPORARY   0x0000FFFFu
-#define RESIST_MASK_PERM_EQUIP  0xFFFF0000u
-#define RESIST_RING     0x00010000u
-#define RESIST_ARMOUR   0x00020000u
+#define BONUS_MASK_TEMPORARY   0x0000FFFFu
+#define BONUS_MASK_PERM_EQUIP  0x7FFF0000u
+#define BONUS_MASK_PERM_SKILL  0x80000000u
+#define BONUS_RING     0x00010000u
+#define BONUS_ARMOUR   0x00020000u
+#define BONUS_WEAPON   0x00040000u
 
 /*! \brief Represent an in-game action by the player
  *
@@ -155,23 +182,99 @@ struct Action
 {
     Game_cmd cmd;
     uint32_t details[8];
+    bool operator ==(Action const& right)
+    {
+        if (cmd != right.cmd) return false;
+        for (int i = 0; i < 8; ++i)
+        {
+            if (details[i] != right.details[i])
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+    bool operator !=(Action const& right)
+    {
+        if (cmd != right.cmd) return true;
+        for (int i = 0; i < 8; ++i)
+        {
+            if (details[i] != right.details[i])
+            {
+                return true;
+            }
+        }
+        return false;
+    }
 };
 
-
 class Level;
 class Level_key;
 class Permobj;
 class Permon;
 class Obj;
+typedef uint32_t Obj_handle;
 class Mon;
+typedef uint32_t Mon_handle;
+
+enum Decal_tag
+{
+    NO_DECAL = -1,
+    Decal_blood,
+    Decal_ichor,
+    Decal_slime,
+    Decal_pus
+};
+#define MAX_DECAL (Decal_pus)
+#define NUM_DECALS (MAX_DECAL + 1)
 
 #define NO_POBJ (-1)
 #define NO_PMON (-1)
-#define NO_OBJ (-1)
-#define NO_MON (-1)
+extern const Obj_handle NO_OBJ;
+extern const Mon_handle NO_MON;
 #define NO_REGION 0xffffffffu
 
+struct Inferno_detail
+{
+    bool by_you;
+    Mon_handle caster;
+    int dice_count;
+    int dice_sides;
+    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) { }
+};
+
+extern bool game_finished;
+extern int game_tick;
+extern bool wizard_mode;
+
 #endif
 
-/* victrix-abyssi.hh */
+/* core.hh */
 // vim:cindent:ts=8:sw=4:expandtab
diff --git a/deeds.cc b/deeds.cc
new file mode 100644 (file)
index 0000000..4206e3b
--- /dev/null
+++ b/deeds.cc
@@ -0,0 +1,347 @@
+/*! \file deeds.cc
+ *  \brief Player-character action implementations
+ */
+
+/* 
+ * 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 "victrix-abyssi.hh"
+#include "combat.hh"
+#include "objects.hh"
+#include "player.hh"
+#include <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <deque>
+
+static Action_cost deed_walk(Action const *act)
+{
+    Offset step = { (int32_t) act->details[0], (int32_t) act->details[1] };
+    return move_player(step);
+}
+
+static Action_cost deed_stand_still(Action const *act)
+{
+    return Cost_std;
+}
+
+static Action_cost deed_ascend(Action const *act)
+{
+    Terrain t = lvl.terrain_at(u.pos);
+    if (terrain_props[t].flags & TFLAG_portal)
+    {
+        if (terrain_props[t].flags & TFLAG_ascend)
+        {
+            notify_inert_portal();
+        }
+        else
+        {
+            leave_level();
+            make_new_level();
+        }
+    }
+    else
+    {
+        if (terrain_props[t].flags & TFLAG_ascend)
+        {
+            notify_ascent_blocked();
+        }
+        else
+        {
+            debug_ascend_non_stairs();
+        }
+    }
+    return Cost_none;
+}
+
+static Action_cost deed_descend(Action const *act)
+{
+    Terrain t = lvl.terrain_at(u.pos);
+    if (terrain_props[t].flags & TFLAG_portal)
+    {
+        if (terrain_props[t].flags & TFLAG_ascend)
+        {
+            notify_inert_portal();
+        }
+        else
+        {
+            leave_level();
+            make_new_level();
+        }
+    }
+    else
+    {
+        if (terrain_props[t].flags & TFLAG_descend)
+        {
+            leave_level();
+            make_new_level();
+        }
+        else
+        {
+            debug_descend_non_stairs();
+        }
+    }
+    return Cost_none;
+}
+
+static Action_cost deed_attack(Action const *act)
+{
+    Offset step = { (int32_t) act->details[0], (int32_t) act->details[1] };
+    return player_attack(step);
+}
+
+static Action_cost deed_get(Action const *act)
+{
+    if (lvl.obj_at(u.pos) != NO_OBJ)
+    {
+        attempt_pickup();
+        return Cost_std;
+    }
+    else
+    {
+        notify_nothing_to_get();
+        return Cost_none;
+    }
+}
+
+static Action_cost deed_drop(Action const *act)
+{
+    int slot = (int) act->details[0];
+    return drop_obj(slot);
+}
+
+static Action_cost deed_wield(Action const *act)
+{
+    int slot = (int) act->details[0];
+    if (slot == SLOT_NOTHING)
+    {
+        return player_unwield(Noise_std);
+    }
+    else
+    {
+        return player_wield(u.inventory[slot], Noise_std);
+    }
+}
+
+static Action_cost deed_armour_on(Action const *act)
+{
+    int slot = (int) act->details[0];
+    return wear_armour(u.inventory[slot]);
+}
+
+static Action_cost deed_armour_off(Action const *act)
+{
+    if (u.armour != NO_OBJ)
+    {
+        /* this actually belongs in a sensible place. */
+        int saved_armour = u.armour;
+        if (unequip_safety_check(BONUS_ARMOUR, Noise_std) == You_pass)
+        {
+            u.armour = NO_OBJ;
+            recalc_defence();
+            notify_armour_unequip(saved_armour);
+            return Cost_std;
+        }
+        return Cost_none;
+    }
+    else
+    {
+        debug_take_off_no_armour();
+        return Cost_none;
+    }
+}
+
+static Action_cost deed_ring_on(Action const *act)
+{
+    int slot = act->details[0];
+    return put_on_ring(u.inventory[slot]);
+}
+
+static Action_cost deed_ring_off(Action const *act)
+{
+    return remove_ring();
+}
+
+static Action_cost deed_quaff(Action const *act)
+{
+    int slot = act->details[0];
+    return quaff_potion(u.inventory[slot]);
+}
+
+static Action_cost deed_read(Action const *act)
+{
+    int slot = act->details[0];
+    return read_scroll(u.inventory[slot]);
+}
+
+static Action_cost deed_ringmagic(Action const *act)
+{
+    if (u.ring == NO_OBJ)
+    {
+        notify_magic_no_ring();
+        return Cost_none;
+    }
+    return magic_ring();
+}
+
+static Action_cost deed_zap(Action const *act)
+{
+    if (u.weapon == NO_OBJ)
+    {
+        notify_zap_no_weapon();
+        return Cost_none;
+    }
+    return zap_weapon();
+}
+
+static Action_cost deed_emanate(Action const *act)
+{
+    if (u.armour == NO_OBJ)
+    {
+        notify_emanate_no_armour();
+        return Cost_none;
+    }
+    return emanate_armour();
+}
+
+static Action_cost deed_eat(Action const *act)
+{
+    int slot = act->details[0];
+    return eat_food(u.inventory[slot]);
+}
+
+static Action_cost deed_combo_powatk(Action const *act)
+{
+    Offset step = { (int32_t) act->details[0], (int32_t) act->details[1] };
+    return player_power_attack(step);
+}
+
+static Action_cost deed_combo_blade_dance(Action const *act)
+{
+    Offset step = { (int32_t) act->details[0], (int32_t) act->details[1] };
+    Mon_handle mon = (Mon_handle) act->details[2];
+    Action_cost ac = move_player(step);
+    if (ac == Cost_std)
+    {
+        Mon const *mptr = mon_snap(mon);
+        Offset atkstep = mptr->pos - u.pos;
+        player_attack(atkstep);
+    }
+    return ac;
+}
+
+static Action_cost deed_combo_charge(Action const *act)
+{
+    Offset step = { (int32_t) act->details[0], (int32_t) act->details[1] };
+    int power = std::min((int) act->details[2], 4);
+    return player_charge(step, power);
+}
+
+static Action_cost deed_combo_flying_leap(Action const *act)
+{
+    Offset step = { (int32_t) act->details[0], (int32_t) act->details[1] };
+    notify_flying_leap();
+    reloc_player(u.pos + step);
+    return Cost_std;
+}
+
+static Action_cost deed_wiz_descend(Action const *act)
+{
+    if (wizard_mode)
+    {
+        leave_level();
+        make_new_level();
+    }
+    else
+    {
+        debug_wizmode_violation();
+    }
+    return Cost_none;
+}
+
+static Action_cost deed_wiz_levup(Action const *act)
+{
+    if (wizard_mode)
+    {
+        if (lev_threshold(u.level) != INT_MAX)
+        {
+            gain_experience((lev_threshold(u.level) - u.experience) + 1);
+        }
+    }
+    else
+    {
+        debug_wizmode_violation();
+    }
+    return Cost_none;
+}
+
+static Action_cost deed_quit(Action const *act)
+{
+    game_finished = 1;
+    return Cost_none;
+}
+
+static Action_cost deed_save(Action const *act)
+{
+    game_finished = 1;
+    save_game();
+    return Cost_none;
+}
+
+Deed_func deed_funcs[NUM_COMMANDS] = 
+{
+    deed_walk,
+    deed_stand_still,
+    deed_ascend,
+    deed_descend,
+    deed_attack,
+    deed_get,
+    deed_drop,
+    deed_wield,
+    deed_armour_on,
+    deed_armour_off,
+    deed_ring_on,
+    deed_ring_off,
+    deed_quaff,
+    deed_read,
+    nullptr, // deed_throw
+    deed_eat,
+    deed_emanate,
+    deed_zap,
+    deed_ringmagic,
+    nullptr, // deed_use_skill
+    nullptr, // deed_alloc_skill
+    deed_save,
+    deed_quit,
+    deed_wiz_levup,
+    deed_wiz_descend,
+    deed_combo_powatk,
+    deed_combo_blade_dance,
+    deed_combo_charge,
+    deed_combo_flying_leap,
+};
+
+/* deeds.cc */
+// vim:cindent:expandtab
index f81e68e..83c401b 100644 (file)
@@ -45,8 +45,8 @@
 #define DISP_NC_SIDE (DISP_NC_RADIUS * 2 + 1)
 
 /* Prototypes for static funcs */
-static cchar_t const *object_char(int object_id);
-static cchar_t const *monster_char(int monster_id);
+static cchar_t const *object_char(int po_ref);
+static cchar_t const *monster_char(int pm_ref);
 static cchar_t const *terrain_char(Terrain terrain_type);
 static void draw_status_line(void);
 static void draw_world(void);
@@ -135,6 +135,8 @@ Attr_wrapper colour_data[1 + LAST_COLOUR] =
     { 0, Gcol_cyan }, /* Iron = dark cyan */
     { A_BOLD, Gcol_brown }, /* Gold = yellow */
     { A_BOLD, Gcol_l_grey }, /* Silver = white */
+    { 0, Gcol_red }, /* Blood = red */
+    { A_BOLD, Gcol_yellow }, /* Pus = yellow */
     /* UI customizable colours */
     { A_BOLD, Gcol_d_grey },
     { 0, Gcol_l_grey },
@@ -143,17 +145,25 @@ Attr_wrapper colour_data[1 + LAST_COLOUR] =
     { A_BOLD, Gcol_blue }
 };
 
+/*! \brief Game colours for decals */
+Gamecolour decal_colours[NUM_DECALS] =
+{
+    Gcol_red,
+    Gcol_l_blue,
+    Gcol_yellow
+};
+
 /*! \brief ncursesw objects for drawing terrain */
 cchar_t terrain_tiles[NUM_TERRAINS];
 
 /*! \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;
@@ -168,7 +178,7 @@ cchar_t const *back_buffer[DISP_HEIGHT][DISP_WIDTH];
 cchar_t const *front_buffer[DISP_HEIGHT][DISP_WIDTH];
 
 /*! \brief Printable English-language names for damage types */
-char const *damtype_names[DT_COUNT] = {
+char const *damtype_names[NUM_DAMTYPES] = {
     "physical damage",
     "cold",
     "fire",
@@ -180,16 +190,76 @@ 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 grim abyssal\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;
+    }
+}
+
+static char const *tips[] = {
+    "Pay attention to your hit points.",
+    "Magic rings never wear out; don't bother carrying duplicates.",
+    "Demon hunters cannot use demonic equipment; it is unclean.",
+    "Chasms, lava, and water can all be crossed using suitable gear.",
+    "Dying with a healing potion in your inventory is embarrassing.",
+    "Fire scrolls are a powerful weapon if you resist fire.",
+    "Corpses are not a food source.",
+    "Corpses blessed by Death do not rise again to trouble the living.",
+    "The PRNG is a dispassionate algorithm, incapable of love or hate.",
+    "Life is not fair, and neither is Obumbrata et Velata.",
+    "Most weapons and armour are consumable resources.",
+    "More tips wanted. Send suggestions to the author, please."
+};
+
+int tip_pool_size = (sizeof tips) / (sizeof tips[0]);
+
+static void draw_mainmenu_tip(void)
+{
+    char const *tip = tips[zero_die(tip_pool_size)];
+    mvwprintw(fullscreen_window, 19, 1, "\n");
+    mvwprintw(fullscreen_window, 19, 31, "Tip of the moment:\n");
+    mvwprintw(fullscreen_window, 21, 1, "\n");
+    mvwprintw(fullscreen_window, 21, (40 - strlen(tip) / 2), "%s\n", tip);
+}
+
 /*! \brief Draw the main menu. */
 static void draw_main_menu(void)
 {
     wclear(fullscreen_window);
-    mvwprintw(fullscreen_window, 1, 23, "----====< Victrix Abyssi >====----\n");
-    mvwprintw(fullscreen_window, 3, 25,  "A roguelike game by Martin Read\n");
+    mvwprintw(fullscreen_window, 1, 20, "----====< Obumbrata et Velata >====----\n");
+    mvwprintw(fullscreen_window, 5, 24, "A roguelike game by Martin Read\n");
     mvwprintw(fullscreen_window, 10, 25, "S)tart new game\n");
     mvwprintw(fullscreen_window, 11, 25, "R)esume existing game\n");
     mvwprintw(fullscreen_window, 12, 25, "Q)uit\n");
-    mvwprintw(fullscreen_window, 23, 25, "Version %d.%d.%d\n", MAJVERS, MINVERS, PATCHVERS);
+    draw_mainmenu_tip();
+    mvwprintw(fullscreen_window, 3, 33, "Version %d.%d.%d\n", MAJVERS, MINVERS, PATCHVERS);
     show_panel(fullscreen_panel);
     update_panels();
     doupdate();
@@ -220,22 +290,22 @@ static cchar_t const *terrain_char(Terrain terrain_type)
 
 /*! \brief Get pointer to cchar_t object for specified permon
  *
- * \param monster_id Specified permon
+ * \param pm_ref Specified permon
  * \return pointer to the specified permon's tile
  */
-static cchar_t const *monster_char(int monster_id)
+static cchar_t const *monster_char(int pm_ref)
 {
-    return permon_tiles + monster_id;
+    return permon_tiles + pm_ref;
 }
 
 /*! \brief Get pointer to cchar_t object for specified permobj
  *
- * \param object_id Specified permobj
+ * \param po_ref Specified permobj
  * \return pointer to the specified permobj's tile
  */
-static cchar_t const *object_char(int object_id)
+static cchar_t const *object_char(int po_ref)
 {
-    return permobj_tiles + object_id;
+    return permobj_tiles + po_ref;
 }
 
 /*! \brief Repopulate the back buffer and set the hard redraw flag */
@@ -278,8 +348,8 @@ void newsym(Coord c)
     }
     else
     {
-        int obj = lvl.obj_at(c);
-        int mon = lvl.mon_at(c);
+        Obj_handle obj = lvl.obj_at(c);
+        Mon_handle mon = lvl.mon_at(c);
         Terrain terr = lvl.terrain_at(c);
         uint32_t flags = lvl.flags_at(c);
         if (c == u.pos)
@@ -288,13 +358,13 @@ void newsym(Coord c)
         }
         else if ((!show_terrain) && (mon != NO_MON) && occupant_visible(c))
         {
-            back_buffer[camoff.y][camoff.x] = monster_char(monsters[mon].mon_id);
+            back_buffer[camoff.y][camoff.x] = monster_char(monsters[mon].pm_ref);
         }
         else if (flags & MAPFLAG_EXPLORED)
         {
             if ((!show_terrain) && (obj != NO_OBJ))
             {
-                back_buffer[camoff.y][camoff.x] = object_char(objects[obj].obj_id);
+                back_buffer[camoff.y][camoff.x] = object_char(objects[obj].po_ref);
             }
             else
             {
@@ -333,6 +403,34 @@ static void draw_world(void)
                 {
                     mvwchgat(world_window, i, j, 1, A_BOLD, Gcol_d_grey, nullptr);
                 }
+               else if (c != u.pos)
+               {
+                   Obj_handle obj = lvl.obj_at(c);
+                   Mon_handle mon = lvl.mon_at(c);
+                   Decal_tag decal_id = lvl.decal_at(c);
+                   if (mon != NO_MON)
+                   {
+                       // FUTURE: handle colour-customized monsters
+                   }
+                   else if (obj != NO_OBJ)
+                   {
+                       Obj const *optr = obj_snap(obj);
+                       if (optr->po_ref == PO_CORPSE)
+                       {
+                           mvwchgat(world_window, i, j, 1,
+                                    colour_data[permons[optr->meta[0]].colour].attr,
+                                    colour_data[permons[optr->meta[0]].colour].cpair,
+                                    nullptr);
+                       }
+                   }
+                   else if (decal_id != NO_DECAL)
+                   {
+                       mvwchgat(world_window, i, j, 1,
+                                colour_data[decal_colours[decal_id]].attr, 
+                                colour_data[decal_colours[decal_id]].cpair, 
+                                nullptr);
+                   }
+               }
                 front_buffer[i][j] = back_buffer[i][j];
             }
         }
@@ -344,17 +442,18 @@ static void load_unicode_tiles(void)
 {
     int i;
     int j;
+    wchar_t wch[2];
     {
-        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);
+        wch[0] = L'â–‘';
+       setcchar(&blank_tile, wch,
+                colour_data[Gcol_d_grey].attr,
+                colour_data[Gcol_d_grey].cpair, 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,
@@ -363,7 +462,6 @@ static void load_unicode_tiles(void)
     }
     for (i = 0; i < NUM_TERRAINS; ++i)
     {
-        wchar_t wch[2];
         /* policy decision: for now we don't support use of combining
          * characters for terrain. */
         j = mbtowc(wch, terrain_props[i].unicode, 4);
@@ -377,7 +475,6 @@ static void load_unicode_tiles(void)
     }
     for (i = 0; i < NUM_OF_PERMOBJS; ++i)
     {
-        wchar_t wch[2];
         /* policy decision: for now we don't support use of combining
          * characters for items. */
         j = mbtowc(wch, permobjs[i].unicode, 4);
@@ -395,17 +492,16 @@ 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,
+            colour_data[Gcol_d_grey].attr,
+            colour_data[Gcol_d_grey].cpair, 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,
@@ -414,7 +510,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,
@@ -423,7 +518,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,
@@ -522,7 +616,7 @@ int launch_user_interface(void)
         char const *term = getenv("TERM");
         if (term && (strstr(term, "xterm") || strstr (term, "rxvt")))
         {
-            fputs("\033]0;Victrix Abyssi 1.0 Alpha 1\007", stdout);
+            fputs("\033]0;Obumbrata et Velata\007", stdout);
             fflush(stdout);
         }
     }
@@ -537,6 +631,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();
@@ -667,7 +763,13 @@ void print_msg(char const *fmt, ...)
 void print_msg(Msg_prio prio, char const *fmt, ...)
 {
     va_list ap;
+    char *s;
+    int i;
+    int j;
     va_start(ap, fmt);
+    s = (char *) malloc(512);
+    i = vasprintf(&s, fmt, ap);
+    va_end(ap);
     switch (prio)
     {
     case Msg_prio::Low:
@@ -686,15 +788,17 @@ void print_msg(Msg_prio prio, char const *fmt, ...)
         wattr_set(message_window, colour_data[Gcol_prio_bug].attr, colour_data[Gcol_prio_bug].cpair, nullptr);
         break;
     }
-    vw_printw(message_window, fmt, ap);
-    wattr_set(message_window, colour_data[Gcol_prio_normal].attr, colour_data[Gcol_prio_normal].cpair, nullptr);
-    va_end(ap);
+    if (j > 50) 
+    {
+       /* TODO catch this */
+    }
+    waddstr(message_window, s);
 #ifdef DEBUG_TO_STDERR
-    va_start(ap, fmt);
-    vfprintf(stderr, fmt, ap);
-    va_end(ap);
+    fwrite(s, 1, j, stderr);
 #endif
+    wattr_set(message_window, colour_data[Gcol_prio_normal].attr, colour_data[Gcol_prio_normal].cpair, nullptr);
     display_update();
+    free(s);
 }
 
 /*! \brief Set a message and whether 'nothing' should be listed in inventory
@@ -759,8 +863,9 @@ static void update_inv(enum poclass_num filter)
         }
         else
         {
+           Obj const *optr = obj_snap(u.inventory[i]);
             if ((filter == POCLASS_NONE) ||
-                 (permobjs[objects[u.inventory[i]].obj_id].poclass == filter))
+                 (permobjs[optr->po_ref].poclass == filter))
             {
                 wattr_set(inventory_window, colour_data[Gcol_l_grey].attr, colour_data[Gcol_l_grey].cpair, nullptr);
             }
@@ -800,7 +905,7 @@ static int inv_select(enum poclass_num filter, char const *action, int accept_bl
 
     for (i = 0; i < 19; i++)
     {
-        if ((u.inventory[i] != NO_OBJ) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[i]].obj_id].poclass == filter)))
+        if ((u.inventory[i] != NO_OBJ) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[i]].po_ref].poclass == filter)))
         {
             items++;
         }
@@ -859,7 +964,7 @@ tryagain:
          * a strict superset of ASCII. IF we're not, the following code may
          * break. */
         selection = ch - 'a';
-        if ((u.inventory[selection] != NO_OBJ) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[selection]].obj_id].poclass == filter)))
+        if ((u.inventory[selection] != NO_OBJ) && ((filter == POCLASS_NONE) || (permobjs[objects[u.inventory[selection]].po_ref].poclass == filter)))
         {
             hide_inv();
             return selection;
@@ -927,6 +1032,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");
@@ -1229,7 +1335,7 @@ void get_player_action(Action *act)
             {
                 print_msg("You are not wearing a ring.\n");
             }
-            else if (ring_removal_unsafe(Noise_std) == You_pass)
+            else if (unequip_safety_check(BONUS_RING, Noise_std) == You_pass)
             {
                 act->cmd = REMOVE_RING;
                 return;
@@ -1251,32 +1357,30 @@ void get_player_action(Action *act)
             break;
         case '<':
             {
-                Terrain t = lvl.terrain_at(u.pos);
-                if (terrain_props[t].flags & TFLAG_ascend)
-                {
-                    act->cmd = GO_UP_STAIRS;
-                    return;
+               Terrain t = lvl.terrain_at(u.pos);
+               if (terrain_props[t].flags & (TFLAG_portal | TFLAG_ascend))
+               {
+                   act->cmd = GO_UP_STAIRS;
+                   return;
                 }
                 else
                 {
-                    print_msg("There are no stairs here.\n");
+                    print_msg("There are no portals or upward stairs here.\n");
                 }
-                // TODO accept this keystroke as "enter portal" too
             }
             break;
         case '>':
             {
                 Terrain t = lvl.terrain_at(u.pos);
-                if (terrain_props[t].flags & TFLAG_descend)
+                if (terrain_props[t].flags & (TFLAG_descend | TFLAG_portal))
                 {
                     act->cmd = GO_DOWN_STAIRS;
                     return;
                 }
                 else
                 {
-                    print_msg("There are no stairs here.\n");
+                    print_msg("There are no portals or downward stairs here.\n");
                 }
-                // TODO accept this keystroke as "enter portal" too
             }
             break;
         case '5':
@@ -1297,6 +1401,16 @@ void get_player_action(Action *act)
                 return;
             }
             break;
+       case '\\':
+           print_msg("You have the following skills:\n");
+           for (int i = 0; i < NUM_SKILLS; ++i)
+           {
+               if (u.known_skill[i])
+               {
+                   print_msg("   %s\n", skill_props[i].name);
+               }
+           }
+           break;
 #ifdef DEBUG_MAPDUMP
         case 'M':
             {
@@ -1340,8 +1454,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;
 }
@@ -1365,8 +1502,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;
 }
 
@@ -1391,6 +1530,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");
@@ -1429,7 +1569,8 @@ void print_help(void)
     print_msg("e   eat something edible\n");
     print_msg("g   pick up an item (also 0 or comma)\n");
     print_msg("d   drop an item\n");
-    print_msg(">   go down stairs\n");
+    print_msg("<   go down stairs or enter a magic portal\n");
+    print_msg(">   go down stairs or enter a magic portal\n");
     print_msg("5   do nothing (wait until next action)\n");
     print_msg(".   do nothing (wait until next action)\n");
     print_msg("\nPress any key to continue...\n");
@@ -1438,15 +1579,14 @@ void print_help(void)
     print_msg("z   activate your weapon's magical power (if any)\n");
     print_msg("m   activate your ring's magical power (if any)\n");
     print_msg("E   activate your armour's magical power (if any)\n");
-    print_msg("\nPress any key to continue...\n");
-    wgetch(message_window);
+    print_msg("\n");
     print_msg("OTHER COMMANDS\n");
     print_msg("S   save and exit\n");
     print_msg("X   quit without saving\n");
     print_msg("i   show your inventory\n");
     print_msg("I   examine an item you are carrying\n");
     print_msg("#   show underlying terrain of occupied squares\n");
-    print_msg("\\   list all recognised items\n");
+    print_msg("\\   list your character's known skills \n");
     print_msg("@   dump your character's details to <name>.dump\n");
     print_msg("?   print this message\n");
     print_msg("\nPress any key to continue...\n");
@@ -1454,7 +1594,7 @@ void print_help(void)
     print_msg("SYMBOLS\n");
     print_msg("@   you\n");
     print_msg(".   floor\n");
-    print_msg(">   stairs down\n");
+    print_msg("☊   magic portal\n");
     print_msg("#   wall\n");
     print_msg("+   a door\n");
     print_msg(")   a weapon\n");
@@ -1464,6 +1604,7 @@ void print_help(void)
     print_msg("?   a scroll\n");
     print_msg("!   a potion\n");
     print_msg("%%   some food\n");
+    print_msg("&   a corpse\n");
     print_msg("1-4 demons (smaller numbers are stronger)\n");
     print_msg("\nMost other monsters are shown as letters.\n");
     print_msg("\nThis is all the help you get. Good luck!\n");
@@ -1507,6 +1648,10 @@ static void show_game_view(void)
 static void run_main_menu(void)
 {
     bool done = false;
+#if 0
+    bool role_selected = false;
+#endif
+    Role_id role = Role_princess;
     char name[16];
     do
     {
@@ -1518,8 +1663,12 @@ static void run_main_menu(void)
             mvwprintw(fullscreen_window, 10, 1, "\n");
             mvwprintw(fullscreen_window, 11, 1, "\n");
             mvwprintw(fullscreen_window, 12, 1, "\n");
-            mvwprintw(fullscreen_window, 13, 19, "Welcome, Princess. Remind me of your name?\n");
+            mvwprintw(fullscreen_window, 13, 24, "Welcome. Remind me of thy name?\n");
             mvwprintw(fullscreen_window, 14, 1, "\n");
+           mvwprintw(fullscreen_window, 15, 1, "\n");
+           mvwprintw(fullscreen_window, 16, 1, "\n");
+           mvwprintw(fullscreen_window, 17, 1, "\n");
+           mvwprintw(fullscreen_window, 18, 1, "\n");
             wmove(fullscreen_window, 14, 30);
             update_panels();
             doupdate();
@@ -1528,11 +1677,45 @@ 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");
+           mvwprintw(fullscreen_window, 19, 1, "\n");
+           mvwprintw(fullscreen_window, 21, 1, "\n");
+#if 0
+           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);
+#endif
+            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':
@@ -1542,27 +1725,35 @@ 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':
         case 'Q':
             endwin();
             exit(0);
+       default:
+           break;
         }
+       draw_mainmenu_tip();
     } while (!done);
     if (!game_finished)
     {
@@ -1608,11 +1799,12 @@ static void examine_square(Offset o)
 {
     Coord c = u.pos + o;
     uint32_t flags;
-    int mon;
-    int obj;
+    Mon_handle mon;
+    Obj_handle obj;
     Terrain terr;
-    if ((c.y < 0) || (c.x < 0) || (c.y >= (lvl.chunks_high << CHUNK_SHIFT)) ||
-        (c.x >= (lvl.chunks_wide << CHUNK_SHIFT)))
+    if ((c.y < 0) || (c.x < 0) ||
+       (c.y >= int32_t(lvl.chunks_high << CHUNK_SHIFT)) ||
+        (c.x >= int32_t(lvl.chunks_wide << CHUNK_SHIFT)))
     {
         print_msg("That square is beyond the bounds of the level.\n");
         return;
@@ -1626,14 +1818,14 @@ static void examine_square(Offset o)
     mon = lvl.mon_at(c);
     obj = lvl.obj_at(c);
     terr = lvl.terrain_at(c);
-    print_msg("%s\n", terrain_props[terr]);
+    print_msg("%s\n", terrain_props[terr].name);
     if ((mon != NO_MON) && mon_visible(mon))
     {
-        print_msg("%s\n%s\n", permons[monsters[mon].mon_id].name, permons[monsters[mon].mon_id].description);
+        print_msg("%s\n%s\n", permons[monsters[mon].pm_ref].name, permons[monsters[mon].pm_ref].description);
     }
     if (obj != NO_OBJ)
     {
-        print_msg("%s\n%s\n", permobjs[objects[obj].obj_id].name, permobjs[objects[obj].obj_id].description);
+        print_msg("%s\n%s\n", permobjs[objects[obj].po_ref].name, permobjs[objects[obj].po_ref].description);
     }
 }
 
@@ -1686,68 +1878,34 @@ static void farlook(void)
 
 void welcome(void)
 {
-    print_msg("Welcome to the Abyss, Princess %s.\n", u.name);
+    print_msg("A welcome to you, Princess %s.\n", u.name);
     print_msg("Press '?' for help.\n");
 }
 
-void describe_object(int obj)
+void describe_object(Obj_handle obj)
 {
-    Obj *optr;
-    Permobj *poptr;
+    Obj const *optr = obj_snap(obj);
+    Permobj const *poptr = permobjs + optr->po_ref;
     print_obj_name(obj);
-    optr = objects + obj;
-    poptr = permobjs + optr->obj_id;
     print_msg("\n%s\n", poptr->description);
 }
 
-void print_obj_name(int obj)
+void print_obj_name(Obj_handle obj)
 {
-    Obj *optr;
-    Permobj *poptr;
-    optr = objects + obj;
-    poptr = permobjs + optr->obj_id;
-    if (optr->quan > 1)
-    {
-        print_msg("%d %s", optr->quan, poptr->plural);
-    }
-    else if (po_is_stackable(optr->obj_id))
-    {
-        print_msg("1 %s", poptr->name);
-    }
-    else if ((poptr->poclass == POCLASS_WEAPON) ||
-             (poptr->poclass == POCLASS_ARMOUR))
-    {
-        print_msg("a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
-    }
-    else
-    {
-        print_msg("a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
-    }
+    char *s;
+    asprint_obj_name(&s, obj);
+    print_msg("%s", s);
+    free(s);
 }
 
-void print_mon_name(int mon, int article)
+void print_mon_name(Mon_handle mon, int article)
 {
-    if (permons[monsters[mon].mon_id].name[0] == '\0')
-    {
-        print_msg("GROB THE VOID (%d)", monsters[mon].mon_id);
-        return;
-    }
-    switch (article)
-    {
-    case 0:     /* a */
-        print_msg("a%s %s", is_vowel(permons[monsters[mon].mon_id].name[0]) ? "n" : "", permons[monsters[mon].mon_id].name);
-        break;
-    case 1: /* the */
-        print_msg("the %s", permons[monsters[mon].mon_id].name);
-        break;
-    case 2: /* A */
-        print_msg("A%s %s", is_vowel(permons[monsters[mon].mon_id].name[0]) ? "n" : "", permons[monsters[mon].mon_id].name);
-        break;
-    case 3: /* The */
-        print_msg("The %s", permons[monsters[mon].mon_id].name);
-        break;
-    }
+    char *s;
+    asprint_mon_name(&s, mon, article);
+    print_msg("%s", s);
+    free(s);
 }
 
+
 /* display-nc.cc */
 // vim:cindent
index 99516db..d57e646 100644 (file)
@@ -1,5 +1,5 @@
 /*! \file display.hh
- *  \brief Display-related definitions for Victrix Abyssi
+ *  \brief Display-related definitions
  */
 
 /* Copyright 2005-2013 Martin Read
@@ -38,24 +38,26 @@ enum class Msg_prio
     Bug
 };
 
-extern void print_msg(char const *fmt, ...);
-extern void print_msg(Msg_prio prio, char const *fmt, ...);
-extern int read_input(char *buffer, int length);
-extern void print_help(void);
-extern int launch_user_interface(void);
-extern void display_update(void);
-extern int display_shutdown(void);
-extern void newsym(Coord c);
-extern void touch_back_buffer(void);
-extern void get_player_action(Action *act);
-extern Pass_fail select_dir(Offset *pstep);
-extern int getYN(char const *msg);
-extern int getyn(char const *msg);
-extern void press_enter(void);
-extern void pressanykey(void);
-extern void show_discoveries(void);
-extern void touch_one_screen(Coord c);
-extern void welcome(void);
+void print_msg(char const *fmt, ...);
+void print_msg(Msg_prio prio, char const *fmt, ...);
+int read_input(char *buffer, int length);
+void print_help(void);
+int launch_user_interface(void);
+void display_update(void);
+int display_shutdown(void);
+void newsym(Coord c);
+void touch_back_buffer(void);
+void get_player_action(Action *act);
+Pass_fail select_dir(Offset *pstep);
+int getYN(char const *msg);
+int getyn(char const *msg);
+void press_enter(void);
+void pressanykey(void);
+void show_discoveries(void);
+void touch_one_screen(Coord c);
+void welcome(void);
+void print_obj_name(Obj_handle obj);
+void print_mon_name(Mon_handle mon, int article);
 
 /* "I've changed things that need to be redisplayed" flags. */
 extern bool hard_redraw;
diff --git a/dungeon.cc b/dungeon.cc
new file mode 100644 (file)
index 0000000..e99e9f8
--- /dev/null
@@ -0,0 +1,269 @@
+/*! \file dungeon.cc
+ *  \brief "dungeon"-like level generators i.e. made of rooms
+ */
+
+/* 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 "victrix-abyssi.hh"
+#include "objects.hh"
+#include "monsters.hh"
+#include "mapgen.hh"
+
+#define DBASH_ROOMS_WIDE (3)
+#define DBASH_ROOMS_HIGH (3)
+/*! \brief Number of rooms on a "dungeonbash"-style level */
+#define DBASH_ROOMS (DBASH_ROOMS_WIDE * DBASH_ROOMS_HIGH)
+/*! \brief Maximum number of corridors on a dungeonbash-style level
+ *
+ * This value corresponds to the situation where every adjacent pair of
+ * rooms is connected. */
+#define DBASH_CORRIDORS ((DBASH_ROOMS_WIDE - 1) * DBASH_ROOMS_HIGH + \
+                         (DBASH_ROOMS_HIGH - 1) * DBASH_ROOMS_WIDE)
+/*! \brief Room generated first */
+#define DBASH_ORIGIN_ROOM (4)
+
+struct Dbash_room
+{
+    Coord inner_tl;
+    Coord inner_br;
+    Coord exit_positions[4];
+};
+
+struct Dbash_corridor
+{
+    Coord endpoints[2];
+};
+
+struct Dbash_layout_data
+{
+    Level_layout id;
+    int32_t exit_room;
+    int32_t entry_room;
+    uint16_t connectivity[DBASH_ROOMS];
+    Dbash_room rooms[DBASH_ROOMS];
+    Dbash_corridor corridors[DBASH_CORRIDORS];
+    void *next_layout_data;
+};
+
+static void excavate_normal_room(Level *l, Coord inner_tl, Coord inner_br, uint32_t region)
+{
+    Coord pos;
+    // Initial pass: make the room interior all floor.
+    for (pos.y = inner_tl.y; pos.y <= inner_br.y; ++pos.y)
+    {
+        for (pos.x = inner_tl.x; pos.x <= inner_br.x; ++pos.x)
+        {
+            l->set_terrain_at(pos, FLOOR);
+            l->set_region_at(pos, region);
+        }
+    }
+}
+
+static void draw_pillar_row(Level *l, Coord end1, Coord end2, Terrain t)
+{
+    Offset delta = end2 - end1;
+    Offset step = mysign(delta) * 2;
+}
+
+#define PILLARS_WEST  0x00000001u
+#define PILLARS_EAST  0x00000002u
+#define PILLARS_NORTH 0x00000004u
+#define PILLARS_SOUTH 0x00000008u
+#define PILLARS_INNER_MASK 0x000000f0u
+#define PILLARS_EMPTY 0u
+#define PILLARS_ALL 1u
+#define PILLARS_EW_AVENUE 2u
+#define PILLARS_NS_AVENUE 3u
+#define PILLARS_CROSSED_AVENUE 4u
+#define PILLARS_INNER_SHIFT 4u
+
+static void excavate_pillar_room(Level *l, Coord inner_tl, Coord inner_br, uint32_t region, uint32_t pillar_pattern)
+{
+    Offset delta = inner_br - inner_tl;
+    if ((delta.y | delta.x) & 1)
+    {
+        throw Obumb_levgen_excep("Awkward dimensions for pillar room");
+    }
+    excavate_normal_room(l, inner_tl, inner_br, region);
+    /* Second pass: What pattern of pillars?
+     *
+     * Low 4 bits: Do we have the "ring" pillars?
+     * Next 4 bits: Choice of interior pattern from:
+     *      0 = no interior pillars
+     *      1 = all interior pillars
+     *      2 = east-west avenue of pillars
+     *      3 = north-south avenue of pillars
+     *      4 = crossed avenues of pillars
+     */
+    Coord end1;
+    Coord end2;
+    if (pillar_pattern & PILLARS_EAST)
+    {
+        end1.y = inner_tl.y + 1;
+        end1.x = end2.x = inner_br.x - 1;
+        end2.y = inner_br.y - 1;
+        draw_pillar_row(l, end1, end2, MASONRY_WALL);
+    }
+    if (pillar_pattern & PILLARS_WEST)
+    {
+        end1.y = inner_tl.y + 1;
+        end1.x = end2.x = inner_tl.x + 1;
+        end2.y = inner_br.y - 1;
+        draw_pillar_row(l, end1, end2, MASONRY_WALL);
+    }
+    if (pillar_pattern & PILLARS_NORTH)
+    {
+        end1.y = end2.y = inner_tl.y + 1;
+        end2.x = inner_tl.x + 1;
+        end2.x = inner_br.x - 1;
+        draw_pillar_row(l, end1, end2, MASONRY_WALL);
+    }
+    if (pillar_pattern & PILLARS_SOUTH)
+    {
+        end1.y = end2.y = inner_tl.y + 1;
+        end2.x = inner_tl.x + 1;
+        end2.x = inner_br.x - 1;
+        draw_pillar_row(l, end1, end2, MASONRY_WALL);
+    }
+    switch ((pillar_pattern >> 4) & 0xf)
+    {
+    default:
+        // TODO emit debug
+    case PILLARS_EMPTY:
+        break;
+    case PILLARS_ALL:
+        end1.x = inner_tl.x;
+        end2.x = inner_tl.x;
+        for (end1.y = end2.y = inner_tl.y + 1;
+             end1.y < inner_br.y; (end1.y += 2), (end2.y += 2))
+        {
+            draw_pillar_row(l, end1, end2, MASONRY_WALL);
+        }
+        break;
+    case PILLARS_EW_AVENUE:
+        if (delta.y > 2)
+        {
+            end1.y = end2.y = (inner_tl.y + inner_br.y) / 2 - 1;
+            end1.x = inner_tl.x + 1;
+            end2.x = inner_br.x - 1;
+            draw_pillar_row(l, end1, end2, MASONRY_WALL);
+            end1.y = end2.y = (inner_tl.y + inner_br.y) / 2 + 1;
+            draw_pillar_row(l, end1, end2, MASONRY_WALL);
+        }
+        break;
+    case PILLARS_NS_AVENUE:
+        if (delta.x > 2)
+        {
+
+            end1.x = end2.x = (inner_tl.x + inner_br.x) / 2 - 1;
+            end1.y = inner_tl.y + 1;
+            end2.y = inner_br.y - 1;
+            draw_pillar_row(l, end1, end2, MASONRY_WALL);
+            end1.y = end2.y = (inner_tl.y + inner_br.y) / 2 + 1;
+            draw_pillar_row(l, end1, end2, MASONRY_WALL);
+        }
+        break;
+    case PILLARS_CROSSED_AVENUE:
+        end1.y = end2.y = (inner_tl.y + inner_br.y) / 2 - 1;
+        end1.x = inner_tl.x + 1;
+        end2.x = inner_br.x - 1;
+        draw_pillar_row(l, end1, end2, MASONRY_WALL);
+        end1.y = end2.y = (inner_tl.y + inner_br.y) / 2 + 1;
+        draw_pillar_row(l, end1, end2, MASONRY_WALL);
+        end1.x = end2.x = (inner_tl.x + inner_br.x) / 2 - 1;
+        end1.y = inner_tl.y + 1;
+        end2.y = inner_br.y - 1;
+        draw_pillar_row(l, end1, end2, MASONRY_WALL);
+        end1.y = end2.y = (inner_tl.y + inner_br.y) / 2 + 1;
+        draw_pillar_row(l, end1, end2, MASONRY_WALL);
+        break;
+    }
+}
+
+/*! \brief Excavate a dungeonbash-style level
+ *
+ * Levels in Martin's Dungeon Bash had nine rooms in a 3x3 grid. This function
+ * reproduces that structure.
+ */
+void build_level_dungeonbash(Level *l)
+{
+    Coord tl;
+    Coord br;
+    int ysize; // internal y-size of room we're about to build
+    int xsize; // internal x-size of room we're about to build
+    Dbash_layout_data *layout_data = new Dbash_layout_data;
+    Dbash_room *rptr;
+    layout_data->id = LAYOUT_DUNGEONBASH;
+    l->layout_data = layout_data;
+    layout_data->entry_room = zero_die(9);
+    do
+    {
+        layout_data->exit_room = zero_die(9);
+    } while (layout_data->exit_room == layout_data->entry_room);
+    /* Go with the standard 3x3 chunk set. */
+    initialize_chunks(l, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+    /* Build the centre room. */
+    ysize = 3 + zero_die(6);
+    xsize = 3 + zero_die(6);
+    tl.y = (GUIDE_EDGE_CHUNKS / 2) - (ysize / 2);
+    br.y = tl.y + (ysize - 1);
+    tl.x = (GUIDE_EDGE_CHUNKS / 2) - (xsize / 2);
+    br.x = tl.x + (xsize - 1);
+    rptr = layout_data->rooms + DBASH_ORIGIN_ROOM;
+    rptr->inner_tl = tl;
+    rptr->inner_br = br;
+    /* Centre room is either plain, or ring pillars with crossed avenues. */
+    if (zero_die(2))
+    {
+        excavate_pillar_room(l, rptr->inner_tl, rptr->inner_br,
+                             DBASH_ORIGIN_ROOM, 0x4f);
+    }
+    else
+    {
+        excavate_normal_room(l, rptr->inner_tl, rptr->inner_br,
+                             DBASH_ORIGIN_ROOM);
+    }
+    /* Other rooms are plain for now. */
+    for (int i = 0; i < DBASH_ROOMS; ++i)
+    {
+        if (i == DBASH_ORIGIN_ROOM)
+        {
+            continue;
+        }
+    }
+}
+
+/*! \brief Excavate a "claustrophobia" level
+ *
+ * "Claustrophobia" levels
+ *
+ * \todo Implement.
+ */
+void build_level_claustrophobia(Level *l)
+{
+}
+
+/* dungeon.cc */
+// vim:cindent:ts=8:sw=4:expandtab
diff --git a/fov.cc b/fov.cc
index b5dd8bd..8b7719e 100644 (file)
--- a/fov.cc
+++ b/fov.cc
@@ -65,7 +65,7 @@ static void compute_row(Radiance *rad, int octant, int radius, double inmost_slo
  *  This function returns true if the specified coordinates are out of bounds
  *  or the terrain at the specified position is opaque.
  */
-static bool dflt_blk(Coord c)
+bool dflt_blk(Coord c)
 {
     return ((c.y <= lvl.min_y()) || (c.x <= lvl.min_x()) || (c.y >= lvl.max_y()) || (c.x >= lvl.max_x()) || (terrain_is_opaque(lvl.terrain_at(c))));
 }
@@ -116,13 +116,6 @@ static inline double centre_slope(int rdl, int trn)
     return ((double) trn) / ((double) rdl);
 }
 
-/*! \brief Reset the affected flags of a Radiance object
- */
-void clear_radiance(Radiance *rad)
-{
-    memset(&(rad->affected), '\0', sizeof rad->affected);
-}
-
 /*! \brief Start computing one octant of a Radiance
  */
 static inline void compute_octant(Radiance *rad, int octant)
@@ -241,54 +234,47 @@ static void compute_row(Radiance *rad, int octant, int radius, double inmost_slo
     }
 }
 
-void compute_radiance(Radiance *rad)
+void Radiance::compute(void)
 {
     int oct;
     /* Compute the eight octants in order. */
     for (oct = 0; oct < 8; ++oct)
     {
-        compute_octant(rad, oct);
+        compute_octant(this, oct);
     }
     /* Mark the centre as (un)affected according to the Radiance's setup. */
-    if (rad->exclude_centre)
-    {
-        rad->affected[MAX_FOV_RADIUS][MAX_FOV_RADIUS] = false;
-    }
-    else
-    {
-        rad->affected[MAX_FOV_RADIUS][MAX_FOV_RADIUS] = true;
-    }
+    affected[MAX_FOV_RADIUS][MAX_FOV_RADIUS] = !exclude_centre;
 }
 
-void resolve_radiance(Radiance *rad)
+void Radiance::resolve(void)
 {
     Coord c;
     int i;
     int j;
     int k;
 
-    switch (rad->order)
+    switch (order)
     {
     case Reo_ascending:
-        for ((i = MAX_FOV_RADIUS - rad->radius), (c.y = rad->centre.y - rad->radius); c.y <= rad->centre.y + rad->radius; ++c.y, ++i)
+        for ((i = MAX_FOV_RADIUS - radius), (c.y = centre.y - radius); c.y <= centre.y + radius; ++c.y, ++i)
         {
-            for ((j = MAX_FOV_RADIUS - rad->radius), (c.x = rad->centre.x - rad->radius); c.x <= rad->centre.x + rad->radius; ++c.x, ++j)
+            for ((j = MAX_FOV_RADIUS - radius), (c.x = centre.x - radius); c.x <= centre.x + radius; ++c.x, ++j)
             {
-                if (rad->affected[i][j])
+                if (affected[i][j])
                 {
-                    rad->effect_fun(c, rad->pvt);
+                    effect_fun(c, pvt);
                 }
             }
         }
         break;
     case Reo_spiral_out:
-        for (i = 0, j = 0, k = 0; i >= -(rad->radius); )
+        for (i = 0, j = 0, k = 0; i >= -(radius); )
         {
-            c.y = rad->centre.y + i;
-            c.x = rad->centre.x + j;
-            if (rad->affected[MAX_FOV_RADIUS + i][MAX_FOV_RADIUS + j])
+            c.y = centre.y + i;
+            c.x = centre.x + j;
+            if (affected[MAX_FOV_RADIUS + i][MAX_FOV_RADIUS + j])
             {
-                rad->effect_fun(c, rad->pvt);
+                effect_fun(c, pvt);
             }
             if ((i == 0) && (j == 0))
             {
@@ -344,13 +330,13 @@ void resolve_radiance(Radiance *rad)
         }
         break;
     case Reo_spiral_in:
-        for (i = -(rad->radius), j = -(rad->radius), k = rad->radius; k >= 0; )
+        for (i = -(radius), j = -(radius), k = radius; k >= 0; )
         {
-            c.y = rad->centre.y + i;
-            c.x = rad->centre.x + j;
-            if (rad->affected[MAX_FOV_RADIUS + i][MAX_FOV_RADIUS + j])
+            c.y = centre.y + i;
+            c.x = centre.x + j;
+            if (affected[MAX_FOV_RADIUS + i][MAX_FOV_RADIUS + j])
             {
-                rad->effect_fun(c, rad->pvt);
+                effect_fun(c, pvt);
             }
             if ((i == 0) && (j == 0))
             {
@@ -406,7 +392,7 @@ void resolve_radiance(Radiance *rad)
         break;
 
     default:
-        debug_unimplemented_radiance_order(rad->order);
+        debug_unimplemented_radiance_order(order);
         abort();
     }
 }
@@ -415,7 +401,7 @@ Radiance player_fov;
 
 void compute_fov(void)
 {
-    clear_radiance(&player_fov);
+    player_fov.clear();
     player_fov.centre = u.pos;
     player_fov.radius = MAX_FOV_RADIUS;
     player_fov.order = Reo_ascending;
@@ -423,8 +409,8 @@ void compute_fov(void)
     player_fov.opaque_fun = dflt_blk;
     player_fov.effect_fun = mark_explored;
     player_fov.pvt = nullptr;
-    compute_radiance(&player_fov);
-    resolve_radiance(&player_fov);
+    player_fov.compute();
+    player_fov.resolve();
 }
 
 bool tile_visible(Coord c)
diff --git a/fov.hh b/fov.hh
index 15f40ad..a8d096f 100644 (file)
--- a/fov.hh
+++ b/fov.hh
@@ -2,7 +2,7 @@
  *  \brief field-of-view header
  */
 
-/* Copyright 2013 Martin Read
+/* Copyright Â© 2013-2014 Martin Read
  * 
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -29,6 +29,8 @@
 #ifndef FOV_HH
 #define FOV_HH
 
+#include <string.h>
+
 #define MAX_FOV_RADIUS 10
 #define FOV_SIDE (2 * MAX_FOV_RADIUS + 1)
 
@@ -52,7 +54,7 @@ typedef enum rad_eval_order Rad_eval_order;
 #define Visflag_central 0x02u
 
 typedef uint8_t Vision_flags; /* 8 bits should do for now */
-class radiance_data
+class Radiance
 {
 public:
     Vision_flags affected[FOV_SIDE][FOV_SIDE];
@@ -63,6 +65,8 @@ public:
     void *pvt;
     bool (*opaque_fun)(Coord c);
     bool (*effect_fun)(Coord c, void *pvt);
+    /* member functions only past this point please */
+    void clear(void) { memset(affected, '\0', sizeof affected); }
     bool test(Coord c) const {
         if ((c.y >= 0) && (c.x >= 0) && (c.y < FOV_SIDE) && (c.x < FOV_SIDE))
         {
@@ -70,19 +74,18 @@ public:
         }
         return false;
     }
+    void compute(void);
+    void resolve(void);
 };
 
-typedef struct radiance_data Radiance;
-
-extern void clear_radiance(Radiance *rad);
-extern void compute_radiance(Radiance *rad);
-extern void resolve_radiance(Radiance *rad);
-extern void compute_fov(void);
-extern bool tile_visible(Coord c);
-extern bool occupant_visible(Coord c);
 extern Radiance player_fov;
 
+void compute_fov(void);
+bool tile_visible(Coord c);
+bool occupant_visible(Coord c);
+bool dflt_blk(Coord c);
+
 #endif
 
-/* fov.h */
+/* fov.hh */
 // vim:cindent
diff --git a/log.cc b/log.cc
index 9cde6e3..708746d 100644 (file)
--- a/log.cc
+++ b/log.cc
@@ -1,5 +1,5 @@
 /* \file log.cc
- * \brief File I/O functions for Victrix Abyssi
+ * \brief File I/O functions for Obumbrata et Velata
  */
 
 /* Copyright 2014 Martin Read
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "victrix-abyssi.hh"
+#include "core.hh"
 #include "objects.hh"
 #include "monsters.hh"
 #include "player.hh"
 #include "map.hh"
+#include "mapgen.hh"
+#include "notify.hh"
 #include "util.h"
 #include <stdio.h>
 #include <stdlib.h>
@@ -48,118 +50,535 @@ static void rebuild_mapmons(void);
 int datadir_fd;
 int confdir_fd;
 
-/*! \brief Read configuration files.
- *
- * \todo Implement configuration files.
- */
-void load_config(void)
+/*! \brief call system(cmd) and throw an exception on failure */
+void wrapped_system(char const *cmd)
 {
-    int i;
-    i = setup_dirs("com.blackswordsonics", "victrix-abyssi", &datadir_fd, &confdir_fd);
-    if (i < 0)
+    int i = system(cmd);
+    if (i != 0)
     {
-        perror("victrix-abyssi: couldn't access configuration directories");
-        exit(1);
+        throw Obumb_sysexcep(errno);
     }
-    // TODO write this function
 }
 
-/*! \brief Rewrite configuration files.
- *
- * \todo Implement configuration files.
- */
-int rewrite_config(void)
+/*! \brief call fread(args) and throw an exception on failure */
+void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *fp)
 {
-    // TODO write this function
-    return 0;
+    size_t i = fread(buf, size, nmemb, fp);
+    if (i != nmemb)
+    {
+        if (ferror(fp))
+        {
+            throw Obumb_sysexcep(errno);
+        }
+        else if (feof(fp))
+        {
+            throw Obumb_dataexcep("Unexpected end of file");
+        }
+        else
+        {
+            throw Obumb_sysexcep(errno);
+        }
+    }
 }
 
-/*! \brief Get a list of extant saved games and character names
- *
- * Fill in the map<string,string> container *dest using character names
- * as keys and filenames as values.
- *
- * \param dest Pointer to map<string,string> container to fill with data
- * \return Number of charname/filename pairs written to container
- * \todo Basic implementation
- * \todo Deal gracefully with a savefile going away before we open it
- */
-int get_savefile_list(std::map<std::string, std::string> *dest)
+/*! \brief Read an unsigned 32-bit integer in network byte order */
+static inline void deserialize_uint32(FILE *fp, uint32_t *i)
 {
-    // TODO write this function
-    return 0;
+    wrapped_fread(i, 1, sizeof *i, fp);
+    *i = ntohl(*i);
 }
 
-/*! \brief Record the details of the Princess's death to the log
- */
-void log_death(Death d, char const *what)
+/*! \brief Write an unsigned 32-bit integer in network byte order */
+static inline void serialize_uint32(FILE *fp, uint32_t i)
 {
-    FILE *fp;
-    int fd;
-    fd = openat(datadir_fd, "victrix-abyssi.log", O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
-    if (fd == -1)
+    i = htonl(i);
+    fwrite(&i, 1, sizeof i, fp);
+}
+
+/*! \brief Read an signed 32-bit integer in network byte order */
+static inline void deserialize_int32(FILE *fp, int32_t *i)
+{
+    int32_t tmp;
+    wrapped_fread(&tmp, 1, sizeof tmp, fp);
+    *i = (int32_t) ntohl(tmp);
+}
+
+/*! \brief Write a signed 32-bit integer in network byte order */
+static inline void serialize_int32(FILE *fp, int32_t i)
+{
+    int32_t tmp = htonl(i);
+    fwrite(&tmp, 1, sizeof tmp, fp);
+}
+
+/*! \brief Read an int in network byte order */
+static inline void deserialize_int(FILE *fp, int *i)
+{
+    int32_t tmp;
+    wrapped_fread(&tmp, 1, sizeof tmp, fp);
+    *i = (int32_t) ntohl(tmp);
+}
+
+/*! \brief Write an int in network byte order */
+static inline void serialize_int(FILE *fp, int i)
+{
+    int32_t tmp = htonl(i);
+    fwrite(&tmp, 1, sizeof tmp, fp);
+}
+
+static inline void deserialize_bool(FILE *fp, bool *b)
+{
+    char tmp;
+    wrapped_fread(&tmp, 1, 1, fp);
+    *b = !!tmp;
+}
+
+static inline void serialize_bool(FILE *fp, bool b)
+{
+    char tmp = b;
+    fwrite(&tmp, 1, 1, fp);
+}
+
+/*! \brief Read a Coord */
+static inline void deserialize_coord(FILE *fp, Coord * c)
+{
+    deserialize_int32(fp, &(c->y));
+    deserialize_int32(fp, &(c->x));
+}
+
+/*! \brief Write a Coord */
+static inline void serialize_coord(FILE *fp, Coord const& c)
+{
+    serialize_int32(fp, c.y);
+    serialize_int32(fp, c.x);
+}
+
+/*! \brief Read an Offset */
+static inline void deserialize_offset(FILE *fp, Offset * o)
+{
+    deserialize_int32(fp, &(o->y));
+    deserialize_int32(fp, &(o->x));
+}
+
+/*! \brief Write an Offset */
+static inline void serialize_offset(FILE *fp, Coord const& o)
+{
+    serialize_int32(fp, o.y);
+    serialize_int32(fp, o.x);
+}
+
+/*! \brief Read a C-style string */
+static inline void deserialize_cstring(FILE *fp, char *str, uint32_t buffer_size)
+{
+    uint32_t i;
+    deserialize_uint32(fp, &i);
+    if (i >= buffer_size)
     {
-        return;
+        // reading garbage
+        throw(-1);
     }
-    fp = fdopen(fd, "a");
-    if (fp)
+    else
     {
-        switch (d)
+        memset(str, '\0', buffer_size);
+        wrapped_fread(str, 1, i, fp);
+    }
+}
+
+/*! \brief Write a C-style string */
+static inline void serialize_cstring(FILE *fp, char const *str, uint32_t expected_max_size)
+{
+    size_t i = strlen(str);
+    if (i >= expected_max_size)
+    {
+        // welp!
+        serialize_uint32(fp, 8);
+        fwrite("(badstr)", 1, 8, fp);
+    }
+    else
+    {
+        serialize_uint32(fp, (uint32_t) i);
+        fwrite(str, 1, i, fp);
+    }
+}
+
+/*! \brief Read a monster handle */
+static inline void deserialize_monhandle(FILE *fp, Mon_handle *m)
+{
+    uint32_t tmp;
+    wrapped_fread(&tmp, 1, sizeof tmp, fp);
+    *m = htonl(tmp);
+}
+
+/*! \brief Write a monster handle */
+static inline void serialize_monhandle(FILE *fp, Mon_handle m)
+{
+    uint32_t tmp = htonl(m);
+    fwrite(&tmp, 1, sizeof tmp, fp);
+}
+
+/*! \brief 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)
+{
+    uint32_t tmp;
+    wrapped_fread(&tmp, 1, sizeof tmp, fp);
+    *o = htonl(tmp);
+}
+
+/*! \brief Write an object handle */
+static inline void serialize_objhandle(FILE *fp, Obj_handle o)
+{
+    uint32_t tmp = htonl(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));
+    deserialize_int(fp, &(mon->hpmax));
+    deserialize_int(fp, &(mon->hpcur));
+    deserialize_int(fp, &(mon->mtohit));
+    deserialize_int(fp, &(mon->rtohit));
+    deserialize_int(fp, &(mon->defence));
+    deserialize_int(fp, &(mon->mdam));
+    deserialize_int(fp, &(mon->rdam));
+    deserialize_int(fp, &(mon->next_summon));
+}
+
+/*! \brief Write a monster */
+static void serialize_monster(FILE *fp, Mon const& mon)
+{
+    serialize_monhandle(fp, mon.self);
+    serialize_int(fp, mon.pm_ref);
+    serialize_uint32(fp, mon.flags);
+    serialize_coord(fp, mon.pos);
+    serialize_coord(fp, mon.ai_lastpos);
+    serialize_int(fp, mon.hpmax);
+    serialize_int(fp, mon.hpcur);
+    serialize_int(fp, mon.mtohit);
+    serialize_int(fp, mon.rtohit);
+    serialize_int(fp, mon.defence);
+    serialize_int(fp, mon.mdam);
+    serialize_int(fp, mon.rdam);
+    serialize_int(fp, mon.next_summon);
+}
+
+/*! \brief Read an object */
+static void deserialize_object(FILE *fp, Obj *obj)
+{
+    deserialize_objhandle(fp, &(obj->self));
+    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));
+    for (int i = 0; i < 4; ++i)
+    {
+        deserialize_uint32(fp, &(obj->meta[i]));
+    }
+    deserialize_int(fp, &(obj->durability));
+}
+
+/*! \brief Write an object */
+static void serialize_object(FILE *fp, Obj const& obj)
+{
+    serialize_objhandle(fp, obj.self);
+    serialize_int(fp, obj.po_ref);
+    serialize_uint32(fp, obj.flags);
+    serialize_coord(fp, obj.pos);
+    serialize_int(fp, obj.quan);
+    for (int i = 0; i < 4; ++i)
+    {
+        serialize_uint32(fp, obj.meta[i]);
+    }
+    serialize_int(fp, obj.durability);
+}
+
+/*! \brief Read the player */
+static void deserialize_player(FILE *fp, Player *player)
+{
+    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));
+    deserialize_int(fp, &(player->adam));
+    deserialize_int(fp, &(player->hpmax));
+    deserialize_int(fp, &(player->hpcur));
+    deserialize_int(fp, &(player->food));
+    deserialize_int(fp, &(player->experience));
+    deserialize_int(fp, &(player->level));
+    deserialize_int(fp, &(player->defence));
+    deserialize_int(fp, &(player->protection));
+    deserialize_int(fp, &(player->leadfoot));
+    deserialize_int(fp, &(player->withering));
+    deserialize_int(fp, &(player->armourmelt));
+    deserialize_int(fp, &(player->speed));
+    for (int i = 0; i < NUM_DAMTYPES; ++i)
+    {
+        deserialize_uint32(fp, &(player->resistances[i]));
+        deserialize_uint32(fp, &(player->damage_amp[i]));
+    }
+    deserialize_uint32(fp, &(player->passwater));
+    deserialize_uint32(fp, &(player->flight));
+    deserialize_uint32(fp, &(player->protective_gear));
+    for (int i = 0; i < NUM_SKILLS; ++i)
+    {
+        deserialize_bool(fp, &(player->known_skill[i]));
+    }
+    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]));
+    }
+}
+
+/*! \brief Write the player */
+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);
+    serialize_int(fp, player.adam);
+    serialize_int(fp, player.hpmax);
+    serialize_int(fp, player.hpcur);
+    serialize_int(fp, player.food);
+    serialize_int(fp, player.experience);
+    serialize_int(fp, player.level);
+    serialize_int(fp, player.defence);
+    serialize_int(fp, player.protection);
+    serialize_int(fp, player.leadfoot);
+    serialize_int(fp, player.withering);
+    serialize_int(fp, player.armourmelt);
+    serialize_int(fp, player.speed);
+    for (int i = 0; i < NUM_DAMTYPES; ++i)
+    {
+        serialize_uint32(fp, player.resistances[i]);
+        serialize_uint32(fp, player.damage_amp[i]);
+    }
+    serialize_uint32(fp, player.passwater);
+    serialize_uint32(fp, player.flight);
+    serialize_uint32(fp, player.protective_gear);
+    for (int i = 0; i < NUM_SKILLS; ++i)
+    {
+        serialize_bool(fp, player.known_skill[i]);
+    }
+    for (int i = 0; i < INVENTORY_SIZE; ++i)
+    {
+        serialize_objhandle(fp, player.inventory[i]);
+    }
+    serialize_objhandle(fp, player.weapon);
+    serialize_objhandle(fp, player.armour);
+    serialize_objhandle(fp, player.ring);
+    for (int i = 0; i < TOTAL_FELL_POWERS; ++i)
+    {
+        serialize_int(fp, player.sympathy[i]);
+    }
+}
+
+/*! \brief Read a Chunk from a FILE.  */
+void deserialize_chunk(FILE *fp, Chunk *c)
+{
+    static uint32_t deserializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t deserializable_flags[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t deserializable_regions[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t deserializable_decals[CHUNK_EDGE][CHUNK_EDGE];
+    int i;
+    int j;
+    wrapped_fread(deserializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    wrapped_fread(deserializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    wrapped_fread(deserializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    wrapped_fread(deserializable_decals, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    for (i = 0; i < CHUNK_EDGE; ++i)
+    {
+        for (j = 0; j < CHUNK_EDGE; ++j)
         {
-        case DEATH_KILLED:
-            fprintf(fp, "%s was killed by %s.\n", u.name, what);
-            break;
-        case DEATH_KILLED_MON:
-            fprintf(fp, "%s was killed by a nasty %s.\n", u.name, what);
-            break;
-        case DEATH_BODY:
-            fprintf(fp, "%s's heart was stopped by %s.\n", u.name, what);
-            break;
-        case DEATH_AGILITY:
-            fprintf(fp, "%s's nerves were destroyed by %s.\n", u.name, what); 
-            break;
-        case DEATH_LASH:
-            fprintf(fp, "%s tasted the lash one time too many.\n", u.name);
-            break;
-        case DEATH_RIBBONS:
-            fprintf(fp, "%s looked good in ribbons.\n", u.name);
-            break;
+            c->terrain[i][j] = (Terrain) ntohl(deserializable_terrain[i][j]);
+            c->flags[i][j] = ntohl(deserializable_flags[i][j]);
+            c->region_number[i][j] = ntohl(deserializable_regions[i][j]);
+            c->decals[i][j] = (Decal_tag) ntohl(deserializable_decals[i][j]);
+            /* objs and mons will get accurately set once we've loaded the
+             * objs and mons. */
+            c->objs[i][j] = NO_OBJ;
+            c->mons[i][j] = NO_MON;
         }
-        fprintf(fp, "    %s died after %d ticks, with %d XP, on dungeon level %d.\n\n", u.name, game_tick, u.experience, depth);
-        fflush(fp);
-        fsync(fd);
-        fclose(fp);
     }
-    else
+}
+
+/*! \brief Write a Chunk out to a FILE.  */
+void serialize_chunk(FILE *fp, Chunk const *c)
+{
+    static uint32_t serializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t serializable_flags[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t serializable_regions[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t serializable_decals[CHUNK_EDGE][CHUNK_EDGE];
+    int i;
+    int j;
+    for (i = 0; i < CHUNK_EDGE; ++i)
     {
-        close(fd);
+        for (j = 0; j < CHUNK_EDGE; ++j)
+        {
+            serializable_terrain[i][j] = htonl(c->terrain[i][j]);
+            serializable_flags[i][j] = htonl(c->flags[i][j]);
+            serializable_regions[i][j] = htonl(c->region_number[i][j]);
+            serializable_decals[i][j] = htonl(c->decals[i][j]);
+        }
     }
+    fwrite(serializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    fwrite(serializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    fwrite(serializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    fwrite(serializable_decals, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
 }
 
-void wrapped_system(char const *cmd)
+/*! \brief Deserialize a Level */
+void deserialize_level(FILE *fp, Level *l)
 {
-    int i = system(cmd);
-    if (i != 0)
+    uint32_t tmp;
+    uint32_t tmp_pair[2];
+    uint16_t tmp_shorts[2];
+    uint32_t i;
+    uint32_t j;
+    wrapped_fread(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
+    l->self.dungeon = ntohs(tmp_shorts[0]);
+    l->self.depth = (int16_t) ntohs(tmp_shorts[1]);
+    wrapped_fread(tmp_pair, sizeof tmp_pair[0], 2, fp);
+    l->origin_off.y = (int32_t) ntohl(tmp_pair[0]);
+    l->origin_off.x = (int32_t) ntohl(tmp_pair[1]);
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->dead_space = Terrain(ntohl(tmp));
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->theme = Level_theme(ntohl(tmp));
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->layout = Level_layout(ntohl(tmp));
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->chunks_high = ntohl(tmp);
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->chunks_wide = ntohl(tmp);
+    initialize_chunks(l, l->chunks_high, l->chunks_wide, false);
+    do
     {
-        throw(i);
+        wrapped_fread(&tmp_pair, sizeof tmp_pair[0], 2, fp);
+        i = ntohl(tmp_pair[0]);
+        j = ntohl(tmp_pair[1]);
+        if (i == ~0u)
+        {
+            break;
+        }
+        if ((i >= l->chunks_high) || (j >= l->chunks_wide))
+        {
+            throw Obumb_dataexcep("Out-of-bounds chunk indices while loading level - save probably corrupt");
+        }
+        l->chunks[i][j] = new Chunk(l->dead_space);
+        deserialize_chunk(fp, l->chunks[i][j]);
     }
+    while (1);
 }
 
-void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *fp)
+/*! \brief Serialize a Level */
+void serialize_level(FILE *fp, Level const *l)
 {
-    size_t i = fread(buf, size, nmemb, fp);
-    if (i != nmemb)
+    uint32_t tmp;
+    uint32_t tmp_pair[2];
+    uint16_t tmp_shorts[2];
+    uint32_t i;
+    uint32_t j;
+    tmp_shorts[0] = htons(l->self.dungeon);
+    tmp_shorts[1] = htons(l->self.depth);
+    fwrite(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
+    tmp_pair[0] = htonl(l->origin_off.y);
+    tmp_pair[1] = htonl(l->origin_off.x);
+    fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+    tmp = htonl(l->dead_space);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    tmp = htonl(l->theme);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    tmp = htonl(l->layout);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    tmp = htonl(l->chunks_high);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    tmp = htonl(l->chunks_wide);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    for (i = 0; i < l->chunks_high; ++i)
     {
-        throw(i);
+        tmp_pair[0] = htonl(i);
+        for (j = 0; j < l->chunks_wide; ++j)
+        {
+            if (l->chunks[i][j])
+            {
+                tmp_pair[1] = htonl(j);
+                fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+                serialize_chunk(fp, l->chunks[i][j]);
+            }
+        }
     }
+    tmp_pair[0] = tmp_pair[1] = ~0u;
+    fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
 }
 
+/*! \brief Save the game; set game_finished flag if successful */
 void save_game(void)
 {
     FILE *fp;
     int fd;
-    uint32_t tmp;
-    fd = openat(datadir_fd, "victrix-abyssi.sav", O_WRONLY|O_TRUNC|O_CREAT, S_IRUSR | S_IWUSR);
+    fd = openat(datadir_fd, "obumbrata.sav", O_WRONLY|O_TRUNC|O_CREAT, S_IRUSR | S_IWUSR);
     if (fd == -1)
     {
         return;
@@ -170,15 +589,27 @@ void save_game(void)
         close(fd);
         return;
     }
-    /* Write out the player. */
-    fwrite(&u, 1, sizeof u, fp);
-    tmp = htonl(depth);
-    fwrite(&tmp, 1, sizeof tmp, fp);
-    tmp = htonl(game_tick);
-    fwrite(&tmp, 1, sizeof tmp, fp);
+    serialize_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);
+    serialize_monhandle(fp, first_free_mon_handle);
+    serialize_player(fp, u);
     serialize_level(fp, &lvl);
-    fwrite(monsters, sizeof (Mon), MONSTERS_IN_PLAY, fp);
-    fwrite(objects, sizeof (Obj), OBJECTS_IN_PLAY, fp);
+    for (auto iter = monsters.begin(); iter != monsters.end(); ++iter)
+    {
+        serialize_monhandle(fp, iter->second.self);
+        serialize_monster(fp, iter->second);
+    }
+    serialize_monhandle(fp, NO_MON);
+    for (auto iter = objects.begin(); iter != objects.end(); ++iter)
+    {
+        serialize_objhandle(fp, iter->second.self);
+        serialize_object(fp, iter->second);
+    }
+    serialize_objhandle(fp, NO_OBJ);
     /* Clean up */
     fflush(fp);
     fsync(fd);
@@ -187,70 +618,167 @@ void save_game(void)
     return;
 }
 
-/*! \brief Monster map reinitialization after reload
- */
+/*! \brief Reinitialize dungeon level monster handle arrays after reload */
 static void rebuild_mapmons(void)
 {
-    int i;
-    for (i = 0; i < 100; i++)
+    for (auto iter = monsters.begin(); iter != monsters.end(); ++iter)
     {
-        if (monsters[i].used)
+        if (iter->second.flags & MF_USED)
         {
-            lvl.set_mon_at(monsters[i].pos, i);
+            lvl.set_mon_at(iter->second.pos, iter->first);
         }
     }
 }
 
-/*! \brief Object map reinitialization after reload
- */
+/*! \brief Reinitialize dungeon level object handle arrays after reload */
 static void rebuild_mapobjs(void)
 {
-    int i;
-    for (i = 0; i < 100; i++)
+    for (auto iter = objects.begin(); iter != objects.end(); ++iter)
     {
-        if (objects[i].used && !objects[i].with_you)
+        if ((iter->second.flags & OF_USED) && !(iter->second.flags & OF_WITH_YOU))
         {
-            lvl.set_obj_at(objects[i].pos, i);
+            lvl.set_obj_at(iter->second.pos, iter->first);
         }
     }
 }
 
-int load_game(void)
+static void check_magic(uint32_t magic)
 {
-    int fd;
-    FILE *fp;
-    fd = openat(datadir_fd, "victrix-abyssi.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)
     {
-        return -1;
-    }
-    wrapped_fread(&u, 1, sizeof u, fp);
-    wrapped_fread(&depth, 1, sizeof depth, fp);
-    depth = ntohl(depth);
-    wrapped_fread(&game_tick, 1, sizeof game_tick, fp);
-    game_tick = ntohl(game_tick);
-    deserialize_level(fp, &lvl);
-    wrapped_fread(monsters, sizeof (Mon), MONSTERS_IN_PLAY, fp);
-    wrapped_fread(objects, sizeof (Obj), OBJECTS_IN_PLAY, fp);
-    rebuild_mapobjs();
-    rebuild_mapmons();
-    fclose(fp);
-    game_finished = false;
-    look_around_you();
-    notify_load_complete();
-    int i = unlinkat(datadir_fd, "victrix-abyssi.sav", 0);
-    if (i == -1)
+        throw Obumb_dataexcep("Minor-version mismatch");
+    }
+}
+
+static void check_majvers(uint32_t majvers)
+{
+    if (majvers != MAJVERS)
     {
-        debug_save_unlink_failed();
+        throw Obumb_dataexcep("Major-version mismatch");
     }
-    return 0;
 }
 
+/*! \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
+    {
+        fd = openat(datadir_fd, "obumbrata.sav", O_RDONLY, 0);
+        if (fd == -1)
+        {
+            throw Obumb_sysexcep(errno);
+        }
+        fp = fdopen(fd, "rb");
+        if (!fp)
+        {
+            throw Obumb_sysexcep(errno);
+        }
+        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)
+        {
+            throw Obumb_dataexcep("Object pool exhausted - save file probably corrupt");
+        }
+        deserialize_monhandle(fp, &first_free_mon_handle);
+        if (first_free_mon_handle == 0)
+        {
+            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;
+    }
+    catch (Obumb_dataexcep e)
+    {
+        /* 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;
+    }
+}
+
+/*! \brief Write human-readable snapshot of the player character
+ *
+ * \todo Sanitize the player character's name, or the filename, somewhere */
 void write_char_dump(void)
 {
     FILE *fp;
@@ -298,5 +826,93 @@ void write_char_dump(void)
     fclose(fp);
 }
 
+/*! \brief Read configuration files.
+ *
+ * \todo Implement configuration files.
+ */
+void load_config(void)
+{
+    int i;
+    i = setup_dirs("com.blackswordsonics", "obumbrata", &datadir_fd, &confdir_fd);
+    if (i < 0)
+    {
+        perror("obumbrata: couldn't access configuration directories");
+        exit(1);
+    }
+    // TODO write this function
+}
+
+/*! \brief Rewrite configuration files.
+ *
+ * \todo Implement configuration files.
+ */
+int rewrite_config(void)
+{
+    // TODO write this function
+    return 0;
+}
+
+/*! \brief Get a list of extant saved games and character names
+ *
+ * Fill in the map<string,string> container *dest using character names
+ * as keys and filenames as values.
+ *
+ * \param dest Pointer to map<string,string> container to fill with data
+ * \return Number of charname/filename pairs written to container
+ * \todo Basic implementation
+ * \todo Deal gracefully with a savefile going away before we open it
+ */
+int get_savefile_list(std::map<std::string, std::string> *dest)
+{
+    // TODO write this function
+    return 0;
+}
+
+/*! \brief Record the details of the PC's death to the log
+ */
+void log_death(Death d, char const *what)
+{
+    FILE *fp;
+    int fd;
+    fd = openat(datadir_fd, "obumbrata.log", O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
+    if (fd == -1)
+    {
+        return;
+    }
+    fp = fdopen(fd, "a");
+    if (fp)
+    {
+        switch (d)
+        {
+        case DEATH_KILLED:
+            fprintf(fp, "%s was killed by %s.\n", u.name, what);
+            break;
+        case DEATH_KILLED_MON:
+            fprintf(fp, "%s was killed by a nasty %s.\n", u.name, what);
+            break;
+        case DEATH_BODY:
+            fprintf(fp, "%s's heart was stopped by %s.\n", u.name, what);
+            break;
+        case DEATH_AGILITY:
+            fprintf(fp, "%s's nerves were destroyed by %s.\n", u.name, what); 
+            break;
+        case DEATH_LASH:
+            fprintf(fp, "%s tasted the lash one time too many.\n", u.name);
+            break;
+        case DEATH_RIBBONS:
+            fprintf(fp, "%s looked good in ribbons.\n", u.name);
+            break;
+        }
+        fprintf(fp, "    %s died after %d ticks, with %d XP, on dungeon level %d.\n\n", u.name, game_tick, u.experience, depth);
+        fflush(fp);
+        fsync(fd);
+        fclose(fp);
+    }
+    else
+    {
+        close(fd);
+    }
+}
+
 /* log.cc */
 // vim:cindent:expandtab
diff --git a/main.cc b/main.cc
index ae7b484..5f39534 100644 (file)
--- a/main.cc
+++ b/main.cc
@@ -1,5 +1,5 @@
 /* \file main.cc
- * \brief main core of Victrix Abyssi
+ * \brief main core
  */
 
 /* Copyright 2005-2014 Martin Read
@@ -51,22 +51,23 @@ 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();
 }
 
 void main_loop(void)
 {
-    int i;
     int action_speed = 0;
     welcome();
     while (!game_finished)
@@ -99,18 +100,17 @@ void main_loop(void)
                 }
             } while (cost == Cost_none);
         }
-        for (i = 0; i < 100; i++)
+        for (auto iter = monsters.begin(); iter != monsters.end(); ++iter)
         {
-            if (!monsters[i].used)
+            if (!(iter->second.flags & MF_ALIVE))
             {
-                /* Unused monster */
                 continue;
             }
             /* Update the monster's status. */
-            update_mon(i);
-            if (action_speed <= permons[monsters[i].mon_id].speed)
+            update_mon(iter->first);
+            if (action_speed <= permons[iter->second.pm_ref].speed)
             {
-                mon_acts(i);
+                mon_acts(iter->first);
             }
             if (game_finished)
             {
@@ -128,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 de25b79..d99a107 100644 (file)
--- a/map.cc
+++ b/map.cc
 Level lvl;
 int depth;
 
+Decal_desc decal_props[NUM_DECALS] =
+{
+    { "blood", "Blood, presumably from a mortal creature.", Gcol_blood },
+    { "demonic ichor", "The uncanny, faintly glowing blue ichor of a demon.", Gcol_l_blue },
+    { "slime", "Unpleasant sticky green goo.", Gcol_green },
+    { "pus", "Sickly yellow gunk from an infected creature.", Gcol_blood },
+};
+
 void drop_all_chunks(Level *l)
 {
-    int i;
-    int j;
+    uint32_t i;
+    uint32_t j;
     if (l->chunks)
     {
         for (i = 0; i < l->chunks_high; ++i)
@@ -71,6 +79,7 @@ Chunk::Chunk(Terrain t)
             terrain[k][m] = t;
             flags[k][m] = 0;
             region_number[k][m] = NO_REGION;
+            decals[k][m] = NO_DECAL;
         }
     }
 }
@@ -83,7 +92,6 @@ void initialize_chunks(Level *l, int height, int width, bool dense)
     int i;
     int j;
     drop_all_chunks(l);
-    l->origin_off = Stationary;
     l->chunks_high = height;
     l->chunks_wide = width;
     new_chunk_grid = new Chunk ** [height];
@@ -107,7 +115,7 @@ void Level::vivify(Coord c)
         grow(North);
         rc = c + origin_off;
     }
-    while (rc.y > (chunks_high << CHUNK_SHIFT))
+    while (rc.y > int32_t(chunks_high << CHUNK_SHIFT))
     {
         grow(South);
     }
@@ -116,7 +124,7 @@ void Level::vivify(Coord c)
         grow(West);
         rc = c + origin_off;
     }
-    while (rc.x > (chunks_wide << CHUNK_SHIFT))
+    while (rc.x > int32_t(chunks_wide << CHUNK_SHIFT))
     {
         grow(East);
     }
@@ -130,11 +138,10 @@ void Level::grow(Offset o, bool dense)
 {
     Chunk ***new_chunk_grid;
     Chunk **new_chunk_row;
-    int i;
-    int j;
+    uint32_t i;
+    uint32_t j;
     if (o.y < 0)
     {
-        int i;
         origin_off.y += CHUNK_EDGE;
         ++chunks_high;
         new_chunk_grid = new Chunk **[chunks_high];
@@ -205,8 +212,8 @@ void Level::grow(Offset o, bool dense)
  */
 Coord Level::random_point(int margin) const
 {
-    Coord tl = { margin, margin };
-    Coord br = { GUIDE_EDGE_SIZE - (margin + 1), GUIDE_EDGE_SIZE - (margin + 1) };
+    Coord tl = { min_y() + margin, min_x() + margin };
+    Coord br = { max_y() - margin, max_x() - margin };
     return inc_boxed(tl, br);
 }
 
@@ -255,7 +262,7 @@ int Level::find_stairs(Terrain t, std::vector<Stair_detail> *dest) const
 int Level::find_stairs(Level_key from, std::vector<Stair_detail> *dest) const
 {
     int count = 0;
-    for (auto iter = lvl.stairs.begin(); iter != lvl.stairs.end(); ++iter)
+    for (auto iter = stairs.begin(); iter != stairs.end(); ++iter)
     {
         if (iter->destination == from)
         {
@@ -268,17 +275,25 @@ int Level::find_stairs(Level_key from, std::vector<Stair_detail> *dest) const
 
 void leave_level(void)
 {
-    int i;
-    for (i = 0; i < 100; i++)
+    for (auto iter = monsters.begin(); iter != monsters.end(); )
+    {
+        auto iter2 = iter;
+        ++iter2;
+        monsters.erase(iter);
+        iter = iter2;
+    }
+    for (auto iter = objects.begin(); iter != objects.end(); )
     {
-        /* Throw away each monster */
-        monsters[i].used = false;
-        /* and each object not carried by the player */
-        if (!objects[i].with_you)
+        auto iter2 = iter;
+        ++iter2;
+        if (!(iter->second.flags & OF_WITH_YOU))
         {
-            objects[i].used = false;
+            objects.erase(iter);
         }
+        iter = iter2;
     }
+    lvl.origin_off = Stationary;
+    drop_all_chunks(&lvl);
     depth++;
 }
 
@@ -286,9 +301,9 @@ void make_new_level(void)
 {
     build_level();
     populate_level();
-    inject_player(lvl.self.naive_prev());
-    look_around_you();
     notify_change_of_depth();
+    inject_player(&lvl, lvl.self.naive_prev());
+    look_around_you();
 }
 
 /*! \brief Random walk which grows the level
@@ -296,28 +311,22 @@ void make_new_level(void)
  * This version of run_random_walk will extend the level boundaries instead
  * of rejecting out-of-bounds coordinates.
  */
-Coord run_random_walk(Coord oc, rwalk_mod_funcptr func,
+Coord run_random_walk(Level *l, Coord oc, rwalk_mod_funcptr func,
                       void const *priv_ptr, int cells)
 {
     int i;
     Coord c = oc;
     int bailout = 10000;
 
-    func(c, priv_ptr);
+    func(l, c, priv_ptr);
     for (i = 0; (i < cells) && (bailout > 0); --bailout)
     {
         oc = c;
-        if (zero_die(2))
-        {
-            c.y += (zero_die(2) ? -1 : 1);
-            c.y = std::min(lvl.max_y() - 2, std::max(c.y, lvl.min_y() + 2));
-        }
-        else
-        {
-            c.x += (zero_die(2) ? -1 : 1);
-            c.x = std::min(lvl.max_x() - 2, std::max(c.x, lvl.min_x() + 2));
-        }
-        switch (func(c, priv_ptr))
+        c += zero_die(2) ?
+            (zero_die(2) ? North : South) :
+            (zero_die(2) ? East : West);
+        c.clamp(l->min_y() + 2, l->min_x() + 2, l->max_y() - 2, l->max_x() - 2);
+        switch (func(l, c, priv_ptr))
         {
         case 0:
             /* nothing changed */
@@ -334,7 +343,7 @@ Coord run_random_walk(Coord oc, rwalk_mod_funcptr func,
     }
     if (bailout < 1)
     {
-        debug_excavation_bailout();
+        throw Obumb_levgen_excep("run_random_walk exceeded bailout limit");
     }
     return c;
 }
@@ -344,42 +353,37 @@ Coord run_random_walk(Coord oc, rwalk_mod_funcptr func,
  * This version of run_random_walk will extend the level boundaries instead
  * of rejecting out-of-bounds coordinates.
  */
-Coord run_random_walk_unbounded(Coord oc, rwalk_mod_funcptr func,
+Coord run_random_walk_unbounded(Level *l, Coord oc, rwalk_mod_funcptr func,
                                 void const *priv_ptr, int cells)
 {
     int i;
     Coord c = oc;
     int bailout = 10000;
 
-    func(c, priv_ptr);
+    func(l, c, priv_ptr);
     for (i = 0; (i < cells) && (bailout > 0); --bailout)
     {
         oc = c;
-        if (zero_die(2))
-        {
-            c.y += (zero_die(2) ? -1 : 1);
-        }
-        else
-        {
-            c.x += (zero_die(2) ? -1 : 1);
-        }
-        if (c.y <= lvl.min_y())
+        c += zero_die(2) ?
+            (zero_die(2) ? North : South) :
+            (zero_die(2) ? East : West);
+        if (c.y <= l->min_y())
         {
-            lvl.grow(North, true);
+            l->grow(North, true);
         }
-        if (c.y >= lvl.max_y())
+        if (c.y >= l->max_y())
         {
-            lvl.grow(South, true);
+            l->grow(South, true);
         }
-        if (c.x <= lvl.min_x())
+        if (c.x <= l->min_x())
         {
-            lvl.grow(West, true);
+            l->grow(West, true);
         }
-        if (c.x >= lvl.max_x())
+        if (c.x >= l->max_x())
         {
-            lvl.grow(East, true);
+            l->grow(East, true);
         }
-        switch (func(c, priv_ptr))
+        switch (func(l, c, priv_ptr))
         {
         case 0:
             /* nothing changed */
@@ -396,7 +400,7 @@ Coord run_random_walk_unbounded(Coord oc, rwalk_mod_funcptr func,
     }
     if (bailout < 1)
     {
-        debug_excavation_bailout();
+        throw Obumb_levgen_excep("run_random_walk exceeded bailout limit");
     }
     return c;
 }
@@ -413,11 +417,15 @@ void build_level(void)
     lvl.stairs.clear();
     rng.extract_serialization(saved_state_buffer, saved_state_size);
     theme_roll = zero_die(depth + 50);
-    if (!zero_die(4))
+    if (!zero_die(6))
+    {
+        lvl.layout = LAYOUT_CAVE_PITS;
+    }
+    else if (!zero_die(5))
     {
         lvl.layout = LAYOUT_CAVE_INTRUSIONS;
     }
-    else if ((depth > 1) && !zero_die(6))
+    else if ((depth > 8) && !zero_die(6))
     {
         lvl.layout = LAYOUT_CAVE_SHRINE;
     }
@@ -444,80 +452,89 @@ void build_level(void)
     switch (lvl.layout)
     {
     case LAYOUT_CAVE_SHRINE:
-        build_level_shrine();
+        build_level_shrine(&lvl);
         break;
     case LAYOUT_CAVE_INTRUSIONS:
-        build_level_intrusions();
+        build_level_intrusions(&lvl);
         break;
     case LAYOUT_DUNGEONBASH:
-        build_level_dungeonbash();
+        build_level_dungeonbash(&lvl);
         break;
     case LAYOUT_CLASSIC_CAVE:
-        build_level_cave();
+        build_level_cave(&lvl);
         break;
-    }
-}
-
-/*! \brief Build a dungeonbash-style rooms-and-corridors level */
-void build_level_dungeonbash()
-{
-    int chy;
-    int chx;
-    /* We know we're going to use all nine chunks, so create them
-     * immediately. */
-    initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
-    /* One room per chunk. */
-    for (chy = 0; chy < lvl.chunks_high; ++chy)
-    {
-        for (chx = 0; chx < lvl.chunks_wide; ++ chx)
+    case LAYOUT_CAVE_PITS:
+        if (depth > 5)
         {
-            /* Smallest allowed room has a 2x2 interior. */
-            Offset room_size = { MIN_ROOM_EDGE + zero_die(5), MIN_ROOM_EDGE + zero_die(5) };
-            /* Each dimension has a 1-in-3 chance to get another boost */
             if (!zero_die(3))
             {
-                room_size.y += zero_die(5);
+                build_level_pits(&lvl, LAVA);
             }
-            if (!zero_die(3))
+            else if (!zero_die(2))
+            {
+                build_level_pits(&lvl, WATER);
+            }
+            else
             {
-                room_size.x += zero_die(5);
+                build_level_pits(&lvl, CHASM);
             }
         }
+        else
+        {
+            build_level_pits(&lvl, CHASM);
+        }
+        break;
+    case LAYOUT_CLAUSTROPHOBIA:
+        build_level_claustrophobia(&lvl);
+        break;
     }
 }
 
 /*! \brief Excavation function for use with random walks */
-int excavation_write(Coord c, void const *data)
+int excavation_write(Level *l, Coord c, void const *data)
 {
     int const *data_as_ints = (int const *) data;
     int const *overwrite = data_as_ints + 2;
     int newterr = data_as_ints[1];
     int j;
-    if (lvl.flags_at(c) & MAPFLAG_HARDWALL)
+    if (l->flags_at(c) & MAPFLAG_HARDWALL)
     {
         /* Don't bite into hardened walls, but don't waste a step on
          * them either. */
         return 2;
     }
+    if (l->terrain_at(c) == newterr)
+    {
+        return 0;
+    }
     for (j = 0; j < data_as_ints[0]; ++j)
     {
-        if (lvl.terrain_at(c) == overwrite[j])
+        if (l->terrain_at(c) == overwrite[j])
         {
-            lvl.set_terrain_at(c, (Terrain) newterr);
+            l->set_terrain_at(c, (Terrain) newterr);
             return 1;
         }
     }
-    return 0;
+    return 2;
 }
 
 /*! \brief "Intrusion" function for use with random walks */
-int intrusion_write(Coord c, void const *data)
+int intrusion_write(Level *l, Coord c, void const *data)
 {
     Terrain const *tptr = (Terrain const *) data;
-    if ((lvl.terrain_at(c) != WALL) || (lvl.flags_at(c) & MAPFLAG_HARDWALL))
+    Terrain old_terr = l->terrain_at(c);
+    // Meander across MAPFLAG_HARDWALL and across whatever terrain we
+    // might be overwriting dead space with.
+    if ((l->flags_at(c) & MAPFLAG_HARDWALL) ||
+        (tptr && (*tptr == old_terr)))
     {
         return 0;
     }
+    // Bounce off anything that isn't "dead space"
+    if (old_terr != l->dead_space)
+    {
+        return 2;
+    }
     /* Don't intrude too closely on the centre of the level */
     if ((c.y > ((GUIDE_EDGE_SIZE / 2) - 4)) && (c.y < ((GUIDE_EDGE_SIZE / 2) - 4)))
     {
@@ -529,14 +546,14 @@ int intrusion_write(Coord c, void const *data)
     }
     if (tptr)
     {
-        lvl.set_terrain_at(c, *tptr);
+        l->set_terrain_at(c, *tptr);
     }
-    lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+    l->set_flags_at(c, MAPFLAG_HARDWALL);
     return 1;
 }
 
 /*! \brief Do a random walk laying down an exclusion area */
-void place_random_intrusion(Terrain new_wall)
+void place_random_intrusion(Level *l, Terrain new_wall)
 {
     Coord c;
     int intrusion_size = inc_flat(27, 54);
@@ -544,19 +561,26 @@ void place_random_intrusion(Terrain new_wall)
     {
         c.x = zero_die(2) ? inc_flat(1, GUIDE_EDGE_SIZE / 3) : inc_flat((2 * GUIDE_EDGE_SIZE) / 3, GUIDE_EDGE_SIZE - 2);
         c.y = zero_die(2) ? inc_flat(1, GUIDE_EDGE_SIZE / 3) : inc_flat((2 * GUIDE_EDGE_SIZE) / 3, GUIDE_EDGE_SIZE - 2);
-    } while (lvl.flags_at(c) & MAPFLAG_HARDWALL);
-    run_random_walk(c, intrusion_write, &new_wall, intrusion_size);
+    } while (l->flags_at(c) & MAPFLAG_HARDWALL);
+    try
+    {
+        run_random_walk(l, c, intrusion_write, &new_wall, intrusion_size);
+    }
+    catch (...)
+    {
+        // undersize intrusions are not a problem
+    }
 }
 
 /*! \brief Get a valid square to generate a monster on */
-Pass_fail get_levgen_mon_floor(Coord *c)
+Pass_fail get_levgen_mon_floor(Level *l, Coord *c)
 {
     int cell_try;
     Coord t;
     for (cell_try = 0; cell_try < 200; cell_try++)
     {
-        t = lvl.random_point(1);
-        if ((lvl.terrain_at(t) != FLOOR) || (lvl.mon_at(t) != NO_MON))
+        t = l->random_point(1);
+        if ((l->terrain_at(t) != FLOOR) || (l->mon_at(t) != NO_MON))
         {
             t = Nowhere;
             continue;
@@ -579,9 +603,9 @@ void populate_level(void)
     Coord c;
     int ic;
     /* Generate some random monsters */
-    for (i = 0; i < 10; i++)
+    for (i = 0; i < (10 + depth); i++)
     {
-        pf = get_levgen_mon_floor(&c);
+        pf = get_levgen_mon_floor(&lvl, &c);
         if (pf == You_fail)
         {
             continue;
@@ -597,7 +621,7 @@ void populate_level(void)
     /* Generate some random treasure */
     for (i = 0; i < ic; i++)
     {
-        pf = get_levgen_mon_floor(&c);
+        pf = get_levgen_mon_floor(&lvl, &c);
         if (pf == You_fail)
         {
             continue;
@@ -607,12 +631,12 @@ void populate_level(void)
 }
 
 /*! \brief Inject the player into the level based on where they arrived from */
-void inject_player(Level_key from)
+void inject_player(Level *l, Level_key from)
 {
     /* For now we are allowing only one linkage between levels */ 
     Coord c;
     std::vector<Stair_detail> stair_list;
-    int i = lvl.find_stairs(from, &stair_list);
+    int i = l->find_stairs(from, &stair_list);
     if (i != 0)
     {
         c = stair_list[0].pos;
@@ -636,198 +660,91 @@ 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 },
-    { "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 */
-bool terrain_is_opaque(Terrain terr)
+bool terrain_is_floor(Terrain terr)
 {
-    return terrain_props[terr].flags & TFLAG_opaque;
+    return terrain_props[terr].flags & TFLAG_floor;
 }
 
 /*! \brief self-explanatory */
-bool terrain_blocks_beings(Terrain terr)
+bool terrain_is_wall(Terrain terr)
 {
-    return terrain_props[terr].flags & TFLAG_block_beings;
+    return terrain_props[terr].flags & TFLAG_wall;
 }
 
 /*! \brief self-explanatory */
-bool terrain_blocks_missiles(Terrain terr)
+bool terrain_is_opaque(Terrain terr)
 {
-    return terrain_props[terr].flags & TFLAG_block_missile;
+    return terrain_props[terr].flags & TFLAG_opaque;
 }
 
-/*! \brief Read a Chunk from a FILE.
- *
- *  Yes, I know "throw(errno)" is tacky and stupid, but it achieves the
- *  desired result: the save is obviously garbage, so there's no point
- *  in finishing loading it.
- */
-void deserialize_chunk(FILE *fp, Chunk *c)
+bool terrain_gapes(Terrain terr)
 {
-    static uint32_t deserializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
-    static uint32_t deserializable_flags[CHUNK_EDGE][CHUNK_EDGE];
-    static uint32_t deserializable_regions[CHUNK_EDGE][CHUNK_EDGE];
-    int i;
-    int j;
-    wrapped_fread(deserializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
-    wrapped_fread(deserializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
-    wrapped_fread(deserializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
-    for (i = 0; i < CHUNK_EDGE; ++i)
-    {
-        for (j = 0; j < CHUNK_EDGE; ++j)
-        {
-            c->terrain[i][j] = (Terrain) ntohl(deserializable_terrain[i][j]);
-            c->flags[i][j] = ntohl(deserializable_flags[i][j]);
-            c->region_number[i][j] = ntohl(deserializable_regions[i][j]);
-            /* objs and mons will get accurately set once we've loaded the
-             * objs and mons. */
-            c->objs[i][j] = NO_OBJ;
-            c->mons[i][j] = NO_MON;
-        }
-    }
+    return terrain_props[terr].flags & TFLAG_fall_hazard;
 }
 
-/*! \brief Write a Chunk out to a FILE.  */
-void serialize_chunk(FILE *fp, Chunk const *c)
+bool terrain_drowns(Terrain terr)
 {
-    static uint32_t serializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
-    static uint32_t serializable_flags[CHUNK_EDGE][CHUNK_EDGE];
-    static uint32_t serializable_regions[CHUNK_EDGE][CHUNK_EDGE];
-    int i;
-    int j;
-    for (i = 0; i < CHUNK_EDGE; ++i)
-    {
-        for (j = 0; j < CHUNK_EDGE; ++j)
-        {
-            serializable_terrain[i][j] = htonl(c->terrain[i][j]);
-            serializable_flags[i][j] = htonl(c->flags[i][j]);
-            serializable_regions[i][j] = htonl(c->region_number[i][j]);
-        }
-    }
-    fwrite(serializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
-    fwrite(serializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
-    fwrite(serializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    return terrain_props[terr].flags & TFLAG_drown_hazard;
 }
 
-/*! \brief Serialize a Level */
-void serialize_level(FILE *fp, Level const *l)
+bool terrain_is_hot(Terrain terr)
 {
-    uint32_t tmp;
-    uint32_t tmp_pair[2];
-    uint16_t tmp_shorts[2];
-    int i;
-    int j;
-    tmp_shorts[0] = htons(l->self.dungeon);
-    tmp_shorts[1] = htons(l->self.depth);
-    fwrite(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
-    tmp_pair[0] = htonl(l->origin_off.y);
-    tmp_pair[1] = htonl(l->origin_off.x);
-    fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
-    tmp = htonl(l->dead_space);
-    fwrite(&tmp, sizeof tmp, 1, fp);
-    tmp = htonl(l->theme);
-    fwrite(&tmp, sizeof tmp, 1, fp);
-    tmp = htonl(l->layout);
-    fwrite(&tmp, sizeof tmp, 1, fp);
-    tmp = htonl(l->chunks_high);
-    fwrite(&tmp, sizeof tmp, 1, fp);
-    tmp = htonl(l->chunks_wide);
-    fwrite(&tmp, sizeof tmp, 1, fp);
-    for (i = 0; i < l->chunks_high; ++i)
-    {
-        tmp_pair[0] = htonl(i);
-        for (j = 0; j < l->chunks_wide; ++j)
-        {
-            if (l->chunks[i][j])
-            {
-                tmp_pair[1] = htonl(j);
-                fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
-                serialize_chunk(fp, l->chunks[i][j]);
-            }
-        }
-    }
-    tmp_pair[0] = tmp_pair[1] = ~0u;
-    fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+    return terrain_props[terr].flags & TFLAG_fire_hazard;
 }
 
-/*! \brief Deserialize a Level
- *
- * \todo Throw an exception if the level is malformed e.g. OOB chunk indices.
- */
-void deserialize_level(FILE *fp, Level *l)
+/*! \brief self-explanatory */
+bool terrain_blocks_items(Terrain terr)
 {
-    uint32_t tmp;
-    uint32_t tmp_pair[2];
-    uint16_t tmp_shorts[2];
-    uint32_t i;
-    uint32_t j;
-    wrapped_fread(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
-    l->self.dungeon = ntohs(tmp_shorts[0]);
-    l->self.depth = (int16_t) ntohs(tmp_shorts[1]);
-    wrapped_fread(tmp_pair, sizeof tmp_pair[0], 2, fp);
-    l->origin_off.y = (int) ntohl(tmp_pair[0]);
-    l->origin_off.x = (int) ntohl(tmp_pair[1]);
-    wrapped_fread(&tmp, sizeof tmp, 1, fp);
-    l->dead_space = Terrain(ntohl(tmp));
-    wrapped_fread(&tmp, sizeof tmp, 1, fp);
-    l->theme = level_theme(ntohl(tmp));
-    wrapped_fread(&tmp, sizeof tmp, 1, fp);
-    l->layout = level_layout(ntohl(tmp));
-    wrapped_fread(&tmp, sizeof tmp, 1, fp);
-    l->chunks_high = ntohl(tmp);
-    wrapped_fread(&tmp, sizeof tmp, 1, fp);
-    l->chunks_wide = ntohl(tmp);
-    initialize_chunks(l, l->chunks_high, l->chunks_wide, false);
-    do
-    {
-        wrapped_fread(&tmp_pair, sizeof tmp_pair[0], 2, fp);
-        i = ntohl(tmp_pair[0]);
-        j = ntohl(tmp_pair[1]);
-        if (i == ~0u)
-        {
-            break;
-        }
-        l->chunks[i][j] = new Chunk(l->dead_space);
-        deserialize_chunk(fp, l->chunks[i][j]);
-    }
-    while (1);
+    return terrain_props[terr].flags & TFLAG_block_items;
 }
 
+/*! \brief self-explanatory */
+bool terrain_blocks_beings(Terrain terr)
+{
+    return terrain_props[terr].flags & TFLAG_block_beings;
+}
+
+/*! \brief self-explanatory */
+bool terrain_blocks_missiles(Terrain terr)
+{
+    return terrain_props[terr].flags & TFLAG_block_missile;
+}
+
+/*! \brief Rotate a connection bitmask */
 uint32_t rotate_connection_mask(uint32_t val, int clockwise_steps)
 {
     clockwise_steps &= 0x3;
     return ((val << clockwise_steps) | ((val & 0xf) >> (4 - clockwise_steps))) & 0xf;
 }
 
+/*! \brief Clean up everything level-related */
 void level_cleanup(void)
 {
-    int i;
-    for (i = 0; i < MONSTERS_IN_PLAY; ++i)
-    {
-        monsters[i].used = false;
-    }
-    for (i = 0; i < OBJECTS_IN_PLAY; ++i)
-    {
-        objects[i].used = false;
-        objects[i].with_you = false;
-    }
+    monsters.clear();
+    objects.clear();
     drop_all_chunks(&lvl);
 }
 
diff --git a/map.cc.old b/map.cc.old
new file mode 100644 (file)
index 0000000..de25b79
--- /dev/null
@@ -0,0 +1,835 @@
+/*! \file map.cc
+ *  \brief Map generation and population
+ */
+
+/* Copyright 2005-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 "victrix-abyssi.hh"
+#include "objects.hh"
+#include "monsters.hh"
+#include "mapgen.hh"
+
+#include <string.h>
+Level lvl;
+int depth;
+
+void drop_all_chunks(Level *l)
+{
+    int i;
+    int j;
+    if (l->chunks)
+    {
+        for (i = 0; i < l->chunks_high; ++i)
+        {
+            for (j = 0; j < l->chunks_wide; ++j)
+            {
+                if (l->chunks[i][j])
+                {
+                    delete l->chunks[i][j];
+                }
+                l->chunks[i][j] = nullptr;
+            }
+            delete[] l->chunks[i];
+            l->chunks[i] = nullptr;
+        }
+        delete[] l->chunks;
+        l->chunks = nullptr;
+    }
+}
+
+Chunk::Chunk(Terrain t)
+{
+    int k, m;
+    for (k = 0; k < CHUNK_EDGE; ++k)
+    {
+        for (m = 0; m < CHUNK_EDGE; ++m)
+        {
+            objs[k][m] = NO_OBJ;
+            mons[k][m] = NO_MON;
+            terrain[k][m] = t;
+            flags[k][m] = 0;
+            region_number[k][m] = NO_REGION;
+        }
+    }
+}
+
+void initialize_chunks(Level *l, int height, int width, bool dense)
+{
+    Chunk ***new_chunk_grid;
+    Chunk **new_chunk_row;
+    Chunk *new_chunk;
+    int i;
+    int j;
+    drop_all_chunks(l);
+    l->origin_off = Stationary;
+    l->chunks_high = height;
+    l->chunks_wide = width;
+    new_chunk_grid = new Chunk ** [height];
+    for (i = 0; i < height; ++i)
+    {
+        new_chunk_row = new Chunk * [width];
+        for (j = 0; j < width; ++j)
+        {
+            new_chunk_row[j] = new_chunk = (dense ? new Chunk(l->dead_space) : nullptr);
+        }
+        new_chunk_grid[i] = new_chunk_row;
+    }
+    l->chunks = new_chunk_grid;
+}
+
+void Level::vivify(Coord c)
+{
+    Coord rc = c + origin_off;
+    while (rc.y < 0)
+    {
+        grow(North);
+        rc = c + origin_off;
+    }
+    while (rc.y > (chunks_high << CHUNK_SHIFT))
+    {
+        grow(South);
+    }
+    while (rc.x < 0)
+    {
+        grow(West);
+        rc = c + origin_off;
+    }
+    while (rc.x > (chunks_wide << CHUNK_SHIFT))
+    {
+        grow(East);
+    }
+    if (!chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT])
+    {
+        chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT] = new Chunk(dead_space);
+    }
+}
+
+void Level::grow(Offset o, bool dense)
+{
+    Chunk ***new_chunk_grid;
+    Chunk **new_chunk_row;
+    int i;
+    int j;
+    if (o.y < 0)
+    {
+        int i;
+        origin_off.y += CHUNK_EDGE;
+        ++chunks_high;
+        new_chunk_grid = new Chunk **[chunks_high];
+        for (i = 0; i < (chunks_high - 1); ++i)
+        {
+            new_chunk_grid[i + 1] = chunks[i];
+        }
+        new_chunk_grid[0] = new_chunk_row = new Chunk *[chunks_wide];
+        for (i = 0; i < chunks_wide; ++i)
+        {
+            new_chunk_row[i] = dense ? new Chunk(dead_space) : nullptr;
+        }
+        delete[] chunks;
+        chunks = new_chunk_grid;
+    }
+    else if (o.y > 0)
+    {
+        ++chunks_high;
+        new_chunk_grid = new Chunk **[chunks_high];
+        for (i = 0; i < (chunks_high - 1); ++i)
+        {
+            new_chunk_grid[i] = chunks[i];
+        }
+        new_chunk_grid[chunks_high - 1] = new_chunk_row = new Chunk *[chunks_wide];
+        for (i = 0; i < chunks_wide; ++i)
+        {
+            new_chunk_row[i] = dense ? new Chunk(dead_space) : nullptr;
+        }
+        delete[] chunks;
+        chunks = new_chunk_grid;
+    }
+    if (o.x < 0)
+    {
+        ++chunks_wide;
+        origin_off.x += CHUNK_EDGE;
+        for (i = 0; i < chunks_high; ++i)
+        {
+            new_chunk_row = new Chunk *[chunks_wide];
+            for (j = 0; j < chunks_wide - 1; ++j)
+            {
+                new_chunk_row[j + 1] = chunks[i][j];
+            }
+            new_chunk_row[0] = dense ? new Chunk(dead_space) : nullptr;
+            delete[] chunks[i];
+            chunks[i] = new_chunk_row;
+        }
+    }
+    else if (o.x > 0)
+    {
+        ++chunks_wide;
+        for (i = 0; i < chunks_high; ++i)
+        {
+            new_chunk_row = new Chunk *[chunks_wide];
+            for (j = 0; j < chunks_wide - 1; ++j)
+            {
+                new_chunk_row[j] = chunks[i][j];
+            }
+            new_chunk_row[chunks_wide - 1] = dense ? new Chunk(dead_space) : nullptr;
+            delete[] chunks[i];
+            chunks[i] = new_chunk_row;
+        }
+    }
+}
+
+/*! \brief Find a random point on the level
+ *
+ * \param margin Width of the "dead" space along each edge in which the point cannot appear
+ */
+Coord Level::random_point(int margin) const
+{
+    Coord tl = { margin, margin };
+    Coord br = { GUIDE_EDGE_SIZE - (margin + 1), GUIDE_EDGE_SIZE - (margin + 1) };
+    return inc_boxed(tl, br);
+}
+
+int Level::add_stairs_at(Coord c, Terrain t, Level_key l)
+{
+    Stair_detail sd = { c, t, l };
+    if (!(terrain_props[t].flags & 
+          (TFLAG_ascend | TFLAG_portal | TFLAG_descend)))
+    {
+        return NO_STAIRS;
+    }
+    set_terrain_at(c, t);
+    stairs.push_back(sd);
+    return stairs.size() - 1;
+}
+
+Level_key const No_level = { 65535, -32768 };
+
+Stair_detail const Bad_stairs = { Nowhere, FLOOR, No_level };
+Stair_detail Level::find_stairs(Coord pos) const
+{
+    for (auto iter = stairs.begin(); iter != stairs.end(); ++iter)
+    {
+        if (iter->pos == pos)
+        {
+            return *iter;
+        }
+    }
+    return Bad_stairs;
+}
+
+int Level::find_stairs(Terrain t, std::vector<Stair_detail> *dest) const
+{
+    int count = 0;
+    for (auto iter = stairs.begin(); iter != stairs.end(); ++iter)
+    {
+        if (iter->t == t)
+        {
+            dest->push_back(*iter);
+            ++count;
+        }
+    }
+    return count;
+}
+
+int Level::find_stairs(Level_key from, std::vector<Stair_detail> *dest) const
+{
+    int count = 0;
+    for (auto iter = lvl.stairs.begin(); iter != lvl.stairs.end(); ++iter)
+    {
+        if (iter->destination == from)
+        {
+            dest->push_back(*iter);
+            ++count;
+        }
+    }
+    return count;
+}
+
+void leave_level(void)
+{
+    int i;
+    for (i = 0; i < 100; i++)
+    {
+        /* Throw away each monster */
+        monsters[i].used = false;
+        /* and each object not carried by the player */
+        if (!objects[i].with_you)
+        {
+            objects[i].used = false;
+        }
+    }
+    depth++;
+}
+
+void make_new_level(void)
+{
+    build_level();
+    populate_level();
+    inject_player(lvl.self.naive_prev());
+    look_around_you();
+    notify_change_of_depth();
+}
+
+/*! \brief Random walk which grows the level
+ *
+ * This version of run_random_walk will extend the level boundaries instead
+ * of rejecting out-of-bounds coordinates.
+ */
+Coord run_random_walk(Coord oc, rwalk_mod_funcptr func,
+                      void const *priv_ptr, int cells)
+{
+    int i;
+    Coord c = oc;
+    int bailout = 10000;
+
+    func(c, priv_ptr);
+    for (i = 0; (i < cells) && (bailout > 0); --bailout)
+    {
+        oc = c;
+        if (zero_die(2))
+        {
+            c.y += (zero_die(2) ? -1 : 1);
+            c.y = std::min(lvl.max_y() - 2, std::max(c.y, lvl.min_y() + 2));
+        }
+        else
+        {
+            c.x += (zero_die(2) ? -1 : 1);
+            c.x = std::min(lvl.max_x() - 2, std::max(c.x, lvl.min_x() + 2));
+        }
+        switch (func(c, priv_ptr))
+        {
+        case 0:
+            /* nothing changed */
+            break;
+        case 1:
+            /* changed normally */
+            ++i;
+            break;
+        case 2:
+            /* reject! */
+            c = oc;
+            break;
+        }
+    }
+    if (bailout < 1)
+    {
+        debug_excavation_bailout();
+    }
+    return c;
+}
+
+/*! \brief Random walk which grows the level
+ *
+ * This version of run_random_walk will extend the level boundaries instead
+ * of rejecting out-of-bounds coordinates.
+ */
+Coord run_random_walk_unbounded(Coord oc, rwalk_mod_funcptr func,
+                                void const *priv_ptr, int cells)
+{
+    int i;
+    Coord c = oc;
+    int bailout = 10000;
+
+    func(c, priv_ptr);
+    for (i = 0; (i < cells) && (bailout > 0); --bailout)
+    {
+        oc = c;
+        if (zero_die(2))
+        {
+            c.y += (zero_die(2) ? -1 : 1);
+        }
+        else
+        {
+            c.x += (zero_die(2) ? -1 : 1);
+        }
+        if (c.y <= lvl.min_y())
+        {
+            lvl.grow(North, true);
+        }
+        if (c.y >= lvl.max_y())
+        {
+            lvl.grow(South, true);
+        }
+        if (c.x <= lvl.min_x())
+        {
+            lvl.grow(West, true);
+        }
+        if (c.x >= lvl.max_x())
+        {
+            lvl.grow(East, true);
+        }
+        switch (func(c, priv_ptr))
+        {
+        case 0:
+            /* nothing changed */
+            break;
+        case 1:
+            /* changed normally */
+            ++i;
+            break;
+        case 2:
+            /* reject! */
+            c = oc;
+            break;
+        }
+    }
+    if (bailout < 1)
+    {
+        debug_excavation_bailout();
+    }
+    return c;
+}
+
+/*! \brief Entry point for level generation.
+ *
+ *  \todo Maybe implement support for Lua-based level generators.
+ */
+void build_level(void)
+{
+    int theme_roll;
+    lvl.self.depth = (int16_t) depth;
+    lvl.self.dungeon = 0;
+    lvl.stairs.clear();
+    rng.extract_serialization(saved_state_buffer, saved_state_size);
+    theme_roll = zero_die(depth + 50);
+    if (!zero_die(4))
+    {
+        lvl.layout = LAYOUT_CAVE_INTRUSIONS;
+    }
+    else if ((depth > 1) && !zero_die(6))
+    {
+        lvl.layout = LAYOUT_CAVE_SHRINE;
+    }
+    else
+    {
+        lvl.layout = LAYOUT_CLASSIC_CAVE;
+    }
+    if ((theme_roll < 50) || (depth < 10))
+    {
+        lvl.theme = THEME_NORMAL; /* no restrictions */
+    }
+    else if (theme_roll < 60)
+    {
+        lvl.theme = THEME_UNDEAD;
+    }
+    else if (theme_roll < 80)
+    {
+        lvl.theme = THEME_DRAGONS;
+    }
+    else if (theme_roll < 90)
+    {
+        lvl.theme = THEME_DEMONS;
+    }
+    switch (lvl.layout)
+    {
+    case LAYOUT_CAVE_SHRINE:
+        build_level_shrine();
+        break;
+    case LAYOUT_CAVE_INTRUSIONS:
+        build_level_intrusions();
+        break;
+    case LAYOUT_DUNGEONBASH:
+        build_level_dungeonbash();
+        break;
+    case LAYOUT_CLASSIC_CAVE:
+        build_level_cave();
+        break;
+    }
+}
+
+/*! \brief Build a dungeonbash-style rooms-and-corridors level */
+void build_level_dungeonbash()
+{
+    int chy;
+    int chx;
+    /* We know we're going to use all nine chunks, so create them
+     * immediately. */
+    initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+    /* One room per chunk. */
+    for (chy = 0; chy < lvl.chunks_high; ++chy)
+    {
+        for (chx = 0; chx < lvl.chunks_wide; ++ chx)
+        {
+            /* Smallest allowed room has a 2x2 interior. */
+            Offset room_size = { MIN_ROOM_EDGE + zero_die(5), MIN_ROOM_EDGE + zero_die(5) };
+            /* Each dimension has a 1-in-3 chance to get another boost */
+            if (!zero_die(3))
+            {
+                room_size.y += zero_die(5);
+            }
+            if (!zero_die(3))
+            {
+                room_size.x += zero_die(5);
+            }
+        }
+    }
+}
+
+/*! \brief Excavation function for use with random walks */
+int excavation_write(Coord c, void const *data)
+{
+    int const *data_as_ints = (int const *) data;
+    int const *overwrite = data_as_ints + 2;
+    int newterr = data_as_ints[1];
+    int j;
+    if (lvl.flags_at(c) & MAPFLAG_HARDWALL)
+    {
+        /* Don't bite into hardened walls, but don't waste a step on
+         * them either. */
+        return 2;
+    }
+    for (j = 0; j < data_as_ints[0]; ++j)
+    {
+        if (lvl.terrain_at(c) == overwrite[j])
+        {
+            lvl.set_terrain_at(c, (Terrain) newterr);
+            return 1;
+        }
+    }
+    return 0;
+}
+
+/*! \brief "Intrusion" function for use with random walks */
+int intrusion_write(Coord c, void const *data)
+{
+    Terrain const *tptr = (Terrain const *) data;
+    if ((lvl.terrain_at(c) != WALL) || (lvl.flags_at(c) & MAPFLAG_HARDWALL))
+    {
+        return 0;
+    }
+    /* Don't intrude too closely on the centre of the level */
+    if ((c.y > ((GUIDE_EDGE_SIZE / 2) - 4)) && (c.y < ((GUIDE_EDGE_SIZE / 2) - 4)))
+    {
+        return 2;
+    }
+    if ((c.x > ((GUIDE_EDGE_SIZE / 2) - 4)) && (c.x < ((GUIDE_EDGE_SIZE / 2) - 4)))
+    {
+        return 2;
+    }
+    if (tptr)
+    {
+        lvl.set_terrain_at(c, *tptr);
+    }
+    lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+    return 1;
+}
+
+/*! \brief Do a random walk laying down an exclusion area */
+void place_random_intrusion(Terrain new_wall)
+{
+    Coord c;
+    int intrusion_size = inc_flat(27, 54);
+    do
+    {
+        c.x = zero_die(2) ? inc_flat(1, GUIDE_EDGE_SIZE / 3) : inc_flat((2 * GUIDE_EDGE_SIZE) / 3, GUIDE_EDGE_SIZE - 2);
+        c.y = zero_die(2) ? inc_flat(1, GUIDE_EDGE_SIZE / 3) : inc_flat((2 * GUIDE_EDGE_SIZE) / 3, GUIDE_EDGE_SIZE - 2);
+    } while (lvl.flags_at(c) & MAPFLAG_HARDWALL);
+    run_random_walk(c, intrusion_write, &new_wall, intrusion_size);
+}
+
+/*! \brief Get a valid square to generate a monster on */
+Pass_fail get_levgen_mon_floor(Coord *c)
+{
+    int cell_try;
+    Coord t;
+    for (cell_try = 0; cell_try < 200; cell_try++)
+    {
+        t = lvl.random_point(1);
+        if ((lvl.terrain_at(t) != FLOOR) || (lvl.mon_at(t) != NO_MON))
+        {
+            t = Nowhere;
+            continue;
+        }
+        break;
+    }
+    if (t == Nowhere)
+    {
+        return You_fail;
+    }
+    *c = t;
+    return You_pass;
+}
+
+/*! \brief Populate the level! */
+void populate_level(void)
+{
+    int i;
+    Pass_fail pf;
+    Coord c;
+    int ic;
+    /* Generate some random monsters */
+    for (i = 0; i < 10; i++)
+    {
+        pf = get_levgen_mon_floor(&c);
+        if (pf == You_fail)
+        {
+            continue;
+        }
+        create_mon(NO_PMON, c);
+    }
+    ic = 3 + depth;
+    if (ic > 40)
+    {
+        /* Never create more than 40 items. */
+        ic = 40;
+    }
+    /* Generate some random treasure */
+    for (i = 0; i < ic; i++)
+    {
+        pf = get_levgen_mon_floor(&c);
+        if (pf == You_fail)
+        {
+            continue;
+        }
+        create_obj(NO_POBJ, 1, 0, c);
+    }
+}
+
+/*! \brief Inject the player into the level based on where they arrived from */
+void inject_player(Level_key from)
+{
+    /* For now we are allowing only one linkage between levels */ 
+    Coord c;
+    std::vector<Stair_detail> stair_list;
+    int i = lvl.find_stairs(from, &stair_list);
+    if (i != 0)
+    {
+        c = stair_list[0].pos;
+    }
+    else
+    {
+        fprintf(stderr, "Couldn't find any stairs!\n");
+        abort();
+    }
+    u.pos = c;
+    reloc_player(u.pos);
+}
+
+/*! \brief Look around the player */
+void look_around_you(void)
+{
+    compute_fov();
+    notify_fov();
+}
+
+/*! \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 },
+    { "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 },
+    { "lava", '}', "≈", Gcol_red, TFLAG_fire_hazard },
+    { "water", '}', "≈", Gcol_blue, TFLAG_drown_hazard },
+};
+
+/*! \brief self-explanatory */
+bool terrain_is_opaque(Terrain terr)
+{
+    return terrain_props[terr].flags & TFLAG_opaque;
+}
+
+/*! \brief self-explanatory */
+bool terrain_blocks_beings(Terrain terr)
+{
+    return terrain_props[terr].flags & TFLAG_block_beings;
+}
+
+/*! \brief self-explanatory */
+bool terrain_blocks_missiles(Terrain terr)
+{
+    return terrain_props[terr].flags & TFLAG_block_missile;
+}
+
+/*! \brief Read a Chunk from a FILE.
+ *
+ *  Yes, I know "throw(errno)" is tacky and stupid, but it achieves the
+ *  desired result: the save is obviously garbage, so there's no point
+ *  in finishing loading it.
+ */
+void deserialize_chunk(FILE *fp, Chunk *c)
+{
+    static uint32_t deserializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t deserializable_flags[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t deserializable_regions[CHUNK_EDGE][CHUNK_EDGE];
+    int i;
+    int j;
+    wrapped_fread(deserializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    wrapped_fread(deserializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    wrapped_fread(deserializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    for (i = 0; i < CHUNK_EDGE; ++i)
+    {
+        for (j = 0; j < CHUNK_EDGE; ++j)
+        {
+            c->terrain[i][j] = (Terrain) ntohl(deserializable_terrain[i][j]);
+            c->flags[i][j] = ntohl(deserializable_flags[i][j]);
+            c->region_number[i][j] = ntohl(deserializable_regions[i][j]);
+            /* objs and mons will get accurately set once we've loaded the
+             * objs and mons. */
+            c->objs[i][j] = NO_OBJ;
+            c->mons[i][j] = NO_MON;
+        }
+    }
+}
+
+/*! \brief Write a Chunk out to a FILE.  */
+void serialize_chunk(FILE *fp, Chunk const *c)
+{
+    static uint32_t serializable_terrain[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t serializable_flags[CHUNK_EDGE][CHUNK_EDGE];
+    static uint32_t serializable_regions[CHUNK_EDGE][CHUNK_EDGE];
+    int i;
+    int j;
+    for (i = 0; i < CHUNK_EDGE; ++i)
+    {
+        for (j = 0; j < CHUNK_EDGE; ++j)
+        {
+            serializable_terrain[i][j] = htonl(c->terrain[i][j]);
+            serializable_flags[i][j] = htonl(c->flags[i][j]);
+            serializable_regions[i][j] = htonl(c->region_number[i][j]);
+        }
+    }
+    fwrite(serializable_terrain, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    fwrite(serializable_flags, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+    fwrite(serializable_regions, 4, CHUNK_EDGE * CHUNK_EDGE, fp);
+}
+
+/*! \brief Serialize a Level */
+void serialize_level(FILE *fp, Level const *l)
+{
+    uint32_t tmp;
+    uint32_t tmp_pair[2];
+    uint16_t tmp_shorts[2];
+    int i;
+    int j;
+    tmp_shorts[0] = htons(l->self.dungeon);
+    tmp_shorts[1] = htons(l->self.depth);
+    fwrite(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
+    tmp_pair[0] = htonl(l->origin_off.y);
+    tmp_pair[1] = htonl(l->origin_off.x);
+    fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+    tmp = htonl(l->dead_space);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    tmp = htonl(l->theme);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    tmp = htonl(l->layout);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    tmp = htonl(l->chunks_high);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    tmp = htonl(l->chunks_wide);
+    fwrite(&tmp, sizeof tmp, 1, fp);
+    for (i = 0; i < l->chunks_high; ++i)
+    {
+        tmp_pair[0] = htonl(i);
+        for (j = 0; j < l->chunks_wide; ++j)
+        {
+            if (l->chunks[i][j])
+            {
+                tmp_pair[1] = htonl(j);
+                fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+                serialize_chunk(fp, l->chunks[i][j]);
+            }
+        }
+    }
+    tmp_pair[0] = tmp_pair[1] = ~0u;
+    fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp);
+}
+
+/*! \brief Deserialize a Level
+ *
+ * \todo Throw an exception if the level is malformed e.g. OOB chunk indices.
+ */
+void deserialize_level(FILE *fp, Level *l)
+{
+    uint32_t tmp;
+    uint32_t tmp_pair[2];
+    uint16_t tmp_shorts[2];
+    uint32_t i;
+    uint32_t j;
+    wrapped_fread(tmp_shorts, sizeof tmp_shorts[0], 2, fp);
+    l->self.dungeon = ntohs(tmp_shorts[0]);
+    l->self.depth = (int16_t) ntohs(tmp_shorts[1]);
+    wrapped_fread(tmp_pair, sizeof tmp_pair[0], 2, fp);
+    l->origin_off.y = (int) ntohl(tmp_pair[0]);
+    l->origin_off.x = (int) ntohl(tmp_pair[1]);
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->dead_space = Terrain(ntohl(tmp));
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->theme = level_theme(ntohl(tmp));
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->layout = level_layout(ntohl(tmp));
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->chunks_high = ntohl(tmp);
+    wrapped_fread(&tmp, sizeof tmp, 1, fp);
+    l->chunks_wide = ntohl(tmp);
+    initialize_chunks(l, l->chunks_high, l->chunks_wide, false);
+    do
+    {
+        wrapped_fread(&tmp_pair, sizeof tmp_pair[0], 2, fp);
+        i = ntohl(tmp_pair[0]);
+        j = ntohl(tmp_pair[1]);
+        if (i == ~0u)
+        {
+            break;
+        }
+        l->chunks[i][j] = new Chunk(l->dead_space);
+        deserialize_chunk(fp, l->chunks[i][j]);
+    }
+    while (1);
+}
+
+uint32_t rotate_connection_mask(uint32_t val, int clockwise_steps)
+{
+    clockwise_steps &= 0x3;
+    return ((val << clockwise_steps) | ((val & 0xf) >> (4 - clockwise_steps))) & 0xf;
+}
+
+void level_cleanup(void)
+{
+    int i;
+    for (i = 0; i < MONSTERS_IN_PLAY; ++i)
+    {
+        monsters[i].used = false;
+    }
+    for (i = 0; i < OBJECTS_IN_PLAY; ++i)
+    {
+        objects[i].used = false;
+        objects[i].with_you = false;
+    }
+    drop_all_chunks(&lvl);
+}
+
+/* map.cc */
+// vim:cindent:ts=8:sw=4:expandtab
diff --git a/map.hh b/map.hh
index 22c78cd..d0e98df 100644 (file)
--- a/map.hh
+++ b/map.hh
@@ -2,7 +2,7 @@
  *  \brief Map-related header
  */
 
-/* Copyright 2005-2013 Martin Read
+/* Copyright 2005-2014 Martin Read
  * 
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #ifndef MAP_HH
 #define MAP_HH
 
-#ifndef VICTRIX_ABYSSI_HH
-#include "victrix-abyssi.hh"
+#ifndef CORE_HH
+#include "core.hh"
+#endif
+
+#ifndef OBJECTS_HH
+#include "objects.hh"
 #endif
 
 #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, 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)
 
 #define MAPFLAG_EXPLORED 0x00000001
 #define MAPFLAG_HARDWALL 0x00000002
-enum level_theme {
+enum Level_theme {
     THEME_NORMAL = 0, THEME_DRAGONS, THEME_DEMONS, THEME_UNDEAD
 };
 
-enum level_layout
+enum Level_layout
 {
     LAYOUT_CLASSIC_CAVE = 0,
     LAYOUT_CAVE_INTRUSIONS, /* the cave has hardened intrusions */
     LAYOUT_CAVE_SHRINE, /* the cave contains a shrine */
-    LAYOUT_DUNGEONBASH /* maybe not for this version: dungeonbash-style room grid */
+    LAYOUT_CAVE_PITS, /* "pits"-style cave. */
+    LAYOUT_DUNGEONBASH, /* dungeonbash-style room grid */
+    LAYOUT_CLAUSTROPHOBIA
 };
 
 #define NO_REGION 0xffffffffu
@@ -81,16 +87,27 @@ struct Terrain_props
 //! Array of terrain properties
 extern Terrain_props terrain_props[NUM_TERRAINS];
 
+struct Decal_desc
+{
+    char const *name;
+    char const *description;
+    Gamecolour colour;
+};
+
+extern Decal_desc decal_props[NUM_DECALS];
+
 #define NO_STAIRS (-1)
 
 #define TFLAG_opaque        0x00000001u
 #define TFLAG_block_beings  0x00000002u
 #define TFLAG_block_ether   0x00000004u
 #define TFLAG_block_missile 0x00000008u
-#define TFLAG_portal        0x00000010u
-#define TFLAG_ascend        0x00000020u
-#define TFLAG_descend       0x00000040u
-#define TFLAG_floor         0x00000080u
+#define TFLAG_block_items   0x00000010u
+#define TFLAG_portal        0x00000020u
+#define TFLAG_ascend        0x00000040u
+#define TFLAG_descend       0x00000080u
+#define TFLAG_floor         0x00000100u
+#define TFLAG_wall          0x00000200u
 #define TFLAG_fire_hazard   0x00010000u
 #define TFLAG_fall_hazard   0x00020000u
 #define TFLAG_drown_hazard  0x00040000u
@@ -105,23 +122,26 @@ extern Terrain_props terrain_props[NUM_TERRAINS];
 class Chunk
 {
 public:
-    int objs[CHUNK_EDGE][CHUNK_EDGE];
-    int mons[CHUNK_EDGE][CHUNK_EDGE];
+    Obj_handle objs[CHUNK_EDGE][CHUNK_EDGE];
+    Mon_handle mons[CHUNK_EDGE][CHUNK_EDGE];
     Terrain terrain[CHUNK_EDGE][CHUNK_EDGE];
     uint32_t flags[CHUNK_EDGE][CHUNK_EDGE];
     uint32_t region_number[CHUNK_EDGE][CHUNK_EDGE];
+    Decal_tag decals[CHUNK_EDGE][CHUNK_EDGE];
     Terrain terrain_at(Coord c) const { return terrain[c.y][c.x]; }
     uint32_t flags_at(Coord c) const { return flags[c.y][c.x]; }
     uint32_t region_at(Coord c) const { return region_number[c.y][c.x]; }
-    uint32_t obj_at(Coord c) const { return objs[c.y][c.x]; }
-    uint32_t mon_at(Coord c) const { return mons[c.y][c.x]; }
+    Obj_handle obj_at(Coord c) const { return objs[c.y][c.x]; }
+    Mon_handle mon_at(Coord c) const { return mons[c.y][c.x]; }
+    Decal_tag decal_at(Coord c) const { return decals[c.y][c.x]; }
     void set_terrain_at(Coord c, Terrain t) { terrain[c.y][c.x] = t; }
     void overwrite_flags_at(Coord c, uint32_t f) { flags[c.y][c.x] = f; }
     void set_flags_at(Coord c, uint32_t f) { flags[c.y][c.x] |= f; }
     void clear_flags_at(Coord c, uint32_t f) { flags[c.y][c.x] &= ~f; }
     void set_region_at(Coord c, uint32_t r) { region_number[c.y][c.x] = r; }
-    void set_obj_at(Coord c, int o) { objs[c.y][c.x] = o; }
-    void set_mon_at(Coord c, int m) { mons[c.y][c.x] = m; }
+    void set_obj_at(Coord c, Obj_handle o) { objs[c.y][c.x] = o; }
+    void set_mon_at(Coord c, Mon_handle m) { mons[c.y][c.x] = m; }
+    void set_decal_at(Coord c, Decal_tag d) { decals[c.y][c.x] = d; }
     Chunk(Terrain t = WALL);
 };
 
@@ -162,101 +182,207 @@ extern Stair_detail const Bad_stairs;
 class Level
 {
 public:
+    Level_key self;
     Chunk ***chunks; //!< 16x16 subsections of the level, not necessarily dense
     Offset origin_off; //!< Don't force a map size change to recalculate all Coords
-    int chunks_high; //!< Chunkwise size of level in the y-direction
-    int chunks_wide; //!< Chunkwise size of level in the x-direction
+    uint32_t chunks_high; //!< Chunkwise size of level in the y-direction
+    uint32_t chunks_wide; //!< Chunkwise size of level in the x-direction
     Terrain dead_space; //!< Terrain to fill new chunks with and return for Coords in unpopulated chunks
-    level_theme theme; //!< Will affect monster and maybe item generation
-    level_layout layout; //!< Determines generation algorithm
-    Level_key self;
+    Level_theme theme; //!< Will affect monster and maybe item generation
+    Level_layout layout; //!< Determines generation algorithm
     std::deque<Stair_detail> stairs;
+    void *layout_data; //!< auxiliary data required by layout
+    void *theme_data; //!< auxiliary data required by layout
+    /* Member functions only past this point, please. */
+    Level() :
+        self(No_level), chunks(nullptr), origin_off(Stationary),
+        chunks_high(0), chunks_wide(0), dead_space(WALL), layout_data(nullptr),
+        theme_data(nullptr)
+    {
+    }
     Terrain terrain_at(Coord c) const
     {
-        Coord rc = c + origin_off;
-        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
-        Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
-        return ch ? ch->terrain_at(c2) : dead_space;
+        if (in_bounds(c))
+        {
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            return ch ? ch->terrain_at(c2) : dead_space;
+        }
+        else
+        {
+            return dead_space;
+        }
     }
     void set_terrain_at(Coord c, Terrain t)
     {
-        /* Algorithms that want to potentially stretch the level are
-         * responsible for telling the level to stretch. */
-        Coord rc = c + origin_off;
-        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
-        Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
-        if (ch)
+        if (in_bounds(c))
         {
-            ch->set_terrain_at(c2, t);
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            if (ch)
+            {
+                ch->set_terrain_at(c2, t);
+            }
         }
     }
     uint32_t flags_at(Coord c) const
     {
-        Coord rc = c + origin_off;
-        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
-        Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
-        return ch ? ch->flags_at(c2) : 0;
+        if (in_bounds(c))
+        {
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            return ch ? ch->flags_at(c2) : 0u;
+        }
+        else
+        {
+            return 0u;
+        }
     }
     void set_flags_at(Coord c, uint32_t to_set) 
     {
-        Coord rc = c + origin_off;
-        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
-        Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
-        if (ch)
+        if (in_bounds(c))
         {
-            ch->set_flags_at(c2, to_set);
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            if (ch)
+            {
+                ch->set_flags_at(c2, to_set);
+            }
         }
     }
     void clear_flags_at(Coord c, uint32_t to_clear)
     {
-        Coord rc = c + origin_off;
-        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
-        Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
-        if (ch)
+        if (in_bounds(c))
+        {
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            if (ch)
+            {
+                ch->clear_flags_at(c2, to_clear);
+            }
+        }
+    }
+    Mon_handle mon_at(Coord c) const
+    {
+        if (in_bounds(c))
+        {
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            return ch ? ch->mon_at(c2) : NO_MON;
+        }
+        else
+        {
+            return NO_MON;
+        }
+    }
+    void set_mon_at(Coord c, Mon_handle mon)
+    {
+        if (in_bounds(c))
+        {
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            if (ch)
+            {
+                ch->set_mon_at(c2, mon);
+            }
+        }
+    }
+    Obj_handle obj_at(Coord c) const
+    {
+        if (in_bounds(c))
+        {
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            return ch ? ch->obj_at(c2) : NO_OBJ;
+        }
+        else
+        {
+            return NO_OBJ;
+        }
+    }
+    void set_obj_at(Coord c, Obj_handle obj)
+    {
+        if (in_bounds(c))
         {
-            ch->clear_flags_at(c2, to_clear);
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            if (ch)
+            {
+                ch->set_obj_at(c2, obj);
+            }
         }
     }
-    int mon_at(Coord c) const
+    Decal_tag decal_at(Coord c) const
     {
-        Coord rc = c + origin_off;
-        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
-        Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
-        return ch ? ch->mon_at(c2) : NO_MON;
+        if (in_bounds(c))
+        {
+
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            return ch ? ch->decal_at(c2) : NO_DECAL;
+        }
+        else
+        {
+            return NO_DECAL;
+        }
     }
-    void set_mon_at(Coord c, int mon)
+    void set_decal_at(Coord c, Decal_tag t)
     {
-        Coord rc = c + origin_off;
-        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
-        Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
-        if (ch)
+        if (in_bounds(c))
         {
-            ch->set_mon_at(c2, mon);
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            if (ch)
+            {
+                ch->set_decal_at(c2, t);
+            }
         }
     }
-    int obj_at(Coord c) const
+    uint32_t region_at(Coord c) const
     {
-        Coord rc = c + origin_off;
-        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
-        Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
-        return ch ? ch->obj_at(c2) : NO_OBJ;
+        if (in_bounds(c))
+        {
+
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            return ch ? ch->region_at(c2) : NO_REGION;
+        }
+        else
+        {
+            return NO_REGION;
+        }
     }
-    void set_obj_at(Coord c, int obj)
+    void set_region_at(Coord c, uint32_t r)
     {
-        Coord rc = c + origin_off;
-        Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
-        Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
-        if (ch)
+        if (in_bounds(c))
         {
-            ch->set_obj_at(c2, obj);
+            Coord rc = c + origin_off;
+            Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK };
+            Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT];
+            if (ch)
+            {
+                ch->set_region_at(c2, r);
+            }
         }
     }
     Coord random_point(int margin) const;
     bool in_bounds(Coord c) const
     {
         c += origin_off;
-        return !((c.y < 0) || (c.x < 0) || (c.y >= (chunks_high << CHUNK_SHIFT)) ||
-            (c.x >= (chunks_wide << CHUNK_SHIFT)));
+        return !((c.y < 0) || (c.x < 0) || (c.y >= int32_t(chunks_high << CHUNK_SHIFT)) ||
+                 (c.x >= int32_t(chunks_wide << CHUNK_SHIFT)));
     }
     int min_y() const
     {
@@ -286,17 +412,21 @@ public:
 extern Level lvl;
 
 extern int depth;
-extern enum level_theme current_theme;
-extern enum level_layout current_layout;
 
 void leave_level(void);
 void make_new_level(void);
 void build_level(void);
 void populate_level(void);
-void inject_player(Level_key from);
+void inject_player(Level *l, Level_key from);
 void look_around_you(void);
+bool terrain_is_wall(Terrain terr);
+bool terrain_is_floor(Terrain terr);
 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);
@@ -304,5 +434,5 @@ void deserialize_level(FILE *fp, Level *l);
 void level_cleanup(void);
 #endif
 
-/* map.h */
+/* map.hh */
 // vim:cindent:expandtab
index 4936e8e..8d14745 100644 (file)
--- a/mapgen.hh
+++ b/mapgen.hh
 #define GUIDE_EDGE_CHUNKS 3
 #define GUIDE_EDGE_SIZE (GUIDE_EDGE_CHUNKS << CHUNK_SHIFT)
 
-extern int depth;
-extern enum level_theme current_theme;
-extern enum level_layout current_layout;
-
-void build_level_shrine(void);
-void build_level_intrusions(void);
-void build_level_cave(void);
-void build_level_dungeonbash(void);
-Pass_fail get_levgen_mon_floor(Coord *c);
-int excavation_write(Coord c, void const *data);
-int intrusion_write(Coord c, void const *data);
+void build_level_shrine(Level *l);
+void build_level_intrusions(Level *l);
+void build_level_cave(Level *l);
+void build_level_pits(Level *l, Terrain t = CHASM);
+void build_level_dungeonbash(Level *l);
+void build_level_claustrophobia(Level *l);
+Pass_fail get_levgen_mon_floor(Level *l, Coord *c);
+int excavation_write(Level *l, Coord c, void const *data);
+int intrusion_write(Level *l, Coord c, void const *data);
 void initialize_chunks(Level *l, int height, int width, bool dense);
 void drop_all_chunks(Level *l);
-void place_shrine(Coord topleft, shrine const *sh, uint32_t accept_conns = 0, Terrain shwall = MASONRY_WALL, Terrain shfloor = FLOOR, bool margin_is_wall = false);
-void place_random_intrusion(Terrain new_wall = WALL);
-void place_cave_stairs(void);
+void place_shrine(Level *l, Coord topleft, shrine const *sh, uint32_t accept_conns = 0, Terrain shwall = MASONRY_WALL, Terrain shfloor = FLOOR, bool margin_is_wall = false);
+void place_random_intrusion(Level *l, Terrain new_wall = WALL);
+void place_cave_stairs(Level *l);
 
 #define ROOMCONN_NORTH 0x00000001u
 #define ROOMCONN_EAST 0x00000002u
@@ -61,12 +59,19 @@ void place_cave_stairs(void);
 #define ROOMCONN_WEST 0x00000008u
 uint32_t rotate_connection_mask(uint32_t val, int clockwise_steps);
 
-typedef int (*rwalk_mod_funcptr)(Coord c, void const *data);
-Coord run_random_walk(Coord oc, rwalk_mod_funcptr func,
+typedef int (*rwalk_mod_funcptr)(Level *l, Coord c, void const *data);
+Coord run_random_walk(Level *l, Coord oc, rwalk_mod_funcptr func,
                       void const *priv_ptr, int cells);
-Coord run_random_walk_unbounded(Coord oc, rwalk_mod_funcptr func,
+Coord run_random_walk_unbounded(Level *l, Coord oc, rwalk_mod_funcptr func,
                                 void const *priv_ptr, int cells);
 
+struct Obumb_levgen_excep
+{
+    char const *str;
+    Obumb_levgen_excep() = delete;
+    Obumb_levgen_excep(char const *s) : str(s) { }
+};
+
 #endif
 
 /* mapgen.hh */
diff --git a/mon2.cc b/mon2.cc
index 8e8b10a..2f1af25 100644 (file)
--- a/mon2.cc
+++ b/mon2.cc
@@ -26,7 +26,6 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-/* TODO: Convert missile AI to a new-style AI function. */
 #define MON2_CC
 #include "victrix-abyssi.hh"
 #include "sorcery.hh"
@@ -48,7 +47,7 @@ static void get_drunk_prefs(Coord loc, Offset delta, Coord *pref_pos);
 static void build_ai_cells(struct ai_cell *cells, Coord loc);
 static Comparison ai_cell_compare(struct ai_cell *cell, Offset delta);
 static void get_dodger_prefs(Coord loc, Offset delta, Coord *pref_pos);
-static void get_chase_prefs(int mon, Coord *pref_pos);
+static void get_chase_prefs(Mon_handle mon, Coord *pref_pos);
 
 /* get_drunk_prefs()
  *
@@ -105,9 +104,9 @@ static void get_drunk_prefs(Coord loc, Offset delta, Coord *pref_pos)
  * details.
  */
 
-static void get_chase_prefs(int mon, Coord *pref_pos)
+static void get_chase_prefs(Mon_handle mon, Coord *pref_pos)
 {
-    Mon const *mptr = monsters + mon;
+    Mon const *mptr = mon_snapv(mon);
     Offset delta = mptr->ai_lastpos.delta(mptr->pos);
     Offset step = mysign(delta);
     Offset absdelta = myabs(delta);
@@ -192,7 +191,7 @@ static void get_seeking_prefs(Coord loc, Offset delta, Coord *pref_pos)
     int i;
     int highest_score = AI_REALLY_HATE;
     int tryct;
-    Mon *mptr = monsters + lvl.mon_at(loc);
+    Mon *mptr = mon_snapv(lvl.mon_at(loc));
     *pref_pos = loc;
     build_ai_cells(ai_cells, loc);
     for (i = 0; i < 8; i++)
@@ -367,7 +366,7 @@ static void get_dodger_prefs(Coord loc, Offset delta, Coord *pref_pos)
     Offset absdelta = myabs(delta);
     int highest_score = AI_REALLY_HATE;
     int tryct;
-    Mon *mptr = monsters + lvl.mon_at(loc);
+    Mon *mptr = mon_snapv(lvl.mon_at(loc));
     *pref_pos = loc;
     build_ai_cells(ai_cells, loc);
     /* Build the local dx/dy arrays. */
@@ -444,7 +443,7 @@ void select_space(Coord *pc, Offset delta, int selection_mode)
     Offset absdelta = myabs(delta);
     Coord c;
     Offset step = mysign(delta);
-    Mon *mptr = monsters + lvl.mon_at(*pc);
+    Mon *mptr = mon_snapv(lvl.mon_at(*pc));
     switch (selection_mode)
     {
     case AI_charger:
@@ -566,14 +565,14 @@ void select_space(Coord *pc, Offset delta, int selection_mode)
         break;
     case AI_chaser:
         /* "chase" AI i.e. pursue your last known position. */
-        get_chase_prefs(mptr - monsters, ai_pos);
+        get_chase_prefs(mptr->self, ai_pos);
         c = ai_pos[0];
         break;
     }
     *pc = c;
 }
 
-void mon_acts(int mon)
+void mon_acts(Mon_handle mon)
 {
     Mon *mptr;
     Offset delta;
@@ -582,7 +581,7 @@ void mon_acts(int mon)
     bool meleerange;
     bool oncardinal;
     bool special_used = false;
-    mptr = monsters + mon;
+    mptr = mon_snapv(mon);
     /* dy,dx == direction monster must go to reach you. */
     c = mptr->pos;
     delta = u.pos.delta(c);
@@ -592,17 +591,17 @@ void mon_acts(int mon)
     if (delta.len_cheb() == 0)
     {
         debug_misplaced_monster();
-        mptr->used = false;
+        mptr->flags &= ~MF_USED;
         lvl.set_mon_at(c, NO_MON);
         return;
     }
     if (lvl.mon_at(c) != mon)
     {
         debug_misplaced_monster();
-        mptr->used = false;
+        mptr->flags &= ~MF_USED;
         if (lvl.mon_at(c) != NO_MON)
         {
-            monsters[lvl.mon_at(c)].used = false;
+            monsters[lvl.mon_at(c)].flags &= ~MF_USED;
             lvl.set_mon_at(c, NO_MON);
         }
         return;
@@ -620,14 +619,14 @@ void mon_acts(int mon)
         /* Adjacent! Attack you.  Demons have a 1 in 10 chance of attempting to
          * summon another demon instead of attacking you, if that individual
          * demon has not summoned in the last 100 ticks. */
-        if ((mptr->mon_id == PM_DEMON) && (mptr->next_summon < game_tick) &&
+        if ((mptr->pm_ref == PM_DEMON) && (mptr->next_summon < game_tick) &&
             !zero_die(10))
         {
             summon_demon_near(c);
             mptr->next_summon = game_tick + 100;
             special_used = true;
         }
-        else if (pmon_is_magician(mptr->mon_id))
+        else if (pmon_is_magician(mptr->pm_ref))
         {
             special_used = mon_use_sorcery(mon);
         }
@@ -639,7 +638,7 @@ void mon_acts(int mon)
     else if (mon_visible(mon))
     {
         /* In sight. */
-        if (pmon_is_magician(mptr->mon_id))
+        if (pmon_is_magician(mptr->pm_ref))
         {
             /* Two-thirds of the time, try to use sorcery. */
             if (zero_die(6) < 4)
@@ -654,7 +653,7 @@ void mon_acts(int mon)
              * as if an archer. */
             select_space(&c, delta, AI_archer);
         }
-        else if (pmon_is_archer(mptr->mon_id))
+        else if (pmon_is_archer(mptr->pm_ref))
         {
             if (oncardinal && (zero_die(6) < 3))
             {
@@ -667,7 +666,7 @@ void mon_acts(int mon)
             }
             select_space(&c, delta, AI_archer);
         }
-        else if (pmon_is_smart(mptr->mon_id))
+        else if (pmon_is_smart(mptr->pm_ref))
         {
             select_space(&c, delta, AI_dodger);
         }
@@ -685,7 +684,7 @@ void mon_acts(int mon)
     {
         /* Out of LOS, but awake. Stupid monsters move "drunkenly"; smart
          * monsters (may) seek you out. */
-        if (pmon_is_magician(mptr->mon_id))
+        if (pmon_is_magician(mptr->pm_ref))
         {
             /* Magicians may have spells that are used when
              * you are out of sight.  For example, some magicians
@@ -696,11 +695,11 @@ void mon_acts(int mon)
         {
             return;
         }
-        if (pmon_is_smart(mptr->mon_id))
+        if (pmon_is_smart(mptr->pm_ref))
         {
             select_space(&c, delta, AI_seeker);
         }
-        else if (pmon_is_stupid(mptr->mon_id) || (mptr->ai_lastpos == Nowhere))
+        else if (pmon_is_stupid(mptr->pm_ref) || (mptr->ai_lastpos == Nowhere))
         {
             select_space(&c, delta, AI_drunk);
         }
diff --git a/mon3.cc b/mon3.cc
new file mode 100644 (file)
index 0000000..f344920
--- /dev/null
+++ b/mon3.cc
@@ -0,0 +1,112 @@
+/*! \file mon3.cc
+ *  \brief Death drop tables and similar things
+ */
+
+/* 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.
+ */
+
+#define MON3_CC
+#include "core.hh"
+#include "monsters.hh"
+#include "objects.hh"
+
+static void goblin_death_drop(Coord c)
+{
+    if (!zero_die(4))
+    {
+       create_obj_near(PO_DAGGER, 1, c);
+    }
+}
+
+static void thug_death_drop(Coord c)
+{
+    if (!zero_die(4))
+    {
+       create_obj_near(PO_MACE, 1, c);
+    }
+    else if (!zero_die(3))
+    {
+       create_obj_near(PO_LEATHER_ARMOUR, 1, c);
+    }
+}
+
+static void hunter_death_drop(Coord c)
+{
+    if (!zero_die(6))
+    {
+       create_obj_near(PO_BOW, 1, c);
+    }
+}
+
+static void duellist_death_drop(Coord c)
+{
+    if (!zero_die(6))
+    {
+       create_obj_near(PO_LONG_SWORD, 1, c);
+    }
+}
+
+static void wizard_death_drop(Coord c)
+{
+    if (!zero_die(4))
+    {
+       create_obj_class_near(POCLASS_SCROLL, 1, false, c);
+    }
+    else if (!zero_die(3))
+    {
+       create_obj_class_near(POCLASS_POTION, 1, false, c);
+    }
+}
+
+static void warlord_death_drop(Coord c)
+{
+    if (!zero_die(3))
+    {
+       create_obj_near(PO_RUNESWORD, 1, c);
+    }
+}
+
+static void demon_death_drop(Coord c)
+{
+    if (!zero_die(100))
+    {
+       create_obj_near(PO_DEVIL_SPLEEN, 1, c);
+    }
+}
+
+Death_drop_entry death_drops[] =
+{
+    { PM_GOBLIN, goblin_death_drop },
+    { PM_THUG, thug_death_drop },
+    { PM_GOON, thug_death_drop },
+    { PM_HUNTER, hunter_death_drop },
+    { PM_DUELLIST, duellist_death_drop },
+    { PM_WIZARD, wizard_death_drop },
+    { PM_WARLORD, warlord_death_drop },
+    { PM_DEMON, demon_death_drop },
+    { NO_PMON, nullptr }
+};
+
+/* mon3.cc */
+// vim:cindent
index cbc31bf..30f0a46 100644 (file)
 #include "monsters.hh"
 #include "objects.hh"
 
-Mon monsters[100];
-static int reject_mon(int pm);
+const Mon_handle NO_MON = 0u;
+
+std::map<Mon_handle, Mon> monsters;
+static bool reject_mon(int pm);
 
 /*! \brief Summon some monsters
  *
@@ -45,7 +47,7 @@ int summoning(Coord c, int how_many)
     Offset delta;
     Coord testpos;
     int tryct;
-    int mon;
+    Mon_handle mon;
     int created = 0;
     int pmon;
     for (i = 0; i < how_many; i++)
@@ -98,9 +100,19 @@ int get_random_pmon(void)
     return pm;
 }
 
-int create_mon(int pm_idx, Coord c)
+Mon_handle first_free_mon_handle = 1u;
+static Mon_handle get_first_free_mon(void)
+{
+    if (first_free_mon_handle == NO_MON)
+    {
+        return NO_MON;
+    }
+    return first_free_mon_handle++;
+}
+
+Mon_handle create_mon(int pm_idx, Coord c)
 {
-    int mon;
+    Mon_handle mon;
     if (lvl.mon_at(c) != NO_MON)
     {
         debug_create_mon_occupied(c);
@@ -115,120 +127,51 @@ int create_mon(int pm_idx, Coord c)
             return NO_MON;
         }
     }
-    for (mon = 0; mon < 100; mon++)
-    {
-        if (!monsters[mon].used)
+    mon = get_first_free_mon();
+    if (mon != NO_MON)
+    {
+        Mon m;
+        m.self = mon;
+        m.pm_ref = pm_idx;
+        m.flags = MF_USED | MF_ALIVE;
+        m.pos = c;
+        m.ai_lastpos = Nowhere;
+        m.hpmax = permons[pm_idx].hp + ood(permons[pm_idx].power, 1);
+        m.hpcur = m.hpmax;
+        m.mtohit = permons[pm_idx].mtohit + ood(permons[pm_idx].power, 3);
+        m.defence = permons[pm_idx].defence + ood(permons[pm_idx].power, 3);
+        m.mdam = permons[pm_idx].mdam + ood(permons[pm_idx].power, 5);
+        if (permons[pm_idx].rdam != NO_ATK)
         {
-            monsters[mon].mon_id = pm_idx;
-            monsters[mon].used = true;
-            monsters[mon].pos = c;
-            monsters[mon].ai_lastpos = Nowhere;
-            monsters[mon].hpmax = permons[pm_idx].hp + ood(permons[pm_idx].power, 1);
-            monsters[mon].hpcur = monsters[mon].hpmax;
-            monsters[mon].mtohit = permons[pm_idx].mtohit + ood(permons[pm_idx].power, 3);
-            monsters[mon].defence = permons[pm_idx].defence + ood(permons[pm_idx].power, 3);
-            monsters[mon].mdam = permons[pm_idx].mdam + ood(permons[pm_idx].power, 5);
-            if (permons[pm_idx].rdam != NO_ATK)
-            {
-                monsters[mon].rtohit = permons[pm_idx].rtohit + ood(permons[pm_idx].power, 3);
-                monsters[mon].rdam = permons[pm_idx].rdam + ood(permons[pm_idx].power, 5);
-            }
-            else
-            {
-                monsters[mon].rtohit = NO_ATK;
-                monsters[mon].rdam = NO_ATK;
-            }
-            monsters[mon].awake = false;
-            lvl.set_mon_at(c, mon);
-            if (mon_visible(mon))
-            {
-                notify_new_mon_at(c, mon);
-            }
-            return mon;
+            m.rtohit = permons[pm_idx].rtohit + ood(permons[pm_idx].power, 3);
+            m.rdam = permons[pm_idx].rdam + ood(permons[pm_idx].power, 5);
+        }
+        else
+        {
+            m.rtohit = NO_ATK;
+            m.rdam = NO_ATK;
         }
+        monsters[mon] = m;
+        lvl.set_mon_at(c, mon);
+        if (mon_visible(mon))
+        {
+            notify_new_mon_at(c, mon);
+        }
+        return mon;
     }
     return NO_MON;
 }
 
-void death_drop(int mon)
+/*! \brief Handle a monster's death drop. */
+void death_drop(Mon_handle mon)
 {
-    Mon *mptr = monsters + mon;
-    int pm = mptr->mon_id;
-    Coord c = mptr->pos;
-    Offset delta;
-    int tryct = 0;
-    while (((lvl.obj_at(c) != NO_OBJ) || (lvl.terrain_at(c) != FLOOR)) &&
-           (tryct < 100))
+    Mon const *mptr = mon_snap(mon);
+    for (int i = 0; death_drops[i].func != nullptr; ++i)
     {
-        delta = random_step();
-        tryct++;
-        c += delta;
-    }
-    if (tryct >= 100)
-    {
-        return;
-    }
-    switch (pm)
-    {
-    case PM_GOBLIN:
-        if (!zero_die(4))
+        if (death_drops[i].pm_ref == mptr->pm_ref)
         {
-            create_obj(PO_DAGGER, 1, 0, c);
+            death_drops[i].func(mptr->pos);
         }
-        break;
-    case PM_THUG:
-    case PM_GOON:
-        if (!zero_die(4))
-        {
-            create_obj(PO_MACE, 1, 0, c);
-        }
-        else if (!zero_die(3))
-        {
-            create_obj(PO_LEATHER_ARMOUR, 1, 0, c);
-        }
-        break;
-    case PM_HUNTER:
-        if (!zero_die(6))
-        {
-            create_obj(PO_BOW, 1, 0, c);
-        }
-        break;
-    case PM_DUELLIST:
-        if (!zero_die(6))
-        {
-            create_obj(PO_LONG_SWORD, 1, 0, c);
-        }
-        break;
-    case PM_WIZARD:
-        if (!zero_die(4))
-        {
-            create_obj_class(POCLASS_SCROLL, 1, 0, c);
-        }
-        else if (!zero_die(3))
-        {
-            create_obj_class(POCLASS_POTION, 1, 0, c);
-        }
-        break;
-    case PM_WARLORD:
-        if (!zero_die(3))
-        {
-            create_obj(PO_RUNESWORD, 1, 0, c);
-        }
-        break;
-    case PM_DEMON:
-        if (!zero_die(100))
-        {
-            create_obj(PO_DEVIL_SPLEEN, 1, 0, c);
-        }
-        break;
-    case PM_DEFILER:
-        if (!zero_die(50))
-        {
-            create_obj(PO_DEVIL_SPLEEN, 1, 0, c);
-        }
-        break;
-    default:
-        break;
     }
 }
 
@@ -258,28 +201,22 @@ bool Mon::can_pass(Coord c) const
         /* Let's *not* stuff all the wall types into a switch, eh? */
         return false;
     }
-    /* Keep the switch, so that we can maintain a convenient distinction
-     * between floor hazards and volumetric hazards. */
-    switch (terr)
+    if (terrain_is_hot(terr) && !can_fly() && !resists(DT_FIRE))
     {
-    case LAVA:
-        if (!can_fly() && !resists(DT_FIRE))
-        {
-            return false;
-        }
-        break;
-    case WATER:
-        if (!can_fly() && !resists(DT_DROWNING))
-        {
-            return false;
-        }
-    default:
-        break;
+        return false;
+    }
+    if (terrain_drowns(terr) && !can_fly() && !resists(DT_DROWNING))
+    {
+        return false;
+    }
+    if (terrain_gapes(terr) && !can_fly())
+    {
+        return false;
     }
     return true;
 }
 
-void heal_mon(int mon, int amount, int cansee)
+void heal_mon(Mon_handle mon, int amount, int cansee)
 {
     if (amount > (monsters[mon].hpmax - monsters[mon].hpcur))
     {
@@ -295,36 +232,63 @@ void heal_mon(int mon, int amount, int cansee)
     }
 }
 
-void unplace_mon(int mon)
+void unplace_mon(Mon_handle mon)
 {
     lvl.set_mon_at(monsters[mon].pos, NO_MON);
-    monsters[mon].used = false;
+    monsters[mon].flags &= ~MF_USED;
+}
+
+void apply_liquid(int pm, Coord c)
+{
+    Decal_tag decal = pmon_fluid_decal(pm);
+    if (pm != NO_DECAL)
+    {
+        lvl.set_decal_at(c, decal);
+    }
 }
 
 /*! \brief Handle the death of a monster
  *
  * \todo Support special effects on monster death
  */
-void kill_mon(int mon, bool by_you)
+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;
+    bool detonate =
+        (pmon_explodes(pm) || (pmon_leaves_corpse(pm) && explode_corpse));
+    if (!terrain_blocks_items(t))
+    {
+        apply_liquid(pm, mptr->pos);
+    }
+    if (detonate)
+    {
+        detonate_mon(mon);
+    }
+    else if (pmon_leaves_corpse(pm))
+    {
+        create_corpse(pm, mptr->pos);
+    }
     death_drop(mon); // phat lewt!
-    monsters[mon].hpcur = -1; // Set HP to -1 so nothing will think it's alive
+    mptr->flags &= ~MF_ALIVE;
+    monsters[mon].hpcur = -1;
     unplace_mon(mon); // cleanup
     if (by_you)
     {
         notify_player_killed_mon(mon);
-        gain_experience(permons[monsters[mon].mon_id].exp);
+        gain_experience(permons[monsters[mon].pm_ref].exp);
     }
-    else if (mon_visible(mon))
+    else if (mon_visible(mon) && !detonate)
     {
         notify_mon_dies(mon);
     }
 }
 
-void damage_mon(int mon, int amount, bool by_you)
+void damage_mon(Mon_handle mon, int amount, bool by_you)
 {
     Mon *mptr;
-    mptr = monsters + mon;
+    mptr = mon_snapv(mon);
     if (amount >= mptr->hpcur)
     {
         kill_mon(mon, by_you);
@@ -335,22 +299,22 @@ void damage_mon(int mon, int amount, bool by_you)
     }
 }
 
-int reject_mon(int pm)
+bool reject_mon(int pm)
 {
     if ((permons[pm].power > depth) || (zero_die(100) < permons[pm].rarity))
     {
-        return 1;
+        return true;
     }
-    return 0;
+    return false;
 }
 
-Pass_fail teleport_mon_to_you(int mon)
+Pass_fail teleport_mon_to_you(Mon_handle mon)
 {
     int tryct;
     Offset delta;
     Coord c;
     int success = 0;
-    Mon *mptr = monsters + mon;
+    Mon *mptr = mon_snapv(mon);
     Coord oldpos = mptr->pos;
     for (tryct = 0; tryct < 40; tryct++)
     {
@@ -371,7 +335,7 @@ Pass_fail teleport_mon_to_you(int mon)
     return You_fail;
 }
 
-Pass_fail teleport_mon(int mon)
+Pass_fail teleport_mon(Mon_handle mon)
 {
     Pass_fail rval = You_fail;
     int cell_try;
@@ -389,10 +353,10 @@ Pass_fail teleport_mon(int mon)
     return rval;
 }
 
-int knockback_mon(int mon, Offset step, bool cansee, bool by_you)
+int knockback_mon(Mon_handle mon, Offset step, bool cansee, bool by_you)
 {
     /* 0 = blocked, 1 = knocked, 2 = killed */
-    Mon *mptr = monsters + mon;
+    Mon *mptr = mon_snapv(mon);
     Coord c = mptr->pos + step;
     Coord savedpos = mptr->pos;
     Terrain terr = lvl.terrain_at(c);
@@ -472,9 +436,9 @@ int knockback_mon(int mon, Offset step, bool cansee, bool by_you)
     return 1;
 }
 
-void reloc_mon(int mon, Coord newpos)
+void reloc_mon(Mon_handle mon, Coord newpos)
 {
-    Mon *mptr = monsters + mon;
+    Mon *mptr = mon_snapv(mon);
     lvl.set_mon_at(mptr->pos, NO_MON);
     notify_new_mon_at(mptr->pos, NO_MON);
     mptr->pos = newpos;
@@ -482,10 +446,10 @@ void reloc_mon(int mon, Coord newpos)
     notify_new_mon_at(mptr->pos, mon);
 }
 
-void move_mon(int mon, Coord c)
+void move_mon(Mon_handle mon, Coord c)
 {
     Mon *mptr;
-    mptr = monsters + mon;
+    mptr = mon_snapv(mon);
     if (!mptr->can_pass(c))
     {
         debug_mon_invalid_move(mon, c);
@@ -502,7 +466,7 @@ void move_mon(int mon, Coord c)
 void summon_demon_near(Coord c)
 {
     Coord c2 = c + random_step();
-    int mon;
+    Mon_handle mon;
     if ((lvl.terrain_at(c2) == FLOOR) && (lvl.mon_at(c2) == NO_MON) &&
         (c2 != u.pos))
     {
@@ -511,14 +475,38 @@ void summon_demon_near(Coord c)
     }
 }
 
-bool mon_visible(int 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);
+    notify_mon_detonates(mon, decal);
+    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;
-    if (monsters[mon].used == 0)
+    Mon const *mptr = mon_snap(mon);
+    if (!(mptr->flags & MF_USED))
     {
         return false;
     }
-    delta = monsters[mon].pos.delta(u.pos);
+    delta = mptr->pos.delta(u.pos);
     if (delta.len_cheb() <= MAX_FOV_RADIUS)
     {
         return (player_fov.affected[MAX_FOV_RADIUS + delta.y][MAX_FOV_RADIUS + delta.x]) & Visflag_central;
@@ -529,14 +517,15 @@ bool mon_visible(int mon)
     }
 }
 
-void update_mon(int mon)
+void update_mon(Mon_handle mon)
 {
     int cansee;
-    if (monsters[mon].hpcur < monsters[mon].hpmax)
+    Mon *mptr = mon_snapv(mon);
+    if (mptr->hpcur < mptr->hpmax)
     {
         cansee = mon_visible(mon);
-        // TODO modify regen handling to use flags/fields instead of switching on mon_id
-        switch (monsters[mon].mon_id)
+        // TODO modify regen handling to use flags/fields instead of switching on pm_ref
+        switch (mptr->pm_ref)
         {
         case PM_TROLL:
             if (!(game_tick % 10))
@@ -568,19 +557,19 @@ bool Mon::resists(Damtyp dt) const
     switch (dt)
     {
     case DT_COLD:
-        return pmon_resists_cold(mon_id);
+        return pmon_resists_cold(pm_ref);
     case DT_FIRE:
-        return pmon_resists_fire(mon_id);
+        return pmon_resists_fire(pm_ref);
     case DT_POISON:
-        return pmon_resists_poison(mon_id);
+        return pmon_resists_poison(pm_ref);
     case DT_NECRO:
-        return pmon_resists_necro(mon_id);
+        return pmon_resists_necro(pm_ref);
     case DT_ELEC:
-        return pmon_resists_elec(mon_id);
+        return pmon_resists_elec(pm_ref);
     case DT_KNOCKBACK:
-        return pmon_resists_knockback(mon_id);
+        return pmon_resists_knockback(pm_ref);
     case DT_DROWNING:
-        return pmon_resists_drowning(mon_id);
+        return pmon_resists_drowning(pm_ref);
     default:
         return false;
     }
@@ -588,13 +577,41 @@ bool Mon::resists(Damtyp dt) const
 
 bool Mon::is_ethereal(void) const
 {
-    return pmon_is_ethereal(mon_id);
+    return pmon_is_ethereal(pm_ref);
 }
 
 bool Mon::can_fly(void) const
 {
-    return pmon_can_fly(mon_id);
+    return pmon_can_fly(pm_ref);
+}
+
+void asprint_mon_name(char **buf, Mon_handle mon, int article)
+{
+    Mon const *mptr = mon_snap(mon);
+    int i;
+    switch (article)
+    {
+    case 0:
+        i = asprintf(buf, "a%s %s", is_vowel(permons[mptr->pm_ref].name[0]) ? "n" : "", permons[mptr->pm_ref].name);
+        break;
+    case 1:
+        i = asprintf(buf, "the %s", permons[mptr->pm_ref].name);
+        break;
+    case 2:
+        i = asprintf(buf, "A%s %s", is_vowel(permons[mptr->pm_ref].name[0]) ? "n" : "", permons[mptr->pm_ref].name);
+        break;
+    case 3:
+        i = asprintf(buf, "The %s", permons[mptr->pm_ref].name);
+        break;
+    default:
+        i = -1;
+    }
+    if (i == -1)
+    {
+        // TODO wibble
+    }
+    return;
 }
 
-/* monsters.c */
-// vim:cindent
+/* monsters.cc */
+// vim:cindent:expandtab
index fc8fd38..7bdeaa3 100644 (file)
 #endif
 
 /* XXX struct mon */
-#define MONSTERS_IN_PLAY 100
+
+#define MF_AWAKE    0x00000001u // gets turns
+#define MF_ALIVE    0x00000002u // gets displayed
+#define MF_USED     0x00000004u // legacy cruft
+
 class Mon {
 public:
-    int mon_id;
+    Mon_handle self;
+    int pm_ref;
     Coord pos;
     Coord ai_lastpos;
-    bool used;
+    uint32_t flags;
     int hpmax;  /* Improved by OOD rating at 1:1. */
     int hpcur;  /* <= 0 is dead. */
     int mtohit; /* Improved by OOD rating at 1:3. */
@@ -54,39 +59,61 @@ public:
     int rdam;   /* Improved by OOD rating at 1:5. */
     bool awake;
     int next_summon;
+    /* member functions only past this point please */
     bool resists(Damtyp dt) const;
     bool can_pass(Coord c) const;
     bool can_fly(void) const;
     bool is_ethereal(void) const;
 };
-extern Mon monsters[MONSTERS_IN_PLAY];
+#include <map>
+extern std::map<Mon_handle, Mon> monsters;
+extern Mon_handle first_free_mon_handle;
+
+inline Mon *mon_snapv(Mon_handle mon)
+{
+    auto iter = monsters.find(mon);
+    return ((iter == monsters.end()) ? nullptr : &(iter->second));
+}
+
+inline Mon const *mon_snap(Mon_handle mon)
+{
+    auto iter = monsters.find(mon);
+    return ((iter == monsters.end()) ? nullptr : &(iter->second));
+}
+
+struct Death_drop_entry
+{
+    int pm_ref;
+    void (*func)(Coord c);
+};
+
+extern Death_drop_entry death_drops[];
 
-#define NO_MON (-1)
 #define PLAYER_MON (-2)
 
-/* XXX monsters.c data and funcs */
-extern void update_mon(int mon);
-extern void mon_acts(int mon);
-extern void death_drop(int mon);
-extern void print_mon_name(int mon, int article);
-extern void summon_demon_near(Coord  c);
-extern int create_mon(int pm_idx, Coord c);
-extern int summoning(Coord c, int how_many);
-extern int ood(int power, int ratio);
-extern int get_random_pmon(void);
-extern void damage_mon(int mon, int amount, bool by_you);
-extern bool mon_visible(int mon);
-extern int knockback_mon(int mon, Offset step, bool cansee, bool by_you);
-extern void move_mon(int mon, Coord c);
-extern void reloc_mon(int mon, Coord c);
-extern Pass_fail teleport_mon(int mon);       /* Randomly relocate monster. */
-extern Pass_fail teleport_mon_to_you(int mon);        /* Relocate monster to your vicinity. */
-extern void heal_mon(int mon, int amount, int cansee);
-extern void kill_mon(int mon, bool by_you);
-extern void unplace_mon(int mon);
+void update_mon(Mon_handle mon);
+void mon_acts(Mon_handle mon);
+void death_drop(Mon_handle mon);
+void asprint_mon_name(char **buf, Mon_handle mon, int article);
+void summon_demon_near(Coord  c);
+Mon_handle create_mon(int pm_idx, Coord c);
+int summoning(Coord c, int how_many);
+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);
+void reloc_mon(Mon_handle mon, Coord c);
+Pass_fail teleport_mon(Mon_handle mon);       /* Randomly relocate monster. */
+Pass_fail teleport_mon_to_you(Mon_handle mon);        /* Relocate monster to your vicinity. */
+void heal_mon(Mon_handle mon, int amount, int cansee);
+void kill_mon(Mon_handle mon, bool by_you, bool explode_corpse = false);
+void unplace_mon(Mon_handle mon);
 
 /* XXX mon2.c data and funcs */
-extern void select_space(int *py, int *px, int dy, int dx, int selection_mode);
+void select_space(Coord *pc, Offset delta, int selection_mode);
 
 #endif
 
index 70e881b..09a5776 100644 (file)
@@ -202,6 +202,12 @@ void notify_food_use(int amount, int hunger_severity)
     }
 }
 
+void notify_learned_skill(Skill_id skill)
+{
+    print_msg("You learned a new skill: %s\n", skill_props[skill].name);
+    print_msg("%s\n", skill_props[skill].desc);
+}
+
 void notify_leadfoot_recovered(void)
 {
     print_msg("You shed your feet of lead.\n");
@@ -222,31 +228,43 @@ 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");
+    print_msg("You never learned 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);
+    display_update();
 }
 
 void notify_obj_at(Coord c)
 {
-    print_msg("You see here ");
-    print_obj_name(lvl.obj_at(c));
-    print_msg(".\n");
+    char *s;
+    asprint_obj_name(&s, lvl.obj_at(c));
+    print_msg("You see here %s.\n", s);
+    free(s);
 }
 
 void debug_move_oob(Coord c)
@@ -269,16 +287,22 @@ void notify_ascent_blocked(void)
     print_msg("A mysterious force prevents you climbing the stairs.\n");
 }
 
+void notify_inert_portal(void)
+{
+    print_msg("This side of the portal is completely inert.\n");
+}
+
 void notify_wasted_gain(void)
 {
     print_msg("You feel disappointed.\n");
 }
 
-void notify_summon_help(int mon, bool success)
+void notify_summon_help(Mon_handle mon, bool success)
 {
     /* Do the summoning... */
-    print_mon_name(mon, 3);
-    print_msg(" calls for help...\n");
+    char *s;
+    asprint_mon_name(&s, mon, 3);
+    print_msg("%s calls for help...\n", s);
     if (success)
     {
         print_msg("... and gets it.\n");
@@ -287,9 +311,10 @@ void notify_summon_help(int mon, bool success)
     {
         print_msg("... luckily for you, help wasn't listening.\n");
     }
+    free(s);
 }
 
-void notify_summon_demon(int mon)
+void notify_summon_demon(Mon_handle mon)
 {
     if (mon != NO_MON)
     {
@@ -301,9 +326,10 @@ void notify_summon_demon(int mon)
     }
 }
 
-void notify_mon_disappear(int mon)
+void notify_mon_disappear(Mon_handle mon)
 {
-    print_mon_name(mon, 3);
+    char *s;
+    asprint_mon_name(&s, mon, 3);
     print_msg(" vanishes in a puff of smoke.\n");
 }
 
@@ -345,10 +371,12 @@ void notify_hellfire_hit(bool resisted)
     print_msg("The fires of hell %s\n", resisted ? "lightly singe you." : "burn you!");
 }
 
-void notify_monster_cursing(int mon)
+void notify_monster_cursing(Mon_handle mon)
 {
-    print_mon_name(mon, 3);
-    print_msg(" points at you and curses horribly.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 3);
+    print_msg("%s points at you and curses horribly.\n", s);
+    free(s);
 }
 
 void notify_no_attackee(void)
@@ -361,25 +389,35 @@ void notify_knockback_mon_pass(void)
     print_msg("Your foe is knocked backwards by the force of the shot.\n");
 }
 
-void notify_player_miss(int mon)
+void notify_player_miss(Mon_handle mon)
+{
+    char *s;
+    asprint_mon_name(&s, mon, 1);
+    print_msg("You miss %s.\n", s);
+    free(s);
+}
+
+void notify_player_charge_mon(Mon_handle mon)
 {
-    print_msg("You miss ");
-    print_mon_name(mon, 1);
-    print_msg(".\n");
+    char *s;
+    asprint_mon_name(&s, mon, 1);
+    print_msg("You charge %s.\n", s);
+    free(s);
 }
 
-void notify_player_hit_mon(int mon)
+void notify_player_hit_mon(Mon_handle mon)
 {
-    print_msg("You hit ");
-    print_mon_name(mon, 1);
-    print_msg(".\n");
+    char *s;
+    asprint_mon_name(&s, mon, 1);
+    print_msg("You hit %s.\n", s);
+    free(s);
 }
 
-void notify_player_combo_powatk(int mon)
+void notify_player_combo_powatk(Mon_handle mon)
 {
-    print_msg("You deal a powerful blow to ");
-    print_mon_name(mon, 1);
-    print_msg(".\n");
+    char *s;
+    asprint_mon_name(&s, mon, 1);
+    print_msg("You deal a powerful blow to %s.\n", s);
 }
 
 void notify_point_blank_warning(void)
@@ -387,97 +425,135 @@ void notify_point_blank_warning(void)
     print_msg(Msg_prio::Alert, "Using a bow at such close quarters is awkward, and you are unlikely to hit your target.\n");
 }
 
-void notify_player_shot_terrain(int obj, Coord c)
+void notify_player_shot_terrain(Obj_handle obj, Coord c)
 {
-    print_msg("Your %s hits the %s.\n", (objects[obj].obj_id == PO_CROSSBOW) ? "bolt" : "arrow", terrain_props[lvl.terrain_at(c)].name);
+    print_msg("Your %s hits the %s.\n", (objects[obj].po_ref == PO_CROSSBOW) ? "bolt" : "arrow", terrain_props[lvl.terrain_at(c)].name);
 }
 
-void notify_ring_boost(int mon, int pobj)
+void notify_ring_boost(Mon_handle mon, int pobj)
 {
+    char *s;
+    asprint_mon_name(&s, mon, 1);
     switch (pobj)
     {
     case PO_FIRE_RING:
-        print_msg("Your ring burns ");
-        print_mon_name(mon, 1);
-        print_msg("!\n");
+        print_msg("Your ring burns %s!\n", s);
         break;
     case PO_VAMPIRE_RING:
-        print_msg("Your ring drains ");
-        print_mon_name(mon, 1);
-        print_msg("!\n");
+        print_msg("Your ring drains %s!\n", s);
         break;
     case PO_FROST_RING:
-        print_msg("Your ring freezes ");
-        print_mon_name(mon, 1);
-        print_msg("!\n");
+        print_msg("Your ring freezes %s!\n", s);
         break;
     }
+    free(s);
+}
+
+void notify_flying_leap(void)
+{
+    print_msg("You vault through the air.\n");
 }
 
-void notify_player_hurt_mon(int mon, int damage)
+
+void notify_player_hurt_mon(Mon_handle mon, int damage)
 {
     print_msg("You do %d damage.\n", damage);
 }
 
-void notify_player_killed_mon(int mon)
+void notify_player_killed_mon(Mon_handle mon)
 {
     newsym(monsters[mon].pos);
-    print_msg("You kill ");
     if (occupant_visible(monsters[mon].pos))
     {
-        print_mon_name(mon, 1);
+       char *s;
+       asprint_mon_name(&s, mon, 1);
+       print_msg("You kill %s!\n", s);
+       free(s);
     }
     else
     {
-        print_msg("something");
+       print_msg("You kill something...\n");
+    }
+}
+
+void notify_mon_dies(Mon_handle mon)
+{
+    newsym(monsters[mon].pos);
+    if (occupant_visible(monsters[mon].pos))
+    {
+       char *s;
+       asprint_mon_name(&s, mon, 2);
+       print_msg("%s dies.\n", &s);
+       free(s);
     }
-    print_msg("!\n");
 }
 
-void notify_mon_dies(int mon)
+void notify_mon_detonates(Mon_handle mon, Decal_tag tag)
 {
     newsym(monsters[mon].pos);
     if (occupant_visible(monsters[mon].pos))
     {
-        print_mon_name(mon, 2);
-        print_msg(" dies.\n");
+       char *s;
+       asprint_mon_name(&s, mon, 2);
+       if (tag != NO_DECAL)
+       {
+           print_msg("%s explodes in a welter of %s!\n", s, decal_props[tag]);
+
+       }
+       else
+       {
+           print_msg("%s explodes!\n", s);
+       }
+       free(s);
     }
 }
 
-void notify_knockback_mon_resisted(int mon)
+void notify_knockback_mon_resisted(Mon_handle mon)
 {
-    print_mon_name(mon, 3);
-    print_msg(" wobbles slightly.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 3);
+    print_msg("%s wobbles slightly.\n", s);
+    free(s);
 }
 
-void notify_knockback_mon_blocked(int mon)
+void notify_knockback_mon_blocked(Mon_handle mon)
 {
-    print_mon_name(mon, 3);
-    print_msg(" is slammed against the wall.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 3);
+    print_msg("%s is slammed against the wall.\n", s);
+    free(s);
 }
 
-void notify_knockback_mon_hover_lava(int mon)
+void notify_knockback_mon_hover_lava(Mon_handle mon)
 {
-    print_mon_name(mon, 3);
-    print_msg(" is hurled out over the lava.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 3);
+    print_msg("%s is hurled out over the lava.\n", s);
+    free(s);
 }
 
-void notify_knockback_mon_hover_water(int mon)
+void notify_knockback_mon_hover_water(Mon_handle mon)
 {
-    print_mon_name(mon, 3);
-    print_msg(" is hurled out over the water.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 3);
+    print_msg("%s is hurled out over the water.\n", s);
+    free(s);
 }
 
-void notify_knockback_mon_immersed_lava(int mon)
+void notify_knockback_mon_immersed_lava(Mon_handle mon)
 {
-    print_mon_name(mon, 3);
-    print_msg(" tumbles into a pool of molten rock.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 3);
+    print_msg("%s tumbles into a pool of molten rock.\n", s);
+    free(s);
 }
 
-void notify_knockback_mon_immersed_water(int mon)
+void notify_knockback_mon_immersed_water(Mon_handle mon)
 {
-    print_mon_name(mon, 3);
-    print_msg(" tumbles into the water.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 3);
+    print_msg("%s tumbles into the water.\n", s);
+    free(s);
 }
 
 void notify_knockback_mon_lava_offscreen(void)
@@ -490,48 +566,57 @@ void notify_knockback_mon_water_offscreen(void)
     print_msg("Splash!\n");
 }
 
-void notify_mon_disappears(int mon, Coord oldpos)
+void notify_mon_disappears(Mon_handle mon, Coord oldpos)
 {
 }
 
-void notify_mon_appears(int mon)
+void notify_mon_appears(Mon_handle mon)
 {
-    print_mon_name(mon, 2);
-    print_msg(" appears in a puff of smoke.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 2);
+    print_msg("%s appears in a puff of smoke.\n", s);
+    free(s);
 }
 
-void notify_mon_hit_armour(int mon)
+void notify_mon_hit_armour(Mon_handle mon)
 {
-    print_msg("Your armour deflects ");
-    print_mon_name(mon, 1);
-    print_msg("'s blow.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 1);
+    print_msg("Your armour deflects %s's blow.\n", s);
+    free(s);
 }
 
-void notify_mon_missed_player(int mon)
+void notify_mon_missed_player(Mon_handle mon)
 {
-    print_mon_name(mon, 3);
-    print_msg(" misses you.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 3);
+    print_msg("%s misses you.\n", s);
+    free(s);
 }
 
-void notify_mon_hit_player(int mon)
+void notify_mon_hit_player(Mon_handle mon)
 {
-    print_mon_name(mon, 3);
-    print_msg(" hits you.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 3);
+    print_msg("%s hits you.\n", s);
+    free(s);
 }
 
-void notify_mon_ranged_attack(int mon)
+void notify_mon_ranged_attack(Mon_handle mon)
 {
-    int pm = monsters[mon].mon_id;
+    int pm = monsters[mon].pm_ref;
     Damtyp dt = permons[pm].rdtyp;
-    print_mon_name(mon, 3);
+    char *s;
+    asprint_mon_name(&s, mon, 3);
     if (dt == DT_PHYS)
     {
-        print_msg(" %s at you!\n", permons[pm].shootverb);
+        print_msg("%s %s at you!\n", s, permons[pm].shootverb);
     }
     else
     {
-        print_msg(" %s %s at you!\n", permons[pm].shootverb, damtype_names[dt]);
+        print_msg("%s %s %s at you!\n", s, permons[pm].shootverb, damtype_names[dt]);
     }
+    free(s);
 }
 
 void notify_mon_ranged_hit_mon(int er, int ee)
@@ -540,12 +625,12 @@ void notify_mon_ranged_hit_mon(int er, int ee)
     print_msg("It hits a bystander.\n");
 }
 
-void notify_mon_ranged_missed_player(int mon)
+void notify_mon_ranged_missed_player(Mon_handle mon)
 {
     print_msg("It misses you.\n");
 }
 
-void notify_mon_ranged_hit_player(int mon)
+void notify_mon_ranged_hit_player(Mon_handle mon)
 {
     print_msg("It hits you!\n");
 }
@@ -586,12 +671,12 @@ void notify_zap_powerless_weapon(void)
     print_msg("Your current weapon seems to have no magic powers to activate.\n");
 }
 
-void notify_armour_equip(int obj)
+void notify_armour_equip(Obj_handle obj)
 {
-    Permobj *pobj = permobjs + objects[obj].obj_id;
+    Permobj *pobj = permobjs + objects[obj].po_ref;
     if (pobj->flags[0] & POF_NOTIFY_EQUIP)
     {
-        switch (objects[u.armour].obj_id)
+        switch (objects[u.armour].po_ref)
         {
         case PO_SET_OF_RIBBONS:
             if (u.sybaritic())
@@ -626,30 +711,20 @@ void notify_armour_equip(int obj)
     }
     else
     {
-        print_msg("Wearing ");
-        print_obj_name(u.armour);
-        print_msg(".\n");
+       char *s;
+       asprint_obj_name(&s, u.armour);
+        print_msg("Wearing %s.\n", s);
+       free(s);
     }
 }
 
-void notify_armour_unequip(int obj)
+void notify_armour_unequip(Obj_handle obj)
 {
-    Permobj *pobj = permobjs + objects[obj].obj_id;
-    // TODO add unequip messages for NOTIFY_EQUIP armours.
+    Permobj *pobj = permobjs + objects[obj].po_ref;
     if (pobj->flags[0] & POF_NOTIFY_EQUIP)
     {
-        switch (objects[obj].obj_id)
+        switch (objects[obj].po_ref)
         {
-        case PO_SET_OF_RIBBONS:
-            if (u.sybaritic())
-            {
-                print_msg("Reluctantly, you peel off the web of ribbons.\n");
-            }
-            else
-            {
-                print_msg("With a sigh of relief you peel off the uncanny web of ribbons.\n");
-            }
-            break;
         case PO_LICHS_ROBE:
             print_msg("The supernatural chill of the robes lingers in your bones even after you put them aside.\n");
             break;
@@ -677,28 +752,47 @@ void notify_armour_unequip(int obj)
     }
 }
 
-void notify_ring_equip(int obj)
+void notify_ring_equip(Obj_handle obj)
 {
-    print_msg("You put on ");
-    print_obj_name(obj);
-    print_msg(".\n");
+    char *s;
+    asprint_obj_name(&s, obj);
+    print_msg("You put on %s.\n", s);
+    free(s);
 }
 
-void notify_ring_unequip(int obj)
+void notify_ring_unequip(Obj_handle obj)
 {
     print_msg("You remove your ring.\n");
 }
 
-void notify_lava_blocks_unequip(void)
+void notify_hot_blocks_unequip(void)
 {
-    print_msg(Msg_prio::Warn, "That item is your only current source of fire resistance; setting it aside here would incinerate you.\n");
+    print_msg(Msg_prio::Warn, "Setting that item aside here would incinerate you.\n");
 }
 
-void notify_water_blocks_unequip(void)
+void notify_wet_blocks_unequip(void)
 {
     print_msg(Msg_prio::Warn, "Setting that item aside here would cause your death by drowning.\n");
 }
 
+void notify_pit_blocks_unequip(void)
+{
+    print_msg(Msg_prio::Warn, "Setting that item aside here would send you plummetting to your death.\n");
+}
+
+void notify_role_blocks_equip(void)
+{
+    switch (u.role)
+    {
+    case Role_demon_hunter:
+       print_msg(Msg_prio::Alert, "Unclean! Unclean! Cast this filth aside!\n");
+       break;
+    default:
+       print_msg(Msg_prio::Alert, "Your chosen path in life precludes the use of that item.\n");
+       break;
+    }
+}
+
 void notify_player_touch_effect(Damtyp dt)
 {
     switch (dt)
@@ -732,7 +826,7 @@ void notify_player_ignore_damage(Damtyp dt)
     switch (dt)
     {
     case DT_FIRE:
-        print_msg("The feel a pleasant warmth.\n");
+        print_msg("You feel a pleasant warmth.\n");
         break;
     case DT_COLD:
         print_msg("You feel a pleasant chill.\n");
@@ -755,9 +849,9 @@ void notify_player_ignore_damage(Damtyp dt)
     }
 }
 
-void notify_item_explodes_flames(int obj)
+void notify_item_explodes_flames(Obj_handle obj)
 {
-    print_msg("The %s explodes in flames!\n", permobjs[objects[obj].obj_id].name);
+    print_msg("The %s explodes in flames!\n", permobjs[objects[obj].po_ref].name);
 }
 
 void notify_read_scroll_protection(void)
@@ -809,10 +903,12 @@ void notify_firestaff_activation(int specific)
     }
 }
 
-void notify_fireitem_hit(int mon)
+void notify_inferno_hit(Mon_handle mon)
 {
-    print_mon_name(mon, 3);
-    print_msg(" is engulfed in roaring flames.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 3);
+    print_msg("%s is engulfed in roaring flames.\n", s);
+    free(s);
 }
 
 void notify_lash_activation(int specific)
@@ -886,19 +982,21 @@ void notify_nothing_to_get(void)
     print_msg("Nothing to get.\n");
 }
 
-void notify_mon_healed(int mon)
+void notify_mon_healed(Mon_handle mon)
 {
-    print_mon_name(mon, 3);
-    print_msg(" looks healthier.\n");
+    char *s;
+    asprint_mon_name(&s, mon, 3);
+    print_msg("%s looks healthier.\n", s);
+    free(s);
 }
 
-void notify_mon_regenerates(int mon)
+void notify_mon_regenerates(Mon_handle mon)
 {
     // TODO allow things that aren't trolls to be regenerators
     print_msg("The troll regenerates.\n");
 }
 
-void notify_unwield(int obj, Noisiness noisy)
+void notify_unwield(Obj_handle obj, Noisiness noisy)
 {
     if (noisy == Noise_std)
     {
@@ -906,28 +1004,59 @@ void notify_unwield(int obj, Noisiness noisy)
     }
 }
 
-void notify_wield_weapon(int obj)
+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)
+       {
+#if 0
+       case PO_WINDSWORD:
+           print_msg("Your stride lightens as you ready the magic blade.\n");
+           break;
+#endif
+       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
+    {
+       char *s;
+       asprint_obj_name(&s, obj);
+       print_msg("Wielding %s.\n", s);
+       free(s);
+    }
 }
 
-void notify_weapon_broke(int obj)
+void notify_weapon_broke(Obj_handle obj)
 {
     print_msg(Msg_prio::Warn, "Your weapon breaks!\n");
 }
 
-void notify_armour_broke(int obj)
+void notify_armour_broke(Obj_handle obj)
 {
     print_msg(Msg_prio::Warn, "Your armour is ruined!\n");
 }
 
-void notify_drop_item(int obj)
+void notify_drop_item(Obj_handle obj)
 {
-    print_msg("You drop ");
-    print_obj_name(obj);
-    print_msg(".\n");
+    char *s;
+    asprint_obj_name(&s, obj);
+    print_msg("You drop %s.\n", s);
+    free(s);
 }
 
 void notify_drop_blocked(void)
@@ -935,7 +1064,7 @@ void notify_drop_blocked(void)
     print_msg(Msg_prio::Alert, "There is no room to drop that here.\n");
 }
 
-void notify_get_item(int from, int slot)
+void notify_get_item(Obj_handle from, int slot)
 {
     if (from != NO_OBJ)
     {
@@ -968,7 +1097,7 @@ void notify_pack_full(void)
  * \param c Affected location
  * \param mon Monster at that location.
  */
-void notify_new_mon_at(Coord c, int mon)
+void notify_new_mon_at(Coord c, Mon_handle mon)
 {
     newsym(c);
     display_update();
@@ -993,7 +1122,7 @@ void notify_tick(void)
 void notify_change_of_depth(void)
 {
     status_updated = true;
-    print_msg("Welcome to level %d of the Abyss.\n", depth);
+    print_msg("Welcome to level %d.\n", depth);
 }
 
 void notify_load_complete(void)
@@ -1072,9 +1201,9 @@ void debug_read_non_scroll(void)
     print_msg(Msg_prio::Bug, "BUG: reading non-scroll\n");
 }
 
-void debug_eat_non_food(int obj)
+void debug_eat_non_food(Obj_handle obj)
 {
-    print_msg(Msg_prio::Bug, "BUG: attempt to eat non-food (%d)!\n", objects[obj].obj_id);
+    print_msg(Msg_prio::Bug, "BUG: attempt to eat non-food (%d)!\n", objects[obj].po_ref);
 }
 
 void debug_quaff_non_potion(void)
@@ -1157,7 +1286,7 @@ void debug_unimplemented_break_reaction(int po)
     print_msg(Msg_prio::Bug, "BUG: permobj %d should react to hitting durability 0 but has no break reaction handler.\n");
 }
 
-void debug_mon_invalid_move(int mon, Coord c)
+void debug_mon_invalid_move(Mon_handle mon, Coord c)
 {
     print_msg(Msg_prio::Bug, "BUG: monster %d attempted move to impassable location y=%d x=%d\n", mon, c.y, c.x);
 }
index d8b0abc..65303ba 100644 (file)
--- a/notify.hh
+++ b/notify.hh
@@ -1,5 +1,5 @@
 /*! \file notify.hh
- *  \brief Notification-related definitions for Victrix Abyssi
+ *  \brief Notification-related definitions
  */
 
 /* Copyright 2014 Martin Read
@@ -51,23 +51,27 @@ void notify_protection_lost(void);
 void notify_wasted_gain(void);
 void notify_defence_recalc(void);
 void notify_food_use(int food_use, int hunger_severity);
+void notify_learned_skill(Skill_id skill);
 
 // 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);
+void notify_flying_leap(void);
 
 // Unsorted notifications
+void notify_new_obj_at(Coord c, Obj_handle obj);
 void notify_obj_at(Coord c);
 void notify_dress_shredded(void);
-void notify_mon_healed(int mon);
-void notify_mon_regenerates(int mon);
-void notify_mon_disappears(int mon, Coord oldpos);
-void notify_mon_appears(int mon);
+void notify_mon_healed(Mon_handle mon);
+void notify_mon_regenerates(Mon_handle mon);
+void notify_mon_disappears(Mon_handle mon, Coord oldpos);
+void notify_mon_appears(Mon_handle mon);
 
 // Item manipulation notifications
 void notify_magic_no_ring(void);
@@ -76,20 +80,23 @@ void notify_zap_no_weapon(void);
 void notify_magic_powerless_ring(void);
 void notify_emanate_powerless_armour(void);
 void notify_zap_powerless_weapon(void);
-void notify_ring_equip(int obj);
-void notify_ring_unequip(int obj);
-void notify_armour_equip(int obj);
-void notify_armour_unequip(int obj);
-void notify_lava_blocks_unequip(void);
-void notify_water_blocks_unequip(void);
+void notify_ring_equip(Obj_handle obj);
+void notify_ring_unequip(Obj_handle obj);
+void notify_armour_equip(Obj_handle obj);
+void notify_armour_unequip(Obj_handle obj);
+void notify_hot_blocks_unequip(void);
+void notify_wet_blocks_unequip(void);
+void notify_pit_blocks_unequip(void);
 void notify_nothing_to_get(void);
-void notify_unwield(int obj, Noisiness noisy);
-void notify_wield_weapon(int obj);
-void notify_weapon_broke(int obj);
-void notify_armour_broke(int obj);
-void notify_drop_item(int obj);
+void notify_unwield(Obj_handle obj, Noisiness noisy);
+void notify_wield_weapon(Obj_handle obj);
+void notify_role_blocks_equip(void);
+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_get_item(int from, int slot);
+void notify_drop_blocked_portal(void);
+void notify_get_item(Obj_handle from, int slot);
 void notify_pack_full(void);
 
 // Magic item use releated notifications
@@ -100,10 +107,10 @@ void notify_quaff_potion_restoration(void);
 void notify_ribbon_activation(int specific);
 void notify_lash_activation(int specific);
 void notify_firestaff_activation(int specific);
-void notify_fireitem_hit(int mon);
+void notify_inferno_hit(Mon_handle mon);
 void notify_telering_activation(int specific);
 
-void notify_item_explodes_flames(int obj);
+void notify_item_explodes_flames(Obj_handle obj);
 void notify_eat_food(bool ravenous);
 void notify_ingest_spleen(void);
 
@@ -111,43 +118,45 @@ void notify_ingest_spleen(void);
 void notify_swing_bow(void);
 void notify_no_attackee(void);
 void notify_no_flask_target(void);
-void notify_player_miss(int mon);
-void notify_ring_boost(int mon, int pobj);
-void notify_player_hit_mon(int mon);
-void notify_player_hurt_mon(int mon, int damage);
-void notify_player_killed_mon(int mon);
-void notify_player_combo_powatk(int mon);
-void notify_knockback_mon_resisted(int mon);
-void notify_knockback_mon_blocked(int mon);
-void notify_knockback_mon_hover_lava(int mon);
-void notify_knockback_mon_hover_water(int mon);
-void notify_knockback_mon_immersed_lava(int mon);
-void notify_knockback_mon_immersed_water(int mon);
+void notify_player_miss(Mon_handle mon);
+void notify_ring_boost(Mon_handle mon, int pobj);
+void notify_player_hit_mon(Mon_handle mon);
+void notify_player_charge_mon(Mon_handle mon);
+void notify_player_hurt_mon(Mon_handle mon, int damage);
+void notify_player_killed_mon(Mon_handle mon);
+void notify_player_combo_powatk(Mon_handle mon);
+void notify_knockback_mon_resisted(Mon_handle mon);
+void notify_knockback_mon_blocked(Mon_handle mon);
+void notify_knockback_mon_hover_lava(Mon_handle mon);
+void notify_knockback_mon_hover_water(Mon_handle mon);
+void notify_knockback_mon_immersed_lava(Mon_handle mon);
+void notify_knockback_mon_immersed_water(Mon_handle mon);
 void notify_knockback_mon_water_offscreen(void);
 void notify_knockback_mon_lava_offscreen(void);
 
 void notify_point_blank_warning(void);
-void notify_player_shot_terrain(int obj, Coord c);
-
-void notify_mon_hit_armour(int mon);
-void notify_mon_missed_player(int mon);
-void notify_mon_hit_player(int mon);
-void notify_mon_ranged_attack(int mon);
-void notify_mon_ranged_hit_player(int mon);
-void notify_mon_ranged_missed_player(int mon);
+void notify_player_shot_terrain(Obj_handle obj, Coord c);
+
+void notify_mon_hit_armour(Mon_handle mon);
+void notify_mon_missed_player(Mon_handle mon);
+void notify_mon_hit_player(Mon_handle mon);
+void notify_mon_ranged_attack(Mon_handle mon);
+void notify_mon_ranged_hit_player(Mon_handle mon);
+void notify_mon_ranged_missed_player(Mon_handle mon);
 void notify_mon_ranged_hit_mon(int er, int ee);
-void notify_mon_dies(int mon);
-void notify_new_mon_at(Coord c, int mon);
+void notify_mon_dies(Mon_handle mon);
+void notify_mon_detonates(Mon_handle mon, Decal_tag decal);
+void notify_new_mon_at(Coord c, Mon_handle mon);
 
 void notify_player_damage_taken(int amount);
 void notify_player_touch_effect(Damtyp dt);
 void notify_player_ignore_damage(Damtyp dt);
 
 // Sorcery notifications
-void notify_summon_help(int mon, bool success);
-void notify_summon_demon(int mon);
-void notify_monster_cursing(int mon);
-void notify_mon_disappear(int mon);
+void notify_summon_help(Mon_handle mon, bool success);
+void notify_summon_demon(Mon_handle mon);
+void notify_monster_cursing(Mon_handle mon);
+void notify_mon_disappear(Mon_handle mon);
 void notify_moncurse_fail(void);
 void notify_start_armourmelt(void);
 void notify_start_withering(void);
@@ -162,6 +171,7 @@ void notify_tick(void);
 void notify_change_of_depth(void);
 void notify_load_complete(void);
 void notify_ascent_blocked(void);
+void notify_inert_portal(void);
 
 // Debugging notifications
 void debug_move_oob(Coord c);
@@ -178,7 +188,7 @@ void debug_take_off_no_armour(void);
 void debug_remove_no_ring(void);
 void debug_put_on_second_ring(void);
 void debug_put_on_uncarried_ring(void);
-void debug_eat_non_food(int obj);
+void debug_eat_non_food(Obj_handle obj);
 void debug_read_non_scroll(void);
 void debug_quaff_non_potion(void);
 void debug_ascend_non_stairs(void);
@@ -190,7 +200,7 @@ void debug_pobj_select_failed(void);
 void debug_misplaced_monster(void);
 void debug_create_mon_occupied(Coord c);
 void debug_monster_pool_exhausted(void);
-void debug_mon_invalid_move(int mon, Coord c);
+void debug_mon_invalid_move(Mon_handle mon, Coord c);
 void debug_pmon_select_failed(void);
 void debug_dump_write_failed(void);
 void debug_unimplemented_activation(int po);
@@ -210,7 +220,7 @@ bool query_wizmode_death(void);
 class Notify_uattkm
 {
 public:
-    int mon;
+    Mon_handle mon;
     bool hit;
     int dmg;
     bool ringflag;
diff --git a/obj2.cc b/obj2.cc
new file mode 100644 (file)
index 0000000..0dbb8c9
--- /dev/null
+++ b/obj2.cc
@@ -0,0 +1,210 @@
+/* \file obj2.cc
+ * \brief Per-PO object use functions 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.
+ */
+
+#define OBJ2_CC
+#include "victrix-abyssi.hh"
+#include "objects.hh"
+#include "monsters.hh"
+#include "fov.hh"
+
+#include <string.h>
+
+/*! \brief Effect of quaffing a body potion */
+static void body_potion_quaff(Obj_handle obj)
+{
+    gain_body(1);
+}
+
+/*! \brief Effect of quaffing a agility potion */
+static void agility_potion_quaff(Obj_handle obj)
+{
+    gain_agility(1);
+}
+
+/*! \brief Effect of quaffing a healing potion */
+static void healing_potion_quaff(Obj_handle obj)
+{
+    int healpercent = inc_flat(30, 50);
+    int healamount = (healpercent * ((u.hpmax > 60) ? u.hpmax : 60)) / 100;
+    heal_u(healamount, 1, 1);
+}
+
+/*! \brief Effect of quaffing a restoration potion */
+static void restoration_potion_quaff(Obj_handle obj)
+{
+    notify_quaff_potion_restoration();
+    if (u.bdam && ((!u.adam) || zero_die(2)))
+    {
+       u.bdam = 0;
+       notify_body_restore();
+    }
+    else if (u.adam)
+    {
+       u.adam = 0;
+       notify_agility_restore();
+    }
+}
+
+/*! \brief Lookup table for effects of operations involving potions */
+Potion_table_entry potion_table[] =
+{
+    { PO_BODY_POTION, body_potion_quaff },
+    { PO_AGILITY_POTION, agility_potion_quaff },
+    { PO_HEALING_POTION, healing_potion_quaff },
+    { PO_RESTORATION_POTION, restoration_potion_quaff },
+    { NO_POBJ, nullptr }
+};
+
+static Inferno_detail fire_scroll_inferno = { true, NO_MON, 4, 10, 0 };
+
+bool inferno_effector(Coord c, void *pvt)
+{
+    Inferno_detail *inf = (Inferno_detail *) pvt;
+    Mon_handle mon = lvl.mon_at(c);
+    if (mon != NO_MON)
+    {
+       Mon const *mptr = mon_snap(mon);
+       if (!pmon_resists_fire(mptr->pm_ref))
+       {
+           notify_inferno_hit(mon);
+           damage_mon(mon, inf->dice_bonus + dice(inf->dice_count, inf->dice_sides), inf->by_you);
+       }
+    }
+    return true;
+}
+
+static Radiance fire_scroll_explosion = 
+{
+    {},
+    Nowhere,
+    MAX_FOV_RADIUS,
+    Reo_spiral_out,
+    true,
+    &fire_scroll_inferno,
+    dflt_blk,
+    inferno_effector
+};
+
+static void fire_scroll_read(Obj_handle obj)
+{
+    notify_item_explodes_flames(obj);
+    if (u.resistances[DT_FIRE])
+    {
+       notify_player_ignore_damage(DT_FIRE);
+    }
+    else
+    {
+       damage_u(dice(4, 10), DEATH_KILLED, "searing flames");
+    }
+    fire_scroll_explosion.clear();
+    fire_scroll_explosion.centre = u.pos;
+    fire_scroll_explosion.compute();
+    fire_scroll_explosion.resolve();
+}
+
+static void teleport_scroll_read(Obj_handle obj)
+{
+    teleport_u();
+}
+
+static void protection_scroll_read(Obj_handle obj)
+{
+    notify_read_scroll_protection();
+    if (!u.protection)
+    {
+       /* Do not prolong existing protection, only grant
+        * protection to the unprotected. */
+       u.protection = 100;
+    }
+    if (u.withering)
+    {
+       u.withering = 0;
+       notify_wither_recovered();
+    }
+    if (u.armourmelt)
+    {
+       u.armourmelt = 0;
+       notify_armourmelt_recovered();
+    }
+    if (u.leadfoot)
+    {
+       u.leadfoot = 0;
+       notify_leadfoot_recovered();
+    }
+    recalc_defence();
+}
+
+/*! \brief Lookup table for effects of reading scrolls */
+Scroll_table_entry scroll_table[] =
+{
+    { PO_FIRE_SCROLL, fire_scroll_read },
+    { PO_TELEPORT_SCROLL, teleport_scroll_read },
+    { PO_PROTECTION_SCROLL, protection_scroll_read },
+    { NO_POBJ, nullptr }
+};
+
+static Inferno_detail fire_staff_inferno = { true, NO_MON, 4, 10, 0 };
+
+static Radiance fire_staff_explosion = 
+{
+    {},
+    Nowhere,
+    MAX_FOV_RADIUS,
+    Reo_spiral_out,
+    true,
+    &fire_staff_inferno,
+    dflt_blk,
+    inferno_effector
+};
+
+static void fire_staff_zap(Obj_handle obj)
+{
+    if (u.food > 150)
+    {
+       u.food -= 150;
+       notify_firestaff_activation(1);
+       fire_staff_explosion.clear();
+       fire_staff_explosion.centre = u.pos;
+       fire_staff_explosion.compute();
+       fire_staff_explosion.resolve();
+       damage_obj(obj);
+    }
+    else
+    {
+       notify_firestaff_activation(0);
+    }
+}
+
+Weapon_table_entry weapon_table[] =
+{
+    { PO_STAFF_OF_FIRE, fire_staff_zap, nullptr, nullptr },
+    { NO_POBJ, nullptr, nullptr, nullptr },
+};
+
+/* obj2.cc */
+// vim:cindent
index bc53025..b92cf3f 100644 (file)
@@ -1,5 +1,5 @@
 /* \file objects.cc
- * \brief Object handling for Victrix Abyssi
+ * \brief Object handling
  */
 
 /* Copyright 2005-2013 Martin Read
 #include "objects.hh"
 #include "monsters.hh"
 
-Obj objects[100];
+#include <string.h>
+
+std::map<Obj_handle, Obj> objects;
+const Obj_handle NO_OBJ = 0u;
+
 int get_random_pobj(void);
 
-char const ring_colours[20][16] = {
-    "gold", "ruby", "sapphire", "ivory", "coral",
-    "amethyst", "silver", "iron", "copper", "jade",
-    "haematite", "bone", "crystal", "platinum", "lead",
-    "diamond", "topaz", "emerald", "electrum", "smoky quartz"
-};
-
-char const scroll_titles[20][16] = {
-    "grem pho", "terra terrax", "phong", "ateh malkuth", "xixaxa",
-    "aku ryo tai san", "qoph shin tau", "ythek shri", "ia ia", "cthulhu fhtagn",
-    "arifech malex", "DOOM", "leme athem", "hail smkznrf", "rorrim foo",
-    "ad aerarium", "ligemrom", "asher ehiyeh", "YELLOW SIGN", "ELDER SIGN"
-};
-
-char const potion_colours[20][16] = {
-    "purple", "red", "blue", "green", "yellow",
-    "orange", "white", "black", "brown", "fizzy",
-    "grey", "silver", "gold", "shimmering", "glowing",
-    "navy blue", "bottle green", "amber", "lilac", "ivory"
-};
-
-Action_cost read_scroll(int obj)
-{
-    Obj *optr = objects + obj;
+/*! \brief Read a magic scroll */
+Action_cost read_scroll(Obj_handle obj)
+{
+    Obj *optr = obj_snapv(obj);
     int i;
-    switch (optr->obj_id)
+    for (i = 0; scroll_table[i].read_func != nullptr; ++i)
     {
-    case PO_TELEPORT_SCROLL:
-        teleport_u();
-        break;
-    case PO_FIRE_SCROLL:
-        notify_item_explodes_flames(obj);
-        if (u.resistances[DT_FIRE])
-        {
-            notify_player_ignore_damage(DT_FIRE);
-        }
-        else
-        {
-            damage_u(dice(4, 10), DEATH_KILLED, "searing flames");
-        }
-        for (i = 0; i < MONSTERS_IN_PLAY; ++i)
-        {
-            if (mon_visible(i))
-            {
-                if (!pmon_resists_fire(monsters[i].mon_id))
-                {
-                    notify_fireitem_hit(i);
-                    damage_mon(i, dice(4, 10), true);
-                }
-            }
-        }
-        break;
-    case PO_PROTECTION_SCROLL:
-        notify_read_scroll_protection();
-        if (!u.protection)
-        {
-            /* Do not prolong existing protection, only grant
-             * protection to the unprotected. */
-            u.protection = 100;
-        }
-        if (u.withering)
-        {
-            u.withering = 0;
-            notify_wither_recovered();
-        }
-        if (u.armourmelt)
-        {
-            u.armourmelt = 0;
-            notify_armourmelt_recovered();
-        }
-        if (u.leadfoot)
+        if (scroll_table[i].pobj == optr->po_ref)
         {
-            u.leadfoot = 0;
-            notify_leadfoot_recovered();
+            scroll_table[i].read_func(obj);
+            consume_obj(obj);
+            return Cost_std;
         }
-        recalc_defence();
-        break;
-    default:
-        debug_read_non_scroll();
-        return Cost_none;
     }
-    consume_obj(obj);
-    return Cost_std;
+    debug_read_non_scroll();
+    return Cost_none;
 }
 
-bool consume_obj(int obj)
+bool consume_obj(Obj_handle obj)
 {
     int i;
-    objects[obj].quan--;
-    if (objects[obj].quan == 0)
+    Obj *optr = obj_snapv(obj);
+    optr->quan--;
+    if (optr->quan == 0)
     {
-        objects[obj].used = false;
-        if (objects[obj].with_you)
+        optr->flags &= ~OF_USED;
+        if (optr->flags & OF_WITH_YOU)
         {
             if (obj == u.armour)
             {
@@ -145,15 +83,12 @@ bool consume_obj(int obj)
                 u.ring = NO_OBJ;
                 recalc_defence();
             }
-            if (!objects[obj].used)
+            for (i = 0; i < 19; i++)
             {
-                for (i = 0; i < 19; i++)
+                if (u.inventory[i] == obj)
                 {
-                    if (u.inventory[i] == obj)
-                    {
-                        u.inventory[i] = NO_OBJ;
-                        break;
-                    }
+                    u.inventory[i] = NO_OBJ;
+                    break;
                 }
             }
         }
@@ -162,17 +97,18 @@ bool consume_obj(int obj)
     return false;
 }
 
-Action_cost eat_food(int obj)
+/*! \brief Consume a food */
+Action_cost eat_food(Obj_handle obj)
 {
-    Obj *optr = objects + obj;
+    Obj *optr = obj_snapv(obj);
     bool ravenous = (u.food < 0);
-    u.food += 1500;
-    if (permobjs[optr->obj_id].poclass != POCLASS_FOOD)
+    if (permobjs[optr->po_ref].poclass != POCLASS_FOOD)
     {
         debug_eat_non_food(obj);
         return Cost_none;
     }
-    if (optr->obj_id == PO_DEVIL_SPLEEN)
+    u.food += permobjs[optr->po_ref].power;
+    if (optr->po_ref == PO_DEVIL_SPLEEN)
     {
         notify_ingest_spleen();
         if (zero_die(2))
@@ -194,142 +130,39 @@ Action_cost eat_food(int obj)
     return Cost_std;
 }
 
-Action_cost quaff_potion(int obj)
+/*! \brief Consume a potion */
+Action_cost quaff_potion(Obj_handle obj)
 {
-    Obj *optr = objects + obj;
-    switch (optr->obj_id)
-    {
-    case PO_BODY_POTION:
-        gain_body(1);
-        break;
-    case PO_AGILITY_POTION:
-        gain_agility(1);
-        break;
-    case PO_HEALING_POTION:
-        {
-            int healpercent = inc_flat(30, 50);
-            int healamount = (healpercent * ((u.hpmax > 60) ? u.hpmax : 60)) / 100;
-            heal_u(healamount, 1, 1);
-        }
-        break;
-    case PO_RESTORATION_POTION:
-        notify_quaff_potion_restoration();
-        if (u.bdam && ((!u.adam) || zero_die(2)))
-        {
-            u.bdam = 0;
-            notify_body_restore();
-        }
-        else if (u.adam)
-        {
-            u.adam = 0;
-            notify_agility_restore();
-        }
-        break;
-    default:
-        debug_quaff_non_potion();
-        return Cost_none;
-    }
-    consume_obj(obj);
-    return Cost_std;
-
-}
-
-void flavours_init(void)
-{
-    int colour_choices[10];
+    Obj *optr = obj_snapv(obj);
     int i;
-    int j;
-    int done;
-    /* Flavoured items use "power" to track their flavour.  This is a
-     * gross and unforgiveable hack. */
-    /* Rings */
-    for (i = 0; i < 10;)
-    {
-        colour_choices[i] = zero_die(20);
-        done = 1;
-        for (j = 0; j < i; j++)
-        {
-            if (colour_choices[i] == colour_choices[j])
-            {
-                done = 0;
-            }
-        }
-        if (done)
-        {
-            i++;
-        }
-    }
-    permobjs[PO_REGENERATION_RING].power = colour_choices[0];
-    permobjs[PO_FIRE_RING].power = colour_choices[1];
-    permobjs[PO_VAMPIRE_RING].power = colour_choices[2];
-    permobjs[PO_FROST_RING].power = colour_choices[3];
-    permobjs[PO_TELEPORT_RING].power = colour_choices[4];
-    /* Scrolls */
-    for (i = 0; i < 10;)
-    {
-        colour_choices[i] = zero_die(20);
-        done = 1;
-        for (j = 0; j < i; j++)
-        {
-            if (colour_choices[i] == colour_choices[j])
-            {
-                done = 0;
-            }
-        }
-        if (done)
-        {
-            i++;
-        }
-    }
-    permobjs[PO_FIRE_SCROLL].power = colour_choices[0];
-    permobjs[PO_TELEPORT_SCROLL].power = colour_choices[1];
-    permobjs[PO_PROTECTION_SCROLL].power = colour_choices[2];
-    /* Potions */
-    for (i = 0; i < 10;)
+    for (i = 0; potion_table[i].pobj != NO_POBJ; ++i)
     {
-        colour_choices[i] = zero_die(20);
-        done = 1;
-        for (j = 0; j < i; j++)
+        if (potion_table[i].pobj == optr->po_ref)
         {
-            if (colour_choices[i] == colour_choices[j])
-            {
-                done = 0;
-            }
-        }
-        if (done)
-        {
-            i++;
+            potion_table[i].quaff_func(obj);
+            consume_obj(obj);
+            return Cost_std;
         }
     }
-    permobjs[PO_HEALING_POTION].power = colour_choices[0];
-    permobjs[PO_BODY_POTION].power = colour_choices[1];
-    permobjs[PO_AGILITY_POTION].power = colour_choices[2];
-    permobjs[PO_RESTORATION_POTION].power = colour_choices[3];
+    debug_quaff_non_potion();
+    return Cost_none;
 }
 
-int get_first_free_obj(void)
+Obj_handle first_free_obj_handle = 1u;
+
+Obj_handle get_first_free_obj(void)
 {
-    int obj;
-    for (obj = 0; obj < 100; obj++)
+    if (first_free_obj_handle == 0u)
     {
-        if (!objects[obj].used)
-        {
-            break;
-        }
+        return NO_OBJ;
     }
-    return (obj == 100) ? NO_OBJ : obj;
+    return first_free_obj_handle++;
 }
 
-int create_obj_class(enum poclass_num po_class, int quantity, bool with_you, Coord c)
+Obj_handle create_obj_class_near(enum poclass_num po_class, int quantity, bool with_you, Coord c)
 {
     int po_idx;
     int tryct;
-    int obj = get_first_free_obj();
-    if (obj == 100)
-    {
-        debug_object_pool_exhausted();
-        return NO_OBJ;
-    }
     for (tryct = 0; tryct < 200; tryct++)
     {
         switch (po_class)
@@ -353,9 +186,7 @@ int create_obj_class(enum poclass_num po_class, int quantity, bool with_you, Coo
         }
         break;
     }
-    objects[obj].obj_id = po_idx;
-    objects[obj].quan = quantity;
-    return obj;
+    return (with_you ? create_obj(po_idx, quantity, true, c) : create_obj_near(po_idx, quantity, c));
 }
 
 int get_random_pobj(void)
@@ -370,9 +201,6 @@ int get_random_pobj(void)
             po_idx = NO_POBJ;
             continue;
         }
-        /* v1.3: Do not permit generation of particularly powerful
-         * items (runeswords, mage armour, etc.) at shallow depths.
-         * (game balance fix) */
         if (depth < permobjs[po_idx].depth)
         {
             po_idx = NO_POBJ;
@@ -383,10 +211,48 @@ int get_random_pobj(void)
     return po_idx;
 }
 
-int create_obj(int po_idx, int quantity, bool with_you, Coord c)
+Obj_handle create_corpse(int pm_idx, Coord c)
+{
+    Obj_handle obj = create_obj_near(PO_CORPSE, 1, c);
+    if (obj != NO_OBJ)
+    {
+        Obj *o = obj_snapv(obj);
+        o->meta[0] = pm_idx;
+        o->meta[1] = 0; // reserved
+        o->meta[2] = 0; // reserved
+        o->meta[3] = 0; // reserved
+    }
+    return obj;
+}
+
+Obj_handle create_obj_near(int po_idx, int quantity, Coord c)
 {
-    int obj = get_first_free_obj();
-    if (obj == 100)
+    Offset delta;
+    Coord nc = c;
+    int panic_count = 200;
+    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_items(lvl.terrain_at(nc)));
+        c = nc;
+        --panic_count;
+    }
+    if (panic_count < 1)
+    {
+        return NO_OBJ;
+    }
+    Obj_handle obj = create_obj(po_idx, quantity, false, c);
+    return obj;
+}
+
+Obj_handle create_obj(int po_idx, int quantity, bool with_you, Coord c)
+{
+    Obj_handle obj = get_first_free_obj();
+    if (obj == NO_OBJ)
     {
         debug_object_pool_exhausted();
         return NO_OBJ;
@@ -400,84 +266,113 @@ int create_obj(int po_idx, int quantity, bool with_you, Coord c)
             return NO_OBJ;
         }
     }
-    objects[obj].obj_id = po_idx;
-    objects[obj].with_you = with_you;
-    objects[obj].used = true;
-    objects[obj].pos = c;
-    objects[obj].quan = quantity;
+    Obj o;
+    memset(o.meta, '\0', sizeof o.meta);
+    o.self = obj;
+    o.po_ref = po_idx;
+    o.flags = OF_USED | (with_you ? OF_WITH_YOU : 0);
+    o.pos = c;
+    o.quan = quantity;
     switch (permobjs[po_idx].poclass)
     {
     case POCLASS_WEAPON:
     case POCLASS_ARMOUR:
-        /* Set durability of weapons and armour to a suitable value.
-         * 100 has been chosen for the moment, but this may need
-         * tuning. */
-        objects[obj].durability = OBJ_MAX_DUR;
+        o.durability = OBJ_MAX_DUR;
         break;
     default:
         break;
     }
-    if (!objects[obj].with_you)
+    objects[obj] = o;
+    if (!(o.flags & OF_WITH_YOU))
     {
         lvl.set_obj_at(c, obj);
+        notify_new_obj_at(c, obj);
     }
     return obj;
 }
 
-void sprint_obj_name(char *buf, int obj, int len)
+void asprint_obj_name(char **buf, Obj_handle obj)
 {
     Obj *optr;
     Permobj *poptr;
-    optr = objects + obj;
-    poptr = permobjs + optr->obj_id;
+    int i;
+    optr = obj_snapv(obj);
+    poptr = permobjs + optr->po_ref;
     if (optr->quan > 1)
     {
-        snprintf(buf, len, "%d %s", optr->quan, poptr->plural);
+        i = asprintf(buf, "%d %s", optr->quan, poptr->plural);
     }
-    else if (po_is_stackable(optr->obj_id))
+    else if (po_is_stackable(optr->po_ref))
     {
-        snprintf(buf, len, "1 %s", poptr->name);
+        i = asprintf(buf, "1 %s", poptr->name);
     }
-    else if ((poptr->poclass == POCLASS_WEAPON) ||
-             (poptr->poclass == POCLASS_ARMOUR))
+    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);
+        i = asprintf(buf, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
+    }
+    else if (optr->po_ref == PO_CORPSE)
+    {
+        Permon *pmptr = permons + optr->meta[0];
+        i = asprintf(buf, "a%s %s corpse", is_vowel(pmptr->name[0]) ? "n" : "", pmptr->name);
     }
     else
     {
-        snprintf(buf, len, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
+        i = asprintf(buf, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
+    }
+    if (i == -1)
+    {
+        // TODO wibble
     }
+    return;
 }
 
-void fprint_obj_name(FILE *fp, int obj)
+void sprint_obj_name(char *buf, Obj_handle obj, int len)
 {
     Obj *optr;
     Permobj *poptr;
-    optr = objects + obj;
-    poptr = permobjs + optr->obj_id;
+    optr = obj_snapv(obj);
+    poptr = permobjs + optr->po_ref;
     if (optr->quan > 1)
     {
-        fprintf(fp, "%d %s", optr->quan, poptr->plural);
+        snprintf(buf, len, "%d %s", optr->quan, poptr->plural);
+    }
+    else if (po_is_stackable(optr->po_ref))
+    {
+        snprintf(buf, len, "1 %s", poptr->name);
     }
-    else if (po_is_stackable(optr->obj_id))
+    else if (poptr->flags[0] & POF_DAMAGEABLE)
     {
-        fprintf(fp, "1 %s", poptr->name);
+        snprintf(buf, len, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
     }
-    else if ((poptr->poclass == POCLASS_WEAPON) ||
-             (poptr->poclass == POCLASS_ARMOUR))
+    else if (optr->po_ref == PO_CORPSE)
     {
-        fprintf(fp, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
+        Permon *pmptr = permons + optr->meta[0];
+        snprintf(buf, len, "a%s %s corpse", is_vowel(pmptr->name[0]) ? "n" : "", pmptr->name);
     }
     else
     {
-        fprintf(fp, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
+        snprintf(buf, len, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name);
     }
 }
 
+void fprint_obj_name(FILE *fp, Obj_handle obj)
+{
+    char *s;
+    asprint_obj_name(&s, obj);
+    fputs(s, fp);
+    free(s);
+}
+
 Action_cost drop_obj(int inv_idx)
 {
     Obj *optr;
-    optr = objects + u.inventory[inv_idx];
+    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)
     {
         optr->pos = u.pos;
@@ -487,7 +382,7 @@ Action_cost drop_obj(int inv_idx)
             u.weapon = NO_OBJ;
         }
         u.inventory[inv_idx] = NO_OBJ;
-        optr->with_you = false;
+        optr->flags &= ~OF_WITH_YOU;
         notify_drop_item(lvl.obj_at(u.pos));
         return Cost_std;
     }
@@ -498,35 +393,120 @@ Action_cost drop_obj(int inv_idx)
     }
 }
 
-bool po_is_stackable(int po)
+Damtyp po_resistance(int po)
 {
-    switch (permobjs[po].poclass)
+    if ((po < 0) || (po >= NUM_OF_PERMOBJS))
     {
-    default:
-        return false;
-    case POCLASS_POTION:
-    case POCLASS_SCROLL:
-    case POCLASS_FOOD:
-        return true;
+        return DT_NONE;
+    }
+    uint32_t res = permobjs[po].flags[1] & POF_RES_MASK;
+    // Items never grant physical damage resistance
+    if ((res > 0) && (res <= LAST_DAMTYPE))
+    {
+        return Damtyp(res);
+    }
+    return DT_NONE;
+}
+
+Damtyp po_damage_amp(int po)
+{
+    if ((po < 0) || (po >= NUM_OF_PERMOBJS))
+    {
+        return DT_NONE;
+    }
+    uint32_t dt = (permobjs[po].flags[1] & POF_DMG_MASK) >> POF_DMG_SHIFT;
+    // Items never grant physical damage amplification
+    if ((dt > 0) && (dt <= LAST_DAMTYPE))
+    {
+        return Damtyp(dt);
     }
+    return DT_NONE;
+}
+
+bool po_is_sword(int po)
+{
+    return (permobjs[po].flags[0] & POF_SWORD);
+}
+
+bool po_is_dagger(int po)
+{
+    return (permobjs[po].flags[0] & POF_DAGGER);
+}
+
+bool po_is_polearm(int po)
+{
+    return (permobjs[po].flags[0] & POF_POLEARM);
+}
+
+bool po_is_bludgeon(int po)
+{
+    return (permobjs[po].flags[0] & POF_BLUDGEON);
+}
+
+bool po_is_demonic(int po)
+{
+    return (permobjs[po].flags[0] & POF_DEMONIC);
+}
+
+bool po_is_dress(int po)
+{
+    return (permobjs[po].flags[0] & POF_DRESS);
+}
+
+bool po_is_leatherwear(int po)
+{
+    return (permobjs[po].flags[0] & POF_LEATHERY);
+}
+
+bool po_is_robe(int po)
+{
+    return (permobjs[po].flags[0] & POF_ROBE);
+}
+
+bool po_is_stackable(int po)
+{
+    return (permobjs[po].flags[0] & POF_STACKABLE);
+}
+
+bool po_grants_speed(int po)
+{
+    return (permobjs[po].flags[1] & POF_SPEED);
+}
+
+bool po_grants_flight(int po)
+{
+    return (permobjs[po].flags[1] & POF_FLIGHT);
+}
+
+bool po_grants_protective(int po)
+{
+    return (permobjs[po].flags[1] & POF_PROTECTIVE);
+}
+
+bool po_grants_passwater(int po)
+{
+    return (permobjs[po].flags[1] & POF_PASS_WATER);
 }
 
 void attempt_pickup(void)
 {
     int i;
     int stackable;
-    stackable = po_is_stackable(objects[lvl.obj_at(u.pos)].obj_id);
+    Obj_handle oh = lvl.obj_at(u.pos);
+    Obj *optr = obj_snapv(oh);
+    Obj *invptr;
+    stackable = po_is_stackable(optr->po_ref);
     if (stackable)
     {
         for (i = 0; i < 19; i++)
         {
-            if ((objects[u.inventory[i]].obj_id == objects[lvl.obj_at(u.pos)].obj_id))
+            invptr = obj_snapv(u.inventory[i]);
+            if (invptr && (invptr->po_ref == optr->po_ref))
             {
-                int stale_obj = lvl.obj_at(u.pos);
-                objects[u.inventory[i]].quan += objects[lvl.obj_at(u.pos)].quan;
-                objects[stale_obj].used = false;
+                invptr->quan += optr->quan;
+                optr->flags &= ~OF_USED;
                 lvl.set_obj_at(u.pos, NO_OBJ);
-                notify_get_item(stale_obj, i);
+                notify_get_item(oh, i);
                 return;
             }
         }
@@ -545,14 +525,14 @@ void attempt_pickup(void)
     }
     u.inventory[i] = lvl.obj_at(u.pos);
     lvl.set_obj_at(u.pos, NO_OBJ);
-    objects[u.inventory[i]].with_you = true;
-    objects[u.inventory[i]].pos = Nowhere;
+    optr->flags |= OF_WITH_YOU;
+    optr->pos = Nowhere;
     notify_get_item(NO_OBJ, i);
 }
 
-void break_reaction(int obj)
+void break_reaction(Obj_handle obj)
 {
-    switch (objects[obj].obj_id)
+    switch (objects[obj].po_ref)
     {
     case PO_SET_OF_RIBBONS:
         if (u.food < 500)
@@ -589,22 +569,22 @@ void break_reaction(int obj)
         break;
 
     default:
-        if (permobjs[objects[obj].obj_id].flags[0] & POF_DRESS)
+        if (permobjs[objects[obj].po_ref].flags[0] & POF_DRESS)
         {
             objects[obj].durability = 50 + zero_die(51);
-            objects[obj].obj_id = PO_RAGGED_SHIFT;
+            objects[obj].po_ref = PO_RAGGED_SHIFT;
             notify_dress_shredded();
             recalc_defence();
         }
         else
         {
-            debug_unimplemented_break_reaction(objects[obj].obj_id);
+            debug_unimplemented_break_reaction(objects[obj].po_ref);
         }
         break;
     }
 }
 
-void damage_obj(int obj)
+void damage_obj(Obj_handle obj)
 {
     /* Only weapons and armour have non-zero durability. */
     if (objects[obj].durability == 0)
@@ -615,26 +595,26 @@ void damage_obj(int obj)
     else
     {
         objects[obj].durability--;
-        if ((objects[obj].durability == 0) && (permobjs[objects[obj].obj_id].flags[0] & POF_BREAK_REACT))
+        if ((objects[obj].durability == 0) && (permobjs[objects[obj].po_ref].flags[0] & POF_BREAK_REACT))
         {
             break_reaction(obj);
         }
     }
 } 
 
-int evasion_penalty(int obj)
+int evasion_penalty(Obj_handle obj)
 {
-    if (permobjs[objects[obj].obj_id].poclass == POCLASS_ARMOUR)
+    if (permobjs[objects[obj].po_ref].poclass == POCLASS_ARMOUR)
     {
-        return permobjs[objects[obj].obj_id].power2;
+        return permobjs[objects[obj].po_ref].power2;
     }
     return 100;
 }
 
 Action_cost magic_ring(void)
 {
-    Obj *optr = objects + u.ring;
-    switch (optr->obj_id)
+    Obj *optr = obj_snapv(u.ring);
+    switch (optr->po_ref)
     {
     case PO_TELEPORT_RING:
         if (u.food >= 50)
@@ -650,9 +630,9 @@ Action_cost magic_ring(void)
         }
         return Cost_none;
     default:
-        if (permobjs[optr->obj_id].flags[0] & POF_ACTIVATABLE)
+        if (permobjs[optr->po_ref].flags[0] & POF_ACTIVATABLE)
         {
-            debug_unimplemented_activation(optr->obj_id);
+            debug_unimplemented_activation(optr->po_ref);
         }
         else
         {
@@ -664,9 +644,9 @@ Action_cost magic_ring(void)
 
 Action_cost emanate_armour(void)
 {
-    Obj *optr = objects + u.armour;
+    Obj *optr = obj_snapv(u.armour);
     Action_cost cost = Cost_none;
-    switch (optr->obj_id)
+    switch (optr->po_ref)
     {
     case PO_SET_OF_RIBBONS:
         if (optr->durability < OBJ_MAX_DUR)
@@ -689,9 +669,9 @@ Action_cost emanate_armour(void)
         }
         break;
     default:
-        if (permobjs[optr->obj_id].flags[0] & POF_ACTIVATABLE)
+        if (permobjs[optr->po_ref].flags[0] & POF_ACTIVATABLE)
         {
-            debug_unimplemented_activation(optr->obj_id);
+            debug_unimplemented_activation(optr->po_ref);
         }
         else
         {
@@ -699,64 +679,32 @@ Action_cost emanate_armour(void)
         }
         break;
     }
-    return Cost_none;
+    return cost;
 }
 
 Action_cost zap_weapon(void)
 {
-    Obj *optr = objects + u.weapon;
-    switch (optr->obj_id)
+    Obj *optr = obj_snapv(u.weapon);
+    int i;
+    for (i = 0; weapon_table[i].pobj != NO_POBJ; ++i)
     {
-    case PO_STAFF_OF_FIRE:
-        if (u.food > 150)
+        if (weapon_table[i].pobj == optr->po_ref)
         {
-            Coord c;
-            u.food -= 150;
-            notify_firestaff_activation(1);
-            for (c.y = u.pos.y - 1; c.y <= u.pos.y + 1; ++c.y)
+            if (weapon_table[i].zap_func != nullptr)
             {
-                if ((c.y < lvl.min_y()) || (c.y >= lvl.max_y()))
-                {
-                    continue;
-                }
-                for (c.x = u.pos.x - 1; c.x <= u.pos.x + 1; ++c.x)
-                {
-                    int mon;
-                    if ((c.x < lvl.min_x()) || (c.x >= lvl.max_x()))
-                    {
-                        continue;
-                    }
-                    mon = lvl.mon_at(c);
-                    if (mon != NO_MON)
-                    {
-                        Mon *mptr = monsters + mon;
-                        if (!pmon_resists_fire(mptr->mon_id))
-                        {
-                            notify_fireitem_hit(mon);
-                            damage_mon(mon, dice(4, 10), true);
-                        }
-                    }
-                }
+                weapon_table[i].zap_func(u.weapon);
+                return Cost_std;
             }
-            damage_obj(u.weapon);
-        }
-        else
-        {
-            notify_firestaff_activation(0);
-            return Cost_none;
-        }
-        return Cost_std;
-    default:
-        if (permobjs[optr->obj_id].flags[0] & POF_ACTIVATABLE)
-        {
-            debug_unimplemented_activation(optr->obj_id);
-        }
-        else
-        {
-            notify_zap_powerless_weapon();
+            else if (permobjs[optr->po_ref].flags[0] & POF_ACTIVATABLE)
+            {
+                debug_unimplemented_activation(optr->po_ref);
+                return Cost_none;
+            }
+            break;
         }
-        return Cost_none;
     }
+    notify_zap_powerless_weapon();
+    return Cost_none;
 }
 
 /*! \brief Unwield the player's currently equipped weapon
@@ -778,28 +726,45 @@ Action_cost player_unwield(Noisiness noisy)
     return Cost_std;
 }
 
-Action_cost player_wield(int slot, Noisiness noisy)
+Pass_fail role_equip_check(Obj_handle obj, Noisiness noisy)
 {
-    if (u.weapon != NO_OBJ)
+    Obj const *optr = obj_snap(obj);
+    if ((u.role == Role_demon_hunter) && po_is_demonic(optr->po_ref))
     {
-        player_unwield(Noise_low);
+        notify_role_blocks_equip();
+        return You_fail;
     }
-    u.weapon = u.inventory[slot];
-    notify_wield_weapon(u.weapon);
-    recalc_defence();
-    return Cost_std;
+    return You_pass;
 }
 
-Action_cost wear_armour(int slot)
+Action_cost player_wield(Obj_handle obj, Noisiness noisy)
+{
+    Pass_fail rej = role_equip_check(obj, noisy);
+    if (rej == You_pass)
+    {
+        if (u.weapon != NO_OBJ)
+        {
+            player_unwield(Noise_low);
+        }
+        u.weapon = obj;
+        notify_wield_weapon(u.weapon);
+        recalc_defence();
+        return Cost_std;
+    }
+    else
+    {
+        return Cost_none;
+    }
+}
+
+Action_cost wear_armour(Obj_handle obj)
 {
-    int obj;
     if (u.armour != NO_OBJ)
     {
         debug_wear_while_wearing();
         return Cost_none;
     }
-    obj = u.inventory[slot];
-    if (!objects[obj].with_you)
+    if (!(objects[obj].flags & OF_WITH_YOU))
     {
         debug_wear_uncarried_armour();
         return Cost_none;
@@ -810,14 +775,14 @@ Action_cost wear_armour(int slot)
     return Cost_std;
 }
 
-Action_cost put_on_ring(int obj)
+Action_cost put_on_ring(Obj_handle obj)
 {
     if (u.ring != NO_OBJ)
     {
         debug_put_on_second_ring();
         return Cost_none;
     }
-    if (!objects[obj].with_you)
+    if (!(objects[obj].flags & OF_WITH_YOU))
     {
         debug_put_on_uncarried_ring();
         return Cost_none;
@@ -842,26 +807,45 @@ Action_cost remove_ring(void)
     return Cost_std;
 }
 
-Pass_fail ring_removal_unsafe(Noisiness noisy)
+Pass_fail unequip_safety_check(uint32_t mask, Noisiness noisy)
 {
-    if ((lvl.terrain_at(u.pos) == LAVA) && (u.resistances[DT_FIRE] == RESIST_RING))
+    Terrain t = lvl.terrain_at(u.pos);
+    if (terrain_is_hot(t) &&
+        (!(u.resistances[DT_FIRE] & ~mask)) &&
+        (!(u.flight & ~mask)))
     {
         if (noisy != Noise_silent)
         {
-            notify_lava_blocks_unequip();
+            notify_hot_blocks_unequip();
         }
         return You_fail;
     }
-    else if ((objects[u.ring].obj_id == PO_FROST_RING) && (lvl.terrain_at(u.pos) == WATER))
+    else if (terrain_drowns(t) &&
+             (!(u.passwater & ~mask)) &&
+             (!(u.flight & ~mask)))
     {
         if (noisy != Noise_silent)
         {
-            notify_water_blocks_unequip();
+            notify_wet_blocks_unequip();
+        }
+        return You_fail;
+    }
+    else if (terrain_gapes(t) &&
+             (!(u.flight & ~mask)))
+    {
+        if (noisy != Noise_silent)
+        {
+            notify_pit_blocks_unequip();
         }
         return You_fail;
     }
     return You_pass;
 }
 
+bool corpse_is_blessed(Obj const *optr)
+{
+    return (optr && (optr->po_ref == PO_CORPSE) && (optr->meta[1] == 1));
+}
+
 /* objects.cc */
-// vim:cindent
+// vim:cindent:expandtab
index 6792b54..ce9c394 100644 (file)
@@ -1,8 +1,8 @@
 /*! \file objects.hh
- *  \brief object-related header for Victrix Abyssi
+ *  \brief object-related header
  */
 
-/* Copyright 2005-2013 Martin Read
+/* Copyright 2005-2014 Martin Read
  * 
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #include "permobj.hh"
 #endif
 
+#include <map>
 /* XXX Obj */
 #define OBJ_MAX_DUR 100
 #define OBJECTS_IN_PLAY 100
+#define OF_USED            0x00000001u
+#define OF_WITH_YOU 0x00000002u
 class Obj {
 public:
-    int obj_id;
-    bool used;   /* Entry is occupied. */
-    bool with_you;       /* Preserved when item DB is reaped on level change. */
+    Obj_handle self;
+    int po_ref;
+    uint32_t flags;
     int quan;
     Coord pos;
+    uint32_t meta[4]; // Things like what pmon a corpse is
     int durability;     /* Weapons and armour degrade with use. */
 };
-extern Obj objects[OBJECTS_IN_PLAY];
-
-#define NO_OBJ (-1)
-
-/* XXX objects.c data and funcs */
-extern void flavours_init(void);
-extern void sprint_obj_name(char *s, int obj, int len);
-extern void fprint_obj_name(FILE *fp, int obj);
-extern void print_obj_name(int obj);
-extern void describe_object(int obj);
-extern int create_obj(int po_idx, int quantity, bool with_you, Coord c);
-extern bool consume_obj(int obj);
-extern int create_obj_class(enum poclass_num pocl, int quantity, bool with_you, Coord c);
-extern void damage_obj(int obj);
-extern int evasion_penalty(int obj);
-
-extern Action_cost drop_obj(int inv_idx);
-extern Action_cost put_on_ring(int obj);
-extern Action_cost remove_ring(void);
-extern Action_cost wear_armour(int obj);
-extern Action_cost take_off_armour(void);
-extern Action_cost read_scroll(int obj);
-extern Action_cost quaff_potion(int obj);
-extern Action_cost eat_food(int obj);
-extern Action_cost magic_ring(void);
-extern Action_cost emanate_armour(void);
-extern Action_cost zap_weapon(void);
-extern Action_cost player_unwield(Noisiness noisy = Noise_std);
-extern Action_cost player_wield(int slot, Noisiness noisy = Noise_std);
-extern void attempt_pickup(void);
-
-extern Pass_fail ring_removal_unsafe(Noisiness noisy = Noise_std);
-extern Pass_fail armour_removal_unsafe(Noisiness noisy = Noise_std);
+extern std::map<Obj_handle, Obj> objects;
+extern Obj_handle first_free_obj_handle;
+
+struct Potion_table_entry
+{
+    int pobj;
+    void (*quaff_func)(Obj_handle obj);
+};
+extern Potion_table_entry potion_table[];
+
+struct Scroll_table_entry
+{
+    int pobj;
+    void (*read_func)(Obj_handle obj);
+};
+
+extern Scroll_table_entry scroll_table[];
+
+struct Weapon_table_entry
+{
+    int pobj;
+    void (*zap_func)(Obj_handle obj);
+    void (*on_wield)(Obj_handle obj);
+    void (*on_unwield)(Obj_handle obj);
+};
+
+extern Weapon_table_entry weapon_table[];
+
+/* XXX objects.cc data and funcs */
+void asprint_obj_name(char **s, Obj_handle obj);
+void sprint_obj_name(char *s, Obj_handle obj, int len);
+void fprint_obj_name(FILE *fp, Obj_handle obj);
+void describe_object(Obj_handle obj);
+Obj_handle create_corpse(int pm_idx, Coord c);
+Obj_handle create_obj_near(int po_idx, int quantity, Coord c);
+Obj_handle create_obj(int po_idx, int quantity, bool with_you, Coord c);
+bool consume_obj(Obj_handle obj);
+Obj_handle create_obj_class_near(enum poclass_num pocl, int quantity, bool with_you, Coord c);
+void damage_obj(Obj_handle obj);
+int evasion_penalty(Obj_handle obj);
+bool corpse_is_blessed(Obj const *optr);
+
+inline Obj *obj_snapv(Obj_handle obj)
+{
+    auto iter = objects.find(obj);
+    return ((iter == objects.end()) ? nullptr : &(iter->second));
+}
+
+inline Obj const *obj_snap(Obj_handle obj)
+{
+    auto iter = objects.find(obj);
+    return ((iter == objects.end()) ? nullptr : &(iter->second));
+}
+
+Action_cost drop_obj(int inv_idx);
+Action_cost put_on_ring(Obj_handle obj);
+Action_cost remove_ring(void);
+Action_cost wear_armour(Obj_handle obj);
+Action_cost take_off_armour(void);
+Action_cost read_scroll(Obj_handle obj);
+Action_cost quaff_potion(Obj_handle obj);
+Action_cost eat_food(Obj_handle obj);
+Action_cost magic_ring(void);
+Action_cost emanate_armour(void);
+Action_cost zap_weapon(void);
+Action_cost player_unwield(Noisiness noisy = Noise_std);
+Action_cost player_wield(Obj_handle obj, Noisiness noisy = Noise_std);
+void attempt_pickup(void);
+Pass_fail unequip_safety_check(uint32_t mask, Noisiness noisy = Noise_std);
 
 #endif
 
index 985c8a0..77690ff 100644 (file)
@@ -1,5 +1,5 @@
 /*! \file permobj.hh
- *  \brief permobj-related header for Victrix Abyssi
+ *  \brief permobj-related header
  */
 
 /* Copyright 2014 Martin Read
@@ -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
@@ -64,12 +64,35 @@ enum poclass_num {
 #define POF_STACKABLE 0x00000004u // item can stack
 #define POF_DAMAGEABLE 0x00000008u // track durability
 #define POF_BREAK_REACT 0x00000010u // item reacts to breakage attempts
-/* 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_ROBE            0x00020000u
+#define POF_LEATHERY        0x00040000u
+#define POF_MELEE_WEAPON    0x00010000u
+#define POF_RANGED_WEAPON   0x00020000u
+#define POF_SWORD           0x00040000u
+#define POF_POLEARM         0x00080000u
+#define POF_BLUDGEON        0x00100000u
+#define POF_DAGGER          0x00200000u
+#define POF_WHIP            0x00400000u
+#define POF_DEMONIC         0x10000000u
+// POF field 1
+#define POF_RES_FIRE    ((uint32_t) DT_FIRE)
+#define POF_RES_COLD    ((uint32_t) DT_COLD)
+#define POF_RES_ELEC    ((uint32_t) DT_NECRO)
+#define POF_RES_NECRO   ((uint32_t) DT_ELEC)
+#define POF_RES_POISON  ((uint32_t) DT_POISON)
+#define POF_RES_MASK    0x000000ffu
+#define POF_DMG_FIRE    (((uint32_t) DT_FIRE) << POF_DMG_SHIFT)
+#define POF_DMG_COLD    (((uint32_t) DT_COLD) << POF_DMG_SHIFT)
+#define POF_DMG_ELEC    (((uint32_t) DT_ELEC) << POF_DMG_SHIFT)
+#define POF_DMG_NECRO   (((uint32_t) DT_NECRO) << POF_DMG_SHIFT)
+#define POF_DMG_POISON  (((uint32_t) DT_POISON) << POF_DMG_SHIFT)
+#define POF_DMG_SHIFT   8
+#define POF_DMG_MASK    (0xffu << POF_DMG_SHIFT)
+#define POF_PASS_WATER  0x00010000u
+#define POF_FLIGHT      0x00020000u
+#define POF_PROTECTIVE  0x00100000u // all damage from enemy attacks halved
+#define POF_SPEED       0x01000000u // bumps you up one speed band
 
 /*! \brief The 'permanent object' database */
 class Permobj {
@@ -89,11 +112,26 @@ public:
 };
 #define NO_POBJ (-1)
 
-extern Permobj permobjs[NUM_OF_PERMOBJS];
-extern bool po_is_stackable(int po);
+extern const int NUM_OF_PERMOBJS;
+extern Permobj permobjs[];
+
+bool po_is_stackable(int po);
+bool po_is_dress(int po);
+bool po_is_leatherwear(int po);
+bool po_is_robe(int po);
+bool po_is_sword(int po);
+bool po_is_dagger(int po);
+bool po_is_polearm(int po);
+bool po_is_bludgeon(int po);
+bool po_is_demonic(int po);
+Damtyp po_resistance(int po);
+Damtyp po_damage_amp(int po);
+bool po_grants_flight(int po);
+bool po_grants_passwater(int po);
+bool po_grants_protective(int po);
+bool po_grants_speed(int po);
 
 #endif
 
 /* permobj.hh */
-// vim:cindent
-
+// vim:cindent:expandtab
index 0fc6fcb..e33fac2 100644 (file)
--- a/permon.hh
+++ b/permon.hh
 #define PMF_QUADRUPED   0x10000000
 #define PMF_SKITTERISH  0x20000000
 #define PMF_HUGE        0x40000000
-#define PMF_SMALL       0x80000000
+#define PMF_SMALL       0x80000000u
+
+#define PMF_MADE_OF_MEAT    0x00000001u
+#define PMF_MADE_OF_GOO     0x00000002u
+#define PMF_MADE_OF_BONE    0x00000004u
+#define PMF_MADE_OF_METAL   0x00000008u
+#define PMF_MADE_OF_ICE     0x00000010u
+#define PMF_MADE_OF_FIRE    0x00000020u
+#define PMF_CONTAINS_BLOOD  0x00010000u
+#define PMF_CONTAINS_PUS    0x00020000u
+#define PMF_BURSTS_ON_DEATH 0x40000000u
+#define PMF_LEAVES_CORPSE   0x80000000u
 
 #define PERMON_FLAG_FIELDS 2
 /*! \brief Describes the baseline stats of a specific kind of monster */
@@ -84,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)
@@ -93,33 +105,38 @@ enum AI_mode {
     AI_charger, AI_archer, AI_dodger, AI_drunk, AI_seeker, AI_chaser
 };
 
-/* XXX pmon2.c data and funcs */
-extern bool pmon_is_archer(int pm);
-extern bool pmon_is_magician(int pm);
-extern bool pmon_is_smart(int pm);
-extern bool pmon_is_stupid(int pm);
-
-extern bool pmon_resists_cold(int pm);
-extern bool pmon_resists_fire(int pm);
-extern bool pmon_resists_poison(int pm);
-extern bool pmon_resists_necro(int pm);
-extern bool pmon_resists_elec(int pm);
-extern bool pmon_resists_drowning(int pm);
-extern bool pmon_resists_knockback(int pm);
-
-extern bool pmon_can_fly(int pm);
-
-extern bool pmon_is_ethereal(int pm);
-extern bool pmon_is_undead(int pm);
-extern bool pmon_is_demonic(int pm);
-
-extern bool pmon_has_hands(int pm);
-extern bool pmon_is_humanoid(int pm);
-extern bool pmon_is_quadruped(int pm);
-extern bool pmon_is_skitterish(int pm);
-extern bool pmon_is_amorphous(int pm);
-extern bool pmon_is_huge(int pm);
-extern bool pmon_is_small(int pm);
+bool pmon_is_archer(int pm);
+bool pmon_is_magician(int pm);
+bool pmon_is_smart(int pm);
+bool pmon_is_stupid(int pm);
+
+bool pmon_resists_cold(int pm);
+bool pmon_resists_fire(int pm);
+bool pmon_resists_poison(int pm);
+bool pmon_resists_necro(int pm);
+bool pmon_resists_elec(int pm);
+bool pmon_resists_drowning(int pm);
+bool pmon_resists_knockback(int pm);
+
+bool pmon_can_fly(int pm);
+
+bool pmon_is_ethereal(int pm);
+bool pmon_is_undead(int pm);
+bool pmon_is_demonic(int pm);
+
+bool pmon_has_hands(int pm);
+bool pmon_is_humanoid(int pm);
+bool pmon_is_quadruped(int pm);
+bool pmon_is_skitterish(int pm);
+bool pmon_is_amorphous(int pm);
+bool pmon_is_huge(int pm);
+bool pmon_is_small(int pm);
+
+inline bool pmon_bleeds(int pm) { return permons[pm].flags[1] & PMF_CONTAINS_BLOOD; }
+inline bool pmon_suppurates(int pm) { return permons[pm].flags[1] & PMF_CONTAINS_PUS; }
+inline bool pmon_explodes(int pm) { return permons[pm].flags[1] & PMF_BURSTS_ON_DEATH; }
+inline bool pmon_leaves_corpse(int pm) { return permons[pm].flags[1] & PMF_LEAVES_CORPSE; }
+Decal_tag pmon_fluid_decal(int pm);
 
 #endif
 
index c9740f4..60eec59 100644 (file)
--- a/player.hh
+++ b/player.hh
@@ -1,5 +1,5 @@
 /*! \file player.hh
- *  \brief Player character  header for Victrix Abyssi
+ *  \brief Player character header
  */
 
 /* Copyright 2014 Martin Read
 #ifndef PLAYER_HH
 #define PLAYER_HH
 
-#include "victrix-abyssi.hh"
+#ifndef CORE_HH
+#include "core.hh"
+#endif
+
+enum Skill_id
+{
+    NO_SKILL = -1,
+    Skill_power_attack = 0,
+    /* Princess skills */
+    Skill_blood_royal,
+    Skill_flying_leap,
+    Skill_regal_radiance,
+    /* Demon Hunter skills */
+    Skill_charge,
+    Skill_blade_dance,
+    Skill_demon_slayer,
+    /* Thanatophile skills */
+    Skill_deaths_grace,
+    Skill_deaths_vengeance,
+    Skill_sanctified_by_death
+};
+
+#define LAST_SKILL (Skill_sanctified_by_death)
+#define NUM_SKILLS (1 + LAST_SKILL)
+
+/*! \brief Skill descriptor */
+struct Skill_desc
+{
+    char const *name;
+    char const *desc;
+};
+
+extern Skill_desc skill_props[NUM_SKILLS];
+
+struct Skill_gain_table
+{
+    int level;
+    Skill_id skill;
+};
+
+extern Skill_gain_table universal_skills[];
+extern Skill_gain_table *skill_tables[NUM_ROLES];
 
 /*! \brief Internal representation of the player character 
  */
+#define INVENTORY_SIZE 19
 class Player {
 public:
     char name[17];  //!< Allows 16 actual characters, plus 0 terminator
+    Mon_handle mh;    //!< Handle for monster representing the player.
+    Mon *mptr;   //!< Pointer for monster representing the player.
     Coord pos;      //!< Position within current dungeon level.
+    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.
@@ -51,13 +96,18 @@ public:
     int withering;  //!< Vile withering curse.
     int armourmelt; //!< Armour-like-dust curse.
     int speed;      //!< Controls how often you act.
-    uint32_t resistances[DT_COUNT]; //!< Resistance masks per damage type
+    uint32_t resistances[NUM_DAMTYPES]; //!< Resistance masks per damage type
+    uint32_t damage_amp[NUM_DAMTYPES]; //!< Damage amplification masks per damage type
+    uint32_t passwater; //!< Can currently walk on water tiles
+    uint32_t flight;   //!< Ignore all hazardfloors and pits
+    uint32_t protective_gear;   //!< Reduce all damage taken
     int level;      //!< Current experience level.
-    int inventory[19];  //!< Object handles of currently carried items.
-    int weapon;     //!< Object handle of currently equipped weapon.
-    int armour;     //!< Object handle of currently equipped armour.
-    int ring;       //!< Object handle of currently equipped ring.
+    Obj_handle inventory[INVENTORY_SIZE];  //!< currently carried items.
+    Obj_handle weapon;     //!< currently equipped weapon.
+    Obj_handle armour;     //!< currently equipped armour.
+    Obj_handle ring;       //!< currently equipped ring.
     int sympathy[TOTAL_FELL_POWERS]; //!< Level of alignment with fell powers
+    bool known_skill[NUM_SKILLS]; //!< 
     /* Methods only after here plzkthx */
     bool resists(Damtyp dtype) const; //!< Does player resist this Damtyp?
     bool martial(void) const //!< Is player significantly influenced by iron?
@@ -76,10 +126,16 @@ public:
 #define SLOT_CANCEL (-1)
 #define SLOT_NOTHING (-2)
 
-/* XXX u.c data and funcs */
 extern Player u;
 
-void u_init(char const *name);
+typedef Action_cost (*Deed_func)(Action const *act);
+
+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, 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);
@@ -98,8 +154,28 @@ void update_player(void);
 inline bool empty_handed(void) { return u.weapon == NO_OBJ; }
 bool wielding_melee_weapon(void);
 bool wielding_ranged_weapon(void);
+Action_cost do_player_action(Action *act);
 
 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 10e7a3d..42896da 100644 (file)
--- a/pmon2.cc
+++ b/pmon2.cc
@@ -27,7 +27,7 @@
  */
 
 #define PMON2_CC
-#include "victrix-abyssi.hh"
+#include "core.hh"
 #include "monsters.hh"
 
 bool pmon_resists_fire(int pm)
@@ -70,6 +70,11 @@ bool pmon_is_undead(int pm)
     return !!(permons[pm].flags[0] & PMF_UNDEAD);
 }
 
+bool pmon_is_demonic(int pm)
+{
+    return !!(permons[pm].flags[0] & PMF_DEMONIC);
+}
+
 bool pmon_is_stupid(int pm)
 {
     return !!(permons[pm].flags[0] & PMF_STUPID);
@@ -100,5 +105,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 e2405e1..51a7f2a 100755 (executable)
--- a/pmon_comp
+++ b/pmon_comp
@@ -13,6 +13,8 @@ sub usage()
 
 our @monsters;
 
+our $max_flag_index = 1;
+
 our %flag_indices =
 (
     'RESIST_FIRE' => 0,
@@ -38,6 +40,16 @@ our %flag_indices =
     'SKITTERISH' => 0,
     'HUGE' => 0,
     'SMALL' => 0,
+    'MADE_OF_MEAT' => 1,
+    'MADE_OF_GOO' => 1,
+    'MADE_OF_BONE' => 1,
+    'MADE_OF_METAL' => 1,
+    'MADE_OF_ICE' => 1,
+    'MADE_OF_FIRE' => 1,
+    'CONTAINS_BLOOD' => 1,
+    'CONTAINS_PUS' => 1,
+    'BURSTS_ON_DEATH' => 1,
+    'LEAVES_CORPSE' => 1,
 );
 
 
@@ -59,15 +71,19 @@ sub flag_string($)
     else
     {
         my $name;
+       my $i;
+       for ($i = 0; $i <= $max_flag_index; ++$i)
+       {
+            $#flag_fields = $i if ($i > $#flag_fields);
+            if (!defined($flag_fields[$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] .= "| PMF_$name ";
         }
     }
@@ -353,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)
@@ -372,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..9f8e756 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,28 @@ our %flag_indices =
     'DRESS' => 0,
     'RANGED_WEAPON' => 0,
     'MELEE_WEAPON' => 0,
+    'SWORD' => 0,
+    'DAGGER' => 0,
+    'BLUDGEON' => 0,
+    'POLEARM' => 0,
+    'WHIP' => 0,
+    'DEMONIC' => 0,
+    'ROBE' => 0,
+    'LEATHERY' => 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,
+    'SPEED' => 1
 );
 
 
@@ -50,15 +73,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 +364,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 +405,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 +426,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 +447,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 +468,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 +489,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 +510,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 +518,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/rng.hh b/rng.hh
index f68b254..b42a427 100644 (file)
--- a/rng.hh
+++ b/rng.hh
@@ -1,5 +1,5 @@
 /*! \file rng.hh
- *  \brief RNG-related header for Victrix Abyssi
+ *  \brief RNG-related header
  */
 
 /* Copyright 2014 Martin Read
diff --git a/role.cc b/role.cc
new file mode 100644 (file)
index 0000000..8472705
--- /dev/null
+++ b/role.cc
@@ -0,0 +1,78 @@
+/*! \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 "core.hh"
+#include "player.hh"
+#include "monsters.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_DAGGER, 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
index 072751e..e9e026b 100644 (file)
--- a/shrine.cc
+++ b/shrine.cc
@@ -106,7 +106,7 @@ struct shrine shrines[4] =
 };
 
 /*! \brief Construction helper for shrines */
-void place_shrine(Coord topleft, shrine const *sh, uint32_t accept_conns, Terrain shwall, Terrain shfloor, bool margin_is_wall)
+void place_shrine(Level *l, Coord topleft, shrine const *sh, uint32_t accept_conns, Terrain shwall, Terrain shfloor, bool margin_is_wall)
 {
     Coord c;
     Coord start;
@@ -165,38 +165,38 @@ void place_shrine(Coord topleft, shrine const *sh, uint32_t accept_conns, Terrai
             switch (sh->grid[shy][shx])
             {
             case '0':
-                lvl.set_terrain_at(c, shfloor);
+                l->set_terrain_at(c, shfloor);
                 /* We want to be able to slap this down as a room, which means
                  * that at least one square on the connectable edges must not
                  * be HARDWALL. Those squares are marked '0'. */
                 break;
             case '1':
-                lvl.set_terrain_at(c, margin_is_wall ? shwall : shfloor);
-                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                l->set_terrain_at(c, margin_is_wall ? shwall : shfloor);
+                l->set_flags_at(c, MAPFLAG_HARDWALL);
                 break;
             case '.':
-                lvl.set_terrain_at(c, shfloor);
-                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                l->set_terrain_at(c, shfloor);
+                l->set_flags_at(c, MAPFLAG_HARDWALL);
                 break;
             case '#':
-                lvl.set_terrain_at(c, shwall);
-                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                l->set_terrain_at(c, shwall);
+                l->set_flags_at(c, MAPFLAG_HARDWALL);
                 break;
             case '+':
-                lvl.set_terrain_at(c, DOOR);
-                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                l->set_terrain_at(c, DOOR);
+                l->set_flags_at(c, MAPFLAG_HARDWALL);
                 break;
             case '_':
-                lvl.set_terrain_at(c, ALTAR);
-                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                l->set_terrain_at(c, ALTAR);
+                l->set_flags_at(c, MAPFLAG_HARDWALL);
                 break;
             case 'L':
-                lvl.set_terrain_at(c, LAVA);
-                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                l->set_terrain_at(c, LAVA);
+                l->set_flags_at(c, MAPFLAG_HARDWALL);
                 break;
             case 'W':
-                lvl.set_terrain_at(c, WATER);
-                lvl.set_flags_at(c, MAPFLAG_HARDWALL);
+                l->set_terrain_at(c, WATER);
+                l->set_flags_at(c, MAPFLAG_HARDWALL);
                 break;
             }
         }
@@ -205,7 +205,7 @@ void place_shrine(Coord topleft, shrine const *sh, uint32_t accept_conns, Terrai
 }
 
 /*! \brief Excavate a cave-with-shrine level. */
-void build_level_shrine(void)
+void build_level_shrine(Level *l)
 {
     Coord shrinepos = { inc_flat(GUIDE_EDGE_SIZE / 4, GUIDE_EDGE_SIZE / 2), inc_flat(GUIDE_EDGE_SIZE / 4, GUIDE_EDGE_SIZE / 2) };
     Coord c;
@@ -217,11 +217,11 @@ void build_level_shrine(void)
     int intrusions;
     int i;
 
-    initialize_chunks(&lvl, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
+    initialize_chunks(l, GUIDE_EDGE_CHUNKS, GUIDE_EDGE_CHUNKS, true);
     intrusions = dice(2, 4) - 1;
     for (i = 0; i < 6; ++i)
     {
-        place_random_intrusion(WALL);
+        place_random_intrusion(l, WALL);
     }
     switch (zero_die(4))
     {
@@ -253,12 +253,12 @@ void build_level_shrine(void)
         c3 = c + North;
         mask = ROOMCONN_EAST;
     }
-    place_shrine(shrinepos, shrines + shrine_num, mask);
-    lvl.set_terrain_at(c, FLOOR);
-    lvl.set_terrain_at(c2, FLOOR);
-    lvl.set_terrain_at(c3, FLOOR);
-    run_random_walk_unbounded(c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
-    place_cave_stairs();
+    place_shrine(l, shrinepos, shrines + shrine_num, mask);
+    l->set_terrain_at(c, FLOOR);
+    l->set_terrain_at(c2, FLOOR);
+    l->set_terrain_at(c3, FLOOR);
+    run_random_walk_unbounded(l, c, excavation_write, walk_data, LEVGEN_WALK_CELLS);
+    place_cave_stairs(l);
 }
 
 /* shrine.cc */
diff --git a/skills.cc b/skills.cc
new file mode 100644 (file)
index 0000000..fde8c04
--- /dev/null
+++ b/skills.cc
@@ -0,0 +1,142 @@
+/*! \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 "victrix-abyssi.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."),
+    },
+    {
+        "Blood Royal",
+        ("The stress of your banishment has partially unlocked the power of "
+         "your bloodline, allowing you to make full use of certain magical "
+         "items that would be ineffective, or less effective, for others."),
+    },
+    {
+       "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. This "
+         "ability has no effect if you have equipped an item allowing you to "
+         "fly."),
+    },
+    {
+        "Regal Radiance",
+        ("If you are wearing an imperatrix gown, you can use the talismans "
+         "embroidered into it to stun nearby enemies for five turns using "
+         "the (E)manate command, at a cost of 100 points of food. Stunned "
+         "enemies cannot take any action against you."),
+    },
+    {
+       "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 protection from the malign arts of "
+         "necromancers, making you immune to the necromantic damage type. "
+         "This does not make you immune to effects such as the 'leadfoot' "
+         "curse."),
+    },
+    {
+        "Death's Vengeance",
+        ("Your melee attacks are imbued with the blessing of beloved Death, "
+         "causing them to do double damage against the undead."),
+    },
+    {
+        "Sanctified by Death",
+        ("Each turn, all corpses adjacent to you are purified by beloved "
+         "Death's holy touch, making them impossible to reanimate as zombies. "
+         "This ability has no effect on existing zombies."),
+    },
+};
+
+Skill_gain_table universal_skills[] = {
+    { 2, Skill_power_attack },
+    { 0, NO_SKILL }
+};
+
+static Skill_gain_table princess_skills[] = {
+    { 2, Skill_flying_leap },
+    //{ 4, Skill_blood_royal },
+    //{ 10, Skill_regal_radiance },
+    { 0, NO_SKILL }
+};
+
+static Skill_gain_table demon_hunter_skills[] = {
+    { 2, Skill_charge },
+    { 4, Skill_demon_slayer },
+    { 6, Skill_blade_dance },
+    { 0, NO_SKILL }
+};
+
+static Skill_gain_table thanatophile_skills[] = {
+    { 2, Skill_deaths_grace },
+    { 4, Skill_deaths_vengeance },
+    { 6, Skill_sanctified_by_death },
+    { 0, NO_SKILL }
+};
+
+Skill_gain_table *skill_tables[NUM_ROLES] = {
+    princess_skills,
+    demon_hunter_skills,
+    thanatophile_skills
+};
+
+/* skills.cc */
+// vim:cindent:expandtab
index badc1ae..8cae08d 100644 (file)
@@ -2,7 +2,7 @@
  *  \brief evil magic used by monsters
  */
 
-/* Copyright 2005-2013 Martin Read
+/* Copyright Â© 2005-2014 Martin Read
  * 
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -28,6 +28,7 @@
 #include "victrix-abyssi.hh"
 #include "sorcery.hh"
 #include "monsters.hh"
+#include "objects.hh"
 #include "combat.hh"
 
 /* SORCERY
  * with high enough agility.
  */
 
-int mon_use_sorcery(int mon)
+typedef void (*Monspell_cast_func)(Mon_handle mon);
+
+static void cast_strike_staff(Mon_handle mon)
 {
-    /* Returns zero for no spell selected, -1 for unsupported spell
-     * selected, 1 for supported spell selected. */
-    Mon *mptr = monsters + mon;
-    Offset delta = u.pos.delta(mptr->pos);
-    Offset step = mysign(delta);
-    enum monspell to_cast = MS_REJECT;
-    int rval = 1;       /* Default to success; failure paths will force this
-                         * to an appropriate value. */
-    int range = delta.len_cheb();
-    bool meleerange = (range < 2);
-    bool oncardinal = delta.rcardinal();
-    int dieroll;
-    bool cansee = mon_visible(mon);
-    int i;
+    mhitu(mon, DT_PHYS);
+}
+
+static void cast_necro_staff(Mon_handle mon)
+{
+    mhitu(mon, DT_NECRO);
+}
+
+static void cast_chilling_touch(Mon_handle mon)
+{
+    mhitu(mon, DT_COLD);
+}
+
+static void cast_lightning_bolt(Mon_handle mon)
+{
+    mshootu(mon);
+}
 
-    switch (monsters[mon].mon_id)
+static void cast_necro_bolt(Mon_handle mon)
+{
+    mshootu(mon);
+}
+
+static void cast_necro_smite(Mon_handle mon)
+{
+    notify_monster_cursing(mon);
+    if (u.resists(DT_NECRO))
+    {
+       notify_necrosmite_fail();
+    }
+    else
     {
-    case PM_ARCHMAGE:
-        if (cansee)
-        {
-            /* We have LOS; choose a spell on that basis. */
-            if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 2))
-            {
-                to_cast = zero_die(3) ? MS_TELEPORT_ESCAPE : MS_TELEPORT_AND_SUMMON;
-            }
-            else if (meleerange && (zero_die(10) > 3))
-            {
-                to_cast = MS_STRIKE_STAFF;
-            }
-            else if (oncardinal)
-            {
-                to_cast = MS_LIGHTNING;
-            }
-        }
-        else if (!zero_die(40))
-        {
-            /* 
-             * We lack LOS, but pass the 1-in-40 chance; use
-             * sorcery to relocate us to the player's location.
-             */
-            to_cast = MS_TELEPORT_ASSAULT;
-        }
-        break;
+       damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].pm_ref].name);
+    }
+}
 
-    case PM_WIZARD:
-        if (cansee)
-        {
-            if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 2))
-            {
-                to_cast = MS_TELEPORT_ESCAPE;
-            }
-            else if (meleerange && (zero_die(10) > 2))
-            {
-                to_cast = MS_STRIKE_STAFF;
-            }
-            else if (oncardinal)
-            {
-                to_cast = MS_LIGHTNING;
-            }
-        }
-        else if (!zero_die(80))
-        {
-            /* we lack LOS, but passed the 1-in-80 chance to
-             * close with the player by means of sorcery. */
-            to_cast = MS_TELEPORT_ASSAULT;
-        }
-        break;
+static void cast_fire_column(Mon_handle mon)
+{
+    notify_monster_cursing(mon);
+    notify_hellfire_hit(u.resists(DT_FIRE));
+    if (u.resists(DT_FIRE))
+    {
+       damage_u(dice(1, 5), DEATH_KILLED_MON, permons[monsters[mon].pm_ref].name);
+    }
+    else
+    {
+       damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].pm_ref].name);
+    }
+}
+
+static void cast_teleport_escape(Mon_handle mon)
+{
+    teleport_mon(mon);
+    notify_mon_disappear(mon);
+}
+
+static void cast_teleport_summon(Mon_handle mon)
+{
+    Mon *mptr = mon_snapv(mon);
+    int i = summoning(mptr->pos, dice(2, 3));
+    mptr->next_summon = game_tick + 1000;
+    notify_summon_help(mon, (i > 0));
+    teleport_mon(mon);
+    notify_mon_disappear(mon);
+}
 
-    case PM_MASTER_LICH:
-        if (cansee)
-        {
-            if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 4))
-            {
-                to_cast = ((mptr->next_summon < game_tick) || !zero_die(3)) ? MS_TELEPORT_ESCAPE : MS_TELEPORT_AND_SUMMON;
-            }
-            else if (meleerange)
-            {
-                switch (zero_die(7))
-                {
-                case 6:
-                    if (!u.withering)
-                    {
-                        to_cast = MS_CURSE_WITHERING;
-                        break;
-                    }
-                case 5:
-                    if (!u.leadfoot)
-                    {
-                        to_cast = MS_CURSE_LEADFOOT;
-                        break;
-                    }
-                    /* fall through */
-                case 4:
-                    if (!u.armourmelt)
-                    {
-                        to_cast = MS_CURSE_ARMOURMELT;
-                        break;
-                    }
-                    /* fall through */
-                default:
-                    to_cast = zero_die(2) ? MS_CHILLING_TOUCH : MS_STRIKE_STAFF;
-                    break;
-                }
-            }
-            else if (range < 3)
-            {
-                switch (zero_die(10))
-                {
-                case 9:
-                    if (!u.withering)
-                    {
-                        to_cast = MS_CURSE_WITHERING;
-                        break;
-                    }
-                case 8:
-                    if (!u.leadfoot)
-                    {
-                        to_cast = MS_CURSE_LEADFOOT;
-                        break;
-                    }
-                    /* fall through */
-                case 7:
-                    if (!u.armourmelt)
-                    {
-                        to_cast = MS_CURSE_ARMOURMELT;
-                        break;
-                    }
-                    /* fall through */
-                default:
-                    to_cast = MS_NECRO_SMITE;
-                    break;
-                }
-            }
-            else if (range < 8)
-            {
-                switch (zero_die(7))
-                {
-                case 6:
-                    if (!u.withering)
-                    {
-                        to_cast = MS_CURSE_WITHERING;
-                        break;
-                    }
-                case 4:
-                    if (!u.leadfoot)
-                    {
-                        to_cast = MS_CURSE_LEADFOOT;
-                        break;
-                    }
-                    /* fall through */
-                case 5:
-                    if (!u.armourmelt)
-                    {
-                        to_cast = MS_CURSE_ARMOURMELT;
-                        break;
-                    }
-                    /* fall through */
-                default:
-                    to_cast = MS_NECRO_SMITE;
-                    break;
-                }
-            }
-        }
-        else if (!zero_die(40))
-        {
-            /* we lack LOS, but passed the 1-in-40 chance to
-             * close with the player by means of sorcery. */
-            to_cast = MS_TELEPORT_ASSAULT;
-        }
-        break;
-    case PM_LICH:
-        if (cansee)
-        {
-            if (meleerange)
-            {
-                dieroll = zero_die(6);
-                switch (dieroll)
-                {
-                case 4:
-                    if (!u.leadfoot)
-                    {
-                        to_cast = MS_CURSE_LEADFOOT;
-                        break;
-                    }
-                    /* fall through */
-                case 5:
-                    if (!u.armourmelt)
-                    {
-                        to_cast = MS_CURSE_ARMOURMELT;
-                        break;
-                    }
-                    /* fall through */
-                default:
-                    to_cast = MS_NECRO_STAFF;
-                    break;
-                }
-            }
-            else if (oncardinal)
-            {
-                if (range < 3)
-                {
-                    switch (zero_die(6))
-                    {
-                    case 4:
-                        if (!u.leadfoot)
-                        {
-                            to_cast = MS_CURSE_LEADFOOT;
-                            break;
-                        }
-                        /* fall through */
-                    case 5:
-                        if (!u.armourmelt)
-                        {
-                            to_cast = MS_CURSE_ARMOURMELT;
-                            break;
-                        }
-                        /* fall through */
-                    default:
-                        to_cast = MS_NECRO_BOLT;
-                        break;
-                    }
-                }
-                else
-                {
-                    to_cast = MS_NECRO_BOLT;
-                }
-            }
-            break;
-        }
-        break;
-    case PM_DEFILER:
-        if (cansee)
-        {
-            if (!meleerange || !zero_die(3))
-            {
-                // 1-in-7 chance of cursing you when in melee range
-                // 3-in-7 chance when out of melee range
-                switch (zero_die(7))
-                {
-                case 6:
-                    if (!u.withering)
-                    {
-                        to_cast = MS_CURSE_WITHERING;
-                        break;
-                    }
-                case 4:
-                    if (!u.leadfoot)
-                    {
-                        to_cast = MS_CURSE_LEADFOOT;
-                        break;
-                    }
-                    /* fall through */
-                case 5:
-                    if (!u.armourmelt)
-                    {
-                        to_cast = MS_CURSE_ARMOURMELT;
-                        break;
-                    }
-                    /* fall through */
-                default:
-                    to_cast = MS_REJECT;
-                    break;
-                }
-            }
-        }
-        break;
+static void cast_teleport_assault(Mon_handle mon)
+{
+    teleport_mon_to_you(mon);
+}
 
-    default:
-        break;
+static void cast_withering(Mon_handle mon)
+{
+    notify_monster_cursing(mon);
+    if (u.protection)
+    {
+       notify_moncurse_fail();
     }
-    switch (to_cast)
+    else
     {
-    default:
-        /* If this happens, we're trying to cast an unimplemented
-         * spell. */
-        debug_bad_monspell(to_cast);
-        rval = -1;
-        break;
+       u.withering = 10 + one_die(10);
+       recalc_defence();
+       notify_start_withering();
+    }
+}
+
+static void cast_leadfoot(Mon_handle mon)
+{
+    notify_monster_cursing(mon);
+    if (u.protection)
+    {
+       notify_moncurse_fail();
+    }
+    else
+    {
+       u.leadfoot = 10 + one_die(10);
+       recalc_defence();
+       notify_start_leadfoot();
+    }
+}
+
+static void cast_armourmelt(Mon_handle mon)
+{
+    notify_monster_cursing(mon);
+    if (u.protection)
+    {
+       notify_moncurse_fail();
+    }
+    else
+    {
+       u.armourmelt = 10 + one_die(10);
+       recalc_defence();
+       notify_start_armourmelt();
+    }
+}
 
-    case MS_REJECT:
-        /* No usable spell available. */
-        rval = 0;
-        break;
+static bool reanimation_effector(Coord c, void *pvt)
+{
+    Obj_handle o = lvl.obj_at(c);
+    Mon_handle m = lvl.mon_at(c);
+    if (m == NO_MON)
+    {
+       Obj const *optr = obj_snap(o);
+       if (!corpse_is_blessed(optr))
+       {
+           m = create_mon(PM_ZOMBIE, c);
+           if (m != NO_MON)
+           {
+               consume_obj(o);
+               return true;
+           }
+       }
+    }
+    return false;
+}
 
-    case MS_STRIKE_STAFF:
-        mhitu(mon, DT_PHYS);
-        break;
+static Radiance reanimate_radiance = 
+{
+    {},
+    Nowhere,
+    MAX_FOV_RADIUS,
+    Reo_spiral_out,
+    false,
+    nullptr,
+    dflt_blk,
+    reanimation_effector
+};
 
-    case MS_NECRO_STAFF:
-        mhitu(mon, DT_NECRO);
-        break;
+static void cast_animate_dead(Mon_handle mon)
+{
+    Mon const *mptr = mon_snap(mon);
+    reanimate_radiance.clear();
+    reanimate_radiance.centre = mptr->pos;
+    reanimate_radiance.compute();
+    reanimate_radiance.resolve();
+}
 
-    case MS_CHILLING_TOUCH:
-        mhitu(mon, DT_COLD);
-        break;
+Monspell_cast_func sorcery_funcs[NUM_MONSPELLS] =
+{
+    cast_strike_staff,
+    cast_necro_staff,
+    cast_chilling_touch,
+    cast_lightning_bolt,
+    cast_necro_bolt,
+    cast_necro_smite,
+    cast_fire_column,
+    cast_armourmelt,
+    cast_leadfoot,
+    cast_withering,
+    cast_teleport_escape,
+    cast_teleport_summon,
+    cast_teleport_assault,
+    cast_animate_dead
+};
 
-    case MS_LIGHTNING:
-    case MS_NECRO_BOLT:
-        mshootu(mon);
-        break;
+typedef Monspell (*Monspell_select_func)(int mon);
+struct Monspell_selector
+{
+    int pm;
+    Monspell_select_func func;
+};
 
-    case MS_TELEPORT_AND_SUMMON:
-        mptr->next_summon = game_tick + 1000;
-        /* (Try to) summon 2-6 monsters. */
-        i = summoning(mptr->pos, dice(2, 3));
-        notify_summon_help(mon, (i > 0));
-        /* ... and fall through. */
-    case MS_TELEPORT_ESCAPE:
-        teleport_mon(mon);
-        notify_mon_disappear(mon);
-        break;
+Monspell wizard_spell_select(int mon)
+{
+    Mon const *mptr = mon_snap(mon);
+    Offset delta = u.pos.delta(mptr->pos);
+    if (mon_visible(mon))
+    {
+       if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 2))
+       {
+           return MS_TELEPORT_ESCAPE;
+       }
+       else if ((delta.len_cheb() == 1) && (zero_die(10) > 2))
+       {
+           return MS_STRIKE_STAFF;
+       }
+       else if (delta.rcardinal())
+       {
+           return MS_LIGHTNING;
+       }
+    }
+    else if (!zero_die(80))
+    {
+       /* we lack LOS, but passed the 1-in-80 chance to
+        * close with the player by means of sorcery. */
+       return MS_TELEPORT_ASSAULT;
+    }
+    return MS_REJECT;
+}
 
-    case MS_TELEPORT_ASSAULT:
-        /* It is rare that a monster will cast this spell, but not
-         * unheard of. */
-        teleport_mon_to_you(mon);
-        break;
+Monspell archmage_spell_select(int mon)
+{
+    Mon const *mptr = mon_snap(mon);
+    Offset delta = u.pos.delta(mptr->pos);
+    if (mon_visible(mon))
+    {
+       /* We have LOS; choose a spell on that basis. */
+       if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 2))
+       {
+           return zero_die(3) ? MS_TELEPORT_ESCAPE : MS_TELEPORT_AND_SUMMON;
+       }
+       else if ((delta.len_cheb() == 1) && (zero_die(10) > 3))
+       {
+           return MS_STRIKE_STAFF;
+       }
+       else if (delta.rcardinal())
+       {
+           return MS_LIGHTNING;
+       }
+    }
+    else if (!zero_die(40))
+    {
+       /* 
+        * We lack LOS, but pass the 1-in-40 chance; use
+        * sorcery to relocate us to the player's location.
+        */
+       return MS_TELEPORT_ASSAULT;
+    }
+    return MS_REJECT;
+}
 
-    case MS_CURSE_ARMOURMELT:
-        notify_monster_cursing(mon);
-        if (u.protection)
-        {
-            notify_moncurse_fail();
-        }
-        else
-        {
-            u.armourmelt = 10 + one_die(10);
-            recalc_defence();
-            notify_start_armourmelt();
-        }
-        break;
+Monspell lich_spell_select(int mon)
+{
+    Mon const *mptr = mon_snap(mon);
+    Offset delta = u.pos.delta(mptr->pos);
+    int range = delta.len_cheb();
+    if (mon_visible(mon))
+    {
+       if (range == 1)
+       {
+           switch (zero_die(6))
+           {
+           case 3:
+               return MS_ANIMATE_DEAD;
+           case 4:
+               if (!u.leadfoot)
+               {
+                   return MS_CURSE_LEADFOOT;
+               }
+               /* fall through */
+           case 5:
+               if (!u.armourmelt)
+               {
+                   return MS_CURSE_ARMOURMELT;
+               }
+               /* fall through */
+           default:
+               return MS_NECRO_STAFF;
+           }
+       }
+       else if (delta.rcardinal())
+       {
+           if (range < 3)
+           {
+               switch (zero_die(6))
+               {
+               case 3:
+                   return MS_ANIMATE_DEAD;
+               case 4:
+                   if (!u.leadfoot)
+                   {
+                       return MS_CURSE_LEADFOOT;
+                   }
+                   /* fall through */
+               case 5:
+                   if (!u.armourmelt)
+                   {
+                       return MS_CURSE_ARMOURMELT;
+                   }
+                   /* fall through */
+               default:
+                   return MS_NECRO_BOLT;
+               }
+           }
+           else
+           {
+               return MS_NECRO_BOLT;
+           }
+       }
+    }
+    return MS_REJECT;
+}
 
-    case MS_CURSE_LEADFOOT:
-        notify_monster_cursing(mon);
-        if (u.protection)
-        {
-            notify_moncurse_fail();
-        }
-        else
-        {
-            u.leadfoot = 10 + one_die(10);
-            recalc_defence();
-            notify_start_leadfoot();
-        }
-        break;
+Monspell master_lich_spell_select(int mon)
+{
+    Mon const *mptr = mon_snap(mon);
+    Offset delta = u.pos.delta(mptr->pos);
+    int range = delta.len_cheb();
+    if (mon_visible(mon))
+    {
+       if ((mptr->hpcur < (mptr->hpmax * 25 / 100)) && (zero_die(10) < 4))
+       {
+           return ((mptr->next_summon < game_tick) || !zero_die(3)) ? MS_TELEPORT_ESCAPE : MS_TELEPORT_AND_SUMMON;
+       }
+       else if (range == 1)
+       {
+           switch (zero_die(7))
+           {
+           case 3:
+               return MS_ANIMATE_DEAD;
+           case 6:
+               if (!u.withering)
+               {
+                   return MS_CURSE_WITHERING;
+               }
+           case 5:
+               if (!u.leadfoot)
+               {
+                   return MS_CURSE_LEADFOOT;
+               }
+               /* fall through */
+           case 4:
+               if (!u.armourmelt)
+               {
+                   return MS_CURSE_ARMOURMELT;
+               }
+               /* fall through */
+           default:
+               return zero_die(2) ? MS_CHILLING_TOUCH : MS_STRIKE_STAFF;
+           }
+       }
+       else if (range < 3)
+       {
+           switch (zero_die(10))
+           {
+           case 6:
+               return MS_ANIMATE_DEAD;
+           case 9:
+               if (!u.withering)
+               {
+                   return MS_CURSE_WITHERING;
+               }
+           case 8:
+               if (!u.leadfoot)
+               {
+                   return MS_CURSE_LEADFOOT;
+               }
+               /* fall through */
+           case 7:
+               if (!u.armourmelt)
+               {
+                   return MS_CURSE_ARMOURMELT;
+               }
+               /* fall through */
+           default:
+               return MS_NECRO_SMITE;
+           }
+       }
+       else if (range < 8)
+       {
+           switch (zero_die(7))
+           {
+           case 3:
+               return MS_ANIMATE_DEAD;
+           case 6:
+               if (!u.withering)
+               {
+                   return MS_CURSE_WITHERING;
+               }
+               /* fall through */
+           case 4:
+               if (!u.leadfoot)
+               {
+                   return MS_CURSE_LEADFOOT;
+               }
+               /* fall through */
+           case 5:
+               if (!u.armourmelt)
+               {
+                   return MS_CURSE_ARMOURMELT;
+               }
+               /* fall through */
+           default:
+               return MS_NECRO_SMITE;
+           }
+       }
+    }
+    else if (!zero_die(40))
+    {
+       /* we lack LOS, but passed the 1-in-40 chance to
+        * close with the player by means of sorcery. */
+       return MS_TELEPORT_ASSAULT;
+    }
+    return MS_REJECT;
+}
 
-    case MS_CURSE_WITHERING:
-        notify_monster_cursing(mon);
-        if (u.protection)
-        {
-            notify_moncurse_fail();
-        }
-        else
-        {
-            u.withering = 10 + one_die(10);
-            recalc_defence();
-            notify_start_withering();
-        }
-        break;
+Monspell defiler_spell_select(int mon)
+{
+    Mon const *mptr = mon_snap(mon);
+    Offset delta = u.pos.delta(mptr->pos);
+    if (mon_visible(mon))
+    {
+       if (!(delta.len_cheb() != 1) || !zero_die(3))
+       {
+           // 1-in-7 chance of cursing you when in melee range
+           // 3-in-7 chance when out of melee range
+           switch (zero_die(7))
+           {
+           case 6:
+               if (!u.withering)
+               {
+                   return MS_CURSE_WITHERING;
+               }
+           case 4:
+               if (!u.leadfoot)
+               {
+                   return MS_CURSE_LEADFOOT;
+               }
+               /* fall through */
+           case 5:
+               if (!u.armourmelt)
+               {
+                   return MS_CURSE_ARMOURMELT;
+               }
+               /* fall through */
+           default:
+               break;
+           }
+       }
+    }
+    return MS_REJECT;
+}
 
-    case MS_NECRO_SMITE:
-        notify_monster_cursing(mon);
-        if (u.resists(DT_NECRO))
-        {
-            notify_necrosmite_fail();
-        }
-        else
-        {
-            damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].mon_id].name);
-        }
-        break;
+Monspell_selector monspell_selectors[] = 
+{
+    { PM_WIZARD, wizard_spell_select },
+    { PM_ARCHMAGE, archmage_spell_select },
+    { PM_LICH, lich_spell_select },
+    { PM_MASTER_LICH, master_lich_spell_select },
+    { PM_DEFILER, defiler_spell_select },
+    { NO_PMON, nullptr }
+};
 
-    case MS_FIRE_COLUMN:
-        notify_monster_cursing(mon);
-        notify_hellfire_hit(u.resists(DT_FIRE));
-        if (u.resists(DT_FIRE))
-        {
-            damage_u(dice(1, 5), DEATH_KILLED_MON, permons[monsters[mon].mon_id].name);
-        }
-        else
-        {
-            damage_u(dice(1, 20), DEATH_KILLED_MON, permons[monsters[mon].mon_id].name);
-        }
-        break;
+int mon_use_sorcery(int mon)
+{
+    /* Returns zero for no spell selected, -1 for unsupported spell
+     * selected, 1 for supported spell selected. */
+    Mon const *mptr = mon_snap(mon);
+    Monspell to_cast = MS_REJECT;
+    int rval;
+    for (int i = 0; monspell_selectors[i].func; ++i)
+    {
+       if (monspell_selectors[i].pm == mptr->pm_ref)
+       {
+           to_cast = monspell_selectors[i].func(mon);
+       }
+    }
+    if (to_cast == MS_REJECT)
+    {
+       rval = 0;
+    }
+    else if (!sorcery_funcs[to_cast])
+    {
+       debug_bad_monspell(to_cast);
+       rval = -1;
+    }
+    else
+    {
+       sorcery_funcs[to_cast](mon);
+       rval = 1;
     }
     return rval;
 }
index dfd647b..862384f 100644 (file)
@@ -31,7 +31,7 @@
 
 /* XXX DATA TYPES XXX */
 
-enum monspell {
+enum Monspell {
     MS_REJECT = -1,         /* Rejection tag. */
     /* "Melee" attacks */
     MS_STRIKE_STAFF,        /* Wizard */
@@ -50,8 +50,12 @@ enum monspell {
     MS_TELEPORT_ESCAPE,     /* Wizard, Archmage, Master Lich */
     MS_TELEPORT_AND_SUMMON, /* Archmage */
     MS_TELEPORT_ASSAULT,    /* Wizard, Archmage, Master Lich */
+    MS_ANIMATE_DEAD,   /* Lich, Master Lich */
 };
 
+#define LAST_MONSPELL (MS_ANIMATE_DEAD)
+#define NUM_MONSPELLS (LAST_MONSPELL + 1)
+
 enum Monspell_mode
 {
     Msmode_melee,
diff --git a/u.cc b/u.cc
index e847902..d8603c7 100644 (file)
--- a/u.cc
+++ b/u.cc
 #include <stdio.h>
 #include <deque>
 
-bool action_rewrite(Action const *act, Action *revised_act);
 struct Player u;
 
 bool wielding_ranged_weapon(void)
 {
-    return permobjs[objects[u.weapon].obj_id].flags[0] & POF_RANGED_WEAPON;
+    return (u.weapon != NO_OBJ) && (permobjs[objects[u.weapon].po_ref].flags[0] & POF_RANGED_WEAPON);
 }
 
 bool wielding_melee_weapon(void)
 {
-    return permobjs[objects[u.weapon].obj_id].flags[0] & POF_MELEE_WEAPON;
+    return (u.weapon != NO_OBJ) && (permobjs[objects[u.weapon].po_ref].flags[0] & POF_MELEE_WEAPON);
+}
+
+static void apply_bonuses(Obj_handle obj, uint32_t bonusmask)
+{
+    if (obj != NO_OBJ)
+    {
+        Obj const *optr = obj_snap(obj);
+        int resistance = po_resistance(optr->po_ref);
+        int dmg_amp = po_damage_amp(optr->po_ref);
+        if (resistance != DT_NONE)
+        {
+            u.resistances[resistance] |= BONUS_ARMOUR;
+        }
+        if (dmg_amp != DT_NONE)
+        {
+            u.damage_amp[dmg_amp] |= BONUS_ARMOUR;
+        }
+        if (po_grants_speed(optr->po_ref))
+        {
+            ++u.speed;
+        }
+        if (po_grants_passwater(optr->po_ref))
+        {
+            u.passwater |= bonusmask;
+        }
+        if (po_grants_flight(optr->po_ref))
+        {
+            u.flight |= bonusmask;
+        }
+        if (po_grants_protective(optr->po_ref))
+        {
+            u.protective_gear |= bonusmask;
+        }
+    }
 }
 
 void recalc_defence(void)
 {
     int i;
-    for (i = 0; i < DT_COUNT; i++)
+    for (i = 0; i < NUM_DAMTYPES; i++)
     {
-        u.resistances[i] &= RESIST_MASK_TEMPORARY;
+        u.resistances[i] &= BONUS_MASK_TEMPORARY;
+        u.damage_amp[i] &= BONUS_MASK_TEMPORARY;
     }
+    u.passwater &= BONUS_MASK_TEMPORARY;
+    u.flight &= BONUS_MASK_TEMPORARY;
+    u.protective_gear &= BONUS_MASK_TEMPORARY;
     u.speed = (u.leadfoot ? 0 : 1);
+    u.defence = u.withering ? (u.agility / 10) : (u.agility / 5);
     if (u.armour != NO_OBJ)
     {
-        u.defence = u.armourmelt ? 0 : permobjs[objects[u.armour].obj_id].power;
-        u.defence += u.withering ? (u.agility / 10) : (u.agility / 5);
-        switch (objects[u.armour].obj_id)
-        {
-        case PO_DRAGONHIDE_ARMOUR:
-        case PO_METEORIC_PLATE_ARMOUR:
-            u.resistances[DT_FIRE] |= RESIST_ARMOUR;
-            break;
-        case PO_ROBE_OF_SWIFTNESS:
-            u.speed++;
-            break;
-        default:
-            break;
-        }
+        Obj const *aptr = obj_snap(u.armour);
+        u.defence += u.armourmelt ? 0 : permobjs[aptr->po_ref].power;
     }
-    else
+    apply_bonuses(u.armour, BONUS_ARMOUR);
+    apply_bonuses(u.ring, BONUS_RING);
+    apply_bonuses(u.weapon, BONUS_WEAPON);
+    if (u.known_skill[Skill_deaths_grace])
     {
-        u.defence = u.withering ? (u.agility / 10) : (u.agility / 5);
+        u.resistances[DT_NECRO] |= BONUS_MASK_PERM_SKILL;
     }
-    if (u.ring != NO_OBJ)
+    if (u.known_skill[Skill_deaths_vengeance])
     {
-        switch (objects[u.ring].obj_id)
-        {
-        case PO_FIRE_RING:
-            u.resistances[DT_FIRE] |= RESIST_RING;
-            break;
-        case PO_FROST_RING:
-            u.resistances[DT_COLD] |= RESIST_RING;
-            break;
-        case PO_VAMPIRE_RING:
-            u.resistances[DT_NECRO] |= RESIST_RING;
-            break;
-        }
+        u.damage_amp[DT_SLAY_UNDEAD] |= BONUS_MASK_PERM_SKILL;
+    }
+    if (u.known_skill[Skill_demon_slayer])
+    {
+        u.damage_amp[DT_SLAY_DEMON] |= BONUS_MASK_PERM_SKILL;
     }
     notify_defence_recalc();
 }
@@ -107,9 +129,10 @@ Action_cost move_player(Offset delta)
     {
         if (u.weapon != NO_OBJ)
         {
-            if ((objects[u.weapon].obj_id == PO_BOW) ||
-                (objects[u.weapon].obj_id == PO_CROSSBOW) ||
-                (objects[u.weapon].obj_id == PO_THUNDERBOW))
+            Obj const *wep = obj_snap(u.weapon);
+            if ((wep->po_ref == PO_BOW) ||
+                (wep->po_ref == PO_CROSSBOW) ||
+                (wep->po_ref == PO_THUNDERBOW))
             {
                 notify_swing_bow();
                 return Cost_none;
@@ -117,66 +140,61 @@ 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 ALTAR:
-        reloc_player(c);
-        return Cost_std;
-    case LAVA:
+    }
+    else if ((!u.flight) && 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:
-        if ((u.ring != NO_OBJ) && (objects[u.ring].obj_id == PO_FROST_RING))
+    }
+    else if ((!u.flight) && terrain_drowns(t))
+    {
+        if (u.passwater)
         {
-            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 ((!u.flight) && 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);
@@ -295,8 +313,8 @@ void heal_u(int amount, int boost, int loud)
     else
     {
         boost = 0;
-        u.hpcur += amount;
     }
+    u.hpcur += amount;
     notify_player_heal(amount, boost, loud);
     return;
 }
@@ -328,7 +346,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)
     {
@@ -338,6 +356,7 @@ void u_init(char const *name)
     {
         strncpy(u.name, name, 16);
     }
+    u.role = role;
     u.body = 10;
     u.bdam = 0;
     u.agility = 10;
@@ -347,13 +366,15 @@ void u_init(char const *name)
     u.experience = 0;
     u.level = 1;
     u.food = 2000;
-    memset(u.inventory, -1, sizeof u.inventory);
-    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];
+    for (int i = 0; i < INVENTORY_SIZE; ++i)
+    {
+        u.inventory[i] = NO_OBJ;
+    }
+    for (int i = 0; i < NUM_SKILLS; ++i)
+    {
+        u.known_skill[i] = false;
+    }
+    role_inventories[role](&u);
     recalc_defence();
 }
 
@@ -385,6 +406,24 @@ void gain_experience(int amount)
     {
         u.level++;
         notify_level_gain();
+        for (int i = 0; universal_skills[i].skill != NO_SKILL; ++i)
+        {
+            if (u.level == universal_skills[i].level)
+            {
+                u.known_skill[universal_skills[i].skill] = true;
+                recalc_defence();
+                notify_learned_skill(universal_skills[i].skill);
+            }
+        }
+        for (int i = 0; skill_tables[u.role][i].skill != NO_SKILL; ++i)
+        {
+            if (u.level == skill_tables[u.role][i].level)
+            {
+                u.known_skill[skill_tables[u.role][i].skill] = true;
+                recalc_defence();
+                notify_learned_skill(skill_tables[u.role][i].skill);
+            }
+        }
         if (!zero_die(2))
         {
             bodygain = gain_body(2);
@@ -402,8 +441,6 @@ void gain_experience(int amount)
         }
         if (hpgain > 0)
         {
-            /* v1.3: Policy change - gaining a level effectively
-             * heals you. */
             u.hpcur += hpgain;
             u.hpmax += hpgain;
             notify_hp_gain(hpgain);
@@ -431,6 +468,9 @@ Pass_fail teleport_u(void)
 
 void update_player(void)
 {
+    Obj const *ring = obj_snap(u.ring);
+    //Obj const *weapon = obj_snap(u.weapon);
+    //Obj const *armour = obj_snap(u.armour);
     if (!(game_tick % 5) && (u.food >= 0) && (u.hpcur < u.hpmax))
     {
         /* Heal player for one hit point; do not allow HP gain,
@@ -446,8 +486,8 @@ void update_player(void)
     /* Once you hit the nutrition endstop, your ring of regeneration stops
      * working, and like normal regen, it won't raise you above 75% HP if
      * your food counter is negative. */
-    if (((game_tick % 10) == 5) &&
-        (objects[u.ring].obj_id == PO_REGENERATION_RING) &&
+    if (((game_tick % 10) == 5) && (ring) &&
+        (ring->po_ref == PO_REGENERATION_RING) &&
         (u.hpcur < ((u.food >= 0) ? u.hpmax : ((u.hpmax * 3) / 4))) &&
         (u.food > MIN_FOOD))
     {
@@ -460,7 +500,8 @@ void update_player(void)
     {
         int food_use = 1;
         int squeal = 0;
-        if ((objects[u.ring].obj_id == PO_REGENERATION_RING) && !(game_tick % 2) && (u.food > MIN_FOOD))
+        if (ring && (ring->po_ref == PO_REGENERATION_RING) &&
+            !(game_tick % 2) && (u.food > MIN_FOOD))
         {
             /* If you are still less hungry than MIN_FOOD nutrition,
              * use one more food every second tick if you are
@@ -478,6 +519,30 @@ void update_player(void)
         u.food -= food_use;
         notify_food_use(food_use, squeal);
     }
+    if (u.known_skill[Skill_sanctified_by_death])
+    {
+        for (int i = 0; i < INVENTORY_SIZE; ++i)
+        {
+            Obj *optr = obj_snapv(u.inventory[i]);
+            if (optr && (optr->po_ref == PO_CORPSE))
+            {
+                optr->meta[1] = 1; // bless this corpse
+            }
+        }
+        Coord c;
+        for (c.y = u.pos.y - 1; c.y <= u.pos.x + 1; ++c.y)
+        {
+            for (c.x = u.pos.x - 1; c.x <= u.pos.x + 1; ++c.x)
+            {
+                Obj_handle o = lvl.obj_at(c);
+                Obj *optr = obj_snapv(o);
+                if (optr && (optr->po_ref == PO_CORPSE))
+                {
+                    optr->meta[1] = 1;
+                }
+            }
+        }
+    }
     if (u.leadfoot > 0)
     {
         u.leadfoot--;
@@ -523,14 +588,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
@@ -544,117 +601,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;
-    int 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)
@@ -664,209 +671,29 @@ bool action_rewrite(Action const *act, Action *revised_act)
     return rewrite_flag;
 }
 
-/*! \brief Process a player action.
- *
- * \todo Make CMD_POWER_ATTACK do something special
- */
+/*! \brief Process a player action. */
 Action_cost do_player_action(Action *act)
 {
-    int slot;
-    Offset step;
     Action redact;
     bool rewritten = action_rewrite(act, &redact);
     if (rewritten)
     {
         act = &redact;
     }
-    switch (act->cmd)
+    if (act->cmd == REJECTED_ACTION)
     {
-    case REJECTED_ACTION:
         return Cost_none;
-
-    case CMD_POWER_ATTACK:
-        step.y = act->details[0];
-        step.x = act->details[1];
-        return player_power_attack(step);
-
-    case WALK:
-        step.y = act->details[0];
-        step.x = act->details[1];
-        return move_player(step);
-
-    case ATTACK:
-        step.y = act->details[0];
-        step.x = act->details[1];
-        return player_attack(step);
-
-    case USE_ACTIVE_SKILL:
-        debug_unimplemented();
+    }
+    if (act->cmd > LAST_COMMAND)
+    {
         return Cost_none;
-
-    case ALLOCATE_SKILL_POINT:
+    }
+    if (deed_funcs[act->cmd] == nullptr)
+    {
         debug_unimplemented();
         return Cost_none;
-
-    case GET_ITEM:
-        if (lvl.obj_at(u.pos) != NO_OBJ)
-        {
-            attempt_pickup();
-            return Cost_std;
-        }
-        else
-        {
-            notify_nothing_to_get();
-            return Cost_none;
-        }
-
-    case WIELD_WEAPON:
-        slot = act->details[0];
-        if (slot == SLOT_NOTHING)
-        {
-            return player_unwield(Noise_std);
-        }
-        else
-        {
-            return player_wield(slot, Noise_std);
-        }
-        break;
-
-    case WEAR_ARMOUR:
-        slot = act->details[0];
-        return wear_armour(slot);
-
-    case EMANATE_ARMOUR:
-        if (u.armour == NO_OBJ)
-        {
-            notify_emanate_no_armour();
-            return Cost_none;
-        }
-        return emanate_armour();
-
-    case ZAP_WEAPON:
-        if (u.weapon == NO_OBJ)
-        {
-            notify_zap_no_weapon();
-            return Cost_none;
-        }
-        return zap_weapon();
-
-    case MAGIC_RING:
-        if (u.weapon == NO_OBJ)
-        {
-            notify_magic_no_ring();
-            return Cost_none;
-        }
-        return magic_ring();
-
-    case TAKE_OFF_ARMOUR:
-        if (u.armour != NO_OBJ)
-        {
-            int saved_armour = u.armour;
-            if ((u.resistances[DT_FIRE] == RESIST_ARMOUR) &&
-                (lvl.terrain_at(u.pos) == LAVA))
-            {
-                notify_lava_blocks_unequip();
-                return Cost_none;
-            }
-            u.armour = NO_OBJ;
-            recalc_defence();
-            notify_armour_unequip(saved_armour);
-            return Cost_std;
-        }
-        else
-        {
-            debug_take_off_no_armour();
-            return Cost_none;
-        }
-
-    case GO_UP_STAIRS:
-        if (lvl.terrain_at(u.pos) == STAIRS_UP)
-        {
-            notify_ascent_blocked();
-        }
-        else
-        {
-            debug_ascend_non_stairs();
-        }
-        return Cost_none;
-
-    case GO_DOWN_STAIRS:
-        if (lvl.terrain_at(u.pos) == STAIRS_DOWN)
-        {
-            leave_level();
-            make_new_level();
-        }
-        else
-        {
-            debug_descend_non_stairs();
-        }
-        return Cost_none;
-
-    case STAND_STILL:
-        return Cost_std;
-
-    case READ_SCROLL:
-        slot = act->details[0];
-        return read_scroll(u.inventory[slot]);
-
-    case EAT_FOOD:
-        slot = act->details[0];
-        return eat_food(u.inventory[slot]);
-
-    case QUAFF_POTION:
-        slot = act->details[0];
-        return quaff_potion(u.inventory[slot]);
-
-    case THROW_FLASK:
-        return Cost_none;
-
-    case REMOVE_RING:
-        return remove_ring();
-
-    case PUT_ON_RING:
-        slot = act->details[0];
-        return put_on_ring(u.inventory[slot]);
-
-    case DROP_ITEM:
-        slot = act->details[0];
-        return drop_obj(slot);
-
-    case SAVE_GAME:
-        game_finished = 1;
-        save_game();
-        return Cost_none;
-
-    case QUIT:
-        game_finished = 1;
-        return Cost_none;
-
-    case WIZARD_DESCEND:
-        if (wizard_mode)
-        {
-            leave_level();
-            make_new_level();
-        }
-        else
-        {
-            debug_wizmode_violation();
-        }
-        return Cost_none;
-
-    case WIZARD_LEVELUP:
-        if (wizard_mode)
-        {
-            if (lev_threshold(u.level) != INT_MAX)
-            {
-                gain_experience((lev_threshold(u.level) - u.experience) + 1);
-            }
-        }
-        else
-        {
-            debug_wizmode_violation();
-        }
-        return Cost_none;
     }
-    return Cost_none;
+    return deed_funcs[act->cmd](act);
 }
 
 void player_cleanup(void)
@@ -874,6 +701,9 @@ void player_cleanup(void)
     int i;
     memset(u.sympathy, '\0', sizeof u.sympathy);
     memset(u.resistances, '\0', sizeof u.resistances);
+    memset(u.damage_amp, '\0', sizeof u.damage_amp);
+    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);
@@ -883,11 +713,16 @@ void player_cleanup(void)
     u.food = 0;
     u.leadfoot = u.protection = u.withering = u.armourmelt = 0;
     u.level = 0;
+    u.flight = u.passwater = u.protective_gear = 0;
     u.weapon = u.armour = u.ring = NO_OBJ;
-    for (i = 0; i < 19; ++i)
+    for (i = 0; i < INVENTORY_SIZE; ++i)
     {
         u.inventory[i] = NO_OBJ;
     }
+    for (i = 0; i < NUM_SKILLS; ++i)
+    {
+        u.known_skill[i] = false;
+    }
 }
 
 /* u.cc */
index 227b7a5..5affa28 100644 (file)
 #include "display.hh"
 #endif
 
-#ifndef NOTIFY_HH
-#include "notify.hh"
-#endif
-
 #ifndef MAP_HH
 #include "map.hh"
 #endif
 #include "fov.hh"
 #endif
 
-/* XXX main.c data and funcs */
-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 */
-extern char const *damtype_names[DT_COUNT];
 
 /* XXX  log.cc */
 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
 
 /* victrix-abyssi.hh */