Final commit for 7drl deadline. 1.0.0 7drl-2014
authorMartin Read <mpread@chiark.greenend.org.uk>
Fri, 14 Mar 2014 20:45:02 +0000 (20:45 +0000)
committerMartin Read <mpread@chiark.greenend.org.uk>
Fri, 14 Mar 2014 20:45:02 +0000 (20:45 +0000)
All further fixes will have to come after the 7drlc is over.

24 files changed:
Makefile
combat.cc
combat.hh
combo.cc
core.hh
deeds.cc
default.permobjs
display-nc.cc
log.cc
map.cc
map.hh
mon1.cc
notify-local-tty.cc
notify.hh
obj1.cc
objects.hh
permobj.hh
player.hh
pmon2.cc
pobj_comp
role.cc
skills.cc
sorcery.cc
u.cc

index 6790a9f..77f41ea 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 combo.o coord.o deeds.o display-nc.o dungeon.o fov.o log.o main.o map.o mon1.o mon2.o mon3.o notify-local-tty.o obj1.o obj2.o pmon2.o rng.o role.o shrine.o sorcery.o u.o util.o
+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 mon1.o mon2.o mon3.o notify-local-tty.o obj1.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:=obumbrata
 MAJVERS:=1
@@ -134,6 +134,9 @@ 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
+
+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
+
 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)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
index fee2098..11d2009 100644 (file)
--- a/combat.cc
+++ b/combat.cc
@@ -54,32 +54,37 @@ int player_melee_base(void)
 
 /*! \brief Calculate the effect of the player's  damage amplifier ring
  */
-bool ring_effectiveness(Mon_handle 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].pm_ref;
     *vamp_healing = 0;
-    // Testing order is fire, vampire, frost.
+    *bonus_damage = 0;
+    // Fire and frost mutually exclude.
     if (u.damage_amp[DT_FIRE] && !pmon_resists_fire(pm))
     {
-       *bonus_damage = dice(2, 4) + ((damage + 1) / 2);
+       *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);
+       *bonus_damage += dice(2, 4) + ((damage + 3) / 4);
        rv = true;
     }
-    else if (u.damage_amp[DT_NECRO] && !pmon_resists_necro(pm))
+    if (u.damage_amp[DT_NECRO] && !pmon_resists_necro(pm))
     {
-       *bonus_damage = dice(2, 4) + ((damage + 3) / 4);
+       *bonus_damage += dice(2, 4) + ((damage + 3) / 4);
        *vamp_healing = std::min(monsters[mon].hpcur, (damage + 5) / 6);
        rv = true;
     }
-    else
+    // 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 = 0;
-       *vamp_healing = 0;
+       *bonus_damage += damage;
     }
     return rv;
 }
@@ -96,7 +101,7 @@ void resolve_player_melee(Mon_handle mon, int damage)
     int healing = 0;
     if (u.ring != NO_OBJ)
     {
-        ring_eff = ring_effectiveness(mon, objects[u.ring].po_ref, 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].po_ref);
@@ -153,13 +158,27 @@ Action_cost player_attack(Offset delta)
     return Cost_std;
 }
 
