1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 1998-2000, Matthes Bender
5 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18 /* A viewport to each player */
19
20 #include "C4Include.h"
21 #include "C4ForbidLibraryCompilation.h"
22 #include "game/C4Viewport.h"
23
24 #include "editor/C4Console.h"
25 #include "editor/C4ViewportWindow.h"
26 #include "game/C4FullScreen.h"
27 #include "game/C4GraphicsSystem.h"
28 #include "graphics/C4Draw.h"
29 #include "graphics/C4GraphicsResource.h"
30 #include "gui/C4GameMessage.h"
31 #include "gui/C4MouseControl.h"
32 #include "gui/C4ScriptGuiWindow.h"
33 #include "landscape/C4Landscape.h"
34 #include "landscape/C4PXS.h"
35 #include "landscape/C4Particles.h"
36 #include "landscape/C4Sky.h"
37 #include "landscape/fow/C4FoWRegion.h"
38 #include "lib/C4Stat.h"
39 #include "network/C4Network2.h"
40 #include "object/C4Def.h"
41 #include "object/C4GameObjects.h"
42 #include "object/C4Object.h"
43 #include "object/C4ObjectMenu.h"
44 #include "player/C4Player.h"
45 #include "player/C4PlayerList.h"
46
DropFile(const char * fileName,float x,float y)47 void C4Viewport::DropFile(const char* fileName, float x, float y)
48 {
49 Game.DropFile(fileName, GetViewX()+x/Zoom, GetViewY()+y/Zoom);
50 }
51
UpdateOutputSize(int32_t new_width,int32_t new_height)52 bool C4Viewport::UpdateOutputSize(int32_t new_width, int32_t new_height)
53 {
54 if (!pWindow) return false;
55 // Output size
56 C4Rect rect;
57 if (new_width)
58 {
59 rect.x = rect.y = 0;
60 rect.Wdt = new_width;
61 rect.Hgt = new_height;
62 }
63 else
64 {
65 #if defined(WITH_QT_EDITOR)
66 // Never query the window - size is always passed from Qt.
67 return false;
68 #else
69 if (!pWindow->GetSize(&rect)) return false;
70 #endif
71 }
72 OutX=rect.x; OutY=rect.y;
73 ViewWdt=rect.Wdt; ViewHgt=rect.Hgt;
74 ScrollView(0,0);
75 // Scroll bars
76 ScrollBarsByViewPosition();
77 // Reset menus
78 ResetMenuPositions=true;
79 // update internal GL size
80 if (pWindow && pWindow->pSurface)
81 pWindow->pSurface->UpdateSize(ViewWdt, ViewHgt);
82 // Update zoom limits based on new size
83 C4Player *plr = ::Players.Get(Player);
84 if (plr) plr->ZoomLimitsToViewport(this);
85 // Done
86 return true;
87 }
88
C4Viewport()89 C4Viewport::C4Viewport()
90 {
91 Player = 0;
92 viewX = viewY = 0;
93 targetViewX = targetViewY = 0;
94 ViewWdt = ViewHgt = 0;
95 BorderLeft = BorderTop = BorderRight = BorderBottom = 0;
96 OutX = OutY = ViewWdt = ViewHgt = 0;
97 DrawX = DrawY = 0;
98 Zoom = 1.0;
99 ZoomTarget = 0.0;
100 ViewportOpenFrame = 0;
101 ZoomLimitMin = ZoomLimitMax = 0; // no limit
102 Next = nullptr;
103 PlayerLock = true;
104 ResetMenuPositions = false;
105 viewOffsX = viewOffsY = 0;
106 fIsNoOwnerViewport = false;
107 }
108
~C4Viewport()109 C4Viewport::~C4Viewport()
110 {
111 DisableFoW();
112 if (pWindow) { delete pWindow->pSurface; pWindow->Clear(); }
113 }
114
DrawOverlay(C4TargetFacet & cgo,const ZoomData & GameZoom)115 void C4Viewport::DrawOverlay(C4TargetFacet &cgo, const ZoomData &GameZoom)
116 {
117 if (!Game.C4S.Head.Film || !Game.C4S.Head.Replay)
118 {
119 // Player info
120 C4ST_STARTNEW(PInfoStat, "C4Viewport::DrawOverlay: Player Info")
121 DrawPlayerInfo(cgo);
122 C4ST_STOP(PInfoStat)
123 C4ST_STARTNEW(MenuStat, "C4Viewport::DrawOverlay: Menu")
124 DrawMenu(cgo);
125 C4ST_STOP(MenuStat)
126 }
127
128 // Control overlays (if not film/replay)
129 if (!Game.C4S.Head.Film || !Game.C4S.Head.Replay)
130 {
131 // Mouse control
132 if (::MouseControl.IsViewport(this))
133 {
134 C4ST_STARTNEW(MouseStat, "C4Viewport::DrawOverlay: Mouse")
135 ::MouseControl.Draw(cgo, GameZoom);
136 // Draw GUI-mouse in EM if active
137 if (pWindow) ::pGUI->RenderMouse(cgo);
138 C4ST_STOP(MouseStat)
139 }
140 }
141 }
142
DrawMenu(C4TargetFacet & cgo0)143 void C4Viewport::DrawMenu(C4TargetFacet &cgo0)
144 {
145 // Get player
146 C4Player *pPlr = ::Players.Get(Player);
147
148 // for menus, cgo is using GUI-syntax: TargetX/Y marks the drawing offset; x/y/Wdt/Hgt marks the offset rect
149 C4TargetFacet cgo; cgo.Set(cgo0);
150 cgo.X = 0; cgo.Y = 0;
151 cgo.Wdt = cgo0.Wdt * cgo0.Zoom; cgo.Hgt = cgo0.Hgt * cgo0.Zoom;
152 cgo.TargetX = float(cgo0.X); cgo.TargetY = float(cgo0.Y);
153 cgo.Zoom = 1;
154 pDraw->SetZoom(cgo.X, cgo.Y, cgo.Zoom);
155
156 // Player eliminated
157 if (pPlr && pPlr->Eliminated)
158 {
159 pDraw->TextOut(FormatString(LoadResStr(pPlr->Surrendered ? "IDS_PLR_SURRENDERED" : "IDS_PLR_ELIMINATED"),pPlr->GetName()).getData(),
160 ::GraphicsResource.FontRegular, 1.0, cgo.Surface,cgo.TargetX+cgo.Wdt/2,cgo.TargetY+2*cgo.Hgt/3,0xfaFF0000,ACenter);
161 return;
162 }
163
164 // Player cursor object menu
165 if (pPlr && pPlr->Cursor && pPlr->Cursor->Menu)
166 {
167 if (ResetMenuPositions) pPlr->Cursor->Menu->ResetLocation();
168 // if mouse is dragging, make it transparent to easy construction site drag+drop
169 bool fDragging=false;
170 if (::MouseControl.IsDragging() && ::MouseControl.IsViewport(this))
171 {
172 fDragging = true;
173 pDraw->ActivateBlitModulation(0x4fffffff);
174 }
175 // draw menu
176 pPlr->Cursor->Menu->Draw(cgo);
177 // reset modulation for dragging
178 if (fDragging) pDraw->DeactivateBlitModulation();
179 }
180 // Player menu
181 if (pPlr && pPlr->Menu.IsActive())
182 {
183 if (ResetMenuPositions) pPlr->Menu.ResetLocation();
184 pPlr->Menu.Draw(cgo);
185 }
186 // Fullscreen menu
187 if (FullScreen.pMenu && FullScreen.pMenu->IsActive())
188 {
189 if (ResetMenuPositions) FullScreen.pMenu->ResetLocation();
190 FullScreen.pMenu->Draw(cgo);
191 }
192
193 // Flag done
194 ResetMenuPositions=false;
195
196 // restore Zoom
197 pDraw->SetZoom(cgo0.X, cgo0.Y, cgo0.Zoom);
198 }
199
Draw(C4TargetFacet & cgo0,bool fDrawGame,bool fDrawOverlay)200 void C4Viewport::Draw(C4TargetFacet &cgo0, bool fDrawGame, bool fDrawOverlay)
201 {
202 #ifdef USE_CONSOLE
203 // No drawing in console mode
204 return;
205 #endif
206 C4TargetFacet cgo; cgo.Set(cgo0);
207 ZoomData GameZoom;
208 GameZoom.X = cgo.X; GameZoom.Y = cgo.Y;
209 GameZoom.Zoom = cgo.Zoom;
210
211 // Draw landscape borders
212 if (BorderLeft > 0.0f) pDraw->BlitSurfaceTile(::GraphicsResource.fctBackground.Surface, cgo.Surface, DrawX, DrawY, BorderLeft, ViewHgt, -DrawX, -DrawY, nullptr);
213 if (BorderTop > 0.0f) pDraw->BlitSurfaceTile(::GraphicsResource.fctBackground.Surface, cgo.Surface, DrawX + BorderLeft, DrawY, ViewWdt - BorderLeft - BorderRight, BorderTop, -DrawX - BorderLeft, -DrawY, nullptr);
214 if (BorderRight > 0.0f) pDraw->BlitSurfaceTile(::GraphicsResource.fctBackground.Surface, cgo.Surface, DrawX + ViewWdt - BorderRight, DrawY, BorderRight, ViewHgt, -DrawX - ViewWdt + BorderRight, -DrawY, nullptr);
215 if (BorderBottom > 0.0f) pDraw->BlitSurfaceTile(::GraphicsResource.fctBackground.Surface, cgo.Surface, DrawX + BorderLeft, DrawY + ViewHgt - BorderBottom, ViewWdt - BorderLeft - BorderRight, BorderBottom, -DrawX - BorderLeft, -DrawY - ViewHgt + BorderBottom, nullptr);
216
217 // Compute non-bordered viewport area
218 cgo.X += BorderLeft; cgo.Y += BorderTop; cgo.Wdt -= (BorderLeft + BorderRight) / cgo.Zoom; cgo.Hgt -= (BorderTop + BorderBottom) / cgo.Zoom;
219 cgo.TargetX += BorderLeft / Zoom; cgo.TargetY += BorderTop / Zoom;
220
221 // Apply Zoom
222 GameZoom.X = cgo.X; GameZoom.Y = cgo.Y;
223 pDraw->SetZoom(GameZoom);
224 // Set clipper to integer bounds around floating point viewport region
225 const FLOAT_RECT clipRect = { DrawX + BorderLeft, DrawX + ViewWdt - BorderRight, DrawY + BorderTop, DrawY + ViewHgt - BorderBottom };
226 const C4Rect& clipRectInt(clipRect);
227 pDraw->SetPrimaryClipper(clipRectInt.x, clipRectInt.y, clipRectInt.x + clipRectInt.Wdt - 1, clipRectInt.y + clipRectInt.Hgt - 1);
228
229 last_game_draw_cgo = cgo;
230
231 if (fDrawGame)
232 {
233 // --- activate FoW here ---
234
235 // Render FoW only if active for player
236 C4Player *pPlr = ::Players.Get(Player);
237 C4FoWRegion* pFoW = nullptr;
238 if (pPlr && pPlr->fFogOfWar) pFoW = this->pFoW.get();
239
240 // Update FoW
241 if (pFoW)
242 {
243 // Viewport region in landscape coordinates
244 const FLOAT_RECT vpRect = { cgo.TargetX, cgo.TargetX + cgo.Wdt, cgo.TargetY, cgo.TargetY + cgo.Hgt };
245 // Region in which the light is calculated
246 // At the moment, just choose integer coordinates to surround the viewport
247 const C4Rect lightRect(vpRect);
248 if (!lightRect.Wdt || !lightRect.Hgt)
249 {
250 // Do not bother initializing FoW on empty region; would cause errors in drawing proc
251 pFoW = nullptr;
252 }
253 else
254 {
255 pFoW->Update(lightRect, vpRect);
256
257 if (!pFoW->Render())
258 {
259 // If FoW init fails, do not set it for further drawing
260 pFoW = nullptr;
261 }
262 }
263 }
264
265 pDraw->SetFoW(pFoW);
266
267 C4ST_STARTNEW(SkyStat, "C4Viewport::Draw: Sky")
268 ::Landscape.GetSky().Draw(cgo);
269 C4ST_STOP(SkyStat)
270
271 ::Objects.Draw(cgo, Player, -2147483647 - 1 /* INT32_MIN */, 0);
272
273 // Draw Landscape
274 C4ST_STARTNEW(LandStat, "C4Viewport::Draw: Landscape")
275 ::Landscape.Draw(cgo, pFoW);
276 C4ST_STOP(LandStat)
277
278 // draw PXS (unclipped!)
279 C4ST_STARTNEW(PXSStat, "C4Viewport::Draw: PXS")
280 ::PXS.Draw(cgo);
281 C4ST_STOP(PXSStat)
282
283 // Draw objects which are behind the particle plane.
284 const int particlePlane = 900;
285 C4ST_STARTNEW(ObjStat, "C4Viewport::Draw: Objects (1)")
286 ::Objects.Draw(cgo, Player, 1, particlePlane);
287 C4ST_STOP(ObjStat)
288
289 // Draw global dynamic particles on a specific Plane
290 // to enable scripters to put objects both behind and in front of particles.
291 C4ST_STARTNEW(PartStat, "C4Viewport::Draw: Dynamic Particles")
292 ::Particles.DrawGlobalParticles(cgo);
293 C4ST_STOP(PartStat)
294
295 // Now the remaining objects in front of the particles (e.g. GUI elements)
296 C4ST_STARTNEW(Obj2Stat, "C4Viewport::Draw: Objects (2)")
297 ::Objects.Draw(cgo, Player, particlePlane + 1, 2147483647 /* INT32_MAX */);
298 C4ST_STOP(Obj2Stat)
299
300 // Draw everything else without FoW
301 pDraw->SetFoW(nullptr);
302 }
303 else
304 {
305 pDraw->DrawBoxDw(cgo.Surface, cgo.X, cgo.Y, cgo.X + cgo.Wdt, cgo.Y + cgo.Hgt, 0xff000000);
306 }
307
308 // Draw PathFinder
309 if (::GraphicsSystem.ShowPathfinder) Game.PathFinder.Draw(cgo);
310
311 // Draw overlay
312 if (!Game.C4S.Head.Film || !Game.C4S.Head.Replay) Game.DrawCrewOverheadText(cgo, Player);
313
314 // Lights overlay
315 if (::GraphicsSystem.ShowLights && pFoW) pFoW->Render(&cgo);
316
317 if (fDrawOverlay)
318 {
319 // Determine zoom of overlay
320 float fGUIZoom = GetGUIZoom();
321 // now restore complete cgo range for overlay drawing
322 pDraw->SetZoom(DrawX,DrawY, fGUIZoom);
323 pDraw->SetPrimaryClipper(DrawX,DrawY,DrawX+(ViewWdt-1),DrawY+(ViewHgt-1));
324 C4TargetFacet gui_cgo;
325 gui_cgo.Set(cgo0);
326
327 gui_cgo.X = DrawX; gui_cgo.Y = DrawY; gui_cgo.Zoom = fGUIZoom;
328 gui_cgo.Wdt = int(float(ViewWdt)/fGUIZoom); gui_cgo.Hgt = int(float(ViewHgt)/fGUIZoom);
329 gui_cgo.TargetX = GetViewX(); gui_cgo.TargetY = GetViewY();
330
331 last_gui_draw_cgo = gui_cgo;
332
333 // draw custom GUI objects
334 ::Objects.ForeObjects.DrawIfCategory(gui_cgo, Player, C4D_Foreground, false);
335
336 // Draw overlay
337 C4ST_STARTNEW(OvrStat, "C4Viewport::Draw: Overlay")
338
339 if (Application.isEditor) ::Console.EditCursor.Draw(cgo);
340
341 // Game messages
342 C4ST_STARTNEW(MsgStat, "C4Viewport::DrawOverlay: Messages")
343 pDraw->SetZoom(0, 0, 1.0);
344 ::Messages.Draw(gui_cgo, cgo, Player);
345 C4ST_STOP(MsgStat)
346
347 // ingame menus
348 C4ST_STARTNEW(GuiWindowStat, "C4Viewport::DrawOverlay: Menus")
349 pDraw->SetZoom(0, 0, 1.0);
350 ::Game.ScriptGuiRoot->DrawAll(gui_cgo, Player);
351 C4ST_STOP(GuiWindowStat)
352
353 DrawOverlay(gui_cgo, GameZoom);
354
355 // Netstats
356 if (::GraphicsSystem.ShowNetstatus)
357 ::Network.DrawStatus(gui_cgo);
358
359 C4ST_STOP(OvrStat)
360
361 }
362
363 // Remove zoom n clippers
364 pDraw->SetZoom(0, 0, 1.0);
365 pDraw->NoPrimaryClipper();
366
367 }
368
BlitOutput()369 void C4Viewport::BlitOutput()
370 {
371 if (pWindow)
372 {
373 C4Rect rtSrc,rtDst;
374 rtSrc.x = DrawX; rtSrc.y = DrawY; rtSrc.Wdt = ViewWdt; rtSrc.Hgt = ViewHgt;
375 rtDst.x = OutX; rtDst.y = OutY; rtDst.Wdt = ViewWdt; rtDst.Hgt = ViewHgt;
376 pWindow->pSurface->PageFlip(&rtSrc, &rtDst);
377 }
378 }
379
Execute()380 void C4Viewport::Execute()
381 {
382 // Adjust position
383 AdjustZoomAndPosition();
384 // Current graphics output
385 C4TargetFacet cgo;
386 C4Surface *target = pWindow ? pWindow->pSurface : FullScreen.pSurface;
387 cgo.Set(target,DrawX,DrawY,float(ViewWdt)/Zoom,float(ViewHgt)/Zoom,GetViewX(),GetViewY(),Zoom);
388 pDraw->PrepareRendering(target);
389 // Load script uniforms from Global.Uniforms
390 auto uniform_pop = pDraw->scriptUniform.Push(::GameScript.ScenPropList.getPropList());
391 // Do not spoil game contents on owner-less viewport
392 bool draw_game = true;
393 if (Player == NO_OWNER)
394 if (!::Application.isEditor && !::Game.DebugMode)
395 if (!::Network.isEnabled() || !::Network.Clients.GetLocal() || !::Network.Clients.GetLocal()->isObserver())
396 if (::Game.PlayerInfos.GetJoinIssuedPlayerCount() > 0) // free scrolling allowed if the scenario was started explicitely without players to inspect the landscape
397 if (Game.C4S.Landscape.Secret)
398 draw_game = false;
399 // Draw
400 Draw(cgo, draw_game, true);
401 // Blit output
402 BlitOutput();
403 }
404
405 /* This method is called whenever the viewport size is changed. Thus, its job
406 is to recalculate the zoom and zoom limits with the new values for ViewWdt
407 and ViewHgt. */
CalculateZoom()408 void C4Viewport::CalculateZoom()
409 {
410 // Zoom is only initialized by player or global setting during viewport creation time, because after that
411 // the player may have changed to another preferred zoom.
412 // However, viewports may change multiple times during startup (because of NO_OWNER viewport being deleted
413 // and possible other player joins). So check by frame counter. Zoom changes done in paused mode on the
414 // player init frame will be lost, but that should not be a problem.
415 if(ViewportOpenFrame >= Game.FrameCounter)
416 InitZoom();
417
418 C4Player *plr = Players.Get(Player);
419 if (plr)
420 plr->ZoomLimitsToViewport(this);
421 else
422 SetZoomLimits(0.8*std::min<float>(float(ViewWdt)/::Landscape.GetWidth(),float(ViewHgt)/::Landscape.GetHeight()), 8);
423
424 }
425
InitZoom()426 void C4Viewport::InitZoom()
427 {
428 C4Player *plr = Players.Get(Player);
429 if (plr)
430 {
431 plr->ZoomToViewport(this, true);
432 }
433 else
434 {
435 ZoomTarget = std::max<float>(float(ViewWdt)/::Landscape.GetWidth(), 1.0f);
436 Zoom = ZoomTarget;
437 }
438 }
439
ChangeZoom(float by_factor)440 void C4Viewport::ChangeZoom(float by_factor)
441 {
442 ZoomTarget *= by_factor;
443 if (ZoomLimitMin && ZoomTarget < ZoomLimitMin) ZoomTarget = ZoomLimitMin;
444 if (ZoomLimitMax && ZoomTarget > ZoomLimitMax) ZoomTarget = ZoomLimitMax;
445 }
446
SetZoom(float to_value,bool direct)447 void C4Viewport::SetZoom(float to_value, bool direct)
448 {
449 ZoomTarget = to_value;
450 if (Player != NO_OWNER || !::Application.isEditor)
451 {
452 if (ZoomLimitMin && ZoomTarget < ZoomLimitMin) ZoomTarget = ZoomLimitMin;
453 if (ZoomLimitMax && ZoomTarget > ZoomLimitMax) ZoomTarget = ZoomLimitMax;
454 }
455 // direct: Set zoom without scrolling to it
456 if (direct) Zoom = ZoomTarget;
457 }
458
SetZoomLimits(float to_min_zoom,float to_max_zoom)459 void C4Viewport::SetZoomLimits(float to_min_zoom, float to_max_zoom)
460 {
461 ZoomLimitMin = to_min_zoom;
462 ZoomLimitMax = to_max_zoom;
463 if (ZoomLimitMax && ZoomLimitMax < ZoomLimitMin) ZoomLimitMax = ZoomLimitMin;
464 ChangeZoom(1); // Constrains zoom to limit.
465 }
466
GetZoomByViewRange(int32_t size_x,int32_t size_y) const467 float C4Viewport::GetZoomByViewRange(int32_t size_x, int32_t size_y) const
468 {
469 // set zoom such that both size_x and size_y are guarantueed to fit into the viewport range
470 // determine whether zoom is to be calculated by x or by y
471 bool zoom_by_y = false;
472 if (size_x && size_y)
473 {
474 zoom_by_y = (size_y * ViewWdt > size_x * ViewHgt);
475 }
476 else if (size_y)
477 {
478 // no x size passed - zoom by y
479 zoom_by_y = true;
480 }
481 else
482 {
483 // 0/0 size passed - zoom to default
484 if (!size_x)
485 size_x = C4VP_DefViewRangeX * 2;
486 zoom_by_y = false;
487 }
488 // zoom calculation
489 if (zoom_by_y)
490 return float(ViewHgt) / size_y;
491 else
492 return float(ViewWdt) / size_x;
493 }
494
SetZoom(float zoomValue)495 void C4Viewport::SetZoom(float zoomValue)
496 {
497 Zoom = zoomValue;
498 // also set target to prevent zoom from changing back
499 ZoomTarget = zoomValue;
500 }
501
AdjustZoomAndPosition()502 void C4Viewport::AdjustZoomAndPosition()
503 {
504 // Move zoom towards target zoom
505 if (ZoomTarget < 0.000001f) CalculateZoom();
506 // Change Zoom
507
508 if (Zoom != ZoomTarget)
509 {
510 float DeltaZoom = Zoom / ZoomTarget;
511 if (DeltaZoom<1) DeltaZoom = 1 / DeltaZoom;
512
513 // Minimal Zoom change factor
514 static const float Z0 = pow(C4GFX_ZoomStep, 1.0f / 8.0f);
515
516 // We change zoom based on (logarithmic) distance of current zoom
517 // to target zoom. The greater the distance the more we adjust the
518 // zoom in one frame. There is a minimal zoom change Z0 to make sure
519 // we reach ZoomTarget in finite time.
520 float ZoomAdjustFactor = Z0 * pow(DeltaZoom, 1.0f / 8.0f);
521
522 if (Zoom == 0)
523 Zoom = ZoomTarget;
524 else
525 {
526 // Remember old viewport center
527 float view_mid_x = this->viewX + float(this->ViewWdt) / Zoom / 2.0f;
528 float view_mid_y = this->viewY + float(this->ViewHgt) / Zoom / 2.0f;
529
530 if (Zoom < ZoomTarget)
531 Zoom = std::min(Zoom * ZoomAdjustFactor, ZoomTarget);
532 if (Zoom > ZoomTarget)
533 Zoom = std::max(Zoom / ZoomAdjustFactor, ZoomTarget);
534
535 // Restore new viewport center
536 this->viewX = view_mid_x - float(this->ViewWdt) / Zoom / 2.0f;
537 this->viewY = view_mid_y - float(this->ViewHgt) / Zoom / 2.0f;
538 }
539 }
540 // Adjust position
541 AdjustPosition(false);
542 }
543
AdjustPosition(bool immediate)544 void C4Viewport::AdjustPosition(bool immediate)
545 {
546 if (ViewWdt == 0 || ViewHgt == 0)
547 {
548 // zero-sized viewport, possibly minimized editor window
549 // don't do anything then
550 return;
551 }
552
553 assert(Zoom>0);
554 assert(ZoomTarget>0);
555
556 float ViewportScrollBorder = fIsNoOwnerViewport ? 0 : float(C4ViewportScrollBorder);
557 C4Player *pPlr = ::Players.Get(Player);
558
559 // View position
560 if (PlayerLock && ValidPlr(Player))
561 {
562 float scrollRange, extraBoundsX, extraBoundsY;
563
564 scrollRange = extraBoundsX = extraBoundsY = 0;
565
566 // target view position (landscape coordinates)
567 float targetCenterViewX = fixtof(pPlr->ViewX);
568 float targetCenterViewY = fixtof(pPlr->ViewY);
569
570 if (pPlr->ViewMode == C4PVM_Scrolling)
571 {
572 extraBoundsX = extraBoundsY = ViewportScrollBorder;
573 }
574 else
575 {
576 scrollRange = std::min(ViewWdt/(10*Zoom),ViewHgt/(10*Zoom));
577
578 // if view is close to border, allow scrolling
579 if (targetCenterViewX < ViewportScrollBorder) extraBoundsX = std::min<float>(ViewportScrollBorder - targetCenterViewX, ViewportScrollBorder);
580 else if (targetCenterViewX >= ::Landscape.GetWidth() - ViewportScrollBorder) extraBoundsX = std::min<float>(targetCenterViewX - ::Landscape.GetWidth(), 0) + ViewportScrollBorder;
581 if (targetCenterViewY < ViewportScrollBorder) extraBoundsY = std::min<float>(ViewportScrollBorder - targetCenterViewY, ViewportScrollBorder);
582 else if (targetCenterViewY >= ::Landscape.GetHeight() - ViewportScrollBorder) extraBoundsY = std::min<float>(targetCenterViewY - ::Landscape.GetHeight(), 0) + ViewportScrollBorder;
583 }
584
585 extraBoundsX = std::max(extraBoundsX, (ViewWdt/Zoom - ::Landscape.GetWidth())/2 + 1);
586 extraBoundsY = std::max(extraBoundsY, (ViewHgt/Zoom - ::Landscape.GetHeight())/2 + 1);
587
588 // add mouse auto scroll
589 if (pPlr->MouseControl && ::MouseControl.InitCentered && Config.Controls.MouseAutoScroll)
590 {
591 float strength = Config.Controls.MouseAutoScroll/100.0f;
592 targetCenterViewX += strength*(::MouseControl.VpX - ViewWdt/2.0f)/Zoom;
593 targetCenterViewY += strength*(::MouseControl.VpY - ViewHgt/2.0f)/Zoom;
594 }
595
596 // scroll range
597 if (!immediate)
598 {
599 targetCenterViewX = Clamp(targetCenterViewX, targetCenterViewX - scrollRange, targetCenterViewX + scrollRange);
600 targetCenterViewY = Clamp(targetCenterViewY, targetCenterViewY - scrollRange, targetCenterViewY + scrollRange);
601 }
602 // bounds
603 targetCenterViewX = Clamp(targetCenterViewX, ViewWdt/Zoom/2 - extraBoundsX, ::Landscape.GetWidth() - ViewWdt/Zoom/2 + extraBoundsX);
604 targetCenterViewY = Clamp(targetCenterViewY, ViewHgt/Zoom/2 - extraBoundsY, ::Landscape.GetHeight() - ViewHgt/Zoom/2 + extraBoundsY);
605
606 targetViewX = targetCenterViewX - ViewWdt/Zoom/2 + viewOffsX;
607 targetViewY = targetCenterViewY - ViewHgt/Zoom/2 + viewOffsY;
608
609 if (immediate)
610 {
611 // immediate scroll
612 SetViewX(targetViewX);
613 SetViewY(targetViewY);
614 }
615 else
616 {
617 // smooth scroll
618 int32_t smooth = Clamp<int32_t>(Config.General.ScrollSmooth, 1, 50);
619 ScrollView((targetViewX - viewX) / smooth, (targetViewY - viewY) / smooth);
620 }
621 }
622
623 UpdateBordersX();
624 UpdateBordersY();
625
626 // NO_OWNER can't scroll
627 if (fIsNoOwnerViewport) { viewOffsX=0; viewOffsY=0; }
628 }
629
CenterPosition()630 void C4Viewport::CenterPosition()
631 {
632 // center viewport position on map
633 // set center position
634 SetViewX(::Landscape.GetWidth()/2 + ViewWdt/Zoom/2);
635 SetViewY(::Landscape.GetHeight()/2 + ViewHgt/Zoom/2);
636 }
637
UpdateBordersX()638 void C4Viewport::UpdateBordersX()
639 {
640 BorderLeft = std::max(-GetViewX() * Zoom, 0.0f);
641 BorderRight = std::max(ViewWdt - ::Landscape.GetWidth() * Zoom + GetViewX() * Zoom, 0.0f);
642 }
643
UpdateBordersY()644 void C4Viewport::UpdateBordersY()
645 {
646 BorderTop = std::max(-GetViewY() * Zoom, 0.0f);
647 BorderBottom = std::max(ViewHgt - ::Landscape.GetHeight() * Zoom + GetViewY() * Zoom, 0.0f);
648 }
649
DrawPlayerInfo(C4TargetFacet & cgo)650 void C4Viewport::DrawPlayerInfo(C4TargetFacet &cgo)
651 {
652 C4Facet ccgo;
653 if (!ValidPlr(Player)) return;
654 // Controls
655 DrawPlayerStartup(cgo);
656 }
657
Init(int32_t iPlayer,bool fSetTempOnly)658 bool C4Viewport::Init(int32_t iPlayer, bool fSetTempOnly)
659 {
660 // Fullscreen viewport initialization
661 // Set Player
662 if (!ValidPlr(iPlayer)) iPlayer = NO_OWNER;
663 Player=iPlayer;
664 ViewportOpenFrame = Game.FrameCounter;
665 if (!fSetTempOnly) fIsNoOwnerViewport = (iPlayer == NO_OWNER);
666 if (Application.isEditor)
667 {
668 // Console viewport initialization
669 // Create window
670 pWindow = std::make_unique<C4ViewportWindow>(this);
671 if (!pWindow->Init(Player))
672 return false;
673 UpdateOutputSize();
674 // Disable player lock on unowned viewports
675 if (!ValidPlr(Player)) TogglePlayerLock();
676 // Don't call Execute right away since it is not yet guaranteed that
677 // the Player has set this as its Viewport, and the drawing routines rely
678 // on that.
679 }
680 else
681 {
682 // Owned viewport: clear any flash message explaining observer menu
683 if (ValidPlr(iPlayer)) ::GraphicsSystem.FlashMessage("");
684 }
685
686 EnableFoW();
687 return true;
688 }
689
DisableFoW()690 void C4Viewport::DisableFoW()
691 {
692 pFoW.reset();
693 }
694
EnableFoW()695 void C4Viewport::EnableFoW()
696 {
697 if (::Landscape.HasFoW() && Player != NO_OWNER)
698 {
699 pFoW = std::make_unique<C4FoWRegion>(::Landscape.GetFoW(), ::Players.Get(Player));
700 }
701 else
702 {
703 DisableFoW();
704 }
705 }
706
707 extern int32_t DrawMessageOffset;
708
DrawPlayerStartup(C4TargetFacet & cgo)709 void C4Viewport::DrawPlayerStartup(C4TargetFacet &cgo)
710 {
711 C4Player *pPlr;
712 if (!(pPlr = ::Players.Get(Player))) return;
713 if (!pPlr->LocalControl || !pPlr->ShowStartup) return;
714 int32_t iNameHgtOff=0;
715
716 // Control
717 // unnecessary with the current control sets
718 if (pPlr && pPlr->ControlSet)
719 {
720 C4Facet controlset_facet = pPlr->ControlSet->GetPicture();
721 if (controlset_facet.Wdt) controlset_facet.Draw(cgo.Surface,
722 cgo.X+(cgo.Wdt-controlset_facet.Wdt)/2,
723 cgo.Y+cgo.Hgt * 2/3 + DrawMessageOffset,
724 0,0);
725 iNameHgtOff=GfxR->fctKeyboard.Hgt;
726 }
727
728 // Name
729 pDraw->TextOut(pPlr->GetName(), ::GraphicsResource.FontRegular, 1.0, cgo.Surface,
730 cgo.X+cgo.Wdt/2,cgo.Y+cgo.Hgt*2/3+iNameHgtOff + DrawMessageOffset,
731 pPlr->ColorDw | 0xff000000, ACenter);
732 }
733
ScrollView(float byX,float byY)734 void C4Viewport::ScrollView(float byX, float byY)
735 {
736 SetViewX(viewX + byX);
737 SetViewY(viewY + byY);
738 }
739
SetViewX(float x)740 void C4Viewport::SetViewX(float x)
741 {
742 viewX = x;
743
744 if (fIsNoOwnerViewport)
745 {
746 if(::Landscape.GetWidth() < ViewWdt / Zoom)
747 {
748 viewX = ::Landscape.GetWidth()/2 - ViewWdt / Zoom / 2;
749 }
750 else
751 {
752 viewX = Clamp(x, 0.0f, ::Landscape.GetWidth() - ViewWdt / Zoom);
753 }
754 }
755
756 UpdateBordersX();
757 }
758
SetViewY(float y)759 void C4Viewport::SetViewY(float y)
760 {
761 viewY = y;
762
763 if (fIsNoOwnerViewport)
764 {
765 if(::Landscape.GetHeight() < ViewHgt / Zoom)
766 {
767 viewY = ::Landscape.GetHeight()/2 - ViewHgt / Zoom / 2;
768 }
769 else
770 {
771 viewY = Clamp(y, 0.0f, ::Landscape.GetHeight() - ViewHgt / Zoom);
772 }
773 }
774
775 UpdateBordersY();
776 }
777
SetOutputSize(int32_t iDrawX,int32_t iDrawY,int32_t iOutX,int32_t iOutY,int32_t iOutWdt,int32_t iOutHgt)778 void C4Viewport::SetOutputSize(int32_t iDrawX, int32_t iDrawY, int32_t iOutX, int32_t iOutY, int32_t iOutWdt, int32_t iOutHgt)
779 {
780 int32_t deltaWidth = ViewWdt-iOutWdt;
781 int32_t deltaHeight = ViewHgt-iOutHgt;
782 // update output parameters
783 DrawX=iDrawX; DrawY=iDrawY;
784 OutX=iOutX; OutY=iOutY;
785 ViewWdt=iOutWdt; ViewHgt=iOutHgt;
786 // update view position: Remain centered at previous position
787 // scrolling the view must be done after setting the new view width and height
788 ScrollView(deltaWidth/2, deltaHeight/2);
789 CalculateZoom();
790 // Reset menus
791 ResetMenuPositions=true;
792 // player uses mouse control? then clip the cursor
793 C4Player *pPlr;
794 if ((pPlr=::Players.Get(Player)))
795 if (pPlr->MouseControl)
796 {
797 ::MouseControl.UpdateClip();
798 // and inform GUI
799 ::pGUI->SetPreferredDlgRect(C4Rect(iOutX, iOutY, iOutWdt, iOutHgt));
800 }
801 }
802
ClearPointers(C4Object * pObj)803 void C4Viewport::ClearPointers(C4Object *pObj)
804 {
805
806 }
807
NextPlayer()808 void C4Viewport::NextPlayer()
809 {
810 C4Player *pPlr; int32_t iPlr;
811 if (!(pPlr = ::Players.Get(Player)))
812 {
813 if (!(pPlr = ::Players.First)) return;
814 }
815 else if (!(pPlr = pPlr->Next))
816 if (Game.C4S.Head.Film && Game.C4S.Head.Replay)
817 pPlr = ::Players.First; // cycle to first in film mode only; in network obs mode allow NO_OWNER-view
818 if (pPlr) iPlr = pPlr->Number; else iPlr = NO_OWNER;
819 if (iPlr != Player) Init(iPlr, true);
820 }
821
IsViewportMenu(class C4Menu * pMenu)822 bool C4Viewport::IsViewportMenu(class C4Menu *pMenu)
823 {
824 // check all associated menus
825 // Get player
826 C4Player *pPlr = ::Players.Get(Player);
827 // Player eliminated: No menu
828 if (pPlr && pPlr->Eliminated) return false;
829 // Player cursor object menu
830 if (pPlr && pPlr->Cursor && pPlr->Cursor->Menu == pMenu) return true;
831 // Player menu
832 if (pPlr && pPlr->Menu.IsActive() && &(pPlr->Menu) == pMenu) return true;
833 // Fullscreen menu (if active, only one viewport can exist)
834 if (FullScreen.pMenu && FullScreen.pMenu->IsActive() && FullScreen.pMenu == pMenu) return true;
835 // no match
836 return false;
837 }
838
839 // C4ViewportList
840
841 C4ViewportList Viewports;
842
C4ViewportList()843 C4ViewportList::C4ViewportList()
844 {
845 ViewportArea.Default();
846 }
847 C4ViewportList::~C4ViewportList() = default;
Clear()848 void C4ViewportList::Clear()
849 {
850 C4Viewport *next;
851 while (FirstViewport)
852 {
853 next=FirstViewport->Next;
854 delete FirstViewport;
855 FirstViewport=next;
856 }
857 FirstViewport=nullptr;
858 }
859
Execute(bool DrawBackground)860 void C4ViewportList::Execute(bool DrawBackground)
861 {
862 // Background redraw
863 if (DrawBackground)
864 DrawFullscreenBackground();
865 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
866 {
867 if (cvp->GetWindow())
868 cvp->GetWindow()->RequestUpdate();
869 else
870 cvp->Execute();
871 }
872 }
873
DrawFullscreenBackground()874 void C4ViewportList::DrawFullscreenBackground()
875 {
876 for (int i=0, iNum=BackgroundAreas.GetCount(); i<iNum; ++i)
877 {
878 const C4Rect &rc = BackgroundAreas.Get(i);
879 pDraw->BlitSurfaceTile(::GraphicsResource.fctBackground.Surface,FullScreen.pSurface,rc.x,rc.y,rc.Wdt,rc.Hgt,-rc.x,-rc.y, nullptr);
880 }
881 }
882
CloseViewport(C4Viewport * cvp)883 bool C4ViewportList::CloseViewport(C4Viewport * cvp)
884 {
885 if (!cvp) return false;
886 // Chop the start of the chain off
887 if (FirstViewport == cvp)
888 {
889 FirstViewport = cvp->Next;
890 delete cvp;
891 StartSoundEffect("UI::CloseViewport");
892 }
893 // Take out of the chain
894 else for (C4Viewport * prev = FirstViewport; prev; prev = prev->Next)
895 {
896 if (prev->Next == cvp)
897 {
898 prev->Next = cvp->Next;
899 delete cvp;
900 StartSoundEffect("UI::CloseViewport");
901 }
902 }
903 // Recalculate viewports
904 RecalculateViewports();
905 // Done
906 return true;
907 }
908 #ifdef USE_WIN32_WINDOWS
GetViewport(HWND hwnd)909 C4Viewport* C4ViewportList::GetViewport(HWND hwnd)
910 {
911 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
912 if (cvp->pWindow->hWindow==hwnd)
913 return cvp;
914 return nullptr;
915 }
916 #endif
CreateViewport(int32_t iPlayer,bool fSilent)917 bool C4ViewportList::CreateViewport(int32_t iPlayer, bool fSilent)
918 {
919 // Create and init new viewport, add to viewport list
920 int32_t iLastCount = GetViewportCount();
921 C4Viewport *nvp = new C4Viewport;
922 bool fOkay = nvp->Init(iPlayer, false);
923 if (!fOkay) { delete nvp; return false; }
924 C4Viewport *pLast;
925 for (pLast=FirstViewport; pLast && pLast->Next; pLast=pLast->Next) {}
926 if (pLast) pLast->Next=nvp; else FirstViewport=nvp;
927 // Recalculate viewports
928 RecalculateViewports();
929 // Viewports start off at centered position
930 nvp->CenterPosition();
931 // Initial player zoom values to viewport (in case they were set early in InitializePlayer, loaded from savegame, etc.)
932 C4Player *plr = ::Players.Get(iPlayer);
933 if (plr)
934 {
935 plr->ZoomToViewport(nvp, true, false, false);
936 plr->ZoomLimitsToViewport(nvp);
937 }
938 // Action sound
939 if (GetViewportCount()!=iLastCount) if (!fSilent)
940 StartSoundEffect("UI::CloseViewport");
941 return true;
942 }
943
DisableFoW()944 void C4ViewportList::DisableFoW()
945 {
946 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
947 cvp->DisableFoW();
948 }
949
EnableFoW()950 void C4ViewportList::EnableFoW()
951 {
952 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
953 cvp->EnableFoW();
954 }
955
ClearPointers(C4Object * pObj)956 void C4ViewportList::ClearPointers(C4Object *pObj)
957 {
958 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
959 cvp->ClearPointers(pObj);
960 }
CloseViewport(int32_t iPlayer,bool fSilent)961 bool C4ViewportList::CloseViewport(int32_t iPlayer, bool fSilent)
962 {
963 // Close all matching viewports
964 int32_t iLastCount = GetViewportCount();
965 C4Viewport *next,*prev=nullptr;
966 for (C4Viewport *cvp=FirstViewport; cvp; cvp=next)
967 {
968 next=cvp->Next;
969 if (cvp->Player==iPlayer || (iPlayer==NO_OWNER && cvp->fIsNoOwnerViewport))
970 {
971 delete cvp;
972 if (prev) prev->Next=next;
973 else FirstViewport=next;
974 }
975 else
976 prev=cvp;
977 }
978 // Anything was done?
979 if (GetViewportCount()!=iLastCount)
980 {
981 // Recalculate viewports
982 RecalculateViewports();
983 // Action sound
984 if (!fSilent) StartSoundEffect("UI::CloseViewport");
985 }
986 return true;
987 }
988
RecalculateViewports()989 void C4ViewportList::RecalculateViewports()
990 {
991
992 // Fullscreen only
993 if (Application.isEditor) return;
994
995 // Sort viewports
996 SortViewportsByPlayerControl();
997
998 // Viewport area
999 int32_t iBorderTop = 0;
1000 if (Config.Graphics.UpperBoard)
1001 iBorderTop = C4UpperBoardHeight;
1002 ViewportArea.Set(FullScreen.pSurface,0,iBorderTop, C4GUI::GetScreenWdt(), C4GUI::GetScreenHgt()-iBorderTop);
1003
1004 // Redraw flag
1005 ::GraphicsSystem.InvalidateBg();
1006 #ifdef _WIN32
1007 // reset mouse clipping
1008 ClipCursor(nullptr);
1009 #else
1010 // StdWindow handles this.
1011 #endif
1012 // reset GUI dlg pos
1013 ::pGUI->SetPreferredDlgRect(C4Rect(ViewportArea.X, ViewportArea.Y, ViewportArea.Wdt, ViewportArea.Hgt));
1014
1015 // fullscreen background: First, cover all of screen
1016 BackgroundAreas.Clear();
1017 BackgroundAreas.AddRect(C4Rect(ViewportArea.X, ViewportArea.Y, ViewportArea.Wdt, ViewportArea.Hgt));
1018
1019 // Viewports
1020 C4Viewport *cvp;
1021 int32_t iViews = 0;
1022 for (cvp=FirstViewport; cvp; cvp=cvp->Next) iViews++;
1023 if (!iViews) return;
1024 int32_t iViewsH = (int32_t) sqrt(float(iViews));
1025 int32_t iViewsX = iViews / iViewsH;
1026 int32_t iViewsL = iViews % iViewsH;
1027 int32_t cViewH,cViewX,ciViewsX;
1028 int32_t cViewWdt,cViewHgt,cOffWdt,cOffHgt,cOffX,cOffY;
1029 cvp=FirstViewport;
1030 for (cViewH=0; cViewH<iViewsH; cViewH++)
1031 {
1032 ciViewsX = iViewsX; if (cViewH<iViewsL) ciViewsX++;
1033 for (cViewX=0; cViewX<ciViewsX; cViewX++)
1034 {
1035 cViewWdt = ViewportArea.Wdt/ciViewsX;
1036 cViewHgt = ViewportArea.Hgt/iViewsH;
1037 cOffX = ViewportArea.X;
1038 cOffY = ViewportArea.Y;
1039 cOffWdt = cOffHgt = 0;
1040 if (ciViewsX * cViewWdt < ViewportArea.Wdt)
1041 cOffX = (ViewportArea.Wdt - ciViewsX * cViewWdt) / 2;
1042 if (iViewsH * cViewHgt < ViewportArea.Hgt)
1043 cOffY = (ViewportArea.Hgt - iViewsH * cViewHgt) / 2 + ViewportArea.Y;
1044 if (Config.Graphics.SplitscreenDividers)
1045 {
1046 if (cViewX < ciViewsX - 1) cOffWdt=4;
1047 if (cViewH < iViewsH - 1) cOffHgt=4;
1048 }
1049 int32_t coViewWdt=cViewWdt-cOffWdt;
1050 int32_t coViewHgt=cViewHgt-cOffHgt;
1051 C4Rect rcOut(cOffX+cViewX*cViewWdt, cOffY+cViewH*cViewHgt, coViewWdt, coViewHgt);
1052 cvp->SetOutputSize(rcOut.x,rcOut.y,rcOut.x,rcOut.y,rcOut.Wdt,rcOut.Hgt);
1053 cvp=cvp->Next;
1054 // clip down area avaiable for background drawing
1055 BackgroundAreas.ClipByRect(rcOut);
1056 }
1057 }
1058 // and finally recalculate script menus
1059 if (::Game.ScriptGuiRoot)
1060 ::Game.ScriptGuiRoot->RequestLayoutUpdate();
1061 }
1062
GetViewportCount()1063 int32_t C4ViewportList::GetViewportCount()
1064 {
1065 int32_t iResult = 0;
1066 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next) iResult++;
1067 return iResult;
1068 }
1069
GetViewport(int32_t iPlayer,C4Viewport * pPrev)1070 C4Viewport* C4ViewportList::GetViewport(int32_t iPlayer, C4Viewport* pPrev)
1071 {
1072 for (C4Viewport *cvp=pPrev ? pPrev->Next : FirstViewport; cvp; cvp=cvp->Next)
1073 if (cvp->Player==iPlayer || (iPlayer==NO_OWNER && cvp->fIsNoOwnerViewport))
1074 return cvp;
1075 return nullptr;
1076 }
1077
GetAudibility(int32_t iX,int32_t iY,int32_t * iPan,int32_t iAudibilityRadius,int32_t * outPlayer)1078 int32_t C4ViewportList::GetAudibility(int32_t iX, int32_t iY, int32_t *iPan, int32_t iAudibilityRadius, int32_t *outPlayer)
1079 {
1080 // default audibility radius
1081 if (!iAudibilityRadius) iAudibilityRadius = C4AudibilityRadius;
1082 // Accumulate audibility by viewports
1083 int32_t iAudible=0; *iPan = 0;
1084 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
1085 {
1086 float distanceToCenterOfViewport = Distance(cvp->GetViewCenterX(),cvp->GetViewCenterY(),iX,iY);
1087 int32_t audibility = Clamp<int32_t>(100 - 100 * distanceToCenterOfViewport / C4AudibilityRadius, 0, 100);
1088 if (audibility > iAudible)
1089 {
1090 iAudible = audibility;
1091 if (outPlayer) *outPlayer = cvp->Player;
1092 }
1093 *iPan += (iX-(cvp->GetViewCenterX())) / 5;
1094 }
1095 *iPan = Clamp<int32_t>(*iPan, -100, 100);
1096 return iAudible;
1097 }
1098
SortViewportsByPlayerControl()1099 void C4ViewportList::SortViewportsByPlayerControl()
1100 {
1101
1102 // Sort
1103 bool fSorted;
1104 C4Player *pPlr1,*pPlr2;
1105 C4Viewport *pView,*pNext,*pPrev;
1106 do
1107 {
1108 fSorted = true;
1109 for (pPrev=nullptr,pView=FirstViewport; pView && (pNext = pView->Next); pView=pNext)
1110 {
1111 // Get players
1112 pPlr1 = ::Players.Get(pView->Player);
1113 pPlr2 = ::Players.Get(pNext->Player);
1114 // Swap order
1115 if (pPlr1 && pPlr2 && pPlr1->ControlSet && pPlr2->ControlSet && ( pPlr1->ControlSet->GetLayoutOrder() > pPlr2->ControlSet->GetLayoutOrder() ))
1116 {
1117 if (pPrev) pPrev->Next = pNext; else FirstViewport = pNext;
1118 pView->Next = pNext->Next;
1119 pNext->Next = pView;
1120 pPrev = pNext;
1121 pNext = pView;
1122 fSorted = false;
1123 }
1124 // Don't swap
1125 else
1126 {
1127 pPrev = pView;
1128 }
1129 }
1130 }
1131 while (!fSorted);
1132
1133 }
1134
ViewportNextPlayer()1135 bool C4ViewportList::ViewportNextPlayer()
1136 {
1137 // safety: switch valid?
1138 if ((!Game.C4S.Head.Film || !Game.C4S.Head.Replay) && !GetViewport(NO_OWNER)) return false;
1139 // do switch then
1140 C4Viewport *vp = GetFirstViewport();
1141 if (!vp) return false;
1142 vp->NextPlayer();
1143 return true;
1144 }
1145
FreeScroll(C4Vec2D vScrollBy)1146 bool C4ViewportList::FreeScroll(C4Vec2D vScrollBy)
1147 {
1148 // safety: move valid?
1149 if ((!Game.C4S.Head.Replay || !Game.C4S.Head.Film) && !GetViewport(NO_OWNER)) return false;
1150 C4Viewport *vp = GetFirstViewport();
1151 if (!vp) return false;
1152 // move then (old static code crap...)
1153 static int32_t vp_vx=0; static int32_t vp_vy=0; static int32_t vp_vf=0;
1154 int32_t dx=vScrollBy.x; int32_t dy=vScrollBy.y;
1155 if (Game.FrameCounter-vp_vf < 5)
1156 { dx += vp_vx; dy += vp_vy; }
1157 vp_vx=dx; vp_vy=dy; vp_vf=Game.FrameCounter;
1158 vp->ScrollView(dx, dy);
1159 return true;
1160 }
1161
ViewportZoomOut()1162 bool C4ViewportList::ViewportZoomOut()
1163 {
1164 for (C4Viewport *vp = FirstViewport; vp; vp = vp->Next) vp->ChangeZoom(1.0f/C4GFX_ZoomStep);
1165 return true;
1166 }
1167
ViewportZoomIn()1168 bool C4ViewportList::ViewportZoomIn()
1169 {
1170 for (C4Viewport *vp = FirstViewport; vp; vp = vp->Next) vp->ChangeZoom(C4GFX_ZoomStep);
1171 return true;
1172 }
1173
MouseMoveToViewport(int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)1174 void C4ViewportList::MouseMoveToViewport(int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
1175 {
1176 // Pass on to mouse controlled viewport
1177 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
1178 if (::MouseControl.IsViewport(cvp))
1179 ::MouseControl.Move( iButton,
1180 Clamp<int32_t>(iX-cvp->OutX,0,cvp->ViewWdt-1),
1181 Clamp<int32_t>(iY-cvp->OutY,0,cvp->ViewHgt-1),
1182 dwKeyParam );
1183 }
1184