forked from CleverRaven/Cataclysm-DDA
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mondeath.cpp
291 lines (265 loc) · 10.8 KB
/
mondeath.cpp
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
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#include "mondeath.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <functional>
#include <iosfwd>
#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "calendar.h"
#include "character.h"
#include "colony.h"
#include "creature.h"
#include "debug.h"
#include "enums.h"
#include "explosion.h"
#include "field_type.h"
#include "fungal_effects.h"
#include "game.h"
#include "harvest.h"
#include "item_stack.h"
#include "itype.h"
#include "kill_tracker.h"
#include "line.h"
#include "make_static.h"
#include "map.h"
#include "map_iterator.h"
#include "mattack_actors.h"
#include "mattack_common.h"
#include "messages.h"
#include "monster.h"
#include "morale_types.h"
#include "mtype.h"
#include "point.h"
#include "rng.h"
#include "sounds.h"
#include "string_formatter.h"
#include "timed_event.h"
#include "translations.h"
#include "type_id.h"
#include "units.h"
#include "value_ptr.h"
#include "viewer.h"
static const efftype_id effect_no_ammo( "no_ammo" );
static const harvest_drop_type_id harvest_drop_bone( "bone" );
static const harvest_drop_type_id harvest_drop_flesh( "flesh" );
static const species_id species_ZOMBIE( "ZOMBIE" );
item *mdeath::normal( monster &z )
{
if( z.no_corpse_quiet ) {
return nullptr;
}
if( !z.quiet_death ) {
if( z.type->in_species( species_ZOMBIE ) ) {
sfx::play_variant_sound( "mon_death", "zombie_death", sfx::get_heard_volume( z.pos() ) );
}
//Currently it is possible to get multiple messages that a monster died.
add_msg_if_player_sees( z, m_good, _( "The %s dies!" ), z.name() );
}
if( z.death_drops ) {
const int max_hp = std::max( z.get_hp_max(), 1 );
const float overflow_damage = std::max( -z.get_hp(), 0 );
const float corpse_damage = 2.5 * overflow_damage / max_hp;
const bool pulverized = corpse_damage > 5 && overflow_damage > z.get_hp_max();
z.bleed(); // leave some blood if we have to
if( pulverized ) {
return splatter( z );
} else {
const float damage = std::floor( corpse_damage * itype::damage_scale );
item *corpse = make_mon_corpse( z, static_cast<int>( damage ) );
if( corpse->is_null() ) {
return nullptr;
}
return corpse;
}
}
return nullptr;
}
static void scatter_chunks( const itype_id &chunk_name, int chunk_amt, monster &z, int distance,
int pile_size = 1 )
{
// can't have less than one item in a pile or it would cause an infinite loop
pile_size = std::max( pile_size, 1 );
// can't have more items in a pile than total items
pile_size = std::min( chunk_amt, pile_size );
distance = std::abs( distance );
const item chunk( chunk_name, calendar::turn, pile_size );
map &here = get_map();
for( int i = 0; i < chunk_amt; i += pile_size ) {
bool drop_chunks = true;
tripoint tarp( z.pos() + point( rng( -distance, distance ), rng( -distance, distance ) ) );
const auto traj = line_to( z.pos(), tarp );
for( size_t j = 0; j < traj.size(); j++ ) {
tarp = traj[j];
if( one_in( 2 ) && z.bloodType().id() ) {
here.add_splatter( z.bloodType(), tarp );
} else {
here.add_splatter( z.gibType(), tarp, rng( 1, j + 1 ) );
}
if( here.impassable( tarp ) ) {
here.bash( tarp, distance );
if( here.impassable( tarp ) ) {
// Target is obstacle, not destroyed by bashing,
// stop trajectory in front of it, if this is the first
// point (e.g. wall adjacent to monster), don't drop anything on it
if( j > 0 ) {
tarp = traj[j - 1];
} else {
drop_chunks = false;
}
break;
}
}
}
if( drop_chunks ) {
here.add_item_or_charges( tarp, chunk );
}
}
}
item *mdeath::splatter( monster &z )
{
const bool gibbable = !z.type->has_flag( MF_NOGIB );
const int max_hp = std::max( z.get_hp_max(), 1 );
const float overflow_damage = std::max( -z.get_hp(), 0 );
const float corpse_damage = 2.5 * overflow_damage / max_hp;
const field_type_id type_blood = z.bloodType();
const field_type_id type_gib = z.gibType();
map &here = get_map();
if( gibbable ) {
const auto area = here.points_in_radius( z.pos(), 1 );
int number_of_gibs = std::min( std::floor( corpse_damage ) - 1, 1 + max_hp / 5.0f );
if( z.type->size >= creature_size::medium ) {
number_of_gibs += rng( 1, 6 );
sfx::play_variant_sound( "mon_death", "zombie_gibbed", sfx::get_heard_volume( z.pos() ) );
}
for( int i = 0; i < number_of_gibs; ++i ) {
here.add_splatter( type_gib, random_entry( area ), rng( 1, i + 1 ) );
here.add_splatter( type_blood, random_entry( area ) );
}
}
// 1% of the weight of the monster is the base, with overflow damage as a multiplier
int gibbed_weight = rng( 0, std::round( to_gram( z.get_weight() ) / 100.0 *
( overflow_damage / max_hp + 1 ) ) );
const int z_weight = to_gram( z.get_weight() );
// limit gibbing to 15%
gibbed_weight = std::min( gibbed_weight, z_weight * 15 / 100 );
if( gibbable ) {
float overflow_ratio = overflow_damage / max_hp + 1.0f;
int gib_distance = std::round( rng( 2, 4 ) );
for( const harvest_entry &entry : *z.type->harvest ) {
// only flesh and bones survive.
if( entry.type == harvest_drop_flesh || entry.type == harvest_drop_bone ) {
// the larger the overflow damage, the less you get
const int chunk_amt =
entry.mass_ratio / overflow_ratio / 10 *
z.get_weight() / item::find_type( itype_id( entry.drop ) )->weight;
scatter_chunks( itype_id( entry.drop ), chunk_amt, z, gib_distance,
chunk_amt / ( gib_distance - 1 ) );
gibbed_weight -= entry.mass_ratio / overflow_ratio / 20 * to_gram( z.get_weight() );
}
}
if( gibbed_weight > 0 ) {
const itype_id &leftover_id = z.type->id->harvest->leftovers;
const int chunk_amount =
gibbed_weight / to_gram( item::find_type( leftover_id )->weight );
scatter_chunks( leftover_id, chunk_amount, z, gib_distance,
chunk_amount / ( gib_distance + 1 ) );
}
// add corpse with gib flag
item corpse = item::make_corpse( z.type->id, calendar::turn, z.unique_name, z.get_upgrade_time() );
if( corpse.is_null() ) {
return nullptr;
}
// Set corpse to damage that aligns with being pulped
corpse.set_damage( 4000 );
corpse.set_flag( STATIC( flag_id( "GIBBED" ) ) );
if( z.has_effect( effect_no_ammo ) ) {
corpse.set_var( "no_ammo", "no_ammo" );
}
return &here.add_item_or_charges( z.pos(), corpse );
}
return nullptr;
}
void mdeath::disappear( monster &z )
{
add_msg_if_player_sees( z, m_good, _( "The %s disappears." ), z.name() );
}
void mdeath::broken( monster &z )
{
// Bail out if flagged (simulates eyebot flying away)
if( z.no_corpse_quiet ) {
return;
}
std::string item_id = z.type->id.str();
if( item_id.compare( 0, 4, "mon_" ) == 0 ) {
item_id.erase( 0, 4 );
}
// make "broken_manhack", or "broken_eyebot", ...
item_id.insert( 0, "broken_" );
item broken_mon( item_id, calendar::turn );
const int max_hp = std::max( z.get_hp_max(), 1 );
const float overflow_damage = std::max( -z.get_hp(), 0 );
const float corpse_damage = 2.5 * overflow_damage / max_hp;
broken_mon.set_damage( static_cast<int>( std::floor( corpse_damage * itype::damage_scale ) ) );
map &here = get_map();
here.add_item_or_charges( z.pos(), broken_mon );
if( z.type->has_flag( MF_DROPS_AMMO ) ) {
for( const std::pair<const itype_id, int> &ammo_entry : z.ammo ) {
if( ammo_entry.second > 0 ) {
bool spawned = false;
for( const std::pair<const std::string, mtype_special_attack> &attack : z.type->special_attacks ) {
if( attack.second->id == "gun" ) {
item gun = item( dynamic_cast<const gun_actor *>( attack.second.get() )->gun_type );
bool same_ammo = false;
if( gun.typeId()->magazine_default.count( item( ammo_entry.first ).ammo_type() ) ) {
same_ammo = true;
}
if( same_ammo && gun.uses_magazine() ) {
std::vector<item> mags;
int ammo_count = ammo_entry.second;
while( ammo_count > 0 ) {
item mag = item( gun.type->magazine_default.find( item( ammo_entry.first ).ammo_type() )->second );
mag.ammo_set( ammo_entry.first,
std::min( ammo_count, mag.type->magazine->capacity ) );
mags.insert( mags.end(), mag );
ammo_count -= mag.type->magazine->capacity;
}
here.spawn_items( z.pos(), mags );
spawned = true;
break;
}
}
}
if( !spawned ) {
here.spawn_item( z.pos(), ammo_entry.first, ammo_entry.second, 1,
calendar::turn );
}
}
}
}
// TODO: make mdeath::splatter work for robots
if( broken_mon.damage() >= broken_mon.max_damage() ) {
add_msg_if_player_sees( z.pos(), m_good, _( "The %s is destroyed!" ), z.name() );
} else {
add_msg_if_player_sees( z.pos(), m_good, _( "The %s collapses!" ), z.name() );
}
}
item *make_mon_corpse( monster &z, int damageLvl )
{
item corpse = item::make_corpse( z.type->id, calendar::turn, z.unique_name, z.get_upgrade_time() );
// All corpses are at 37 C at time of death
// This may not be true but anything better would be way too complicated
if( z.is_warm() ) {
corpse.set_item_temperature( units::from_celcius( 37 ) );
}
corpse.set_damage( damageLvl );
if( z.has_effect( effect_no_ammo ) ) {
corpse.set_var( "no_ammo", "no_ammo" );
}
return &get_map().add_item_or_charges( z.pos(), corpse );
}