From: Martin Read Date: Mon, 10 Mar 2014 17:32:22 +0000 (+0000) Subject: Renaming objects.cc to obj1.cc X-Git-Tag: 1.0.0~10 X-Git-Url: http://git.blackswordsonics.com/?a=commitdiff_plain;h=997c36b263a20e159371b2f239ff5e1b565a122f;p=obumbrata Renaming objects.cc to obj1.cc --- diff --git a/obj1.cc b/obj1.cc new file mode 100644 index 0000000..0d5c014 --- /dev/null +++ b/obj1.cc @@ -0,0 +1,915 @@ +/* \file objects.cc + * \brief Object handling for Obumbrata et Velata + */ + +/* Copyright 2005-2013 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 OBJECTS_CC +#include "obumbrata.hh" +#include "objects.hh" +#include "monsters.hh" + +#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" +}; + +/*! \brief Read a magic scroll */ +Action_cost read_scroll(Obj_handle obj) +{ + Obj *optr = obj_snapv(obj); + switch (optr->po_ref) + { + 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"); + } +#if 0 + // TODO iterate over visible monsters + for (i = 0; i < MONSTERS_IN_PLAY; ++i) + { + if (mon_visible(i)) + { + if (!pmon_resists_fire(monsters[i].pm_ref)) + { + notify_fireitem_hit(i); + damage_mon(i, dice(4, 10), true); + } + } + } +#endif + 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) + { + u.leadfoot = 0; + notify_leadfoot_recovered(); + } + recalc_defence(); + break; + default: + debug_read_non_scroll(); + return Cost_none; + } + consume_obj(obj); + return Cost_std; +} + +bool consume_obj(Obj_handle obj) +{ + int i; + Obj *optr = obj_snapv(obj); + optr->quan--; + if (optr->quan == 0) + { + optr->flags &= ~OF_USED; + if (optr->flags & OF_WITH_YOU) + { + if (obj == u.armour) + { + u.armour = NO_OBJ; + recalc_defence(); + notify_armour_broke(obj); + } + else if (obj == u.weapon) + { + u.weapon = NO_OBJ; + recalc_defence(); + notify_weapon_broke(obj); + } + else if (obj == u.ring) + { + u.ring = NO_OBJ; + recalc_defence(); + } + for (i = 0; i < 19; i++) + { + if (u.inventory[i] == obj) + { + u.inventory[i] = NO_OBJ; + break; + } + } + } + return true; + } + return false; +} + +/*! \brief Consume a food */ +Action_cost eat_food(Obj_handle obj) +{ + Obj *optr = obj_snapv(obj); + bool ravenous = (u.food < 0); + u.food += 1500; + if (permobjs[optr->po_ref].poclass != POCLASS_FOOD) + { + debug_eat_non_food(obj); + return Cost_none; + } + if (optr->po_ref == PO_DEVIL_SPLEEN) + { + notify_ingest_spleen(); + if (zero_die(2)) + { + gain_body(1); + } + else + { + gain_agility(1); + } + // TODO add more tracking state to this item so we don't have to randomize the fell power choice + ++u.sympathy[zero_die(TOTAL_FELL_POWERS)]; + } + else + { + notify_eat_food(ravenous); + } + consume_obj(obj); + return Cost_std; +} + +/*! \brief Effect of quaffing a body potion */ +static void body_potion_quaff(void) +{ + gain_body(1); +} + +/*! \brief Effect of quaffing a agility potion */ +static void agility_potion_quaff(void) +{ + gain_agility(1); +} + +/*! \brief Effect of quaffing a healing potion */ +static void healing_potion_quaff(void) +{ + 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(void) +{ + 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(); + } +} + +struct Potion_table_entry +{ + int pobj; + void (*quaff_func)(void); +}; + +static Potion_table_entry quaff_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 }, +}; + +/*! \brief Consume a potion */ +Action_cost quaff_potion(Obj_handle obj) +{ + Obj *optr = obj_snapv(obj); + int i; + for (i = 0; quaff_table[i].pobj != NO_POBJ; ++i) + { + if (quaff_table[i].pobj == optr->po_ref) + { + quaff_table[i].quaff_func(); + consume_obj(obj); + return Cost_std; + } + } + debug_quaff_non_potion(); + return Cost_none; +} + +void flavours_init(void) +{ + int colour_choices[10]; + 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;) + { + 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_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]; +} + +Obj_handle first_free_obj_handle = 1u; + +Obj_handle get_first_free_obj(void) +{ + if (first_free_obj_handle == 0u) + { + return NO_OBJ; + } + return first_free_obj_handle++; +} + +Obj_handle create_obj_class_near(enum poclass_num po_class, int quantity, bool with_you, Coord c) +{ + int po_idx; + int tryct; + for (tryct = 0; tryct < 200; tryct++) + { + switch (po_class) + { + case POCLASS_POTION: + po_idx = inc_flat(PO_FIRST_POTION, PO_LAST_POTION); + break; + case POCLASS_SCROLL: + po_idx = inc_flat(PO_FIRST_SCROLL, PO_LAST_SCROLL); + break; + case POCLASS_RING: + po_idx = inc_flat(PO_FIRST_RING, PO_LAST_RING); + break; + default: + /* No getting armour/weapons by class... yet. */ + return NO_OBJ; + } + if (zero_die(100) < permobjs[po_idx].rarity) + { + continue; + } + break; + } + return (with_you ? create_obj(po_idx, quantity, with_you, c) : create_obj_near(po_idx, quantity, c)); +} + +int get_random_pobj(void) +{ + int tryct; + int po_idx; + for (tryct = 0; tryct < 200; tryct++) + { + po_idx = zero_die(NUM_OF_PERMOBJS); + if (zero_die(100) < permobjs[po_idx].rarity) + { + po_idx = NO_POBJ; + continue; + } + if (depth < permobjs[po_idx].depth) + { + po_idx = NO_POBJ; + continue; + } + break; + } + return po_idx; +} + +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) +{ + Offset delta; + Coord nc; + int panic_count = 200; + while ((lvl.obj_at(c) != NO_OBJ) && (panic_count > 0)) + { + do + { + delta = random_step(); + nc = c + delta; + } + while (terrain_blocks_beings(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; + } + if (po_idx == NO_POBJ) + { + po_idx = get_random_pobj(); + if (po_idx == NO_POBJ) + { + debug_pobj_select_failed(); + return NO_OBJ; + } + } + 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: + o.durability = OBJ_MAX_DUR; + break; + default: + break; + } + objects[obj] = o; + if (!(o.flags & OF_WITH_YOU)) + { + lvl.set_obj_at(c, obj); + notify_new_obj_at(c, obj); + } + return obj; +} + +void asprint_obj_name(char **buf, Obj_handle obj) +{ + Obj *optr; + Permobj *poptr; + int i; + optr = obj_snapv(obj); + poptr = permobjs + optr->po_ref; + if (optr->quan > 1) + { + i = asprintf(buf, "%d %s", optr->quan, poptr->plural); + } + else if (po_is_stackable(optr->po_ref)) + { + i = asprintf(buf, "1 %s", poptr->name); + } + else if ((poptr->poclass == POCLASS_WEAPON) || + (poptr->poclass == POCLASS_ARMOUR)) + { + 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 + { + i = asprintf(buf, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name); + } + if (i == -1) + { + // TODO wibble + } + return; +} + +void sprint_obj_name(char *buf, Obj_handle obj, int len) +{ + Obj *optr; + Permobj *poptr; + optr = obj_snapv(obj); + poptr = permobjs + optr->po_ref; + if (optr->quan > 1) + { + 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 ((poptr->poclass == POCLASS_WEAPON) || + (poptr->poclass == POCLASS_ARMOUR)) + { + snprintf(buf, len, "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]; + snprintf(buf, len, "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); + } +} + +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 = obj_snapv(u.inventory[inv_idx]); + if (lvl.obj_at(u.pos) == NO_OBJ) + { + optr->pos = u.pos; + lvl.set_obj_at(u.pos, u.inventory[inv_idx]); + if (u.weapon == u.inventory[inv_idx]) + { + u.weapon = NO_OBJ; + } + u.inventory[inv_idx] = NO_OBJ; + optr->flags &= ~OF_WITH_YOU; + notify_drop_item(lvl.obj_at(u.pos)); + return Cost_std; + } + else + { + notify_drop_blocked(); + return Cost_none; + } +} + +bool po_is_stackable(int po) +{ + switch (permobjs[po].poclass) + { + default: + return false; + case POCLASS_POTION: + case POCLASS_SCROLL: + case POCLASS_FOOD: + return true; + } +} + +void attempt_pickup(void) +{ + int i; + int stackable; + 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++) + { + invptr = obj_snapv(u.inventory[i]); + if (invptr && (invptr->po_ref == optr->po_ref)) + { + invptr->quan += optr->quan; + optr->flags &= ~OF_USED; + lvl.set_obj_at(u.pos, NO_OBJ); + notify_get_item(oh, i); + return; + } + } + } + for (i = 0; i < 19; i++) + { + if (u.inventory[i] == NO_OBJ) + { + break; + } + } + if (i == 19) + { + notify_pack_full(); + return; + } + u.inventory[i] = lvl.obj_at(u.pos); + lvl.set_obj_at(u.pos, NO_OBJ); + optr->flags |= OF_WITH_YOU; + optr->pos = Nowhere; + notify_get_item(NO_OBJ, i); +} + +void break_reaction(Obj_handle obj) +{ + switch (objects[obj].po_ref) + { + case PO_TORMENTORS_LASH: + if (u.food < 500) + { + int shortfall = (u.food < 0) ? 500 : 500 - u.food; + int damage = (shortfall + 24) / 25; + u.food = (u.food < 0) ? u.food : 0; + notify_lash_activation(3); + damage_u(damage, DEATH_LASH, ""); + } + else + { + u.food -= 500; + objects[obj].durability = 100; + notify_lash_activation(1); + } + break; + + default: + if (permobjs[objects[obj].po_ref].flags[0] & POF_DRESS) + { + objects[obj].durability = 50 + zero_die(51); + objects[obj].po_ref = PO_RAGGED_SHIFT; + notify_dress_shredded(); + recalc_defence(); + } + else + { + debug_unimplemented_break_reaction(objects[obj].po_ref); + } + break; + } +} + +void damage_obj(Obj_handle obj) +{ + /* Only weapons and armour have non-zero durability. */ + if (objects[obj].durability == 0) + { + /* Break the object. Weapons and armour don't stack. */ + consume_obj(obj); + } + else + { + objects[obj].durability--; + if ((objects[obj].durability == 0) && (permobjs[objects[obj].po_ref].flags[0] & POF_BREAK_REACT)) + { + break_reaction(obj); + } + } +} + +int evasion_penalty(Obj_handle obj) +{ + if (permobjs[objects[obj].po_ref].poclass == POCLASS_ARMOUR) + { + return permobjs[objects[obj].po_ref].power2; + } + return 100; +} + +Action_cost magic_ring(void) +{ + Obj *optr = obj_snapv(u.ring); + switch (optr->po_ref) + { + case PO_TELEPORT_RING: + if (u.food >= 50) + { + u.food -= 50; + notify_telering_activation(1); + teleport_u(); + return Cost_std; + } + else + { + notify_telering_activation(0); + } + return Cost_none; + default: + if (permobjs[optr->po_ref].flags[0] & POF_ACTIVATABLE) + { + debug_unimplemented_activation(optr->po_ref); + } + else + { + notify_magic_powerless_ring(); + } + return Cost_none; + } +} + +Action_cost emanate_armour(void) +{ + Obj *optr = obj_snapv(u.armour); + switch (optr->po_ref) + { + default: + if (permobjs[optr->po_ref].flags[0] & POF_ACTIVATABLE) + { + debug_unimplemented_activation(optr->po_ref); + } + else + { + notify_emanate_powerless_armour(); + } + break; + } + return Cost_none; +} + +Action_cost zap_weapon(void) +{ + Obj *optr = obj_snapv(u.weapon); + switch (optr->po_ref) + { + case PO_STAFF_OF_FIRE: + if (u.food > 150) + { + 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 ((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) + { + Mon_handle mon; + if ((c.x < lvl.min_x()) || (c.x >= lvl.max_x())) + { + continue; + } + mon = lvl.mon_at(c); + if (mon != NO_MON) + { + Mon *mptr = mon_snapv(mon); + if (!pmon_resists_fire(mptr->pm_ref)) + { + notify_fireitem_hit(mon); + damage_mon(mon, dice(4, 10), true); + } + } + } + } + damage_obj(u.weapon); + } + else + { + notify_firestaff_activation(0); + return Cost_none; + } + return Cost_std; + default: + if (permobjs[optr->po_ref].flags[0] & POF_ACTIVATABLE) + { + debug_unimplemented_activation(optr->po_ref); + } + else + { + notify_zap_powerless_weapon(); + } + return Cost_none; + } +} + +/*! \brief Unwield the player's currently equipped weapon + * + * \todo Stickycurse? + * \todo Life-essential resistances? + */ +Action_cost player_unwield(Noisiness noisy) +{ + int saved_weapon = u.weapon; + if (u.weapon == NO_OBJ) + { + debug_unwield_nothing(); + return Cost_none; + } + u.weapon = NO_OBJ; + recalc_defence(); + notify_unwield(saved_weapon, Noise_std); + return Cost_std; +} + +Action_cost player_wield(Obj_handle obj, Noisiness noisy) +{ + if (u.weapon != NO_OBJ) + { + player_unwield(Noise_low); + } + u.weapon = obj; + notify_wield_weapon(u.weapon); + recalc_defence(); + return Cost_std; +} + +Action_cost wear_armour(Obj_handle obj) +{ + if (u.armour != NO_OBJ) + { + debug_wear_while_wearing(); + return Cost_none; + } + if (!(objects[obj].flags & OF_WITH_YOU)) + { + debug_wear_uncarried_armour(); + return Cost_none; + } + u.armour = obj; + recalc_defence(); + notify_armour_equip(u.armour); + return Cost_std; +} + +Action_cost put_on_ring(Obj_handle obj) +{ + if (u.ring != NO_OBJ) + { + debug_put_on_second_ring(); + return Cost_none; + } + if (!(objects[obj].flags & OF_WITH_YOU)) + { + debug_put_on_uncarried_ring(); + return Cost_none; + } + u.ring = obj; + notify_ring_equip(u.ring); + recalc_defence(); + return Cost_std; +} + +Action_cost remove_ring(void) +{ + int saved_ring = u.ring; + if (u.ring == NO_OBJ) + { + debug_remove_no_ring(); + return Cost_none; + } + u.ring = NO_OBJ; + recalc_defence(); + notify_ring_unequip(saved_ring); + return Cost_std; +} + +Pass_fail ring_removal_unsafe(Noisiness noisy) +{ + if ((lvl.terrain_at(u.pos) == LAVA) && (u.resistances[DT_FIRE] == RESIST_RING)) + { + if (noisy != Noise_silent) + { + notify_lava_blocks_unequip(); + } + return You_fail; + } + else if ((objects[u.ring].po_ref == PO_FROST_RING) && (lvl.terrain_at(u.pos) == WATER)) + { + if (noisy != Noise_silent) + { + notify_water_blocks_unequip(); + } + return You_fail; + } + return You_pass; +} + +/* objects.cc */ +// vim:cindent diff --git a/objects.cc b/objects.cc deleted file mode 100644 index 0d5c014..0000000 --- a/objects.cc +++ /dev/null @@ -1,915 +0,0 @@ -/* \file objects.cc - * \brief Object handling for Obumbrata et Velata - */ - -/* Copyright 2005-2013 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 OBJECTS_CC -#include "obumbrata.hh" -#include "objects.hh" -#include "monsters.hh" - -#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" -}; - -/*! \brief Read a magic scroll */ -Action_cost read_scroll(Obj_handle obj) -{ - Obj *optr = obj_snapv(obj); - switch (optr->po_ref) - { - 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"); - } -#if 0 - // TODO iterate over visible monsters - for (i = 0; i < MONSTERS_IN_PLAY; ++i) - { - if (mon_visible(i)) - { - if (!pmon_resists_fire(monsters[i].pm_ref)) - { - notify_fireitem_hit(i); - damage_mon(i, dice(4, 10), true); - } - } - } -#endif - 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) - { - u.leadfoot = 0; - notify_leadfoot_recovered(); - } - recalc_defence(); - break; - default: - debug_read_non_scroll(); - return Cost_none; - } - consume_obj(obj); - return Cost_std; -} - -bool consume_obj(Obj_handle obj) -{ - int i; - Obj *optr = obj_snapv(obj); - optr->quan--; - if (optr->quan == 0) - { - optr->flags &= ~OF_USED; - if (optr->flags & OF_WITH_YOU) - { - if (obj == u.armour) - { - u.armour = NO_OBJ; - recalc_defence(); - notify_armour_broke(obj); - } - else if (obj == u.weapon) - { - u.weapon = NO_OBJ; - recalc_defence(); - notify_weapon_broke(obj); - } - else if (obj == u.ring) - { - u.ring = NO_OBJ; - recalc_defence(); - } - for (i = 0; i < 19; i++) - { - if (u.inventory[i] == obj) - { - u.inventory[i] = NO_OBJ; - break; - } - } - } - return true; - } - return false; -} - -/*! \brief Consume a food */ -Action_cost eat_food(Obj_handle obj) -{ - Obj *optr = obj_snapv(obj); - bool ravenous = (u.food < 0); - u.food += 1500; - if (permobjs[optr->po_ref].poclass != POCLASS_FOOD) - { - debug_eat_non_food(obj); - return Cost_none; - } - if (optr->po_ref == PO_DEVIL_SPLEEN) - { - notify_ingest_spleen(); - if (zero_die(2)) - { - gain_body(1); - } - else - { - gain_agility(1); - } - // TODO add more tracking state to this item so we don't have to randomize the fell power choice - ++u.sympathy[zero_die(TOTAL_FELL_POWERS)]; - } - else - { - notify_eat_food(ravenous); - } - consume_obj(obj); - return Cost_std; -} - -/*! \brief Effect of quaffing a body potion */ -static void body_potion_quaff(void) -{ - gain_body(1); -} - -/*! \brief Effect of quaffing a agility potion */ -static void agility_potion_quaff(void) -{ - gain_agility(1); -} - -/*! \brief Effect of quaffing a healing potion */ -static void healing_potion_quaff(void) -{ - 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(void) -{ - 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(); - } -} - -struct Potion_table_entry -{ - int pobj; - void (*quaff_func)(void); -}; - -static Potion_table_entry quaff_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 }, -}; - -/*! \brief Consume a potion */ -Action_cost quaff_potion(Obj_handle obj) -{ - Obj *optr = obj_snapv(obj); - int i; - for (i = 0; quaff_table[i].pobj != NO_POBJ; ++i) - { - if (quaff_table[i].pobj == optr->po_ref) - { - quaff_table[i].quaff_func(); - consume_obj(obj); - return Cost_std; - } - } - debug_quaff_non_potion(); - return Cost_none; -} - -void flavours_init(void) -{ - int colour_choices[10]; - 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;) - { - 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_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]; -} - -Obj_handle first_free_obj_handle = 1u; - -Obj_handle get_first_free_obj(void) -{ - if (first_free_obj_handle == 0u) - { - return NO_OBJ; - } - return first_free_obj_handle++; -} - -Obj_handle create_obj_class_near(enum poclass_num po_class, int quantity, bool with_you, Coord c) -{ - int po_idx; - int tryct; - for (tryct = 0; tryct < 200; tryct++) - { - switch (po_class) - { - case POCLASS_POTION: - po_idx = inc_flat(PO_FIRST_POTION, PO_LAST_POTION); - break; - case POCLASS_SCROLL: - po_idx = inc_flat(PO_FIRST_SCROLL, PO_LAST_SCROLL); - break; - case POCLASS_RING: - po_idx = inc_flat(PO_FIRST_RING, PO_LAST_RING); - break; - default: - /* No getting armour/weapons by class... yet. */ - return NO_OBJ; - } - if (zero_die(100) < permobjs[po_idx].rarity) - { - continue; - } - break; - } - return (with_you ? create_obj(po_idx, quantity, with_you, c) : create_obj_near(po_idx, quantity, c)); -} - -int get_random_pobj(void) -{ - int tryct; - int po_idx; - for (tryct = 0; tryct < 200; tryct++) - { - po_idx = zero_die(NUM_OF_PERMOBJS); - if (zero_die(100) < permobjs[po_idx].rarity) - { - po_idx = NO_POBJ; - continue; - } - if (depth < permobjs[po_idx].depth) - { - po_idx = NO_POBJ; - continue; - } - break; - } - return po_idx; -} - -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) -{ - Offset delta; - Coord nc; - int panic_count = 200; - while ((lvl.obj_at(c) != NO_OBJ) && (panic_count > 0)) - { - do - { - delta = random_step(); - nc = c + delta; - } - while (terrain_blocks_beings(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; - } - if (po_idx == NO_POBJ) - { - po_idx = get_random_pobj(); - if (po_idx == NO_POBJ) - { - debug_pobj_select_failed(); - return NO_OBJ; - } - } - 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: - o.durability = OBJ_MAX_DUR; - break; - default: - break; - } - objects[obj] = o; - if (!(o.flags & OF_WITH_YOU)) - { - lvl.set_obj_at(c, obj); - notify_new_obj_at(c, obj); - } - return obj; -} - -void asprint_obj_name(char **buf, Obj_handle obj) -{ - Obj *optr; - Permobj *poptr; - int i; - optr = obj_snapv(obj); - poptr = permobjs + optr->po_ref; - if (optr->quan > 1) - { - i = asprintf(buf, "%d %s", optr->quan, poptr->plural); - } - else if (po_is_stackable(optr->po_ref)) - { - i = asprintf(buf, "1 %s", poptr->name); - } - else if ((poptr->poclass == POCLASS_WEAPON) || - (poptr->poclass == POCLASS_ARMOUR)) - { - 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 - { - i = asprintf(buf, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name); - } - if (i == -1) - { - // TODO wibble - } - return; -} - -void sprint_obj_name(char *buf, Obj_handle obj, int len) -{ - Obj *optr; - Permobj *poptr; - optr = obj_snapv(obj); - poptr = permobjs + optr->po_ref; - if (optr->quan > 1) - { - 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 ((poptr->poclass == POCLASS_WEAPON) || - (poptr->poclass == POCLASS_ARMOUR)) - { - snprintf(buf, len, "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]; - snprintf(buf, len, "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); - } -} - -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 = obj_snapv(u.inventory[inv_idx]); - if (lvl.obj_at(u.pos) == NO_OBJ) - { - optr->pos = u.pos; - lvl.set_obj_at(u.pos, u.inventory[inv_idx]); - if (u.weapon == u.inventory[inv_idx]) - { - u.weapon = NO_OBJ; - } - u.inventory[inv_idx] = NO_OBJ; - optr->flags &= ~OF_WITH_YOU; - notify_drop_item(lvl.obj_at(u.pos)); - return Cost_std; - } - else - { - notify_drop_blocked(); - return Cost_none; - } -} - -bool po_is_stackable(int po) -{ - switch (permobjs[po].poclass) - { - default: - return false; - case POCLASS_POTION: - case POCLASS_SCROLL: - case POCLASS_FOOD: - return true; - } -} - -void attempt_pickup(void) -{ - int i; - int stackable; - 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++) - { - invptr = obj_snapv(u.inventory[i]); - if (invptr && (invptr->po_ref == optr->po_ref)) - { - invptr->quan += optr->quan; - optr->flags &= ~OF_USED; - lvl.set_obj_at(u.pos, NO_OBJ); - notify_get_item(oh, i); - return; - } - } - } - for (i = 0; i < 19; i++) - { - if (u.inventory[i] == NO_OBJ) - { - break; - } - } - if (i == 19) - { - notify_pack_full(); - return; - } - u.inventory[i] = lvl.obj_at(u.pos); - lvl.set_obj_at(u.pos, NO_OBJ); - optr->flags |= OF_WITH_YOU; - optr->pos = Nowhere; - notify_get_item(NO_OBJ, i); -} - -void break_reaction(Obj_handle obj) -{ - switch (objects[obj].po_ref) - { - case PO_TORMENTORS_LASH: - if (u.food < 500) - { - int shortfall = (u.food < 0) ? 500 : 500 - u.food; - int damage = (shortfall + 24) / 25; - u.food = (u.food < 0) ? u.food : 0; - notify_lash_activation(3); - damage_u(damage, DEATH_LASH, ""); - } - else - { - u.food -= 500; - objects[obj].durability = 100; - notify_lash_activation(1); - } - break; - - default: - if (permobjs[objects[obj].po_ref].flags[0] & POF_DRESS) - { - objects[obj].durability = 50 + zero_die(51); - objects[obj].po_ref = PO_RAGGED_SHIFT; - notify_dress_shredded(); - recalc_defence(); - } - else - { - debug_unimplemented_break_reaction(objects[obj].po_ref); - } - break; - } -} - -void damage_obj(Obj_handle obj) -{ - /* Only weapons and armour have non-zero durability. */ - if (objects[obj].durability == 0) - { - /* Break the object. Weapons and armour don't stack. */ - consume_obj(obj); - } - else - { - objects[obj].durability--; - if ((objects[obj].durability == 0) && (permobjs[objects[obj].po_ref].flags[0] & POF_BREAK_REACT)) - { - break_reaction(obj); - } - } -} - -int evasion_penalty(Obj_handle obj) -{ - if (permobjs[objects[obj].po_ref].poclass == POCLASS_ARMOUR) - { - return permobjs[objects[obj].po_ref].power2; - } - return 100; -} - -Action_cost magic_ring(void) -{ - Obj *optr = obj_snapv(u.ring); - switch (optr->po_ref) - { - case PO_TELEPORT_RING: - if (u.food >= 50) - { - u.food -= 50; - notify_telering_activation(1); - teleport_u(); - return Cost_std; - } - else - { - notify_telering_activation(0); - } - return Cost_none; - default: - if (permobjs[optr->po_ref].flags[0] & POF_ACTIVATABLE) - { - debug_unimplemented_activation(optr->po_ref); - } - else - { - notify_magic_powerless_ring(); - } - return Cost_none; - } -} - -Action_cost emanate_armour(void) -{ - Obj *optr = obj_snapv(u.armour); - switch (optr->po_ref) - { - default: - if (permobjs[optr->po_ref].flags[0] & POF_ACTIVATABLE) - { - debug_unimplemented_activation(optr->po_ref); - } - else - { - notify_emanate_powerless_armour(); - } - break; - } - return Cost_none; -} - -Action_cost zap_weapon(void) -{ - Obj *optr = obj_snapv(u.weapon); - switch (optr->po_ref) - { - case PO_STAFF_OF_FIRE: - if (u.food > 150) - { - 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 ((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) - { - Mon_handle mon; - if ((c.x < lvl.min_x()) || (c.x >= lvl.max_x())) - { - continue; - } - mon = lvl.mon_at(c); - if (mon != NO_MON) - { - Mon *mptr = mon_snapv(mon); - if (!pmon_resists_fire(mptr->pm_ref)) - { - notify_fireitem_hit(mon); - damage_mon(mon, dice(4, 10), true); - } - } - } - } - damage_obj(u.weapon); - } - else - { - notify_firestaff_activation(0); - return Cost_none; - } - return Cost_std; - default: - if (permobjs[optr->po_ref].flags[0] & POF_ACTIVATABLE) - { - debug_unimplemented_activation(optr->po_ref); - } - else - { - notify_zap_powerless_weapon(); - } - return Cost_none; - } -} - -/*! \brief Unwield the player's currently equipped weapon - * - * \todo Stickycurse? - * \todo Life-essential resistances? - */ -Action_cost player_unwield(Noisiness noisy) -{ - int saved_weapon = u.weapon; - if (u.weapon == NO_OBJ) - { - debug_unwield_nothing(); - return Cost_none; - } - u.weapon = NO_OBJ; - recalc_defence(); - notify_unwield(saved_weapon, Noise_std); - return Cost_std; -} - -Action_cost player_wield(Obj_handle obj, Noisiness noisy) -{ - if (u.weapon != NO_OBJ) - { - player_unwield(Noise_low); - } - u.weapon = obj; - notify_wield_weapon(u.weapon); - recalc_defence(); - return Cost_std; -} - -Action_cost wear_armour(Obj_handle obj) -{ - if (u.armour != NO_OBJ) - { - debug_wear_while_wearing(); - return Cost_none; - } - if (!(objects[obj].flags & OF_WITH_YOU)) - { - debug_wear_uncarried_armour(); - return Cost_none; - } - u.armour = obj; - recalc_defence(); - notify_armour_equip(u.armour); - return Cost_std; -} - -Action_cost put_on_ring(Obj_handle obj) -{ - if (u.ring != NO_OBJ) - { - debug_put_on_second_ring(); - return Cost_none; - } - if (!(objects[obj].flags & OF_WITH_YOU)) - { - debug_put_on_uncarried_ring(); - return Cost_none; - } - u.ring = obj; - notify_ring_equip(u.ring); - recalc_defence(); - return Cost_std; -} - -Action_cost remove_ring(void) -{ - int saved_ring = u.ring; - if (u.ring == NO_OBJ) - { - debug_remove_no_ring(); - return Cost_none; - } - u.ring = NO_OBJ; - recalc_defence(); - notify_ring_unequip(saved_ring); - return Cost_std; -} - -Pass_fail ring_removal_unsafe(Noisiness noisy) -{ - if ((lvl.terrain_at(u.pos) == LAVA) && (u.resistances[DT_FIRE] == RESIST_RING)) - { - if (noisy != Noise_silent) - { - notify_lava_blocks_unequip(); - } - return You_fail; - } - else if ((objects[u.ring].po_ref == PO_FROST_RING) && (lvl.terrain_at(u.pos) == WATER)) - { - if (noisy != Noise_silent) - { - notify_water_blocks_unequip(); - } - return You_fail; - } - return You_pass; -} - -/* objects.cc */ -// vim:cindent