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

Magic phase 7 - spell effects #30428

Merged
merged 1 commit into from
May 24, 2019
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
327 changes: 327 additions & 0 deletions src/magic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -802,3 +802,330 @@ int known_magic::time_to_learn_spell( const player &p, spell_id sp ) const
return base_time * ( 1.0 + sp.obj().difficulty / ( 1.0 + ( p.get_int() - 8.0 ) / 8.0 ) +
( p.get_skill_level( skill_id( "SPELLCRAFT" ) ) / 10.0 ) );
}

// spell_effect

namespace spell_effect
{

static tripoint random_point( int min_distance, int max_distance, const tripoint &player_pos )
{
const int angle = rng( 0, 360 );
const int dist = rng( min_distance, max_distance );
const int x = round( dist * cos( angle ) );
const int y = round( dist * sin( angle ) );
return tripoint( x + player_pos.x, y + player_pos.y, player_pos.z );
}

void teleport( int min_distance, int max_distance )
{
if( min_distance > max_distance || min_distance < 0 || max_distance < 0 ) {
debugmsg( "ERROR: Teleport argument(s) invalid" );
return;
}
const tripoint player_pos = g->u.pos();
tripoint target;
// limit the loop just in case it's impossble to find a valid point in the range
int tries = 0;
do {
target = random_point( min_distance, max_distance, player_pos );
tries++;
} while( g->m.impassable( target ) && tries < 20 );
if( tries == 20 ) {
add_msg( m_bad, _( "Unable to find a valid target for teleport." ) );
return;
}
g->place_player( target );
}

void pain_split()
{
player &p = g->u;
add_msg( m_info, _( "Your injuries even out." ) );
int num_limbs = 0; // number of limbs effected (broken don't count)
int total_hp = 0; // total hp among limbs

for( const int &part : p.hp_cur ) {
if( part != 0 ) {
num_limbs++;
total_hp += part;
}
}
for( int &part : p.hp_cur ) {
const int hp_each = total_hp / num_limbs;
if( part != 0 ) {
part = hp_each;
}
}
}

void move_earth( const tripoint &target )
{
ter_id ter_here = g->m.ter( target );

std::set<ter_id> empty_air = { t_hole };
std::set<ter_id> deep_pit = { t_pit, t_slope_down };
std::set<ter_id> shallow_pit = { t_pit_corpsed, t_pit_covered, t_pit_glass, t_pit_glass_covered, t_pit_shallow, t_pit_spiked, t_pit_spiked, t_pit_spiked_covered, t_rootcellar };
std::set<ter_id> soft_dirt = { t_grave, t_dirt, t_sand, t_clay, t_dirtmound, t_grass, t_grass_long, t_grass_tall, t_grass_golf, t_grass_dead, t_grass_white, t_dirtfloor, t_fungus_floor_in, t_fungus_floor_sup, t_fungus_floor_out, t_sandbox };
// rock: can still be dug through with patience, converts to sand upon completion
std::set<ter_id> hard_dirt = { t_pavement, t_pavement_y, t_sidewalk, t_concrete, t_thconc_floor, t_thconc_floor_olight, t_strconc_floor, t_floor, t_floor_waxed, t_carpet_red, t_carpet_yellow, t_carpet_purple, t_carpet_green, t_linoleum_white, t_linoleum_gray, t_slope_up, t_rock_red, t_rock_green, t_rock_blue, t_floor_red, t_floor_green, t_floor_blue, t_pavement_bg_dp, t_pavement_y_bg_dp, t_sidewalk_bg_dp };

if( empty_air.count( ter_here ) == 1 ) {
add_msg( m_bad, _( "All the dust in the air here falls to the ground." ) );
} else if( deep_pit.count( ter_here ) == 1 ) {
g->m.ter_set( target, t_hole );
add_msg( _( "The pit has deepened further." ) );
} else if( shallow_pit.count( ter_here ) == 1 ) {
g->m.ter_set( target, t_pit );
add_msg( _( "More debris shifts out of the pit." ) );
} else if( soft_dirt.count( ter_here ) == 1 ) {
g->m.ter_set( target, t_pit_shallow );
add_msg( _( "The earth moves out of the way for you" ) );
} else if( hard_dirt.count( ter_here ) == 1 ) {
g->m.ter_set( target, t_sand );
add_msg( _( "The rocks here are ground into sand." ) );
} else {
add_msg( m_bad, _( "The earth here does not listen to your command to move." ) );
}
}

static bool in_spell_aoe( const tripoint &target, const tripoint &epicenter, const int &radius,
const bool ignore_walls )
{
if( ignore_walls ) {
return rl_dist( epicenter, target ) <= radius;
}
std::vector<tripoint> trajectory = line_to( epicenter, target );
for( const tripoint &pt : trajectory ) {
if( g->m.impassable( pt ) ) {
return false;
}
}
return rl_dist( epicenter, target ) <= radius;
}

static std::set<tripoint> spell_effect_blast( spell &, const tripoint &, const tripoint &target,
const int aoe_radius, const bool ignore_walls )
{
std::set<tripoint> targets;
// TODO: Make this breadth-first
for( int x = target.x - aoe_radius; x < target.x + aoe_radius; x++ ) {
for( int y = target.y - aoe_radius; y < target.y + aoe_radius; y++ ) {
for( int z = target.z - aoe_radius; z < target.z + aoe_radius; z++ ) {
const tripoint potential_target( x, y, z );
if( in_spell_aoe( potential_target, target, aoe_radius, ignore_walls ) ) {
targets.emplace( potential_target );
}
}
}
}
return targets;
}

static std::set<tripoint> spell_effect_cone( spell &sp, const tripoint &source,
const tripoint &target,
const int aoe_radius, const bool ignore_walls )
{
std::set<tripoint> targets;
// cones go all the way to end (if they don't hit an obstacle)
const int range = sp.range();
const int initial_angle = coord_to_angle( source, target );
std::set<tripoint> end_points;
for( int angle = initial_angle - floor( aoe_radius / 2.0 );
angle <= initial_angle + ceil( aoe_radius / 2.0 ); angle++ ) {
tripoint potential;
calc_ray_end( angle, range, source, potential );
end_points.emplace( potential );
}
for( const tripoint &ep : end_points ) {
std::vector<tripoint> trajectory = line_to( ep, source );
for( const tripoint &tp : trajectory ) {
if( ignore_walls || g->m.passable( tp ) ) {
targets.emplace( tp );
} else {
break;
}
}
}
// we don't want to hit ourselves in the blast!
targets.erase( source );
return targets;
}

static std::set<tripoint> spell_effect_line( spell &, const tripoint &source,
const tripoint &target,
const int aoe_radius, const bool ignore_walls )
{
std::set<tripoint> targets;
const int initial_angle = coord_to_angle( source, target );
tripoint clockwise_starting_point;
calc_ray_end( initial_angle - 90, floor( aoe_radius / 2.0 ), source, clockwise_starting_point );
tripoint cclockwise_starting_point;
calc_ray_end( initial_angle + 90, ceil( aoe_radius / 2.0 ), source, cclockwise_starting_point );
tripoint clockwise_end_point;
calc_ray_end( initial_angle - 90, floor( aoe_radius / 2.0 ), target, clockwise_end_point );
tripoint cclockwise_end_point;
calc_ray_end( initial_angle + 90, ceil( aoe_radius / 2.0 ), target, cclockwise_end_point );

std::vector<tripoint> start_width_lh = line_to( source, cclockwise_starting_point );
std::vector<tripoint> start_width_rh = line_to( source, clockwise_starting_point );

int start_width_lh_blocked = start_width_lh.size();
int start_width_rh_blocked = start_width_rh.size() + 1;
for( const tripoint &p : start_width_lh ) {
if( ignore_walls || g->m.passable( p ) ) {
start_width_lh_blocked--;
} else {
break;
}
}
for( const tripoint &p : start_width_rh ) {
if( ignore_walls || g->m.passable( p ) ) {
start_width_rh_blocked--;
} else {
break;
}
}

std::reverse( start_width_rh.begin(), start_width_rh.end() );
std::vector<tripoint> start_width;
start_width.reserve( start_width_lh.size() + start_width_rh.size() + 2 );
start_width.insert( start_width.end(), start_width_rh.begin(), start_width_rh.end() );
start_width.emplace_back( source );
start_width.insert( start_width.end(), start_width_lh.begin(), start_width_lh.end() );
std::vector<tripoint> end_width = line_to( cclockwise_end_point, clockwise_end_point );
// line_to omits the starting point. we want it back.
end_width.insert( end_width.begin(), cclockwise_end_point );

// we're going from right to left (clockwise to counterclockwise)
for( int i = start_width_rh_blocked;
i <= static_cast<int>( start_width.size() ) - start_width_lh_blocked; i++ ) {
for( tripoint &ep : end_width ) {
for( tripoint &p : line_to( start_width[i], ep ) ) {
if( ignore_walls || g->m.passable( p ) ) {
targets.emplace( p );
} else {
break;
}
}
}
}

targets.erase( source );

return targets;
}

// spells do not reduce in damage the further away from the epicenter the targets are
// rather they do their full damage in the entire area of effect
static std::set<tripoint> spell_effect_area( spell &sp, const tripoint &source,
const tripoint &target,
std::function<std::set<tripoint>( spell &, const tripoint &, const tripoint &, const int, const bool )>
aoe_func, bool ignore_walls = false )
{
std::set<tripoint> targets = { target }; // initialize with epicenter
if( sp.aoe() <= 1 ) {
return targets;
}

const int aoe_radius = sp.aoe();
targets = aoe_func( sp, source, target, aoe_radius, ignore_walls );

for( const tripoint &p : targets ) {
if( !sp.is_valid_target( p ) ) {
targets.erase( p );
}
}

// Draw the explosion
std::map<tripoint, nc_color> explosion_colors;
for( auto &pt : targets ) {
explosion_colors[pt] = sp.damage_type_color();
}

explosion_handler::draw_custom_explosion( g->u.pos(), explosion_colors );
return targets;
}

static void damage_targets( spell &sp, std::set<tripoint> targets )
{
for( const tripoint target : targets ) {
Creature *const cr = g->critter_at<Creature>( target );
if( !cr ) {
continue;
}

projectile bolt;
bolt.impact = sp.get_damage_instance();
bolt.proj_effects.emplace( "magic" );

dealt_projectile_attack atk;
atk.end_point = target;
atk.hit_critter = cr;
atk.proj = bolt;
if( sp.damage() > 0 ) {
cr->deal_projectile_attack( &g->u, atk, true );
} else {
sp.heal( target );
add_msg( m_good, _( "%s wounds are closing up!" ), cr->disp_name( true ) );
}
if( !sp.effect_data().empty() ) {
const int dur_moves = sp.duration();
const time_duration dur_td = 1_turns * dur_moves / 100;
const std::vector<body_part> all_bp = { bp_head, bp_torso, bp_arm_l, bp_arm_r, bp_leg_l, bp_leg_r, };
for( const body_part bp : all_bp ) {
cr->add_effect( efftype_id( sp.effect_data() ), dur_td, bp );
}
}
}
}

void projectile_attack( spell &sp, const tripoint &source, const tripoint &target )
{
std::vector<tripoint> trajectory = line_to( source, target );
for( const tripoint &pt : trajectory ) {
if( g->m.impassable( pt ) || pt == trajectory.back() ) {
target_attack( sp, source, target );
}
}
}

void target_attack( spell &sp, const tripoint &source, const tripoint &epicenter )
{
damage_targets( sp, spell_effect_area( sp, source, epicenter, spell_effect_blast ) );
}

void cone_attack( spell &sp, const tripoint &source, const tripoint &target )
{
damage_targets( sp, spell_effect_area( sp, source, target, spell_effect_cone ) );
}

void line_attack( spell &sp, const tripoint &source, const tripoint &target )
{
damage_targets( sp, spell_effect_area( sp, source, target, spell_effect_line ) );
}

void spawn_ethereal_item( spell &sp )
{
item granted( sp.effect_data(), calendar::turn );
if( !granted.is_comestible() ) {
granted.set_rot( -sp.duration_turns() );
granted.set_flag( "ETHEREAL_ITEM" );
}
if( granted.count_by_charges() && sp.damage() > 0 ) {
granted.charges = sp.damage();
}
if( g->u.can_wear( granted ).success() ) {
granted.set_flag( "FIT" );
g->u.wear_item( granted, false );
} else {
g->u.weapon = granted;
}
if( !granted.count_by_charges() ) {
for( int i = 1; i < sp.damage(); i++ ) {
g->u.i_add( granted );
}
}
}

}
2 changes: 1 addition & 1 deletion src/magic.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ namespace spell_effect
{
void teleport( int min_distance, int max_distance );
void pain_split(); // only does g->u
void shallow_pit( const tripoint &target );
void move_earth( const tripoint &target );
void target_attack( spell &sp, const tripoint &source, const tripoint &target );
void projectile_attack( spell &sp, const tripoint &source, const tripoint &target );
void cone_attack( spell &sp, const tripoint &source, const tripoint &target );
Expand Down