/*
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 {
static std::string soldier_name(procgen::prng_t *prng)
{
std::stringstream ss;
static const char *names[] = {
"Heinrich", "Hans", "Ernst", "Dieter", "Werner", "Wolfgang",
"Uwe", "Klaus", "Adolf"
};
static const char *surnames[] = {
"Kloss", "Schmidt", "Göring", "Fischer", "Müller", "Weber",
"Schulz", "Schäfer", "Klein", "Wolf", "Schröder", "Neumann",
"Zimmermann"
};
ss << names[prng->next() % (sizeof(names) / sizeof(names[0]))];
ss << " ";
ss << surnames[prng->next() % (sizeof(surnames) / sizeof(surnames[0]))];
return ss.str();
}
const float soldier_head_offset = 0.10f;
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 - soldier_head_offset);
render_size[1] = v2f_t(+0.5f, +0.3f);
cmodel.cflags = CF_BODY;
move.cflags = CF_SOLID | CF_BODY | CF_WATER;
name = soldier_name(&game->prng);
wake();
friendly = true;
controllable = true;
health = max_health = 28;
storage.max_shells = storage.shells = 20;
storage.grenades = 0;
storage.max_grenades = 3;
}
std::string german_plural(size_t count, const char *sg, const char *pl)
{
return std::to_string(count) + " " + (count == 1 ? sg : pl);
}
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();
if (r <= 0.1f)
return aim;
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;
v2f_t muzzle_point;
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);
}
muzzle_point = x + v2f_t(0, -0.7f);
tracer = new fx_tracer_t(game, muzzle_point, 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;
if (next_attack && game->time < next_attack)
return;
if (!storage.shells) {
next_attack = game->time + SEC(1);
say("Keine Schrotpatronen mehr!");
return;
}
storage.shells--;
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 < 7; i++) {
v2f_t end;
end = spread_aim(x, aim, 0.15f, &game->prng);
shoot(x, end, 2);
}
flash = new fx_flash_t(game, x, 5.0f, sf::Color(255, 170, 50, 80));
flash->place(&game->world);
assets::soldier.fire.play_3d(x);
game->hivemind_alert(x, 14.0f, true, x);
next_attack = game->time + MSEC(1000);
last_attack = game->time;
}
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);
fire_shotgun(aim);
}
void unit_soldier_t::on_think(void)
{
keep_moving(2.0);
game->wake_area(x);
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);
}
target_and_attack();
}
void unit_soldier_t::on_damage(unit_t *attacker)
{
if (health < -30) {
assets::fx.gibbing.play_3d(x);
game->deletion_list.insert(this);
return;
} else if (health < -10) {
render_size[0] = v2f_t(-0.5f, -1.2f);
render_size[1] = v2f_t(+0.5f, +0.3f);
if (!gibbed)
assets::fx.gibbing.play_3d(x);
else
assets::fx.corpse_hit.play_3d(x);
place(world, x);
gibbed = true;
} else if (health <= 0) {
render_size[0] = v2f_t(-0.75f, -0.5f);
render_size[1] = v2f_t(+0.75f, +0.5f);
if (!dead)
assets::soldier.death.play_3d(x);
else
assets::fx.corpse_hit.play_3d(x);
place(world, x);
} else
assets::soldier.pain.play_3d(x);
}
void unit_soldier_t::on_death(void)
{
render_layer = render::LAYER_FLAT;
cmodel.cflags = CF_BACKGROUND;
place(world, x);
controllable = false;
move_marker.reset();
aim_marker.reset();
}
void unit_soldier_t::command_throw_grenade(v2f_t at)
{
grenade_t *grenade;
if (!storage.grenades) {
say("Keine Granaten!");
return;
}
storage.grenades--;
grenade = new grenade_t(game, x, at, this);
grenade->place(&game->world);
}
void unit_soldier_t::command_restock(bool grenades)
{
rectf_t rect;
bool replicators = false;
size_t needed, taken = 0;
if (grenades)
needed = storage.max_grenades - storage.grenades;
else
needed = storage.max_shells - storage.shells;
if (!needed) {
say("Ich kann nicht mehr tragen.");
return;
}
rect = rectf_t::around(x, 2.0f);
for (world::entity_t *ent : game->world.get_entities(rect, CF_SOLID)) {
world::trace_t trace;
unit_t *unit;
size_t take;
if (ent->type != ET_UNIT)
continue;
unit = dynamic_cast(ent);
if (unit->type != UNIT_REPLICATOR)
continue;
trace = world->ray_v_all(x, unit->x, CF_SOLID, this);
if (trace.hit && trace.ent != ent)
continue;
replicators = true;
if (grenades) {
take = std::min(needed, unit->storage.grenades);
taken += take;
unit->storage.grenades -= take;
} else {
take = std::min(needed, unit->storage.shells);
taken += take;
unit->storage.shells -= take;
}
}
if (!replicators) {
say("Es gibt keine Replikators.");
return;
}
if (!taken) {
if (grenades)
say("Ich konnte keine Granaten finden.");
else
say("Ich konnte keine Schrotpatronen finden.");
return;
}
if (grenades) {
storage.grenades += taken;
say("Ich habe " + german_plural(taken, "Granate", "Granaten") + " abgenommen." );
} else {
storage.shells += taken;
say("Ich habe " + german_plural(taken, "Schrotpatrone", "Schrotpatronen") + " abgenommen." );
}
}
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) {
rectf_t rect_body;
render::oriented_sprite_t *legs, *body;
float body_angle;
rect_body = render_bounds;
rect_body[0][1] += soldier_head_offset;
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 && last_attack + MSEC(100) > game->time)
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, rect_body, move.angle);
render->render(game->now * 10, body, rect_body, 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 (gibbed)
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);
}
}