/* 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 + 3.0 > game->now) { float anim, height; v2f_t text_pos; sf::Color color; anim = pow((game->now - say_time) / 3.0f, 5.0f); text_pos = render_bounds[0] + v2f_t(render_bounds.dim(0) / 2, -render_bounds.dim(1) * 0.1); height = size.dim_min() * 0.50f; text_pos[1] -= anim * height * 2.0f; color = sf::Color(255, 255, 255, 255.0f * (1.0f - anim)); render->render_text(text_pos, height, say_text, render::ALIGN_CENTER_BOTTOM, color); } if (selected == selection_cookie && type != UNIT_SOLDIER && type != UNIT_SCIENTIST) 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; on_damage(attacker); if (health <= 0 && !dead) die(attacker); switch (type) { case UNIT_SOLDIER: case UNIT_SCIENTIST: case UNIT_BUILDER: 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_SCIENTIST: case UNIT_BUILDER: case UNIT_SPIDER: game->interface.print(name + " died."); break; default: game->interface.print(name + " was destroyed."); break; } dead = true; death_time = game->now; cmodel.cflags = 0; move.path.clear(); sleep(); ignore_waking = true; game->selected_units.erase(this); 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); } } void state_t::wake_area(v2f_t x) { rectf_t bounds; bounds[0] = x - v2f_t(10, 10); bounds[1] = x + v2f_t(10, 10); for (world::entity_t *went : world.get_entities(bounds, -1)) { auto ent = dynamic_cast(went); // WTF? if (!ent) continue; // Wake everything around. if (!ent->ignore_waking) { ent->wake_time = now; if (!ent->awake) ent->wake(); } } } } // namespace game