/* 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); } }