/* 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" #include namespace game { bool load_assets(void) { assets::load(); return true; } size_t selection_cookie = 1; entity_t::entity_t(game::state_t *game_, int type_) : world::entity_t(type_) { game = game_; } entity_t::~entity_t(void) { sleep(); if (world) unlink(); } void entity_t::place(world::world_t *world_) { bool do_spawn = false; if (!world) do_spawn = true; link(world_); if (do_spawn) on_spawn(); } void entity_t::place(world::world_t *world, v2f_t x_) { x = x_; cmodel.bounds = size + x; render_bounds = render_size + x; place(world); } void entity_t::wake(void) { awake = true; wake_time = game->now; game->awake_entities.insert(this); if (!ignore_waking) on_wake(); } void entity_t::sleep(void) { awake = false; game->awake_entities.erase(this); } void state_t::start(void) { std::random_device rd; std::uniform_int_distribution dist(0, (uint32_t)1 << 31); world.seed_procgen(dist(rd)); world.generator = worldgen; world.generator_data = (void*)this; resume(); } void state_t::stop(void) { } bool state_t::select_unit(unit_t *unit, int type) { switch (type) { case SELECT_NEW: case SELECT_OR: if (unit->selected == selection_cookie) return false; else goto do_select; case SELECT_XOR: if (unit->selected == selection_cookie) goto do_deselect; else goto do_select; } do_select: unit->selected = selection_cookie; selected_units.insert(unit); return true; do_deselect: unit->selected = selection_cookie - 1; selected_units.erase(unit); return false; } void state_t::select(rectf_t rect, int type) { bool select_one; std::list ents; if (type == SELECT_NEW) { selection_cookie++; selected_units.clear(); } select_one = rect.area() < 0.2f; ents = world.get_render_entities(rect); if (select_one) ents.sort(render::visibility_order); for (world::entity_t *ent : ents) { unit_t *unit; if (ent->type != ET_UNIT) continue; unit = (unit_t*)ent; if (!unit->controllable) continue; if (select_unit(unit, type)) { switch (unit->type) { case unit_t::UNIT_SOLDIER: unit->say("Ich bin bereit.", false); break; case unit_t::UNIT_SCIENTIST: unit->say("Я готов.", false); break; case unit_t::UNIT_BUILDER: unit->say("Gotowy na rozkaz.", false); break; default: break; } } if (select_one) break; } } enum { COMMAND_FIRST_TELEPORTER, COMMAND_MOVE, COMMAND_FIRE, COMMAND_THROW_GRENADE, COMMAND_STOP, COMMAND_RESTOCK_SHELLS, COMMAND_RESTOCK_GRENADES, COMMAND_HIRE_SOLDIER, COMMAND_HIRE_SCIENTIST, COMMAND_HIRE_BUILDER, COMMAND_GATHER, COMMAND_REPAIR, COMMAND_BUILD_TELEPORTER, COMMAND_BUILD_REPLICATOR, COMMAND_REPLICATE_SHELLS, COMMAND_REPLICATE_GRENADES, }; bool state_t::populate_pie_menu(std::vector &items) { bool soldiers = false, teleporters = false, grenades = false, scientists = false, builders = false, replicators = false, restock_shells = false, restock_grenades = false; items.clear(); if (!first_teleporter_placed) { items.push_back((interface::pie_item_t){&assets::ui.icon_teleporter, "Place the teleporter", COMMAND_FIRST_TELEPORTER}); return true; } if (selected_units.size() == 0) return false; for (unit_t *unit : selected_units) { if (unit->dead || !unit->controllable || !unit->constructed) continue; switch (unit->type) { case unit_t::UNIT_SOLDIER: soldiers = true; if (unit->storage.grenades) grenades = true; if (unit->storage.shells < unit->storage.max_shells) restock_shells = true; if (unit->storage.grenades < unit->storage.max_grenades) restock_grenades = true; break; case unit_t::UNIT_SCIENTIST: scientists = true; break; case unit_t::UNIT_BUILDER: builders = true; break; case unit_t::UNIT_TELEPORTER: teleporters = true; break; case unit_t::UNIT_REPLICATOR: replicators = true; break; default:; } } if (soldiers || scientists || builders) { items.push_back((interface::pie_item_t){&assets::ui.icon_move, "Move", COMMAND_MOVE}); items.push_back((interface::pie_item_t){&assets::ui.icon_stop, "Stop", COMMAND_STOP}); } if (soldiers) { items.push_back((interface::pie_item_t){&assets::ui.icon_fire, "Fire", COMMAND_FIRE}); if (grenades) items.push_back((interface::pie_item_t){&assets::ui.icon_grenades, "Throw a grenade", COMMAND_THROW_GRENADE}); if (restock_shells) items.push_back((interface::pie_item_t){&assets::ui.icon_shells, "Restock shells", COMMAND_RESTOCK_SHELLS}); if (restock_grenades) items.push_back((interface::pie_item_t){&assets::ui.icon_grenades, "Restock grenades", COMMAND_RESTOCK_GRENADES}); } if (scientists) items.push_back((interface::pie_item_t){&assets::ui.crystals, "Gather", COMMAND_GATHER}); if (builders) { items.push_back((interface::pie_item_t){&assets::ui.icon_repair, "Repair", COMMAND_REPAIR}); items.push_back((interface::pie_item_t){&assets::ui.icon_teleporter, "Build a teleporter", COMMAND_BUILD_TELEPORTER}); items.push_back((interface::pie_item_t){&assets::ui.icon_replicator, "Build a replicator", COMMAND_BUILD_REPLICATOR}); } if (teleporters) { items.push_back((interface::pie_item_t){&assets::ui.icon_soldier, "Hire a soldier", COMMAND_HIRE_SOLDIER}); items.push_back((interface::pie_item_t){&assets::ui.icon_scientist, "Hire a scientist", COMMAND_HIRE_SCIENTIST}); items.push_back((interface::pie_item_t){&assets::ui.icon_builder, "Hire a builder", COMMAND_HIRE_BUILDER}); } if (replicators) { items.push_back((interface::pie_item_t){&assets::ui.icon_shells, "Replicate shells", COMMAND_REPLICATE_SHELLS}); items.push_back((interface::pie_item_t){&assets::ui.icon_grenades, "Replicate grenades", COMMAND_REPLICATE_GRENADES}); } return true; } void state_t::command_first_teleporter(v2f_t where) { unit_teleporter_t *teleporter; world::cmodel_t test; teleporter = new unit_teleporter_t(this); teleporter->place(&world, where); test = teleporter->cmodel; test.cflags = CF_SOLID|CF_BODY|CF_BODY_SMALL|CF_DECOS|CF_WATER|CF_SURFACE2; if (world.test_rect(&test, teleporter)) { interface.print("There is no room for a teleporter here."); delete teleporter; return; } teleporter->constructed = true; teleporter->health = teleporter->max_health; first_teleporter_placed = true; } static void command_soldier(unit_soldier_t *soldier, v2f_t x, int number) { switch (number) { case COMMAND_MOVE: if (!soldier->start_moving(x)) soldier->say("Es gibt keinen Weg.", false); else { soldier->move_marker = std::make_unique(soldier->game, soldier->move.path.back()); soldier->say("Ich bin unterwegs.", false); } break; case COMMAND_STOP: soldier->stop_moving(); soldier->manual_firing = false; soldier->say("Halt.", false); soldier->aim_marker.reset(); break; case COMMAND_FIRE: soldier->manual_firing = true; soldier->manual_firing_target = x; soldier->say("Feuer!", false); break; case COMMAND_THROW_GRENADE: soldier->command_throw_grenade(x); break; case COMMAND_RESTOCK_SHELLS: soldier->command_restock(false); break; case COMMAND_RESTOCK_GRENADES: soldier->command_restock(true); break; } } static void command_scientist(unit_scientist_t *scientist, v2f_t x, int number) { switch (number) { case COMMAND_MOVE: if (!scientist->start_moving(x)) scientist->say("Нет пути.", false); else { scientist->move_marker = std::make_unique(scientist->game, scientist->move.path.back()); scientist->say("Я иду.", false); } break; case COMMAND_STOP: scientist->stop_moving(); scientist->say("Стоп.", false); scientist->gather_marker.reset(); break; case COMMAND_GATHER: scientist->gathering = true; scientist->gathering_at = x; scientist->gather_marker = std::make_unique(scientist->game, x); scientist->say("Я собираю.", false); break; } } static void command_builder(unit_builder_t *builder, v2f_t x, int number) { switch (number) { case COMMAND_MOVE: if (!builder->start_moving(x)) builder->say("Nie ma tam drogi.", false); else { builder->move_marker = std::make_unique(builder->game, builder->move.path.back()); builder->say("W drodze.", false); } break; case COMMAND_STOP: builder->command_stop(); break; case COMMAND_REPAIR: builder->command_repair(x); break; case COMMAND_BUILD_TELEPORTER: builder->command_build(x, unit_t::UNIT_TELEPORTER); break; case COMMAND_BUILD_REPLICATOR: builder->command_build(x, unit_t::UNIT_REPLICATOR); break; } } static void command_teleporter(unit_teleporter_t *teleporter, v2f_t x, int number) { switch (number) { case COMMAND_HIRE_SOLDIER: teleporter->activate(unit_t::UNIT_SOLDIER); break; case COMMAND_HIRE_SCIENTIST: teleporter->activate(unit_t::UNIT_SCIENTIST); break; case COMMAND_HIRE_BUILDER: teleporter->activate(unit_t::UNIT_BUILDER); break; } } static void command_replicator(unit_replicator_t *replicator, v2f_t x, int number) { switch (number) { case COMMAND_REPLICATE_SHELLS: replicator->activate(false); break; case COMMAND_REPLICATE_GRENADES: replicator->activate(true); break; } } void state_t::command(v2f_t x, int number) { bool unlink; if (!first_teleporter_placed || number == COMMAND_FIRST_TELEPORTER) { command_first_teleporter(x); return; } if (!selected_units.size()) return; unlink = (number == COMMAND_MOVE); if (unlink) for (unit_t *unit : selected_units) unit->cmodel.ignore = true; auto copy = selected_units; for (unit_t *unit : copy) { if (unit->dead || !unit->controllable) continue; switch (unit->type) { case unit_t::UNIT_SOLDIER: command_soldier(dynamic_cast(unit), x, number); break; case unit_t::UNIT_SCIENTIST: command_scientist(dynamic_cast(unit), x, number); break; case unit_t::UNIT_BUILDER: command_builder(dynamic_cast(unit), x, number); break; case unit_t::UNIT_TELEPORTER: command_teleporter(dynamic_cast(unit), x, number); break; case unit_t::UNIT_REPLICATOR: command_replicator(dynamic_cast(unit), x, number); default:; } } if (unlink) for (unit_t *unit : selected_units) unit->cmodel.ignore = false; } void state_t::pause(void) { paused = true; } void state_t::resume(void) { t0 = nclock(); frames_since_t0 = 0; paused = false; } void state_t::check_game_over(void) { if (game_over_displayed) return; if (!first_teleporter_placed) return; // Can collect crystals and got a teleporter. if (num_scientists && num_teleporters) return; // Got a teleporter and enough crystals for a scientist. if (num_teleporters && crystals >= 95) return; // Got a scientist, a builder and enough crystals for a teleporter. if (num_scientists && num_builders && crystals >= 265) return; // Got a builder and enough crystals for a teleporter and a scientist. if (num_builders && crystals >= 265 + 95) return; interface.print("FURTHER PROGRESS IS NOT POSSIBLE."); interface.print("At this point you might want to restart the game."); game_over_displayed = true; } #define TIME_DELTA ((ntime_t)1000000000 / 100) #define TIME_LIMIT ((ntime_t)1000000000 / 20) void state_t::tick(ntime_t time_) { size_t target; if (paused) return; target = (time_ - t0) / TIME_DELTA; while (frames_since_t0 < target) { ntime_t tmp; // FIXME: Is this non-deterministic enough? prng.seed(prng.next() ^ time); // setting up old variables (refactor them out eventually) now = time * 1.0e-9; dt = TIME_DELTA * 1.0e-9; for (entity_t *ent : deletion_list) delete ent; deletion_list.clear(); // on_think can insert/erase elements of awake_entities so iterate // over a copy of it. auto copy = awake_entities; for (entity_t *ent : copy) if (awake_entities.find(ent) != awake_entities.end()) ent->on_think(); check_game_over(); frames++; frames_since_t0++; time += TIME_DELTA; fc_game.tick(); tmp = nclock(); if (tmp - time_ > TIME_LIMIT) { t0 = nclock(); frames_since_t0 = 0; frames_behind++; interface.print("(Lag: " + std::to_string((tmp - time_) / MSEC(1)) + " ms)"); break; } } } #define XRES 9 #define YRES 9 void state_t::compute_ambience(void) { const size_t samples = XRES * YRES; rectf_t area = interface.world_view; v2f_t origins[AMBIENT_COUNT]; size_t hits[AMBIENT_COUNT]; for (size_t i = 0; i < AMBIENT_COUNT; i++) { origins[i] = v2f_t(0, 0); hits[i] = 0; } // resolution chosen arbitrarily for (size_t y = 0; y < YRES; y++) for (size_t x = 0; x < XRES; x++) { v2f_t point; world::tile_t *tile; int type; point[0] = lerp(area[0][0], area[1][0], y / (YRES - 1.0f)); point[1] = lerp(area[0][1], area[1][1], x / (XRES - 1.0f)); tile = world.get_tile(world::tile_index_t(point), world::SECTOR_FULL); switch (tile->type) { case TILE_DIRT: type = AMBIENT_WIND; break; case TILE_GRAVEL: case TILE_STONE: type = AMBIENT_CHASM; break; case TILE_WATER: type = AMBIENT_WATER; break; case TILE_DIRT_RED: case TILE_STONE_RED: type = AMBIENT_NEXUS; break; default: continue; } origins[type] += point; hits[type]++; } for (size_t i = 0; i < AMBIENT_COUNT; i++) { assets::ambients[i].origin = origins[i] / hits[i]; assets::ambients[i].weight = (float)hits[i] / samples; } } void state_t::explosion(v2f_t x) { fx_explosion_t *explosion; const float r = 5.0f; rectf_t rect; rect[0] = x - v2f_t(r, r); rect[1] = x + v2f_t(r, r); for (world::entity_t *ent : world.get_entities(rect, -1)) { v2f_t center; float dist, damage; world::trace_t trace; entity_t *g_ent; center = ent->cmodel.bounds.center(); dist = (center - x).len(); if (dist > r) continue; trace = world.ray_v_all(x, center, CF_SOLID, ent); if (trace.frac < 1.0f) continue; damage = clamp(40.0f / dist, 0.0f, 1000.0f); g_ent = dynamic_cast(ent); g_ent->damage((int)damage, nullptr); } explosion = new fx_explosion_t(this, x); explosion->place(&world); hivemind_alert(x, 20.0f, false, v2f_t(0, 0)); } } //namespace game