summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/bloodspatter.cc
blob: 3897eeba441548f3161dedab91fd4190850d2c4b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/**
 * @file
 * @brief Functions for spattering blood all over the place.
 **/

#include "AppHdr.h"

#include "bloodspatter.h"

#include "cloud.h"
#include "coordit.h"
#include "env.h"
#include "fprop.h"
#include "losglobal.h"
#include "shout.h"
#include "terrain.h"
#include "religion.h"

static bool allow_bleeding_on_square(const coord_def& where)
{
    // No bleeding onto sanctuary ground, please.
    // Also not necessary if already covered in blood.
    if (is_bloodcovered(where) || is_sanctuary(where))
        return false;

    // No spattering into lava or water.
    if (grd(where) >= DNGN_LAVA && grd(where) < DNGN_FLOOR)
        return false;

    // No spattering into fountains (other than blood).
    if (grd(where) == DNGN_FOUNTAIN_BLUE
        || grd(where) == DNGN_FOUNTAIN_SPARKLING)
    {
        return false;
    }

    // The good gods like to keep their altars pristine.
    if (is_good_god(feat_altar_god(grd(where))))
        return false;

    return true;
}

bool maybe_bloodify_square(const coord_def& where)
{
    if (!allow_bleeding_on_square(where))
        return false;

    env.pgrid(where) |= FPROP_BLOODY;
    return true;
}

/**
 * Rotate the wall blood splat tile, so that it is facing the source.
 *
 * Wall blood splat tiles are drawned with the blood dripping down. We need
 * the tile to be facing an orthogonal empty space for the effect to look
 * good. We choose the empty space closest to the source of the blood.
 *
 * @param where Coordinates of the wall where there is a blood splat.
 * @param from Coordinates of the source of the blood.
 * @param old_blood blood splats created at level generation are old and can
 * have some blood inscriptions. Only for south facing splats, so you don't
 * have to turn your head to read the inscriptions.
 */
static void _orient_wall_blood(const coord_def& where, coord_def from,
                               bool old_blood)
{
    if (!feat_is_wall(env.grid(where)))
        return;

    if (from == INVALID_COORD)
        from = where;

    coord_def closer = INVALID_COORD;
    int dist = INT_MAX;
    for (orth_adjacent_iterator ai(where); ai; ++ai)
    {
        if (in_bounds(*ai) && !cell_is_solid(*ai)
            && cell_see_cell(from, *ai, LOS_SOLID)
            && (distance2(*ai, from) < dist
                || distance2(*ai, from) == dist && coinflip()))
        {
            closer = *ai;
            dist = distance2(*ai, from);
        }
    }

    // If we didn't find anything, the wall is in a corner.
    // We don't want blood tile there.
    if (closer == INVALID_COORD)
    {
        env.pgrid(where) &= ~FPROP_BLOODY;
        return;
    }

    const coord_def diff = where - closer;
    if (diff == coord_def(1, 0))
        env.pgrid(where) |= FPROP_BLOOD_WEST;
    else if (diff == coord_def(0, 1))
        env.pgrid(where) |= FPROP_BLOOD_NORTH;
    else if (diff == coord_def(-1, 0))
        env.pgrid(where) |= FPROP_BLOOD_EAST;
    else if (old_blood && one_chance_in(10))
        env.pgrid(where) |= FPROP_OLD_BLOOD;
}

