/*
* 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 <algorithm>
#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;
}