+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 = mon_snapv(mon);
+    mp = mon_snap(mon);
     tohit = hitbase / 3 + zero_die(hitbase - hitbase / 3);
     if (tohit < mp->defence)
     {
index c2be288..2a2d6f1 100644 (file)
--- a/combat.hh
+++ b/combat.hh
 
 #define agility_modifier() (u.withering ? (u.agility / 10) : (u.agility / 5))
 /* XXX combat.c data and funcs */
-extern Action_cost player_attack(Offset step);
-extern Action_cost player_power_attack(Offset step);
-extern void resolve_player_melee(Mon_handle mon, int damage);
-extern int mhitu(Mon_handle mon, Damtyp dtyp);
-extern int uhitm(Mon_handle mon);
-extern int mshootu(Mon_handle 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
 {
index 6e3467c..bea7c01 100644 (file)
--- a/combo.cc
+++ b/combo.cc
@@ -139,15 +139,12 @@ Combo_phase action_default_combophase[NUM_COMMANDS] =
 
 static bool powatk_avail(Player const *player)
 {
-    if (player->level >= 2)
+    if (player->known_skill[Skill_power_attack])
     {
-        if (player->weapon != NO_OBJ)
+        Obj const *optr = obj_snap(player->weapon);
+        if (optr && (permobjs[optr->po_ref].flags[0] & POF_RANGED_WEAPON))
         {
-            Obj const *optr = obj_snap(player->weapon);
-            if (permobjs[optr->po_ref].flags[0] & POF_RANGED_WEAPON)
-            {
-                return false;
-            }
+            return false;
         }
         return true;
     }
@@ -176,13 +173,194 @@ static Combo_state powatk_detector(std::deque<Action>* action_list)
     }
     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[] =
 {
-    /* Everyone gets power attack at level 2. */
+    { "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 }
 };
diff --git a/core.hh b/core.hh
index 90aa341..c4c27a7 100644 (file)
--- a/core.hh
+++ b/core.hh
@@ -106,9 +106,11 @@ 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 LAST_DAMTYPE (DT_DROWNING)
+#define LAST_DAMTYPE (DT_SLAY_UNDEAD)
 #define NUM_DAMTYPES (1 + LAST_DAMTYPE)
 
 /*! \brief Identification code for player actions */
@@ -123,9 +125,9 @@ enum Game_cmd {
     SAVE_GAME, QUIT,
     WIZARD_LEVELUP, WIZARD_DESCEND,
     /* combos begin here */
-    COMBO_POWATK
+    COMBO_POWATK, COMBO_BLADE_DANCE, COMBO_CHARGE, COMBO_FLYING_LEAP
 };
-#define LAST_COMMAND (COMBO_POWATK)
+#define LAST_COMMAND (COMBO_FLYING_LEAP)
 #define NUM_COMMANDS (1 + LAST_COMMAND)
 
 /*! \brief Is this action a valid combo step? */
@@ -165,7 +167,8 @@ enum Fell_powers {
 #define TOTAL_FELL_POWERS (1 + FePo_flesh)
 
 #define BONUS_MASK_TEMPORARY   0x0000FFFFu
-#define BONUS_MASK_PERM_EQUIP  0xFFFF0000u
+#define BONUS_MASK_PERM_EQUIP  0x7FFF0000u
+#define BONUS_MASK_PERM_SKILL  0x80000000u
 #define BONUS_RING     0x00010000u
 #define BONUS_ARMOUR   0x00020000u
 #define BONUS_WEAPON   0x00040000u
@@ -178,6 +181,30 @@ 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;
@@ -194,6 +221,7 @@ enum Decal_tag
     NO_DECAL = -1,
     Decal_blood,
     Decal_ichor,
+    Decal_slime,
     Decal_pus
 };
 #define MAX_DECAL (Decal_pus)
index 1634648..b8a4f76 100644 (file)
--- a/deeds.cc
+++ b/deeds.cc
@@ -238,6 +238,35 @@ static Action_cost deed_combo_powatk(Action const *act)
     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)
@@ -308,7 +337,10 @@ Deed_func deed_funcs[NUM_COMMANDS] =
     deed_quit,
     deed_wiz_levup,
     deed_wiz_descend,
-    deed_combo_powatk
+    deed_combo_powatk,
+    deed_combo_blade_dance,
+    deed_combo_charge,
+    deed_combo_flying_leap,
 };
 
 /* deeds.cc */
index 0db5f1f..82206ff 100644 (file)
@@ -34,6 +34,7 @@ POWER2 0
 DEPTH 1
 DAMAGEABLE
 MELEE_WEAPON
+DAGGER
 
 WEAPON long sword
 PLURAL long swords
@@ -47,6 +48,7 @@ POWER2 0
 DEPTH 4
 DAMAGEABLE
 MELEE_WEAPON
+SWORD
 
 WEAPON windsword
 PLURAL windswords
@@ -62,10 +64,11 @@ DAMAGEABLE
 MELEE_WEAPON
 NOTIFY_EQUIP
 FLIGHT
+SWORD
 
 WEAPON runesword
 PLURAL runeswords
-DESC An eerily glowing sword engraved with many strange runes.
+DESC An eerily glowing long sword engraved with many strange runes.
 RARITY 80
 ASCII ')'
 UTF8 ")"
@@ -75,6 +78,7 @@ POWER2 0
 DEPTH 12
 DAMAGEABLE
 MELEE_WEAPON
+SWORD
 
 WEAPON mace
 PLURAL maces
@@ -88,6 +92,7 @@ POWER2 0
 DEPTH 2
 DAMAGEABLE
 MELEE_WEAPON
+BLUDGEON
 
 WEAPON spear
 PLURAL spears
@@ -101,6 +106,7 @@ POWER2 0
 DEPTH 3
 DAMAGEABLE
 MELEE_WEAPON
+POLEARM
 
 WEAPON hellglaive
 PLURAL hellglaives
@@ -117,6 +123,7 @@ DAMAGEABLE
 BREAK_REACT
 MELEE_WEAPON
 DEMONIC
+POLEARM
 
 WEAPON plague scythe
 PLURAL plague sycthes
@@ -131,6 +138,7 @@ DEPTH 30
 NOTIFY_EQUIP
 MELEE_WEAPON
 DEMONIC
+POLEARM
 
 WEAPON tormentor's lash
 PLURAL tormentor's lashes
@@ -147,6 +155,7 @@ DAMAGEABLE
 BREAK_REACT
 MELEE_WEAPON
 DEMONIC
+WHIP
 
 WEAPON death staff
 PLURAL death staves
@@ -160,6 +169,7 @@ POWER2 0
 DEPTH 30
 NOTIFY_EQUIP
 MELEE_WEAPON
+BLUDGEON
 
 WEAPON staff of fire
 PLURAL staves of fire
@@ -174,6 +184,7 @@ DEPTH 20
 DAMAGEABLE
 ACTIVATABLE
 MELEE_WEAPON
+BLUDGEON
 
 WEAPON bow
 PLURAL bows
@@ -187,6 +198,7 @@ POWER2 0
 DEPTH 1
 DAMAGEABLE
 RANGED_WEAPON
+
 WEAPON crossbow
 PLURAL crossbows
 DESC A crossbow.
@@ -308,6 +320,7 @@ POWER 3
 POWER2 10
 DEPTH 1
 DAMAGEABLE
+LEATHERY
 
 ARMOUR chainmail
 PLURAL suits of chainmail
@@ -356,6 +369,7 @@ POWER 2
 POWER2 5
 DEPTH 1
 DAMAGEABLE
+ROBE
 
 ARMOUR robe of swiftness
 PLURAL robes of swiftness
@@ -369,6 +383,7 @@ POWER2 0
 DEPTH 8
 DAMAGEABLE
 SPEED
+ROBE
 
 ARMOUR robe of shadows
 PLURAL robes of shadows
@@ -381,6 +396,7 @@ POWER 14
 POWER2 -15
 DEPTH 18
 DAMAGEABLE
+ROBE
 
 ARMOUR dragonhide armour
 PLURAL suits of dragonhide armour
@@ -394,6 +410,7 @@ POWER2 10
 DEPTH 21
 DAMAGEABLE
 RES_FIRE
+LEATHERY
 
 ARMOUR meteoric plate armour
 PLURAL suits of meteoric plate armour
@@ -476,6 +493,7 @@ DAMAGEABLE
 BREAK_REACT
 RES_POISON
 DEMONIC
+ROBE
 
 ARMOUR lich's robe
 PLURAL lich's robes
@@ -489,6 +507,7 @@ POWER2 0
 DEPTH 30
 NOTIFY_EQUIP
 RES_NECRO
+ROBE
 
 ARMOUR infernite armour
 PLURAL suits of infernite armour
index 2ca0636..e22a58e 100644 (file)
@@ -223,16 +223,43 @@ static void announce_role(Role_id role)
     }
 }
 
