Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misc fixes for explosions and radio activation #1326

Merged
merged 15 commits into from
Feb 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions data/json/flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -1648,12 +1648,7 @@
"context": [ ]
},
{
"id": "RADIOCAR",
"type": "json_flag",
"context": [ ]
},
{
"id": "RADIO_CONTAINER",
"id": "RADIO_CONTROLLED",
"type": "json_flag",
"context": [ ]
},
Expand Down
1 change: 1 addition & 0 deletions data/json/items/tool/explosives.json
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,7 @@
"symbol": "*",
"color": "light_green",
"turns_per_charge": 1,
"max_charges": 1000,
"use_action": {
"type": "explosion",
"fields_type": "fd_nuke_gas",
Expand Down
5 changes: 2 additions & 3 deletions data/json/items/tool/radio_tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
"charges_per_use": 1,
"proportional": { "weight": 0.73, "volume": 0.75, "price": 0.8 },
"use_action": "RADIOCAR",
"flags": [ "RADIO_CONTAINER" ],
"magazines": [ [ "battery", [ "light_minus_disposable_cell", "light_minus_battery_cell", "light_minus_atomic_battery_cell" ] ] ]
},
{
Expand All @@ -76,11 +75,11 @@
"description": "This remote-controlled car is on, and draining its batteries just like a real electric car! Use a remote control to drive it around.",
"turns_per_charge": 5,
"use_action": "RADIOCARON",
"flags": [ "LIGHT_8", "RADIO_CONTAINER", "TRADER_AVOID" ]
"flags": [ "LIGHT_8", "RADIO_CONTROLLED", "TRADER_AVOID" ]
},
{
"id": "radio_mod",
"type": "GENERIC",
"type": "TOOL",
"category": "spare_parts",
"name": { "str": "radio activation mod" },
"description": "This small piece of electronics can be attached to certain items and activate them after receiving a radio signal.",
Expand Down
8 changes: 7 additions & 1 deletion data/json/recipes/electronic/tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,13 @@
"components": [
[ [ "antenna", 1 ] ],
[ [ "pilot_light", 1 ] ],
[ [ "light_minus_battery_cell", 1 ] ],
[
[ "light_minus_battery_cell", 1 ],
[ "light_minus_disposable_cell", 1 ],
[ "light_battery_cell", 1 ],
[ "light_disposable_cell", 1 ],
[ "light_plus_battery_cell", 1 ]
],
[ [ "amplifier", 1 ] ],
[ [ "cable", 2 ] ]
]
Expand Down
11 changes: 6 additions & 5 deletions doc/JSON_FLAGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1281,11 +1281,12 @@ Melee flags are fully compatible with tool flags, and vice versa.
- ```NO_UNLOAD``` Cannot be unloaded.
- ```POWERED``` If turned ON, item uses its own source of power, instead of relying on power of the user
- ```RADIOCARITEM``` Item can be put into a remote controlled car.
- ```RADIOSIGNAL_1``` Activated per radios signal 1.
- ```RADIOSIGNAL_2``` Activated per radios signal 2.
- ```RADIOSIGNAL_3``` Activated per radios signal 3.
- ```RADIO_ACTIVATION``` It is activated by a remote control (also requires RADIOSIGNAL*).
- ```RADIO_CONTAINER``` It's a container of something that is radio controlled.
- ```RADIOSIGNAL_1``` Activated per radios signal 1 (Red).
- ```RADIOSIGNAL_2``` Activated per radios signal 2 (Blue).
- ```RADIOSIGNAL_3``` Activated per radios signal 3 (Green).
- ```RADIO_ACTIVATION``` Item can be activated by a remote control (also requires RADIOSIGNAL_*).
- ```RADIO_INVOKE_PROC``` After being activated via radio signal the item will have its charges removed. Can be used for bypassing bomb countdown.
- ```RADIO_CONTROLLABLE``` It can be moved around via a remote control.
- ```RADIO_MODABLE``` Indicates the item can be made into a radio-activated item.
- ```RADIO_MOD``` The item has been made into a radio-activated item.
- ```RECHARGE``` Gain charges when placed in a cargo area with a recharge station.
Expand Down
8 changes: 8 additions & 0 deletions src/animation.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ struct point_with_value {
using one_bucket = std::vector<point_with_value>;
using bucketed_points = std::list<one_bucket>;

namespace explosion_handler
{
void draw_explosion( const tripoint &p, int radius, const nc_color &col,
const std::string &exp_name );
void draw_custom_explosion( const tripoint &p, const std::map<tripoint, nc_color> &area,
const std::string &exp_name );
} // namespace explosion_handler

// TODO: Better file
bucketed_points bucket_by_distance( const tripoint &origin,
const std::map<tripoint, double> &to_bucket );
Expand Down
10 changes: 9 additions & 1 deletion src/bionics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,15 @@ bool Character::activate_bionic( int b, bool eff_only )
mod_moves( -100 );
} else if( bio.id == bio_shockwave ) {
add_msg_activate();
explosion_handler::shockwave( pos(), 3, 4, 2, 8, true, "explosion" );

shockwave_data sw;
sw.affects_player = false;
sw.radius = 3;
sw.force = 4;
sw.stun = 2;
sw.dam_mult = 8;

explosion_handler::shockwave( pos(), sw, "explosion" );
add_msg_if_player( m_neutral, _( "You unleash a powerful shockwave!" ) );
mod_moves( -100 );
} else if( bio.id == bio_meteorologist ) {
Expand Down
14 changes: 14 additions & 0 deletions src/creature.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class window;
} // namespace catacurses
class avatar;
class Character;
class npc;
class monster;
class field;
class field_entry;
class JsonObject;
Expand Down Expand Up @@ -91,6 +93,18 @@ class Creature
virtual bool is_monster() const {
return false;
}
virtual monster *as_monster() {
return nullptr;
}
virtual const monster *as_monster() const {
return nullptr;
}
virtual npc *as_npc() {
return nullptr;
}
virtual const npc *as_npc() const {
return nullptr;
}
virtual Character *as_character() {
return nullptr;
}
Expand Down
135 changes: 109 additions & 26 deletions src/explosion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <utility>
#include <vector>

#include "animation.h"
#include "avatar.h"
#include "bodypart.h"
#include "calendar.h"
Expand All @@ -23,6 +24,7 @@
#include "damage.h"
#include "debug.h"
#include "enums.h"
#include "explosion_queue.h"
#include "field_type.h"
#include "flat_set.h"
#include "game.h"
Expand Down Expand Up @@ -406,6 +408,16 @@ void explosion( const tripoint &p, float power, float factor, bool fire, int leg

void explosion( const tripoint &p, const explosion_data &ex )
{
queued_explosion qe( p, ExplosionType::Regular );
qe.exp_data = ex;
get_explosion_queue().add( std::move( qe ) );
}

void explosion_funcs::regular( const queued_explosion &qe )
{
const tripoint &p = qe.pos;
const explosion_data &ex = qe.exp_data;

const int noise = ex.damage / explosion_handler::power_to_dmg_mult * ( ex.fire ? 2 : 10 );
if( noise >= 30 ) {
sounds::sound( p, noise, sounds::sound_t::combat, _( "a huge explosion!" ), false, "explosion",
Expand Down Expand Up @@ -486,9 +498,19 @@ void explosion( const tripoint &p, const explosion_data &ex )

void flashbang( const tripoint &p, bool player_immune, const std::string &exp_name )
{
draw_explosion( p, 8, c_white, exp_name );
queued_explosion qe( p, ExplosionType::Flashbang );
qe.affects_player = !player_immune;
qe.graphics_name = exp_name;
get_explosion_queue().add( std::move( qe ) );
}

void explosion_funcs::flashbang( const queued_explosion &qe )
{
const tripoint &p = qe.pos;

draw_explosion( p, 8, c_white, qe.graphics_name );
int dist = rl_dist( g->u.pos(), p );
if( dist <= 8 && !player_immune ) {
if( dist <= 8 && qe.affects_player ) {
if( !g->u.has_bionic( bio_ears ) && !g->u.is_wearing( itype_rm13_armor_on ) ) {
g->u.add_effect( effect_deaf, time_duration::from_turns( 40 - dist * 4 ) );
}
Expand Down Expand Up @@ -532,38 +554,49 @@ void flashbang( const tripoint &p, bool player_immune, const std::string &exp_na
// TODO: Blind/deafen NPC
}

void shockwave( const tripoint &p, int radius, int force, int stun, int dam_mult,
bool ignore_player, const std::string &exp_name )
void shockwave( const tripoint &p, const shockwave_data &sw, const std::string &exp_name )
{
draw_explosion( p, radius, c_blue, exp_name );
queued_explosion qe( p, ExplosionType::Shockwave );
qe.swave_data = sw;
qe.graphics_name = exp_name;
get_explosion_queue().add( std::move( qe ) );
}

void explosion_funcs::shockwave( const queued_explosion &qe )
{
const tripoint &p = qe.pos;
const shockwave_data &sw = qe.swave_data;

draw_explosion( p, sw.radius, c_blue, qe.graphics_name );

sounds::sound( p, force * force * dam_mult / 2, sounds::sound_t::combat, _( "Crack!" ), false,
sounds::sound( p, sw.force * sw.force * sw.dam_mult / 2, sounds::sound_t::combat, _( "Crack!" ),
false,
"misc", "shockwave" );

for( monster &critter : g->all_monsters() ) {
if( critter.posz() != p.z ) {
continue;
}
if( rl_dist( critter.pos(), p ) <= radius ) {
if( rl_dist( critter.pos(), p ) <= sw.radius ) {
add_msg( _( "%s is caught in the shockwave!" ), critter.name() );
g->knockback( p, critter.pos(), force, stun, dam_mult );
g->knockback( p, critter.pos(), sw.force, sw.stun, sw.dam_mult );
}
}
// TODO: combine the two loops and the case for g->u using all_creatures()
for( npc &guy : g->all_npcs() ) {
if( guy.posz() != p.z ) {
continue;
}
if( rl_dist( guy.pos(), p ) <= radius ) {
if( rl_dist( guy.pos(), p ) <= sw.radius ) {
add_msg( _( "%s is caught in the shockwave!" ), guy.name );
g->knockback( p, guy.pos(), force, stun, dam_mult );
g->knockback( p, guy.pos(), sw.force, sw.stun, sw.dam_mult );
}
}
if( rl_dist( g->u.pos(), p ) <= radius && !ignore_player &&
if( rl_dist( g->u.pos(), p ) <= sw.radius && sw.affects_player &&
( !g->u.has_trait( trait_LEG_TENT_BRACE ) || g->u.footwear_factor() == 1 ||
( g->u.footwear_factor() == .5 && one_in( 2 ) ) ) ) {
add_msg( m_bad, _( "You're caught in the shockwave!" ) );
g->knockback( p, g->u.pos(), force, stun, dam_mult );
g->knockback( p, g->u.pos(), sw.force, sw.stun, sw.dam_mult );
}
}

Expand Down Expand Up @@ -708,28 +741,41 @@ void emp_blast( const tripoint &p )

void resonance_cascade( const tripoint &p )
{
get_explosion_queue().add( queued_explosion( p, ExplosionType::ResonanceCascade ) );
}

void explosion_funcs::resonance_cascade( const queued_explosion &qe )
{
const tripoint &p = qe.pos;

const time_duration maxglow = time_duration::from_turns( 100 - 5 * trig_dist( p, g->u.pos() ) );
MonsterGroupResult spawn_details;
if( maxglow > 0_turns ) {
const time_duration minglow = std::max( 0_turns, time_duration::from_turns( 60 - 5 * trig_dist( p,
g->u.pos() ) ) );
g->u.add_effect( effect_teleglow, rng( minglow, maxglow ) * 100 );
}
int startx = ( p.x < 8 ? 0 : p.x - 8 ), endx = ( p.x + 8 >= SEEX * 3 ? SEEX * 3 - 1 : p.x + 8 );
int starty = ( p.y < 8 ? 0 : p.y - 8 ), endy = ( p.y + 8 >= SEEY * 3 ? SEEY * 3 - 1 : p.y + 8 );
tripoint dest( startx, starty, p.z );
for( int &i = dest.x; i <= endx; i++ ) {
for( int &j = dest.y; j <= endy; j++ ) {
switch( rng( 1, 80 ) ) {

constexpr half_open_rectangle<point> map_bounds( point_zero, point( MAPSIZE_X, MAPSIZE_Y ) );
constexpr point cascade_reach( 8, 8 );

point start = clamp( p.xy() - cascade_reach, map_bounds );
point end = clamp( p.xy() + cascade_reach, map_bounds );

std::vector<int> rolls;

tripoint dest = p;
for( dest.y = start.y ; dest.y < end.y; dest.y++ ) {
for( dest.x = start.x ; dest.x < end.x; dest.x++ ) {
switch( rng( 0, 80 ) ) {
case 1:
case 2:
emp_blast( dest );
break;
case 3:
case 4:
case 5:
for( int k = i - 1; k <= i + 1; k++ ) {
for( int l = j - 1; l <= j + 1; l++ ) {
for( int k = -1; k <= 1; k++ ) {
for( int l = -1; l <= 1; l++ ) {
field_type_id type = fd_null;
switch( rng( 1, 7 ) ) {
case 1:
Expand All @@ -751,7 +797,7 @@ void resonance_cascade( const tripoint &p )
break;
}
if( !one_in( 3 ) ) {
g->m.add_field( { k, l, p.z }, type, 3 );
g->m.add_field( dest + point( k, l ), type, 3 );
}
}
}
Expand All @@ -769,18 +815,24 @@ void resonance_cascade( const tripoint &p )
break;
case 13:
case 14:
case 15:
spawn_details = MonsterGroupManager::GetResultFromGroup( GROUP_NETHER );
case 15: {
MonsterGroupResult spawn_details = MonsterGroupManager::GetResultFromGroup( GROUP_NETHER );
g->place_critter_at( spawn_details.name, dest );
break;
}
case 16:
case 17:
case 18:
g->m.destroy( dest );
break;
case 19:
explosion( dest, rng( 1, 10 ), rng( 0, 1 ) * rng( 0, 6 ), one_in( 4 ) );
case 19: {
explosion_data ex;
ex.radius = 1;
ex.damage = rng( 1, 10 );
ex.fire = one_in( 4 );
explosion( dest, ex );
break;
}
default:
break;
}
Expand All @@ -807,6 +859,37 @@ float blast_radius_from_legacy( int power, float distance_factor )
( std::log( 0.75f ) / std::log( distance_factor ) );
}

explosion_queue &get_explosion_queue()
{
static explosion_queue singleton;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nicer if it was in game or some other tracker instead of the anti-pattern thing.
Now, game is a god class, but the number of anti-patterns would remain the same instead of being +1'd.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, game would've been a better place for this, but that haven't crossed my mind back then.

return singleton;
}

void explosion_queue::execute()
{
while( !elems.empty() ) {
queued_explosion exp = std::move( elems.front() );
elems.pop_front();
switch( exp.type ) {
case ExplosionType::Regular:
explosion_funcs::regular( exp );
break;
case ExplosionType::Flashbang:
explosion_funcs::flashbang( exp );
break;
case ExplosionType::ResonanceCascade:
explosion_funcs::resonance_cascade( exp );
break;
case ExplosionType::Shockwave:
explosion_funcs::shockwave( exp );
break;
default:
debugmsg( "Explosion type not implemented." );
break;
}
}
}

} // namespace explosion_handler

float shrapnel_calc( const float &intensity, const float &last_obstacle, const int & )
Expand Down
Loading