/src/C4GraphicsSystem.cpp
C++ | 953 lines | 698 code | 84 blank | 171 comment | 181 complexity | bf2f6730bb8ea7f8994ef14389436a10 MD5 | raw file
Possible License(s): WTFPL, 0BSD, LGPL-2.1, CC-BY-3.0
1/*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 1998-2000, 2004, 2008 Matthes Bender
5 * Copyright (c) 2001-2003, 2005-2009 Sven Eberhardt
6 * Copyright (c) 2001 Michael K?ser
7 * Copyright (c) 2005-2006, 2008 G?nther Brammer
8 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
9 *
10 * Portions might be copyrighted by other authors who have contributed
11 * to OpenClonk.
12 *
13 * Permission to use, copy, modify, and/or distribute this software for any
14 * purpose with or without fee is hereby granted, provided that the above
15 * copyright notice and this permission notice appear in all copies.
16 * See isc_license.txt for full license and disclaimer.
17 *
18 * "Clonk" is a registered trademark of Matthes Bender.
19 * See clonk_trademark_license.txt for full license.
20 */
21
22/* Operates viewports, message board and draws the game */
23
24#include <C4Include.h>
25#include <C4GraphicsSystem.h>
26
27#ifndef BIG_C4INCLUDE
28#include <C4Viewport.h>
29#include <C4Application.h>
30#include <C4Console.h>
31#include <C4Random.h>
32#include <C4SurfaceFile.h>
33#include <C4FullScreen.h>
34#include <C4Gui.h>
35#include <C4LoaderScreen.h>
36#include <C4Player.h>
37#include <C4SoundSystem.h>
38#include <C4MouseControl.h>
39#include <C4GraphicsResource.h>
40#include <C4Landscape.h>
41#include <C4Network2.h>
42#include <C4Game.h>
43#include <C4PlayerList.h>
44#include <C4GameObjects.h>
45#endif
46
47#include <StdPNG.h>
48
49C4GraphicsSystem::C4GraphicsSystem()
50 {
51 fViewportClassRegistered=false;
52 Default();
53 }
54
55C4GraphicsSystem::~C4GraphicsSystem()
56 {
57 Clear();
58 }
59#ifdef _WIN32
60LRESULT APIENTRY ViewportWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
61
62BOOL C4GraphicsSystem::RegisterViewportClass(HINSTANCE hInst)
63 {
64 // register landscape viewport class
65 WNDCLASSEX WndClass;
66 WndClass.cbSize=sizeof(WNDCLASSEX);
67 WndClass.style = CS_DBLCLKS | CS_BYTEALIGNCLIENT;
68 WndClass.lpfnWndProc = ViewportWinProc;
69 WndClass.cbClsExtra = 0;
70 WndClass.cbWndExtra = 0;
71 WndClass.hInstance = hInst;
72 WndClass.hCursor = LoadCursor (NULL, IDC_ARROW);
73 WndClass.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
74 WndClass.lpszMenuName = NULL;
75 WndClass.lpszClassName = C4ViewportClassName;
76 WndClass.hIcon = LoadIcon (hInst, MAKEINTRESOURCE (IDI_01_C4S) );
77 WndClass.hIconSm = LoadIcon (hInst, MAKEINTRESOURCE (IDI_01_C4S) );
78 if (!RegisterClassEx(&WndClass)) return FALSE;
79 // register GUI dialog class
80 return C4GUI::Dialog::RegisterWindowClass(hInst);
81 }
82#endif
83bool C4GraphicsSystem::Init()
84 {
85#ifdef _WIN32
86 // Register viewport class
87 if (!fViewportClassRegistered)
88 if (!RegisterViewportClass(Application.GetInstance()))
89 return false;
90 fViewportClassRegistered=true;
91#endif
92 // Init video module
93 if (Config.Graphics.VideoModule)
94 Video.Init(Application.DDraw->lpBack);
95 // Success
96 return true;
97 }
98
99void C4GraphicsSystem::Clear()
100 {
101 // Clear message board
102 MessageBoard.Clear();
103 // Clear upper board
104 UpperBoard.Clear();
105 // clear loader
106 if (pLoaderScreen) { delete pLoaderScreen; pLoaderScreen=NULL; }
107 // Close viewports
108 C4Viewport *next;
109 while (FirstViewport)
110 {
111 next=FirstViewport->Next;
112 delete FirstViewport;
113 FirstViewport=next;
114 }
115 FirstViewport=NULL;
116 // Clear video system
117 Video.Clear();
118 // No debug stuff
119 DeactivateDebugOutput();
120 }
121
122bool C4GraphicsSystem::SetPalette()
123 {
124 // Set primary palette by game palette
125 if (!Application.DDraw->SetPrimaryPalette(::GraphicsResource.GamePalette, ::GraphicsResource.AlphaPalette)) return false;
126 return true;
127 }
128
129extern int32_t iLastControlSize,iPacketDelay;
130extern int32_t ControlQueueSize,ControlQueueDataSize;
131
132int32_t ScreenTick=0, ScreenRate=1;
133
134bool C4GraphicsSystem::StartDrawing()
135 {
136 // only if ddraw is ready
137 if (!Application.DDraw) return false;
138 if (!Application.DDraw->Active) return false;
139
140 // only if application is active or windowed (if config allows)
141 if (!Application.Active && (Application.isFullScreen || !Config.Graphics.RenderInactiveEM)) return false;
142
143 // drawing OK
144 return true;
145 }
146
147void C4GraphicsSystem::FinishDrawing()
148 {
149 if (Application.isFullScreen) Application.DDraw->PageFlip();
150 }
151
152void C4GraphicsSystem::Execute()
153 {
154 // activity check
155 if (!StartDrawing()) return;
156
157 bool fBGDrawn = false;
158
159 // If lobby running, message board only (page flip done by startup message board)
160 if (!::pGUI || !::pGUI->HasFullscreenDialog(true)) // allow for message board behind GUI
161 if(::Network.isLobbyActive() || !Game.IsRunning)
162 if (Application.isFullScreen)
163 {
164 // Message board
165 if (iRedrawBackground) ClearFullscreenBackground();
166 MessageBoard.Execute();
167 if (!::pGUI || !C4GUI::IsActive())
168 { FinishDrawing(); return; }
169 fBGDrawn = true;
170 }
171
172 // fullscreen GUI?
173 if (Application.isFullScreen && ::pGUI && C4GUI::IsActive() && (::pGUI->HasFullscreenDialog(false) || !Game.IsRunning))
174 {
175 if (!fBGDrawn && iRedrawBackground) ClearFullscreenBackground();
176 ::pGUI->Render(!fBGDrawn);
177 FinishDrawing();
178 return;
179 }
180
181 // Fixed screen rate in old network
182 ScreenRate = 1;
183
184 // Background redraw
185 if (Application.isFullScreen)
186 if (iRedrawBackground)
187 DrawFullscreenBackground();
188
189 // Screen rate skip frame draw
190 ScreenTick++; if (ScreenTick>=ScreenRate) ScreenTick=0;
191
192 // Reset object audibility
193 ::Objects.ResetAudibility();
194
195 // some hack to ensure the mouse is drawn after a dialog close and before any
196 // movement messages
197 if (::pGUI && !C4GUI::IsActive())
198 SetMouseInGUI(false, false);
199
200 // Viewports
201 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
202 cvp->Execute();
203
204 if (Application.isFullScreen)
205 {
206 // Upper board
207 UpperBoard.Execute();
208
209 // Message board
210 MessageBoard.Execute();
211
212 // Help & Messages
213 DrawHelp();
214 DrawHoldMessages();
215 DrawFlashMessage();
216 }
217
218 // InGame-GUI
219 if (::pGUI && C4GUI::IsActive())
220 {
221 ::pGUI->Render(false);
222 }
223
224 // Palette update
225 if (fSetPalette) { SetPalette(); /*SetDarkColorTable();*/ fSetPalette=false; }
226
227 // gamma update
228 if (fSetGamma)
229 {
230 ApplyGamma();
231 fSetGamma=false;
232 }
233
234 // Video record & status (fullsrceen)
235 if (Application.isFullScreen)
236 Video.Execute();
237
238 // done
239 FinishDrawing();
240 }
241
242bool C4GraphicsSystem::CloseViewport(C4Viewport * cvp)
243 {
244 if (!cvp) return false;
245 /*C4Viewport *next,*prev=NULL;
246 for (C4Viewport *cvp2=FirstViewport; cvp2; cvp2=next)
247 {
248 next=cvp2->Next;
249 if (cvp2 == cvp)
250 {
251 delete cvp;
252 StartSoundEffect("CloseViewport");
253 if (prev) prev->Next=next;
254 else FirstViewport=next;
255 }
256 else
257 prev=cvp2;
258 }*/
259 // Chop the start of the chain off
260 if (FirstViewport == cvp)
261 {
262 FirstViewport = cvp->Next;
263 delete cvp;
264 StartSoundEffect("CloseViewport");
265 }
266 // Take out of the chain
267 else for (C4Viewport * prev = FirstViewport; prev; prev = prev->Next)
268 {
269 if (prev->Next == cvp)
270 {
271 prev->Next = cvp->Next;
272 delete cvp;
273 StartSoundEffect("CloseViewport");
274 }
275 }
276 // Recalculate viewports
277 RecalculateViewports();
278 // Done
279 return true;
280 }
281#ifdef _WIN32
282C4Viewport* C4GraphicsSystem::GetViewport(HWND hwnd)
283 {
284 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
285 if (cvp->pWindow->hWindow==hwnd)
286 return cvp;
287 return NULL;
288 }
289#endif
290bool C4GraphicsSystem::CreateViewport(int32_t iPlayer, bool fSilent)
291 {
292 // Create and init new viewport, add to viewport list
293 int32_t iLastCount = GetViewportCount();
294 C4Viewport *nvp = new C4Viewport;
295 bool fOkay = false;
296 if (Application.isFullScreen)
297 fOkay = nvp->Init(iPlayer, false);
298 else
299 fOkay = nvp->Init(&Console,&Application,iPlayer);
300 if (!fOkay) { delete nvp; return false; }
301 C4Viewport *pLast;
302 for (pLast=FirstViewport; pLast && pLast->Next; pLast=pLast->Next) {}
303 if (pLast) pLast->Next=nvp; else FirstViewport=nvp;
304 // Recalculate viewports
305 RecalculateViewports();
306 // Viewports start off at centered position
307 nvp->CenterPosition();
308 // Action sound
309 if (GetViewportCount()!=iLastCount) if (!fSilent)
310 StartSoundEffect("CloseViewport");
311 return true;
312 }
313
314void C4GraphicsSystem::ClearPointers(C4Object *pObj)
315 {
316 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
317 cvp->ClearPointers(pObj);
318 }
319
320void C4GraphicsSystem::Default()
321 {
322 UpperBoard.Default();
323 MessageBoard.Default();
324 FirstViewport=NULL;
325 InvalidateBg();
326 ViewportArea.Default();
327 ShowVertices=false;
328 ShowAction=false;
329 ShowCommand=false;
330 ShowEntrance=false;
331 ShowPathfinder=false;
332 ShowNetstatus=false;
333 ShowSolidMask=false;
334 ShowHelp=false;
335 FlashMessageText[0]=0;
336 FlashMessageTime=0; FlashMessageX=FlashMessageY=0;
337 fSetPalette=false;
338 Video.Default();
339 for (int32_t iRamp=0; iRamp<3*C4MaxGammaRamps; iRamp+=3)
340 { dwGamma[iRamp+0]=0; dwGamma[iRamp+1]=0x808080; dwGamma[iRamp+2]=0xffffff; }
341 fSetGamma=false;
342 pLoaderScreen=NULL;
343 }
344
345void C4GraphicsSystem::DrawFullscreenBackground()
346 {
347 for (int i=0, iNum=BackgroundAreas.GetCount(); i<iNum; ++i)
348 {
349 const C4Rect &rc = BackgroundAreas.Get(i);
350 Application.DDraw->BlitSurfaceTile(::GraphicsResource.fctBackground.Surface,Application.DDraw->lpBack,rc.x,rc.y,rc.Wdt,rc.Hgt,-rc.x,-rc.y);
351 }
352 --iRedrawBackground;
353 }
354
355void C4GraphicsSystem::ClearFullscreenBackground()
356 {
357 Application.DDraw->FillBG(0);
358 --iRedrawBackground;
359 }
360
361void OnSurfaceRestore()
362 {
363 ::GraphicsSystem.InvalidateBg();
364 }
365
366bool C4GraphicsSystem::InitLoaderScreen(const char *szLoaderSpec, bool fDrawBlackScreenFirst)
367 {
368 // create new loader; overwrite current only if successful
369 C4LoaderScreen *pNewLoader = new C4LoaderScreen();
370 pNewLoader->SetBlackScreen(fDrawBlackScreenFirst);
371 if (!pNewLoader->Init(szLoaderSpec)) { delete pNewLoader; return false; }
372 if (pLoaderScreen) delete pLoaderScreen;
373 pLoaderScreen = pNewLoader;
374 // apply user gamma for loader
375 ApplyGamma();
376 // done, success
377 return true;
378 }
379
380void C4GraphicsSystem::EnableLoaderDrawing()
381 {
382 // reset black screen loader flag
383 if (pLoaderScreen) pLoaderScreen->SetBlackScreen(false);
384 }
385
386bool C4GraphicsSystem::CloseViewport(int32_t iPlayer, bool fSilent)
387 {
388 // Close all matching viewports
389 int32_t iLastCount = GetViewportCount();
390 C4Viewport *next,*prev=NULL;
391 for (C4Viewport *cvp=FirstViewport; cvp; cvp=next)
392 {
393 next=cvp->Next;
394 if (cvp->Player==iPlayer || (iPlayer==NO_OWNER && cvp->fIsNoOwnerViewport))
395 {
396 delete cvp;
397 if (prev) prev->Next=next;
398 else FirstViewport=next;
399 }
400 else
401 prev=cvp;
402 }
403 // Recalculate viewports
404 RecalculateViewports();
405 // Action sound
406 if (GetViewportCount()!=iLastCount) if (!fSilent)
407 StartSoundEffect("CloseViewport");
408 return true;
409 }
410
411void C4GraphicsSystem::RecalculateViewports()
412 {
413
414 // Fullscreen only
415 if (!Application.isFullScreen) return;
416
417 // Sort viewports
418 SortViewportsByPlayerControl();
419
420 // Viewport area
421 int32_t iBorderTop = 0, iBorderBottom = 0;
422 if (Config.Graphics.UpperBoard)
423 iBorderTop = C4UpperBoardHeight;
424 iBorderBottom = MessageBoard.Output.Hgt;
425 ViewportArea.Set(Application.DDraw->lpBack,0,iBorderTop, C4GUI::GetScreenWdt(), C4GUI::GetScreenHgt()-iBorderTop-iBorderBottom);
426
427 // Redraw flag
428 InvalidateBg();
429#ifdef _WIN32
430 // reset mouse clipping
431 ClipCursor(NULL);
432#else
433 // StdWindow handles this.
434#endif
435 // reset GUI dlg pos
436 if (::pGUI)
437 ::pGUI->SetPreferredDlgRect(C4Rect(ViewportArea.X, ViewportArea.Y, ViewportArea.Wdt, ViewportArea.Hgt));
438
439 // fullscreen background: First, cover all of screen
440 BackgroundAreas.Clear();
441 BackgroundAreas.AddRect(C4Rect(ViewportArea.X, ViewportArea.Y, ViewportArea.Wdt, ViewportArea.Hgt));
442
443 // Viewports
444 C4Viewport *cvp;
445 int32_t iViews = 0;
446 for (cvp=FirstViewport; cvp; cvp=cvp->Next) iViews++;
447 if (!iViews) return;
448 int32_t iViewsH = (int32_t) sqrt(float(iViews));
449 int32_t iViewsX = iViews / iViewsH;
450 int32_t iViewsL = iViews % iViewsH;
451 int32_t cViewH,cViewX,ciViewsX;
452 int32_t cViewWdt,cViewHgt,cOffWdt,cOffHgt,cOffX,cOffY;
453 cvp=FirstViewport;
454 for (cViewH=0; cViewH<iViewsH; cViewH++)
455 {
456 ciViewsX = iViewsX; if (cViewH<iViewsL) ciViewsX++;
457 for (cViewX=0; cViewX<ciViewsX; cViewX++)
458 {
459 cViewWdt = ViewportArea.Wdt/ciViewsX;
460 cViewHgt = ViewportArea.Hgt/iViewsH;
461 cOffX = ViewportArea.X;
462 cOffY = ViewportArea.Y;
463 cOffWdt = cOffHgt = 0;
464 if (ciViewsX * cViewWdt < ViewportArea.Wdt)
465 cOffX = (ViewportArea.Wdt - ciViewsX * cViewWdt) / 2;
466 if (iViewsH * cViewHgt < ViewportArea.Hgt)
467 cOffY = (ViewportArea.Hgt - iViewsH * cViewHgt) / 2 + ViewportArea.Y;
468 if (Config.Graphics.SplitscreenDividers)
469 {
470 if (cViewX < ciViewsX - 1) cOffWdt=4;
471 if (cViewH < iViewsH - 1) cOffHgt=4;
472 }
473 int32_t coViewWdt=cViewWdt-cOffWdt;
474 int32_t coViewHgt=cViewHgt-cOffHgt;
475 C4Rect rcOut(cOffX+cViewX*cViewWdt, cOffY+cViewH*cViewHgt, coViewWdt, coViewHgt);
476 cvp->SetOutputSize(rcOut.x,rcOut.y,rcOut.x,rcOut.y,rcOut.Wdt,rcOut.Hgt);
477 cvp=cvp->Next;
478 // clip down area avaiable for background drawing
479 BackgroundAreas.ClipByRect(rcOut);
480 }
481 }
482 }
483
484int32_t C4GraphicsSystem::GetViewportCount()
485 {
486 int32_t iResult = 0;
487 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next) iResult++;
488 return iResult;
489 }
490
491C4Viewport* C4GraphicsSystem::GetViewport(int32_t iPlayer)
492 {
493 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
494 if (cvp->Player==iPlayer || (iPlayer==NO_OWNER && cvp->fIsNoOwnerViewport))
495 return cvp;
496 return NULL;
497 }
498
499int32_t LayoutOrder(int32_t iControl)
500 {
501 // Convert keyboard control index to keyboard layout order
502 switch (iControl)
503 {
504 case C4P_Control_Keyboard1: return 0;
505 case C4P_Control_Keyboard2: return 3;
506 case C4P_Control_Keyboard3: return 1;
507 case C4P_Control_Keyboard4: return 2;
508 }
509 return iControl;
510 }
511
512void C4GraphicsSystem::SortViewportsByPlayerControl()
513 {
514
515 // Sort
516 bool fSorted;
517 C4Player *pPlr1,*pPlr2;
518 C4Viewport *pView,*pNext,*pPrev;
519 do
520 {
521 fSorted = true;
522 for (pPrev=NULL,pView=FirstViewport; pView && (pNext = pView->Next); pView=pNext)
523 {
524 // Get players
525 pPlr1 = ::Players.Get(pView->Player);
526 pPlr2 = ::Players.Get(pNext->Player);
527 // Swap order
528 if (pPlr1 && pPlr2 && ( LayoutOrder(pPlr1->Control) > LayoutOrder(pPlr2->Control) ))
529 {
530 if (pPrev) pPrev->Next = pNext; else FirstViewport = pNext;
531 pView->Next = pNext->Next;
532 pNext->Next = pView;
533 pPrev = pNext;
534 pNext = pView;
535 fSorted = false;
536 }
537 // Don't swap
538 else
539 {
540 pPrev = pView;
541 }
542 }
543 }
544 while (!fSorted);
545
546 }
547
548void C4GraphicsSystem::MouseMove(int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam, class C4Viewport *pVP)
549 {
550 // pass on to GUI
551 // Special: Don't pass if dragging and button is not upped
552 if (::pGUI && ::pGUI->IsActive() && !::MouseControl.IsDragging())
553 {
554 bool fResult = ::pGUI->MouseInput(iButton, iX, iY, dwKeyParam, NULL, pVP);
555 if (::pGUI && ::pGUI->HasMouseFocus()) { SetMouseInGUI(true, true); return; }
556 // non-exclusive GUI: inform mouse-control about GUI-result
557 SetMouseInGUI(fResult, true);
558 // abort if GUI processed it
559 if (fResult) return;
560 }
561 else
562 // no GUI: mouse is not in GUI
563 SetMouseInGUI(false, true);
564 // mouse control enabled?
565 if (!::MouseControl.IsActive())
566 {
567 // enable mouse in GUI, if a mouse-only-dlg is displayed
568 if (::pGUI && ::pGUI->GetMouseControlledDialogCount())
569 SetMouseInGUI(true, true);
570 return;
571 }
572 // Pass on to mouse controlled viewport
573 MouseMoveToViewport(iButton, iX, iY, dwKeyParam);
574 }
575
576void C4GraphicsSystem::MouseMoveToViewport(int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
577 {
578 // Pass on to mouse controlled viewport
579 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
580 if (::MouseControl.IsViewport(cvp))
581 ::MouseControl.Move( iButton,
582 BoundBy<int32_t>(iX-cvp->OutX,0,cvp->ViewWdt-1),
583 BoundBy<int32_t>(iY-cvp->OutY,0,cvp->ViewHgt-1),
584 dwKeyParam );
585 }
586
587void C4GraphicsSystem::SetMouseInGUI(bool fInGUI, bool fByMouse)
588 {
589 // inform mouse control and GUI
590 if (::pGUI)
591 {
592 ::pGUI->Mouse.SetOwnedMouse(fInGUI);
593 // initial movement to ensure mouse control pos is correct
594 if (!::MouseControl.IsMouseOwned() && !fInGUI && !fByMouse)
595 {
596 ::MouseControl.SetOwnedMouse(true);
597 MouseMoveToViewport(C4MC_Button_None, int32_t(::pGUI->Mouse.x*C4GUI::GetZoom()), int32_t(::pGUI->Mouse.y*C4GUI::GetZoom()), ::pGUI->Mouse.dwKeys);
598 }
599 }
600 ::MouseControl.SetOwnedMouse(!fInGUI);
601 }
602
603bool C4GraphicsSystem::SaveScreenshot(bool fSaveAll)
604 {
605 // Filename
606 char szFilename[_MAX_PATH+1];
607 int32_t iScreenshotIndex=1;
608 const char *strFilePath = NULL;
609 do
610 sprintf(szFilename,"Screenshot%03i.png",iScreenshotIndex++);
611 while (FileExists(strFilePath = Config.AtScreenshotPath(szFilename)));
612 bool fSuccess=DoSaveScreenshot(fSaveAll, strFilePath);
613 // log if successful/where it has been stored
614 if (!fSuccess)
615 LogF(LoadResStr("IDS_PRC_SCREENSHOTERROR"), Config.AtUserDataRelativePath(Config.AtScreenshotPath(szFilename)));
616 else
617 LogF(LoadResStr("IDS_PRC_SCREENSHOT"), Config.AtUserDataRelativePath(Config.AtScreenshotPath(szFilename)));
618 // return success
619 return !!fSuccess;
620 }
621
622bool C4GraphicsSystem::DoSaveScreenshot(bool fSaveAll, const char *szFilename)
623 {
624 // Fullscreen only
625 if (!Application.isFullScreen) return false;
626 // back surface must be present
627 if (!Application.DDraw->lpBack) return false;
628
629 // save landscape
630 if (fSaveAll)
631 {
632 // get viewport to draw in
633 C4Viewport *pVP=GetFirstViewport(); if (!pVP) return false;
634 // create image large enough to hold the landcape
635 CPNGFile png; int32_t lWdt=GBackWdt,lHgt=GBackHgt;
636 if (!png.Create(lWdt, lHgt, false)) return false;
637 // get backbuffer size
638 int32_t bkWdt=C4GUI::GetScreenWdt(), bkHgt=C4GUI::GetScreenHgt();
639 if (!bkWdt || !bkHgt) return false;
640 // facet for blitting
641 C4TargetFacet bkFct;
642 // mark background to be redrawn
643 InvalidateBg();
644 // backup and clear sky parallaxity
645 int32_t iParX=::Landscape.Sky.ParX; ::Landscape.Sky.ParX=10;
646 int32_t iParY=::Landscape.Sky.ParY; ::Landscape.Sky.ParY=10;
647 // temporarily change viewport player
648 int32_t iVpPlr=pVP->Player; pVP->Player=NO_OWNER;
649 // blit all tiles needed
650 for (int32_t iY=0; iY<lHgt; iY+=bkHgt) for (int32_t iX=0; iX<lWdt; iX+=bkWdt)
651 {
652 // get max width/height
653 int32_t bkWdt2=bkWdt,bkHgt2=bkHgt;
654 if (iX+bkWdt2>lWdt) bkWdt2-=iX+bkWdt2-lWdt;
655 if (iY+bkHgt2>lHgt) bkHgt2-=iY+bkHgt2-lHgt;
656 // update facet
657 bkFct.Set(Application.DDraw->lpBack, 0, 0, bkWdt2, bkHgt2, iX, iY);
658 // draw there
659 pVP->Draw(bkFct, false);
660 // render
661 Application.DDraw->PageFlip(); Application.DDraw->PageFlip();
662 // get output (locking primary!)
663 if (Application.DDraw->lpBack->Lock())
664 {
665 // transfer each pixel - slooow...
666 for (int32_t iY2=0; iY2<bkHgt2; ++iY2)
667 for (int32_t iX2=0; iX2<bkWdt2; ++iX2)
668 png.SetPix(iX+iX2, iY+iY2, Application.DDraw->ApplyGammaTo(Application.DDraw->lpBack->GetPixDw(iX2, iY2, false)));
669 // done; unlock
670 Application.DDraw->lpBack->Unlock();
671 }
672 }
673 // restore viewport player
674 pVP->Player=iVpPlr;
675 // restore parallaxity
676 ::Landscape.Sky.ParX=iParX;
677 ::Landscape.Sky.ParY=iParY;
678 // save!
679 return png.Save(szFilename);
680 }
681 // Save primary surface
682 return Application.DDraw->lpBack->SavePNG(szFilename, false, true, false);
683 }
684
685void C4GraphicsSystem::DeactivateDebugOutput()
686 {
687 ShowVertices=false;
688 ShowAction=false;
689 ShowCommand=false;
690 ShowEntrance=false;
691 ShowPathfinder=false; // allow pathfinder! - why this??
692 ShowSolidMask=false;
693 ShowNetstatus=false;
694 }
695
696void C4GraphicsSystem::DrawHoldMessages()
697 {
698 if (Application.isFullScreen && Game.HaltCount)
699 {
700 Application.DDraw->TextOut("Pause", ::GraphicsResource.FontRegular,1.0,
701 Application.DDraw->lpBack, C4GUI::GetScreenWdt()/2,
702 C4GUI::GetScreenHgt()/2 - ::GraphicsResource.FontRegular.iLineHgt*2,
703 CStdDDraw::DEFAULT_MESSAGE_COLOR, ACenter);
704 ::GraphicsSystem.OverwriteBg();
705 }
706 }
707
708BYTE FindPaletteColor(BYTE *bypPalette, int32_t iRed, int32_t iGreen, int32_t iBlue)
709 {
710 int32_t iClosest=0;
711 for (int32_t iColor=1; iColor<256; iColor++)
712 if (Abs(bypPalette[iColor*3+0]-iRed)+Abs(bypPalette[iColor*3+1]-iGreen)+Abs(bypPalette[iColor*3+2]-iBlue)
713 < Abs(bypPalette[iClosest*3+0]-iRed)+Abs(bypPalette[iClosest*3+1]-iGreen)+Abs(bypPalette[iClosest*3+2]-iBlue) )
714 iClosest = iColor;
715 return iClosest;
716 }
717
718void C4GraphicsSystem::SetDarkColorTable()
719 {
720 const int32_t iDarkening=80;
721 // Using GamePalette
722 BYTE *bypPalette = ::GraphicsResource.GamePalette;
723 for (int32_t iColor=0; iColor<256; iColor++)
724 DarkColorTable[iColor]=FindPaletteColor(bypPalette,Max<int32_t>(bypPalette[iColor*3+0]-iDarkening,0),Max<int32_t>(bypPalette[iColor*3+1]-iDarkening,0),Max<int32_t>(bypPalette[iColor*3+2]-iDarkening,0));
725 }
726
727void C4GraphicsSystem::FlashMessage(const char *szMessage)
728 {
729 // Store message
730 SCopy(szMessage, FlashMessageText, C4MaxTitle);
731 // Calculate message time
732 FlashMessageTime = SLen(FlashMessageText) * 2;
733 // Initial position
734 FlashMessageX = -1;
735 FlashMessageY = 10;
736 // Upper board active: stay below upper board
737 if (Config.Graphics.UpperBoard)
738 FlashMessageY += C4UpperBoardHeight;
739 // More than one viewport: try to stay below portraits etc.
740 if (GetViewportCount() > 1)
741 FlashMessageY += 64;
742 // New flash message: redraw background (might be drawing one message on top of another)
743 InvalidateBg();
744 }
745
746void C4GraphicsSystem::FlashMessageOnOff(const char *strWhat, bool fOn)
747 {
748 StdStrBuf strMessage;
749 strMessage.Format("%s: %s", strWhat, LoadResStr(fOn ? "IDS_CTL_ON" : "IDS_CTL_OFF"));
750 FlashMessage(strMessage.getData());
751 }
752
753void C4GraphicsSystem::DrawFlashMessage()
754 {
755 if (!FlashMessageTime) return;
756 if (!Application.isFullScreen) return;
757 Application.DDraw->TextOut(FlashMessageText, ::GraphicsResource.FontRegular, 1.0, Application.DDraw->lpBack,
758 (FlashMessageX==-1) ? C4GUI::GetScreenWdt()/2 : FlashMessageX,
759 (FlashMessageY==-1) ? C4GUI::GetScreenHgt()/2 : FlashMessageY,
760 CStdDDraw::DEFAULT_MESSAGE_COLOR,
761 (FlashMessageX==-1) ? ACenter : ALeft);
762 FlashMessageTime--;
763 // Flash message timed out: redraw background
764 if (!FlashMessageTime) InvalidateBg();
765 }
766
767void C4GraphicsSystem::DrawHelp()
768 {
769 if (!ShowHelp) return;
770 if (!Application.isFullScreen) return;
771 int32_t iX = ViewportArea.X, iY = ViewportArea.Y;
772 int32_t iWdt = ViewportArea.Wdt;
773 StdStrBuf strText;
774 // left coloumn
775 strText.AppendFormat("[%s]\n\n", LoadResStr("IDS_CTL_GAMEFUNCTIONS"));
776 // main functions
777 strText.AppendFormat("<c ffff00>%s</c> - %s\n", GetKeyboardInputName("ToggleShowHelp").getData(), LoadResStr("IDS_CON_HELP"));
778 strText.AppendFormat("<c ffff00>%s</c> - %s\n", GetKeyboardInputName("MusicToggle").getData(), LoadResStr("IDS_CTL_MUSIC"));
779 strText.AppendFormat("<c ffff00>%s</c> - %s\n", GetKeyboardInputName("NetClientListDlgToggle").getData(), LoadResStr("IDS_DLG_NETWORK"));
780 // messages
781 StdCopyStrBuf strAltChatKey(GetKeyboardInputName("ChatOpen", false, 0));
782 strText.AppendFormat("\n<c ffff00>%s/%s</c> - %s\n", GetKeyboardInputName("ChatOpen", false, 1).getData(), strAltChatKey.getData(), LoadResStr("IDS_CTL_SENDMESSAGE"));
783 strText.AppendFormat("<c ffff00>%s</c> - %s\n", GetKeyboardInputName("MsgBoardScrollUp").getData(), LoadResStr("IDS_CTL_MESSAGEBOARDBACK"));
784 strText.AppendFormat("<c ffff00>%s</c> - %s\n", GetKeyboardInputName("MsgBoardScrollDown").getData(), LoadResStr("IDS_CTL_MESSAGEBOARDFORWARD"));
785 // irc chat
786 strText.AppendFormat("\n<c ffff00>%s</c> - %s\n", GetKeyboardInputName("ToggleChat").getData(), LoadResStr("IDS_CTL_IRCCHAT"));
787 // scoreboard
788 strText.AppendFormat("\n<c ffff00>%s</c> - %s\n", GetKeyboardInputName("ScoreboardToggle").getData(), LoadResStr("IDS_CTL_SCOREBOARD"));
789 // screenshots
790 strText.AppendFormat("\n<c ffff00>%s</c> - %s\n", GetKeyboardInputName("Screenshot").getData(), LoadResStr("IDS_CTL_SCREENSHOT"));
791 strText.AppendFormat("<c ffff00>%s</c> - %s\n", GetKeyboardInputName("ScreenshotEx").getData(), LoadResStr("IDS_CTL_SCREENSHOTEX"));
792
793 Application.DDraw->TextOut(strText.getData(), ::GraphicsResource.FontRegular, 1.0, Application.DDraw->lpBack,
794 iX + 128, iY + 64, CStdDDraw::DEFAULT_MESSAGE_COLOR, ALeft);
795
796 // right coloumn
797 strText.Clear();
798 // game speed
799 strText.AppendFormat("\n\n<c ffff00>%s</c> - %s\n", GetKeyboardInputName("GameSpeedUp").getData(), LoadResStr("IDS_CTL_GAMESPEEDUP"));
800 strText.AppendFormat("<c ffff00>%s</c> - %s\n", GetKeyboardInputName("GameSlowDown").getData(), LoadResStr("IDS_CTL_GAMESPEEDDOWN"));
801 // debug
802 strText.AppendFormat("\n\n[%s]\n\n", "Debug");
803 strText.AppendFormat("<c ffff00>%s</c> - %s\n", GetKeyboardInputName("DbgModeToggle").getData(), LoadResStr("IDS_CTL_DEBUGMODE"));
804 strText.AppendFormat("<c ffff00>%s</c> - %s\n", GetKeyboardInputName("DbgShowVtxToggle").getData(), "Entrance+Vertices");
805 strText.AppendFormat("<c ffff00>%s</c> - %s\n", GetKeyboardInputName("DbgShowActionToggle").getData(), "Actions/Commands/Pathfinder");
806 strText.AppendFormat("<c ffff00>%s</c> - %s\n", GetKeyboardInputName("DbgShowSolidMaskToggle").getData(), "SolidMasks");
807 Application.DDraw->TextOut(strText.getData(), ::GraphicsResource.FontRegular, 1.0, Application.DDraw->lpBack,
808 iX + iWdt/2 + 64, iY + 64, CStdDDraw::DEFAULT_MESSAGE_COLOR, ALeft);
809 }
810
811int32_t C4GraphicsSystem::GetAudibility(int32_t iX, int32_t iY, int32_t *iPan, int32_t iAudibilityRadius)
812 {
813 // default audibility radius
814 if (!iAudibilityRadius) iAudibilityRadius = C4AudibilityRadius;
815 // Accumulate audibility by viewports
816 int32_t iAudible=0; *iPan = 0;
817 for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next)
818 {
819 iAudible = Max( iAudible,
820 BoundBy<int32_t>(100-100*Distance(cvp->ViewX+cvp->ViewWdt/2,cvp->ViewY+cvp->ViewHgt/2,iX,iY)/C4AudibilityRadius,0,100) );
821 *iPan += (iX-(cvp->ViewX+cvp->ViewWdt/2)) / 5;
822 }
823 *iPan = BoundBy<int32_t>(*iPan, -100, 100);
824 return iAudible;
825 }
826
827void C4GraphicsSystem::SetGamma(DWORD dwClr1, DWORD dwClr2, DWORD dwClr3, int32_t iRampIndex)
828 {
829 // No gamma effects
830 if (Config.Graphics.DisableGamma) return;
831 if (iRampIndex < 0 || iRampIndex >= C4MaxGammaRamps) return;
832 // turn ramp index into array offset
833 iRampIndex*=3;
834 // set array members
835 dwGamma[iRampIndex+0]=dwClr1;
836 dwGamma[iRampIndex+1]=dwClr2;
837 dwGamma[iRampIndex+2]=dwClr3;
838 // mark gamma ramp to be recalculated
839 fSetGamma=true;
840 }
841
842void C4GraphicsSystem::ApplyGamma()
843 {
844 // No gamma effects
845 if (Config.Graphics.DisableGamma) return;
846 // calculate color channels by adding the difference between the gamma ramps to their normals
847 int32_t ChanOff[3];
848 DWORD Gamma[3];
849 const int32_t DefChanVal[3] = { 0x00, 0x80, 0xff };
850 // calc offset for curve points
851 for (int32_t iCurve=0; iCurve<3; ++iCurve)
852 {
853 ZeroMemory(ChanOff, sizeof(int32_t)*3);
854 // ...channels...
855 for (int32_t iChan=0; iChan<3; ++iChan)
856 // ...ramps...
857 for (int32_t iRamp=0; iRamp<C4MaxGammaRamps; ++iRamp)
858 // add offset
859 ChanOff[iChan]+=(int32_t) BYTE(dwGamma[iRamp*3+iCurve]>>(16-iChan*8)) - DefChanVal[iCurve];
860 // calc curve point
861 Gamma[iCurve]=C4RGB(BoundBy<int32_t>(DefChanVal[iCurve]+ChanOff[0], 0, 255), BoundBy<int32_t>(DefChanVal[iCurve]+ChanOff[1], 0, 255), BoundBy<int32_t>(DefChanVal[iCurve]+ChanOff[2], 0, 255));
862 }
863 // set gamma
864 Application.DDraw->SetGamma(Gamma[0], Gamma[1], Gamma[2]);
865 }
866
867bool C4GraphicsSystem::ToggleShowNetStatus()
868 {
869 ShowNetstatus = !ShowNetstatus;
870 return true;
871 }
872
873bool C4GraphicsSystem::ToggleShowVertices()
874 {
875 if (!Game.DebugMode && !Console.Active) { FlashMessage(LoadResStr("IDS_MSG_NODEBUGMODE")); return false; }
876 Toggle(ShowVertices);
877 Toggle(ShowEntrance); // vertices and entrance now toggled together
878 FlashMessageOnOff("Entrance+Vertices", ShowVertices || ShowEntrance);
879 return true;
880 }
881
882bool C4GraphicsSystem::ToggleShowAction()
883 {
884 if (!Game.DebugMode && !Console.Active) { FlashMessage(LoadResStr("IDS_MSG_NODEBUGMODE")); return false; }
885 if (!(ShowAction || ShowCommand || ShowPathfinder))
886 { ShowAction = true; FlashMessage("Actions"); }
887 else if (ShowAction)
888 { ShowAction = false; ShowCommand = true; FlashMessage("Commands"); }
889 else if (ShowCommand)
890 { ShowCommand = false; ShowPathfinder = true; FlashMessage("Pathfinder"); }
891 else if (ShowPathfinder)
892 { ShowPathfinder = false; FlashMessageOnOff("Actions/Commands/Pathfinder", false); }
893 return true;
894 }
895
896bool C4GraphicsSystem::ToggleShowSolidMask()
897 {
898 if (!Game.DebugMode && !Console.Active) { FlashMessage(LoadResStr("IDS_MSG_NODEBUGMODE")); return false; }
899 Toggle(ShowSolidMask);
900 FlashMessageOnOff("SolidMasks", !!ShowSolidMask);
901 return true;
902 }
903
904bool C4GraphicsSystem::ToggleShowHelp()
905 {
906 Toggle(ShowHelp);
907 // Turned off? Invalidate background.
908 if (!ShowHelp) InvalidateBg();
909 return true;
910 }
911
912bool C4GraphicsSystem::ViewportNextPlayer()
913 {
914 // safety: switch valid?
915 if ((!Game.C4S.Head.Film || !Game.C4S.Head.Replay) && !::GraphicsSystem.GetViewport(NO_OWNER)) return false;
916 // do switch then
917 C4Viewport *vp = GetFirstViewport();
918 if (!vp) return false;
919 vp->NextPlayer();
920 return true;
921 }
922
923bool C4GraphicsSystem::FreeScroll(C4Vec2D vScrollBy)
924 {
925 // safety: move valid?
926 if ((!Game.C4S.Head.Replay || !Game.C4S.Head.Film) && !::GraphicsSystem.GetViewport(NO_OWNER)) return false;
927 C4Viewport *vp = GetFirstViewport();
928 if (!vp) return false;
929 // move then (old static code crap...)
930 static int32_t vp_vx=0; static int32_t vp_vy=0; static int32_t vp_vf=0;
931 int32_t dx=vScrollBy.x; int32_t dy=vScrollBy.y;
932 if (Game.FrameCounter-vp_vf < 5)
933 { dx += vp_vx; dy += vp_vy; }
934 vp_vx=dx; vp_vy=dy; vp_vf=Game.FrameCounter;
935 vp->ViewX+=dx; vp->ViewY+=dy;
936 return true;
937 }
938
939bool C4GraphicsSystem::ViewportZoomOut()
940 {
941 for (C4Viewport *vp = FirstViewport; vp; vp = vp->Next) vp->ChangeZoom(1.0f/C4GFX_ZoomStep);
942 if (FirstViewport) FlashMessage(FormatString("%s: %f", "[!]Zoom", (float)FirstViewport->ZoomTarget).getData());
943 return true;
944 }
945
946bool C4GraphicsSystem::ViewportZoomIn()
947 {
948 for (C4Viewport *vp = FirstViewport; vp; vp = vp->Next) vp->ChangeZoom(C4GFX_ZoomStep);
949 if (FirstViewport) FlashMessage(FormatString("%s: %f", "[!]Zoom", (float)FirstViewport->ZoomTarget).getData());
950 return true;
951 }
952
953C4GraphicsSystem GraphicsSystem;