+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, 20, "----====< Obumbrata et Velata >====----\n");
-    mvwprintw(fullscreen_window, 3, 25,  "A roguelike game by Martin Read\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, 23, "Version %d.%d.%d (7DRLC 2014 Edition)\n", MAJVERS, MINVERS, PATCHVERS);
     show_panel(fullscreen_panel);
     update_panels();
     doupdate();
@@ -1374,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':
             {
@@ -1549,7 +1586,7 @@ void print_help(void)
     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");
@@ -1624,7 +1661,7 @@ static void run_main_menu(void)
             mvwprintw(fullscreen_window, 10, 1, "\n");
             mvwprintw(fullscreen_window, 11, 1, "\n");
             mvwprintw(fullscreen_window, 12, 1, "\n");
-            mvwprintw(fullscreen_window, 13, 19, "Welcome. Remind me of thy 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");
@@ -1642,6 +1679,8 @@ static void run_main_menu(void)
            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");
            role_selected = false;
            do
            {
@@ -1707,7 +1746,10 @@ static void run_main_menu(void)
         case 'Q':
             endwin();
             exit(0);
+       default:
+           break;
         }
+       draw_mainmenu_tip();
     } while (!done);
     if (!game_finished)
     {
@@ -1849,6 +1891,7 @@ void print_obj_name(Obj_handle obj)
     char *s;
     asprint_obj_name(&s, obj);
     print_msg("%s", s);
+    free(s);
 }
 
 void print_mon_name(Mon_handle mon, int article)
diff --git a/log.cc b/log.cc
index 41ae374..5071eea 100644 (file)
--- a/log.cc
+++ b/log.cc
@@ -124,6 +124,19 @@ static inline void serialize_int(FILE *fp, int 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)
 {
@@ -352,6 +365,10 @@ static void deserialize_player(FILE *fp, Player *player)
     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]));
