Skip to content

Commit

Permalink
[CR]AoE cone for birdshot (#1118)
Browse files Browse the repository at this point in the history
* Implement shapes: cone, offset, rotate

* Test-backed cone generation

* Kinda working cone birdshot

* Rotation matrix

* AoE predictor with probability

* Working window breaking AoE

* Spread damage when attacking human-shaped

* Random offset + rough animation

* Shaped attacks don't aim. Bump dispersion for birdshot.

* Curses mode animation

* Most of tiles animation of cones

* Kinda OK tiles animation

* Exclude origin from cone

* Item info + adjustment to armor mult display
  • Loading branch information
Coolthulhu authored Nov 27, 2021
1 parent 9c859b0 commit 7835dd3
Show file tree
Hide file tree
Showing 47 changed files with 1,840 additions and 228 deletions.
7 changes: 5 additions & 2 deletions data/json/items/ammo/shot.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,11 @@
"description": "Weak shotgun ammunition. Designed for hunting birds and other small game, its applications in combat are very limited.",
"price": 100,
"price_postapoc": 400,
"flags": [ "IRREPLACEABLE_CONSUMABLE" ],
"proportional": { "damage": { "damage_type": "stab", "amount": 0.3 }, "recoil": 0.6, "loudness": 0.8 },
"range": 0,
"damage": { "damage_type": "stab", "amount": 20, "armor_multiplier": 2.0 },
"dispersion": 1000,
"loudness": 80,
"shape": [ "cone", { "half_angle": 15, "length": 8 } ],
"extend": { "effects": [ "NOGIB" ] }
},
{
Expand Down
18 changes: 16 additions & 2 deletions gfx/MSX++UnDeadPeopleEdition/tile_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -133518,7 +133518,21 @@
},
{
"file": "fallback.png",
"tiles": [],
"//": "Also placeholder tiles for shotgun cone blast",
"tiles": [
{
"id": "shot_cone_weak",
"fg": 19296
},
{
"id": "shot_cone_medium",
"fg": 19297
},
{
"id": "shot_cone_strong",
"fg": 19298
}
],
"ascii": [
{
"offset": 0,
Expand Down Expand Up @@ -133603,4 +133617,4 @@
]
}
]
}
}
10 changes: 9 additions & 1 deletion gfx/tile_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,15 @@
"fg":0
},
{
"id":"animation_bullet_shrapnel",
"id":"shot_cone_weak",
"fg":0
},
{
"id":"shot_cone_medium",
"fg":0
},
{
"id":"shot_cone_strong",
"fg":0
},
{
Expand Down
21 changes: 21 additions & 0 deletions gfx/tile_config_template.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,27 @@
"rotates":false,
"multitile":false
},
{
"id":"shot_cone_weak",
"fg":-1,
"bg":-1,
"rotates":false,
"multitile":false
},
{
"id":"shot_cone_medium",
"fg":-1,
"bg":-1,
"rotates":false,
"multitile":false
},
{
"id":"shot_cone_strong",
"fg":-1,
"bg":-1,
"rotates":false,
"multitile":false
},
{
"id":"cursor",
"fg":-1,
Expand Down
12 changes: 11 additions & 1 deletion src/activity_actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,19 @@ void aim_activity_actor::do_turn( player_activity &act, Character &who )
return;
}
}
cata::optional<shape_factory> shape_gen;
if( weapon->ammo_current() && weapon->ammo_current()->ammo &&
weapon->ammo_current()->ammo->shape ) {
shape_gen = weapon->ammo_current()->ammo->shape;
}

g->temp_exit_fullscreen();
target_handler::trajectory trajectory = target_handler::mode_fire( you, *this );
target_handler::trajectory trajectory;
if( !shape_gen ) {
trajectory = target_handler::mode_fire( you, *this );
} else {
trajectory = target_handler::mode_shaped( you, *shape_gen, *this );
}
g->reenter_fullscreen();

if( aborted ) {
Expand Down
160 changes: 160 additions & 0 deletions src/animation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "point.h"
#include "popup.h"
#include "posix_time.h"
#include "ranged.h"
#include "translations.h"
#include "type_id.h"
#include "ui_manager.h"
Expand Down Expand Up @@ -83,6 +84,13 @@ class bullet_animation : public basic_animation
}
};

class wave_animation : public basic_animation
{
public:
wave_animation() : basic_animation( 1 ) {
}
};

