1 /*
2 * Copyright (C) 2002-2020 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20 #include "logic/game.h"
21
22 #include <memory>
23 #include <string>
24
25 #ifndef _WIN32
26 #include <unistd.h> // for usleep
27 #else
28 #include <SDL_events.h> // for a dirty hack.
29 #include <windows.h>
30 #endif
31
32 #include "base/i18n.h"
33 #include "base/log.h"
34 #include "base/macros.h"
35 #include "base/time_string.h"
36 #include "base/warning.h"
37 #include "build_info.h"
38 #include "economy/economy.h"
39 #include "economy/portdock.h"
40 #include "game_io/game_loader.h"
41 #include "game_io/game_preload_packet.h"
42 #include "io/fileread.h"
43 #include "io/filesystem/filesystem_exceptions.h"
44 #include "io/filesystem/layered_filesystem.h"
45 #include "io/filewrite.h"
46 #include "logic/cmd_calculate_statistics.h"
47 #include "logic/cmd_luacoroutine.h"
48 #include "logic/cmd_luascript.h"
49 #include "logic/filesystem_constants.h"
50 #include "logic/game_settings.h"
51 #include "logic/map_objects/tribes/carrier.h"
52 #include "logic/map_objects/tribes/market.h"
53 #include "logic/map_objects/tribes/militarysite.h"
54 #include "logic/map_objects/tribes/ship.h"
55 #include "logic/map_objects/tribes/soldier.h"
56 #include "logic/map_objects/tribes/trainingsite.h"
57 #include "logic/map_objects/tribes/tribe_descr.h"
58 #include "logic/map_objects/tribes/warehouse.h"
59 #include "logic/player.h"
60 #include "logic/playercommand.h"
61 #include "logic/replay.h"
62 #include "logic/single_player_game_controller.h"
63 #include "map_io/widelands_map_loader.h"
64 #include "scripting/lua_table.h"
65 #include "sound/sound_handler.h"
66 #include "ui_basic/progresswindow.h"
67 #include "wlapplication_options.h"
68 #include "wui/interactive_player.h"
69
70 namespace Widelands {
71
72 /// Define this to get lots of debugging output concerned with syncs
73 // #define SYNC_DEBUG
74
~SyncWrapper()75 Game::SyncWrapper::~SyncWrapper() {
76 if (dump_ != nullptr) {
77 if (!syncstreamsave_)
78 try {
79 g_fs->fs_unlink(dumpfname_);
80 } catch (const FileError& e) {
81 // not really a problem if deletion fails, but we'll log it
82 log("Deleting synchstream file %s failed: %s\n", dumpfname_.c_str(), e.what());
83 }
84 }
85 }
86
start_dump(const std::string & fname)87 void Game::SyncWrapper::start_dump(const std::string& fname) {
88 dumpfname_ = fname + kSyncstreamExtension;
89 dump_.reset(g_fs->open_stream_write(dumpfname_));
90 current_excerpt_id_ = 0;
91 excerpts_buffer_[current_excerpt_id_].clear();
92 }
93
data(void const * const sync_data,size_t const size)94 void Game::SyncWrapper::data(void const* const sync_data, size_t const size) {
95 #ifdef SYNC_DEBUG
96 uint32_t time = game_.get_gametime();
97 log("[sync:%08u t=%6u]", counter_, time);
98 for (size_t i = 0; i < size; ++i)
99 log(" %02x", (static_cast<uint8_t const*>(sync_data))[i]);
100 log("\n");
101 #endif
102
103 if (dump_ != nullptr && static_cast<int32_t>(counter_ - next_diskspacecheck_) >= 0) {
104 next_diskspacecheck_ = counter_ + 16 * 1024 * 1024;
105
106 if (g_fs->disk_space() < kMinimumDiskSpace) {
107 log("Stop writing to syncstream file: disk is getting full.\n");
108 dump_.reset();
109 }
110 }
111
112 if (dump_ != nullptr) {
113 try {
114 dump_->data(sync_data, size);
115 } catch (const WException&) {
116 log("Writing to syncstream file %s failed. Stop synctream dump.\n", dumpfname_.c_str());
117 dump_.reset();
118 }
119 assert(current_excerpt_id_ < kExcerptSize);
120 excerpts_buffer_[current_excerpt_id_].append(static_cast<const char*>(sync_data), size);
121 }
122
123 target_.data(sync_data, size);
124 counter_ += size;
125 }
126
Game()127 Game::Game()
128 : EditorGameBase(new LuaGameInterface(this)),
129 forester_cache_(),
130 syncwrapper_(*this, synchash_),
131 ctrl_(nullptr),
132 writereplay_(true),
133 writesyncstream_(false),
134 ai_training_mode_(false),
135 auto_speed_(false),
136 state_(gs_notrunning),
137 cmdqueue_(*this),
138 scenario_difficulty_(kScenarioDifficultyNotSet),
139 /** TRANSLATORS: Win condition for this game has not been set. */
140 win_condition_displayname_(_("Not set")),
141 replay_(false) {
142 Economy::initialize_serial();
143 }
144
~Game()145 Game::~Game() {
146 }
147
sync_reset()148 void Game::sync_reset() {
149 syncwrapper_.counter_ = 0;
150
151 synchash_.reset();
152 log("[sync] Reset\n");
153 }
154
155 /**
156 * \return a pointer to the \ref InteractivePlayer if any.
157 * \note This function may return 0 (in particular, it will return 0 during
158 * playback or if player is spectator)
159 */
get_ipl()160 InteractivePlayer* Game::get_ipl() {
161 return dynamic_cast<InteractivePlayer*>(get_ibase());
162 }
163
set_game_controller(GameController * const ctrl)164 void Game::set_game_controller(GameController* const ctrl) {
165 ctrl_ = ctrl;
166 }
167
set_ai_training_mode(const bool mode)168 void Game::set_ai_training_mode(const bool mode) {
169 ai_training_mode_ = mode;
170 }
171
set_auto_speed(const bool mode)172 void Game::set_auto_speed(const bool mode) {
173 auto_speed_ = mode;
174 }
175
game_controller()176 GameController* Game::game_controller() {
177 return ctrl_;
178 }
179
set_write_replay(bool const wr)180 void Game::set_write_replay(bool const wr) {
181 // we want to allow for the possibility to stop writing our replay
182 // this is to ensure we do not crash because of diskspace
183 // still this is only possibe to go from true->false
184 // still probally should not do this with an assert but with better checks
185 assert(state_ == gs_notrunning || !wr);
186
187 writereplay_ = wr;
188 }
189
set_write_syncstream(bool const wr)190 void Game::set_write_syncstream(bool const wr) {
191 assert(state_ == gs_notrunning);
192
193 writesyncstream_ = wr;
194 }
195
196 /**
197 * Set whether the syncstream dump should be copied to a permanent location
198 * at the end of the game.
199 */
save_syncstream(bool const save)200 void Game::save_syncstream(bool const save) {
201 syncwrapper_.syncstreamsave_ = save;
202 }
203
run_splayer_scenario_direct(const std::string & mapname,const std::string & script_to_run)204 bool Game::run_splayer_scenario_direct(const std::string& mapname,
205 const std::string& script_to_run) {
206 // Replays can't handle scenarios
207 set_write_replay(false);
208
209 std::unique_ptr<MapLoader> maploader(mutable_map()->get_correct_loader(mapname));
210 if (!maploader) {
211 throw wexception("could not load \"%s\"", mapname.c_str());
212 }
213
214 create_loader_ui({"general_game"}, false);
215
216 step_loader_ui(_("Preloading map…"));
217 maploader->preload_map(true);
218 change_loader_ui_background(map().get_background());
219
220 step_loader_ui(_("Loading world…"));
221 world();
222 step_loader_ui(_("Loading tribes…"));
223 tribes();
224
225 // If the scenario has custrom tribe entites, load them.
226 const std::string custom_tribe_script = mapname + "/scripting/tribes/init.lua";
227 if (g_fs->file_exists(custom_tribe_script)) {
228 lua().run_script(custom_tribe_script);
229 }
230
231 // We have to create the players here.
232 step_loader_ui(_("Creating players…"));
233 PlayerNumber const nr_players = map().get_nrplayers();
234 iterate_player_numbers(p, nr_players) {
235 // If tribe name is empty, pick a random tribe
236 std::string tribe = map().get_scenario_player_tribe(p);
237 if (tribe.empty()) {
238 log("Setting random tribe for Player %d\n", static_cast<unsigned int>(p));
239 const DescriptionIndex random = std::rand() % tribes().nrtribes();
240 tribe = tribes().get_tribe_descr(random)->name();
241 }
242 add_player(p, 0, tribe, map().get_scenario_player_name(p));
243 get_player(p)->set_ai(map().get_scenario_player_ai(p));
244 }
245 win_condition_displayname_ = "Scenario";
246
247 set_ibase(new InteractivePlayer(*this, get_config_section(), 1, false));
248
249 step_loader_ui(_("Loading map…"));
250 maploader->load_map_complete(*this, Widelands::MapLoader::LoadType::kScenario);
251 maploader.reset();
252
253 set_game_controller(new SinglePlayerGameController(*this, true, 1));
254 try {
255 bool const result = run(NewSPScenario, script_to_run, false, "single_player");
256 delete ctrl_;
257 ctrl_ = nullptr;
258 return result;
259 } catch (...) {
260 delete ctrl_;
261 ctrl_ = nullptr;
262 throw;
263 }
264 }
265
266 /**
267 * Initialize the game based on the given settings.
268 *
269 */
init_newgame(const GameSettings & settings)270 void Game::init_newgame(const GameSettings& settings) {
271 assert(has_loader_ui());
272
273 step_loader_ui(_("Preloading map…"));
274
275 std::unique_ptr<MapLoader> maploader(mutable_map()->get_correct_loader(settings.mapfilename));
276 assert(maploader != nullptr);
277 maploader->preload_map(settings.scenario);
278 change_loader_ui_background(map().get_background());
279
280 step_loader_ui(_("Loading world…"));
281 world();
282
283 step_loader_ui(_("Loading tribes…"));
284 tribes();
285
286 step_loader_ui(_("Creating players…"));
287
288 std::vector<PlayerSettings> shared;
289 std::vector<uint8_t> shared_num;
290 for (uint32_t i = 0; i < settings.players.size(); ++i) {
291 const PlayerSettings& playersettings = settings.players[i];
292
293 if (playersettings.state == PlayerSettings::State::kClosed ||
294 playersettings.state == PlayerSettings::State::kOpen)
295 continue;
296 else if (playersettings.state == PlayerSettings::State::kShared) {
297 shared.push_back(playersettings);
298 shared_num.push_back(i + 1);
299 continue;
300 }
301
302 add_player(i + 1, playersettings.initialization_index, playersettings.tribe,
303 playersettings.name, playersettings.team);
304 get_player(i + 1)->set_ai(playersettings.ai);
305 }
306
307 // Add shared in starting positions
308 for (uint8_t n = 0; n < shared.size(); ++n) {
309 // This player's starting position is used in another (shared) kingdom
310 get_player(shared.at(n).shared_in)
311 ->add_further_starting_position(shared_num.at(n), shared.at(n).initialization_index);
312 }
313
314 step_loader_ui(_("Loading map…"));
315 maploader->load_map_complete(*this, settings.scenario ?
316 Widelands::MapLoader::LoadType::kScenario :
317 Widelands::MapLoader::LoadType::kGame);
318
319 // Check for win_conditions
320 if (!settings.scenario) {
321 step_loader_ui(_("Initializing game…"));
322 if (settings.peaceful) {
323 for (uint32_t i = 1; i < settings.players.size(); ++i) {
324 if (Player* p1 = get_player(i)) {
325 for (uint32_t j = i + 1; j <= settings.players.size(); ++j) {
326 if (Player* p2 = get_player(j)) {
327 p1->set_attack_forbidden(j, true);
328 p2->set_attack_forbidden(i, true);
329 }
330 }
331 }
332 }
333 }
334
335 std::unique_ptr<LuaTable> table(lua().run_script(settings.win_condition_script));
336 table->do_not_warn_about_unaccessed_keys();
337 win_condition_displayname_ = table->get_string("name");
338 if (table->has_key<std::string>("init")) {
339 std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("init");
340 cr->resume();
341 }
342 std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("func");
343 enqueue_command(new CmdLuaCoroutine(get_gametime() + 100, std::move(cr)));
344 } else {
345 win_condition_displayname_ = "Scenario";
346 }
347 }
348
349 /**
350 * Initialize the savegame based on the given settings.
351 * At return the game is at the same state like a map loaded with Game::init()
352 * Only difference is, that players are already initialized.
353 * run<Returncode>() takes care about this difference.
354 */
init_savegame(const GameSettings & settings)355 void Game::init_savegame(const GameSettings& settings) {
356 assert(has_loader_ui());
357
358 step_loader_ui(_("Preloading map…"));
359
360 try {
361 GameLoader gl(settings.mapfilename, *this);
362 Widelands::GamePreloadPacket gpdp;
363 gl.preload_game(gpdp);
364 change_loader_ui_background(gpdp.get_background());
365
366 win_condition_displayname_ = gpdp.get_win_condition();
367 if (win_condition_displayname_ == "Scenario") {
368 // Replays can't handle scenarios
369 set_write_replay(false);
370 }
371
372 step_loader_ui(_("Loading…"));
373 gl.load_game(settings.multiplayer);
374 // Players might have selected a different AI type
375 for (uint8_t i = 0; i < settings.players.size(); ++i) {
376 const PlayerSettings& playersettings = settings.players[i];
377 if (playersettings.state == PlayerSettings::State::kComputer) {
378 get_player(i + 1)->set_ai(playersettings.ai);
379 }
380 }
381 } catch (...) {
382 throw;
383 }
384 }
385
run_load_game(const std::string & filename,const std::string & script_to_run)386 bool Game::run_load_game(const std::string& filename, const std::string& script_to_run) {
387 create_loader_ui({"general_game", "singleplayer"}, false);
388 int8_t player_nr;
389
390 step_loader_ui(_("Preloading map…"));
391
392 {
393 GameLoader gl(filename, *this);
394
395 Widelands::GamePreloadPacket gpdp;
396 gl.preload_game(gpdp);
397 change_loader_ui_background(gpdp.get_background());
398
399 win_condition_displayname_ = gpdp.get_win_condition();
400 if (win_condition_displayname_ == "Scenario") {
401 // Replays can't handle scenarios
402 set_write_replay(false);
403 }
404
405 player_nr = gpdp.get_player_nr();
406 set_ibase(new InteractivePlayer(*this, get_config_section(), player_nr, false));
407
408 step_loader_ui(_("Loading…"));
409 gl.load_game();
410 }
411
412 // Store the filename for further saves
413 save_handler().set_current_filename(filename);
414
415 set_game_controller(new SinglePlayerGameController(*this, true, player_nr));
416 try {
417 bool const result = run(Loaded, script_to_run, false, "single_player");
418 delete ctrl_;
419 ctrl_ = nullptr;
420 return result;
421 } catch (...) {
422 delete ctrl_;
423 ctrl_ = nullptr;
424 throw;
425 }
426 }
427
428 /**
429 * Called for every game after loading (from a savegame or just from a map
430 * during single/multiplayer/scenario).
431 *
432 * Ensure that players and player controllers are setup properly (in particular
433 * AI and the \ref InteractivePlayer if any).
434 */
postload()435 void Game::postload() {
436 EditorGameBase::postload();
437 get_ibase()->postload();
438 }
439
440 /**
441 * This runs a game, including game creation phase.
442 *
443 * The setup and loading of a game happens (or rather: will happen) in three
444 * stages.
445 * 1. First of all, the host (or single player) configures the game. During
446 * this time, only short descriptions of the game data (such as map
447 * headers)are loaded to minimize loading times.
448 * 2a. Once the game is about to start and the configuration screen is finished,
449 * all logic data (map, tribe information, building information) is loaded
450 * during postload.
451 * 2b. If a game is created, initial player positions are set. This step is
452 * skipped when a game is loaded.
453 * 3. After this has happened, the game graphics are loaded.
454 *
455 * \return true if a game actually took place, false otherwise
456 */
run(StartGameType const start_game_type,const std::string & script_to_run,bool replay,const std::string & prefix_for_replays)457 bool Game::run(StartGameType const start_game_type,
458 const std::string& script_to_run,
459 bool replay,
460 const std::string& prefix_for_replays) {
461 assert(has_loader_ui());
462
463 replay_ = replay;
464 postload();
465
466 if (start_game_type != Loaded) {
467 PlayerNumber const nr_players = map().get_nrplayers();
468 if (start_game_type == NewNonScenario) {
469 step_loader_ui(_("Creating player infrastructure…"));
470 iterate_players_existing(p, nr_players, *this, plr) {
471 plr->create_default_infrastructure();
472 }
473 } else {
474 // Is a scenario!
475 // Replays can't handle scenarios
476 set_write_replay(false);
477 iterate_players_existing_novar(p, nr_players, *this) {
478 if (!map().get_starting_pos(p))
479 throw WLWarning(_("Missing starting position"),
480 _("Widelands could not start the game, because player %u has "
481 "no starting position.\n"
482 "You can manually add a starting position with the Widelands "
483 "Editor to fix this problem."),
484 static_cast<unsigned int>(p));
485 }
486 }
487
488 if (get_ipl()) {
489 // Scroll map to starting position for new games.
490 // Loaded games are handled in GameInteractivePlayerPacket for single player, and in
491 // InteractiveGameBase::start() for multiplayer.
492 get_ipl()->map_view()->scroll_to_field(
493 map().get_starting_pos(get_ipl()->player_number()), MapView::Transition::Jump);
494 }
495
496 // Prepare the map, set default textures
497 mutable_map()->recalc_default_resources(world());
498
499 // Finally, set the scenario names and tribes to represent
500 // the correct names of the players
501 iterate_player_numbers(p, nr_players) {
502 const Player* const plr = get_player(p);
503 const std::string no_name;
504 const std::string& player_tribe = plr ? plr->tribe().name() : no_name;
505 const std::string& player_name = plr ? plr->get_name() : no_name;
506 const std::string& player_ai = plr ? plr->get_ai() : no_name;
507 mutable_map()->set_scenario_player_tribe(p, player_tribe);
508 mutable_map()->set_scenario_player_name(p, player_name);
509 mutable_map()->set_scenario_player_ai(p, player_ai);
510 mutable_map()->set_scenario_player_closeable(p, false); // player is already initialized.
511 }
512
513 // Run the init script, if the map provides one.
514 if (start_game_type == NewSPScenario)
515 enqueue_command(new CmdLuaScript(get_gametime(), "map:scripting/init.lua"));
516 else if (start_game_type == NewMPScenario)
517 enqueue_command(new CmdLuaScript(get_gametime(), "map:scripting/multiplayer_init.lua"));
518
519 // Queue first statistics calculation
520 enqueue_command(new CmdCalculateStatistics(get_gametime() + 1));
521 }
522
523 if (!script_to_run.empty() && (start_game_type == NewSPScenario || start_game_type == Loaded)) {
524 enqueue_command(new CmdLuaScript(get_gametime() + 1, script_to_run));
525 }
526
527 if (writereplay_ || writesyncstream_) {
528 // Derive a replay filename from the current time
529 const std::string fname = kReplayDir + g_fs->file_separator() + std::string(timestring()) +
530 std::string("_") + prefix_for_replays + kReplayExtension;
531 if (writereplay_) {
532 log("Starting replay writer\n");
533
534 assert(!replaywriter_);
535 replaywriter_.reset(new ReplayWriter(*this, fname));
536
537 log("Replay writer has started\n");
538 }
539
540 if (writesyncstream_)
541 syncwrapper_.start_dump(fname);
542 }
543
544 sync_reset();
545
546 load_graphics();
547
548 #ifdef _WIN32
549 // Clear the event queue before starting game because we don't want
550 // to handle events at game start that happened during loading procedure.
551 SDL_Event event;
552 while (SDL_PollEvent(&event))
553 ;
554 #endif
555
556 g_sh->change_music("ingame", 1000);
557
558 state_ = gs_running;
559
560 remove_loader_ui();
561
562 get_ibase()->run<UI::Panel::Returncodes>();
563
564 state_ = gs_ending;
565
566 g_sh->change_music("menu", 1000);
567
568 cleanup_objects();
569 set_ibase(nullptr);
570
571 state_ = gs_notrunning;
572
573 return true;
574 }
575
576 /**
577 * think() is called by the UI objects initiated during Game::run()
578 * during their modal loop.
579 * Depending on the current state we advance game logic and stuff,
580 * running the cmd queue etc.
581 */
think()582 void Game::think() {
583 assert(ctrl_);
584
585 ctrl_->think();
586
587 if (state_ == gs_running) {
588 // TODO(sirver): This is not good. Here, it depends on the speed of the
589 // computer and the fps if and when the game is saved - this is very bad
590 // for scenarios and even worse for the regression suite (which relies on
591 // the timings of savings.
592 cmdqueue().run_queue(ctrl_->get_frametime(), get_gametime_pointer());
593
594 // check if autosave is needed
595 savehandler_.think(*this);
596 }
597 }
598
599 /**
600 * Cleanup for load
601 * \deprecated
602 */
603 // TODO(unknown): Get rid of this. Prefer to delete and recreate Game-style objects
604 // Note that this needs fixes in the editor.
cleanup_for_load()605 void Game::cleanup_for_load() {
606 state_ = gs_notrunning;
607
608 EditorGameBase::cleanup_for_load();
609
610 cmdqueue().flush();
611
612 // Statistics
613 general_stats_.clear();
614 }
615
616 /**
617 * Game logic code may write to the synchronization
618 * token stream. All written data will be hashed and can be used to
619 * check for network or replay desyncs.
620 *
621 * \return the synchronization token stream
622 *
623 * \note This is returned as a \ref StreamWrite object to prevent
624 * the caller from messing with the checksumming process.
625 */
syncstream()626 StreamWrite& Game::syncstream() {
627 return syncwrapper_;
628 }
629
630 /**
631 * Switches to the next part of the syncstream excerpt.
632 */
report_sync_request()633 void Game::report_sync_request() {
634 syncwrapper_.current_excerpt_id_ =
635 (syncwrapper_.current_excerpt_id_ + 1) % SyncWrapper::kExcerptSize;
636 syncwrapper_.excerpts_buffer_[syncwrapper_.current_excerpt_id_].clear();
637 }
638
639 /**
640 * Triggers writing of syncstream excerpt and adds the playernumber of the desynced player
641 * to the stream.
642 * Playernumber should be negative when called by network clients
643 */
report_desync(int32_t playernumber)644 void Game::report_desync(int32_t playernumber) {
645 if (syncwrapper_.dumpfname_.empty()) {
646 log("Error: A desync occurred but no filename for the syncstream has been set.");
647 return;
648 }
649 // Replace .wss extension of syncstream file with .wse extension for syncstream extract
650 std::string filename = syncwrapper_.dumpfname_;
651 assert(syncwrapper_.dumpfname_.length() > kSyncstreamExtension.length());
652 filename.replace(filename.length() - kSyncstreamExtension.length(),
653 kSyncstreamExtension.length(), kSyncstreamExcerptExtension);
654 std::unique_ptr<StreamWrite> file(g_fs->open_stream_write(filename));
655 assert(file != nullptr);
656 // Write revision, branch and build type of this build to the file
657 file->unsigned_32(build_id().length());
658 file->text(build_id());
659 file->unsigned_32(build_type().length());
660 file->text(build_type());
661 file->signed_32(playernumber);
662 // Write our buffers to the file. Start with the oldest one
663 const size_t i2 = (syncwrapper_.current_excerpt_id_ + 1) % SyncWrapper::kExcerptSize;
664 size_t i = i2;
665 do {
666 file->text(syncwrapper_.excerpts_buffer_[i]);
667 syncwrapper_.excerpts_buffer_[i].clear();
668 i = (i + 1) % SyncWrapper::kExcerptSize;
669 } while (i != i2);
670 file->unsigned_8(SyncEntry::kDesync);
671 file->signed_32(playernumber);
672 // Restart buffers
673 syncwrapper_.current_excerpt_id_ = 0;
674 }
675
676 /**
677 * Calculate the current synchronization checksum and copy
678 * it into the given array, without affecting the subsequent
679 * checksumming process.
680 *
681 * \return the checksum
682 */
get_sync_hash() const683 Md5Checksum Game::get_sync_hash() const {
684 MD5Checksum<StreamWrite> copy(synchash_);
685
686 copy.finish_checksum();
687 return copy.get_checksum();
688 }
689
690 /**
691 * Return a random value that can be used in parallel game logic
692 * simulation.
693 *
694 * \note Do NOT use for random events in the UI or other display code.
695 */
logic_rand()696 uint32_t Game::logic_rand() {
697 uint32_t const result = rng().rand();
698 syncstream().unsigned_8(SyncEntry::kRandom);
699 syncstream().unsigned_32(result);
700 return result;
701 }
702
703 /**
704 * All player-issued commands must enter the queue through this function.
705 * It takes the appropriate action, i.e. either add to the cmd_queue or send
706 * across the network.
707 */
send_player_command(PlayerCommand * pc)708 void Game::send_player_command(PlayerCommand* pc) {
709 ctrl_->send_player_command(pc);
710 }
711
712 /**
713 * Actually enqueue a command.
714 *
715 * \note In a network game, player commands are only allowed to enter the
716 * command queue after being accepted by the networking logic via
717 * \ref send_player_command, so you must never enqueue a player command
718 * directly.
719 */
enqueue_command(Command * const cmd)720 void Game::enqueue_command(Command* const cmd) {
721 if (writereplay_ && replaywriter_) {
722 if (upcast(PlayerCommand, plcmd, cmd)) {
723 replaywriter_->send_player_command(plcmd);
724 }
725 }
726 cmdqueue().enqueue(cmd);
727 }
728
729 // we might want to make these inlines:
send_player_bulldoze(PlayerImmovable & pi,bool const recurse)730 void Game::send_player_bulldoze(PlayerImmovable& pi, bool const recurse) {
731 send_player_command(new CmdBulldoze(get_gametime(), pi.owner().player_number(), pi, recurse));
732 }
733
send_player_dismantle(PlayerImmovable & pi,bool kw)734 void Game::send_player_dismantle(PlayerImmovable& pi, bool kw) {
735 send_player_command(
736 new CmdDismantleBuilding(get_gametime(), pi.owner().player_number(), pi, kw));
737 }
738
send_player_build(int32_t const pid,const Coords & coords,DescriptionIndex const id)739 void Game::send_player_build(int32_t const pid, const Coords& coords, DescriptionIndex const id) {
740 assert(tribes().building_exists(id));
741 send_player_command(new CmdBuild(get_gametime(), pid, coords, id));
742 }
743
send_player_build_flag(int32_t const pid,const Coords & coords)744 void Game::send_player_build_flag(int32_t const pid, const Coords& coords) {
745 send_player_command(new CmdBuildFlag(get_gametime(), pid, coords));
746 }
747
send_player_build_road(int32_t pid,Path & path)748 void Game::send_player_build_road(int32_t pid, Path& path) {
749 send_player_command(new CmdBuildRoad(get_gametime(), pid, path));
750 }
751
send_player_build_waterway(int32_t pid,Path & path)752 void Game::send_player_build_waterway(int32_t pid, Path& path) {
753 send_player_command(new CmdBuildWaterway(get_gametime(), pid, path));
754 }
755
send_player_flagaction(Flag & flag)756 void Game::send_player_flagaction(Flag& flag) {
757 send_player_command(new CmdFlagAction(get_gametime(), flag.owner().player_number(), flag));
758 }
759
send_player_start_stop_building(Building & building)760 void Game::send_player_start_stop_building(Building& building) {
761 send_player_command(
762 new CmdStartStopBuilding(get_gametime(), building.owner().player_number(), building));
763 }
764
send_player_militarysite_set_soldier_preference(Building & building,SoldierPreference my_preference)765 void Game::send_player_militarysite_set_soldier_preference(Building& building,
766 SoldierPreference my_preference) {
767 send_player_command(new CmdMilitarySiteSetSoldierPreference(
768 get_gametime(), building.owner().player_number(), building, my_preference));
769 }
770
send_player_start_or_cancel_expedition(Building & building)771 void Game::send_player_start_or_cancel_expedition(Building& building) {
772 send_player_command(
773 new CmdStartOrCancelExpedition(get_gametime(), building.owner().player_number(), building));
774 }
775
send_player_enhance_building(Building & building,DescriptionIndex const id,bool kw)776 void Game::send_player_enhance_building(Building& building, DescriptionIndex const id, bool kw) {
777 assert(building.descr().type() == MapObjectType::CONSTRUCTIONSITE ||
778 building.owner().tribe().has_building(id));
779 send_player_command(
780 new CmdEnhanceBuilding(get_gametime(), building.owner().player_number(), building, id, kw));
781 }
782
send_player_evict_worker(Worker & worker)783 void Game::send_player_evict_worker(Worker& worker) {
784 send_player_command(new CmdEvictWorker(get_gametime(), worker.owner().player_number(), worker));
785 }
786
send_player_set_ware_priority(PlayerImmovable & imm,int32_t const type,DescriptionIndex const index,int32_t const prio,bool cs)787 void Game::send_player_set_ware_priority(PlayerImmovable& imm,
788 int32_t const type,
789 DescriptionIndex const index,
790 int32_t const prio,
791 bool cs) {
792 send_player_command(new CmdSetWarePriority(
793 get_gametime(), imm.owner().player_number(), imm, type, index, prio, cs));
794 }
795
send_player_set_input_max_fill(PlayerImmovable & imm,DescriptionIndex const index,WareWorker type,uint32_t const max_fill,bool cs)796 void Game::send_player_set_input_max_fill(PlayerImmovable& imm,
797 DescriptionIndex const index,
798 WareWorker type,
799 uint32_t const max_fill,
800 bool cs) {
801 send_player_command(new CmdSetInputMaxFill(
802 get_gametime(), imm.owner().player_number(), imm, index, type, max_fill, cs));
803 }
804
send_player_change_training_options(TrainingSite & ts,TrainingAttribute attr,int32_t const val)805 void Game::send_player_change_training_options(TrainingSite& ts,
806 TrainingAttribute attr,
807 int32_t const val) {
808 send_player_command(
809 new CmdChangeTrainingOptions(get_gametime(), ts.owner().player_number(), ts, attr, val));
810 }
811
send_player_drop_soldier(Building & b,int32_t const ser)812 void Game::send_player_drop_soldier(Building& b, int32_t const ser) {
813 assert(ser != -1);
814 send_player_command(new CmdDropSoldier(get_gametime(), b.owner().player_number(), b, ser));
815 }
816
send_player_change_soldier_capacity(Building & b,int32_t const val)817 void Game::send_player_change_soldier_capacity(Building& b, int32_t const val) {
818 send_player_command(
819 new CmdChangeSoldierCapacity(get_gametime(), b.owner().player_number(), b, val));
820 }
821
send_player_enemyflagaction(const Flag & flag,PlayerNumber const who_attacks,const std::vector<Serial> & soldiers)822 void Game::send_player_enemyflagaction(const Flag& flag,
823 PlayerNumber const who_attacks,
824 const std::vector<Serial>& soldiers) {
825 if (1 < player(who_attacks)
826 .vision(Map::get_index(flag.get_building()->get_position(), map().get_width())))
827 send_player_command(new CmdEnemyFlagAction(get_gametime(), who_attacks, flag, soldiers));
828 }
829
send_player_ship_scouting_direction(Ship & ship,WalkingDir direction)830 void Game::send_player_ship_scouting_direction(Ship& ship, WalkingDir direction) {
831 send_player_command(new CmdShipScoutDirection(
832 get_gametime(), ship.get_owner()->player_number(), ship.serial(), direction));
833 }
834
send_player_ship_construct_port(Ship & ship,Coords coords)835 void Game::send_player_ship_construct_port(Ship& ship, Coords coords) {
836 send_player_command(new CmdShipConstructPort(
837 get_gametime(), ship.get_owner()->player_number(), ship.serial(), coords));
838 }
839
send_player_ship_explore_island(Ship & ship,IslandExploreDirection direction)840 void Game::send_player_ship_explore_island(Ship& ship, IslandExploreDirection direction) {
841 send_player_command(new CmdShipExploreIsland(
842 get_gametime(), ship.get_owner()->player_number(), ship.serial(), direction));
843 }
844
send_player_sink_ship(Ship & ship)845 void Game::send_player_sink_ship(Ship& ship) {
846 send_player_command(
847 new CmdShipSink(get_gametime(), ship.get_owner()->player_number(), ship.serial()));
848 }
849
send_player_cancel_expedition_ship(Ship & ship)850 void Game::send_player_cancel_expedition_ship(Ship& ship) {
851 send_player_command(new CmdShipCancelExpedition(
852 get_gametime(), ship.get_owner()->player_number(), ship.serial()));
853 }
854
send_player_expedition_config(PortDock & pd,WareWorker ww,DescriptionIndex di,bool add)855 void Game::send_player_expedition_config(PortDock& pd,
856 WareWorker ww,
857 DescriptionIndex di,
858 bool add) {
859 send_player_command(
860 new CmdExpeditionConfig(get_gametime(), pd.get_owner()->player_number(), pd, ww, di, add));
861 }
862
send_player_propose_trade(const Trade & trade)863 void Game::send_player_propose_trade(const Trade& trade) {
864 auto* object = objects().get_object(trade.initiator);
865 assert(object != nullptr);
866 send_player_command(
867 new CmdProposeTrade(get_gametime(), object->get_owner()->player_number(), trade));
868 }
869
send_player_set_stock_policy(Building & imm,WareWorker ww,DescriptionIndex di,StockPolicy sp)870 void Game::send_player_set_stock_policy(Building& imm,
871 WareWorker ww,
872 DescriptionIndex di,
873 StockPolicy sp) {
874 send_player_command(new CmdSetStockPolicy(
875 get_gametime(), imm.get_owner()->player_number(), imm, ww == wwWORKER, di, sp));
876 }
877
propose_trade(const Trade & trade)878 int Game::propose_trade(const Trade& trade) {
879 // TODO(sirver,trading): Check if a trade is possible (i.e. if there is a
880 // path between the two markets);
881 const int id = next_trade_agreement_id_;
882 ++next_trade_agreement_id_;
883
884 auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator));
885 auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver));
886 // This is only ever called through a PlayerCommand and that already made
887 // sure that the objects still exist. Since no time has passed, they should
888 // not have vanished under us.
889 assert(initiator != nullptr);
890 assert(receiver != nullptr);
891
892 receiver->removed.connect([this, id](const uint32_t /* serial */) { cancel_trade(id); });
893 initiator->removed.connect([this, id](const uint32_t /* serial */) { cancel_trade(id); });
894
895 receiver->send_message(*this, Message::Type::kTradeOfferReceived, _("Trade Offer"),
896 receiver->descr().icon_filename(), receiver->descr().descname(),
897 _("This market has received a new trade offer."), true);
898 trade_agreements_[id] = TradeAgreement{TradeAgreement::State::kProposed, trade};
899
900 // TODO(sirver,trading): this should be done through another player_command, but I
901 // want to get to the trade logic implementation now.
902 accept_trade(id);
903 return id;
904 }
905
accept_trade(const int trade_id)906 void Game::accept_trade(const int trade_id) {
907 auto it = trade_agreements_.find(trade_id);
908 if (it == trade_agreements_.end()) {
909 log("Game::accept_trade: Trade %d has vanished. Ignoring.\n", trade_id);
910 return;
911 }
912 const Trade& trade = it->second.trade;
913 auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator));
914 auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver));
915 if (initiator == nullptr || receiver == nullptr) {
916 cancel_trade(trade_id);
917 return;
918 }
919
920 initiator->new_trade(trade_id, trade.items_to_send, trade.num_batches, trade.receiver);
921 receiver->new_trade(trade_id, trade.items_to_receive, trade.num_batches, trade.initiator);
922
923 // TODO(sirver,trading): Message the users that the trade has been accepted.
924 }
925
cancel_trade(int trade_id)926 void Game::cancel_trade(int trade_id) {
927 // The trade id might be long gone - since we never disconnect from the
928 // 'removed' signal of the two buildings, we might be invoked long after the
929 // trade was deleted for other reasons.
930 const auto it = trade_agreements_.find(trade_id);
931 if (it == trade_agreements_.end()) {
932 return;
933 }
934 const auto& trade = it->second.trade;
935
936 auto* initiator = dynamic_cast<Market*>(objects().get_object(trade.initiator));
937 if (initiator != nullptr) {
938 initiator->cancel_trade(trade_id);
939 // TODO(sirver,trading): Send message to owner that the trade has been canceled.
940 }
941
942 auto* receiver = dynamic_cast<Market*>(objects().get_object(trade.receiver));
943 if (receiver != nullptr) {
944 receiver->cancel_trade(trade_id);
945 // TODO(sirver,trading): Send message to owner that the trade has been canceled.
946 }
947 trade_agreements_.erase(trade_id);
948 }
949
lua()950 LuaGameInterface& Game::lua() {
951 return static_cast<LuaGameInterface&>(EditorGameBase::lua());
952 }
953
get_win_condition_displayname() const954 const std::string& Game::get_win_condition_displayname() const {
955 return win_condition_displayname_;
956 }
set_win_condition_displayname(const std::string & name)957 void Game::set_win_condition_displayname(const std::string& name) {
958 win_condition_displayname_ = name;
959 }
960
961 /**
962 * Sample global statistics for the game.
963 */
sample_statistics()964 void Game::sample_statistics() {
965 // Update general stats
966 PlayerNumber const nr_plrs = map().get_nrplayers();
967 std::vector<uint32_t> land_size;
968 std::vector<uint32_t> nr_buildings;
969 std::vector<uint32_t> nr_casualties;
970 std::vector<uint32_t> nr_kills;
971 std::vector<uint32_t> nr_msites_lost;
972 std::vector<uint32_t> nr_msites_defeated;
973 std::vector<uint32_t> nr_civil_blds_lost;
974 std::vector<uint32_t> nr_civil_blds_defeated;
975 std::vector<uint32_t> miltary_strength;
976 std::vector<uint32_t> nr_workers;
977 std::vector<uint32_t> nr_wares;
978 std::vector<uint32_t> productivity;
979 std::vector<uint32_t> nr_production_sites;
980 std::vector<uint32_t> custom_statistic;
981 land_size.resize(nr_plrs);
982 nr_buildings.resize(nr_plrs);
983 nr_casualties.resize(nr_plrs);
984 nr_kills.resize(nr_plrs);
985 nr_msites_lost.resize(nr_plrs);
986 nr_msites_defeated.resize(nr_plrs);
987 nr_civil_blds_lost.resize(nr_plrs);
988 nr_civil_blds_defeated.resize(nr_plrs);
989 miltary_strength.resize(nr_plrs);
990 nr_workers.resize(nr_plrs);
991 nr_wares.resize(nr_plrs);
992 productivity.resize(nr_plrs);
993 nr_production_sites.resize(nr_plrs);
994 custom_statistic.resize(nr_plrs);
995
996 // We walk the map, to gain all needed information.
997 const Map& themap = map();
998 Extent const extent = themap.extent();
999 iterate_Map_FCoords(themap, extent, fc) {
1000 if (PlayerNumber const owner = fc.field->get_owned_by())
1001 ++land_size[owner - 1];
1002
1003 // Get the immovable
1004 if (upcast(Building, building, fc.field->get_immovable()))
1005 if (building->get_position() == fc) { // only count main location
1006 uint8_t const player_index = building->owner().player_number() - 1;
1007 ++nr_buildings[player_index];
1008
1009 // If it is a productionsite, add its productivity.
1010 if (upcast(ProductionSite, productionsite, building)) {
1011 ++nr_production_sites[player_index];
1012 productivity[player_index] += productionsite->get_statistics_percent();
1013 }
1014 }
1015
1016 // Now, walk the bobs
1017 for (Bob const* b = fc.field->get_first_bob(); b; b = b->get_next_bob())
1018 if (upcast(Soldier const, s, b))
1019 miltary_strength[s->owner().player_number() - 1] +=
1020 s->get_level(TrainingAttribute::kTotal) + 1; // So that level 0 also counts.
1021 }
1022
1023 // Number of workers / wares / casualties / kills.
1024 iterate_players_existing(p, nr_plrs, *this, plr) {
1025 uint32_t wostock = 0;
1026 uint32_t wastock = 0;
1027
1028 for (const auto& economy : plr->economies()) {
1029 const TribeDescr& tribe = plr->tribe();
1030
1031 switch (economy.second->type()) {
1032 case wwWARE:
1033 for (const DescriptionIndex& ware_index : tribe.wares()) {
1034 wastock += economy.second->stock_ware_or_worker(ware_index);
1035 }
1036 break;
1037 case wwWORKER:
1038 for (const DescriptionIndex& worker_index : tribe.workers()) {
1039 if (tribe.get_worker_descr(worker_index)->type() != MapObjectType::CARRIER) {
1040 wostock += economy.second->stock_ware_or_worker(worker_index);
1041 }
1042 }
1043 break;
1044 }
1045 }
1046 nr_wares[p - 1] = wastock;
1047 nr_workers[p - 1] = wostock;
1048 nr_casualties[p - 1] = plr->casualties();
1049 nr_kills[p - 1] = plr->kills();
1050 nr_msites_lost[p - 1] = plr->msites_lost();
1051 nr_msites_defeated[p - 1] = plr->msites_defeated();
1052 nr_civil_blds_lost[p - 1] = plr->civil_blds_lost();
1053 nr_civil_blds_defeated[p - 1] = plr->civil_blds_defeated();
1054 }
1055
1056 // Now, divide the statistics
1057 for (uint32_t i = 0; i < map().get_nrplayers(); ++i) {
1058 if (productivity[i])
1059 productivity[i] /= nr_production_sites[i];
1060 }
1061
1062 // If there is a hook function defined to sample special statistics in this
1063 // game, call the corresponding Lua function
1064 std::unique_ptr<LuaTable> hook = lua().get_hook("custom_statistic");
1065 if (hook) {
1066 hook->do_not_warn_about_unaccessed_keys();
1067 iterate_players_existing(p, nr_plrs, *this, plr) {
1068 std::unique_ptr<LuaCoroutine> cr(hook->get_coroutine("calculator"));
1069 cr->push_arg(plr);
1070 cr->resume();
1071 custom_statistic[p - 1] = cr->pop_uint32();
1072 }
1073 }
1074
1075 // Now, push this on the general statistics
1076 general_stats_.resize(map().get_nrplayers());
1077 for (uint32_t i = 0; i < map().get_nrplayers(); ++i) {
1078 general_stats_[i].land_size.push_back(land_size[i]);
1079 general_stats_[i].nr_buildings.push_back(nr_buildings[i]);
1080 general_stats_[i].nr_casualties.push_back(nr_casualties[i]);
1081 general_stats_[i].nr_kills.push_back(nr_kills[i]);
1082 general_stats_[i].nr_msites_lost.push_back(nr_msites_lost[i]);
1083 general_stats_[i].nr_msites_defeated.push_back(nr_msites_defeated[i]);
1084 general_stats_[i].nr_civil_blds_lost.push_back(nr_civil_blds_lost[i]);
1085 general_stats_[i].nr_civil_blds_defeated.push_back(nr_civil_blds_defeated[i]);
1086 general_stats_[i].miltary_strength.push_back(miltary_strength[i]);
1087 general_stats_[i].nr_workers.push_back(nr_workers[i]);
1088 general_stats_[i].nr_wares.push_back(nr_wares[i]);
1089 general_stats_[i].productivity.push_back(productivity[i]);
1090 general_stats_[i].custom_statistic.push_back(custom_statistic[i]);
1091 }
1092
1093 // Calculate statistics for the players
1094 const PlayerNumber nr_players = map().get_nrplayers();
1095 iterate_players_existing(p, nr_players, *this, plr) plr->sample_statistics();
1096 }
1097
1098 /**
1099 * Read statistics data from a file.
1100 *
1101 * \param fr file to read from
1102 */
read_statistics(FileRead & fr)1103 void Game::read_statistics(FileRead& fr) {
1104 fr.unsigned_32(); // used to be last stats update time
1105
1106 // Read general statistics
1107 uint32_t entries = fr.unsigned_16();
1108 const PlayerNumber nr_players = map().get_nrplayers();
1109 general_stats_.resize(nr_players);
1110
1111 iterate_players_existing_novar(p, nr_players, *this) {
1112 general_stats_[p - 1].land_size.resize(entries);
1113 general_stats_[p - 1].nr_workers.resize(entries);
1114 general_stats_[p - 1].nr_buildings.resize(entries);
1115 general_stats_[p - 1].nr_wares.resize(entries);
1116 general_stats_[p - 1].productivity.resize(entries);
1117 general_stats_[p - 1].nr_casualties.resize(entries);
1118 general_stats_[p - 1].nr_kills.resize(entries);
1119 general_stats_[p - 1].nr_msites_lost.resize(entries);
1120 general_stats_[p - 1].nr_msites_defeated.resize(entries);
1121 general_stats_[p - 1].nr_civil_blds_lost.resize(entries);
1122 general_stats_[p - 1].nr_civil_blds_defeated.resize(entries);
1123 general_stats_[p - 1].miltary_strength.resize(entries);
1124 general_stats_[p - 1].custom_statistic.resize(entries);
1125 }
1126
1127 iterate_players_existing_novar(
1128 p, nr_players, *this) for (uint32_t j = 0; j < general_stats_[p - 1].land_size.size(); ++j) {
1129 general_stats_[p - 1].land_size[j] = fr.unsigned_32();
1130 general_stats_[p - 1].nr_workers[j] = fr.unsigned_32();
1131 general_stats_[p - 1].nr_buildings[j] = fr.unsigned_32();
1132 general_stats_[p - 1].nr_wares[j] = fr.unsigned_32();
1133 general_stats_[p - 1].productivity[j] = fr.unsigned_32();
1134 general_stats_[p - 1].nr_casualties[j] = fr.unsigned_32();
1135 general_stats_[p - 1].nr_kills[j] = fr.unsigned_32();
1136 general_stats_[p - 1].nr_msites_lost[j] = fr.unsigned_32();
1137 general_stats_[p - 1].nr_msites_defeated[j] = fr.unsigned_32();
1138 general_stats_[p - 1].nr_civil_blds_lost[j] = fr.unsigned_32();
1139 general_stats_[p - 1].nr_civil_blds_defeated[j] = fr.unsigned_32();
1140 general_stats_[p - 1].miltary_strength[j] = fr.unsigned_32();
1141 general_stats_[p - 1].custom_statistic[j] = fr.unsigned_32();
1142 }
1143 }
1144
1145 /**
1146 * Write general statistics to the given file.
1147 */
write_statistics(FileWrite & fw)1148 void Game::write_statistics(FileWrite& fw) {
1149 fw.unsigned_32(0); // Used to be last stats update time. No longer needed
1150
1151 // General statistics
1152 // First, we write the size of the statistics arrays
1153 uint32_t entries = 0;
1154
1155 const PlayerNumber nr_players = map().get_nrplayers();
1156 iterate_players_existing_novar(p, nr_players, *this) if (!general_stats_.empty()) {
1157 entries = general_stats_[p - 1].land_size.size();
1158 break;
1159 }
1160
1161 fw.unsigned_16(entries);
1162
1163 iterate_players_existing_novar(p, nr_players, *this) for (uint32_t j = 0; j < entries; ++j) {
1164 fw.unsigned_32(general_stats_[p - 1].land_size[j]);
1165 fw.unsigned_32(general_stats_[p - 1].nr_workers[j]);
1166 fw.unsigned_32(general_stats_[p - 1].nr_buildings[j]);
1167 fw.unsigned_32(general_stats_[p - 1].nr_wares[j]);
1168 fw.unsigned_32(general_stats_[p - 1].productivity[j]);
1169 fw.unsigned_32(general_stats_[p - 1].nr_casualties[j]);
1170 fw.unsigned_32(general_stats_[p - 1].nr_kills[j]);
1171 fw.unsigned_32(general_stats_[p - 1].nr_msites_lost[j]);
1172 fw.unsigned_32(general_stats_[p - 1].nr_msites_defeated[j]);
1173 fw.unsigned_32(general_stats_[p - 1].nr_civil_blds_lost[j]);
1174 fw.unsigned_32(general_stats_[p - 1].nr_civil_blds_defeated[j]);
1175 fw.unsigned_32(general_stats_[p - 1].miltary_strength[j]);
1176 fw.unsigned_32(general_stats_[p - 1].custom_statistic[j]);
1177 }
1178 }
1179 } // namespace Widelands
1180