From 04f5058cac8e12d3b85834bda4589239932f371a Mon Sep 17 00:00:00 2001 From: zelgadis Date: Mon, 1 Dec 2008 04:00:00 +0000 Subject: Add some chaos attacks/weapons. Missiles and launchers of chaos fire a bolt of a random type (including enchantments, which isn't so good since they don't have a visible beam). Might want to add BEAM_CHAOS and make it a beam of that. Weapons of chaos either does a random brand effect (fire, poison, etc) or a random chaos effect (which includes cloning the attack victim). The AF_CHAOS monster attack flavour either does a random monster flavour or chaos effect (same chaos effects as for weapons). The relative frequency of all the different effects/brands/flavours no doubt needs adjustment. All of this is currently only available via Xom. 10% of all common-type demons sent in by Xom will be chaos spawn (the only kind that use AF_CHAOS, and never randomly generated otherwise). All item gifts from Xom which are generated with a brand will have their brand switched to chaos (this should probably be made to happen less than 100% of the time). And finally one of Xom's bad acts is to upgrade a non-branded weapon of a nearby hostile monster to a chaos brand (this might need to be made less (or more) common). Oh, and if a randart has a brand which would be identified if it were merely an ego weapon, it now identifies the RAP_BRAND property of the randart. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7704 c06c8d41-db1a-0410-9941-cceddc491573 --- crawl-ref/source/dat/database/godspeak.txt | 5 + crawl-ref/source/dat/database/monspeak.txt | 14 + crawl-ref/source/dat/descript/monsters.txt | 5 + crawl-ref/source/describe.cc | 20 +- crawl-ref/source/enum.h | 1 + crawl-ref/source/fight.cc | 433 +++++++++++++++++++++++++++-- crawl-ref/source/fight.h | 9 + crawl-ref/source/item_use.cc | 182 +++++++++++- crawl-ref/source/item_use.h | 2 + crawl-ref/source/itemname.cc | 6 + crawl-ref/source/itemprop.h | 4 +- crawl-ref/source/mon-data.h | 12 +- crawl-ref/source/mon-util.cc | 90 ++++-- crawl-ref/source/mon-util.h | 6 +- crawl-ref/source/mstuff2.cc | 11 +- crawl-ref/source/religion.cc | 70 ++++- crawl-ref/source/religion.h | 1 + crawl-ref/source/shopping.cc | 1 + crawl-ref/source/xom.cc | 292 ++++++++++++++++++- 19 files changed, 1080 insertions(+), 84 deletions(-) (limited to 'crawl-ref/source') diff --git a/crawl-ref/source/dat/database/godspeak.txt b/crawl-ref/source/dat/database/godspeak.txt index 299870dd54..382a186f89 100644 --- a/crawl-ref/source/dat/database/godspeak.txt +++ b/crawl-ref/source/dat/database/godspeak.txt @@ -260,6 +260,11 @@ Xom casts you into the Abyss! The world seems to spin as Xom's maniacal laughter rings in your ears. %%%% +# Xom upgrades a nearby hostile monster's weapon to a chaos brand +Xom chaos upgrade + +"Have a taste of chaos, mortal." +%%%% ##################### # other effects ##################### diff --git a/crawl-ref/source/dat/database/monspeak.txt b/crawl-ref/source/dat/database/monspeak.txt index b86a461b0b..80ae1f879d 100644 --- a/crawl-ref/source/dat/database/monspeak.txt +++ b/crawl-ref/source/dat/database/monspeak.txt @@ -2218,6 +2218,20 @@ _Xtahua_rare_ # Specific non-unique monsters ########################################## %%%% +# Chaos spawns shouldn't have coherent speech, since that would be too +# orderly for beings of pure chaos. +chaos spawn + +VISUAL:@The_monster@ grows dozens of eye stalks in order to get a better look at you. + +VISUAL:@The_monster@ splits into many small globs of multi-coloured light, then recombines. + +VISUAL:@The_monster@ breifly grows a face disturbingly similar to your own. + +@The_monster@ ululates chillingly with its many mouths. + +@The_monster@ gibbers incoherently in a cacophony of voices. +%%%% crystal golem VISUAL:@The_monster@ glitters in the dim dungeon light. diff --git a/crawl-ref/source/dat/descript/monsters.txt b/crawl-ref/source/dat/descript/monsters.txt index dd0fbf0a69..5060800209 100644 --- a/crawl-ref/source/dat/descript/monsters.txt +++ b/crawl-ref/source/dat/descript/monsters.txt @@ -50,6 +50,11 @@ Cerebov A violent and wrathful demon, Cerebov appears as a giant human covered in shining golden armour and wielding a huge twisted sword. %%%% +Chaos spawn + +A being of pure chaos, its form is constantly shifitng, growing and then +losing eyes, mouths, claws, and tentacles. +%%%% Daeva A divine agent of the Shining One, it is a towering winged figure with an aura of brilliant golden light. diff --git a/crawl-ref/source/describe.cc b/crawl-ref/source/describe.cc index b80634e36b..4fd6f9a611 100644 --- a/crawl-ref/source/describe.cc +++ b/crawl-ref/source/describe.cc @@ -981,6 +981,14 @@ static std::string _describe_weapon(const item_def &item, bool verbose) description += "It turns projectiles fired from it into " "bolts of frost."; break; + case SPWPN_CHAOS: + if (is_range_weapon(item)) + description += "Each time it fires it turns the launched " + "projectile into a different, random type of bolt."; + else + description += "Each time it hits an enemy it has a " + "different, random effect."; + break; case SPWPN_VAMPIRICISM: description += "It inflicts no extra harm, " "but heals its wielder somewhat when " @@ -1145,11 +1153,21 @@ static std::string _describe_ammo( const item_def &item ) if (item.special && item_type_known(item)) { description += "$$"; + std::string bolt_name; switch (item.special) { case SPMSL_FLAME: + bolt_name = "flame"; + // Intentional fall-through case SPMSL_ICE: + if (bolt_name.empty()) + bolt_name = "ice"; + // Intentional fall-through + case SPMSL_CHAOS: + if (bolt_name.empty()) + bolt_name = "a random type"; + description += "When "; if (can_throw) @@ -1163,7 +1181,7 @@ static std::string _describe_ammo( const item_def &item ) description += "fired from an appropriate launcher, "; description += "it turns into a bolt of "; - description += (item.special == SPMSL_FLAME) ? "flame" : "ice"; + description += bolt_name; description += "."; break; case SPMSL_POISONED: diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h index 361764cb82..08bd09a537 100644 --- a/crawl-ref/source/enum.h +++ b/crawl-ref/source/enum.h @@ -681,6 +681,7 @@ enum conduct_type DID_EAT_SOULED_BEING, // Zin DID_DELIBERATE_MUTATING, // Zin DID_CAUSE_GLOWING, // Zin + DID_CHAOS, // Zin (used weapon/magic of chaos) DID_DESTROY_ORCISH_IDOL, // Beogh DID_CREATE_LIFE, // unused diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc index 465afc5ed3..d7dedf3a99 100644 --- a/crawl-ref/source/fight.cc +++ b/crawl-ref/source/fight.cc @@ -1215,7 +1215,7 @@ bool melee_attack::player_apply_aux_unarmed() } if (def->hit_points < 1) - monster_die(def, KILL_YOU, NON_MONSTER); + _monster_die(def, KILL_YOU, NON_MONSTER); return (!def->alive()); } @@ -1849,6 +1849,25 @@ bool melee_attack::player_monattk_hit_effects(bool mondied) return (!def->alive()); } +void melee_attack::_monster_die(monsters* monster, killer_type killer, + int killer_index) +{ + const bool chaos = damage_brand == SPWPN_CHAOS; + + // Copy defender before it gets reset by monster_die() + monsters* def_copy = NULL; + if (chaos) + def_copy = new monsters(*monster); + + monster_die(monster, killer, killer_index); + + if (chaos) + { + chaos_killed_defender(def_copy); + delete def_copy; + } +} + static bool is_boolean_resist(beam_type flavour) { switch (flavour) @@ -2078,13 +2097,17 @@ bool melee_attack::distortion_affects_defender() emit_nodmg_hit_message(); if (defender->atype() == ACT_PLAYER && attacker_visible - && weapon != NULL && !is_artefact(*weapon)) + && weapon != NULL && !is_unrandom_artefact(*weapon) + && !is_fixed_artefact(*weapon)) { // If the player is being sent to the Abyss by being attacked // with a distortion weapon, then we have to ID it before // the player goes to Abyss, while the weapon object is // still in memory. - set_ident_flags(*weapon, ISFLAG_KNOW_TYPE); + if (is_random_artefact(*weapon)) + randart_wpn_learn_prop(*weapon, RAP_BRAND); + else + set_ident_flags(*weapon, ISFLAG_KNOW_TYPE); } else if (defender_visible) obvious_effect = true; @@ -2097,8 +2120,331 @@ bool melee_attack::distortion_affects_defender() return (false); } +static bool _can_clone(const actor *defender, coord_def *pos, int *midx) +{ + pos->set(-1, -1); + *midx = NON_MONSTER; + + // Maybe create a player ghost? + if (defender->atype() == ACT_PLAYER) + return (false); + + const monsters* mon = dynamic_cast(defender); + + // No uniques, pandemonium lords or player ghosts. Also, figuring + // out the name for the clone of a named monster isn't worth it. + if (mons_is_unique(mon->type) || mon->is_named() || mon->ghost.get()) + return (false); + + // Holy beings can't be duplicated by chaotic means. + if (mons_is_holy(mon)) + return (false); + + // Is there space for the clone? + int squares = 0; + for (int i = 0; i < 8; i++) + { + const coord_def p = mon->pos() + Compass[i]; + + if (in_bounds(p) && p != you.pos() && mgrd(p) == NON_MONSTER + && monster_habitable_grid(mon, grd(p))) + { + if (one_chance_in(++squares)) + *pos = p; + } + } + if (squares == 0) + return (false); + + // Is there an open slot in menv? + for (int i = 0; i < MAX_MONSTERS; i++) + if (menv[i].type == -1) + { + *midx = i; + break; + } + + if (*midx == NON_MONSTER) + return (false); + + // Is the monster carrying an artefact? + for (int i = 0; i < NUM_MONSTER_SLOTS; i++) + { + const int index = mon->inv[i]; + + if (index == NON_ITEM) + continue; + + if (is_artefact(mitm[index])) + return (false); + } + + return (true); +} + +static bool _do_clone(monsters* orig, coord_def pos, int midx) +{ + bool obvious = false; + + monsters &mon(menv[midx]); + + mon = *orig; + + mon.position = pos; + mgrd(pos) = midx; + + // Duplicate objects, or unequip them if they can't be duplicated. + for (int i = 0; i < NUM_MONSTER_SLOTS; i++) + { + const int old_index = orig->inv[i]; + + if (old_index == NON_ITEM) + continue; + + const int new_index = get_item_slot(0); + if (new_index == NON_ITEM) + { + mon.unequip(mitm[old_index], i, 0, true); + mon.inv[i] = NON_ITEM; + continue; + } + + mon.inv[i] = new_index; + mitm[new_index] = mitm[old_index]; + } + + // The player shouldn't get new permanent followers from cloning. + if (mon.attitude == ATT_FRIENDLY && !mons_is_summoned(&mon)) + mon.mark_summoned(6, true); + + if (you.can_see(orig) && you.can_see(&mon)) + { + simple_monster_message(orig, " is duplicated!"); + obvious = true; + } + + mark_interesting_monst(&mon, mon.behaviour); + if (you.can_see(&mon)) + { + seen_monster(&mon); + viewwindow(true, false); + } + + return (obvious); +} + +enum chaos_type +{ + CHAOS_CLONE, + CHAOS_POLY, + CHAOS_POLY_UP, + CHAOS_MAKE_SHIFTER, + CHAOS_HEAL, + CHAOS_HASTE, + CHAOS_INVIS, + CHAOS_SLOW, + CHAOS_PARA, + CHAOS_PETRIFY, + NUM_CHAOS_TYPES +}; + +// XXX: We might want to vary the probabilites for the various effects +// based on whether the source is weapon of chaos or a monster with +// AF_CHAOS +void melee_attack::chaos_affects_defender() +{ + coord_def clone_pos; + int clone_midx; + const bool mon = defender->atype() == ACT_MONSTER; + const bool immune = mon && mons_immune_magic(def); + const bool is_shifter = mon && mons_is_shapeshifter(def); + const bool is_chaotic = mon && mons_is_chaotic(def); + const bool can_clone = _can_clone(defender, &clone_pos, &clone_midx); + const bool can_poly = is_shifter || (defender->can_safely_mutate() + && !immune); + + int clone_chance = can_clone ? 1 : 0; + int poly_chance = can_poly ? 1 : 0; + int poly_up_chance = can_poly ? 1 : 0; + int shifter_chance = can_poly ? 1 : 0; + + if (is_chaotic) + { + // Polymorphing might reduce amount of chaos in the world. + poly_chance = 0; + poly_up_chance = 0; + + // Chaos wants more chaos. + clone_chance *= 2; + + // Chaos loves shifters. + if (is_shifter) + { + clone_chance *= 2; + poly_up_chance = 4; + + // Already a shifter + shifter_chance = 0; + } + } + + // NOTE: Must appear in exact same order as in chaos_type enumeration. + int probs[NUM_CHAOS_TYPES] = + { + clone_chance, // CHAOS_CLONE + poly_chance, // CHAOS_POLY + poly_up_chance, // CHAOS_POLY_UP + shifter_chance, // CHAOS_MAKE_SHIFTER + + 5, // CHAOS_HEAL + 5, // CHAOS_HASTE + 5, // CHAOS_INVIS + + 15, // CHAOS_SLOW + 15, // CHAOS_PARA + 15, // CHAOS_PETRIFY + }; + + bolt beam; + beam.flavour = BEAM_NONE; + + int choice = choose_random_weighted(probs, probs + NUM_CHAOS_TYPES); + switch(static_cast(choice)) + { + case CHAOS_CLONE: + ASSERT(can_clone); + ASSERT(defender->atype() == ACT_MONSTER); + obvious_effect = _do_clone(def, clone_pos, clone_midx); + break; + + case CHAOS_POLY: + ASSERT(can_poly); + beam.flavour = BEAM_POLYMORPH; + break; + + case CHAOS_POLY_UP: + ASSERT(can_poly); + ASSERT(defender->atype() == ACT_MONSTER); + + obvious_effect = you.can_see(defender); + monster_polymorph(def, RANDOM_MONSTER, PPT_MORE, true); + break; + + case CHAOS_MAKE_SHIFTER: + ASSERT(can_poly); + ASSERT(!is_shifter); + ASSERT(defender->atype() == ACT_MONSTER); + + obvious_effect = you.can_see(defender); + def->add_ench(one_chance_in(3) ? + ENCH_GLOWING_SHAPESHIFTER : ENCH_SHAPESHIFTER); + // Immediately polymorph monster, just to make the effect obvious. + monster_polymorph(def, RANDOM_MONSTER, PPT_SAME, true); + break; + + case CHAOS_HEAL: + beam.flavour = BEAM_HEALING; + break; + + case CHAOS_HASTE: + beam.flavour = BEAM_HASTE; + break; + + case CHAOS_INVIS: + beam.flavour = BEAM_INVISIBILITY; + break; + + case CHAOS_SLOW: + beam.flavour = BEAM_SLOW; + break; + + case CHAOS_PARA: + beam.flavour = BEAM_PARALYSIS; + break; + + case CHAOS_PETRIFY: + beam.flavour = BEAM_PETRIFY; + break; + + default: + ASSERT(!"Invalid chaos effect type"); + break; + } + + if (beam.flavour != BEAM_NONE) + { + beam.name = atk_name(DESC_CAP_THE); + beam.range = 1; + beam.colour = BLACK; + beam.is_beam = false; + beam.is_explosion = false; + beam.is_big_cloud = false; + beam.effect_known = false; + + beam.thrower = (attacker->atype() == ACT_PLAYER) ? KILL_YOU + : def->confused_by_you() ? KILL_YOU_CONF + : KILL_MON; + beam.beam_source = + (attacker->atype() == ACT_PLAYER) ? MHITYOU : monster_index(atk); + + beam.source = attacker->pos(); + beam.target = defender->pos(); + beam.pos = defender->pos(); + + beam.damage = dice_def(damage_done + special_damage + aux_damage, 1); + + beam.ench_power = beam.damage.num; + + fire_beam(beam); + + if (you.can_see(defender)) + obvious_effect = beam.obvious_effect; + } + + if (!you.can_see(attacker)) + obvious_effect = false; +} + +void melee_attack::chaos_affects_attacker() +{ +} + +// NOTE: Isn't called if monster dies from poisoning caused by chaos. +void melee_attack::chaos_killed_defender(monsters* def_copy) +{ +} + +// NOTE: random_chaos_brand() and random_chaos_attack_flavour() should +// return a set of effects that are roughly the same, to make it easy +// for chaos_affects_defender() not to do duplicate effects caused +// by the non-chaos brands/flavours they return. +int melee_attack::random_chaos_brand() +{ + int brands[] = {SPWPN_FLAMING, SPWPN_FREEZING, SPWPN_ELECTROCUTION, + SPWPN_VENOM, SPWPN_DRAINING, SPWPN_VAMPIRICISM, + SPWPN_PAIN, SPWPN_DISTORTION, SPWPN_CONFUSE, + SPWPN_CHAOS}; + return (RANDOM_ELEMENT(brands)); +} + +mon_attack_flavour melee_attack::random_chaos_attack_flavour() +{ + mon_attack_flavour flavours[] = + {AF_FIRE, AF_COLD, AF_ELEC, AF_POISON_NASTY, AF_VAMPIRIC, AF_DISTORT, + AF_CONFUSE, AF_CHAOS}; + return (RANDOM_ELEMENT(flavours)); +} + bool melee_attack::apply_damage_brand() { + bool brand_was_known = false; + + if (weapon) + if (is_random_artefact(*weapon)) + brand_was_known = randart_known_wpn_property(*weapon, RAP_BRAND); + else + brand_was_known = item_type_known(*weapon); + bool ret = false; // Monster resistance to the brand. @@ -2106,7 +2452,14 @@ bool melee_attack::apply_damage_brand() special_damage = 0; obvious_effect = false; - switch (damage_brand) + + int brand; + if (damage_brand == SPWPN_CHAOS) + brand = random_chaos_brand(); + else + brand = damage_brand; + + switch (brand) { case SPWPN_FLAMING: res = fire_res_apply_cerebov_downgrade( defender->res_fire() ); @@ -2306,8 +2659,6 @@ bool melee_attack::apply_damage_brand() { emit_nodmg_hit_message(); - // FIXME Currently Confusing Touch is the *only* way to get - // here. Generalise. const int hdcheck = (defender->holiness() == MH_NATURAL? random2(30) : random2(22)); @@ -2323,26 +2674,42 @@ bool melee_attack::apply_damage_brand() (attacker->atype() == ACT_PLAYER) ? MHITYOU : monster_index(atk); mons_ench_f2( def, beam_temp ); + obvious_effect = beam_temp.obvious_effect; } - if (attacker->atype() == ACT_PLAYER) + if (attacker->atype() == ACT_PLAYER && damage_brand == SPWPN_CONFUSE) { + ASSERT(you.duration[DUR_CONFUSING_TOUCH]); you.duration[DUR_CONFUSING_TOUCH] -= roll_dice(3, 5); if (you.duration[DUR_CONFUSING_TOUCH] < 1) you.duration[DUR_CONFUSING_TOUCH] = 1; + obvious_effect = false; } break; } + + case SPWPN_CHAOS: + chaos_affects_defender(); + break; } + if (attacker->atype() == ACT_PLAYER && damage_brand == SPWPN_CHAOS) + // If your god objects to using chaos then it makes the + // brand obvious. + if (did_god_conduct(DID_CHAOS, 2 + random2(3), brand_was_known)) + obvious_effect = true; + if (!obvious_effect) obvious_effect = !special_damage_message.empty(); if (obvious_effect && attacker_visible && weapon != NULL - && !is_artefact(*weapon)) + && !is_unrandom_artefact(*weapon) && !is_fixed_artefact(*weapon)) { - set_ident_flags(*weapon, ISFLAG_KNOW_TYPE); + if (is_random_artefact(*weapon)) + randart_wpn_learn_prop(*weapon, RAP_BRAND); + else + set_ident_flags(*weapon, ISFLAG_KNOW_TYPE); } return (ret); @@ -2621,7 +2988,7 @@ bool melee_attack::player_check_monster_died() player_monattk_hit_effects(true); - monster_die(def, KILL_YOU, NON_MONSTER); + _monster_die(def, KILL_YOU, NON_MONSTER); return (true); } @@ -3571,7 +3938,11 @@ void melee_attack::mons_apply_attack_flavour(const mon_attack_def &attk) { // Most of this is from BWR 4.1.2. - switch (attk.flavour) + mon_attack_flavour flavour = attk.flavour; + if (flavour == AF_CHAOS) + flavour = random_chaos_attack_flavour(); + + switch (flavour) { default: break; @@ -3811,6 +4182,10 @@ void melee_attack::mons_apply_attack_flavour(const mon_attack_def &attk) case AF_NAPALM: mons_do_napalm(); break; + + case AF_CHAOS: + chaos_affects_defender(); + break; } } @@ -3822,6 +4197,7 @@ void melee_attack::mons_perform_attack_rounds() // Melee combat, tell attacker to wield its melee weapon. atk->wield_melee_weapon(); + monsters* def_copy = NULL; for (attack_number = 0; attack_number < nrounds; ++attack_number) { // Monster went away? @@ -3855,6 +4231,13 @@ void melee_attack::mons_perform_attack_rounds() mons_set_weapon(attk); to_hit = mons_to_hit(); + const bool chaos_attack = attk.flavour == AF_CHAOS + || damage_brand == SPWPN_CHAOS; + + // Make copy of monster before monster_die() resets it. + if (chaos_attack && defender->atype() == ACT_MONSTER && !def_copy) + def_copy = new monsters(*def); + final_attack_delay = mons_attk_delay(); if (damage_brand == SPWPN_SPEED) final_attack_delay = final_attack_delay / 2 + 1; @@ -3945,13 +4328,17 @@ void melee_attack::mons_perform_attack_rounds() defender->hurt(attacker, damage_done + special_damage); - // Yredelemnul's injury mirroring can kill the attacker. - if (!attacker->alive() || !defender->alive() - || attacker == defender) + if (!defender->alive()) { - return; + if (chaos_attack && defender->atype() == ACT_MONSTER) + chaos_killed_defender(def_copy); + break; } + // Yredelemnul's injury mirroring can kill the attacker. + if (!attacker->alive() || attacker == defender) + break; + special_damage = 0; special_damage_message.clear(); apply_damage_brand(); @@ -3962,18 +4349,28 @@ void melee_attack::mons_perform_attack_rounds() if (special_damage > 0) defender->hurt(attacker, special_damage); + if (!defender->alive()) + { + if (chaos_attack && defender->atype() == ACT_MONSTER) + chaos_killed_defender(def_copy); + break; + } + // Yredelemnul's injury mirroring can kill the attacker. if (!attacker->alive()) - return; + break; } item_def *weap = atk->mslot_item(MSLOT_WEAPON); - if (weap && weap->cursed() && is_range_weapon(*weap) - && !(weap->flags & ISFLAG_KNOW_CURSE)) + if (weap && you.can_see(atk) && weap->cursed() + && is_range_weapon(*weap)) { set_ident_flags( *weap, ISFLAG_KNOW_CURSE ); } } + + if (def_copy) + delete def_copy; } bool melee_attack::mons_perform_attack() diff --git a/crawl-ref/source/fight.h b/crawl-ref/source/fight.h index 4ac3b0051d..8df5ec790d 100644 --- a/crawl-ref/source/fight.h +++ b/crawl-ref/source/fight.h @@ -13,6 +13,7 @@ #include "externs.h" #include "randart.h" +#include "mon-util.h" enum unarmed_attack_type { @@ -198,6 +199,11 @@ private: // Returns true if the defender is banished. bool distortion_affects_defender(); + void chaos_affects_defender(); + void chaos_affects_attacker(); + void chaos_killed_defender(monsters* def_copy); + int random_chaos_brand(); + private: // Monster-attack specific stuff bool mons_attack_you(); @@ -222,6 +228,8 @@ private: std::string mons_defender_name(); void wasp_paralyse_defender(); + mon_attack_flavour random_chaos_attack_flavour(); + private: // Player-attack specific stuff bool player_attack(); @@ -261,6 +269,7 @@ private: std::string player_why_missed(); void player_warn_miss(); void player_check_weapon_effects(); + void _monster_die(monsters *monster, killer_type killer, int killer_index); }; #endif diff --git a/crawl-ref/source/item_use.cc b/crawl-ref/source/item_use.cc index 198fd61ad0..6f300fffd2 100644 --- a/crawl-ref/source/item_use.cc +++ b/crawl-ref/source/item_use.cc @@ -140,9 +140,8 @@ bool can_wield(item_def *weapon, bool say_reason, return (false); } - int weap_brand = get_weapon_brand(*weapon); if ((you.is_undead || you.species == SP_DEMONSPAWN) - && (weap_brand == SPWPN_HOLY_WRATH || is_blessed_blade(*weapon))) + && is_holy_item(*weapon)) { if (say_reason) { @@ -634,6 +633,11 @@ void wield_effects(int item_wield_2, bool showMsgs) mpr("A searing pain shoots up your arm!"); break; + case SPWPN_CHAOS: + mpr("It is briefly surrounded by a scintillating arua " + "of random colours."); + break; + case SPWPN_SINGING_SWORD: if (!was_known) { @@ -1649,12 +1653,15 @@ int launcher_final_speed(const item_def &launcher, const item_def *shield) // positive: frost, negative: flame, zero: neither bool elemental_missile_beam(int launcher_brand, int ammo_brand) { + if (launcher_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS) + return (true); + int element = (launcher_brand == SPWPN_FROST + ammo_brand == SPMSL_ICE - launcher_brand == SPWPN_FLAME - ammo_brand == SPMSL_FLAME); - return (element); + return (element != 0); } // XXX This is a bit too generous, as it lets the player determine @@ -1668,6 +1675,8 @@ static bool determines_ammo_brand(int bow_brand, int ammo_brand) return (false); if (bow_brand == SPWPN_VENOM && ammo_brand == SPMSL_POISONED) return (false); + if (bow_brand == SPWPN_CHAOS && ammo_brand == SPMSL_CHAOS) + return (false); return (true); } @@ -1739,6 +1748,140 @@ void _merge_ammo_in_inventory(int slot) } } +std::string setup_chaos_ammo(bolt &pbolt, item_def ammo) +{ + ASSERT(!is_artefact(ammo)); + + const bool poisoned = (get_ammo_brand(ammo) == SPMSL_POISONED); + + // Don't choose BEAM_POISON or BEAM_HEALING if we have poisoned ammo. + const int pois_weight = poisoned ? 0 : 10; + const int heal_weight = poisoned ? 0 : 10; + + const beam_type flavour = static_cast( + random_choose_weighted( pois_weight, BEAM_POISON, + heal_weight, BEAM_HEALING, + + 10, BEAM_FIRE, + 10, BEAM_COLD, + 10, BEAM_ELECTRICITY, + 10, BEAM_NEG, + 10, BEAM_ACID, + 10, BEAM_HELLFIRE, + 10, BEAM_NAPALM, + 10, BEAM_HELLFROST, + 10, BEAM_SLOW, + 10, BEAM_HASTE, + 10, BEAM_PARALYSIS, + 10, BEAM_CONFUSION, + 10, BEAM_INVISIBILITY, + 10, BEAM_POLYMORPH, + 10, BEAM_BANISH, + 10, BEAM_DISINTEGRATION, + 0 )); + + std::string name; + int colour; + + if (poisoned) + name = "poison "; + + switch(flavour) + { + case BEAM_POISON: + name += "poison"; + colour = EC_POISON; + break; + case BEAM_HEALING: + name += "healing"; + colour = EC_HEAL; + break; + case BEAM_FIRE: + name += "flame"; + colour = EC_FIRE; + break; + case BEAM_COLD: + name += "frost"; + colour = EC_ICE; + break; + case BEAM_ELECTRICITY: + name += "lightning"; + colour = EC_ELECTRICITY; + break; + case BEAM_NEG: + name += "negative energy"; + colour = EC_NECRO; + break; + case BEAM_ACID: + name += "acid"; + colour = YELLOW; + break; + case BEAM_HELLFIRE: + name += "hellfire"; + colour = EC_FIRE; + break; + case BEAM_NAPALM: + name += "sticky fire"; + colour = EC_FIRE; + break; + case BEAM_HELLFROST: + name += "hellfrost"; + colour = EC_ICE; + break; + case BEAM_SLOW: + name += "slowing"; + colour = EC_ENCHANT; + break; + case BEAM_HASTE: + name += "hasting"; + colour = EC_ENCHANT; + break; + case BEAM_PARALYSIS: + name += "paralysis"; + colour = EC_ENCHANT; + break; + case BEAM_CONFUSION: + name += "confusion"; + colour = EC_ENCHANT; + break; + case BEAM_INVISIBILITY: + name += "invisibility"; + colour = EC_ENCHANT; + break; + case BEAM_POLYMORPH: + name += "polymorphing"; + colour = EC_MUTAGENIC; + break; + case BEAM_BANISH: + name += "banishment"; + colour = EC_WARP; + break; + case BEAM_DISINTEGRATION: + name += "disintegration"; + colour = EC_DEATH; + break; + default: + ASSERT(!"Invalid chaos ammo flavour."); + break; + } + + pbolt.name = "bolt of "; + pbolt.name += name; + + pbolt.flavour = flavour; + pbolt.colour = colour; + pbolt.type = dchar_glyph(DCHAR_FIRED_BOLT); + + // Get name for a plain arrow/bolt/dart/needle. + ammo.special = 0; + + std::string ammo_name = ammo.name(DESC_NOCAP_A); + ammo_name += " of "; + ammo_name += name; + + return ammo_name; +} + // throw_it - currently handles player throwing only. Monster // throwing is handled in mstuff2:mons_throw() // Note: If teleport is true, assume that pbolt is already set up, @@ -1785,6 +1928,9 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus, item_def& thrown = you.inv[throw_2]; + // Did we know the ammo's brand before throwing it? + const bool ammon_brand_known = item_type_known(thrown); + // Get the ammo/weapon type. Convenience. const object_class_type wepClass = thrown.base_type; const int wepType = thrown.sub_type; @@ -1888,6 +2034,7 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus, // Now start real firing! origin_set_unknown(item); + std::string ammo_name; if (is_blood_potion(item) && thrown.quantity > 1) { @@ -2389,8 +2536,19 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus, // and vice versa. // Note that bow_brand is known since the bow is equipped. - if ((bow_brand == SPWPN_FLAME || ammo_brand == SPMSL_FLAME) - && ammo_brand != SPMSL_ICE && bow_brand != SPWPN_FROST) + + // Chaos overides flame and frost/ice. + if (bow_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS) + { + ammo_name = setup_chaos_ammo(pbolt, item); + + // [dshaligram] Branded arrows are much stronger. + dice_mult = (dice_mult * 150) / 100; + + pbolt.effect_known = false; + } + else if ((bow_brand == SPWPN_FLAME || ammo_brand == SPMSL_FLAME) + && ammo_brand != SPMSL_ICE && bow_brand != SPWPN_FROST) { // [dshaligram] Branded arrows are much stronger. dice_mult = (dice_mult * 150) / 100; @@ -2407,9 +2565,8 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus, pbolt.thrower = KILL_YOU_MISSILE; pbolt.aux_source.clear(); } - - if ((bow_brand == SPWPN_FROST || ammo_brand == SPMSL_ICE) - && ammo_brand != SPMSL_FLAME && bow_brand != SPWPN_FLAME) + else if ((bow_brand == SPWPN_FROST || ammo_brand == SPMSL_ICE) + && ammo_brand != SPMSL_FLAME && bow_brand != SPWPN_FLAME) { // [dshaligram] Branded arrows are much stronger. dice_mult = (dice_mult * 150) / 100; @@ -2522,12 +2679,15 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus, } } + if (ammo_name.empty()) + ammo_name = ammo.name(DESC_NOCAP_A); + // Create message. mprf( "%s %s%s %s.", teleport ? "Magically, you" : "You", projected ? "" : "awkwardly ", projected == LRET_LAUNCHED ? "shoot" : "throw", - ammo.name(DESC_NOCAP_A).c_str() ); + ammo_name.c_str() ); // Ensure we're firing a 'missile'-type beam. pbolt.is_beam = false; @@ -2559,6 +2719,10 @@ bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus, did_return = false; } + if (bow_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS) + did_god_conduct(DID_CHAOS, 2 + random2(3), + bow_brand == SPWPN_CHAOS || ammon_brand_known); + if (did_return) { // Fire beam in reverse. diff --git a/crawl-ref/source/item_use.h b/crawl-ref/source/item_use.h index 31009b522e..c6f5820b18 100644 --- a/crawl-ref/source/item_use.h +++ b/crawl-ref/source/item_use.h @@ -160,6 +160,8 @@ bool puton_item(int slot, bool prompt_finger = true); bool enchant_weapon(enchant_stat_type which_stat, bool quiet, item_def &wpn); bool enchant_armour(int &ac_change, bool quiet, item_def &arm); +std::string setup_chaos_ammo(bolt &pbolt, item_def item); + bool throw_it(bolt &pbolt, int throw_2, bool teleport = false, int acc_bonus = 0, dist *target = NULL); diff --git a/crawl-ref/source/itemname.cc b/crawl-ref/source/itemname.cc index a12e1c7804..feb61f56cd 100644 --- a/crawl-ref/source/itemname.cc +++ b/crawl-ref/source/itemname.cc @@ -305,6 +305,9 @@ const char* weapon_brand_name(const item_def& item, bool terse) case SPWPN_FLAME: return ((terse) ? " (flame)" : " of flame"); case SPWPN_FROST: return ((terse) ? " (frost)" : " of frost"); + // both ranged and non-ranged + case SPWPN_CHAOS: return ((terse) ? " (chaos)" : " of chaos"); + // randart brands default: return ""; } @@ -1089,6 +1092,9 @@ std::string item_def::name_aux( description_level_type desc, case SPMSL_RETURNING: buff << ((terse) ? " (return)" : " of returning"); break; + case SPMSL_CHAOS: + buff << ((terse) ? " (chaos)" : " of chaos"); + break; default: buff << " (buggy)"; } diff --git a/crawl-ref/source/itemprop.h b/crawl-ref/source/itemprop.h index 36a3e12c4f..2723fc4057 100644 --- a/crawl-ref/source/itemprop.h +++ b/crawl-ref/source/itemprop.h @@ -98,6 +98,7 @@ enum brand_type // equivalent to (you.inv[].special or mitm[].special) % 30 MAX_PAN_LORD_BRANDS = SPWPN_RETURNING, SPWPN_CONFUSE, + SPWPN_CHAOS, SPWPN_RANDART_I = 25, // 25 SPWPN_RANDART_II, SPWPN_RANDART_III, @@ -354,7 +355,8 @@ enum special_missile_type // to separate from weapons in general {dlb} SPMSL_POISONED, // 3 SPMSL_POISONED_II, // 4 - unused SPMSL_CURARE, // 5 - SPMSL_RETURNING // 6 + SPMSL_RETURNING, // 6 + SPMSL_CHAOS // 7 }; enum special_ring_type // jewellery mitm[].special values diff --git a/crawl-ref/source/mon-data.h b/crawl-ref/source/mon-data.h index 8c466dfaca..91f3c46a5b 100644 --- a/crawl-ref/source/mon-data.h +++ b/crawl-ref/source/mon-data.h @@ -3448,13 +3448,13 @@ static monsterentry mondata[] = { { MONS_CHAOS_SPAWN, '3', EC_RANDOM, "chaos spawn", - M_SEE_INVIS | M_EVIL, + M_SEE_INVIS | M_EVIL | M_INSUBSTANTIAL, MR_NO_FLAGS, - 0, 10, MONS_CHAOS_SPAWN, MONS_CHAOS_SPAWN, MH_NATURAL, -3, - { AT_NO_ATK, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, - { 0, 0, 0, 0 }, - 0, 0, MST_NO_SPELLS, CE_MUTAGEN_RANDOM, Z_NOZOMBIE, S_RANDOM, I_NORMAL, - HT_LAND, 0, DEFAULT_ENERGY, MONUSE_NOTHING, SIZE_BIG + 0, 12, MONS_CHAOS_SPAWN, MONS_CHAOS_SPAWN, MH_DEMONIC, -7, + { {AT_RANDOM, AF_CHAOS, 21}, AT_NO_ATK, AT_NO_ATK, AT_NO_ATK }, + { 6, 3, 5, 0 }, + 7, 12, MST_NO_SPELLS, CE_MUTAGEN_RANDOM, Z_NOZOMBIE, S_RANDOM, I_ANIMAL, + HT_LAND, 11, DEFAULT_ENERGY, MONUSE_NOTHING, SIZE_BIG }, // reaper etc. ('2') diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc index 841ba05762..2e73ae5c9f 100644 --- a/crawl-ref/source/mon-util.cc +++ b/crawl-ref/source/mon-util.cc @@ -575,7 +575,8 @@ bool mons_is_chaotic(const monsters *mon) return (true); const int attk_flavour = mons_attack_spec(mon, 0).flavour; - return (attk_flavour == AF_MUTATE || attk_flavour == AF_ROT); + return (attk_flavour == AF_MUTATE || attk_flavour == AF_ROT + || attk_flavour == AF_CHAOS); } bool mons_is_poisoner(const monsters *mon) @@ -738,14 +739,19 @@ int get_shout_noise_level(const shout_type shout) } } -// Only the beast uses S_RANDOM for noise type. +// Only beasts and chaos spawns uses S_RANDOM for noise type. // Pandemonium lords can also get here but this mostly used for the // "says" verb used for insults. static bool _shout_fits_monster(int type, int shout) { - if (shout == NUM_SHOUTS || shout >= NUM_LOUDNESS) + if (shout == NUM_SHOUTS || shout >= NUM_LOUDNESS || shout == S_SILENT) return (false); + // Chaos spawns can do anything but demon taunts, since they're + // not coherenet enough to actually say words. + if (type == MONS_CHAOS_SPAWN) + return (shout != S_DEMON_TAUNT); + // For demon lords almost everything is fair game. // It's only used for the shouting verb ("say", "bellow", "roar", ...) // anyway. @@ -760,8 +766,6 @@ static bool _shout_fits_monster(int type, int shout) case S_WHINE: // The beast cannot speak. case S_DEMON_TAUNT: - // Silent is boring. - case S_SILENT: return (false); default: return (true); @@ -1018,17 +1022,16 @@ mon_attack_def mons_attack_spec(const monsters *mon, int attk_number) ASSERT(smc); mon_attack_def attk = smc->attack[attk_number]; + if (attk.type == AT_RANDOM) + attk.type = static_cast(random_range(AT_HIT, + AT_BUTT)); + if (attk.flavour == AF_KLOWN) { - switch (random2(6)) - { - case 0: attk.flavour = AF_POISON_NASTY; break; - case 1: attk.flavour = AF_ROT; break; - case 2: attk.flavour = AF_DRAIN_XP; break; - case 3: attk.flavour = AF_FIRE; break; - case 4: attk.flavour = AF_COLD; break; - case 5: attk.flavour = AF_BLINK; break; - } + mon_attack_flavour flavours[] = + {AF_POISON_NASTY, AF_ROT, AF_DRAIN_XP, AF_FIRE, AF_COLD, AF_BLINK}; + + attk.flavour = RANDOM_ELEMENT(flavours); } return (zombified ? downscale_zombie_attack(mon, attk) : attk); @@ -3637,6 +3640,10 @@ void monsters::equip_weapon(item_def &item, int near, bool msg) case SPWPN_DISTORTION: mpr("Its appearance distorts for a moment."); break; + case SPWPN_CHAOS: + mpr("It is briefly surrounded by a scintillating arua of " + "random colours."); + break; default: // A ranged weapon without special message is known to be unbranded. @@ -3645,7 +3652,12 @@ void monsters::equip_weapon(item_def &item, int near, bool msg) } if (message_given) - set_ident_flags(item, ISFLAG_KNOW_TYPE); + { + if (is_random_artefact(item)) + randart_wpn_learn_prop(item, RAP_BRAND); + else + set_ident_flags(item, ISFLAG_KNOW_TYPE); + } } } @@ -3739,7 +3751,12 @@ void monsters::unequip_weapon(item_def &item, int near, bool msg) message_given = false; } if (message_given) - set_ident_flags(item, ISFLAG_KNOW_TYPE); + { + if (is_random_artefact(item)) + randart_wpn_learn_prop(item, RAP_BRAND); + else + set_ident_flags(item, ISFLAG_KNOW_TYPE); + } } } @@ -4687,6 +4704,8 @@ std::string monsters::hand_name(bool plural, bool *can_plural) const std::string str; char ch = mons_char(type); + const bool rand = (type == MONS_CHAOS_SPAWN); + switch(get_mon_shape(this)) { case MON_SHAPE_CENTAUR: @@ -4705,7 +4724,7 @@ std::string monsters::hand_name(bool plural, bool *can_plural) const case MON_SHAPE_QUADRUPED_TAILLESS: case MON_SHAPE_QUADRUPED_WINGED: case MON_SHAPE_ARACHNID: - if (type == MONS_SCORPION) + if (type == MONS_SCORPION || rand && one_chance_in(4)) str = "pincer"; else { @@ -4738,7 +4757,7 @@ std::string monsters::hand_name(bool plural, bool *can_plural) const break; case MON_SHAPE_MISC: - if (ch == 'x' || ch == 'X') + if (ch == 'x' || ch == 'X' || rand) { str = "tentacle"; break; @@ -4768,14 +4787,25 @@ std::string monsters::hand_name(bool plural, bool *can_plural) const case MONS_GIANT_ORANGE_BRAIN: default: - str = "body"; - can_plural = false; + if (rand) + str = "rhizome"; + else + { + str = "body"; + can_plural = false; + } break; } } if (str.empty()) + { + // Reduce the chance of a random-shaped monster having hands. + if (rand && coinflip()) + return (hand_name(plural, can_plural)); + str = "hand"; + } if (plural && *can_plural) str = pluralise(str); @@ -4793,6 +4823,8 @@ std::string monsters::foot_name(bool plural, bool *can_plural) const std::string str; char ch = mons_char(type); + const bool rand = (type == MONS_CHAOS_SPAWN); + switch(get_mon_shape(this)) { case MON_SHAPE_INSECT: @@ -4823,7 +4855,12 @@ std::string monsters::foot_name(bool plural, bool *can_plural) const case MON_SHAPE_QUADRUPED: case MON_SHAPE_QUADRUPED_TAILLESS: case MON_SHAPE_QUADRUPED_WINGED: - if (ch == 'h') + if (rand) + { + const char* feet[] = {"paw", "talon", "hoof"}; + str = RANDOM_ELEMENT(feet); + } + else if (ch == 'h') str = "paw"; else if (ch == 'l' || ch == 'D') str = "talon"; @@ -4862,7 +4899,7 @@ std::string monsters::foot_name(bool plural, bool *can_plural) const break; case MON_SHAPE_MISC: - if (ch == 'x' || ch == 'X') + if (ch == 'x' || ch == 'X' || rand) { str = "tentacle"; break; @@ -4877,7 +4914,13 @@ std::string monsters::foot_name(bool plural, bool *can_plural) const } if (str.empty()) + { + // Reduce the chance of a random-shaped monster having feet. + if (rand && coinflip()) + return (foot_name(plural, can_plural)); + return (plural ? "feet" : "foot"); + } if (plural && *can_plural) str = pluralise(str); @@ -7679,6 +7722,9 @@ mon_body_shape get_mon_shape(const monsters *mon) mon_body_shape get_mon_shape(const int type) { + if (type == MONS_CHAOS_SPAWN) + return static_cast(random2(MON_SHAPE_MISC + 1)); + switch(mons_char(type)) { case 'a': // ants and cockroaches diff --git a/crawl-ref/source/mon-util.h b/crawl-ref/source/mon-util.h index 2526ab012c..add15efa74 100644 --- a/crawl-ref/source/mon-util.h +++ b/crawl-ref/source/mon-util.h @@ -49,7 +49,8 @@ enum mon_attack_type AT_TAIL_SLAP, AT_BUTT, - AT_SHOOT // Attack representing missile damage for M_ARCHER. + AT_SHOOT, // Attack representing missile damage for M_ARCHER. + AT_RANDOM // Anything but AT_SHOOT }; enum mon_attack_flavour @@ -79,7 +80,8 @@ enum mon_attack_flavour AF_KLOWN, AF_DISTORT, AF_RAGE, - AF_NAPALM + AF_NAPALM, + AF_CHAOS }; // properties of the monster class (other than resists/vulnerabilities) diff --git a/crawl-ref/source/mstuff2.cc b/crawl-ref/source/mstuff2.cc index 18da736352..a6e5c5f5fb 100644 --- a/crawl-ref/source/mstuff2.cc +++ b/crawl-ref/source/mstuff2.cc @@ -1040,9 +1040,16 @@ bool mons_throw(struct monsters *monster, struct bolt &pbolt, int hand_used) monster->speed_increment += speed_delta; } + // Chaos overides flame and frost + if (bow_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS) + { + (void) setup_chaos_ammo(pbolt, item); + baseHit += 2; + exDamBonus += 6; + } // WEAPON or AMMO of FIRE - if (bow_brand == SPWPN_FLAME && ammo_brand != SPMSL_ICE - || ammo_brand == SPMSL_FLAME && bow_brand != SPWPN_FROST) + else if (bow_brand == SPWPN_FLAME && ammo_brand != SPMSL_ICE + || ammo_brand == SPMSL_FLAME && bow_brand != SPWPN_FROST) { baseHit += 2; exDamBonus += 6; diff --git a/crawl-ref/source/religion.cc b/crawl-ref/source/religion.cc index a949d74a73..96f45320d3 100644 --- a/crawl-ref/source/religion.cc +++ b/crawl-ref/source/religion.cc @@ -742,6 +742,7 @@ std::string get_god_dislikes(god_type which_god, bool /*verbose*/) "have been avoided"); dislikes.push_back("you polymorph monsters"); dislikes.push_back("you eat the flesh of sentient beings"); + dislikes.push_back("you use weapons or missiles of chaos"); break; case GOD_SHINING_ONE: @@ -2952,6 +2953,21 @@ bool did_god_conduct(conduct_type thing_done, int level, bool known, } break; + case DID_CHAOS: + if (you.religion == GOD_ZIN) + { + retval = true; + if (!known) + { + simple_god_message(" forgives your inadvertent chaotic " + "act, just this once."); + break; + } + piety_change = -level; + penance = level; + } + break; + case DID_DESTROY_ORCISH_IDOL: if (you.religion == GOD_BEOGH) { @@ -2975,6 +2991,7 @@ bool did_god_conduct(conduct_type thing_done, int level, bool known, else _dock_piety(-piety_change, penance); +#define DEBUG_DIAGNOSTICS 1 #if DEBUG_DIAGNOSTICS if (retval) { @@ -2992,7 +3009,7 @@ bool did_god_conduct(conduct_type thing_done, int level, bool known, "Servant Kill Holy", "Spell Memorise", "Spell Cast", "Spell Practise", "Spell Nonutility", "Cards", "Stimulants", "Drink Blood", "Cannibalism", "Eat Meat", "Eat Souled Being", - "Deliberate Mutation", "Cause Glowing", + "Deliberate Mutation", "Cause Glowing", "Use Chaos", "Destroy Orcish Idol", "Create Life" }; @@ -3261,7 +3278,7 @@ bool is_holy_item(const item_def& item) { const int item_brand = get_weapon_brand(item); - retval = (item_brand == SPWPN_HOLY_WRATH); + retval = (item_brand == SPWPN_HOLY_WRATH || is_blessed_blade(item)); break; } case OBJ_SCROLLS: @@ -3329,6 +3346,40 @@ bool is_evil_item(const item_def& item) return (retval); } +bool is_chaotic_item(const item_def& item) +{ + bool retval = false; + + switch (item.base_type) + { + case OBJ_WEAPONS: + { + const int item_brand = get_weapon_brand(item); + retval = (item_brand == SPWPN_CHAOS); + } + break; + case OBJ_MISSILES: + { + const int item_brand = get_ammo_brand(item); + retval = (item_brand == SPMSL_CHAOS); + } + break; + case OBJ_WANDS: + retval = (item.sub_type == WAND_POLYMORPH_OTHER); + break; + case OBJ_POTIONS: + retval = (item.sub_type == POT_MUTATION); + break; + default: + break; + } + + if (is_random_artefact(item) && randart_wpn_property(item, RAP_MUTAGENIC)) + retval = true; + + return (retval); +} + bool good_god_dislikes_item_handling(const item_def &item) { return (is_good_god(you.religion) && is_evil_item(item) @@ -3346,19 +3397,8 @@ bool god_dislikes_item_handling(const item_def &item) if (you.religion == GOD_ZIN) { - if (((item.base_type == OBJ_POTIONS && item.sub_type == POT_MUTATION) - || (item.base_type == OBJ_WANDS - && item.sub_type == WAND_POLYMORPH_OTHER)) - && item_type_known(item)) - { + if (item_type_known(item) && is_chaotic_item(item)) return (true); - } - - if (is_random_artefact(item) - && randart_known_wpn_property(item, RAP_MUTAGENIC)) - { - return (true); - } } if (you.religion == GOD_SHINING_ONE) @@ -3378,7 +3418,9 @@ bool god_dislikes_item_handling(const item_def &item) const int item_brand = get_ammo_brand(item); if (item_brand == SPMSL_POISONED || item_brand == SPMSL_CURARE) + { return (true); + } } else if (item.base_type == OBJ_STAVES && (item.sub_type == STAFF_POISON diff --git a/crawl-ref/source/religion.h b/crawl-ref/source/religion.h index dd72be5c46..99f585b283 100644 --- a/crawl-ref/source/religion.h +++ b/crawl-ref/source/religion.h @@ -105,6 +105,7 @@ void beogh_convert_orc(monsters *orc, bool emergency, bool converted_by_follower = false); bool is_holy_item(const item_def& item); bool is_evil_item(const item_def& item); +bool is_chaotic_item(const item_def& item); bool good_god_dislikes_item_handling(const item_def &item); bool god_dislikes_item_handling(const item_def &item); bool trog_burn_spellbooks(); diff --git a/crawl-ref/source/shopping.cc b/crawl-ref/source/shopping.cc index 33ca97f4f9..40cfd757df 100644 --- a/crawl-ref/source/shopping.cc +++ b/crawl-ref/source/shopping.cc @@ -830,6 +830,7 @@ unsigned int item_value( item_def item, bool ident ) valued *= 50; break; + case SPWPN_CHAOS: case SPWPN_SPEED: valued *= 40; break; diff --git a/crawl-ref/source/xom.cc b/crawl-ref/source/xom.cc index fdd3dabe2f..3c0224fe9f 100644 --- a/crawl-ref/source/xom.cc +++ b/crawl-ref/source/xom.cc @@ -202,6 +202,43 @@ static void _xom_makes_you_cast_random_spell(int sever) your_spells(spell, sever, false); } +static void _try_brand_switch(const int item_index) +{ + if (item_index == NON_ITEM) + return; + + item_def &item(mitm[item_index]); + + if (is_unrandom_artefact(item) || is_fixed_artefact(item)) + return; + + if (item.base_type != OBJ_WEAPONS && item.base_type != OBJ_MISSILES) + return; + + int brand; + if (item.base_type == OBJ_WEAPONS) + { + // Only switch already branded items. + if (get_weapon_brand(item) == SPWPN_NORMAL) + return; + + brand = (int) SPWPN_CHAOS; + } + else + { + // Only switch already branded items. + if (get_ammo_brand(item) == SPWPN_NORMAL) + return; + + brand = (int) SPMSL_CHAOS; + } + + if (is_random_artefact(item)) + randart_set_property(item, RAP_BRAND, brand); + else + item.special = brand; +} + static void _xom_make_item(object_class_type base, int subtype, int power) { int thing_created = @@ -213,6 +250,8 @@ static void _xom_make_item(object_class_type base, int subtype, int power) return; } + _try_brand_switch(thing_created); + god_acting gdact(GOD_XOM); move_item_to_grid(&thing_created, you.pos()); @@ -223,6 +262,24 @@ static void _xom_make_item(object_class_type base, int subtype, int power) origin_acquired(mitm[thing_created], GOD_XOM); } +static void _xom_acquirement(object_class_type force_class) +{ + god_acting gdact(GOD_XOM); + + int item_index = NON_ITEM; + + if (!acquirement(force_class, GOD_XOM, false, &item_index) + || item_index == NON_ITEM) + { + god_speaks(GOD_XOM, "\"No, never mind.\""); + return; + } + + _try_brand_switch(item_index); + + stop_running(); +} + static object_class_type _get_unrelated_wield_class(object_class_type ref) { object_class_type objtype = OBJ_WEAPONS; @@ -269,7 +326,7 @@ static bool _xom_annoyance_gift(int power) // For added humour, give the same sub-type. _xom_make_item(weapon->base_type, weapon->sub_type, power * 3); else - acquirement(weapon->base_type, GOD_XOM); + _xom_acquirement(weapon->base_type); return (true); } @@ -315,7 +372,7 @@ static bool _xom_annoyance_gift(int power) _get_unrelated_wield_class(weapon->base_type); if (x_chance_in_y(power, 256)) - acquirement(objtype, GOD_XOM); + _xom_acquirement(objtype); else _xom_make_item(objtype, OBJ_RANDOM, power * 3); return (true); @@ -365,7 +422,7 @@ static bool _xom_gives_item(int power) god_acting gdact(GOD_XOM); - acquirement(objtype, GOD_XOM); + _xom_acquirement(objtype); } else { @@ -384,6 +441,185 @@ static bool _choose_mutatable_monster(const monsters* mon) && !mons_is_submerged(mon)); } +static bool _is_chaos_upgradeable(const item_def &item, + const monsters* mon) +{ + // Since Xom is a god he is capable of changing randarts, but not + // other artifacts. + if (is_artefact(item) && !is_random_artefact(item)) + return (false); + + // Only upgrade permanent items, since the player should get a + // chance to use the item if s/he can defeat the monster. + if (item.flags & ISFLAG_SUMMONED) + return (false); + + // Don't know how to downgrade blessed blades to normal blades. + // Can be justified as good gods protecting blessed blades. + if (is_blessed_blade(item)) + return (false); + + // God gifts from good gods are protected. Also, Beogh hates all + // the other gods so he'll protect his gifts as well. + if (item.orig_monnum < 0) + { + god_type iorig = static_cast(-item.orig_monnum - 2); + if ((iorig > GOD_NO_GOD && iorig < NUM_GODS) + && (is_good_god(iorig) || iorig == GOD_BEOGH)) + { + return (false); + } + } + + // Leave branded items alone, since this is supposed to be an + // upgrade. + if (item.base_type == OBJ_MISSILES) + { + // Don't make boulders or throwing nets of chaos. + if (item.sub_type == MI_LARGE_ROCK + || item.sub_type == MI_THROWING_NET) + { + return (false); + } + + if (get_ammo_brand(item) == SPMSL_NORMAL) + return (true); + } + else + { + // If the weapon is a launcher and the monster is either out + // of ammo or is carrying javelins then don't bother upgrading + // launcher. + if (is_range_weapon(item) + && (mon->inv[MSLOT_MISSILE] == NON_ITEM + || !has_launcher(mitm[mon->inv[MSLOT_MISSILE]]))) + { + return (false); + } + if (get_weapon_brand(item) == SPWPN_NORMAL) + return (true); + } + + return (false); +} + +static bool _choose_chaos_upgrade(const monsters* mon) +{ + // Only choose monsters that will attack. + if (!mon->alive() || mons_attitude(mon) != ATT_HOSTILE + || mons_is_fleeing(mon) || mons_is_panicking(mon)) + { + return (false); + } + + if (mons_itemuse(mon) < MONUSE_STARTING_EQUIPMENT) + return (false); + + // Holy beings are presumably protected by another god, unless they're + // gifts from Xom. + if (mons_is_holy(mon) && mon->god != GOD_XOM) + return (false); + + // God gifts from good gods will be protected by their god from being + // given chaos weapons, while other gods won't mind the help in their + // servants killing the player. + if (mon->god != GOD_NO_GOD && is_good_god(mon->god)) + return (false); + + // Beogh presumably doesn't want Xom messing with his orcs, even if + // it would give them a better weapon. + if (mons_genus(mon->type) == MONS_ORC) + return (false); + + mon_inv_type slots[] = {MSLOT_WEAPON, MSLOT_ALT_WEAPON, MSLOT_MISSILE}; + + // NOTE: Code assumes that the monster will only be carrying one + // missile launcher at a time. + bool special_launcher = false; + for (int i = 0; i < 3; i++) + { + const mon_inv_type slot = slots[i]; + const int midx = mon->inv[slot]; + + if (midx == NON_ITEM) + continue; + const item_def &item(mitm[midx]); + + // Monster already has a chaos weapon, give upgrade to a different + // monster. + if (is_chaotic_item(item)) + return (false); + + if (_is_chaos_upgradeable(item, mon)) + { + if (item.base_type != OBJ_MISSILES) + return (true); + + // If for some weird reason a monster is carrying a bow + // and javelins then branding the javelins is okay since + // they won't be fired by the bow. + if (!special_launcher || !has_launcher(item)) + return (true); + } + + if (is_range_weapon(item)) + { + // If the launcher alters its ammo then branding the monster's + // ammo won't be an upgrade. + int brand = get_weapon_brand(item); + if (brand == SPWPN_FLAME || brand == SPWPN_FROST + || brand == SPWPN_VENOM) + { + special_launcher = true; + } + } + } + + return (false); +} + +static void _do_chaos_upgrade(item_def &item, const monsters* mon) +{ + ASSERT(item.base_type == OBJ_MISSILES + || item.base_type == OBJ_WEAPONS); + ASSERT(!is_unrandom_artefact(item) && !is_fixed_artefact(item)); + + bool seen = false; + if (mon && you.can_see(mon) && item.base_type == OBJ_WEAPONS) + { + seen = true; + + description_level_type desc = mons_friendly(mon) ? DESC_CAP_YOUR : + DESC_CAP_THE; + std::string msg = mon->name(desc); + msg += "'s "; + msg = replace_all(msg, "s's", "s'"); // Proper posessive. + + msg += item.name(DESC_PLAIN, false, false, false); + + msg += " is briefly surrounded by a scintillating arua of " + "random colours."; + + mpr(msg.c_str()); + } + + const int brand = (item.base_type == OBJ_WEAPONS) ? (int) SPWPN_CHAOS : + (int) SPMSL_CHAOS; + + if (is_random_artefact(item)) + { + randart_set_property(item, RAP_BRAND, brand); + if (seen) + randart_wpn_learn_prop(item, RAP_BRAND); + } + else + { + item.special = brand; + if (seen) + set_ident_flags(item, ISFLAG_KNOW_TYPE); + } +} + static monster_type _xom_random_demon(int sever, bool use_greater_demons = true) { const int roll = random2(1000 - (27 - you.experience_level) * 10); @@ -402,8 +638,15 @@ static monster_type _xom_random_demon(int sever, bool use_greater_demons = true) if (dct == DEMON_GREATER && coinflip()) demon = summon_any_holy_being(HOLY_BEING_WARRIOR); else - demon = summon_any_demon( - (use_greater_demons || dct != DEMON_GREATER) ? dct : DEMON_COMMON); + { + const demon_class_type dct2 = + (!use_greater_demons && dct == DEMON_GREATER) ? DEMON_COMMON : dct; + + if (dct2 == DEMON_COMMON && one_chance_in(10)) + demon = MONS_CHAOS_SPAWN; + else + demon = summon_any_demon(dct2); + } return (demon); } @@ -771,6 +1014,37 @@ static bool _xom_is_bad(int sever) done = true; } else if (x_chance_in_y(7, sever)) + { + monsters *mon = + choose_random_nearby_monster(0, _choose_chaos_upgrade); + + if (!mon) + continue; + + god_speaks(GOD_XOM, _get_xom_speech("chaos upgrade").c_str()); + + mon_inv_type slots[] = {MSLOT_WEAPON, MSLOT_ALT_WEAPON, + MSLOT_MISSILE}; + for (int i = 0; i < 3; i++) + { + int idx = mon->inv[slots[i]]; + if (idx == NON_ITEM) + continue; + + item_def &item(mitm[idx]); + if (!_is_chaos_upgradeable(item, mon)) + continue; + + _do_chaos_upgrade(item, mon); + done = true; + break; + } + ASSERT(done); + + // Wake the monster up. + behaviour_event( mon, ME_ALERT, MHITYOU ); + } + else if (x_chance_in_y(8, sever)) { if (you.can_safely_mutate() && player_mutation_level(MUT_MUTATION_RESISTANCE) < 3) @@ -793,7 +1067,7 @@ static bool _xom_is_bad(int sever) } } } - else if (x_chance_in_y(8, sever)) + else if (x_chance_in_y(9, sever)) { if (there_are_monsters_nearby(false, false)) { @@ -819,7 +1093,7 @@ static bool _xom_is_bad(int sever) } } } - else if (x_chance_in_y(9, sever)) + else if (x_chance_in_y(10, sever)) { std::string speech = _get_xom_speech("draining or torment"); @@ -850,7 +1124,7 @@ static bool _xom_is_bad(int sever) } } } - else if (x_chance_in_y(10, sever)) + else if (x_chance_in_y(11, sever)) { std::string speech = _get_xom_speech("hostile monster"); @@ -890,7 +1164,7 @@ static bool _xom_is_bad(int sever) } } } - else if (x_chance_in_y(11, sever)) + else if (x_chance_in_y(12, sever)) { god_speaks(GOD_XOM, _get_xom_speech("major miscast effect").c_str()); -- cgit v1.2.3-54-g00ecf