cave.cc
combat.cc
combat.hh
+combo.cc
coord.cc
coord.hh
core.hh
pobj_comp
rng.cc
rng.hh
+role.cc
shrine.cc
+skills.cc
sorcery.cc
sorcery.hh
u.cc
GENERATED_OBJS:=permobj.o permons.o
GENERATED_SOURCE:=permobj.cc pobj_id.hh permons.cc pmon_id.hh
GENERATED_MAKES:=dirs.mk features.mk
-HANDWRITTEN_OBJS:=cave.o combat.o coord.o deeds.o display-nc.o fov.o log.o main.o map.o mon1.o mon2.o mon3.o notify-local-tty.o obj1.o obj2.o pmon2.o rng.o shrine.o sorcery.o u.o util.o
+HANDWRITTEN_OBJS:=cave.o combat.o combo.o coord.o deeds.o display-nc.o fov.o log.o main.o map.o mon1.o mon2.o mon3.o notify-local-tty.o obj1.o obj2.o pmon2.o rng.o role.o shrine.o sorcery.o u.o util.o
OBJS:=$(GENERATED_OBJS) $(HANDWRITTEN_OBJS)
GAME:=obumbrata
MAJVERS:=1
my-debworkflow: my-debclean debianize-archive
mkdir archive-test
- cp $(ARCHIVENAME).orig.tar.gz archive-test
+ mv $(ARCHIVENAME).orig.tar.gz archive-test
(cd archive-test && tar xzf $(ARCHIVENAME).orig.tar.gz)
cp -R debian archive-test/$(ARCHIVEDIR)/debian
(cd archive-test/$(ARCHIVEDIR) && debuild -uc -us)
combat.o: $(srcdir)/combat.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/monsters.hh $(srcdir)/objects.hh $(srcdir)/notify.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+combo.o: $(srcdir)/combo.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
coord.o: $(srcdir)/coord.cc $(srcdir)/coord.hh
deeds.o: $(srcdir)/deeds.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
obj2.o: $(srcdir)/obj2.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+rng.o: $(srcdir)/rng.cc $(srcdir)/rng.hh
+
+role.o: $(srcdir)/role.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
+
shrine.o: $(srcdir)/shrine.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/map.hh $(srcdir)/mapgen.hh
sorcery.o: $(srcdir)/sorcery.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/sorcery.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh
}
if (u.weapon != NO_OBJ)
{
- damage_obj(u.weapon);
+ if (permobjs[objects[u.weapon].po_ref].flags[0] & POF_DAMAGEABLE)
+ {
+ damage_obj(u.weapon);
+ }
}
}
if ((u.armour != NO_OBJ) && (tohit > agility_modifier()))
{
/* Monster hit your armour. */
- damage_obj(u.armour);
+ if (permobjs[objects[u.armour].po_ref].flags[0] & POF_DAMAGEABLE)
+ {
+ damage_obj(u.armour);
+ }
notify_mon_hit_armour(mon);
}
else
--- /dev/null
+/*! \file combo.cc
+ * \brief Combo detection for Obumbrata et Velata
+ */
+
+/*
+ * Copyright © 2014 Martin Read
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "obumbrata.hh"
+#include "combat.hh"
+#include "objects.hh"
+#include <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <deque>
+
+/* Design note: Completing a combo discards the moves that comprised the
+ * combo from the list, and pushes the combo's special action itself if the
+ * combo is marked as Combo_valid. */
+
+static bool rewrite_stand_still_1(Action *revisable_action)
+{
+ return false;
+}
+
+static bool rewrite_walk_1(Action *revisable_action)
+{
+ Mon_handle mon;
+ Offset o = { (int32_t) revisable_action->details[0], (int32_t) revisable_action->details[1] };
+ Coord c = u.pos + o;
+ if (!lvl.in_bounds(c))
+ {
+ debug_move_oob(c);
+ revisable_action->cmd = REJECTED_ACTION;
+ return true;
+ }
+ else if (lvl.mon_at(c) != NO_MON)
+ {
+ mon = lvl.mon_at(c);
+ revisable_action->cmd = ATTACK;
+ return true;
+ }
+ return false;
+}
+
+static bool rewrite_attack_1(Action *revisable_action)
+{
+ Offset o = { (int32_t) revisable_action->details[0], (int32_t) revisable_action->details[1] };
+ Coord c = u.pos + o;
+ if (!lvl.in_bounds(c))
+ {
+ debug_move_oob(c); // TODO notify_attack_oob()
+ revisable_action->cmd = REJECTED_ACTION;
+ return true;
+ }
+ return false;
+}
+
+Action_rewriter_phase1_func action_rewrite_phase1[NUM_COMMANDS] =
+{
+ rewrite_walk_1,
+ rewrite_stand_still_1,
+ nullptr, // rewrite_ascend_1
+ nullptr, // rewrite_descend_1
+ rewrite_attack_1,
+ nullptr, // rewrite_get_1
+ nullptr, // rewrite_drop_1
+ nullptr, // rewrite_wield_1
+ nullptr, // rewrite_armour_on_1
+ nullptr, // rewrite_armour_off_1
+ nullptr, // rewrite ring_on_1
+ nullptr, // rewrite_ring_off_1
+ nullptr, // rewrite_quaff_1
+ nullptr, // rewrite_read_1
+ nullptr, // rewrite_throw_1
+ nullptr, // rewrite_eat_1
+ nullptr, // rewrite_emanate_1
+ nullptr, // rewrite_zap_1
+ nullptr, // rewrite_ringmagic_1
+ nullptr, // rewrite_use_skill_1
+ nullptr, // rewrite_allocate_skill_1
+ nullptr, // rewrite_save_1
+ nullptr, // rewrite_quit_1
+ nullptr, // rewrite_wizard_levup_1
+ nullptr, // rewrite_wizard_descend_1
+ nullptr, // rewrite_powatk_1
+};
+
+Combo_phase action_default_combophase[NUM_COMMANDS] =
+{
+ Combo_valid, // walk
+ Combo_valid, // stand still
+ Combo_invalid, // go up
+ Combo_invalid, // go down
+ Combo_finisher, // attack
+ Combo_invalid, // get
+ Combo_invalid, // drop
+ Combo_invalid, // wield
+ Combo_invalid, // wear
+ Combo_invalid, // take off
+ Combo_invalid, // put on
+ Combo_invalid, // remove
+ Combo_invalid, // quaff
+ Combo_invalid, // read
+ Combo_invalid, // throw
+ Combo_invalid, // eat
+ Combo_invalid, // emanate
+ Combo_invalid, // zap
+ Combo_invalid, // magic (ring)
+ Combo_invalid, // use skill
+ Combo_invalid, // allocate skill poiont
+ Combo_invalid, // save
+ Combo_invalid, // quit
+ Combo_invalid, // wizard mode levelup
+ Combo_invalid, // wizard mode descend
+ Combo_invalid, // combo: power attack
+};
+
+static bool powatk_avail(Player const *player)
+{
+ if (player->level >= 2)
+ {
+ if (player->weapon != NO_OBJ)
+ {
+ Obj const *optr = obj_snap(player->weapon);
+ if (permobjs[optr->po_ref].flags[0] & POF_RANGED_WEAPON)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+static Combo_state powatk_detector(std::deque<Action>* action_list)
+{
+ if ((*action_list)[0].cmd == STAND_STILL)
+ {
+ if ((*action_list).size() > 1)
+ {
+ if ((*action_list)[1].cmd == ATTACK)
+ {
+ (*action_list)[0] = (*action_list)[1];
+ (*action_list)[0].cmd = COMBO_POWATK;
+ action_list->resize(1);
+ return Combo_state_complete;
+ }
+ return Combo_state_none;
+ }
+ else
+ {
+ return Combo_state_partial;
+ }
+ }
+ else
+ {
+ return Combo_state_none;
+ }
+}
+
+Combo_entry combo_entries[] =
+{
+ /* Everyone gets power attack at level 2. */
+ { "power attack", STAND_STILL, powatk_avail, powatk_detector },
+ { "empty fencepost entry", REJECTED_ACTION, nullptr, nullptr }
+};
+
+/* combo.cc */
+// vim:cindent:expandtab
#include <stdbool.h>
#include <stdio.h>
#include <arpa/inet.h>
+#include <deque>
#ifndef COORD_HH
#include "coord.hh"
ATTACK,
GET_ITEM, DROP_ITEM,
WIELD_WEAPON, WEAR_ARMOUR, TAKE_OFF_ARMOUR, PUT_ON_RING, REMOVE_RING,
- QUAFF_POTION, READ_SCROLL, THROW_FLASK, EAT_FOOD,
+ QUAFF_POTION, READ_SCROLL, THROW_ITEM, EAT_FOOD,
EMANATE_ARMOUR, ZAP_WEAPON, MAGIC_RING,
USE_ACTIVE_SKILL, ALLOCATE_SKILL_POINT,
SAVE_GAME, QUIT,
WIZARD_LEVELUP, WIZARD_DESCEND,
/* combos begin here */
- CMD_POWER_ATTACK
+ COMBO_POWATK
};
-#define LAST_COMMAND (CMD_POWER_ATTACK)
+#define LAST_COMMAND (COMBO_POWATK)
#define NUM_COMMANDS (1 + LAST_COMMAND)
+/*! \brief Is this action a valid combo step? */
+enum Combo_phase
+{
+ Combo_invalid, // No, it isn't.
+ Combo_valid, // Yes, it is.
+ Combo_finisher // Only at the end of a combo.
+};
+
+/*! \brief Did we detect a combo? */
+enum Combo_state
+{
+ Combo_state_none, // We did not detect the combo
+ Combo_state_partial, // We detected a leading subset of the combo
+ Combo_state_complete // We detected the whole combo
+};
+
/*! \brief Identification code for ways to die
*
* Sadly, there are not 52 kinds of way to die.
uint32_t details[8];
};
-
class Level;
class Level_key;
class Permobj;
int dice_bonus;
};
+enum Role_id {
+ Role_none = -1,
+ Role_princess = 0,
+ Role_demon_hunter,
+ Role_thanatophile
+};
+#define LAST_ROLE (Role_thanatophile)
+#define NUM_ROLES (1 + LAST_ROLE)
+
+#define OBUMBRATA_MAGIC_NUMBER 0x0b756d62
+
+extern void game_cleanup(void);
+
+struct Obumb_sysexcep
+{
+ int err; // errno value
+ Obumb_sysexcep() = delete;
+ Obumb_sysexcep(int e) : err(e) { }
+};
+
+struct Obumb_dataexcep
+{
+ char const *str;
+ Obumb_dataexcep() = delete;
+ Obumb_dataexcep(char const *s) : str(s) { }
+};
+
#endif
/* core.hh */
return Cost_none;
}
-deed_func deed_funcs[NUM_COMMANDS] =
+Deed_func deed_funcs[NUM_COMMANDS] =
{
deed_walk,
deed_stand_still,
WEAPON long sword
PLURAL long swords
-DESC A steel sword of simple but sturdy construction; the blade is three feet long.
+DESC The evolution of the cruciform sword in response to ever-heavier plate armour, featuring an unsharpened ricasso to allow its use 'at the half-sword'.
RARITY 30
ASCII ')'
UTF8 ")"
DAMAGEABLE
MELEE_WEAPON
-WEAPON mace
-PLURAL maces
-DESC A flanged lump of iron on an iron haft.
-RARITY 30
+WEAPON windsword
+PLURAL windswords
+DESC A magic sword imbued with the power of the rushing winds, enabling the wielder to pass safely over such hazards as water, lava, and gaping chasms.
+RARITY 80
ASCII ')'
UTF8 ")"
-COLOUR iron
-POWER 7
+COLOUR white
+POWER 13
POWER2 0
-DEPTH 2
+DEPTH 10
DAMAGEABLE
MELEE_WEAPON
+NOTIFY_EQUIP
+FLIGHT
WEAPON runesword
PLURAL runeswords
DAMAGEABLE
MELEE_WEAPON
+WEAPON mace
+PLURAL maces
+DESC A flanged lump of iron on an iron haft.
+RARITY 30
+ASCII ')'
+UTF8 ")"
+COLOUR iron
+POWER 7
+POWER2 0
+DEPTH 2
+DAMAGEABLE
+MELEE_WEAPON
+
+WEAPON spear
+PLURAL spears
+DESC A blade on a stick.
+RARITY 50
+ASCII ')'
+UTF8 ")"
+COLOUR brown
+POWER 8
+POWER2 0
+DEPTH 3
+DAMAGEABLE
+MELEE_WEAPON
+
WEAPON hellglaive
PLURAL hellglaives
DESC A black-hafted polearm with a long, wickedly serrated blade. It thrums with infernal power.
POWER2 10
DEPTH 21
DAMAGEABLE
+RES_FIRE
ARMOUR meteoric plate armour
PLURAL suits of meteoric plate armour
POWER2 40
DEPTH 27
DAMAGEABLE
+RES_FIRE
ARMOUR sacred chainmail
PLURAL suits of sacred chainmail
COLOUR green
POWER 3
POWER2 10
-DEPTH 1
+DEPTH 3
DRESS
DAMAGEABLE
BREAK_REACT
NOTIFY_EQUIP
DAMAGEABLE
BREAK_REACT
+RES_POISON
ARMOUR lich's robe
PLURAL lich's robes
POWER2 0
DEPTH 30
NOTIFY_EQUIP
+RES_NECRO
ARMOUR infernite armour
PLURAL suits of infernite armour
POWER2 20
DEPTH 30
NOTIFY_EQUIP
+RES_FIRE
RING regeneration ring
PLURAL regeneration rings
POWER 0
POWER2 0
DEPTH 1
+RES_FIRE
+DMG_FIRE
RING vampire ring
PLURAL vampire rings
POWER 0
POWER2 0
DEPTH 12
+RES_NECRO
+DMG_NECRO
RING frost ring
PLURAL frost rings
POWER 0
POWER2 0
DEPTH 1
+RES_COLD
+DMG_COLD
+PASS_WATER
RING teleport ring
PLURAL teleport rings
POWER 0
POWER2 0
DEPTH 15
+RES_POISON
RING protection ring
PLURAL protection rings
POWER 0
POWER2 0
DEPTH 15
+PROTECTIVE
RING imperial seal
PLURAL imperial seals
ASCII '%'
UTF8 "%"
COLOUR brown
-POWER 0
+POWER 1500
POWER2 0
DEPTH 1
STACKABLE
ASCII '%'
UTF8 "%"
COLOUR yellow
-POWER 0
+POWER 1500
POWER2 0
DEPTH 1
STACKABLE
ASCII '%'
UTF8 "%"
COLOUR white
-POWER 0
+POWER 1500
POWER2 0
DEPTH 1
STACKABLE
cchar_t terrain_tiles[NUM_TERRAINS];
/*! \brief ncursesw objects for drawing items */
-cchar_t permobj_tiles[NUM_OF_PERMOBJS];
+cchar_t *permobj_tiles;
#define DISP_HEIGHT (DISP_NC_SIDE)
#define DISP_WIDTH (DISP_NC_SIDE)
/*! \brief ncursesw objects for drawing monsters */
-cchar_t permon_tiles[NUM_OF_PERMONS];
+cchar_t *permon_tiles;
/*! \brief ncursesw object for drawing unexplored space */
cchar_t blank_tile;
"drowning",
};
+static void announce_role(Role_id role)
+{
+ switch (role)
+ {
+ case Role_none:
+ print_msg(Msg_prio::Bug, "\nBUG: no role selected.\n\n");
+ break;
+ case Role_princess:
+ print_msg("\nYou are the rightful heir to your mother's throne,\n");
+ print_msg("and have been banished to this strange, shadowy\n");
+ print_msg("realm by your father's treacherous bastard. The\n");
+ print_msg("power of the royal house's magic echoes in your\n");
+ print_msg("soul, though you have little hope of directing it\n");
+ print_msg("well without the proper talismans.\n\n");
+ break;
+ case Role_demon_hunter:
+ print_msg("\nWhile fighting the members of a devil-cult, you were\n");
+ print_msg("banished to this strange, shadowy realm by the foul\n");
+ print_msg("sorcery of the cult leader. You imagine your training\n");
+ print_msg("will serve you well here, for what could be a more\n");
+ print_msg("obvious home for demons than a realm to which a cultist\n");
+ print_msg("would banish you?\n\n");
+ break;
+ case Role_thanatophile:
+ print_msg("\nPursuing a notorious necromancer, you stumbled on a\n");
+ print_msg("magical trap in his lair and found yourself banished\n");
+ print_msg("to this strange, shadowy realm. The austere arts of\n");
+ print_msg("beloved Death will serve well should you encounter the\n");
+ print_msg("walking dead in this terrible place.\n\n");
+ break;
+ }
+}
+
/*! \brief Draw the main menu. */
static void draw_main_menu(void)
{
static void load_ascii_tiles(void)
{
int i;
- {
- wchar_t wch[2];
- wch[0] = L'@';
- wch[1] = 0;
- setcchar(&player_tile, wch, 0, 0, nullptr);
- wch[0] = L' ';
- setcchar(&blank_tile, wch, 0, 0, nullptr);
- }
+ wchar_t wch[2];
+ wch[0] = L'@';
+ wch[1] = 0;
+ setcchar(&player_tile, wch, 0, 0, nullptr);
+ wch[0] = L' ';
+ setcchar(&blank_tile, wch, 0, 0, nullptr);
for (i = 0; i < NUM_OF_PERMONS; ++i)
{
- wchar_t wch[2];
wch[0] = permons[i].sym;
wch[1] = 0;
setcchar(permon_tiles + i, wch,
}
for (i = 0; i < NUM_TERRAINS; ++i)
{
- wchar_t wch[2];
wch[0] = terrain_props[i].ascii;
wch[1] = 0;
setcchar(terrain_tiles + i, wch,
}
for (i = 0; i < NUM_OF_PERMOBJS; ++i)
{
- wchar_t wch[2];
wch[0] = permobjs[i].sym;
wch[1] = 0;
setcchar(permobj_tiles + i, wch,
init_pair(Gcol_d_grey, COLOR_BLACK, COLOR_BLACK);
init_pair(Gcol_purple, COLOR_MAGENTA, COLOR_BLACK);
init_pair(Gcol_cyan, COLOR_CYAN, COLOR_BLACK);
+ permon_tiles = new cchar_t[NUM_OF_PERMONS];
+ permobj_tiles = new cchar_t[NUM_OF_PERMOBJS];
if (force_ascii)
{
load_ascii_tiles();
break;
case '\x1b':
case ' ':
+ print_msg("Cancelled.\n");
return You_fail; /* cancelled. */
default:
print_msg("Bad direction (use movement keys).\n");
*/
int display_shutdown(void)
{
+ delete[] permobj_tiles;
+ permobj_tiles = nullptr;
+ delete[] permon_tiles;
+ permon_tiles = nullptr;
+ hide_panel(status_panel);
+ hide_panel(world_panel);
+ hide_panel(message_panel);
+ hide_panel(inventory_panel);
+ hide_panel(fullscreen_panel);
+ update_panels();
+ doupdate();
clear();
refresh();
+ del_panel(status_panel);
+ del_panel(world_panel);
+ del_panel(message_panel);
+ del_panel(inventory_panel);
+ del_panel(fullscreen_panel);
+ delwin(status_window);
+ delwin(world_window);
+ delwin(message_window);
+ delwin(inventory_window);
+ delwin(fullscreen_window);
+ status_panel = world_panel = message_panel = inventory_panel = fullscreen_panel = nullptr;
+ status_window = world_window = message_window = inventory_window = fullscreen_window = nullptr;
endwin();
return 0;
}
ch = wgetch(message_window);
if (ch == 'Y')
{
+ print_msg("Confirmed.\n");
return 1;
}
+ print_msg("Cancelled.\n");
return 0;
}
return 0;
case '\x1b':
case ' ':
+ print_msg("Cancelled.\n");
return -1;
default:
print_msg("Invalid response. Press y or n (ESC or space to cancel)\n");
static void run_main_menu(void)
{
bool done = false;
+ bool role_selected = false;
+ Role_id role = Role_princess;
char name[16];
do
{
mvwprintw(fullscreen_window, 10, 1, "\n");
mvwprintw(fullscreen_window, 11, 1, "\n");
mvwprintw(fullscreen_window, 12, 1, "\n");
- mvwprintw(fullscreen_window, 13, 19, "Welcome. Remind me of your name?\n");
+ mvwprintw(fullscreen_window, 13, 19, "Welcome. Remind me of thy name?\n");
mvwprintw(fullscreen_window, 14, 1, "\n");
wmove(fullscreen_window, 14, 30);
update_panels();
mvwgetnstr(fullscreen_window, 14, 30, name, 16);
curs_set(0);
noecho();
- new_game(name);
+ mvwprintw(fullscreen_window, 15, 19, "What path dost thou follow in thy quest?\n");
+ mvwprintw(fullscreen_window, 16, 25, "(P)rincess\n");
+ mvwprintw(fullscreen_window, 17, 25, "(D)emon Hunter\n");
+ mvwprintw(fullscreen_window, 18, 25, "(T)hanatophile\n");
+ role_selected = false;
+ do
+ {
+ int ch = wgetch(fullscreen_window);
+ switch (ch)
+ {
+ case 'p':
+ case 'P':
+ role_selected = true;
+ role = Role_princess;
+ break;
+ case 'd':
+ case 'D':
+ role_selected = true;
+ role = Role_demon_hunter;
+ break;
+ case 't':
+ case 'T':
+ role_selected = true;
+ role = Role_thanatophile;
+ break;
+ default:
+ break;
+ }
+ } while (!role_selected);
+ new_game(name, role);
done = true;
wclear(message_window);
wmove(message_window, 0, 0);
show_game_view();
+ announce_role(role);
break;
case 'c':
case 'C':
case 'R':
// TODO implement loading properly.
{
- int i;
wclear(message_window);
wmove(message_window, 0, 0);
- i = load_game();
- if (i != -1)
- {
- done = true;
- }
- else
- {
- mvwprintw(fullscreen_window, 14, 30, "No saved game found.\n");
+ try
+ {
+ load_game();
+ done = true;
+ }
+ catch (Obumb_sysexcep e)
+ {
+ mvwprintw(fullscreen_window, 14, 20, "Error opening saved game:\n");
+ mvwprintw(fullscreen_window, 15, 1, "%s\n", strerror(e.err));
+ }
+ catch (Obumb_dataexcep e)
+ {
+ mvwprintw(fullscreen_window, 14, 20, "Error loading saved game:\n");
+ mvwprintw(fullscreen_window, 15, 1, "%s\n", e.str);
update_panels();
doupdate();
- }
+ }
}
break;
case 'q':
free(s);
}
+
/* display-nc.cc */
// vim:cindent
int datadir_fd;
int confdir_fd;
-/*! \brief call system(cmd) and throw an exception on failure
- *
- * \todo throw a proper exception
- */
+/*! \brief call system(cmd) and throw an exception on failure */
void wrapped_system(char const *cmd)
{
int i = system(cmd);
if (i != 0)
{
- throw(i);
+ throw Obumb_sysexcep(errno);
}
}
-/*! \brief call fread(args) and throw an exception on failure
- *
- * \todo throw a proper exception
- */
+/*! \brief call fread(args) and throw an exception on failure */
void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *fp)
{
size_t i = fread(buf, size, nmemb, fp);
if (i != nmemb)
{
- throw(i);
+ if (ferror(fp))
+ {
+ throw Obumb_sysexcep(errno);
+ }
+ else if (feof(fp))
+ {
+ throw Obumb_dataexcep("Unexpected end of file");
+ }
+ else
+ {
+ throw Obumb_sysexcep(errno);
+ }
}
}
fwrite(&tmp, 1, sizeof tmp, fp);
}
+/*! \brief Throw exception if monster handle invalid */
+static void check_monhandle(Mon_handle mon)
+{
+ if (mon >= first_free_mon_handle)
+ {
+ throw Obumb_dataexcep("Object handle >= first free object handle - save game probably corrupt");
+ }
+}
+
+static void check_pmref(int pm)
+{
+ if ((pm < 0) || (pm >= NUM_OF_PERMONS))
+ {
+ throw Obumb_dataexcep("Monster permon ID out of bounds - save game probably corrupt");
+ }
+}
+
/*! \brief Read an object handle */
static inline void deserialize_objhandle(FILE *fp, Obj_handle *o)
{
fwrite(&tmp, 1, sizeof tmp, fp);
}
+static void check_poref(int po)
+{
+ if ((po < 0) || (po >= NUM_OF_PERMOBJS))
+ {
+ throw Obumb_dataexcep("Object permobj ID out of bounds - save game probably corrupt");
+ }
+}
+
+/*! \brief Throw exception if object handle invalid */
+static void check_objhandle(Obj_handle obj)
+{
+ if (obj >= first_free_obj_handle)
+ {
+ throw Obumb_dataexcep("Object handle >= first free object handle - save game probably corrupt");
+ }
+}
+
/*! \brief Read a monster */
static void deserialize_monster(FILE *fp, Mon * mon)
{
deserialize_monhandle(fp, &(mon->self));
+ check_monhandle(mon->self);
deserialize_int(fp, &(mon->pm_ref));
+ check_pmref(mon->pm_ref);
deserialize_uint32(fp, &(mon->flags));
deserialize_coord(fp, &(mon->pos));
deserialize_coord(fp, &(mon->ai_lastpos));
static void deserialize_object(FILE *fp, Obj *obj)
{
deserialize_objhandle(fp, &(obj->self));
+ check_objhandle(obj->self);
deserialize_int(fp, &(obj->po_ref));
+ check_poref(obj->po_ref);
deserialize_uint32(fp, &(obj->flags));
deserialize_coord(fp, &(obj->pos));
deserialize_int(fp, &(obj->quan));
/*! \brief Read the player */
static void deserialize_player(FILE *fp, Player *player)
{
+ int32_t tmp;
deserialize_cstring(fp, player->name, 16);
deserialize_monhandle(fp, &(player->mh));
deserialize_coord(fp, &(player->pos));
+ deserialize_int(fp, &tmp);
+ player->role = (Role_id) tmp;
deserialize_int(fp, &(player->body));
deserialize_int(fp, &(player->bdam));
deserialize_int(fp, &(player->agility));
for (int i = 0; i < INVENTORY_SIZE; ++i)
{
deserialize_objhandle(fp, &(player->inventory[i]));
+ check_objhandle(player->inventory[i]);
}
deserialize_objhandle(fp, &(player->weapon));
+ check_objhandle(player->weapon);
deserialize_objhandle(fp, &(player->armour));
+ check_objhandle(player->armour);
deserialize_objhandle(fp, &(player->ring));
+ check_objhandle(player->ring);
for (int i = 0; i < TOTAL_FELL_POWERS; ++i)
{
deserialize_int(fp, &(player->sympathy[i]));
serialize_cstring(fp, player.name, 16);
serialize_monhandle(fp, player.mh);
serialize_coord(fp, player.pos);
+ serialize_int(fp, (int) player.role);
serialize_int(fp, player.body);
serialize_int(fp, player.bdam);
serialize_int(fp, player.agility);
close(fd);
return;
}
+ serialize_uint32(fp, OBUMBRATA_MAGIC_NUMBER);
+ serialize_uint32(fp, MAJVERS);
+ serialize_uint32(fp, MINVERS);
serialize_int(fp, depth);
serialize_int(fp, game_tick);
serialize_objhandle(fp, first_free_obj_handle);
}
}
-/*! \brief Reload and unlink a saved game */
-int load_game(void)
+static void check_magic(uint32_t magic)
{
- int fd;
- FILE *fp;
- fd = openat(datadir_fd, "obumbrata.sav", O_RDONLY, 0);
- if (fd == -1)
+ if (magic != OBUMBRATA_MAGIC_NUMBER)
{
- return -1;
+ throw Obumb_dataexcep("Invalid file ID code");
}
- fp = fdopen(fd, "rb");
- if (!fp)
+}
+
+static void check_minvers(uint32_t minvers)
+{
+ if (minvers != MINVERS)
+ {
+ throw Obumb_dataexcep("Minor-version mismatch");
+ }
+}
+
+static void check_majvers(uint32_t majvers)
+{
+ if (majvers != MAJVERS)
{
- return -1;
+ throw Obumb_dataexcep("Major-version mismatch");
}
- deserialize_int(fp, &depth);
- deserialize_int(fp, &game_tick);
- deserialize_objhandle(fp, &first_free_obj_handle);
- deserialize_monhandle(fp, &first_free_mon_handle);
- deserialize_player(fp, &u);
- deserialize_level(fp, &lvl);
- while (1)
+}
+
+/*! \brief Reload and unlink a saved game
+ *
+ * Any read errors or probable data corruption encountered during loading will
+ * be reported as an exception. */
+void load_game(void)
+{
+ int fd = -1;
+ FILE *fp = nullptr;
+ uint32_t tmp;
+ try
{
- Mon_handle mon;
- deserialize_monhandle(fp, &mon);
- if (mon != NO_MON)
+ fd = openat(datadir_fd, "obumbrata.sav", O_RDONLY, 0);
+ if (fd == -1)
{
- Mon m;
- deserialize_monster(fp, &m);
- monsters[mon] = m;
+ throw Obumb_sysexcep(errno);
}
- else
+ fp = fdopen(fd, "rb");
+ if (!fp)
{
- break;
+ throw Obumb_sysexcep(errno);
}
- }
- while (1)
- {
- Obj_handle obj;
- deserialize_objhandle(fp, &obj);
- if (obj != NO_OBJ)
+ deserialize_uint32(fp, &tmp);
+ check_magic(tmp);
+ deserialize_uint32(fp, &tmp);
+ check_majvers(tmp);
+ deserialize_uint32(fp, &tmp);
+ check_minvers(tmp);
+ deserialize_int(fp, &depth);
+ deserialize_int(fp, &game_tick);
+ deserialize_objhandle(fp, &first_free_obj_handle);
+ if (first_free_obj_handle == 0)
{
- Obj o;
- deserialize_object(fp, &o);
- objects[obj] = o;
+ throw Obumb_dataexcep("Object pool exhausted - save file probably corrupt");
}
- else
+ deserialize_monhandle(fp, &first_free_mon_handle);
+ if (first_free_mon_handle == 0)
{
- break;
+ throw Obumb_dataexcep("Monster pool exhausted - save file probably corrupt");
+ }
+ deserialize_player(fp, &u);
+ deserialize_level(fp, &lvl);
+ while (1)
+ {
+ Mon_handle mon;
+ deserialize_monhandle(fp, &mon);
+ check_monhandle(mon);
+ if (mon != NO_MON)
+ {
+ Mon m;
+ deserialize_monster(fp, &m);
+ monsters[mon] = m;
+ }
+ else
+ {
+ break;
+ }
}
+ while (1)
+ {
+ Obj_handle obj;
+ deserialize_objhandle(fp, &obj);
+ check_objhandle(obj);
+ if (obj != NO_OBJ)
+ {
+ Obj o;
+ deserialize_object(fp, &o);
+ objects[obj] = o;
+ }
+ else
+ {
+ break;
+ }
+ }
+ rebuild_mapobjs();
+ rebuild_mapmons();
+ u.mptr = mon_snapv(u.mh);
+ fclose(fp);
+ game_finished = false;
+ look_around_you();
+ notify_load_complete();
+ int i = unlinkat(datadir_fd, "obumbrata.sav", 0);
+ if (i == -1)
+ {
+ debug_save_unlink_failed();
+ }
+ return;
}
- rebuild_mapobjs();
- rebuild_mapmons();
- u.mptr = mon_snapv(u.mh);
- fclose(fp);
- game_finished = false;
- look_around_you();
- notify_load_complete();
- int i = unlinkat(datadir_fd, "obumbrata.sav", 0);
- if (i == -1)
+ catch (Obumb_dataexcep e)
{
- debug_save_unlink_failed();
+ /* NOTE: if we got a dataexcep, we've definitely opened fp, so no need
+ * to check for null pointer */
+ fclose(fp);
+ game_cleanup();
+ throw;
+ }
+ catch (Obumb_sysexcep e)
+ {
+ if (fp)
+ {
+ fclose(fp);
+ }
+ else if (fd != -1)
+ {
+ close(fd);
+ }
+ game_cleanup();
+ throw;
}
- return 0;
}
/*! \brief Write human-readable snapshot of the player character
{
depth = 0;
game_tick = 0;
+ first_free_obj_handle = 1u;
+ first_free_mon_handle = 1u;
level_cleanup();
player_cleanup();
}
-void new_game(char const *name)
+void new_game(char const *name, Role_id role)
{
game_finished = false;
depth = 1;
game_tick = 0;
- u_init(name);
+ u_init(name, role);
make_new_level();
}
game_cleanup();
}
-/*! \brief main()
- */
+/*! \brief main() */
int main(void)
{
load_config();
{
build_level();
populate_level();
+ notify_change_of_depth();
inject_player(&lvl, lvl.self.naive_prev());
look_around_you();
- notify_change_of_depth();
}
/*! \brief Random walk which grows the level
/*! \brief Description of terrain types */
Terrain_props terrain_props[NUM_TERRAINS] =
{
- { "cave wall", '#', "█", Gcol_brown, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
- { "masonry wall", '#', "█", Gcol_l_grey, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
- { "amethyst wall", '#', "█", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
- { "iron wall", '#', "█", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
- { "skin wall", '#', "█", Gcol_l_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
- { "bone wall", '#', "█", Gcol_white, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile },
- { "door", '+', "+", Gcol_l_grey, TFLAG_opaque | TFLAG_block_missile },
+ { "cave wall", '#', "█", Gcol_brown, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items },
+ { "masonry wall", '#', "█", Gcol_l_grey, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items },
+ { "amethyst wall", '#', "█", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items },
+ { "iron wall", '#', "█", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items },
+ { "skin wall", '#', "█", Gcol_l_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items },
+ { "bone wall", '#', "█", Gcol_white, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items },
+ { "door", '+', "+", Gcol_l_grey, TFLAG_opaque | TFLAG_block_missile | TFLAG_block_items },
{ "floor", '.', "·", Gcol_l_grey, TFLAG_floor },
{ "amethyst floor", '.', "·", Gcol_purple, TFLAG_floor },
{ "iron floor", '.', "·", Gcol_iron, TFLAG_floor },
{ "skin floor", '.', "·", Gcol_l_purple, TFLAG_floor },
{ "bone floor", '.', "·", Gcol_white, TFLAG_floor },
- { "altar", '_', "_", Gcol_l_grey, 0 },
- { "upward stairs", '<', "<", Gcol_l_grey, TFLAG_ascend },
- { "downward stairs", '>', ">", Gcol_l_grey, TFLAG_descend },
- { "inert portal", '^', "☊", Gcol_d_grey, TFLAG_portal | TFLAG_ascend },
- { "active portal", '^', "☊", Gcol_white, TFLAG_portal | TFLAG_descend },
- { "lava", '}', "≈", Gcol_red, TFLAG_fire_hazard },
- { "water", '}', "≈", Gcol_blue, TFLAG_drown_hazard },
+ { "altar", '_', "_", Gcol_l_grey, 0 | TFLAG_block_items },
+ { "upward stairs", '<', "<", Gcol_l_grey, TFLAG_ascend | TFLAG_block_items },
+ { "downward stairs", '>', ">", Gcol_l_grey, TFLAG_descend | TFLAG_block_items },
+ { "inert portal", '^', "☊", Gcol_d_grey, TFLAG_portal | TFLAG_ascend | TFLAG_block_items },
+ { "active portal", '^', "☊", Gcol_white, TFLAG_portal | TFLAG_descend | TFLAG_block_items },
+ { "chasm", ':', "↓", Gcol_d_grey, TFLAG_fall_hazard | TFLAG_block_items },
+ { "lava", '}', "≈", Gcol_red, TFLAG_fire_hazard | TFLAG_block_items },
+ { "water", '}', "≈", Gcol_blue, TFLAG_drown_hazard | TFLAG_block_items },
};
/*! \brief self-explanatory */
return terrain_props[terr].flags & TFLAG_opaque;
}
+bool terrain_gapes(Terrain terr)
+{
+ return terrain_props[terr].flags & TFLAG_fall_hazard;
+}
+
+bool terrain_drowns(Terrain terr)
+{
+ return terrain_props[terr].flags & TFLAG_drown_hazard;
+}
+
+bool terrain_is_hot(Terrain terr)
+{
+ return terrain_props[terr].flags & TFLAG_fire_hazard;
+}
+
+/*! \brief self-explanatory */
+bool terrain_blocks_items(Terrain terr)
+{
+ return terrain_props[terr].flags & TFLAG_block_items;
+}
+
/*! \brief self-explanatory */
bool terrain_blocks_beings(Terrain terr)
{
#include <deque>
/* XXX enum terrain_num */
enum Terrain {
- WALL = 0, MASONRY_WALL, AMETHYST_WALL, IRON_WALL, SKIN_WALL, BONE_WALL, DOOR, FLOOR, AMETHYST_FLOOR, IRON_FLOOR, SKIN_FLOOR, BONE_FLOOR, ALTAR, STAIRS_UP, STAIRS_DOWN, PORTAL_LANDING, PORTAL_ONWARD, LAVA, WATER
+ WALL = 0, MASONRY_WALL, AMETHYST_WALL, IRON_WALL, SKIN_WALL, BONE_WALL, DOOR, FLOOR, AMETHYST_FLOOR, IRON_FLOOR, SKIN_FLOOR, BONE_FLOOR, ALTAR, STAIRS_UP, STAIRS_DOWN, PORTAL_LANDING, PORTAL_ONWARD, CHASM, LAVA, WATER
};
#define MAX_TERRAIN (WATER)
#define NUM_TERRAINS (1 + MAX_TERRAIN)
#define TFLAG_ascend 0x00000020u
#define TFLAG_descend 0x00000040u
#define TFLAG_floor 0x00000080u
+#define TFLAG_block_items 0x00000100u
#define TFLAG_fire_hazard 0x00010000u
#define TFLAG_fall_hazard 0x00020000u
#define TFLAG_drown_hazard 0x00040000u
void inject_player(Level *l, Level_key from);
void look_around_you(void);
bool terrain_is_opaque(Terrain terr);
+bool terrain_is_hot(Terrain terr);
+bool terrain_gapes(Terrain terr);
+bool terrain_drowns(Terrain terr);
bool terrain_blocks_beings(Terrain terr);
+bool terrain_blocks_items(Terrain terr);
bool terrain_blocks_missiles(Terrain terr);
void serialize_level(FILE *fp, Level const *l);
void deserialize_level(FILE *fp, Level *l);
void apply_liquid(int pm, Coord c)
{
- if (pmon_bleeds(pm))
+ Decal_tag decal = pmon_fluid_decal(pm);
+ if (pm != NO_DECAL)
{
- lvl.set_decal_at(c, Decal_blood);
- }
- else if (pmon_suppurates(pm))
- {
- lvl.set_decal_at(c, Decal_pus);
+ lvl.set_decal_at(c, decal);
}
}
void kill_mon(Mon_handle mon, bool by_you, bool explode_corpse)
{
Mon *mptr = mon_snapv(mon);
+ Terrain t = lvl.terrain_at(mptr->pos);
int pm = mptr->pm_ref;
- apply_liquid(pm, mptr->pos);
+ if (!terrain_blocks_items(t))
+ {
+ apply_liquid(pm, mptr->pos);
+ }
if (pmon_explodes(pm) || (pmon_leaves_corpse(pm) && explode_corpse))
{
- // TODO shower the surroundings with the monster's liquid
+ detonate_mon(mon);
}
else if (pmon_leaves_corpse(pm))
{
if ((lvl.terrain_at(c2) == FLOOR) && (lvl.mon_at(c2) == NO_MON) &&
(c2 != u.pos))
{
- mon = create_mon(PM_DEMON, c2);
- notify_summon_demon(mon);
+ mon = create_mon(PM_DEMON, c2);
+ notify_summon_demon(mon);
}
}
+void detonate_mon(Mon_handle mon)
+{
+ Mon *mptr = mon_snapv(mon);
+ Coord c;
+ int pm = mptr->pm_ref;
+ Decal_tag decal = pmon_fluid_decal(pm);
+ if (decal != NO_DECAL)
+ {
+ for (c.y = mptr->pos.y - 1; c.y <= mptr->pos.y + 1; ++c.y)
+ {
+ for (c.x = mptr->pos.x - 1; c.x <= mptr->pos.x + 1; ++c.x)
+ {
+ if (lvl.in_bounds(c))
+ {
+ lvl.set_decal_at(c, decal);
+ }
+ }
+ }
+ }
+ return;
+}
+
bool mon_visible(Mon_handle mon)
{
Offset delta;
int ood(int power, int ratio);
int get_random_pmon(void);
void damage_mon(Mon_handle mon, int amount, bool by_you);
+void detonate_mon(Mon_handle mon);
bool mon_visible(Mon_handle mon);
int knockback_mon(Mon_handle mon, Offset step, bool cansee, bool by_you);
void move_mon(Mon_handle mon, Coord c);
print_msg("You feel like you are no longer being helped.\n");
}
-void notify_start_lavawalk(void)
+void notify_start_hotwalk(Terrain t)
{
- print_msg("You walk on the lava.\n");
+ print_msg("You stride fearlessly across the %s.\n", terrain_props[t].name);
}
-void notify_blocked_lava(void)
+void notify_blocked_hot(Terrain t)
{
- print_msg("The fierce heat of the molten rock repels you.\n");
+ print_msg("The fierce heat of the %s repels you.\n", terrain_props[t].name);
}
-void notify_start_waterwalk(void)
+void notify_start_waterwalk(Terrain t)
{
- print_msg("You walk on the water.\n");
+ print_msg("You stride fearlessly across the surface of the %s.\n", terrain_props[t].name);
}
-void notify_blocked_water(void)
+void notify_blocked_water(Terrain t)
{
print_msg("The idiot who raised you never taught you to swim.\n");
}
+void notify_portal_underfoot(void)
+{
+ print_msg("There is a stone arch here%s.\n", (lvl.terrain_at(u.pos) == PORTAL_ONWARD) ? ", glowing with arcane power" : "");
+}
+
void notify_new_obj_at(Coord c, Obj_handle obj)
{
newsym(c);
void notify_wield_weapon(Obj_handle obj)
{
- print_msg("Wielding ");
- print_obj_name(obj);
- print_msg(".\n");
+ Obj const *optr = obj_snap(obj);
+ int po = optr->po_ref;
+ if (permobjs[po].flags[0] & POF_NOTIFY_EQUIP)
+ {
+ switch (po)
+ {
+ case PO_WINDSWORD:
+ print_msg("Your stride lightens as you ready the magic blade.\n");
+ break;
+ case PO_HELLGLAIVE:
+ print_msg("Infernal power suffuses you as you heft the cruel pole-arm.\n");
+ break;
+ case PO_PLAGUE_SCYTHE:
+ print_msg("The scent of pestilence and decay fills your nostrils as you ready the scythe.\n");
+ break;
+ case PO_DEATH_STAFF:
+ print_msg("The chill of the grave suffuses you as you ready the black staff.\n");
+ break;
+ case PO_TORMENTORS_LASH:
+ print_msg("A%s tingle of power runs up your arm as you ready the magic whip.\n",
+ u.sybaritic() ? " delightful" : "n uncanny");
+ break;
+ }
+ }
+ else
+ {
+ print_msg("Wielding ");
+ print_obj_name(obj);
+ print_msg(".\n");
+ }
}
void notify_weapon_broke(Obj_handle obj)
// Resistance notifications
// Player movement notifications
-void notify_start_lavawalk(void);
-void notify_blocked_lava(void);
-void notify_start_waterwalk(void);
-void notify_blocked_water(void);
+void notify_start_hotwalk(Terrain t);
+void notify_blocked_hot(Terrain t);
+void notify_start_waterwalk(Terrain t);
+void notify_blocked_water(Terrain t);
void notify_cant_go(void);
+void notify_portal_underfoot(void);
// Unsorted notifications
void notify_new_obj_at(Coord c, Obj_handle obj);
void notify_armour_broke(Obj_handle obj);
void notify_drop_item(Obj_handle obj);
void notify_drop_blocked(void);
+void notify_drop_blocked_portal(void);
void notify_get_item(Obj_handle from, int slot);
void notify_pack_full(void);
{
Obj *optr = obj_snapv(obj);
bool ravenous = (u.food < 0);
- u.food += 1500;
if (permobjs[optr->po_ref].poclass != POCLASS_FOOD)
{
debug_eat_non_food(obj);
return Cost_none;
}
+ u.food += permobjs[optr->po_ref].power;
if (optr->po_ref == PO_DEVIL_SPLEEN)
{
notify_ingest_spleen();
}
break;
}
- return (with_you ? create_obj(po_idx, quantity, with_you, c) : create_obj_near(po_idx, quantity, c));
+ return (with_you ? create_obj(po_idx, quantity, true, c) : create_obj_near(po_idx, quantity, c));
}
int get_random_pobj(void)
Obj_handle create_obj_near(int po_idx, int quantity, Coord c)
{
Offset delta;
- Coord nc;
+ Coord nc = c;
int panic_count = 200;
- while ((lvl.obj_at(c) != NO_OBJ) && (panic_count > 0))
+ while ((terrain_blocks_items(lvl.terrain_at(nc)) || (lvl.obj_at(c) != NO_OBJ)) && (panic_count > 0))
{
do
{
delta = random_step();
nc = c + delta;
}
- while (terrain_blocks_beings(lvl.terrain_at(nc)));
+ while (terrain_blocks_items(lvl.terrain_at(nc)));
c = nc;
--panic_count;
}
{
i = asprintf(buf, "1 %s", poptr->name);
}
- else if ((poptr->poclass == POCLASS_WEAPON) ||
- (poptr->poclass == POCLASS_ARMOUR))
+ else if (poptr->flags[0] & POF_DAMAGEABLE)
{
i = asprintf(buf, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
}
{
snprintf(buf, len, "1 %s", poptr->name);
}
- else if ((poptr->poclass == POCLASS_WEAPON) ||
- (poptr->poclass == POCLASS_ARMOUR))
+ else if (poptr->flags[0] & POF_DAMAGEABLE)
{
snprintf(buf, len, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR);
}
Action_cost drop_obj(int inv_idx)
{
Obj *optr;
+ Terrain t = lvl.terrain_at(u.pos);
+ if (terrain_props[t].flags & TFLAG_portal)
+ {
+ notify_drop_blocked();
+ return Cost_none;
+ }
optr = obj_snapv(u.inventory[inv_idx]);
if (lvl.obj_at(u.pos) == NO_OBJ)
{
#include "display.hh"
#endif
-#ifndef NOTIFY_HH
-#include "notify.hh"
-#endif
-
#ifndef MAP_HH
#include "map.hh"
#endif
extern int game_tick;
extern bool wizard_mode;
Action_cost do_player_action(Action *act);
-void new_game(char const *name);
+void new_game(char const *name, Role_id role);
void main_loop(void);
/* XXX misc.c data and funcs */
extern void load_config(void);
extern void wrapped_system(char const *cmd);
extern void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *stream);
-extern int load_game(void);
+extern void load_game(void);
extern void save_game(void);
+#ifndef NOTIFY_HH
+#include "notify.hh"
+#endif
+
#endif
/* obumbrata.hh */
#include "pobj_id.hh"
-#define POBJ_FLAG_WORDS 1
+#define POBJ_FLAG_WORDS 2
// POF field 0
#define POF_NOTIFY_EQUIP 0x00000001u // item has special messages when changing equip status
/* Yes, DRESS and MELEE_WEAPON have the same value. This is OK because
* only an ARMOUR can possibly be a DRESS, and only a WEAPON can possibly be
* a MELEE_WEAPON. */
-#define POF_DRESS 0x00010000u
-#define POF_MELEE_WEAPON 0x00010000u
-#define POF_RANGED_WEAPON 0x00020000u
+#define POF_DRESS 0x00010000u
+#define POF_MELEE_WEAPON 0x00010000u
+#define POF_RANGED_WEAPON 0x00020000u
+
+// POF field 1
+#define POF_RES_FIRE 0x00000001u
+#define POF_RES_COLD 0x00000002u
+#define POF_RES_ELEC 0x00000003u
+#define POF_RES_NECRO 0x00000004u
+#define POF_RES_POISON 0x00000005u
+#define POF_RES_MASK 0x000000ffu
+#define POF_DMG_FIRE 0x00000100u
+#define POF_DMG_COLD 0x00000200u
+#define POF_DMG_ELEC 0x00000300u
+#define POF_DMG_NECRO 0x00000400u
+#define POF_DMG_POISON 0x00000500u
+#define POF_DMG_MASK 0x0000ff00u
+#define POF_PASS_WATER 0x00010000u
+#define POF_FLIGHT 0x00020000u
+#define POF_PROTECTIVE 0x00100000u // all damage from enemy attacks halved
/*! \brief The 'permanent object' database */
class Permobj {
};
#define NO_POBJ (-1)
-extern Permobj permobjs[NUM_OF_PERMOBJS];
+extern const int NUM_OF_PERMOBJS;
+extern Permobj permobjs[];
extern bool po_is_stackable(int po);
#endif
/* permobj.hh */
-// vim:cindent
-
+// vim:cindent:expandtab
int speed; /* 0 = slow; 1 = normal; 2 = quick */
uint32_t flags[PERMON_FLAG_FIELDS]; /* resistances, AI settings, etc. */
};
-extern struct Permon permons[NUM_OF_PERMONS];
+extern struct Permon permons[];
+extern int const NUM_OF_PERMONS;
#define NO_ATK (-1)
#define NO_PMON (-1)
inline bool pmon_suppurates(int pm) { return permons[pm].flags[1] & PMF_CONTAINS_PUS; }
inline bool pmon_explodes(int pm) { return permons[pm].flags[1] & PMF_BURSTS_ON_DEATH; }
inline bool pmon_leaves_corpse(int pm) { return permons[pm].flags[1] & PMF_LEAVES_CORPSE; }
+Decal_tag pmon_fluid_decal(int pm);
#endif
#include "obumbrata.hh"
+enum Skill_id
+{
+ Skill_power_attack,
+ /* Princess skills */
+ Skill_artificer,
+ Skill_flying_leap,
+ /* Demon Hunter skills */
+ Skill_charge,
+ Skill_blade_dance,
+ Skill_demon_slayer,
+ /* Thanatophile skills */
+ Skill_deaths_grace,
+ Skill_deaths_vengeance
+};
+
+#define NO_SKILL (-1)
+#define LAST_SKILL (Skill_deaths_vengeance)
+#define NUM_SKILLS (1 + LAST_SKILL)
+
+/*! \brief Skill descriptor */
+struct Skill_desc
+{
+ char const *name;
+ char const *description;
+};
+
+extern Skill_desc skill_props[NUM_SKILLS];
+
/*! \brief Internal representation of the player character
*/
#define INVENTORY_SIZE 19
Mon_handle mh; //!< Handle for monster representing the player.
Mon *mptr; //!< Pointer for monster representing the player.
Coord pos; //!< Position within current dungeon level.
+ Role_id role; //!< Starting "job"; hypothetically affects gains
int body; //!< Combined stamina and strength stat.
int bdam; //!< Current level of temporary Body drain
int agility; //!< Combined accuracy and avoidance stat.
extern Player u;
-typedef Action_cost (*deed_func)(Action const *act);
+typedef Action_cost (*Deed_func)(Action const *act);
+
+extern Deed_func deed_funcs[NUM_COMMANDS];
-extern deed_func deed_funcs[NUM_COMMANDS];
+typedef void (*Role_inventory_func)(Player *p);
+extern Role_inventory_func role_inventories[NUM_ROLES];
-void u_init(char const *name);
+void u_init(char const *name, Role_id role);
void write_char_dump(void);
int do_death(Death d, char const *what);
void heal_u(int amount, int boost, int loud);
bool wielding_ranged_weapon(void);
void player_cleanup(void);
+
+typedef bool (*Action_rewriter_phase1_func)(Action *revisable_action);
+extern Action_rewriter_phase1_func action_rewrite_phase1[NUM_COMMANDS];
+extern Combo_phase action_default_combophase[NUM_COMMANDS];
+
+typedef Combo_state (*Combo_detector)(std::deque<Action>* action_list);
+typedef bool (*Combo_avail_func)(Player const *player);
+
+struct Combo_entry
+{
+ char const *name;
+ Game_cmd first_step; //!< if this move isn't present, don't call detector()
+ Combo_avail_func avail;
+ Combo_detector detector;
+};
+
+extern Combo_entry combo_entries[];
+
+/*! \brief ID numbers for skills */
#endif
/* player.hh */
return !!(permons[pm].flags[0] & PMF_ETHEREAL);
}
+Decal_tag pmon_fluid_decal(int pm)
+{
+ if (permons[pm].flags[1] & PMF_CONTAINS_BLOOD)
+ {
+ return Decal_blood;
+ }
+ if (permons[pm].flags[1] & PMF_CONTAINS_PUS)
+ {
+ return Decal_pus;
+ }
+ return NO_DECAL;
+}
+
/* pmon2.c */
// vim:cindent
open(HEADERFILE, ">", "pmon_id.hh") or die "pmon_comp: could not open pmon_id.hh for write: $!";
open(SOURCEFILE, ">", "permons.cc") or die "pmon_comp: could not open permons.cc for write: $!";
print HEADERFILE "// pmon_id.hh\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname\n// then use pmon_comp to regenerate this file and permons.cc\n#pragma once\nenum Pmon_id {\n";
-print SOURCEFILE "// permons.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pmon_comp to\n// regenreate this file and pmon_id.hh\n#include \"core.hh\"\n#include \"permon.hh\"\nPermon permons[NUM_OF_PERMONS] = {\n";
+print SOURCEFILE "// permons.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pmon_comp to\n// regenreate this file and pmon_id.hh\n#include \"core.hh\"\n#include \"permon.hh\"\nPermon permons[] = {\n";
my $phref;
my $i;
my $total_mons = 0;
-my @firsts;
my $tagname;
for ($i = 0; $i <= $#monsters; ++$i, ++$total_mons)
printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", %s, %s, Gcol_%s, %d, %d, %d, %d, %d, %d, %d, DT_%s, \"%s\", %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{rarity}, $phref->{power}, $phref->{hp}, $phref->{mtohit}, $phref->{rtohit}, $phref->{mdam}, $phref->{rdam}, $phref->{rdtyp}, $phref->{shootverb}, $phref->{defence}, $phref->{exp}, $phref->{speed}, flag_string($phref->{flags});
}
-print SOURCEFILE "};\n// permons.cc\n";
-printf HEADERFILE "};\n".join('', @firsts)."\n#define NUM_OF_PERMONS %d\n\n// pmon_id.hh\n", $total_mons;
+printf SOURCEFILE "};\nconst int NUM_OF_PERMONS = %d;\n// permons.cc\n", $total_mons;
+printf HEADERFILE "};\n\n// pmon_id.hh\n";
close(SOURCEFILE);
close(HEADERFILE);
our @food;
our @carrion;
+our $max_flag_index = 1;
our %flag_indices =
(
'NOTIFY_EQUIP' => 0,
'DRESS' => 0,
'RANGED_WEAPON' => 0,
'MELEE_WEAPON' => 0,
+ 'RES_FIRE' => 1,
+ 'RES_COLD' => 1,
+ 'RES_ELEC' => 1,
+ 'RES_NECRO' => 1,
+ 'RES_POISON' => 1,
+ 'DMG_FIRE' => 1,
+ 'DMG_COLD' => 1,
+ 'DMG_ELEC' => 1,
+ 'DMG_NECRO' => 1,
+ 'DMG_POISON' => 1,
+ 'PASS_WATER' => 1,
+ 'FLIGHT' => 1,
+ 'PROTECTIVE' => 1
);
else
{
my $name;
+ $#flag_fields = $max_flag_index;
+ for (my $i = 0; $i <= $max_flag_index; ++$i)
+ {
+ $flag_fields[$i] = "0 ";
+ }
for $name (@$aref)
{
die("Attempt to generate a flag string containing an undefined flag $name!") if !exists($flag_indices{$name});
my $idx = $flag_indices{$name};
- $#flag_fields = $idx if ($idx > $#flag_fields);
- if (!defined($flag_fields[$idx]))
- {
- $flag_fields[$idx] = "0 ";
- }
$flag_fields[$idx] .= "| POF_$name ";
}
}
open(HEADERFILE, ">", "pobj_id.hh") or die "pobj_comp: could not open pobj_id.hh for write: $!";
open(SOURCEFILE, ">", "permobj.cc") or die "pobj_comp: could not open permobj.cc for write: $!";
print HEADERFILE "// pobj_id.hh\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname\n// then use pobj_comp to regenerate this file and permobj.cc\n#pragma once\nenum Pobj_id {\n";
-print SOURCEFILE "// permobj.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pobj_comp to\n// regenreate this file and pobj_id.hh\n#include \"core.hh\"\n#include \"permobj.hh\"\nPermobj permobjs[NUM_OF_PERMOBJS] = {\n";
+print SOURCEFILE "// permobj.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pobj_comp to\n// regenreate this file and pobj_id.hh\n#include \"core.hh\"\n#include \"permobj.hh\"\nPermobj permobjs[] = {\n";
my $phref;
my $i;
my $total_objs = 0;
print HEADERFILE ",\n";
}
print HEADERFILE " ${tagname}";
- printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_ARMOUR, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+ printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_ARMOUR, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
}
if (defined($tagname))
{
print HEADERFILE ",\n";
}
print HEADERFILE " ${tagname}";
- printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_FOOD, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+ printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_FOOD, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
}
if (defined($tagname))
{
print HEADERFILE ",\n";
}
print HEADERFILE " ${tagname}";
- printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_SCROLL, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+ printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_SCROLL, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
}
if (defined($tagname))
{
print HEADERFILE ",\n";
}
print HEADERFILE " ${tagname}";
- printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_POTION, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+ printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_POTION, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
}
if (defined($tagname))
{
print HEADERFILE ",\n";
}
print HEADERFILE " ${tagname}";
- printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_RING, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+ printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_RING, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
}
if (defined($tagname))
{
print HEADERFILE ",\n";
}
print HEADERFILE " ${tagname}";
- printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_CARRION, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
+ printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_CARRION, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags});
}
if (defined($tagname))
{
undef $tagname;
}
-print SOURCEFILE "};\n// permobj.cc\n";
-printf HEADERFILE "};\n".join('', @firsts)."\n#define NUM_OF_PERMOBJS %d\n\n// pobj_id.hh\n", $total_objs;
+printf SOURCEFILE "};\nconst int NUM_OF_PERMOBJS = %d;\n// permobj.cc\n", ${total_objs};
+print HEADERFILE "};\n".join('', @firsts)."\n\n// pobj_id.hh\n";
close(SOURCEFILE);
close(HEADERFILE);
--- /dev/null
+/*! \file role.cc
+ * \brief Per-role layer-character initialization/advancement
+ */
+
+/*
+ * Copyright 2014 Martin Read
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "obumbrata.hh"
+#include "combat.hh"
+#include "objects.hh"
+#include <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <deque>
+
+static void princess_inventory(Player *p)
+{
+ p->inventory[0] = create_obj(PO_DAGGER, 1, true, Nowhere);
+ p->inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere);
+ p->inventory[2] = create_obj(PO_BATTLE_BALLGOWN, 1, true, Nowhere);
+ p->weapon = p->inventory[0];
+ p->ring = NO_OBJ;
+ p->armour = p->inventory[2];
+}
+
+static void demonhunter_inventory(Player *p)
+{
+ p->inventory[0] = create_obj(PO_MACE, 1, true, Nowhere);
+ p->inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere);
+ p->inventory[2] = create_obj(PO_LEATHER_ARMOUR, 1, true, Nowhere);
+ p->weapon = p->inventory[0];
+ p->ring = NO_OBJ;
+ p->armour = p->inventory[2];
+}
+
+static void thanatophile_inventory(Player *p)
+{
+ p->inventory[0] = create_obj(PO_MACE, 1, true, Nowhere);
+ p->inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere);
+ p->inventory[2] = create_obj(PO_LEATHER_ARMOUR, 1, true, Nowhere);
+ p->weapon = p->inventory[0];
+ p->ring = NO_OBJ;
+ p->armour = p->inventory[2];
+}
+
+Role_inventory_func role_inventories[NUM_ROLES] =
+{
+ princess_inventory,
+ demonhunter_inventory,
+ thanatophile_inventory
+};
+
+/* role.cc */
+// vim:cindent:expandtab
--- /dev/null
+/*! \file skills.cc
+ * \brief Player-character skill data
+ */
+
+/*
+ * Copyright © 2014 Martin Read
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "obumbrata.hh"
+#include "combat.hh"
+#include "objects.hh"
+#include "player.hh"
+#include <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <deque>
+
+/*! \brief Descriptions of the player character's possible skills */
+Skill_desc skill_props[NUM_SKILLS] =
+{
+ {
+ "Power Attack",
+ ("On the turn after you use the 'wait' command, any melee attack you "
+ "make will hit automatically and do 75 percent more damage than "
+ "normal."),
+ },
+ {
+ "Flying Leap",
+ ("While wearing a dress, robe, or leather armour, you can jump across "
+ "any one-square environmental hazard (lava, chasms, etc.) that has "
+ "clear space on the other side by attempting to move onto it.");
+ },
+ {
+ "Charge",
+ ("If you move in the same direction on two (or more) consecutive "
+ "turns, any melee attack you make in that direction on the "
+ "immediately following turn will hit automatically and do more "
+ "damage than usual."),
+ },
+ {
+ "Blade Dance",
+ ("While wielding a sword or dagger, if you move diagonally past an "
+ "enemy, you will make a free melee attack against that enemy; this "
+ "attack will have normal accuracy and damage. Moving diagonally "
+ "between two enemies will randomly select one of them to be the "
+ "victim of this attack."),
+ },
+ {
+ "Demon Slayer",
+ ("Your instincts for dealing with demonkind have developed to the "
+ "point that your melee attacks do double damage against demons."),
+ },
+ {
+ "Death's Grace",
+ ("Beloved Death has granted you partial protection from the malign "
+ "arts of necromancers. You take greatly reduced damage from "
+ "necromantic magic."),
+ },
+ {
+ "Death's Vengeance",
+ ("Your melee attacks are imbued with the blessing of beloved Death, "
+ "causing them to do double damage against the undead."),
+ },
+};
+
+/* skills.cc */
+// vim:cindent:expandtab
#include <stdio.h>
#include <deque>
-bool action_rewrite(Action const *act, Action *revised_act);
struct Player u;
bool wielding_ranged_weapon(void)
}
return player_attack(delta);
}
- switch (lvl.terrain_at(c))
+ Terrain t = lvl.terrain_at(c);
+ if (terrain_blocks_beings(t))
{
- case WALL:
- case MASONRY_WALL:
- case AMETHYST_WALL:
- case IRON_WALL:
- case SKIN_WALL:
- case BONE_WALL:
notify_cant_go();
return Cost_none;
- case FLOOR:
- case AMETHYST_FLOOR:
- case IRON_FLOOR:
- case SKIN_FLOOR:
- case BONE_FLOOR:
- case DOOR:
- case STAIRS_UP:
- case STAIRS_DOWN:
- case PORTAL_LANDING:
- case PORTAL_ONWARD:
- case ALTAR:
- reloc_player(c);
- return Cost_std;
- case LAVA:
+ }
+ else if (terrain_is_hot(t))
+ {
if (u.resistances[DT_FIRE])
{
- if (lvl.terrain_at(u.pos) != LAVA)
+ if (!terrain_is_hot(lvl.terrain_at(u.pos)))
{
- notify_start_lavawalk();
+ notify_start_hotwalk(t);
}
- reloc_player(c);
- return Cost_std;
}
else
{
- notify_blocked_lava();
+ notify_blocked_hot(t);
return Cost_none;
}
- case WATER:
+ }
+ else if (terrain_drowns(t))
+ {
if (u.ring != NO_OBJ)
{
Obj const *ring = obj_snap(u.ring);
if (ring->po_ref == PO_FROST_RING)
{
- if (lvl.terrain_at(u.pos) != WATER)
+ if (!terrain_drowns(lvl.terrain_at(u.pos)))
{
- notify_start_waterwalk();
+ notify_start_waterwalk(t);
}
}
- reloc_player(c);
- return Cost_std;
}
else
{
- notify_blocked_water();
+ notify_blocked_water(t);
return Cost_none;
}
}
- return Cost_none;
+ else if (terrain_gapes(t))
+ {
+ notify_cant_go();
+ return Cost_none;
+ }
+ reloc_player(c);
+ return Cost_std;
}
void reloc_player(Coord c)
{
+ Terrain t = lvl.terrain_at(c);
Coord oc = u.pos;
u.pos = c;
look_around_you();
+ if (terrain_props[t].flags & TFLAG_portal)
+ {
+ notify_portal_underfoot();
+ }
if (lvl.obj_at(c) != NO_OBJ)
{
notify_obj_at(c);
return really;
}
-void u_init(char const *name)
+void u_init(char const *name, Role_id role)
{
if (!name || !*name)
{
{
u.inventory[i] = NO_OBJ;
}
- u.inventory[0] = create_obj(PO_DAGGER, 1, true, Nowhere);
- u.inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere);
- u.inventory[2] = create_obj(PO_BATTLE_BALLGOWN, 1, true, Nowhere);
- u.weapon = u.inventory[0];
- u.ring = NO_OBJ;
- u.armour = u.inventory[2];
+ role_inventories[role](&u);
recalc_defence();
}
/*! \brief Action history for combo detection */
std::deque<Action> past_actions;
-/*! \brief Constants used in combo detection logic */
-enum Combo_phase
-{
- Combo_invalid,
- Combo_valid,
- Combo_finisher
-};
-
/* \brief Process action combos
*
* This function is responsible for detecting whether the most recently
* \return true if revised_act was written to; false otherwise
*/
-bool action_rewrite(Action const *act, Action *revised_act)
+static bool action_rewrite(Action const *act, Action *revised_act)
{
Coord c = u.pos;
- Mon_handle mon = NO_MON;
- Offset o;
Action tmp_act = *act;
Combo_phase p;
bool rewrite_flag = false;
- switch (tmp_act.cmd)
+ if (tmp_act.cmd == REJECTED_ACTION)
{
- case STAND_STILL:
- p = Combo_valid;
- break;
- case ATTACK:
- o.y = tmp_act.details[0];
- o.x = tmp_act.details[1];
- c = u.pos + o;
- if (!lvl.in_bounds(c))
- {
- debug_move_oob(c); // TODO notify_attack_oob()
- tmp_act.cmd = REJECTED_ACTION;
- rewrite_flag = true;
- p = Combo_invalid;
- break;
- }
- mon = lvl.mon_at(c);
- p = Combo_finisher;
- break;
- case WALK:
- o.y = tmp_act.details[0];
- o.x = tmp_act.details[1];
- c = u.pos + o;
- if (!lvl.in_bounds(c))
- {
- debug_move_oob(c);
- tmp_act.cmd = REJECTED_ACTION;
- rewrite_flag = true;
- p = Combo_invalid;
- }
- else if (lvl.mon_at(c) != NO_MON)
- {
- mon = lvl.mon_at(c);
- tmp_act.cmd = ATTACK;
- rewrite_flag = true;
- p = Combo_finisher;
- }
- else
- {
- p = Combo_valid;
- }
- break;
- default:
- rewrite_flag = false;
- p = Combo_invalid;
- break;
+ return false;
}
+ if (action_rewrite_phase1[tmp_act.cmd] != nullptr)
+ {
+ rewrite_flag = action_rewrite_phase1[tmp_act.cmd](&tmp_act);
+ }
+ p = action_default_combophase[tmp_act.cmd];
switch (p)
{
case Combo_invalid:
+ past_actions.clear();
break;
case Combo_valid:
- if (past_actions.empty())
- {
- past_actions.push_front(tmp_act);
- }
- else
+ case Combo_finisher:
{
- switch (tmp_act.cmd)
+ Combo_state cs = Combo_state_none;
+ Combo_state tmp_cs = Combo_state_none;
+ past_actions.push_back(tmp_act);
+ do
{
- case STAND_STILL:
- /* For now, no combo opens with more than one stand still,
- * and no combo starts with something else then chains through
- * a stand still */
- if (past_actions.front().cmd != STAND_STILL)
+ for (int i = 0; combo_entries[i].first_step != REJECTED_ACTION; ++i)
{
- past_actions.clear();
- past_actions.push_front(tmp_act);
+ if (past_actions[0].cmd == combo_entries[i].first_step)
+ {
+ if (combo_entries[i].avail(&u))
+ {
+ tmp_cs = combo_entries[i].detector(&past_actions);
+ if (tmp_cs == Combo_state_complete)
+ {
+ tmp_act = past_actions[0];
+ rewrite_flag = true;
+ cs = Combo_state_complete;
+ break;
+ }
+ if (tmp_cs == Combo_state_partial)
+ {
+ cs = Combo_state_partial;
+ }
+ }
+ }
}
- break;
- case WALK:
- /* For now, no combo chains through a WALK, but the charge
- * combo will when I have the mental effort to write it. */
- past_actions.clear();
- break;
- default:
- DEBUG_CONTROL_FLOW();
- break;
- }
- }
- break;
- case Combo_finisher:
- if (!past_actions.empty())
- {
- switch (past_actions.front().cmd)
- {
- case STAND_STILL:
- if ((empty_handed() || wielding_melee_weapon()) &&
- (mon != NO_MON))
+ /* If we didn't even get a partial match, drop the oldest
+ * action and try again. */
+ if (cs == Combo_state_none)
{
- rewrite_flag = true;
- tmp_act.cmd = CMD_POWER_ATTACK;
+ past_actions.pop_front();
}
- break;
- case WALK:
- break;
- default:
- DEBUG_CONTROL_FLOW();
- break;
+ } while ((cs == Combo_state_none) && (past_actions.size() >= 1));
+ if ((p == Combo_finisher) && (cs != Combo_state_complete))
+ {
+ past_actions.clear();
}
}
- past_actions.clear();
break;
}
if (rewrite_flag)
/*! \brief Process a player action.
*
- * \todo Make CMD_POWER_ATTACK do something special
+ * \todo Make COMBO_POWATK do something special
*/
Action_cost do_player_action(Action *act)
{
int i;
memset(u.sympathy, '\0', sizeof u.sympathy);
memset(u.resistances, '\0', sizeof u.resistances);
+ u.mptr = nullptr;
+ u.mh = NO_MON;
u.experience = u.level = 0;
u.body = u.agility = u.bdam = u.adam = 0;
memset(u.name, '\0', sizeof u.name);