From: Martin Read Date: Sun, 16 Mar 2014 20:43:44 +0000 (+0000) Subject: Merged back Obumbrata et Velata engine improvements X-Git-Url: http://git.blackswordsonics.com/?a=commitdiff_plain;h=3c2d1c5214c2edc92a20b839c61a2c904ef9aeec;p=victrix-abyssi Merged back Obumbrata et Velata engine improvements --- 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 */