bool is_point_visible( const tripoint &p, int margin = 0 )
{
return g->is_in_viewport( p, margin ) && g->u.sees( p );
Expand Down Expand Up @@ -962,6 +970,158 @@ void game::draw_monster_override( const tripoint &, const mtype_id &, const int,
}
#endif

bucketed_points bucket_by_distance( const tripoint &origin,
const std::map<tripoint, double> &to_bucket )
{
std::map<int, one_bucket> by_distance;
for( const std::pair<tripoint, double> &pv : to_bucket ) {
int dist = trig_dist_squared( origin, pv.first );
by_distance[dist].emplace_back( pv.first, pv.second );
}
bucketed_points buckets;
for( const std::pair<int, one_bucket> &bc : by_distance ) {
buckets.emplace_back( bc.second );
}
return buckets;
}

bucketed_points optimal_bucketing( const bucketed_points &buckets, size_t max_buckets )
{
if( buckets.size() <= max_buckets ) {
return buckets;
}
assert( max_buckets > 1 );

std::vector<size_t> sizes = {};
for( const one_bucket &bc : buckets ) {
sizes.emplace_back( bc.size() );
}

bucketed_points optimal = buckets;
// TODO: Good algorithm here, this one is a greedy finder of smallest adjacent size sums
for( size_t i = 0; i < buckets.size() - max_buckets; i++ ) {
auto smallest = sizes.begin();
size_t smallest_sum = *smallest + *( smallest + 1 );
for( auto iter = sizes.begin() + 1; ( iter + 1 ) != sizes.end(); iter++ ) {
size_t sum = *iter + *( iter + 1 );
if( sum < smallest_sum ) {
smallest = iter;
smallest_sum = sum;
}
}

size_t distance = std::distance( sizes.begin(), smallest );
sizes[distance] += sizes[distance + 1];
sizes.erase( smallest + 1 );
auto left_bucket = std::next( optimal.begin(), distance );
auto right_bucket = std::next( left_bucket );
left_bucket->insert( left_bucket->end(), right_bucket->begin(), right_bucket->end() );
optimal.erase( right_bucket );
}

return optimal;
}

static void draw_cone_aoe_curses( const tripoint &, const bucketed_points &waves )
{
// Calculate screen offset relative to player + view offset position
const avatar &u = get_avatar();
const tripoint center = u.pos() + u.view_offset;
const tripoint topleft( center.x - catacurses::getmaxx( g->w_terrain ) / 2,
center.y - catacurses::getmaxy( g->w_terrain ) / 2, 0 );

auto it = waves.begin();
shared_ptr_fast<game::draw_callback_t> wave_cb =
make_shared_fast<game::draw_callback_t>( [&]() {
// All the buckets up until now
for( auto inner_it = waves.begin(); inner_it != std::next( it ); inner_it++ ) {
for( const point_with_value &pr : *inner_it ) {
// update tripoint in relation to top left corner of curses window
// mvwputch already filters out of bounds coordinates
const tripoint p = pr.pt - topleft;
int intensity = ( pr.val >= 1.0 ) + ( pr.val >= 0.5 ) + ( inner_it == it );
nc_color col;
switch( intensity ) {
case 3:
col = c_red;
break;
case 2:
col = c_yellow;
break;
case 1:
col = c_white;
break;
default:
col = c_dark_gray;
break;
}

// TODO: Prettier
mvwputch( g->w_terrain, p.xy(), col, '*' );
}
}
} );
g->add_draw_callback( wave_cb );

wave_animation anim;
for( it = waves.begin(); it != waves.end(); it++ ) {
anim.progress();
}
}

namespace ranged
{
void draw_cone_aoe( const tripoint &origin, const std::map<tripoint, double> &aoe )
{
if( test_mode ) {
return;
}

bucketed_points buckets = bucket_by_distance( origin, aoe );
// That hardcoded value could be improved... Not sure about the name
size_t max_bucket_count = std::min<size_t>( 10, aoe.size() );
bucketed_points waves = optimal_bucketing( buckets, max_bucket_count );

#if defined(TILES)
if( !use_tiles ) {
draw_cone_aoe_curses( origin, waves );
return;
}

// This is copied from explosion code
// Not sure if it couldn't be cleaner, without that lambda capture thing
one_bucket combined_layer;
combined_layer.reserve( aoe.size() );

wave_animation anim;

shared_ptr_fast<game::draw_callback_t> wave_cb =
make_shared_fast<game::draw_callback_t>( [&]() {
tilecontext->init_draw_cone_aoe( origin, combined_layer );
} );
g->add_draw_callback( wave_cb );

for( const one_bucket &layer : waves ) {
// Older layers get a fade effect
for( point_with_value &pv : combined_layer ) {
pv.val *= 1.0 - ( 2.0 / max_bucket_count );
}
combined_layer.insert( combined_layer.end(), layer.begin(), layer.end() );
if( std::any_of( combined_layer.begin(), combined_layer.end(),
[]( const point_with_value & element ) {
return is_point_visible( element.pt );
} ) ) {
anim.progress();
}
}

tilecontext->void_cone_aoe();
#else
draw_cone_aoe_curses( origin, waves );
#endif
}
} // namespace ranged

bool minimap_requires_animation()
{
#if defined(TILES)
Expand Down
23 changes: 23 additions & 0 deletions src/animation.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
#ifndef CATA_SRC_ANIMATION_H
#define CATA_SRC_ANIMATION_H

#include <list>
#include <map>
#include <vector>

#include "color.h"
#include "point.h"

enum explosion_neighbors {
N_NO_NEIGHBORS = 0,
Expand Down Expand Up @@ -31,6 +36,24 @@ struct explosion_tile {
nc_color color;
};

struct point_with_value {
point_with_value() = default;
point_with_value( const point_with_value & ) = default;
point_with_value( const tripoint &pt, double val )
: pt( pt ), val( val )
{}
tripoint pt;
double val;
};

using one_bucket = std::vector<point_with_value>;
using bucketed_points = std::list<one_bucket>;

// TODO: Better file
bucketed_points bucket_by_distance( const tripoint &origin,
const std::map<tripoint, double> &to_bucket );
bucketed_points optimal_bucketing( const bucketed_points &buckets, size_t max_buckets );

bool minimap_requires_animation();
bool terrain_requires_animation();

Expand Down
2 changes: 1 addition & 1 deletion src/ballistics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ dealt_projectile_attack projectile_attack( const projectile &proj_arg, const tri
double rad = atan2( dy, dx );

// cap wild misses at +/- 30 degrees
rad += ( one_in( 2 ) ? 1 : -1 ) * std::min( ARCMIN( aim.dispersion ), DEGREES( 30 ) );
rad += ( one_in( 2 ) ? 1 : -1 ) * std::min( degmin2rad( aim.dispersion ), deg2rad( 30 ) );

// TODO: This should also represent the miss on z axis
const int offset = std::min<int>( range, std::sqrt( aim.missed_by_tiles ) );
Expand Down
Loading

0 comments on commit 7835dd3

Please sign in to comment.