@@ -399,6 +416,10 @@ static void serialize_player(FILE *fp, Player const& player)
     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]);
diff --git a/map.cc b/map.cc
index b91eaca..b6f2580 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)
 {
     uint32_t i;
diff --git a/map.hh b/map.hh
index 8bf450d..159e646 100644 (file)
--- a/map.hh
+++ b/map.hh
@@ -83,6 +83,15 @@ 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
diff --git a/mon1.cc b/mon1.cc
index f4dbf84..87f7b2c 100644 (file)
--- a/mon1.cc
+++ b/mon1.cc
@@ -256,11 +256,13 @@ 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 (pmon_explodes(pm) || (pmon_leaves_corpse(pm) && explode_corpse))
+    if (detonate)
     {
        detonate_mon(mon);
     }
@@ -277,7 +279,7 @@ void kill_mon(Mon_handle mon, bool by_you, bool explode_corpse)
         notify_player_killed_mon(mon);
         gain_experience(permons[monsters[mon].pm_ref].exp);
     }
-    else if (mon_visible(mon))
+    else if (mon_visible(mon) && !detonate)
     {
         notify_mon_dies(mon);
     }
@@ -479,6 +481,7 @@ void detonate_mon(Mon_handle 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)
index 20cf9f7..d25fa70 100644 (file)
@@ -199,6 +199,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");
@@ -252,9 +258,10 @@ void notify_new_obj_at(Coord c, Obj_handle obj)
 
 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)
@@ -290,8 +297,9 @@ void notify_wasted_gain(void)
 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");
@@ -300,6 +308,7 @@ void notify_summon_help(Mon_handle mon, bool success)
     {
         print_msg("... luckily for you, help wasn't listening.\n");
     }
+    free(s);
 }
 
 void notify_summon_demon(Mon_handle mon)
@@ -316,7 +325,8 @@ void notify_summon_demon(Mon_handle 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");
 }
 
@@ -360,8 +370,10 @@ void notify_hellfire_hit(bool resisted)
 
 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)
@@ -376,23 +388,33 @@ void notify_knockback_mon_pass(void)
 
 void notify_player_miss(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 miss %s.\n", s);
+    free(s);
+}
+
+void notify_player_charge_mon(Mon_handle mon)
+{
+    char *s;
+    asprint_mon_name(&s, mon, 1);
+    print_msg("You charge %s.\n", s);
+    free(s);
 }
 
 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(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)
@@ -407,26 +429,29 @@ void notify_player_shot_terrain(Obj_handle obj, Coord c)
 
 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(Mon_handle mon, int damage)
 {
     print_msg("You do %d damage.\n", damage);
@@ -435,16 +460,17 @@ void notify_player_hurt_mon(Mon_handle mon, int damage)
 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");
     }
-    print_msg("!\n");
 }
 
 void notify_mon_dies(Mon_handle mon)
@@ -452,45 +478,79 @@ void notify_mon_dies(Mon_handle mon)
     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);