static void _maybe_bloodify_square(const coord_def& where, int amount,
                                   bool spatter = false,
                                   bool smell_alert = true,
                                   const coord_def& from = INVALID_COORD,
                                   const bool old_blood = false)
{
    if (amount < 1)
        return;

    bool may_bleed = allow_bleeding_on_square(where);

    bool ignite_blood = player_mutation_level(MUT_IGNITE_BLOOD)
    && you.see_cell(where);

    if (ignite_blood)
        amount *= 2;

    if (x_chance_in_y(amount, 20))
    {
        dprf("might bleed now; square: (%d, %d); amount = %d",
             where.x, where.y, amount);
        if (may_bleed)
        {
            env.pgrid(where) |= FPROP_BLOODY;
            _orient_wall_blood(where, from, old_blood);

            if (ignite_blood
                && !cell_is_solid(where)
                && env.cgrid(where) == EMPTY_CLOUD)
            {
                place_cloud(CLOUD_FIRE, where, 5 + random2(6), &you);
            }
        }

        // The blood spilled can be detected via blood scent even if the
        // spot it would fall is prevented from becoming bloodied (for
        // example, because it is water)
        if (smell_alert && in_bounds(where))
            blood_smell(12, where);

        if (spatter)
        {
            // Smaller chance of spattering surrounding squares.
            for (adjacent_iterator ai(where); ai; ++ai)
            {
                _maybe_bloodify_square(*ai, amount/15, false, true, from,
                                       old_blood);
            }
        }
    }
}

// Currently flavour only: colour ground (and possibly adjacent squares) red.
// "damage" depends on damage taken (or hitpoints, if damage higher),
// or, for sacrifices, on the number of chunks possible to get out of a corpse.
void bleed_onto_floor(const coord_def& where, monster_type montype,
                      int damage, bool spatter, bool smell_alert,
                      const coord_def& from, const bool old_blood)
{
    ASSERT_IN_BOUNDS(where);

    if (montype == MONS_PLAYER && !you.can_bleed())
        return;

    if (montype != NUM_MONSTERS && montype != MONS_PLAYER)
    {
        monster m;
        m.type = montype;
        if (!m.can_bleed())
            return;
    }

    _maybe_bloodify_square(where, damage, spatter, smell_alert, from,
                           old_blood);
}

void blood_spray(const coord_def& origin, monster_type montype, int level)
{
    int tries = 0;
    for (int i = 0; i < level; ++i)
    {
        // Blood drops are small and light and suffer a lot of wind
        // resistance.
        int range = random2(8) + 1;

        while (tries < 5000)
        {
            ++tries;

            coord_def bloody = origin;
            bloody.x += random_range(-range, range);
            bloody.y += random_range(-range, range);

            if (in_bounds(bloody) && cell_see_cell(origin, bloody, LOS_SOLID))
            {
                bleed_onto_floor(bloody, montype, 99, false, true, origin);
                break;
            }
        }
    }
}

static void _spatter_neighbours(const coord_def& where, int chance,
                                const coord_def& from = INVALID_COORD)
{
    for (adjacent_iterator ai(where, false); ai; ++ai)
    {
        if (!allow_bleeding_on_square(*ai))
            continue;

        if (one_chance_in(chance))
        {
            env.pgrid(*ai) |= FPROP_BLOODY;
            _orient_wall_blood(where, from, true);
            _spatter_neighbours(*ai, chance+1, from);
        }
    }
}

void generate_random_blood_spatter_on_level(const map_bitmask *susceptible_area)
{
    const int max_cluster = 7 + random2(9);

    // Lower chances for large bloodshed areas if we have many clusters,
    // but increase chances if we have few.
    // Chances for startprob are [1..3] for 7-9 clusters,
    //                       ... [1..4] for 10-12 clusters, and
    //                       ... [2..5] for 13-15 clusters.

    int min_prob = 1;
    int max_prob = 4;

    if (max_cluster < 10)
        max_prob--;
    else if (max_cluster > 12)
        min_prob++;

    for (int i = 0; i < max_cluster; ++i)
    {
        const coord_def c = random_in_bounds();

        if (susceptible_area && !(*susceptible_area)(c))
            continue;

        // startprob is used to initialise the chance for neighbours
        // being spattered, which will be decreased by 1 per recursion
        // round. We then use one_chance_in(chance) to determine
        // whether to spatter a given grid or not. Thus, startprob = 1
        // means that initially all surrounding grids will be
        // spattered (3x3), and the _higher_ startprob the _lower_ the
        // overall chance for spattering and the _smaller_ the
        // bloodshed area.
        const int startprob = min_prob + random2(max_prob);

        maybe_bloodify_square(c);

        _spatter_neighbours(c, startprob);
    }
}