/* * File: godabil.cc * Summary: God-granted abilities. */ #include "AppHdr.h" #include #include "beam.h" #include "cloud.h" #include "colour.h" #include "coord.h" #include "coordit.h" #include "database.h" #include "directn.h" #include "effects.h" #include "env.h" #include "files.h" #include "godabil.h" #include "invent.h" #include "itemprop.h" #include "items.h" #include "kills.h" #include "message.h" #include "misc.h" #include "mon-act.h" #include "mon-behv.h" #include "mon-iter.h" #include "mon-place.h" #include "mgen_data.h" #include "mon-stuff.h" #include "mon-util.h" #include "mutation.h" #include "options.h" #include "random.h" #include "religion.h" #include "shopping.h" #include "spells1.h" #include "spells3.h" #include "spells4.h" #include "spl-book.h" #include "spl-util.h" #include "state.h" #include "stuff.h" #include "terrain.h" #include "view.h" bool yred_injury_mirror(bool actual) { return (you.religion == GOD_YREDELEMNUL && !player_under_penance() && you.piety >= piety_breakpoint(1) && (!actual || you.duration[DUR_PRAYER])); } bool beogh_water_walk() { return (you.religion == GOD_BEOGH && !player_under_penance() && you.piety >= piety_breakpoint(4)); } bool jiyva_grant_jelly(bool actual) { return (you.religion == GOD_JIYVA && !player_under_penance() && you.piety >= piety_breakpoint(2) && (!actual || you.duration[DUR_PRAYER])); } bool jiyva_remove_bad_mutation() { if (!how_mutated()) { mpr("You have no bad mutations to be cured!"); return (false); } // Ensure that only bad mutations are removed. if (!delete_mutation(RANDOM_BAD_MUTATION, true, false, true, true)) { canned_msg(MSG_NOTHING_HAPPENS); return (false); } mpr("You feel cleansed."); return (true); } bool vehumet_supports_spell(spell_type spell) { if (spell_typematch(spell, SPTYP_CONJURATION | SPTYP_SUMMONING)) return (true); if (spell == SPELL_SHATTER || spell == SPELL_FRAGMENTATION || spell == SPELL_SANDBLAST) { return (true); } return (false); } // Returns false if the invocation fails (no spellbooks in sight, etc.). bool trog_burn_spellbooks() { if (you.religion != GOD_TROG) return (false); god_acting gdact; for (stack_iterator si(you.pos()); si; ++si) { if (si->base_type == OBJ_BOOKS && si->sub_type != BOOK_MANUAL && si->sub_type != BOOK_DESTRUCTION) { mpr("Burning your own feet might not be such a smart idea!"); return (false); } } int totalpiety = 0; for (radius_iterator ri(you.pos(), LOS_RADIUS, true, true, true); ri; ++ri) { // If a grid is blocked, books lying there will be ignored. // Allow bombing of monsters. const unsigned short cloud = env.cgrid(*ri); if (feat_is_solid(grd(*ri)) || cloud != EMPTY_CLOUD && env.cloud[cloud].type != CLOUD_FIRE) { continue; } int count = 0; int rarity = 0; for (stack_iterator si(*ri); si; ++si) { if (si->base_type != OBJ_BOOKS || si->sub_type == BOOK_MANUAL || si->sub_type == BOOK_DESTRUCTION) { continue; } // Ignore {!D} inscribed books. if (!check_warning_inscriptions(*si, OPER_DESTROY)) { mpr("Won't ignite {!D} inscribed book."); continue; } rarity += book_rarity(si->sub_type); // Piety increases by 2 for books never cracked open, else 1. // Conversely, rarity influences the duration of the pyre. if (!item_type_known(*si)) totalpiety += 2; else totalpiety++; dprf("Burned book rarity: %d", rarity); destroy_item(si.link()); count++; } if (count) { if (cloud != EMPTY_CLOUD) { // Reinforce the cloud. mpr("The fire roars with new energy!"); const int extra_dur = count + random2(rarity / 2); env.cloud[cloud].decay += extra_dur * 5; env.cloud[cloud].set_whose(KC_YOU); continue; } const int duration = std::min(4 + count + random2(rarity/2), 23); place_cloud(CLOUD_FIRE, *ri, duration, KC_YOU); mprf(MSGCH_GOD, "The book%s burst%s into flames.", count == 1 ? "" : "s", count == 1 ? "s" : ""); } } if (!totalpiety) { mpr("You cannot see a spellbook to ignite!"); return (false); } else { simple_god_message(" is delighted!", GOD_TROG); gain_piety(totalpiety); } return (true); } static bool _is_yred_enslaved_soul(const monsters* mon) { return (mon->alive() && mons_enslaved_soul(mon)); } static bool _yred_enslaved_souls_on_level_disappear() { bool success = false; for (monster_iterator mi; mi; ++mi) { if (_is_yred_enslaved_soul(*mi)) { #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Undead soul disappearing: %s on level %d, branch %d", mi->name(DESC_PLAIN).c_str(), static_cast(you.your_level), static_cast(you.where_are_you)); #endif simple_monster_message(*mi, " is freed."); // The monster disappears. monster_die(*mi, KILL_DISMISSED, NON_MONSTER); success = true; } } return (success); } static bool _yred_souls_disappear() { return (apply_to_all_dungeons(_yred_enslaved_souls_on_level_disappear)); } void yred_make_enslaved_soul(monsters *mon, bool force_hostile, bool quiet, bool unrestricted) { if (!unrestricted) _yred_souls_disappear(); const int type = mon->type; monster_type soul_type = mons_species(type); const std::string whose = you.can_see(mon) ? apostrophise(mon->name(DESC_CAP_THE)) : mon->pronoun(PRONOUN_CAP_POSSESSIVE); const bool twisted = !unrestricted ? !x_chance_in_y(you.skills[SK_INVOCATIONS] * 20 / 9 + 20, 100) : false; int corps = -1; // If the monster's held in a net, get it out. mons_clear_trapping_net(mon); const monsters orig = *mon; if (twisted) { mon->type = mons_zombie_size(soul_type) == Z_BIG ? MONS_ABOMINATION_LARGE : MONS_ABOMINATION_SMALL; mon->base_monster = MONS_NO_MONSTER; } else { // Drop the monster's corpse, so that it can be properly // re-equipped below. corps = place_monster_corpse(mon, true, true); } // Drop the monster's equipment. monster_drop_ething(mon); // Recreate the monster as an abomination, or as itself before // turning it into a spectral thing below. define_monster(*mon); mon->colour = ETC_UNHOLY; mon->flags |= MF_NO_REWARD; mon->flags |= MF_ENSLAVED_SOUL; if (twisted) // Mark abominations as undead. mon->flags |= MF_HONORARY_UNDEAD; else if (corps != -1) { // Turn the monster into a spectral thing, minus the usual // adjustments for zombified monsters. mon->type = MONS_SPECTRAL_THING; mon->base_monster = soul_type; // Re-equip the spectral thing. equip_undead(mon->pos(), corps, mon->mindex(), mon->base_monster); // Destroy the monster's corpse, as it's no longer needed. destroy_item(corps); } name_zombie(mon, &orig); mons_make_god_gift(mon, GOD_YREDELEMNUL); mon->attitude = !force_hostile ? ATT_FRIENDLY : ATT_HOSTILE; behaviour_event(mon, ME_ALERT, !force_hostile ? MHITNOT : MHITYOU); if (!quiet) { mprf("%s soul %s, and %s.", whose.c_str(), twisted ? "becomes twisted" : "remains intact", !force_hostile ? "is now yours" : "fights you"); } } bool fedhas_passthrough_class(const monster_type mc) { return (you.religion == GOD_FEDHAS && mons_class_is_plant(mc) && mons_class_is_stationary(mc)); } // Fedhas allows worshipers to walk on top of stationary plants and // fungi. bool fedhas_passthrough(const monsters * target) { return (target && fedhas_passthrough_class(target->type) && (target->type != MONS_OKLOB_PLANT || target->attitude != ATT_HOSTILE)); } // Fedhas worshipers can shoot through non-hostile plants, can a // particular beam go through a particular monster? bool fedhas_shoot_through(const bolt & beam, const monsters * victim) { actor * originator = beam.agent(); if (!victim || !originator) return (false); bool origin_worships_fedhas; mon_attitude_type origin_attitude; if (originator->atype() == ACT_PLAYER) { origin_worships_fedhas = you.religion == GOD_FEDHAS; origin_attitude = ATT_FRIENDLY; } else { monsters * temp = dynamic_cast (originator); if (!temp) return (false); origin_worships_fedhas = temp->god == GOD_FEDHAS; origin_attitude = temp->attitude; } return (origin_worships_fedhas && fedhas_protects(victim) && !beam.is_enchantment() && !(beam.is_explosion && beam.in_explosion_phase) && (mons_atts_aligned(victim->attitude, origin_attitude) || victim->neutral() )); } // Turns corpses in LOS into skeletons and grows toadstools on them. // Can also turn zombies into skeletons and destroy ghoul-type monsters. // Returns the number of corpses consumed. int fungal_bloom() { int seen_mushrooms = 0; int seen_corpses = 0; int processed_count = 0; bool kills = false; for (radius_iterator i(you.pos(), LOS_RADIUS); i; ++i) { monsters * target = monster_at(*i); if (target && target->is_summoned()) continue; if (target && target->mons_species() != MONS_TOADSTOOL) { switch (mons_genus(target->mons_species())) { case MONS_ZOMBIE_SMALL: // Maybe turn a zombie into a skeleton. if (mons_skeleton(mons_zombie_base(target))) { processed_count++; monster_type skele_type = MONS_SKELETON_LARGE; if (mons_zombie_size(mons_zombie_base(target)) == Z_SMALL) skele_type = MONS_SKELETON_SMALL; // Killing and replacing the zombie since upgrade_type // doesn't get skeleton speed right (and doesn't // reduce the victim's HP). This is awkward. -cao mgen_data mg(skele_type, target->behaviour, NULL, 0, 0, target->pos(), target->foe, MG_FORCE_BEH | MG_FORCE_PLACE, target->god, mons_zombie_base(target), target->number); unsigned monster_flags = target->flags; int current_hp = target->hit_points; mon_enchant_list ench = target->enchantments; simple_monster_message(target, "'s flesh rots away."); monster_die(target, KILL_MISC, NON_MONSTER, true); int monster = create_monster(mg); env.mons[monster].flags = monster_flags; env.mons[monster].enchantments = ench; if (env.mons[monster].hit_points > current_hp) env.mons[monster].hit_points = current_hp; behaviour_event(&env.mons[monster], ME_ALERT, MHITYOU); continue; } // Else fall through and destroy the zombie. // Ghoul-type monsters are always destroyed. case MONS_GHOUL: { simple_monster_message(target, " rots away and dies."); coord_def pos = target->pos(); int colour = target->colour; int corpse = monster_die(target, KILL_MISC, NON_MONSTER, true); kills = true; // If a corpse didn't drop, create a toadstool. // If one did drop, we will create toadstools from it as usual // later on. if (corpse < 0) { const int mushroom = create_monster( mgen_data(MONS_TOADSTOOL, BEH_FRIENDLY, &you, 0, 0, pos, MHITNOT, MG_FORCE_PLACE, GOD_NO_GOD, MONS_NO_MONSTER, 0, colour), false); if (mushroom != -1) seen_mushrooms++; processed_count++; continue; } break; } default: continue; } } for (stack_iterator j(*i); j; ++j) { bool corpse_on_pos = false; if (j->base_type == OBJ_CORPSES && j->sub_type == CORPSE_BODY) { corpse_on_pos = true; int trial_prob = mushroom_prob(*j); processed_count++; int target_count = 1 + binomial_generator(20, trial_prob); int seen_per; spawn_corpse_mushrooms(*j, target_count, seen_per, BEH_FRIENDLY, true); seen_mushrooms += seen_per; // Either turn this corpse into a skeleton or destroy it. if (mons_skeleton(j->plus)) turn_corpse_into_skeleton(*j); else destroy_item(j->index()); } if (corpse_on_pos && you.see_cell(*i)) seen_corpses++; } } if (seen_mushrooms > 0) mushroom_spawn_message(seen_mushrooms, seen_corpses); if (kills) mprf("That felt like a moral victory."); return (processed_count); } static int _create_plant(coord_def & target, int hp_adjust = 0) { if (actor_at(target) || !mons_class_can_pass(MONS_PLANT, grd(target))) return (0); const int plant = create_monster(mgen_data (MONS_PLANT, BEH_FRIENDLY, &you, 0, 0, target, MHITNOT, MG_FORCE_PLACE, GOD_FEDHAS)); if (plant != -1) { env.mons[plant].flags |= MF_ATT_CHANGE_ATTEMPT; env.mons[plant].max_hit_points += hp_adjust; env.mons[plant].hit_points += hp_adjust; if (you.see_cell(target)) { if (hp_adjust) mpr("A plant grows up from the ground, it is strengthened by Fedhas."); else mpr("A plant grows up from the ground."); } } return (plant != -1); } bool sunlight() { int c_size = 5; int x_offset[] = {-1, 0, 0, 0, 1}; int y_offset[] = { 0,-1, 0, 1, 0}; dist spelld; bolt temp_bolt; temp_bolt.colour = YELLOW; direction(spelld, DIR_TARGET, TARG_HOSTILE, LOS_RADIUS, false, false, false, true, "Select sunlight destination", NULL, true); if (!spelld.isValid) return (false); coord_def base = spelld.target; int evap_count = 0; int plant_count = 0; int processed_count = 0; // This is dealt with outside of the main loop. int cloud_count = 0; // FIXME: Uncomfortable level of code duplication here but the explosion // code in bolt subjects the input radius to r*(r+1) for the threshold and // since r is an integer we can never get just the 4-connected neighbours. // Anyway the bolt code doesn't seem to be well set up to handle the // 'occasional plant' gimmick. for (int i = 0; i < c_size; ++i) { coord_def target = base; target.x += x_offset[i]; target.y += y_offset[i]; if (!in_bounds(target) || feat_is_solid(grd(target))) continue; temp_bolt.explosion_draw_cell(target); actor *victim = actor_at(target); // If this is a water square we will evaporate it. dungeon_feature_type ftype = grd(target); dungeon_feature_type orig_type = ftype; switch (ftype) { case DNGN_SHALLOW_WATER: ftype = DNGN_FLOOR; break; case DNGN_DEEP_WATER: ftype = DNGN_SHALLOW_WATER; break; default: break; } if (orig_type != ftype) { dungeon_terrain_changed(target, ftype); if (you.see_cell(target)) evap_count++; // This is a little awkward but if we evaporated all the way to // the dungeon floor that may have given a monster // ENCH_AQUATIC_LAND, and if that happened the player should get // credit if the monster dies. The enchantment is inflicted via // the dungeon_terrain_changed call chain and that doesn't keep // track of what caused the terrain change. -cao monsters * monster = monster_at(target); if (monster && ftype == DNGN_FLOOR && monster->has_ench(ENCH_AQUATIC_LAND)) { mon_enchant temp = monster->get_ench(ENCH_AQUATIC_LAND); temp.who = KC_YOU; monster->add_ench(temp); } processed_count++; } monsters *mons = monster_at(target); if (victim) { if (!mons) you.backlight(); else { backlight_monsters(target, 1, 0); behaviour_event(mons, ME_ALERT, MHITYOU); } processed_count++; } else if (one_chance_in(100) && ftype >= DNGN_FLOOR_MIN && ftype <= DNGN_FLOOR_MAX && orig_type == DNGN_SHALLOW_WATER) { // Create a plant. const int plant = create_monster(mgen_data(MONS_PLANT, BEH_HOSTILE, &you, 0, 0, target, MHITNOT, MG_FORCE_PLACE, GOD_FEDHAS)); if (plant != -1 && you.see_cell(target)) plant_count++; processed_count++; } } // We damage clousd for a large radius, though. for (radius_iterator ai(base, 7); ai; ++ai) { if (env.cgrid(*ai) != EMPTY_CLOUD) { const int cloudidx = env.cgrid(*ai); if (env.cloud[cloudidx].type == CLOUD_GLOOM) { cloud_count++; delete_cloud(cloudidx); } } } #ifndef USE_TILE // Move the cursor out of the way (it looks weird). cgotoxy(base.x, base.y, GOTO_DNGN); #endif delay(200); if (plant_count) { mprf("%s grow%s in the sunlight.", (plant_count > 1 ? "Some plants": "A plant"), (plant_count > 1 ? "": "s")); } if (evap_count) mprf("Some water evaporates in the bright sunlight."); if (cloud_count) mprf("Sunlight penetrates the thick gloom."); return (processed_count); } template bool less_second(const T & left, const T & right) { return (left.second < right.second); } typedef std::pair point_distance; // dfs starting at origin, find the distance from the origin to the targets // (not leaving LOS, not crossing monsters or solid walls) and store that in // distances static void _path_distance(coord_def & origin, std::vector & targets, std::vector & distances) { std::set exclusion; std::queue fringe; fringe.push(point_distance(origin,0)); int idx = origin.x + origin.y * X_WIDTH; exclusion.insert(idx); while (!fringe.empty()) { point_distance current = fringe.front(); fringe.pop(); // did we hit a target? for (unsigned i = 0; i < targets.size(); ++i) { if (current.first == targets[i]) { distances[i] = current.second; break; } } for (adjacent_iterator adj_it(current.first); adj_it; ++adj_it) { idx = adj_it->x + adj_it->y * X_WIDTH; if (you.see_cell(*adj_it) && !feat_is_solid(env.grid(*adj_it)) && exclusion.insert(idx).second) { monsters * temp = monster_at(*adj_it); if (!temp || (temp->attitude == ATT_HOSTILE && temp->mons_species() != MONS_PLANT && temp->mons_species() != MONS_TOADSTOOL && temp->mons_species() != MONS_FUNGUS && temp->mons_species() != MONS_BALLISTOMYCETE)) { fringe.push(point_distance(*adj_it, current.second+1)); } } } } } // so we are basically going to compute point to point distance between // the points of origin and the end points (origins and targets respecitvely) // We will return a vector consisting of the minimum distances along one // dimension of the distance matrix. static void _point_point_distance(std::vector & origins, std::vector & targets, bool origin_to_target, std::vector & distances) { distances.clear(); // Consider a matrix where the points of origin form the rows and // the target points form the column, we want to take the minimum along // one of those dimensions. if (origin_to_target) distances.resize(origins.size(), INT_MAX); else distances.resize(targets.size(), INT_MAX); std::vector current_distances(targets.size(), 0); for (unsigned i = 0; i < origins.size(); ++i) { for (unsigned j = 0; j < current_distances.size(); ++j) current_distances[j] = INT_MAX; _path_distance(origins[i], targets, current_distances); // So we got the distance from a point of origin to one of the // targets. What should we do with it? if (origin_to_target) { // The minimum of current_distances is points(i) int min_dist = current_distances[0]; for (unsigned j = 1; j < current_distances.size(); ++j) if (current_distances[j] < min_dist) min_dist = current_distances[j]; distances[i] = min_dist; } else { for (unsigned j = 0; j < targets.size(); ++j) { if (i == 0) distances[j] = current_distances[j]; else if (current_distances[j] < distances[j]) distances[j] = current_distances[j]; } } } } // So the idea is we want to decide which adjacent tiles are in the most 'danger' // We claim danger is proportional to the minimum distances from the point to a // (hostile) monster. This function carries out at most 8 depth-first searches // to calculate the distances in question. In practice it should be called for // at most 7 searches since 8 (all adjacent free, > 8 monsters in view) can be // special cased easily. bool prioritise_adjacent(const coord_def &target, std::vector & candidates) { radius_iterator los_it(target, LOS_RADIUS, true, true, true); std::vector mons_positions; // collect hostile monster positions in LOS for ( ; los_it; ++los_it) { monsters *hostile = monster_at(*los_it); if (hostile && hostile->attitude == ATT_HOSTILE) mons_positions.push_back(hostile->pos()); } if (mons_positions.empty()) { std::random_shuffle(candidates.begin(), candidates.end()); return (true); } bool squares_to_monsters = mons_positions.size() > candidates.size(); std::vector distances; // So the idea is we will search from either possible plant locations to // monsters or from monsters to possible plant locations, but honestly the // implementation is unnecessarily tense and doing plants to monsters all // the time would be fine. Yet I'm reluctant to change it because it does // work. if (squares_to_monsters) { _point_point_distance(candidates, mons_positions, squares_to_monsters, distances); } else { _point_point_distance(mons_positions, candidates, squares_to_monsters, distances); } std::vector possible_moves(candidates.size()); for (unsigned i = 0; i < possible_moves.size(); ++i) { possible_moves[i].first = candidates[i]; possible_moves[i].second = distances[i]; } std::sort(possible_moves.begin(), possible_moves.end(), less_second); for (unsigned i = 0; i < candidates.size(); ++i) candidates[i] = possible_moves[i].first; return (true); } // Prompt the user to select a stack of fruit from their inventory. The // user can optionally select only a partial stack of fruit (the count // variable will store the number of fruit the user wants). Return the // index of the item selected in the user's inventory, or a negative // number if the prompt failed (user cancelled or had no fruit). int _prompt_for_fruit(int & count, const char * prompt_string) { int rc = prompt_invent_item(prompt_string, MT_INVLIST, OSEL_FRUIT, true, true, true, '\0', -1, &count); if (prompt_failed(rc)) return (rc); // Return PROMPT_INAPPROPRIATE if the 'object selected isn't // actually fruit. if (!is_fruit(you.inv[rc])) return (PROMPT_INAPPROPRIATE); // Handle it if the user lies about the amount of fruit available. if (count > you.inv[rc].quantity) count = you.inv[rc].quantity; return (rc); } bool _prompt_amount(int max, int & selected, const std::string & prompt) { selected = max; while (true) { msg::streams(MSGCH_PROMPT) << prompt <<" (" << max << " max)"<< std::endl; unsigned char keyin = get_ch(); // Cancel if (keyin == ESCAPE || keyin == ' ') { canned_msg( MSG_OK ); return (false); } // Default is max if (keyin == '\n' || keyin == '\r') return (true); // Otherwise they should enter a digit if (isdigit(keyin)) { selected = keyin - '0'; if (selected > 0 && selected <= max) return (true); } // else they entered some garbage? } return (max); } int _collect_fruit(std::vector > & available_fruit) { int total=0; for (int i = 0; i < ENDOFPACK; i++) { if (you.inv[i].is_valid() && is_fruit(you.inv[i]) ) { total += you.inv[i].quantity; available_fruit.push_back(std::pair (you.inv[i].quantity, i)); } } return (total); } bool _less_first(const std::pair & left, const std::pair & right) { return (left.first < right.first); } void _decrease_amount(std::vector > & available, int amount) { int total_decrease = amount; for (unsigned i=0; i < available.size() && amount > 0; i++) { int decrease_amount = available[i].first; if (decrease_amount > amount) { decrease_amount = amount; } amount -= decrease_amount; dec_inv_item_quantity(available[i].second, decrease_amount); } if (total_decrease > 1) mprf("%d pieces of fruit are consumed!", total_decrease); else mpr("A piece of fruit is consumed!"); } // Create a ring or partial ring around the caster. The user is // prompted to select a stack of fruit, and then plants are placed on open // squares adjacent to the user. Of course, one piece of fruit is // consumed per plant, so a complete ring may not be formed. bool plant_ring_from_fruit() { // How much fruit is available? std::vector > collected_fruit; int total_fruit = _collect_fruit(collected_fruit); // How many adjacent open spaces are there? std::vector adjacent; for (adjacent_iterator adj_it(you.pos()); adj_it; ++adj_it) { if (mons_class_can_pass(MONS_PLANT, env.grid(*adj_it)) && !actor_at(*adj_it)) { adjacent.push_back(*adj_it); } } int max_use = std::min(total_fruit, int(adjacent.size()) ); // Don't prompt if we can't do anything (due to having no fruit or // no squares to place plants on). if (max_use == 0) return (false); // And how many plants does the user want to create? int target_count; if (!_prompt_amount(max_use, target_count, "How many plants will you create?")) { return (false); } if ((int)adjacent.size() > target_count) prioritise_adjacent(you.pos(), adjacent); int hp_adjust = you.skills[SK_INVOCATIONS] * 10; int created_count = 0; for (int i = 0; i < target_count; ++i) { if (_create_plant(adjacent[i], hp_adjust)) created_count++; } std::sort(collected_fruit.begin(), collected_fruit.end(), _less_first); _decrease_amount(collected_fruit, created_count); return (created_count); } // Create a circle of water around the target, with a radius of // approximately 2. This turns normal floor tiles into shallow water // and turns (unoccupied) shallow water into deep water. There is a // chance of spawning plants or fungus on unoccupied dry floor tiles // outside of the rainfall area. Return the number of plants/fungi // created. int rain(const coord_def &target) { int spawned_count = 0; int processed_count = 0; for (radius_iterator rad(target, LOS_RADIUS, true, true, true); rad; ++rad) { // Adjust the shape of the rainfall slightly to make it look // nicer. I want a threshold of 2.5 on the euclidean distance, // so a threshold of 6 prior to the sqrt is close enough. int rain_thresh = 6; coord_def local = *rad - target; dungeon_feature_type ftype = grd(*rad); if (local.abs() > rain_thresh) { // Maybe spawn a plant on (dry, open) squares that are in // LOS but outside the rainfall area. In open space, there // are 213 squares in LOS, and we are going to drop water on // (25-4) of those, so if we want x plants to spawn on // average in open space, the trial probability should be // x/192. if (x_chance_in_y(5, 192) && !actor_at(*rad) && ftype >= DNGN_FLOOR_MIN && ftype <= DNGN_FLOOR_MAX) { const int plant = create_monster(mgen_data (coinflip() ? MONS_PLANT : MONS_FUNGUS, BEH_GOOD_NEUTRAL, &you, 0, 0, *rad, MHITNOT, MG_FORCE_PLACE, GOD_FEDHAS)); if (plant != -1) spawned_count++; processed_count++; } continue; } // Turn regular floor squares only into shallow water. if (ftype >= DNGN_FLOOR_MIN && ftype <= DNGN_FLOOR_MAX) { dungeon_terrain_changed(*rad, DNGN_SHALLOW_WATER); processed_count++; } // We can also turn shallow water into deep water, but we're // just going to skip cases where there is something on the // shallow water. Destroying items will probably be annoying, // and insta-killing monsters is clearly out of the question. else if (!actor_at(*rad) && igrd(*rad) == NON_ITEM && ftype == DNGN_SHALLOW_WATER) { dungeon_terrain_changed(*rad, DNGN_DEEP_WATER); processed_count++; } if (ftype >= DNGN_MINMOVE) { // Maybe place a raincloud. // // The rainfall area is 20 (5*5 - 4 (corners) - 1 (center)); // the expected number of clouds generated by a fixed chance // per tile is 20 * p = expected. Say an Invocations skill // of 27 gives expected 5 clouds. int max_expected = 5; int expected = div_rand_round(max_expected * you.skills[SK_INVOCATIONS], 27); if (x_chance_in_y(expected, 20)) { place_cloud(CLOUD_RAIN, *rad, 10, KC_YOU); processed_count++; } } } if (spawned_count > 0) { mprf("%s grow%s in the rain.", (spawned_count > 1 ? "Some plants" : "A plant"), (spawned_count > 1 ? "" : "s")); } return (processed_count); } // Destroy corpses in the player's LOS (first corpse on a stack only) // and make 1 giant spore per corpse. Spores are given the input as // their starting behavior; the function returns the number of corpses // processed. int corpse_spores(beh_type behavior) { int count = 0; for (radius_iterator rad(you.pos(), LOS_RADIUS, true, true, true); rad; ++rad) { for (stack_iterator stack_it(*rad); stack_it; ++stack_it) { if (stack_it->base_type == OBJ_CORPSES && stack_it->sub_type == CORPSE_BODY) { count++; int rc = create_monster(mgen_data(MONS_GIANT_SPORE, behavior, &you, 0, 0, *rad, MHITNOT, MG_FORCE_PLACE)); if (rc != -1) { env.mons[rc].flags |= MF_ATT_CHANGE_ATTEMPT; if (behavior == BEH_FRIENDLY) { env.mons[rc].behaviour = BEH_WANDER; env.mons[rc].foe = MHITNOT; } } if (mons_skeleton(stack_it->plus)) turn_corpse_into_skeleton(*stack_it); else destroy_item(stack_it->index()); break; } } } return (count); } struct monster_conversion { monster_conversion() { base_monster = NULL; piety_cost = 0; fruit_cost = 0; } monsters * base_monster; int piety_cost; int fruit_cost; monster_type new_type; }; // Given a monster (which should be a plant/fungus), see if // evolve_flora() can upgrade it, and set up a monster_conversion // structure for it. Return true (and fill in possible_monster) if the // monster can be upgraded, and return false otherwise. bool _possible_evolution(monsters * input, monster_conversion & possible_monster) { possible_monster.base_monster = input; switch (input->mons_species()) { case MONS_PLANT: case MONS_BUSH: possible_monster.new_type = MONS_OKLOB_PLANT; possible_monster.fruit_cost = 1; break; case MONS_FUNGUS: case MONS_BALLISTOMYCETE: possible_monster.new_type = MONS_WANDERING_MUSHROOM; possible_monster.piety_cost = 1; break; case MONS_TOADSTOOL: possible_monster.new_type = MONS_WANDERING_MUSHROOM; possible_monster.piety_cost = 2; break; default: return (false); } return (true); } void _collect_adjacent_monsters(std::vector & available, const coord_def & center) { for (adjacent_iterator adjacent(center, false); adjacent; ++adjacent) { monsters * candidate = monster_at(*adjacent); monster_conversion monster_upgrade; if (candidate && _possible_evolution(candidate, monster_upgrade)) available.push_back(monster_upgrade); } } void _cost_summary(int oklob_count, int wandering_count, int total_in_range) { mesclr(true); if (oklob_count) { std::string str = (oklob_count > 1 ? "ts" : "t"); mprf("Upgrading %d plan%s to oklob plan%s (%d fruit)", oklob_count, str.c_str(), str.c_str(), oklob_count); } if (wandering_count) mprf("Upgrading %d fungi to wandering mushroo%s (piety cost)", wandering_count, (wandering_count > 1 ? "ms" : "m ")); } bool evolve_flora() { // Collect adjacent monsters std::vector available_monsters; _collect_adjacent_monsters(available_monsters, you.pos()); // No monsters in range can be upgraded. if (available_monsters.empty() ) { mpr("No flora in range can be evolved."); return (false); } // What are the total costs of all adjacent upgrades? int piety_cost = 0; int fruit_cost = 0; int oklob_generation = 0; int wandering_generation = 0; for (unsigned i=0; i < available_monsters.size(); i++) { piety_cost += available_monsters[i].piety_cost; fruit_cost += available_monsters[i].fruit_cost; switch(available_monsters[i].new_type) { case MONS_OKLOB_PLANT: oklob_generation++; break; case MONS_WANDERING_MUSHROOM: wandering_generation++; break; default: break; } } std::vector > collected_fruit; int total_fruit = _collect_fruit(collected_fruit); int useable_fruit = std::min(total_fruit, fruit_cost); _cost_summary(useable_fruit, wandering_generation, available_monsters.size()); crawl_state.darken_range = 1; viewwindow(false, false); int target_fruit = useable_fruit; // Ask the user how many fruit to use if (useable_fruit > 1) { if(!_prompt_amount(useable_fruit, target_fruit, "How many oklobs will you create?")) { crawl_state.darken_range = -1; viewwindow(false,false); return (false); } } else { delay(500); } crawl_state.darken_range = -1; viewwindow(false, false); int fruit_used = target_fruit; int reduction = fruit_cost - target_fruit; if (int(available_monsters.size()) <= reduction) { mprf("Not enough fruit available."); return false; } int plants_evolved = 0; int toadstools_evolved = 0; int fungi_evolved = 0; std::random_shuffle(available_monsters.begin(), available_monsters.end() ); for (unsigned i=0; i < available_monsters.size(); i++) { monsters * current_plant = available_monsters[i].base_monster; monster_conversion current_target = available_monsters[i]; if (current_target.new_type == MONS_OKLOB_PLANT) { if (target_fruit) target_fruit--; else continue; } else if (you.piety > current_target.piety_cost) { lose_piety(current_target.piety_cost); } // This would wipe out our remaining piety, so don't do it. else { continue; } switch (current_plant->mons_species()) { case MONS_PLANT: plants_evolved++; break; case MONS_FUNGUS: case MONS_BALLISTOMYCETE: fungi_evolved++; break; case MONS_TOADSTOOL: toadstools_evolved++; break; }; current_plant->upgrade_type(current_target.new_type, true, true); current_plant->god = GOD_FEDHAS; current_plant->attitude = ATT_FRIENDLY; current_plant->flags |= MF_NO_REWARD; current_plant->flags |= MF_ATT_CHANGE_ATTEMPT; // Try to remove slowly dying in case we are upgrading a // toadstool, and spore production in case we are upgrading a // ballistomycete. current_plant->del_ench(ENCH_SLOWLY_DYING); current_plant->del_ench(ENCH_SPORE_PRODUCTION); } if (fruit_used) { _decrease_amount(collected_fruit, fruit_used); } return (true); } bool ponderousify_armour() { int item_slot = -1; do { if (item_slot == -1) { item_slot = prompt_invent_item("Make which item ponderous?", MT_INVLIST, OSEL_PONDER_ARM, true, true, false); } if (prompt_failed(item_slot)) return (false); item_def& arm(you.inv[item_slot]); if (!is_enchantable_armour(arm, true, true) || get_armour_ego_type(arm) != SPARM_NORMAL || get_armour_slot(arm) != EQ_BODY_ARMOUR) { mpr("This armour can't be made ponderous. " "Choose a different one, or Esc to abort."); if (Options.auto_list) more(); item_slot = -1; mpr("You can't enchant that."); //does not appear continue; } //make item desc runed if desc was vanilla? set_item_ego_type(arm, OBJ_ARMOUR, SPARM_PONDEROUSNESS); you.redraw_armour_class = true; you.redraw_evasion = true; simple_god_message(" says: Use this wisely!"); return (true); } while (true); return true; } static int _slouch_monsters(coord_def where, int pow, int, actor* agent) { monsters* mon = monster_at(where); if (mon == NULL || mons_is_stationary(mon) || mon->cannot_move() || mons_is_projectile(mon->type)) return (0); int dmg = (mon->speed - 1000/player_movement_speed()/player_speed()); dmg = (dmg > 0 ? roll_dice(dmg*4, 3)/2 : 0); mon->hurt(agent, dmg, BEAM_MMISSILE, true); return (1); } int cheibriados_slouch(int pow) { return (apply_area_visible(_slouch_monsters, pow)); } //////////////////////////////////////////////////////////////////////////// static int _lugonu_warp_monster(coord_def where, int pow, int, actor *) { if (!in_bounds(where)) return (0); monsters* mon = monster_at(where); if (mon == NULL) return (0); if (!mon->friendly()) behaviour_event(mon, ME_ANNOY, MHITYOU); if (mon->check_res_magic(pow * 2)) { mprf("%s %s.", mon->name(DESC_CAP_THE).c_str(), mons_resist_string(mon)); return (1); } const int damage = 1 + random2(pow / 6); if (mons_genus(mon->type) == MONS_BLINK_FROG) mon->heal(damage, false); else if (!mon->check_res_magic(pow)) { mon->hurt(&you, damage); if (!mon->alive()) return (1); } mon->blink(); return (1); } static void _lugonu_warp_area(int pow) { apply_area_around_square(_lugonu_warp_monster, you.pos(), pow); } void lugonu_bends_space() { const int pow = 4 + skill_bump(SK_INVOCATIONS); const bool area_warp = random2(pow) > 9; mprf("Space bends %saround you!", area_warp ? "sharply " : ""); if (area_warp) _lugonu_warp_area(pow); random_blink(false, true); const int damage = roll_dice(1, 4); ouch(damage, NON_MONSTER, KILLED_BY_WILD_MAGIC, "a spatial distortion"); } //////////////////////////////////////////////////////////////////////// void cheibriados_time_bend(int pow) { mpr("The flow of time bends around you."); for (adjacent_iterator ai(you.pos()); ai; ++ai) { monsters* mon = monster_at(*ai); if (mon && !mons_is_stationary(mon)) { if (roll_dice(mon->hit_dice, 3) > random2avg(pow, 2)) { mprf("%s %s.", mon->name(DESC_CAP_THE).c_str(), mons_resist_string(mon)); continue; } simple_god_message( make_stringf(" rebukes %s.", mon->name(DESC_NOCAP_THE).c_str()).c_str(), GOD_CHEIBRIADOS); do_slow_monster(mon, KC_YOU); } } } void cheibriados_time_step(int pow) // pow is the number of turns to skip { const coord_def old_pos = you.pos(); mpr("You step out of the flow of time."); flash_view(LIGHTBLUE); you.moveto(coord_def(0, 0)); you.duration[DUR_TIME_STEP] = pow; you.time_taken = 10; do { run_environment_effects(); handle_monsters(); manage_clouds(); } while (--you.duration[DUR_TIME_STEP] > 0); // Update corpses, etc. This does also shift monsters, but only by // a tiny bit. update_level(pow * 10); #ifndef USE_TILE delay(1000); #endif you.moveto(old_pos); you.duration[DUR_TIME_STEP] = 0; flash_view(0); mpr("You return to the normal time flow."); }