diff --git a/data/mods/Magiclysm/Spells/attunements/Earth_Elemental.json b/data/mods/Magiclysm/Spells/attunements/Earth_Elemental.json index cb0182f6e4bc0..da693ebb61446 100644 --- a/data/mods/Magiclysm/Spells/attunements/Earth_Elemental.json +++ b/data/mods/Magiclysm/Spells/attunements/Earth_Elemental.json @@ -48,5 +48,56 @@ "max_aoe": 35, "aoe_increment": 1, "max_level": 35 + }, + { + "id": "rock_blast", + "type": "SPELL", + "description": "Fire three large rocks at an enemy, causing blunt damage and knocking it back.", + "valid_targets": [ "hostile" ], + "flags": [ "SOMATIC", "NO_LEGS" ], + "effect": "attack", + "shape": "blast", + "max_level": 35, + "min_damage": 20, + "max_damage": 180, + "damage_increment": 4.6, + "min_range": 8, + "max_range": 15, + "range_increment": 0.2, + "sound_id": "earth_spell", + "sound_variant": "strong", + "energy_source": "STAMINA", + "spell_class": "EARTH_ELEMENTAL", + "damage_type": "bash", + "base_energy_cost": 5000, + "base_casting_time": 200, + "extra_effects": [ { "id": "single_rock_blast" }, { "id": "single_rock_blast" }, { "id": "rock_blast_push" } ] + }, + { + "id": "single_rock_blast", + "type": "SPELL", + "description": "A single portion of the rock blast spell", + "valid_targets": [ "hostile" ], + "effect": "attack", + "shape": "blast", + "max_level": 35, + "min_damage": 20, + "max_damage": 180, + "damage_increment": 4.6, + "damage_type": "bash", + "sound_id": "earth_spell", + "sound_variant": "strong" + }, + { + "id": "rock_blast_push", + "type": "SPELL", + "description": "The push portion of the rock blast spell", + "valid_targets": [ "hostile" ], + "effect": "directed_push", + "shape": "blast", + "max_level": 35, + "min_damage": 1, + "max_damage": 3, + "damage_increment": 0.1 } ] diff --git a/data/mods/Magiclysm/Spells/debug.json b/data/mods/Magiclysm/Spells/debug.json index 784ddecc84a51..6a4fc49fc97f9 100644 --- a/data/mods/Magiclysm/Spells/debug.json +++ b/data/mods/Magiclysm/Spells/debug.json @@ -40,6 +40,38 @@ "//7": "Learn spells by adding map of 'spell_id': 'level' ", "learn_spells": { "create_atomic_light": 1, "megablast": 2 } }, + { + "type": "SPELL", + "id": "debug_push_basic", + "name": "debug push no aoe", + "description": "pushes all types of objects with an aoe of 0", + "valid_targets": [ "ally", "self", "hostile", "field", "item", "ground" ], + "effect": "directed_push", + "shape": "blast", + "min_damage": 0, + "max_damage": 10, + "damage_increment": 1, + "min_range": 20, + "max_range": 20, + "max_level": 10 + }, + { + "type": "SPELL", + "id": "debug_push_blast", + "name": "debug push blast", + "description": "pushes all types of objects with an aoe of 3 in a blast pattern", + "valid_targets": [ "ally", "self", "hostile", "field", "item", "ground" ], + "effect": "directed_push", + "shape": "blast", + "min_aoe": 3, + "max_aoe": 3, + "min_damage": 0, + "max_damage": 10, + "damage_increment": 1, + "min_range": 20, + "max_range": 20, + "max_level": 10 + }, { "id": "spawn_debug_monster", "type": "SPELL", diff --git a/data/mods/Magiclysm/traits/attunements.json b/data/mods/Magiclysm/traits/attunements.json index a0eaa758a0b47..6cf314c6ba21d 100644 --- a/data/mods/Magiclysm/traits/attunements.json +++ b/data/mods/Magiclysm/traits/attunements.json @@ -294,7 +294,7 @@ "valid": false, "description": "Stone is superior to flesh. You have allowed this axiom into your life, and work to correct your own weakness with biomancy. The strength of the Earth rushed up to meet you with open arms.", "prereqs": [ "BIOMANCER", "EARTHSHAPER" ], - "spells_learned": [ [ "quake", 5 ] ], + "spells_learned": [ [ "quake", 5 ], [ "rock_blast", 5 ] ], "armor": [ { "parts": [ "torso", "arm_l", "arm_r", "hand_l", "hand_r", "leg_l", "leg_r", "foot_l", "foot_r", "head" ], "cut": 15 } ], diff --git a/src/magic.cpp b/src/magic.cpp index 9d2e9c839dfd1..5a8c7add2fef7 100644 --- a/src/magic.cpp +++ b/src/magic.cpp @@ -239,6 +239,7 @@ void spell_type::load( const JsonObject &jo, const std::string & ) { "translocate", spell_effect::translocate }, { "area_pull", spell_effect::area_pull }, { "area_push", spell_effect::area_push }, + { "directed_push", spell_effect::directed_push }, { "timed_event", spell_effect::timed_event }, { "ter_transform", spell_effect::transform_blast }, { "noise", spell_effect::noise }, diff --git a/src/magic.h b/src/magic.h index ef67d1c5b28ca..480874bc4b81d 100644 --- a/src/magic.h +++ b/src/magic.h @@ -642,6 +642,7 @@ void targeted_polymorph( const spell &sp, Creature &caster, const tripoint &targ void area_pull( const spell &sp, Creature &caster, const tripoint ¢er ); void area_push( const spell &sp, Creature &caster, const tripoint ¢er ); +void directed_push( const spell &sp, Creature &caster, const tripoint &target ); std::set spell_effect_blast( const override_parameters ¶ms, const tripoint &, const tripoint &target ); diff --git a/src/magic_spell_effect.cpp b/src/magic_spell_effect.cpp index 5da5002afbeb6..cdab14324f975 100644 --- a/src/magic_spell_effect.cpp +++ b/src/magic_spell_effect.cpp @@ -41,6 +41,7 @@ #include "monster.h" #include "monstergenerator.h" #include "mtype.h" +#include "npc.h" #include "optional.h" #include "overmapbuffer.h" #include "pimpl.h" @@ -654,6 +655,30 @@ void area_expander::sort_descending() } ); } +static void move_items( map &here, const tripoint &from, const tripoint &to ) +{ + for( const item &it : here.i_at( from ) ) { + here.add_item( to, it ); + } + here.i_clear( from ); +} + +static void move_field( map &here, const tripoint &from, const tripoint &to ) +{ + field &src_field = here.field_at( from ); + std::map moving_fields; + for( const std::pair &fd : src_field ) { + if( fd.first.is_valid() && !fd.first.id().is_null() ) { + const int intensity = fd.second.get_field_intensity(); + moving_fields.emplace( fd.first, intensity ); + } + } + for( const std::pair &fd : moving_fields ) { + here.remove_field( from, fd.first ); + here.set_field_intensity( to, fd.first, fd.second ); + } +} + // Moving all objects from one point to another by the power of magic. static void spell_move( const spell &sp, const Creature &caster, const tripoint &from, const tripoint &to ) @@ -686,30 +711,12 @@ static void spell_move( const spell &sp, const Creature &caster, // Moving items if( sp.is_valid_target( spell_target::item ) ) { - map &here = get_map(); - map_stack src_items = here.i_at( from ); - map_stack dst_items = here.i_at( to ); - for( const item &item : src_items ) { - dst_items.insert( item ); - } - src_items.clear(); + move_items( get_map(), from, to ); } // Moving fields. if( sp.is_valid_target( spell_target::field ) ) { - map &here = get_map(); - field &src_field = here.field_at( from ); - std::map moving_fields; - for( const std::pair &fd : src_field ) { - if( fd.first.is_valid() && !fd.first.id().is_null() ) { - const int intensity = fd.second.get_field_intensity(); - moving_fields.emplace( fd.first, intensity ); - } - } - for( const std::pair &fd : moving_fields ) { - here.remove_field( from, fd.first ); - here.set_field_intensity( to, fd.first, fd.second ); - } + move_field( get_map(), from, to ); } } @@ -749,6 +756,113 @@ void spell_effect::area_push( const spell &sp, Creature &caster, const tripoint sp.make_sound( caster.pos() ); } +static void character_push_effects( Creature *caster, Character &guy, tripoint &push_dest, + const int push_distance, const std::vector &push_vec ) +{ + int dist_left = std::abs( push_distance ); + for( const tripoint &pushed_point : push_vec ) { + if( get_map().impassable( pushed_point ) ) { + guy.hurtall( dist_left * 4, caster ); + push_dest = pushed_point; + break; + } else { + dist_left--; + } + } + guy.setpos( push_dest ); +} + +void spell_effect::directed_push( const spell &sp, Creature &caster, const tripoint &target ) +{ + std::set area = spell_effect_area( sp, target, caster ); + // this group of variables is for deferring movement of the avatar + int pushed_distance; + tripoint push_to; + std::vector pushed_vec; + bool player_pushed = false; + + ::map &here = get_map(); + + // whether it's push or pull, so how the multimap is sorted + // -1 is push and 1 is pull + const int sign = sp.damage() > 0 ? -1 : 1; + + std::multimap targets_ordered_by_range; + for( const tripoint &pt : area ) { + targets_ordered_by_range.emplace( sign * rl_dist( pt, caster.pos() ), pt ); + } + + for( const std::pair &pair : targets_ordered_by_range ) { + const tripoint &push_point = pair.second; + const int angle = coord_to_angle( caster.pos(), target ); + // positive is push, negative is pull + int push_distance = sp.damage(); + const int prev_distance = rl_dist( caster.pos(), target ); + if( push_distance < 0 ) { + push_distance = std::max( -std::abs( push_distance ), -std::abs( prev_distance ) ); + } + if( push_distance == 0 ) { + continue; + } + + tripoint push_dest; + calc_ray_end( angle, push_distance, push_point, push_dest ); + const std::vector push_vec = line_to( push_point, push_dest ); + + const Creature *critter = g->critter_at( push_point ); + if( critter != nullptr ) { + const Creature::Attitude attitude_to_target = + caster.attitude_to( *g->critter_at( push_point ) ); + + monster *mon = g->critter_at( push_point ); + npc *guy = g->critter_at( push_point ); + + if( ( sp.is_valid_target( spell_target::self ) && push_point == caster.pos() ) || + ( attitude_to_target == Creature::Attitude::FRIENDLY && + sp.is_valid_target( spell_target::ally ) ) || + ( ( attitude_to_target == Creature::Attitude::HOSTILE || + attitude_to_target == Creature::Attitude::NEUTRAL ) && + sp.is_valid_target( spell_target::hostile ) ) ) { + if( g->critter_at( push_point ) ) { + // defer this because this absolutely must be done last in order not to mess up our calculations + player_pushed = true; + pushed_distance = push_distance; + push_to = push_dest; + pushed_vec = push_vec; + } else if( mon ) { + int dist_left = std::abs( push_distance ); + for( const tripoint &pushed_push_point : push_vec ) { + if( get_map().impassable( pushed_push_point ) ) { + mon->apply_damage( &caster, bodypart_id(), dist_left * 10 ); + push_dest = pushed_push_point; + break; + } else { + dist_left--; + } + } + mon->setpos( push_dest ); + } else if( guy ) { + character_push_effects( &caster, *guy, push_dest, push_distance, push_vec ); + } + } + } + + if( sp.is_valid_target( spell_target::item ) && here.has_items( push_point ) ) { + move_items( here, push_point, push_dest ); + } + + + if( sp.is_valid_target( spell_target::field ) ) { + move_field( here, push_point, push_dest ); + } + } + + // deferred avatar pushing + if( player_pushed ) { + character_push_effects( &caster, get_avatar(), push_to, pushed_distance, pushed_vec ); + } +} + void spell_effect::spawn_ethereal_item( const spell &sp, Creature &caster, const tripoint & ) { item granted( sp.effect_data(), calendar::turn );