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