1 // _________ __ __
2 // / _____// |_____________ _/ |______ ____ __ __ ______
3 // \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/
4 // / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ |
5 // /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ >
6 // \/ \/ \//_____/ \/
7 // ______________________ ______________________
8 // T H E W A R B E G I N S
9 // Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name mainloop.cpp - The main game loop. */
12 //
13 // (c) Copyright 1998-2019 by Lutz Sammer, Jimmy Salmon and Andrettin
14 //
15 // This program is free software; you can redistribute it and/or modify
16 // it under the terms of the GNU General Public License as published by
17 // the Free Software Foundation; only version 2 of the License.
18 //
19 // This program is distributed in the hope that it will be useful,
20 // but WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 // GNU General Public License for more details.
23 //
24 // You should have received a copy of the GNU General Public License
25 // along with this program; if not, write to the Free Software
26 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 // 02111-1307, USA.
28 //
29
30 //@{
31
32 //----------------------------------------------------------------------------
33 // Includes
34 //----------------------------------------------------------------------------
35
36 #include "stratagus.h"
37
38 #include "actions.h"
39 #include "character.h"
40 #include "civilization.h"
41 #include "commands.h"
42 #include "dialogue.h"
43 #include "editor.h"
44 //Wyrmgus start
45 #include "font.h"
46 //Wyrmgus end
47 #include "game.h"
48 //Wyrmgus start
49 #include "grand_strategy.h"
50 #include "luacallback.h"
51 //Wyrmgus end
52 #include "map/map.h"
53 #include "map/map_layer.h"
54 #include "map/terrain_type.h"
55 #include "map/tileset.h" //for tile animation
56 #include "missile.h"
57 #include "network.h"
58 #include "particle.h"
59 #include "plane.h"
60 //Wyrmgus start
61 #include "quest.h"
62 //Wyrmgus end
63 #include "replay.h"
64 #include "results.h"
65 //Wyrmgus start
66 #include "settings.h"
67 //Wyrmgus end
68 #include "sound.h"
69 #include "sound_server.h"
70 #include "time/calendar.h"
71 #include "time/time_of_day.h"
72 #include "translate.h"
73 #include "trigger.h"
74 #include "ui/interface.h"
75 #include "ui/ui.h"
76 #include "unit/unit.h"
77 //Wyrmgus start
78 #include "unit/unit_manager.h"
79 #include "upgrade/upgrade.h"
80 //Wyrmgus end
81 #include "video.h"
82 #include "world.h"
83
84 #include <guichan.h>
85
86 #ifdef USE_OAML
87 #include <oaml.h>
88
89 extern oamlApi *oaml;
90 extern bool enableOAML;
91 #endif
92
93 void DrawGuichanWidgets();
94
95 //----------------------------------------------------------------------------
96 // Variables
97 //----------------------------------------------------------------------------
98
99 /// variable set when we are scrolling via keyboard
100 int KeyScrollState = ScrollNone;
101
102 /// variable set when we are scrolling via mouse
103 int MouseScrollState = ScrollNone;
104
105 EventCallback GameCallbacks; /// Game callbacks
106 EventCallback EditorCallbacks; /// Editor callbacks
107
108 //----------------------------------------------------------------------------
109 // Functions
110 //----------------------------------------------------------------------------
111
112 /**
113 ** Handle scrolling area.
114 **
115 ** @param state Scroll direction/state.
116 ** @param fast Flag scroll faster.
117 **
118 ** @todo Support dynamic acceleration of scroll speed.
119 ** @todo If the scroll key is longer pressed the area is scrolled faster.
120 */
DoScrollArea(int state,bool fast,bool isKeyboard)121 void DoScrollArea(int state, bool fast, bool isKeyboard)
122 {
123 CViewport *vp;
124 int stepx;
125 int stepy;
126 static int remx = 0; // FIXME: docu
127 static int remy = 0; // FIXME: docu
128
129 int speed = isKeyboard ? UI.KeyScrollSpeed : UI.MouseScrollSpeed;
130
131 if (state == ScrollNone) {
132 return;
133 }
134
135 vp = UI.SelectedViewport;
136
137 if (fast) {
138 //Wyrmgus start
139 // stepx = (int)(speed * vp->MapWidth / 2 * Map.GetCurrentPixelTileSize().x * FRAMES_PER_SECOND / 4);
140 // stepy = (int)(speed * vp->MapHeight / 2 * Map.GetCurrentPixelTileSize().y * FRAMES_PER_SECOND / 4);
141 stepx = (int)(speed * Map.GetCurrentPixelTileSize().x * FRAMES_PER_SECOND / 4 * 4);
142 stepy = (int)(speed * Map.GetCurrentPixelTileSize().y * FRAMES_PER_SECOND / 4 * 4);
143 //Wyrmgus end
144 } else {// dynamic: let these variables increase up to fast..
145 // FIXME: pixels per second should be configurable
146 stepx = (int)(speed * Map.GetCurrentPixelTileSize().x * FRAMES_PER_SECOND / 4);
147 stepy = (int)(speed * Map.GetCurrentPixelTileSize().y * FRAMES_PER_SECOND / 4);
148 }
149 if ((state & (ScrollLeft | ScrollRight)) && (state & (ScrollLeft | ScrollRight)) != (ScrollLeft | ScrollRight)) {
150 stepx = stepx * 100 * 100 / VideoSyncSpeed / FRAMES_PER_SECOND / (SkipFrames + 1);
151 remx += stepx - (stepx / 100) * 100;
152 stepx /= 100;
153 if (remx > 100) {
154 ++stepx;
155 remx -= 100;
156 }
157 } else {
158 stepx = 0;
159 }
160 if ((state & (ScrollUp | ScrollDown)) && (state & (ScrollUp | ScrollDown)) != (ScrollUp | ScrollDown)) {
161 stepy = stepy * 100 * 100 / VideoSyncSpeed / FRAMES_PER_SECOND / (SkipFrames + 1);
162 remy += stepy - (stepy / 100) * 100;
163 stepy /= 100;
164 if (remy > 100) {
165 ++stepy;
166 remy -= 100;
167 }
168 } else {
169 stepy = 0;
170 }
171
172 if (state & ScrollUp) {
173 stepy = -stepy;
174 }
175 if (state & ScrollLeft) {
176 stepx = -stepx;
177 }
178 const PixelDiff offset(stepx, stepy);
179
180 vp->Set(vp->MapPos, vp->Offset + offset);
181
182 // This recalulates some values
183 HandleMouseMove(CursorScreenPos);
184 }
185
186 /**
187 ** Draw map area
188 */
DrawMapArea()189 void DrawMapArea()
190 {
191 // Draw all of the viewports
192 for (CViewport *vp = UI.Viewports; vp < UI.Viewports + UI.NumViewports; ++vp) {
193 // Center viewport on tracked unit
194 if (vp->Unit) {
195 if (vp->Unit->Destroyed || vp->Unit->CurrentAction() == UnitActionDie) {
196 vp->Unit = nullptr;
197 } else {
198 if (UI.CurrentMapLayer != vp->Unit->MapLayer) {
199 ChangeCurrentMapLayer(vp->Unit->MapLayer->ID);
200 }
201 vp->Center(vp->Unit->GetMapPixelPosCenter());
202 }
203 }
204 vp->Draw();
205 }
206 }
207
208 /**
209 ** Display update.
210 **
211 ** This functions updates everything on screen. The map, the gui, the
212 ** cursors.
213 */
UpdateDisplay()214 void UpdateDisplay()
215 {
216 if (GameRunning || Editor.Running == EditorEditing) {
217 // to prevent empty spaces in the UI
218 #if defined(USE_OPENGL) || defined(USE_GLES)
219 Video.FillRectangleClip(ColorBlack, 0, 0, Video.ViewportWidth, Video.ViewportHeight);
220 #else
221 Video.FillRectangleClip(ColorBlack, 0, 0, Video.Width, Video.Height);
222 #endif
223 DrawMapArea();
224 DrawMessages();
225
226 if (CursorState == CursorStateRectangle) {
227 DrawCursor();
228 }
229
230 //Wyrmgus start
231 if (CursorBuilding && CursorOn == CursorOnMap) {
232 DrawBuildingCursor();
233 }
234 //Wyrmgus end
235
236 if ((Preference.BigScreen && !BigMapMode) || (!Preference.BigScreen && BigMapMode)) {
237 UiToggleBigMap();
238 }
239
240 if (!BigMapMode) {
241 for (size_t i = 0; i < UI.Fillers.size(); ++i) {
242 UI.Fillers[i].G->DrawSubClip(0, 0,
243 UI.Fillers[i].G->Width,
244 UI.Fillers[i].G->Height,
245 UI.Fillers[i].X, UI.Fillers[i].Y);
246 }
247 DrawMenuButtonArea();
248 DrawUserDefinedButtons();
249
250 UI.Minimap.Draw();
251 UI.Minimap.DrawViewportArea(*UI.SelectedViewport);
252
253 UI.InfoPanel.Draw();
254 DrawResources();
255 DrawTime();
256 DrawAge();
257 DrawMapLayerButtons();
258 UI.StatusLine.Draw();
259 UI.StatusLine.DrawCosts();
260 UI.ButtonPanel.Draw();
261 }
262
263 DrawTimer();
264
265 //Wyrmgus start
266 //draw worker icon if there are idle workers
267 if (UI.IdleWorkerButton && !ThisPlayer->FreeWorkers.empty()) {
268 const PixelPos pos(UI.IdleWorkerButton->X, UI.IdleWorkerButton->Y);
269 const int flag = (ButtonAreaUnderCursor == ButtonAreaIdleWorker && ButtonUnderCursor == 0) ? (IconActive | (MouseButtons & LeftButton)) : 0;
270
271 ThisPlayer->FreeWorkers[0]->GetIcon().Icon->DrawUnitIcon(*UI.IdleWorkerButton->Style, flag, pos, ".", ThisPlayer->Index);
272 }
273
274 //draw icon if there are units with available level up upgrades
275 if (UI.LevelUpUnitButton && !ThisPlayer->LevelUpUnits.empty()) {
276 const PixelPos pos(UI.LevelUpUnitButton->X, UI.LevelUpUnitButton->Y);
277 const int flag = (ButtonAreaUnderCursor == ButtonAreaLevelUpUnit && ButtonUnderCursor == 0) ? (IconActive | (MouseButtons & LeftButton)) : 0;
278
279 ThisPlayer->LevelUpUnits[0]->GetIcon().Icon->DrawUnitIcon(*UI.LevelUpUnitButton->Style, flag, pos, "", ThisPlayer->Index);
280 }
281
282 //draw icon if the player has a hero
283 for (size_t i = 0; i < UI.HeroUnitButtons.size() && i < ThisPlayer->Heroes.size(); ++i) {
284 const PixelPos pos(UI.HeroUnitButtons[i].X, UI.HeroUnitButtons[i].Y);
285 const int flag = (ButtonAreaUnderCursor == ButtonAreaHeroUnit && ButtonUnderCursor == i) ? (IconActive | (MouseButtons & LeftButton)) : 0;
286
287 ThisPlayer->Heroes[i]->GetIcon().Icon->DrawUnitIcon(*UI.HeroUnitButtons[i].Style, flag, pos, "", ThisPlayer->Index);
288 }
289
290 DrawPopups();
291 //Wyrmgus end
292 }
293
294 DrawPieMenu(); // draw pie menu only if needed
295
296 DrawGuichanWidgets();
297
298 if (CursorState != CursorStateRectangle) {
299 DrawCursor();
300 }
301
302 //
303 // Update changes to display.
304 //
305 Invalidate();
306 }
307
InitGameCallbacks()308 static void InitGameCallbacks()
309 {
310 GameCallbacks.ButtonPressed = HandleButtonDown;
311 GameCallbacks.ButtonReleased = HandleButtonUp;
312 GameCallbacks.MouseMoved = HandleMouseMove;
313 GameCallbacks.MouseExit = HandleMouseExit;
314 GameCallbacks.KeyPressed = HandleKeyDown;
315 GameCallbacks.KeyReleased = HandleKeyUp;
316 GameCallbacks.KeyRepeated = HandleKeyRepeat;
317 GameCallbacks.NetworkEvent = NetworkEvent;
318 }
319
GameLogicLoop()320 static void GameLogicLoop()
321 {
322 // Can't find a better place.
323 // FIXME: We need to find a better place!
324 SaveGameLoading = false;
325
326 #ifdef USE_OAML
327 if (enableOAML && oaml && UI.CurrentMapLayer->GetTimeOfDay()) {
328 // Time of day can change our main music loop, if the current playing track is set for this
329 SetMusicCondition(OAML_CONDID_MAIN_LOOP, UI.CurrentMapLayer->GetTimeOfDay()->ID);
330 }
331 #endif
332
333 //
334 // Game logic part
335 //
336 if (!GamePaused && NetworkInSync && !SkipGameCycle) {
337 SinglePlayerReplayEachCycle();
338 ++GameCycle;
339 MultiPlayerReplayEachCycle();
340 NetworkCommands(); // Get network commands
341 TriggersEachCycle();// handle triggers
342 UnitActions(); // handle units
343 MissileActions(); // handle missiles
344 PlayersEachCycle(); // handle players
345 UpdateTimer(); // update game timer
346
347 for (size_t z = 0; z < Map.MapLayers.size(); ++z) {
348 CMapLayer *map_layer = Map.MapLayers[z];
349 map_layer->DoPerCycleLoop();
350 }
351
352 //
353 // Work todo each second.
354 // Split into different frames, to reduce cpu time.
355 // Increment mana of magic units.
356 // Update mini-map.
357 // Update map fog of war.
358 // Call AI.
359 // Check game goals.
360 // Check rescue of units.
361 //
362 switch (GameCycle % CYCLES_PER_SECOND) {
363 case 0: // At cycle 0, start all ai players...
364 if (GameCycle == 0) {
365 for (int player = 0; player < NumPlayers; ++player) {
366 PlayersEachSecond(player);
367 }
368 }
369 break;
370 case 1:
371 break;
372 case 2:
373 break;
374 case 3: // minimap update
375 UI.Minimap.UpdateCache = true;
376 break;
377 case 4:
378 break;
379 case 5: // forest grow
380 Map.RegenerateForest();
381 break;
382 case 6: // overtaking units
383 RescueUnits();
384 break;
385 //Wyrmgus start
386 /*
387 default: {
388 // FIXME: assume that NumPlayers < (CYCLES_PER_SECOND - 7)
389 int player = (GameCycle % CYCLES_PER_SECOND) - 7;
390 Assert(player >= 0);
391 if (player < NumPlayers) {
392 PlayersEachSecond(player);
393 }
394 }
395 */
396 //Wyrmgus end
397 }
398
399 //Wyrmgus start
400 int player = (GameCycle - 1) % CYCLES_PER_SECOND;
401 Assert(player >= 0);
402 if (player < NumPlayers) {
403 PlayersEachSecond(player);
404 if ((player + CYCLES_PER_SECOND) < NumPlayers) {
405 PlayersEachSecond(player + CYCLES_PER_SECOND);
406 }
407 }
408
409 player = (GameCycle - 1) % (CYCLES_PER_MINUTE / 2);
410 Assert(player >= 0);
411 if (player < NumPlayers) {
412 PlayersEachHalfMinute(player);
413 }
414
415 player = (GameCycle - 1) % CYCLES_PER_MINUTE;
416 Assert(player >= 0);
417 if (player < NumPlayers) {
418 PlayersEachMinute(player);
419 }
420 //Wyrmgus end
421
422 if (GameCycle > 0) {
423 if (GameCycle % CYCLES_PER_IN_GAME_HOUR == 0) {
424 CDate::CurrentTotalHours++;
425
426 for (size_t i = 0; i < CCalendar::Calendars.size(); ++i) {
427 CCalendar *calendar = CCalendar::Calendars[i];
428
429 calendar->CurrentDate.AddHours(calendar, 1, DEFAULT_DAY_MULTIPLIER_PER_YEAR);
430
431 if (calendar->CurrentDayOfTheWeek != -1 && CDate::CurrentTotalHours % calendar->HoursPerDay == 0) { //day passed in the calendar
432 calendar->CurrentDayOfTheWeek++;
433 calendar->CurrentDayOfTheWeek %= calendar->DaysOfTheWeek.size();
434 }
435 }
436
437 for (size_t z = 0; z < Map.MapLayers.size(); ++z) {
438 CMapLayer *map_layer = Map.MapLayers[z];
439 map_layer->DoPerHourLoop();
440 }
441 }
442 }
443
444 if (Preference.AutosaveMinutes != 0 && !IsNetworkGame() && GameCycle > 0 && (GameCycle % (CYCLES_PER_MINUTE * Preference.AutosaveMinutes)) == 0) { // autosave every X minutes, if the option is enabled
445 UI.StatusLine.Set(_("Autosave"));
446 //Wyrmgus start
447 // SaveGame("autosave.sav");
448 CclCommand("if (RunSaveGame ~= nil) then RunSaveGame(\"autosave.sav\") end;");
449 //Wyrmgus end
450 }
451 }
452
453 UpdateMessages(); // update messages
454 ParticleManager.update(); // handle particles
455 CheckMusicFinished(); // Check for next song
456
457 if (FastForwardCycle <= GameCycle || !(GameCycle & 0x3f)) {
458 WaitEventsOneFrame();
459 }
460
461 if (!NetworkInSync) {
462 NetworkRecover(); // recover network
463 }
464 }
465
466 //#define REALVIDEO
467 #ifdef REALVIDEO
468 static int RealVideoSyncSpeed;
469 #endif
470
DisplayLoop()471 static void DisplayLoop()
472 {
473 #if defined(USE_OPENGL) || defined(USE_GLES)
474 if (UseOpenGL) {
475 /* update only if screen changed */
476 ValidateOpenGLScreen();
477 }
478 #endif
479
480 /* update only if viewmode changed */
481 CheckViewportMode();
482
483 /*
484 * update only if Update flag is set
485 * FIXME: still not secure
486 */
487 if (UI.Minimap.UpdateCache) {
488 UI.Minimap.Update();
489 UI.Minimap.UpdateCache = false;
490 }
491
492 //
493 // Map scrolling
494
495 //
496 DoScrollArea(MouseScrollState | KeyScrollState, (KeyModifiers & ModifierControl) != 0, MouseScrollState == 0 && KeyScrollState > 0);
497
498 ColorCycle();
499
500 #ifdef REALVIDEO
501 if (FastForwardCycle > GameCycle && RealVideoSyncSpeed != VideoSyncSpeed) {
502 RealVideoSyncSpeed = VideoSyncSpeed;
503 VideoSyncSpeed = 3000;
504 }
505 #endif
506 if (FastForwardCycle <= GameCycle || GameCycle <= 10 || !(GameCycle & 0x3f)) {
507 //FIXME: this might be better placed somewhere at front of the
508 // program, as we now still have a game on the background and
509 // need to go through the game-menu or supply a map file
510 UpdateDisplay();
511
512 //
513 // If double-buffered mode, we will display the contains of
514 // VideoMemory. If direct mode this does nothing. In X11 it does
515 // XFlush
516 //
517 RealizeVideoMemory();
518 }
519 #ifdef REALVIDEO
520 if (FastForwardCycle == GameCycle) {
521 VideoSyncSpeed = RealVideoSyncSpeed;
522 }
523 #endif
524 }
525
SingleGameLoop()526 static void SingleGameLoop()
527 {
528 while (GameRunning) {
529 DisplayLoop();
530 GameLogicLoop();
531 }
532 }
533
534 /**
535 ** Game main loop.
536 **
537 ** Unit actions.
538 ** Missile actions.
539 ** Players (AI).
540 ** Cyclic events (color cycle,...)
541 ** Display update.
542 ** Input/Network/Sound.
543 */
GameMainLoop()544 void GameMainLoop()
545 {
546 const EventCallback *old_callbacks;
547
548 InitGameCallbacks();
549
550 old_callbacks = GetCallbacks();
551 SetCallbacks(&GameCallbacks);
552
553 SetVideoSync();
554 GameCursor = UI.Point.Cursor;
555 //Wyrmgus start
556 GameEstablishing = false;
557 //Wyrmgus end
558 GameRunning = true;
559
560 CParticleManager::init();
561
562 #ifdef REALVIDEO
563 RealVideoSyncSpeed = VideoSyncSpeed;
564 #endif
565
566 CclCommand("if (GameStarting ~= nil) then GameStarting() end");
567
568 MultiPlayerReplayEachCycle();
569
570 //Wyrmgus start
571 if (GameCycle == 0) { // so that these don't trigger when loading a saved game
572 if (CurrentCampaign != nullptr) {
573 for (int i = 0; i < NumPlayers; ++i) {
574 if (Players[i].Type != PlayerNobody && Players[i].Race != 0 && Players[i].Faction != -1) {
575 if (CurrentCampaign->StartDate.Year) {
576 CCivilization *civilization = CCivilization::Civilizations[Players[i].Race];
577 CFaction *faction = PlayerRaces.Factions[Players[i].Faction];
578
579 for (std::map<std::string, std::map<CDate, bool>>::iterator iterator = civilization->HistoricalUpgrades.begin(); iterator != civilization->HistoricalUpgrades.end(); ++iterator) {
580 int upgrade_id = UpgradeIdByIdent(iterator->first);
581 if (upgrade_id == -1) {
582 fprintf(stderr, "Upgrade \"%s\" doesn't exist.\n", iterator->first.c_str());
583 continue;
584 }
585 for (std::map<CDate, bool>::reverse_iterator second_iterator = iterator->second.rbegin(); second_iterator != iterator->second.rend(); ++second_iterator) {
586 if (second_iterator->first.Year == 0 || CurrentCampaign->StartDate.ContainsDate(second_iterator->first)) {
587 if (second_iterator->second && UpgradeIdentAllowed(Players[i], iterator->first.c_str()) != 'R') {
588 UpgradeAcquire(Players[i], AllUpgrades[upgrade_id]);
589 } else if (!second_iterator->second) {
590 break;
591 }
592 }
593 }
594 }
595
596 for (std::map<std::string, std::map<CDate, bool>>::iterator iterator = faction->HistoricalUpgrades.begin(); iterator != faction->HistoricalUpgrades.end(); ++iterator) {
597 int upgrade_id = UpgradeIdByIdent(iterator->first);
598 if (upgrade_id == -1) {
599 fprintf(stderr, "Upgrade \"%s\" doesn't exist.\n", iterator->first.c_str());
600 continue;
601 }
602 for (std::map<CDate, bool>::reverse_iterator second_iterator = iterator->second.rbegin(); second_iterator != iterator->second.rend(); ++second_iterator) {
603 if (second_iterator->first.Year == 0 || CurrentCampaign->StartDate.ContainsDate(second_iterator->first)) {
604 if (second_iterator->second && UpgradeIdentAllowed(Players[i], iterator->first.c_str()) != 'R') {
605 UpgradeAcquire(Players[i], AllUpgrades[upgrade_id]);
606 } else if (!second_iterator->second) {
607 break;
608 }
609 }
610 }
611 }
612
613 for (std::map<std::pair<CDate, CFaction *>, int>::iterator iterator = faction->HistoricalDiplomacyStates.begin(); iterator != faction->HistoricalDiplomacyStates.end(); ++iterator) { //set the appropriate historical diplomacy states to other factions
614 if (iterator->first.first.Year == 0 || CurrentCampaign->StartDate.ContainsDate(iterator->first.first)) {
615 CPlayer *diplomacy_state_player = GetFactionPlayer(iterator->first.second);
616 if (diplomacy_state_player) {
617 CommandDiplomacy(i, iterator->second, diplomacy_state_player->Index);
618 CommandDiplomacy(diplomacy_state_player->Index, iterator->second, i);
619 if (iterator->second == DiplomacyAllied) {
620 CommandSharedVision(i, true, diplomacy_state_player->Index);
621 CommandSharedVision(diplomacy_state_player->Index, true, i);
622 }
623 }
624 }
625 }
626
627 for (std::map<std::pair<CDate, int>, int>::iterator iterator = faction->HistoricalResources.begin(); iterator != faction->HistoricalResources.end(); ++iterator) { //set the appropriate historical resource quantities
628 if (iterator->first.first.Year == 0 || CurrentCampaign->StartDate.ContainsDate(iterator->first.first)) {
629 Players[i].SetResource(iterator->first.second, iterator->second);
630 }
631 }
632 }
633 }
634 }
635
636 if (CurrentCampaign->StartEffects) {
637 CurrentCampaign->StartEffects->pushPreamble();
638 CurrentCampaign->StartEffects->run();
639 }
640 }
641
642 //if the person player has no faction, bring up the faction choice interface
643 if (ThisPlayer && ThisPlayer->Faction == -1) {
644 char buf[256];
645 snprintf(buf, sizeof(buf), "if (ChooseFaction ~= nil) then ChooseFaction(\"%s\", \"%s\") end", ThisPlayer->Race != -1 ? PlayerRaces.Name[ThisPlayer->Race].c_str() : "", "");
646 CclCommand(buf);
647 }
648
649 if (!IsNetworkGame() && ThisPlayer && CurrentCustomHero != nullptr) {
650 Vec2i resPos;
651 FindNearestDrop(*CurrentCustomHero->Type, ThisPlayer->StartPos, resPos, LookingW, ThisPlayer->StartMapLayer);
652 CUnit *custom_hero = MakeUnitAndPlace(resPos, *CurrentCustomHero->Type, ThisPlayer, ThisPlayer->StartMapLayer);
653 custom_hero->SetCharacter(CurrentCustomHero->Ident, true);
654 }
655
656 //update the sold units of all units before starting, to make sure they fit the current conditions
657 for (CUnitManager::Iterator it = UnitManager.begin(); it != UnitManager.end(); ++it) {
658 CUnit *unit = *it;
659 if (unit && unit->IsAlive()) {
660 unit->UpdateSoldUnits();
661 }
662 }
663
664 if (CurrentQuest != nullptr && CurrentQuest->IntroductionDialogue != nullptr) {
665 CurrentQuest->IntroductionDialogue->Call(ThisPlayer->Index);
666 }
667 }
668 //Wyrmgus end
669
670 SingleGameLoop();
671
672 //
673 // Game over
674 //
675 if (GameResult == GameExit) {
676 Exit(0);
677 return;
678 }
679
680 #ifdef REALVIDEO
681 if (FastForwardCycle > GameCycle) {
682 VideoSyncSpeed = RealVideoSyncSpeed;
683 }
684 #endif
685 NetworkQuitGame();
686 EndReplayLog();
687
688 GameCycle = 0;//????
689 CDate::CurrentTotalHours = 0;
690 CParticleManager::exit();
691 FlagRevealMap = 0;
692 ReplayRevealMap = 0;
693 GamePaused = false;
694 GodMode = false;
695
696 SetCallbacks(old_callbacks);
697 }
698
699 //@}
700