+       print_msg("%s dies.\n", &s);
+       free(s);
+    }
+}
+
+void notify_mon_detonates(Mon_handle mon, Decal_tag tag)
+{
+    newsym(monsters[mon].pos);
+    if (occupant_visible(monsters[mon].pos))
+    {
+       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(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(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(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(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(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(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)
@@ -509,42 +569,51 @@ void notify_mon_disappears(Mon_handle mon, Coord oldpos)
 
 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(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(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(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(Mon_handle mon)
 {
     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)
@@ -629,16 +698,16 @@ void notify_armour_equip(Obj_handle 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(Obj_handle obj)
 {
     Permobj *pobj = permobjs + objects[obj].po_ref;
-    // TODO add unequip messages for NOTIFY_EQUIP armours.
     if (pobj->flags[0] & POF_NOTIFY_EQUIP)
     {
         switch (objects[obj].po_ref)
@@ -672,9 +741,10 @@ void notify_armour_unequip(Obj_handle 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(Obj_handle obj)
@@ -697,6 +767,19 @@ 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)
@@ -809,8 +892,10 @@ void notify_firestaff_activation(int specific)
 
 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,8 +971,10 @@ void notify_nothing_to_get(void)
 
 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(Mon_handle mon)
@@ -932,9 +1019,10 @@ void notify_wield_weapon(Obj_handle obj)
     }
     else
     {
-       print_msg("Wielding ");
-       print_obj_name(obj);
-       print_msg(".\n");
+       char *s;
+       asprint_obj_name(&s, obj);
+       print_msg("Wielding %s.\n", s);
+       free(s);
     }
 }
 
@@ -950,9 +1038,10 @@ void notify_armour_broke(Obj_handle 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)
index 833000c..4eef7ff 100644 (file)
--- a/notify.hh
+++ b/notify.hh
@@ -51,6 +51,7 @@ 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
 
@@ -61,6 +62,7 @@ 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);
@@ -88,6 +90,7 @@ void notify_pit_blocks_unequip(void);
 void notify_nothing_to_get(void);
 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);
@@ -118,6 +121,7 @@ void notify_no_flask_target(void);
 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);
@@ -141,6 +145,7 @@ 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(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);
diff --git a/obj1.cc b/obj1.cc
index 802e1b1..bbe6be4 100644 (file)
--- a/obj1.cc
+++ b/obj1.cc
@@ -423,6 +423,46 @@ Damtyp po_damage_amp(int po)
     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);
@@ -639,8 +679,8 @@ Action_cost player_unwield(Noisiness noisy)
     int saved_weapon = u.weapon;
     if (u.weapon == NO_OBJ)
     {
-        debug_unwield_nothing();
-        return Cost_none;
+       debug_unwield_nothing();
+       return Cost_none;
     }
     u.weapon = NO_OBJ;
     recalc_defence();
@@ -648,16 +688,35 @@ Action_cost player_unwield(Noisiness noisy)
     return Cost_std;
 }
 
+Pass_fail role_equip_check(Obj_handle obj, Noisiness noisy)
+{
+    Obj const *optr = obj_snap(obj);
+    if ((u.role == Role_demon_hunter) && po_is_demonic(optr->po_ref))
+    {
+       notify_role_blocks_equip();
+       return You_fail;
+    }
+    return You_pass;
+}
+
 Action_cost player_wield(Obj_handle obj, Noisiness noisy)
 {
-    if (u.weapon != NO_OBJ)
+    Pass_fail rej = role_equip_check(obj, noisy);
+    if (rej == You_pass)
     {
-        player_unwield(Noise_low);
+       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;
     }
-    u.weapon = obj;
-    notify_wield_weapon(u.weapon);
-    recalc_defence();
-    return Cost_std;
 }
 
 Action_cost wear_armour(Obj_handle obj)
@@ -745,5 +804,10 @@ Pass_fail unequip_safety_check(uint32_t mask, Noisiness noisy)
     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
index 1a38c82..5a53e6d 100644 (file)
@@ -93,6 +93,7 @@ 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)
 {
index 386d1c2..03aa5c8 100644 (file)
@@ -64,12 +64,16 @@ 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_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
@@ -113,6 +117,14 @@ 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);
index a8301c7..857dee2 100644 (file)
--- a/player.hh
+++ b/player.hh
 
 enum Skill_id
 {
-    Skill_power_attack,
+    NO_SKILL = -1,
+    Skill_power_attack = 0,
     /* Princess skills */
-    Skill_artificer,
+    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_deaths_vengeance,
+    Skill_sanctified_by_death
 };
 
-#define NO_SKILL (-1)
-#define LAST_SKILL (Skill_deaths_vengeance)
+#define LAST_SKILL (Skill_sanctified_by_death)
 #define NUM_SKILLS (1 + LAST_SKILL)
 
 /*! \brief Skill descriptor */
 struct Skill_desc
 {
     char const *name;
-    char const *description;
+    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
@@ -94,6 +105,7 @@ public:
     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?
index be9bb80..dadd2f8 100644 (file)
--- a/pmon2.cc
+++ b/pmon2.cc
@@ -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);
index 759778f..9f8e756 100755 (executable)
--- a/pobj_comp
+++ b/pobj_comp
@@ -30,7 +30,14 @@ 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,
diff --git a/role.cc b/role.cc
index 7de3e00..4ce93d2 100644 (file)
--- a/role.cc
+++ b/role.cc
@@ -57,7 +57,7 @@ static void demonhunter_inventory(Player *p)
 
 static void thanatophile_inventory(Player *p)
 {
-    p->inventory[0] = create_obj(PO_MACE, 1, true, Nowhere);
+    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];
index 0198b82..1630c5e 100644 (file)
--- a/skills.cc
+++ b/skills.cc
@@ -46,10 +46,25 @@ Skill_desc skill_props[NUM_SKILLS] =
         "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.");
+         "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",
@@ -73,15 +88,54 @@ Skill_desc skill_props[NUM_SKILLS] =
     },
     {
         "Death's Grace",
-        ("Beloved Death has granted you partial protection from the malign "
-         "arts of necromancers. You take greatly reduced damage from "
-         "necromantic magic."),
+        ("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 */
index 5382d82..df0f6bb 100644 (file)
@@ -28,6 +28,7 @@
 #include "obumbrata.hh"
 #include "sorcery.hh"
 #include "monsters.hh"
+#include "objects.hh"
 #include "combat.hh"
 
 /* SORCERY
@@ -184,8 +185,45 @@ static void cast_armourmelt(Mon_handle mon)
     }
 }
 
+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;
+}
+
+static Radiance reanimate_radiance = 
+{
+    {},
+    Nowhere,
+    MAX_FOV_RADIUS,
+    Reo_spiral_out,
+    false,
+    nullptr,
+    dflt_blk,
+    reanimation_effector
+};
+
 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();
 }
 
 Monspell_cast_func sorcery_funcs[NUM_MONSPELLS] =
@@ -283,6 +321,8 @@ Monspell lich_spell_select(int mon)
        {
            switch (zero_die(6))
            {
+           case 3:
+               return MS_ANIMATE_DEAD;
            case 4:
                if (!u.leadfoot)
                {
@@ -305,6 +345,8 @@ Monspell lich_spell_select(int mon)
            {
                switch (zero_die(6))
                {
+               case 3:
+                   return MS_ANIMATE_DEAD;
                case 4:
                    if (!u.leadfoot)
                    {
@@ -345,6 +387,8 @@ Monspell master_lich_spell_select(int mon)
        {
            switch (zero_die(7))
            {
+           case 3:
+               return MS_ANIMATE_DEAD;
            case 6:
                if (!u.withering)
                {
@@ -370,6 +414,8 @@ Monspell master_lich_spell_select(int mon)
        {
            switch (zero_die(10))
            {
+           case 6:
+               return MS_ANIMATE_DEAD;
            case 9:
                if (!u.withering)
                {
@@ -395,11 +441,14 @@ Monspell master_lich_spell_select(int mon)
        {
            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)
                {
diff --git a/u.cc b/u.cc
index 8afe1ae..b47f782 100644 (file)
--- a/u.cc
+++ b/u.cc
@@ -102,6 +102,18 @@ void recalc_defence(void)
     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.resistances[DT_NECRO] |= BONUS_MASK_PERM_SKILL;
+    }
+    if (u.known_skill[Skill_deaths_vengeance])
+    {
+        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();
 }
 
@@ -344,6 +356,7 @@ void u_init(char const *name, Role_id role)
     {
         strncpy(u.name, name, 16);
     }
+    u.role = role;
     u.body = 10;
     u.bdam = 0;
     u.agility = 10;
@@ -353,10 +366,14 @@ void u_init(char const *name, Role_id role)
     u.experience = 0;
     u.level = 1;
     u.food = 2000;
-    for (int i = 0; i < 19; ++i)
+    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();
 }
@@ -389,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);
@@ -406,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);
@@ -486,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--;
@@ -661,10 +718,14 @@ void player_cleanup(void)
     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 */