/*
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_t::unit_t(game::state_t *game_, unit_t::type_t type_) : game::entity_t(game_, ET_UNIT)
{
game = game_;
type = type_;
}
void unit_t::render_to(render::state_t *render)
{
if (!dead && health < max_health)
{
rectf_t bar;
float frac;
sf::Color color;
bar[0][0] = (render_bounds[0][0] + render_bounds[1][0]) / 2;
bar[0][1] = render_bounds[0][1] - 0.05f;
bar[0][0] -= 0.3f;
bar[1][0] = bar[0][0] + 0.6f;
bar[1][1] = bar[0][1] + 0.05f;
render->render_rect(bar, sf::Color::Black);
frac = (float)health / max_health;
if (frac < 0.3f)
color = sf::Color::Red;
else if (frac < 0.75f)
color = sf::Color::Yellow;
else
color = sf::Color::Green;
bar[1][0] = lerp(bar[0][0], bar[1][0], frac);
render->render_rect(bar, color);
}
if (move.moving && debug_draw_paths)
render->debug_path(x, &move.path);
if (!dead && say_time + 5.0 > game->now) {
v2f_t text_pos;
float height;
text_pos = render_bounds[0] + v2f_t(render_bounds.dim(0) / 2, -render_bounds.dim(1) * 0.1);
height = size.dim_min() * 0.50f;
render->render_text(text_pos, height, say_text,
render::ALIGN_CENTER_BOTTOM,
sf::Color::White);
}
if (selected == selection_cookie && type != UNIT_SOLDIER)
render->render_hlrect(render_bounds, sf::Color::Green);
if (debug_AI) {
v2f_t text_pos;
float height;
std::stringstream ss;
if (dead)
ss << "D";
if (awake)
ss << "A";
else
ss << "S";
if (controllable)
ss << "C";
text_pos = render_bounds[0] + v2f_t(render_bounds.dim(0) / 2, -render_bounds.dim(1) * 0.1);
height = size.dim_min() * 0.40f;
text_pos[1] -= height;
render->render_text(text_pos, height, ss.str(),
render::ALIGN_CENTER_BOTTOM,
sf::Color::White);
}
}
void unit_t::say(std::string str, bool on_interface)
{
say_text = str;
say_time = game->now;
if (on_interface)
game->interface->print(name + ": " + str);
}
bool unit_t::keep_moving(double speed)
{
float time;
bool rv = true;
if (!move.moving)
return true;
time = game->dt * speed;
while (time > 0.0f) {
v2f_t delta, next, x_new;
world::cmodel_t test_cmodel;
if (!move.path.size()) {
move.moving = false;
break;
}
next = *move.path.begin();
delta = next - x;
if (delta.len() != 0.0f)
move.angle = delta.angle();
if (delta.len() >= time) {
x_new = x + delta * time / delta.len();
time -= delta.len();
} else {
x_new = next;
time -= delta.len();
move.path.pop_front();
}
test_cmodel.bounds = size + x_new;
test_cmodel.cflags = move.cflags;
if (world->test_rect(&test_cmodel, this)) {
rv = false;
break;
}
x = x_new;
cmodel.bounds = test_cmodel.bounds;
}
place(world, x);
move.blocked = !rv;
return rv;
}
bool unit_t::start_moving(v2f_t dst)
{
world::cmodel_t rep;
if (!world) {
printf("unit_t::start_moving: entity is not linked\n");
return false;
}
move.dst = dst;
move.path.clear();
move.last_step = x;
rep.cflags = move.cflags;
rep.bounds = cmodel.bounds;
if (!world->find_path(x, move.dst, &rep, this, &move.path)) {
move.moving = false;
return false;
}
move.moving = true;
return true;
}
void unit_t::stop_moving(void)
{
move.path.clear();
move.moving = false;
}
void unit_t::damage(int points, unit_t *attacker)
{
fx_blood_t *blood;
bool alien;
health -= points;
if (health <= 0)
die(attacker);
else {
wake();
on_damage(attacker);
}
switch (type) {
case UNIT_SOLDIER:
alien = false;
break;
case UNIT_SPIDER:
case UNIT_NEST:
alien = true;
break;
default:
return;
}
blood = new fx_blood_t(game, x, alien);
blood->place(&game->world);
}
void unit_t::die(unit_t *killer)
{
switch (type) {
case UNIT_SOLDIER:
case UNIT_SPIDER:
game->interface->print(name + " " + text::get(text::UNIT_DEATH) + ".");
break;
default:
game->interface->print(name + " " + text::get(text::UNIT_DESTRUCTION) + ".");
break;
}
dead = true;
death_time = game->now;
cmodel.cflags = 0;
move.path.clear();
sleep();
ignore_waking = true;
on_death();
}
unit_t *find_target(world::world_t *world, v2f_t x, float r,
bool friendly)
{
rectf_t rect;
float nearest_dist = INFINITY;
unit_t *nearest = NULL;
rect[0] = x - v2f_t(r, r);
rect[1] = x + v2f_t(r, r);
for (world::entity_t *ent : world->get_entities(rect, -1)) {
unit_t *unit;
float dist;
if (ent->type != ET_UNIT)
continue;
unit = (unit_t*)ent;
if (unit->friendly != friendly)
continue;
if (unit->dead)
continue;
dist = (unit->x - x).len();
if (dist < nearest_dist) {
nearest_dist = dist;
nearest = unit;
}
}
return nearest;
}
void unit_t::random_walk(void)
{
if (move.random_walk_time &&
game->time - move.random_walk_time < MSEC(1000))
return;
move.moving = true;
move.blocked = false;
move.dst = x + game->prng.unit_vec2() * 10;
move.path.clear();
move.path.push_back(move.dst);
move.random_walk_time = game->time;
}
void state_t::hivemind_alert(v2f_t x, float r, bool do_move, v2f_t move_to)
{
rectf_t search;
std::list ents;
search[0] = x - v2f_t(r, r);
search[1] = x + v2f_t(r, r);
ents = world.get_entities(search, CF_BODY_SMALL);
for (world::entity_t *ent : ents) {
unit_t *unit;
if (ent->type != ET_UNIT)
continue;
unit = dynamic_cast(ent);
if ((unit->x - x).len() > r)
continue;
if (unit->type != unit_t::UNIT_SPIDER)
continue;
if (unit->have_target)
continue;
unit->wake();
if (!do_move)
continue;
// Move towards the target if it's close, otherwise walk
// randomly to avoid fire.
if ((unit->x - move_to).len() > 25.0f)
continue;
unit->start_moving(move_to);
}
if (debug_AI) {
fx_debug_hivemind_t *debug;
debug = new fx_debug_hivemind_t(this, x, r, do_move, move_to);
debug->place(&world);
}
}
} // namespace game