/* * File: cloud.cc * Summary: Functions related to clouds. * Written by: Brent Ross * * Creating a cloud module so all the cloud stuff can be isolated. */ #include "AppHdr.h" #include #include "externs.h" #include "areas.h" #include "branch.h" #include "cloud.h" #include "colour.h" #include "coord.h" #include "coordit.h" #include "dungeon.h" #include "fprop.h" #include "mapmark.h" #include "ouch.h" #include "player.h" #include "random.h" #include "spells4.h" #include "stuff.h" #include "env.h" #include "terrain.h" #ifdef USE_TILE #include "tiledef-gui.h" #include "tiledef-main.h" #endif #include "mutation.h" static int _actual_spread_rate(cloud_type type, int spread_rate) { if (spread_rate >= 0) return spread_rate; switch (type) { case CLOUD_GLOOM: return 50; case CLOUD_STEAM: case CLOUD_GREY_SMOKE: case CLOUD_BLACK_SMOKE: return 22; case CLOUD_RAIN: return 11; default: return 0; } } #ifdef DEBUG static bool _killer_whose_match(kill_category whose, killer_type killer) { switch (whose) { case KC_YOU: return (killer == KILL_YOU_MISSILE || killer == KILL_YOU_CONF); case KC_FRIENDLY: return (killer == KILL_MON_MISSILE || killer == KILL_YOU_CONF); case KC_OTHER: return (killer == KILL_MON_MISSILE || killer == KILL_MISCAST || killer == KILL_MISC); case KC_NCATEGORIES: ASSERT(false); } return (false); } #endif static void _new_cloud( int cloud, cloud_type type, const coord_def& p, int decay, kill_category whose, killer_type killer, unsigned char spread_rate, int colour, std::string name, std::string tile) { ASSERT( env.cloud[cloud].type == CLOUD_NONE ); ASSERT(_killer_whose_match(whose, killer)); cloud_struct& c = env.cloud[cloud]; c.type = type; c.decay = decay; c.pos = p; c.whose = whose; c.killer = killer; c.spread_rate = spread_rate; c.colour = colour; c.name = name; #ifdef USE_TILE if (!tile.empty()) { unsigned int index; if (!tile_main_index(tile.c_str(), index)) { mprf(MSGCH_ERROR, "Invalid tile requested for cloud: '%s'.", tile.c_str()); tile = ""; } } #endif c.tile = tile; env.cgrid(p) = cloud; env.cloud_no++; } static void _place_new_cloud(cloud_type cltype, const coord_def& p, int decay, kill_category whose, killer_type killer, int spread_rate = -1, int colour = -1, std::string name = "", std::string tile = "") { if (env.cloud_no >= MAX_CLOUDS) return; // Find slot for cloud. for (int ci = 0; ci < MAX_CLOUDS; ci++) { if (env.cloud[ci].type == CLOUD_NONE) // i.e., is empty { _new_cloud( ci, cltype, p, decay, whose, killer, spread_rate, colour, name, tile ); break; } } } static int _spread_cloud(const cloud_struct &cloud) { const int spreadch = cloud.decay > 30? 80 : cloud.decay > 20? 50 : 30; int extra_decay = 0; for ( adjacent_iterator ai(cloud.pos); ai; ++ai ) { if (random2(100) >= spreadch) continue; if (!in_bounds(*ai) || env.cgrid(*ai) != EMPTY_CLOUD || feat_is_solid(grd(*ai)) || is_sanctuary(*ai) && !is_harmless_cloud(cloud.type)) { continue; } int newdecay = cloud.decay / 2 + 1; if (newdecay >= cloud.decay) newdecay = cloud.decay - 1; _place_new_cloud( cloud.type, *ai, newdecay, cloud.whose, cloud.killer, cloud.spread_rate, cloud.colour, cloud.name, cloud.tile ); extra_decay += 8; } return (extra_decay); } static void _spread_fire(const cloud_struct &cloud) { int make_flames = one_chance_in(5); for ( adjacent_iterator ai(cloud.pos); ai; ++ai ) { if (!in_bounds(*ai) || env.cgrid(*ai) != EMPTY_CLOUD || is_sanctuary(*ai)) continue; // burning trees produce flames all around if (!cell_is_solid(*ai) && make_flames) _place_new_cloud( CLOUD_FIRE, *ai, cloud.decay/2+1, cloud.whose, cloud.killer, cloud.spread_rate, cloud.colour, cloud.name, cloud.tile ); // forest fire doesn't spread in all directions at once, // every neighbouring square gets a separate roll if (grd(*ai) == DNGN_TREES && one_chance_in(20)) { if (you.see_cell(*ai)) mpr("The forest fire spreads!"); grd(*ai) = dgn_tree_base_feature_at(*ai); _place_new_cloud( cloud.type, *ai, random2(30)+25, cloud.whose, cloud.killer, cloud.spread_rate, cloud.colour, cloud.name, cloud.tile ); } } } static void _cloud_fire_interacts_with_terrain(const cloud_struct &cloud) { for (adjacent_iterator ai(cloud.pos); ai; ++ai) { const coord_def p(*ai); if (feat_is_watery(grd(p)) && env.cgrid(p) == EMPTY_CLOUD) { _place_new_cloud(CLOUD_STEAM, p, cloud.decay / 2 + 1, cloud.whose, cloud.killer); } } } void cloud_interacts_with_terrain(const cloud_struct &cloud) { if (cloud.type == CLOUD_FIRE || cloud.type == CLOUD_FOREST_FIRE) _cloud_fire_interacts_with_terrain(cloud); } static void _dissipate_cloud(int cloudidx, int dissipate) { cloud_struct &cloud = env.cloud[cloudidx]; // Apply calculated rate to the actual cloud. cloud.decay -= dissipate; if (cloud.type == CLOUD_FOREST_FIRE) _spread_fire(cloud); else if (x_chance_in_y(cloud.spread_rate, 100)) { cloud.spread_rate -= div_rand_round(cloud.spread_rate, 10); cloud.decay -= _spread_cloud(cloud); } // Check for total dissipation and handle accordingly. if (cloud.decay < 1) delete_cloud(cloudidx); } void manage_clouds() { for (int i = 0; i < MAX_CLOUDS; ++i) { cloud_struct& cloud = env.cloud[i]; if (cloud.type == CLOUD_NONE) continue; int dissipate = you.time_taken; // Fire clouds dissipate faster over water, // rain and cold clouds dissipate faster over lava. if (cloud.type == CLOUD_FIRE && grd(cloud.pos) == DNGN_DEEP_WATER) dissipate *= 4; else if ((cloud.type == CLOUD_COLD || cloud.type == CLOUD_RAIN) && grd(cloud.pos) == DNGN_LAVA) dissipate *= 4; else if (cloud.type == CLOUD_GLOOM) { int count = 0; for (adjacent_iterator ai(cloud.pos); ai; ++ai) if (env.cgrid(*ai) != EMPTY_CLOUD) if (env.cloud[env.cgrid(*ai)].type == CLOUD_GLOOM) count++; if (!haloers(cloud.pos).empty() && !silenced(cloud.pos)) count = 0; if (count < 4) dissipate *= 50; else dissipate /= 20; } cloud_interacts_with_terrain(cloud); expose_items_to_element(cloud2beam(cloud.type), cloud.pos, 2); _dissipate_cloud(i, dissipate); } } void delete_cloud( int cloud ) { cloud_struct& c = env.cloud[cloud]; if (c.type != CLOUD_NONE) { if (c.type == CLOUD_RAIN) { // Rain clouds can occasionally leave shallow water or deepen it: // If we're near lava, chance of leaving water is lower; // if we're near deep water already, chance of leaving water // is slightly higher. if (one_chance_in((5 + count_neighbours(c.pos, DNGN_LAVA)) - count_neighbours(c.pos, DNGN_DEEP_WATER))) { dungeon_feature_type feat; if (grd(c.pos) == DNGN_FLOOR) feat = DNGN_SHALLOW_WATER; else if (grd(c.pos) == DNGN_SHALLOW_WATER && you.pos() != c.pos && one_chance_in(3)) // Don't drown the player! feat = DNGN_DEEP_WATER; else feat = grd(c.pos); if (grd(c.pos) != feat) { if (you.pos() == c.pos) mpr("The rain has left you waist-deep in water!"); dungeon_terrain_changed(c.pos, feat); } } } c.type = CLOUD_NONE; c.decay = 0; c.whose = KC_OTHER; c.killer = KILL_NONE; c.spread_rate = 0; c.colour = -1; c.name = ""; c.tile = ""; env.cgrid(c.pos) = EMPTY_CLOUD; c.pos.reset(); env.cloud_no--; } } // The current use of this function is for shifting in the abyss, so // that clouds get moved along with the rest of the map. void move_cloud( int cloud, const coord_def& newpos ) { if (cloud != EMPTY_CLOUD) { const coord_def oldpos = env.cloud[cloud].pos; env.cgrid(oldpos) = EMPTY_CLOUD; env.cgrid(newpos) = cloud; env.cloud[cloud].pos = newpos; } } // Places a cloud with the given stats assuming one doesn't already // exist at that point. void check_place_cloud( cloud_type cl_type, const coord_def& p, int lifetime, kill_category whose, int spread_rate, int colour, std::string name, std::string tile) { check_place_cloud(cl_type, p, lifetime, whose, cloud_struct::whose_to_killer(whose), spread_rate, colour, name, tile); } // Places a cloud with the given stats assuming one doesn't already // exist at that point. void check_place_cloud( cloud_type cl_type, const coord_def& p, int lifetime, killer_type killer, int spread_rate, int colour, std::string name, std::string tile) { check_place_cloud(cl_type, p, lifetime, cloud_struct::killer_to_whose(killer), killer, spread_rate, colour, name, tile); } // Places a cloud with the given stats assuming one doesn't already // exist at that point. void check_place_cloud( cloud_type cl_type, const coord_def& p, int lifetime, kill_category whose, killer_type killer, int spread_rate, int colour, std::string name, std::string tile) { if (!in_bounds(p) || env.cgrid(p) != EMPTY_CLOUD) return; place_cloud( cl_type, p, lifetime, whose, killer, spread_rate, colour, name, tile ); } int steam_cloud_damage(const cloud_struct &cloud) { return steam_cloud_damage(cloud.decay); } int steam_cloud_damage(int decay) { decay = std::min(decay, 60); decay = std::max(decay, 10); // Damage in range 3 - 16. return ((decay * 13 + 20) / 50); } // Places a cloud with the given stats. May delete old clouds to // make way if there are too many on level. Will overwrite an old // cloud under some circumstances. void place_cloud(cloud_type cl_type, const coord_def& ctarget, int cl_range, kill_category whose, int _spread_rate, int colour, std::string name, std::string tile) { place_cloud(cl_type, ctarget, cl_range, whose, cloud_struct::whose_to_killer(whose), _spread_rate, colour, name, tile); } // Places a cloud with the given stats. May delete old clouds to // make way if there are too many on level. Will overwrite an old // cloud under some circumstances. void place_cloud(cloud_type cl_type, const coord_def& ctarget, int cl_range, killer_type killer, int _spread_rate, int colour, std::string name, std::string tile) { place_cloud(cl_type, ctarget, cl_range, cloud_struct::killer_to_whose(killer), killer, _spread_rate, colour, name, tile); } bool cloud_is_inferior(cloud_type inf, cloud_type superior) { return (inf == CLOUD_STINK && superior == CLOUD_POISON); } // Places a cloud with the given stats. May delete old clouds to // make way if there are too many on level. Will overwrite an old // cloud under some circumstances. void place_cloud(cloud_type cl_type, const coord_def& ctarget, int cl_range, kill_category whose, killer_type killer, int _spread_rate, int colour, std::string name, std::string tile) { if (is_sanctuary(ctarget) && !is_harmless_cloud(cl_type)) return; int cl_new = -1; const int target_cgrid = env.cgrid(ctarget); if (target_cgrid != EMPTY_CLOUD) { // There's already a cloud here. See if we can overwrite it. cloud_struct& old_cloud = env.cloud[target_cgrid]; if (old_cloud.type >= CLOUD_GREY_SMOKE && old_cloud.type <= CLOUD_STEAM || cloud_is_inferior(old_cloud.type, cl_type) || old_cloud.type == CLOUD_BLACK_SMOKE || old_cloud.type == CLOUD_MIST || old_cloud.decay <= 20) // soon gone { // Delete this cloud and replace it. cl_new = target_cgrid; delete_cloud(target_cgrid); } else // Guess not. return; } const int spread_rate = _actual_spread_rate(cl_type, _spread_rate); // Too many clouds. if (env.cloud_no >= MAX_CLOUDS) { // Default to random in case there's no low quality clouds. int cl_del = random2(MAX_CLOUDS); for (int ci = 0; ci < MAX_CLOUDS; ci++) { cloud_struct& cloud = env.cloud[ci]; if (cloud.type >= CLOUD_GREY_SMOKE && cloud.type <= CLOUD_STEAM || cloud.type == CLOUD_BLACK_SMOKE || cloud.type == CLOUD_MIST || cloud.decay <= 20) // soon gone { cl_del = ci; break; } } delete_cloud(cl_del); cl_new = cl_del; } // Create new cloud. if (cl_new != -1) { _new_cloud( cl_new, cl_type, ctarget, cl_range * 10, whose, killer, spread_rate, colour, name, tile ); } else { // Find slot for cloud. for (int ci = 0; ci < MAX_CLOUDS; ci++) { if (env.cloud[ci].type == CLOUD_NONE) // ie is empty { _new_cloud( ci, cl_type, ctarget, cl_range * 10, whose, killer, spread_rate, colour, name, tile ); break; } } } } bool is_opaque_cloud(unsigned char cloud_idx) { if (cloud_idx == EMPTY_CLOUD) return (false); const int ctype = env.cloud[cloud_idx].type; return (ctype >= CLOUD_OPAQUE_FIRST && ctype <= CLOUD_OPAQUE_LAST); } cloud_type cloud_type_at(const coord_def &c) { const int cloudno = env.cgrid(c); return (cloudno == EMPTY_CLOUD ? CLOUD_NONE : env.cloud[cloudno].type); } cloud_type random_smoke_type() { // including black to keep variety switch ( random2(4) ) { case 0: return CLOUD_GREY_SMOKE; case 1: return CLOUD_BLUE_SMOKE; case 2: return CLOUD_BLACK_SMOKE; case 3: return CLOUD_PURPLE_SMOKE; } return CLOUD_DEBUGGING; } cloud_type beam2cloud(beam_type flavour) { switch (flavour) { default: case BEAM_NONE: return CLOUD_NONE; case BEAM_FIRE: case BEAM_POTION_FIRE: return CLOUD_FIRE; case BEAM_POTION_STINKING_CLOUD: return CLOUD_STINK; case BEAM_COLD: case BEAM_POTION_COLD: return CLOUD_COLD; case BEAM_POISON: case BEAM_POTION_POISON: return CLOUD_POISON; case BEAM_POTION_BLACK_SMOKE: return CLOUD_BLACK_SMOKE; case BEAM_POTION_GREY_SMOKE: return CLOUD_GREY_SMOKE; case BEAM_POTION_BLUE_SMOKE: return CLOUD_BLUE_SMOKE; case BEAM_POTION_PURPLE_SMOKE: return CLOUD_PURPLE_SMOKE; case BEAM_STEAM: case BEAM_POTION_STEAM: return CLOUD_STEAM; case BEAM_MIASMA: case BEAM_POTION_MIASMA: return CLOUD_MIASMA; case BEAM_CHAOS: return CLOUD_CHAOS; case BEAM_POTION_RAIN: return CLOUD_RAIN; case BEAM_POTION_MUTAGENIC: return CLOUD_MUTAGENIC; case BEAM_GLOOM: return CLOUD_GLOOM; case BEAM_RANDOM: return CLOUD_RANDOM; } } beam_type cloud2beam(cloud_type flavour) { switch (flavour) { default: case CLOUD_NONE: return BEAM_NONE; case CLOUD_FIRE: return BEAM_FIRE; case CLOUD_FOREST_FIRE: return BEAM_FIRE; case CLOUD_STINK: return BEAM_POTION_STINKING_CLOUD; case CLOUD_COLD: return BEAM_COLD; case CLOUD_POISON: return BEAM_POISON; case CLOUD_BLACK_SMOKE: return BEAM_POTION_BLACK_SMOKE; case CLOUD_GREY_SMOKE: return BEAM_POTION_GREY_SMOKE; case CLOUD_BLUE_SMOKE: return BEAM_POTION_BLUE_SMOKE; case CLOUD_PURPLE_SMOKE: return BEAM_POTION_PURPLE_SMOKE; case CLOUD_STEAM: return BEAM_STEAM; case CLOUD_MIASMA: return BEAM_MIASMA; case CLOUD_CHAOS: return BEAM_CHAOS; case CLOUD_RAIN: return BEAM_POTION_RAIN; case CLOUD_MUTAGENIC: return BEAM_POTION_MUTAGENIC; case CLOUD_GLOOM: return BEAM_GLOOM; case CLOUD_RANDOM: return BEAM_RANDOM; } } // Returns by how much damage gets divided due to elemental resistances. // Damage is reduced to, level 1 -> 1/2, level 2 -> 1/3, level 3 -> 1/5, or // for "boolean" attacks (which use bonus_res = 1, sticky flame/electricity) // to level 1 -> 1/3, level 2 -> 1/4, or level 3 -> 1/6. // With the old formula (1 + resist * resist) this used to be // 1/2, 1/5, 1/10 (normal) and 1/3, 1/6, 1/11 (boolean), respectively. int resist_fraction(int resist, int bonus_res) { return ((3*resist + 1)/2 + bonus_res); } // NOTE: Keep in sync with in_a_cloud() int max_cloud_damage(cloud_type cl_type, int power) { int speed = player_speed(); int dam = 0; int resist = 0; switch (cl_type) { case CLOUD_FIRE: case CLOUD_FOREST_FIRE: if (you.duration[DUR_FIRE_SHIELD]) return (0); resist = player_res_fire(); // Intentional fall-throuigh case CLOUD_COLD: if (you.mutation[MUT_PASSIVE_FREEZE]) return (0); if (cl_type == CLOUD_COLD) resist = player_res_cold(); if (resist <= 0) { dam += 32 * speed / 10; if (resist < 0) dam += 16 * speed / 10; } else { dam += 32 * speed / 10; dam /= resist_fraction(resist); } break; case CLOUD_STINK: if (player_res_poison()) return (0); dam += 2 * speed / 10; break; case CLOUD_POISON: if (player_res_poison()) return (0); dam += 9 * speed / 10; break; case CLOUD_STEAM: { ASSERT(power >= 0); if (player_res_steam() > 0 || power == 0) return (0); const int base_dam = steam_cloud_damage(power * 10); dam += (base_dam - 1) * speed / 10; const int res_fire = player_res_fire(); if (res_fire < 0) dam += base_dam / 2 * speed / 10; else if (res_fire) dam /= 1 + (res_fire / 2); break; } case CLOUD_MIASMA: if (you.res_rotting()) return (0); dam += 11 * speed / 10; break; default: break; } if (dam < 0) dam = 0; return (dam); } // NOTE: Keep in sync with max_cloud_damage() void in_a_cloud() { int cl = env.cgrid(you.pos()); int hurted = 0; int resist; std::string name = env.cloud[cl].name; if (you.duration[DUR_CONDENSATION_SHIELD] > 0) remove_condensation_shield(); switch (env.cloud[cl].type) { case CLOUD_FIRE: case CLOUD_FOREST_FIRE: if (you.duration[DUR_FIRE_SHIELD]) return; mprf("You are engulfed in %s!", !name.empty() ? name.c_str() : "roaring flames"); resist = player_res_fire(); if (resist <= 0) { hurted += ((random2avg(23, 3) + 10) * you.time_taken) / 10; if (resist < 0) hurted += ((random2avg(14, 2) + 3) * you.time_taken) / 10; hurted -= random2(you.armour_class()); if (hurted < 0) hurted = 0; else ouch(hurted, cl, KILLED_BY_CLOUD, "flame"); } else { canned_msg(MSG_YOU_RESIST); hurted += ((random2avg(23, 3) + 10) * you.time_taken) / 10; hurted /= resist_fraction(resist); ouch(hurted, cl, KILLED_BY_CLOUD, "flame"); } expose_player_to_element(BEAM_FIRE, 7); break; case CLOUD_STINK: // If you don't have to breathe, unaffected mprf("You are engulfed in %s!", !name.empty() ? name.c_str() : "noxious fumes"); if (player_res_poison()) break; hurted += (random2(3) * you.time_taken) / 10; if (hurted < 1) hurted = 0; else ouch((hurted * you.time_taken) / 10, cl, KILLED_BY_CLOUD, "noxious fumes"); if (1 + random2(27) >= you.experience_level) { mpr("You choke on the stench!"); // effectively one or two turns, since it will be // decremented right away confuse_player( (coinflip() ? 3 : 2) ); } break; case CLOUD_COLD: if (you.mutation[MUT_PASSIVE_FREEZE]) break; mprf("You are engulfed in %s!", !name.empty() ? name.c_str() : "freezing vapours"); resist = player_res_cold(); if (resist <= 0) { hurted += ((random2avg(23, 3) + 10) * you.time_taken) / 10; if (resist < 0) hurted += ((random2avg(14, 2) + 3) * you.time_taken) / 10; hurted -= random2(you.armour_class()); if (hurted < 0) hurted = 0; ouch(hurted, cl, KILLED_BY_CLOUD, "freezing vapour"); } else { canned_msg(MSG_YOU_RESIST); hurted += ((random2avg(23, 3) + 10) * you.time_taken) / 10; hurted /= resist_fraction(resist); ouch(hurted, cl, KILLED_BY_CLOUD, "freezing vapour"); } expose_player_to_element(BEAM_COLD, 7); break; case CLOUD_POISON: // If you don't have to breathe, unaffected mprf("You are engulfed in %s!", !name.empty() ? name.c_str() : "poison gas"); if (!player_res_poison()) { ouch((random2(10) * you.time_taken) / 10, cl, KILLED_BY_CLOUD, "poison gas"); poison_player(1); } break; case CLOUD_GREY_SMOKE: case CLOUD_BLUE_SMOKE: case CLOUD_TLOC_ENERGY: case CLOUD_PURPLE_SMOKE: case CLOUD_BLACK_SMOKE: mprf("You are engulfed in %s!", !name.empty() ? name.c_str() : "a cloud of smoke"); break; case CLOUD_STEAM: { mprf("You are engulfed in %s!", !name.empty() ? name.c_str() : "a cloud of scalding steam"); if (player_res_steam() > 0) { mpr("It doesn't seem to affect you."); return; } const int base_dam = steam_cloud_damage(env.cloud[cl]); hurted += (random2avg(base_dam, 2) * you.time_taken) / 10; const int res_fire = player_res_fire(); if (res_fire < 0) hurted += (random2(base_dam / 2 + 1) * you.time_taken) / 10; else if (res_fire) hurted /= 1 + (res_fire / 2); if (hurted < 0) hurted = 0; ouch(hurted, cl, KILLED_BY_CLOUD, "steam"); break; } case CLOUD_MIASMA: mprf("You are engulfed in %s!", !name.empty() ? name.c_str() : "a dark miasma"); if (you.res_rotting()) return; miasma_player(); hurted += (random2avg(12, 3) * you.time_taken) / 10; // 3 if (hurted < 0) hurted = 0; ouch(hurted, cl, KILLED_BY_CLOUD, "foul pestilence"); break; case CLOUD_RAIN: if (you.duration[DUR_FIRE_SHIELD]) you.duration[DUR_FIRE_SHIELD] = 1; if (you.misled()) { mpr("The rain washes away illusions!", MSGCH_DURATION); you.duration[DUR_MISLED] = 0; } if (name.empty() || name == "the rain") mpr("You are standing in the rain."); else mprf("You are engulfed in %s.", name.c_str()); break; case CLOUD_MUTAGENIC: mprf("You are engulfed in %s!", !name.empty() ? name.c_str() : "a mutagenic fog"); if (coinflip()) { mpr("Strange energies course through your body."); if (one_chance_in(3)) you.mutate(); else give_bad_mutation(); } break; case CLOUD_GLOOM: mprf("You are engulfed in %s!", !name.empty() ? name.c_str() : "a thick gloom"); break; default: break; } return; } // end in_a_cloud() bool is_damaging_cloud(cloud_type type, bool temp) { switch (type) { // always harmful... case CLOUD_FIRE: case CLOUD_FOREST_FIRE: // ... unless a Ring of Flames is up and it's a fire cloud. if (temp && you.duration[DUR_FIRE_SHIELD]) return (false); case CLOUD_CHAOS: return (true); case CLOUD_COLD: return (!you.mutation[MUT_PASSIVE_FREEZE]); // Only harmful if the player doesn't have the necessary resistances. // Takes into account what the player can *know* and what s/he can // also expect to be the case a few turns later (ignores spells). case CLOUD_STINK: case CLOUD_POISON: return (!player_res_poison(false, temp)); case CLOUD_STEAM: return (player_res_steam(false, temp) <= 0); case CLOUD_MIASMA: return (!you.res_rotting()); case CLOUD_MUTAGENIC: return (you.can_mutate()); default: // Smoke, never harmful. return (false); } } bool is_harmless_cloud(cloud_type type) { switch (type) { case CLOUD_NONE: case CLOUD_BLACK_SMOKE: case CLOUD_GREY_SMOKE: case CLOUD_BLUE_SMOKE: case CLOUD_PURPLE_SMOKE: case CLOUD_TLOC_ENERGY: case CLOUD_MIST: case CLOUD_RAIN: case CLOUD_MAGIC_TRAIL: case CLOUD_GLOOM: case CLOUD_DEBUGGING: return (true); default: return (false); } } bool in_what_cloud(cloud_type type) { int cl = env.cgrid(you.pos()); if (env.cgrid(you.pos()) == EMPTY_CLOUD) return (false); if (env.cloud[cl].type == type) return (true); return (false); } cloud_type in_what_cloud() { int cl = env.cgrid(you.pos()); if (env.cgrid(you.pos()) == EMPTY_CLOUD) return (CLOUD_NONE); return (env.cloud[cl].type); } std::string cloud_name(int cloudno) { if (!env.cloud[cloudno].name.empty()) return (env.cloud[cloudno].name); else return cloud_name(env.cloud[cloudno].type); } std::string cloud_name(cloud_type type) { switch (type) { case CLOUD_FIRE: return "flame"; case CLOUD_FOREST_FIRE: return "fire"; case CLOUD_STINK: return "noxious fumes"; case CLOUD_COLD: return "freezing vapour"; case CLOUD_POISON: return "poison gases"; case CLOUD_GREY_SMOKE: return "grey smoke"; case CLOUD_BLUE_SMOKE: return "blue smoke"; case CLOUD_PURPLE_SMOKE: return "purple smoke"; case CLOUD_TLOC_ENERGY: return "translocational energy"; case CLOUD_STEAM: return "steam"; case CLOUD_MIASMA: return "foul pestilence"; case CLOUD_BLACK_SMOKE: return "black smoke"; case CLOUD_MIST: return "thin mist"; case CLOUD_CHAOS: return "seething chaos"; case CLOUD_RAIN: return "rain"; case CLOUD_MUTAGENIC: return "mutagenic fog"; case CLOUD_MAGIC_TRAIL: return "magical condensation"; case CLOUD_GLOOM: return "gloom"; default: return "buggy goodness"; } } //////////////////////////////////////////////////////////////////////// // cloud_struct kill_category cloud_struct::killer_to_whose(killer_type killer) { switch (killer) { case KILL_YOU: case KILL_YOU_MISSILE: case KILL_YOU_CONF: return (KC_YOU); case KILL_MON: case KILL_MON_MISSILE: case KILL_MISC: return (KC_OTHER); default: ASSERT(false); } return (KC_OTHER); } killer_type cloud_struct::whose_to_killer(kill_category whose) { switch (whose) { case KC_YOU: return(KILL_YOU_MISSILE); case KC_FRIENDLY: return(KILL_MON_MISSILE); case KC_OTHER: return(KILL_MISC); case KC_NCATEGORIES: ASSERT(false); } return (KILL_NONE); } void cloud_struct::set_whose(kill_category _whose) { whose = _whose; killer = whose_to_killer(whose); } void cloud_struct::set_killer(killer_type _killer) { killer = _killer; whose = killer_to_whose(killer); switch (killer) { case KILL_YOU: killer = KILL_YOU_MISSILE; break; case KILL_MON: killer = KILL_MON_MISSILE; break; default: break; } } int get_cloud_colour(int cloudno) { int which_colour = LIGHTGREY; if (env.cloud[cloudno].colour != -1) return (env.cloud[cloudno].colour); switch (env.cloud[cloudno].type) { case CLOUD_FIRE: case CLOUD_FOREST_FIRE: if (env.cloud[cloudno].decay <= 20) which_colour = RED; else if (env.cloud[cloudno].decay <= 40) which_colour = LIGHTRED; else if (one_chance_in(4)) which_colour = RED; else if (one_chance_in(4)) which_colour = LIGHTRED; else which_colour = YELLOW; break; case CLOUD_STINK: which_colour = GREEN; break; case CLOUD_COLD: if (env.cloud[cloudno].decay <= 20) which_colour = BLUE; else if (env.cloud[cloudno].decay <= 40) which_colour = LIGHTBLUE; else if (one_chance_in(4)) which_colour = BLUE; else if (one_chance_in(4)) which_colour = LIGHTBLUE; else which_colour = WHITE; break; case CLOUD_POISON: which_colour = (one_chance_in(3) ? LIGHTGREEN : GREEN); break; case CLOUD_BLUE_SMOKE: which_colour = LIGHTBLUE; break; case CLOUD_PURPLE_SMOKE: case CLOUD_TLOC_ENERGY: case CLOUD_GLOOM: which_colour = MAGENTA; break; case CLOUD_MIASMA: case CLOUD_BLACK_SMOKE: which_colour = DARKGREY; break; case CLOUD_RAIN: case CLOUD_MIST: which_colour = ETC_MIST; break; case CLOUD_CHAOS: which_colour = ETC_RANDOM; break; case CLOUD_MUTAGENIC: which_colour = ETC_MUTAGENIC; break; case CLOUD_MAGIC_TRAIL: which_colour = ETC_MAGIC; break; default: which_colour = LIGHTGREY; break; } return (which_colour); } ////////////////////////////////////////////////////////////////////////// // Fog machine stuff void place_fog_machine(fog_machine_type fm_type, cloud_type cl_type, int x, int y, int size, int power) { ASSERT(fm_type >= FM_GEYSER && fm_type < NUM_FOG_MACHINE_TYPES); ASSERT(cl_type > CLOUD_NONE && (cl_type < CLOUD_RANDOM || cl_type == CLOUD_DEBUGGING)); ASSERT(size >= 1); ASSERT(power >= 1); const char* fog_types[] = { "geyser", "spread", "brownian" }; try { char buf [160]; sprintf(buf, "lua_mapless:fog_machine_%s(\"%s\", %d, %d)", fog_types[fm_type], cloud_name(cl_type).c_str(), size, power); map_marker *mark = map_lua_marker::parse_marker(buf, ""); if (mark == NULL) { mprf(MSGCH_DIAGNOSTICS, "Unable to parse fog machine from '%s'", buf); return; } mark->pos = coord_def(x, y); env.markers.add(mark); } catch (const std::string &err) { mprf(MSGCH_ERROR, "Error while making fog machine: %s", err.c_str()); } } void place_fog_machine(fog_machine_data data, int x, int y) { place_fog_machine(data.fm_type, data.cl_type, x, y, data.size, data.power); } bool valid_fog_machine_data(fog_machine_data data) { if (data.fm_type < FM_GEYSER || data.fm_type >= NUM_FOG_MACHINE_TYPES) return (false); if (data.cl_type <= CLOUD_NONE || (data.cl_type >= CLOUD_RANDOM && data.cl_type != CLOUD_DEBUGGING)) return (false); if (data.size < 1 || data.power < 1) return (false); return (true); } int num_fogs_for_place(int level_number, const level_id &place) { if (level_number == -1) level_number = place.absdepth(); switch (place.level_type) { case LEVEL_DUNGEON: { Branch &branch = branches[place.branch]; ASSERT((branch.num_fogs_function == NULL && branch.rand_fog_function == NULL) || (branch.num_fogs_function != NULL && branch.rand_fog_function != NULL)); if (branch.num_fogs_function == NULL) return 0; return branch.num_fogs_function(level_number); } case LEVEL_ABYSS: return fogs_abyss_number(level_number); case LEVEL_PANDEMONIUM: return fogs_pan_number(level_number); case LEVEL_LABYRINTH: return fogs_lab_number(level_number); default: return 0; } return 0; } fog_machine_data random_fog_for_place(int level_number, const level_id &place) { fog_machine_data data = {NUM_FOG_MACHINE_TYPES, CLOUD_NONE, -1, -1}; if (level_number == -1) level_number = place.absdepth(); switch (place.level_type) { case LEVEL_DUNGEON: { Branch &branch = branches[place.branch]; ASSERT(branch.num_fogs_function != NULL && branch.rand_fog_function != NULL); branch.rand_fog_function(level_number, data); return data; } case LEVEL_ABYSS: return fogs_abyss_type(level_number); case LEVEL_PANDEMONIUM: return fogs_pan_type(level_number); case LEVEL_LABYRINTH: return fogs_lab_type(level_number); default: ASSERT(false); return data; } ASSERT(false); return data; } int fogs_pan_number(int level_number) { return 0; } fog_machine_data fogs_pan_type(int level_number) { fog_machine_data data = {NUM_FOG_MACHINE_TYPES, CLOUD_NONE, -1, -1}; return data; } int fogs_abyss_number(int level_number) { return 0; } fog_machine_data fogs_abyss_type(int level_number) { fog_machine_data data = {NUM_FOG_MACHINE_TYPES, CLOUD_NONE, -1, -1}; return data; } int fogs_lab_number(int level_number) { return 0; } fog_machine_data fogs_lab_type(int level_number) { fog_machine_data data = {NUM_FOG_MACHINE_TYPES, CLOUD_NONE, -1, -1}; return data; }