From c2f5361f63deeed1c7415f742dfa83f90d0ce699 Mon Sep 17 00:00:00 2001 From: Martin Read Date: Fri, 14 Mar 2014 20:45:02 +0000 Subject: [PATCH] Final commit for 7drl deadline. All further fixes will have to come after the 7drlc is over. --- Makefile | 5 +- combat.cc | 43 +++++++--- combat.hh | 15 ++-- combo.cc | 194 +++++++++++++++++++++++++++++++++++++++++-- core.hh | 38 +++++++-- deeds.cc | 34 +++++++- default.permobjs | 21 ++++- display-nc.cc | 51 +++++++++++- log.cc | 21 +++++ map.cc | 8 ++ map.hh | 9 ++ mon1.cc | 7 +- notify-local-tty.cc | 235 ++++++++++++++++++++++++++++++++++++---------------- notify.hh | 5 ++ obj1.cc | 80 ++++++++++++++++-- objects.hh | 1 + permobj.hh | 18 +++- player.hh | 24 ++++-- pmon2.cc | 5 ++ pobj_comp | 7 ++ role.cc | 2 +- skills.cc | 62 +++++++++++++- sorcery.cc | 49 +++++++++++ u.cc | 69 ++++++++++++++- 24 files changed, 863 insertions(+), 140 deletions(-) diff --git a/Makefile b/Makefile index 6790a9f..77f41ea 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ vpath %.o . GENERATED_OBJS:=permobj.o permons.o GENERATED_SOURCE:=permobj.cc pobj_id.hh permons.cc pmon_id.hh GENERATED_MAKES:=dirs.mk features.mk -HANDWRITTEN_OBJS:=cave.o combat.o 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 diff --git a/combat.cc b/combat.cc index fee2098..11d2009 100644 --- 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) { diff --git a/combat.hh b/combat.hh index c2be288..2a2d6f1 100644 --- a/combat.hh +++ b/combat.hh @@ -39,13 +39,14 @@ #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 { diff --git a/combo.cc b/combo.cc index 6e3467c..bea7c01 100644 --- 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_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_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_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_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 --- 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) diff --git a/deeds.cc b/deeds.cc index 1634648..b8a4f76 100644 --- 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 */ diff --git a/default.permobjs b/default.permobjs index 0db5f1f..82206ff 100644 --- a/default.permobjs +++ b/default.permobjs @@ -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 diff --git a/display-nc.cc b/display-nc.cc index 2ca0636..e22a58e 100644 --- a/display-nc.cc +++ b/display-nc.cc @@ -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 .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 --- 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 --- a/map.cc +++ b/map.cc @@ -35,6 +35,14 @@ 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 --- 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 --- 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) diff --git a/notify-local-tty.cc b/notify-local-tty.cc index 20cf9f7..d25fa70 100644 --- a/notify-local-tty.cc +++ b/notify-local-tty.cc @@ -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) diff --git a/notify.hh b/notify.hh index 833000c..4eef7ff 100644 --- 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 --- 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 diff --git a/objects.hh b/objects.hh index 1a38c82..5a53e6d 100644 --- a/objects.hh +++ b/objects.hh @@ -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) { diff --git a/permobj.hh b/permobj.hh index 386d1c2..03aa5c8 100644 --- a/permobj.hh +++ b/permobj.hh @@ -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); diff --git a/player.hh b/player.hh index a8301c7..857dee2 100644 --- a/player.hh +++ b/player.hh @@ -33,32 +33,43 @@ 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? diff --git a/pmon2.cc b/pmon2.cc index be9bb80..dadd2f8 100644 --- 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); diff --git a/pobj_comp b/pobj_comp index 759778f..9f8e756 100755 --- 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 --- 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]; diff --git a/skills.cc b/skills.cc index 0198b82..1630c5e 100644 --- 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 */ diff --git a/sorcery.cc b/sorcery.cc index 5382d82..df0f6bb 100644 --- a/sorcery.cc +++ b/sorcery.cc @@ -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 --- 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 */ -- 2.11.0