* core.hh: New command CMD_POWER_ATTACK, and some Doxygenation.
* default.permobjs: All weapons now flagged melee or ranged.
* monsters.cc, monsters.hh: Elimination of newsym() calls and addition of new
functions to adjust flow of updates.
* notify-local-tty.cc, notify.hh: New notifications galore.
* permobj.hh, pobj_comp: New item flags
* player.hh: new boolean functions for examining player's wielding status
* u.cc: Action rewriting system with tech-demo combo detection case.
Sir Not Appearing In This Commit: Making a power attack be an actual thing
instead of just an ordinary attack.
#define LAST_FIXED_COLOUR Gcol_silver
#define LAST_COLOUR Gcol_prio_bug
-/* XXX enum damtyp - types of damage. */
+/*! \brief Identification code for damage types */
enum Damtyp {
DT_PHYS = 0, DT_COLD, DT_FIRE, DT_NECRO,
DT_ELEC, DT_HELLFIRE, DT_POISON,
};
#define DT_COUNT (1 + DT_DROWNING)
-/* XXX enum Game_cmd - player actions. */
+/*! \brief Identification code for player actions */
enum Game_cmd {
- WALK, STAND_STILL, GO_DOWN_STAIRS, ATTACK,
+ REJECTED_ACTION = -1, WALK, STAND_STILL, GO_DOWN_STAIRS, ATTACK,
GET_ITEM, DROP_ITEM,
WIELD_WEAPON, WEAR_ARMOUR, TAKE_OFF_ARMOUR, PUT_ON_RING, REMOVE_RING,
QUAFF_POTION, READ_SCROLL, THROW_FLASK, EAT_FOOD,
EMANATE_ARMOUR, ZAP_WEAPON, MAGIC_RING,
USE_ACTIVE_SKILL, ALLOCATE_SKILL_POINT,
SAVE_GAME, QUIT,
- WIZARD_LEVELUP, WIZARD_DESCEND
+ WIZARD_LEVELUP, WIZARD_DESCEND,
+ /* combos begin here */
+ CMD_POWER_ATTACK
};
-/* XXX enum Death */
-/* Sadly, there are not 52 kinds of way to die. */
+/*! \brief Identification code for ways to die
+ *
+ * Sadly, there are not 52 kinds of way to die.
+ */
enum Death {
DEATH_KILLED, DEATH_KILLED_MON, DEATH_BODY, DEATH_AGILITY,
DEATH_LASH, DEATH_RIBBONS
};
+/*! \brief Fell powers that might influence the Princess's fate
+ */
enum Fell_powers {
- FePo_iron, // boosted by ironguard armour, hellglaive
- FePo_decay, // boosted by foetid vestments,
- FePo_bone, // boosted by lich's robe, vampire ring
- FePo_flesh // boosted by t's lash or the ribbons,
+ FePo_iron,
+ FePo_decay,
+ FePo_bone,
+ FePo_flesh
};
#define TOTAL_FELL_POWERS (1 + FePo_flesh)
#define RESIST_RING 0x00010000u
#define RESIST_ARMOUR 0x00020000u
-/*! \brief Represent an in-game action in more detail than Game_cmd
+/*! \brief Represent an in-game action by the player
+ *
+ * This might, at some point, get adapted to
*/
struct Action
{
POWER2 0
DEPTH 1
DAMAGEABLE
+MELEE_WEAPON
WEAPON long sword
PLURAL long swords
POWER2 0
DEPTH 4
DAMAGEABLE
+MELEE_WEAPON
WEAPON mace
PLURAL maces
POWER2 0
DEPTH 2
DAMAGEABLE
+MELEE_WEAPON
WEAPON runesword
PLURAL runeswords
POWER2 0
DEPTH 12
DAMAGEABLE
+MELEE_WEAPON
WEAPON hellglaive
PLURAL hellglaives
NOTIFY_EQUIP
DAMAGEABLE
BREAK_REACT
+MELEE_WEAPON
WEAPON plague scythe
PLURAL plague sycthes
POWER2 0
DEPTH 30
NOTIFY_EQUIP
+MELEE_WEAPON
WEAPON tormentor's lash
PLURAL tormentor's lashes
NOTIFY_EQUIP
DAMAGEABLE
BREAK_REACT
+MELEE_WEAPON
WEAPON death staff
PLURAL death staves
POWER2 0
DEPTH 30
NOTIFY_EQUIP
+MELEE_WEAPON
WEAPON staff of fire
PLURAL staves of fire
DEPTH 20
DAMAGEABLE
ACTIVATABLE
+MELEE_WEAPON
WEAPON bow
PLURAL bows
POWER2 0
DEPTH 1
DAMAGEABLE
-
+RANGED_WEAPON
WEAPON crossbow
PLURAL crossbows
DESC A crossbow.
POWER2 0
DEPTH 6
DAMAGEABLE
+RANGED_WEAPON
WEAPON thunderbow
PLURAL thunderbows
POWER2 0
DEPTH 30
DAMAGEABLE
+RANGED_WEAPON
POTION healing potion
PLURAL healing potions
}
monsters[mon].awake = false;
lvl.set_mon_at(c, mon);
- newsym(c);
+ if (mon_visible(mon))
+ {
+ notify_new_mon_at(c, mon);
+ }
return mon;
}
}
}
}
+void unplace_mon(int mon)
+{
+ lvl.set_mon_at(monsters[mon].pos, NO_MON);
+ monsters[mon].used = false;
+}
+
+/*! \brief Handle the death of a monster
+ *
+ * \todo Support special effects on monster death
+ */
+void kill_mon(int mon, bool by_you)
+{
+ death_drop(mon); // phat lewt!
+ monsters[mon].hpcur = -1; // Set HP to -1 so nothing will think it's alive
+ unplace_mon(mon); // cleanup
+ if (by_you)
+ {
+ notify_player_killed_mon(mon);
+ gain_experience(permons[monsters[mon].mon_id].exp);
+ }
+ else if (mon_visible(mon))
+ {
+ notify_mon_dies(mon);
+ }
+}
+
void damage_mon(int mon, int amount, bool by_you)
{
Mon *mptr;
mptr = monsters + mon;
if (amount >= mptr->hpcur)
{
- if (by_you)
- {
- notify_player_killed_mon(mon);
- gain_experience(permons[mptr->mon_id].exp);
- }
- else if (mon_visible(mon))
- {
- notify_mon_dies(mon);
- }
- death_drop(mon);
- lvl.set_mon_at(mptr->pos, NO_MON);
- newsym(mptr->pos);
- mptr->used = 0;
- map_updated = 1;
+ kill_mon(mon, by_you);
display_update();
}
else
/* 0 = blocked, 1 = knocked, 2 = killed */
Mon *mptr = monsters + mon;
Coord c = mptr->pos + step;
+ Coord savedpos = mptr->pos;
Terrain terr = lvl.terrain_at(c);
if (mptr->resists(DT_KNOCKBACK))
}
return 0;
}
+ reloc_mon(mon, c);
switch (terr)
{
case LAVA:
}
if (!mptr->resists(DT_FIRE))
{
- damage_mon(mon, 9999, by_you);
+ kill_mon(mon, by_you);
return 2;
}
}
}
if (!mptr->resists(DT_DROWNING))
{
- damage_mon(mon, 9999, by_you);
+ kill_mon(mon, by_you);
return 2;
}
}
default:
break;
}
- reloc_mon(mon, c);
return 1;
}
{
Mon *mptr = monsters + mon;
lvl.set_mon_at(mptr->pos, NO_MON);
- newsym(mptr->pos);
+ notify_new_mon_at(mptr->pos, NO_MON);
mptr->pos = newpos;
lvl.set_mon_at(mptr->pos, mon);
- newsym(mptr->pos);
+ notify_new_mon_at(mptr->pos, mon);
}
void move_mon(int mon, Coord c)
extern Pass_fail teleport_mon(int mon); /* Randomly relocate monster. */
extern Pass_fail teleport_mon_to_you(int mon); /* Relocate monster to your vicinity. */
extern void heal_mon(int mon, int amount, int cansee);
+extern void kill_mon(int mon, bool by_you);
+extern void unplace_mon(int mon);
/* XXX mon2.c data and funcs */
extern void select_space(int *py, int *px, int dy, int dx, int selection_mode);
void notify_player_killed_mon(int mon)
{
+ newsym(monsters[mon].pos);
print_msg("You kill ");
- if (mon_visible(mon))
+ if (occupant_visible(monsters[mon].pos))
{
print_mon_name(mon, 1);
}
void notify_mon_dies(int mon)
{
- print_mon_name(mon, 2);
- print_msg(" dies.\n");
+ newsym(monsters[mon].pos);
+ if (occupant_visible(monsters[mon].pos))
+ {
+ print_mon_name(mon, 2);
+ print_msg(" dies.\n");
+ }
}
void notify_knockback_mon_resisted(int mon)
{
print_msg(Msg_prio::Alert, "Your pack is full.\n");
}
+
+void notify_new_mon_at(Coord c, int mon)
+{
+ newsym(c);
+}
+
/* Debugging notifications */
void debug_bad_monspell(int spell)
print_msg(Msg_prio::Alert, "NOTICE: savefile unlink() failed - are you trying to savescum?\n");
}
+void debug_attacking_with_wielded_nonweapon(void)
+{
+ print_msg(Msg_prio::Bug, "BUG: attacking with wielded nonweapon\n");
+}
+
+void debug_control_flow(char const *func, int line)
+{
+ print_msg(Msg_prio::Bug, "BUG: control flow passed through unexpected line %d in function %s\n", line, func);
+}
+
/* notify-local-tty.cc */
// vim:cindent
void notify_mon_ranged_missed_player(int mon);
void notify_mon_ranged_hit_mon(int er, int ee);
void notify_mon_dies(int mon);
+void notify_new_mon_at(Coord c, int mon);
void notify_player_damage_taken(int amount);
void notify_player_touch_effect(Damtyp dt);
void debug_unimplemented_break_reaction(int po);
void debug_unimplemented_radiance_order(int order);
void debug_save_unlink_failed(void);
+void debug_attacking_with_wielded_nonweapon(void);
+void debug_control_flow(char const *func, int line);
+
+#define DEBUG_CONTROL_FLOW() debug_control_flow(__FUNCTION__, __LINE__)
// Provisional object designs
class Notify_uattkm
#define POF_STACKABLE 0x00000004u // item can stack
#define POF_DAMAGEABLE 0x00000008u // track durability
#define POF_BREAK_REACT 0x00000010u // item reacts to breakage attempts
+/* Yes, DRESS and MELEE_WEAPON have the same value. This is OK because
+ * only an ARMOUR can possibly be a DRESS, and only a WEAPON can possibly be
+ * a MELEE_WEAPON. */
#define POF_DRESS 0x00010000u
+#define POF_MELEE_WEAPON 0x00010000u
+#define POF_RANGED_WEAPON 0x00020000u
/*! \brief The 'permanent object' database */
class Permobj {
#define SLOT_NOTHING (-2)
/* XXX u.c data and funcs */
+extern Player u;
extern void u_init(char const *name);
extern void write_char_dump(void);
extern int do_death(Death d, char const *what);
extern void recalc_defence(void);
extern Pass_fail teleport_u(void);
extern void update_player(void);
-
-extern Player u;
+inline bool empty_handed(void) { return u.weapon == NO_OBJ; }
+extern bool wielding_melee_weapon(void);
+extern bool wielding_ranged_weapon(void);
#endif
'DAMAGEABLE' => 0,
'BREAK_REACT' => 0,
'DRESS' => 0,
+ 'RANGED_WEAPON' => 0,
+ 'MELEE_WEAPON' => 0,
);
#include <stdio.h>
#include <deque>
+bool action_rewrite(Action const *act, Action *revised_act);
struct Player u;
+bool wielding_ranged_weapon(void)
+{
+ return permobjs[objects[u.weapon].obj_id].flags[0] & POF_RANGED_WEAPON;
+}
+
+bool wielding_melee_weapon(void)
+{
+ return permobjs[objects[u.weapon].obj_id].flags[0] & POF_MELEE_WEAPON;
+}
+
void recalc_defence(void)
{
int i;
enum Combo_phase
{
Combo_invalid,
- Combo_link,
+ Combo_valid,
Combo_finisher
};
* \return true if revised_act was written to; false otherwise
*/
-bool detect_combo(Action const *act, Action *revised_act)
+bool action_rewrite(Action const *act, Action *revised_act)
{
- //Combo_phase p;
- // for now, we use if rather than switch because there are only two
- // valid action types in a combo: move, and attack.
- if (act->cmd == WALK)
+ Coord c;
+ Offset o;
+ Action tmp_act = *act;
+ Combo_phase p;
+ bool rewrite_flag;
+ switch (tmp_act.cmd)
{
- *revised_act = *act;
- return true;
+ case STAND_STILL:
+ p = Combo_valid;
+ break;
+ case ATTACK:
+ o.y = tmp_act.details[0];
+ o.x = tmp_act.details[1];
+ c = u.pos + o;
+ if (!lvl.in_bounds(c))
+ {
+ debug_move_oob(c); // TODO notify_attack_oob()
+ tmp_act.cmd = REJECTED_ACTION;
+ rewrite_flag = true;
+ p = Combo_invalid;
+ break;
+ }
+ p = Combo_finisher;
+ break;
+ case WALK:
+ o.y = tmp_act.details[0];
+ o.x = tmp_act.details[1];
+ c = u.pos + o;
+ if (!lvl.in_bounds(c))
+ {
+ debug_move_oob(c);
+ tmp_act.cmd = REJECTED_ACTION;
+ rewrite_flag = true;
+ p = Combo_invalid;
+ }
+ else if (lvl.mon_at(c) != NO_MON)
+ {
+ tmp_act.cmd = ATTACK;
+ rewrite_flag = true;
+ p = Combo_finisher;
+ }
+ else
+ {
+ p = Combo_valid;
+ }
+ break;
+ default:
+ rewrite_flag = false;
+ p = Combo_invalid;
+ break;
}
- else if (act->cmd == ATTACK)
+ switch (p)
{
- *revised_act = *act;
- return true;
+ case Combo_invalid:
+ break;
+ case Combo_valid:
+ if (past_actions.empty())
+ {
+ past_actions.push_front(tmp_act);
+ }
+ else
+ {
+ switch (tmp_act.cmd)
+ {
+ case STAND_STILL:
+ /* For now, no combo opens with more than one stand still,
+ * and no combo starts with something else then chains through
+ * a stand still */
+ if (past_actions.front().cmd != STAND_STILL)
+ {
+ past_actions.clear();
+ past_actions.push_front(tmp_act);
+ }
+ break;
+ case WALK:
+ /* For now, no combo chains through a WALK, but the charge
+ * combo will when I have the mental effort to write it. */
+ past_actions.clear();
+ break;
+ default:
+ DEBUG_CONTROL_FLOW();
+ break;
+ }
+ }
+ break;
+ case Combo_finisher:
+ if (!past_actions.empty())
+ {
+ switch (past_actions.front().cmd)
+ {
+ case STAND_STILL:
+ if (empty_handed() || wielding_melee_weapon())
+ {
+ rewrite_flag = true;
+ tmp_act.cmd = CMD_POWER_ATTACK;
+ }
+ break;
+ case WALK:
+ break;
+ default:
+ DEBUG_CONTROL_FLOW();
+ break;
+ }
+ }
+ past_actions.clear();
+ break;
}
- else
+ if (rewrite_flag)
{
- return false;
+ *revised_act = tmp_act;
}
+ return rewrite_flag;
}
-/*! \brief Process a player action. */
+/*! \brief Process a player action.
+ *
+ * \todo Make CMD_POWER_ATTACK do something special
+ */
Action_cost do_player_action(Action *act)
{
int slot;
Offset step;
Action redact;
- bool rewritten = detect_combo(act, &redact);
+ bool rewritten = action_rewrite(act, &redact);
if (rewritten)
{
act = &redact;
}
switch (act->cmd)
{
+ case REJECTED_ACTION:
+ return Cost_none;
+
+ case CMD_POWER_ATTACK:
+ step.y = act->details[0];
+ step.x = act->details[1];
+ return player_attack(step);
+
case WALK:
step.y = act->details[0];
step.x = act->details[1];