/*
This file is part of Minitrem.
Minitrem is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Minitrem is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Minitrem. If not, see .
*/
#include "game.hpp"
namespace game {
unit_soldier_t::unit_soldier_t(game::state_t *game) : unit_t(game, UNIT_SOLDIER)
{
size[0] = v2f_t(-0.3f, +0.0f);
size[1] = v2f_t(+0.3f, +0.3f);
render_size[0] = v2f_t(-0.5f, -1.2f);
render_size[1] = v2f_t(+0.5f, +0.3f);
cmodel.cflags = CF_BODY;
move.cflags = CF_SOLID | CF_BODY | CF_WATER;
name = text::get(text::UNIT_NAME_SOLDIER);
wake();
friendly = true;
controllable = true;
health = max_health = 20;
}
void unit_soldier_t::check_area(void)
{
rectf_t bounds;
bounds[0] = x - v2f_t(10, 10);
bounds[1] = x + v2f_t(10, 10);
for (world::entity_t *went : game->world.get_entities(bounds, -1)) {
auto ent = dynamic_cast(went);
// WTF?
if (!ent)
continue;
if (ent == this)
continue;
// Wake everything around.
if (!ent->ignore_waking) {
ent->wake_time = game->now;
if (!ent->awake)
ent->wake();
}
}
}
static v2f_t spread_aim(v2f_t x, v2f_t aim, float cof, procgen::prng_t *prng)
{
float r, r_, dth;
v2f_t tmp;
r = (aim - x).len();
r_ = r + cof * r * prng->next_float(-1.0f, 1.0f);
tmp = (aim - x) / r * r_;
dth = cof * prng->next_float(-1.0f, 1.0f);
return x + v2f_t(cos(dth) * tmp[0] - sin(dth) * tmp[1],
sin(dth) * tmp[0] + cos(dth) * tmp[1]);
}
void unit_soldier_t::shoot(v2f_t from, v2f_t at, int damage)
{
world::trace_t trace;
fx_tracer_t *tracer;
trace = world->ray_v_all_p3d(from, at, CF_SOLID|CF_BODY|CF_BODY_SMALL, -1, this);
if (trace.hit && trace.ent) {
entity_t *ent = dynamic_cast(trace.ent);
ent->damage(damage, this);
}
tracer = new fx_tracer_t(game, from, trace.end);
tracer->place(&game->world);
if (trace.hit && (!trace.ent || trace.ent->type == ET_UNIT)) {
fx_bullet_miss_t *bullet_miss;
bool water;
water = (trace.tile && trace.tile->type == TILE_WATER);
bullet_miss = new fx_bullet_miss_t(game, trace.end, water);
bullet_miss->place(&game->world);
}
}
void unit_soldier_t::fire_shotgun(v2f_t aim)
{
v2f_t muzzle_point;
world::trace_t trace;
fx_flash_t *flash;
muzzle_point = x + v2f_t(0, -0.7f);
trace = world->ray_v_all_p3d(x, aim, CF_SOLID|CF_BODY|CF_BODY_SMALL, -1, this);
if (!manual_firing && trace.hit &&
trace.ent && trace.ent->type == ET_UNIT) {
unit_t *unit = dynamic_cast(trace.ent);
// Refuse to shoot friendlies.
if (unit->friendly)
return;
}
for (size_t i = 0; i < 5; i++) {
v2f_t end;
end = spread_aim(muzzle_point, aim, 0.1f, &game->prng);
shoot(muzzle_point, end, 1);
}
flash = new fx_flash_t(game, muzzle_point, 5.0f, sf::Color(255, 170, 50, 80));
flash->place(&game->world);
last_attack = game->now;
assets::soldier.fire.play_3d(x);
game->hivemind_alert(x, 14.0f, true, x);
}
void unit_soldier_t::target_and_attack(void)
{
unit_t *target;
v2f_t aim;
if (manual_firing) {
aim = manual_firing_target;
last_target_x = aim;
goto skip_targetting;
}
if (game->now < next_targetting)
return;
next_targetting = game->now + 0.2;
target = find_target(world, x, 5.0f, false);
if (!target) {
aim_marker.reset();
return;
}
aim = target->x;
last_target_time = game->now;
last_target_x = target->x;
skip_targetting:
aim_marker = std::make_unique(game, aim);
if (last_attack + game->prng.next_float(1.4f, 1.6f) > game->now)
return;
fire_shotgun(aim);
}
void unit_soldier_t::on_think(void)
{
keep_moving(2.0);
if (!move.moving)
move_marker.reset();
if (move.moving && (x - move.last_step).len() > 0.5f) {
move.last_step = x;
assets::soldier.step_stone.play_3d(x);
}
check_area();
target_and_attack();
}
void unit_soldier_t::on_damage(unit_t *attacker)
{
assets::soldier.pain.play_3d(x);
}
void unit_soldier_t::on_death(void)
{
if (health >= -10) {
render_size[0] = v2f_t(-0.75f, -0.5f);
render_size[1] = v2f_t(+0.75f, +0.5f);
assets::soldier.death.play_3d(x);
} else {
assets::soldier.gib_sound.play_3d(x);
}
render_layer = render::LAYER_FLAT;
cmodel.cflags = CF_BACKGROUND;
place(world, x);
controllable = false;
game->selected_units.erase(this);
move_marker.reset();
aim_marker.reset();
}
void unit_soldier_t::render_to(render::state_t *render)
{
sf::Color selection_color;
if (selected == selection_cookie) {
if (health == max_health)
selection_color = sf::Color::Green;
else if (health >= max_health / 2)
selection_color = sf::Color::Yellow;
else if (dead)
selection_color = sf::Color::Black;
else
selection_color = sf::Color::Red;
render->render(0.0, &assets::unit_selected, cmodel.bounds, selection_color);
}
if (!dead) {
render::oriented_sprite_t *legs, *body;
float body_angle;
if (move.moving && !move.blocked)
legs = &assets::soldier.legs_walking;
else
legs = &assets::soldier.legs_idle;
if (manual_firing || last_target_time + 3 > game->now) {
if (last_attack + 0.1 > game->now)
body = &assets::soldier.body_firing;
else
body = &assets::soldier.body_aiming;
body_angle = (last_target_x - x).angle();
} else {
body = &assets::soldier.body_idle;
body_angle = move.angle;
}
render->render(game->now * 10, legs, render_bounds, move.angle);
render->render(game->now * 10, body, render_bounds, body_angle);
render->render(game->now * 10, &assets::soldier.head_idle, render_bounds, body_angle);
} else {
float phase = clamp((game->now - death_time) * 5, 0, 0.9);
if (health < -10)
render->render(phase, &assets::soldier.gibbing, render_bounds);
else
render->render(phase, &assets::soldier.dead, render_bounds);
}
unit_t::render_to(render);
}
void unit_soldier_t::render_late_to(render::state_t *render)
{
if (selected == selection_cookie)
render->render(0.0, &assets::unit_selected_halo, cmodel.bounds, selection_color);
}
}