From eef51d7a1ec7ce7e471d76f82154d2b147d6228b Mon Sep 17 00:00:00 2001 From: Charles Otto Date: Fri, 6 Nov 2009 20:46:30 -0500 Subject: Move Feawn's abilities to godabil.h/cc --- crawl-ref/source/godabil.cc | 870 +++++++++++++++++++++++++++++++++++++++++++ crawl-ref/source/godabil.h | 8 + crawl-ref/source/godwrath.cc | 1 + crawl-ref/source/spells2.cc | 860 ------------------------------------------ crawl-ref/source/spells2.h | 13 - 5 files changed, 879 insertions(+), 873 deletions(-) diff --git a/crawl-ref/source/godabil.cc b/crawl-ref/source/godabil.cc index eaaa52e861..b176f06005 100644 --- a/crawl-ref/source/godabil.cc +++ b/crawl-ref/source/godabil.cc @@ -5,6 +5,9 @@ #include "AppHdr.h" +#include + +#include "beam.h" #include "cloud.h" #include "colour.h" #include "coord.h" @@ -20,6 +23,7 @@ #include "misc.h" #include "mon-act.h" #include "mon-behv.h" +#include "monplace.h" #include "monstuff.h" #include "mon-util.h" #include "mutation.h" @@ -319,6 +323,872 @@ bool feawn_passthrough(const monsters * target) || target->attitude != ATT_HOSTILE)); } + +// 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) + { + actor *target = actor_at(*i); + if (target && (target->atype() == ACT_PLAYER + || target->is_summoned())) + { + continue; + } + + monsters * mons = monster_at(*i); + + if (mons && mons->mons_species() != MONS_TOADSTOOL) + { + switch (mons_genus(mons->mons_species())) + { + case MONS_ZOMBIE_SMALL: + // Maybe turn a zombie into a skeleton. + if (mons_skeleton(mons_zombie_base(mons))) + { + processed_count++; + + monster_type skele_type = MONS_SKELETON_LARGE; + if (mons_zombie_size(mons_zombie_base(mons)) == Z_SMALL) + skele_type = MONS_SKELETON_SMALL; + + simple_monster_message(mons, "'s flesh rots away."); + + mons->upgrade_type(skele_type, true, true); + behaviour_event(mons, ME_ALERT, MHITYOU); + + continue; + } + // Else fall through and destroy the zombie. + // Ghoul-type monsters are always destroyed. + case MONS_GHOUL: + { + simple_monster_message(mons, " rots away and dies."); + + coord_def pos = mons->pos(); + int colour = mons->colour; + int corpse = monster_die(mons, 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, + 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 && observe_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) +{ + 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, + 0, + 0, + target, + MHITNOT, + MG_FORCE_PLACE, GOD_FEAWN)); + + + if (plant != -1) + { + env.mons[plant].flags |= MF_ATT_CHANGE_ATTEMPT; + if (observe_cell(target)) + 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; + + // 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); + + switch (ftype) + { + case DNGN_SHALLOW_WATER: + ftype = DNGN_FLOOR; + break; + + case DNGN_DEEP_WATER: + ftype = DNGN_SHALLOW_WATER; + break; + + default: + break; + } + + if (grd(target) != ftype) + { + grd(target) = ftype; + if (observe_cell(target)) + evap_count++; + } + + monsters *mons = monster_at(target); + + // Pop submerged status. + if (mons && mons_habitat(mons) == HT_WATER) + { + mons->del_ench(ENCH_SUBMERGED); + if (ftype == DNGN_FLOOR) + mons->add_ench(mon_enchant(ENCH_AQUATIC_LAND, 0, KC_YOU)); + } + + if (victim) + { + if (!mons) + you.backlight(); + else + backlight_monsters(target, 1, 0); + } + else if (one_chance_in(100) + && ftype >= DNGN_FLOOR_MIN + && ftype <= DNGN_FLOOR_MAX) + { + // Create a plant. + const int plant = create_monster(mgen_data(MONS_PLANT, + BEH_HOSTILE, + 0, + 0, + target, + MHITNOT, + MG_FORCE_PLACE, + GOD_FEAWN)); + + if (plant != -1 && observe_cell(target)) + plant_count++; + } + } + // move the cursor out of the way (it looks weird ok) +#ifndef USE_TILE + 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."); + + return (true); +} + +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; i < current_distances.size(); ++i) + 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); +} + +// 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() +{ + int possible_count; + int created_count = 0; + int rc = _prompt_for_fruit(possible_count, + "Use which fruit? [0-9] specify amount"); + + // Prompt failed? + if (rc < 0) + return (false); + + 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); + } + } + + if ((int)adjacent.size() > possible_count) + { + prioritise_adjacent(you.pos(), adjacent); + } + + unsigned target_count = + (possible_count < (int)adjacent.size()) ? possible_count + : adjacent.size(); + + for (unsigned i = 0; i < target_count; ++i) + { + if (_create_plant(adjacent[i])) + created_count++; + } + + dec_inv_item_quantity(rc, 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; + 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, + 0, + 0, + *rad, + MHITNOT, + MG_FORCE_PLACE, GOD_FEAWN)); + + if (plant != -1) + spawned_count++; + } + + continue; + } + + // Turn regular floor squares only into shallow water. + if (ftype >= DNGN_FLOOR_MIN && ftype <= DNGN_FLOOR_MAX) + { + grd(*rad) = DNGN_SHALLOW_WATER; + // Remove blood stains as well. + env.map(*rad).property &= ~(FPROP_BLOODY); + + monsters *mon = monster_at(*rad); + if (mon && mon->has_ench(ENCH_AQUATIC_LAND)) + mon->del_ench(ENCH_AQUATIC_LAND); + } + // 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) + { + grd(*rad) = DNGN_DEEP_WATER; + } + + 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); + } + + + } + + if (spawned_count > 0) + { + mprf("%s grow%s in the rain.", + (spawned_count > 1 ? "Some plants" : "A plant"), + (spawned_count > 1 ? "" : "s")); + } + + return (spawned_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, + 0, + 0, + *rad, + MHITNOT, + MG_FORCE_PLACE)); + + if (rc!=-1) + env.mons[rc].flags |= MF_ATT_CHANGE_ATTEMPT; + + if (mons_skeleton(stack_it->plus)) + turn_corpse_into_skeleton(*stack_it); + else + destroy_item(stack_it->index()); + + break; + } + } + } + + return (count); +} + +struct monster_conversion +{ + monsters * base_monster; + int cost; + monster_type new_type; +}; + +bool operator<(const monster_conversion & left, + const monster_conversion & right) +{ + if (left.cost == right.cost) + return (coinflip()); + + return (left.cost < right.cost); +} + +// 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) +{ + int plant_cost = 10; + int toadstool_cost = 1; + int fungus_cost = 5; + + possible_monster.base_monster = input; + switch (input->mons_species()) + { + case MONS_PLANT: + possible_monster.cost = plant_cost; + possible_monster.new_type = MONS_OKLOB_PLANT; + break; + + case MONS_FUNGUS: + case MONS_BALLISTOMYCETE: + possible_monster.cost = fungus_cost; + possible_monster.new_type = MONS_WANDERING_MUSHROOM; + break; + + case MONS_TOADSTOOL: + possible_monster.cost = toadstool_cost; + possible_monster.new_type = MONS_BALLISTOMYCETE; + break; + + default: + return (false); + } + + return (true); +} + +bool evolve_flora() +{ + int rc; + int available_count; + + int points_per_fruit = 8; + int oklob_cost = 10; + + float approx_oklob_rate = float(points_per_fruit)/float(oklob_cost); + + char prompt_string[100]; + memset(prompt_string,0,100); + sprintf(prompt_string, + "Use which fruit? %1.1f oklob plants per fruit, [0-9] specify amount", + approx_oklob_rate); + + rc = _prompt_for_fruit(available_count, prompt_string); + + // Prompt failed? + if (rc < 0) + return (false); + + int points = points_per_fruit * available_count; + int starting_points = points; + + std::priority_queue available_targets; + + monster_conversion temp_conversion; + + for (radius_iterator rad(you.pos(), LOS_RADIUS, true, true, true); + rad; ++rad) + { + monsters * target = monster_at(*rad); + + if (!target) + continue; + + if (_possible_evolution(target, temp_conversion)) + available_targets.push(temp_conversion); + } + + // Nothing available to upgrade. + if (available_targets.empty()) + { + mpr("No flora in sight can be evolved."); + return (false); + } + + int plants_evolved = 0; + int toadstools_evolved = 0; + int fungi_evolved = 0; + + while (!available_targets.empty() && points > 0) + { + monster_conversion current_target = available_targets.top(); + + monsters * current_plant = current_target.base_monster; + available_targets.pop(); + + // Can we afford this thing? + if (current_target.cost > points) + continue; + + points -= current_target.cost; + + 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_FEAWN; + current_plant->attitude = ATT_FRIENDLY; + current_plant->flags |= MF_CREATED_FRIENDLY; + 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 + // fungus. + current_plant->del_ench(ENCH_SLOWLY_DYING); + current_plant->del_ench(ENCH_SPORE_PRODUCTION); + + if (current_plant->mons_species() == MONS_BALLISTOMYCETE) + current_plant->add_ench(ENCH_SPORE_PRODUCTION); + + // Maybe we can upgrade it again? + if (_possible_evolution(current_plant, temp_conversion) + && temp_conversion.cost <= points) + { + available_targets.push(temp_conversion); + } + } + + // How many pieces of fruit did we use up? + int points_used = starting_points - points; + int fruit_used = points_used / points_per_fruit; + if (points_used % points_per_fruit) + fruit_used++; + + // The player didn't have enough points to upgrade anything (probably + // supplied only one fruit). + if (!fruit_used) + { + mpr("Not enough fruit to cause evolution."); + return (false); + } + + dec_inv_item_quantity(rc, fruit_used); + + // Mention how many plants were used. + if (fruit_used > 1) + mprf("%d pieces of fruit are consumed!", fruit_used); + else + mpr("A piece of fruit is consumed!"); + + // Messaging for generated plants. + if (plants_evolved > 1) + mprf("%d plants can now spit acid.", plants_evolved); + else if (plants_evolved == 1) + mpr("A plant can now spit acid."); + + if (toadstools_evolved > 1) + mprf("%d toadstools gained stability.", toadstools_evolved); + else if (toadstools_evolved == 1) + mpr("A toadstool gained stability."); + + if (fungi_evolved > 1) + { + mprf("%d fungal colonies can now pick up their mycelia and move.", + fungi_evolved); + } + else if (fungi_evolved == 1) + mpr("A fungal colony can now pick up its mycelia and move."); + + return (true); +} + bool ponderousify_armour() { int item_slot = -1; diff --git a/crawl-ref/source/godabil.h b/crawl-ref/source/godabil.h index 81cd805150..02f90c5baa 100644 --- a/crawl-ref/source/godabil.h +++ b/crawl-ref/source/godabil.h @@ -19,6 +19,14 @@ bool beogh_water_walk(); void yred_make_enslaved_soul(monsters *mon, bool force_hostile = false, bool quiet = false, bool unrestricted = false); bool feawn_passthrough(const monsters * target); +int fungal_bloom(); +bool sunlight(); +bool prioritise_adjacent(const coord_def &target, + std::vector &candidates); +bool plant_ring_from_fruit(); +int rain(const coord_def &target); +int corpse_spores(beh_type behavior = BEH_FRIENDLY); +bool evolve_flora(); bool vehumet_supports_spell(spell_type spell); diff --git a/crawl-ref/source/godwrath.cc b/crawl-ref/source/godwrath.cc index a17ce1b727..088d5083de 100644 --- a/crawl-ref/source/godwrath.cc +++ b/crawl-ref/source/godwrath.cc @@ -15,6 +15,7 @@ #include "effects.h" #include "enum.h" #include "food.h" +#include "godabil.h" #include "goditem.h" #include "it_use2.h" #include "message.h" diff --git a/crawl-ref/source/spells2.cc b/crawl-ref/source/spells2.cc index bbeb789948..034e891c6c 100644 --- a/crawl-ref/source/spells2.cc +++ b/crawl-ref/source/spells2.cc @@ -13,7 +13,6 @@ #include #include #include -#include #include "externs.h" @@ -1716,862 +1715,3 @@ bool cast_conjure_ball_lightning(int pow, god_type god) return (success); } - -// 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) - { - actor *target = actor_at(*i); - if (target && (target->atype() == ACT_PLAYER - || target->is_summoned())) - { - continue; - } - - monsters * mons = monster_at(*i); - - if (mons && mons->mons_species() != MONS_TOADSTOOL) - { - switch (mons_genus(mons->mons_species())) - { - case MONS_ZOMBIE_SMALL: - // Maybe turn a zombie into a skeleton. - if (mons_skeleton(mons_zombie_base(mons))) - { - processed_count++; - - monster_type skele_type = MONS_SKELETON_LARGE; - if (mons_zombie_size(mons_zombie_base(mons)) == Z_SMALL) - skele_type = MONS_SKELETON_SMALL; - - simple_monster_message(mons, "'s flesh rots away."); - - mons->upgrade_type(skele_type, true, true); - behaviour_event(mons, ME_ALERT, MHITYOU); - - continue; - } - // Else fall through and destroy the zombie. - // Ghoul-type monsters are always destroyed. - case MONS_GHOUL: - { - simple_monster_message(mons, " rots away and dies."); - - coord_def pos = mons->pos(); - int colour = mons->colour; - int corpse = monster_die(mons, 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, - 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 && observe_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); -} - -int create_plant(coord_def & target) -{ - 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, - 0, - 0, - target, - MHITNOT, - MG_FORCE_PLACE, GOD_FEAWN)); - - - if (plant != -1) - { - env.mons[plant].flags |= MF_ATT_CHANGE_ATTEMPT; - if (observe_cell(target)) - 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; - - // 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); - - switch (ftype) - { - case DNGN_SHALLOW_WATER: - ftype = DNGN_FLOOR; - break; - - case DNGN_DEEP_WATER: - ftype = DNGN_SHALLOW_WATER; - break; - - default: - break; - } - - if (grd(target) != ftype) - { - grd(target) = ftype; - if (observe_cell(target)) - evap_count++; - } - - monsters *mons = monster_at(target); - - // Pop submerged status. - if (mons && mons_habitat(mons) == HT_WATER) - { - mons->del_ench(ENCH_SUBMERGED); - if (ftype == DNGN_FLOOR) - mons->add_ench(mon_enchant(ENCH_AQUATIC_LAND, 0, KC_YOU)); - } - - if (victim) - { - if (!mons) - you.backlight(); - else - backlight_monsters(target, 1, 0); - } - else if (one_chance_in(100) - && ftype >= DNGN_FLOOR_MIN - && ftype <= DNGN_FLOOR_MAX) - { - // Create a plant. - const int plant = create_monster(mgen_data(MONS_PLANT, - BEH_HOSTILE, - 0, - 0, - target, - MHITNOT, - MG_FORCE_PLACE, - GOD_FEAWN)); - - if (plant != -1 && observe_cell(target)) - plant_count++; - } - } - // move the cursor out of the way (it looks weird ok) -#ifndef USE_TILE - 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."); - - return (true); -} - -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 -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. -void point_point(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; i < current_distances.size(); ++i) - 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(candidates, mons_positions, squares_to_monsters, distances); - else - point_point(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); -} - -// 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() -{ - int possible_count; - int created_count = 0; - int rc = _prompt_for_fruit(possible_count, - "Use which fruit? [0-9] specify amount"); - - // Prompt failed? - if (rc < 0) - return (false); - - 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); - } - } - - if ((int)adjacent.size() > possible_count) - { - prioritise_adjacent(you.pos(), adjacent); - } - - unsigned target_count = - (possible_count < (int)adjacent.size()) ? possible_count - : adjacent.size(); - - for (unsigned i = 0; i < target_count; ++i) - { - if (create_plant(adjacent[i])) - created_count++; - } - - dec_inv_item_quantity(rc, 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; - 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, - 0, - 0, - *rad, - MHITNOT, - MG_FORCE_PLACE, GOD_FEAWN)); - - if (plant != -1) - spawned_count++; - } - - continue; - } - - // Turn regular floor squares only into shallow water. - if (ftype >= DNGN_FLOOR_MIN && ftype <= DNGN_FLOOR_MAX) - { - grd(*rad) = DNGN_SHALLOW_WATER; - // Remove blood stains as well. - env.map(*rad).property &= ~(FPROP_BLOODY); - - monsters *mon = monster_at(*rad); - if (mon && mon->has_ench(ENCH_AQUATIC_LAND)) - mon->del_ench(ENCH_AQUATIC_LAND); - } - // 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) - { - grd(*rad) = DNGN_DEEP_WATER; - } - - 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); - } - - - } - - if (spawned_count > 0) - { - mprf("%s grow%s in the rain.", - (spawned_count > 1 ? "Some plants" : "A plant"), - (spawned_count > 1 ? "" : "s")); - } - - return (spawned_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, - 0, - 0, - *rad, - MHITNOT, - MG_FORCE_PLACE)); - - if (rc!=-1) - env.mons[rc].flags |= MF_ATT_CHANGE_ATTEMPT; - - if (mons_skeleton(stack_it->plus)) - turn_corpse_into_skeleton(*stack_it); - else - destroy_item(stack_it->index()); - - break; - } - } - } - - return (count); -} - -struct monster_conversion -{ - monsters * base_monster; - int cost; - monster_type new_type; -}; - -bool operator<(const monster_conversion & left, - const monster_conversion & right) -{ - if (left.cost == right.cost) - return (coinflip()); - - return (left.cost < right.cost); -} - -// 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) -{ - int plant_cost = 10; - int toadstool_cost = 1; - int fungus_cost = 5; - - possible_monster.base_monster = input; - switch (input->mons_species()) - { - case MONS_PLANT: - possible_monster.cost = plant_cost; - possible_monster.new_type = MONS_OKLOB_PLANT; - break; - - case MONS_FUNGUS: - case MONS_BALLISTOMYCETE: - possible_monster.cost = fungus_cost; - possible_monster.new_type = MONS_WANDERING_MUSHROOM; - break; - - case MONS_TOADSTOOL: - possible_monster.cost = toadstool_cost; - possible_monster.new_type = MONS_BALLISTOMYCETE; - break; - - default: - return (false); - } - - return (true); -} - -bool evolve_flora() -{ - int rc; - int available_count; - - int points_per_fruit = 8; - int oklob_cost = 10; - - float approx_oklob_rate = float(points_per_fruit)/float(oklob_cost); - - char prompt_string[100]; - memset(prompt_string,0,100); - sprintf(prompt_string, - "Use which fruit? %1.1f oklob plants per fruit, [0-9] specify amount", - approx_oklob_rate); - - rc = _prompt_for_fruit(available_count, prompt_string); - - // Prompt failed? - if (rc < 0) - return (false); - - int points = points_per_fruit * available_count; - int starting_points = points; - - std::priority_queue available_targets; - - monster_conversion temp_conversion; - - for (radius_iterator rad(you.pos(), LOS_RADIUS, true, true, true); - rad; ++rad) - { - monsters * target = monster_at(*rad); - - if (!target) - continue; - - if (_possible_evolution(target, temp_conversion)) - available_targets.push(temp_conversion); - } - - // Nothing available to upgrade. - if (available_targets.empty()) - { - mpr("No flora in sight can be evolved."); - return (false); - } - - int plants_evolved = 0; - int toadstools_evolved = 0; - int fungi_evolved = 0; - - while (!available_targets.empty() && points > 0) - { - monster_conversion current_target = available_targets.top(); - - monsters * current_plant = current_target.base_monster; - available_targets.pop(); - - // Can we afford this thing? - if (current_target.cost > points) - continue; - - points -= current_target.cost; - - 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_FEAWN; - current_plant->attitude = ATT_FRIENDLY; - current_plant->flags |= MF_CREATED_FRIENDLY; - 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 - // fungus. - current_plant->del_ench(ENCH_SLOWLY_DYING); - current_plant->del_ench(ENCH_SPORE_PRODUCTION); - - if (current_plant->mons_species() == MONS_BALLISTOMYCETE) - current_plant->add_ench(ENCH_SPORE_PRODUCTION); - - // Maybe we can upgrade it again? - if (_possible_evolution(current_plant, temp_conversion) - && temp_conversion.cost <= points) - { - available_targets.push(temp_conversion); - } - } - - // How many pieces of fruit did we use up? - int points_used = starting_points - points; - int fruit_used = points_used / points_per_fruit; - if (points_used % points_per_fruit) - fruit_used++; - - // The player didn't have enough points to upgrade anything (probably - // supplied only one fruit). - if (!fruit_used) - { - mpr("Not enough fruit to cause evolution."); - return (false); - } - - dec_inv_item_quantity(rc, fruit_used); - - // Mention how many plants were used. - if (fruit_used > 1) - mprf("%d pieces of fruit are consumed!", fruit_used); - else - mpr("A piece of fruit is consumed!"); - - // Messaging for generated plants. - if (plants_evolved > 1) - mprf("%d plants can now spit acid.", plants_evolved); - else if (plants_evolved == 1) - mpr("A plant can now spit acid."); - - if (toadstools_evolved > 1) - mprf("%d toadstools gained stability.", toadstools_evolved); - else if (toadstools_evolved == 1) - mpr("A toadstool gained stability."); - - if (fungi_evolved > 1) - { - mprf("%d fungal colonies can now pick up their mycelia and move.", - fungi_evolved); - } - else if (fungi_evolved == 1) - mpr("A fungal colony can now pick up its mycelia and move."); - - return (true); -} diff --git a/crawl-ref/source/spells2.h b/crawl-ref/source/spells2.h index dcc02dfed2..943b85e98e 100644 --- a/crawl-ref/source/spells2.h +++ b/crawl-ref/source/spells2.h @@ -27,19 +27,6 @@ void cast_refrigeration(int pow); void cast_toxic_radiance(void); void drain_life(int pow); -int fungal_bloom(); -int create_plant(coord_def & target); -//bool plant_from_fruit(); -bool sunlight(); - -bool prioritise_adjacent(const coord_def &target, - std::vector &candidates); -bool plant_ring_from_fruit(); - -int rain(const coord_def &target); -int corpse_spores(beh_type behavior = BEH_FRIENDLY); -bool evolve_flora(); - bool restore_stat(unsigned char which_stat, unsigned char stat_gain, bool suppress_msg, bool recovery = false); -- cgit v1.2.3-54-g00ecf