PageRenderTime 109ms CodeModel.GetById 23ms app.highlight 79ms RepoModel.GetById 0ms app.codeStats 0ms

/src/C4GraphicsSystem.cpp

https://bitbucket.org/randrian/openclonk2
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;