/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 1998-2000, Matthes Bender * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ /* A viewport to each player */ #include "C4Include.h" #include "game/C4Viewport.h" #include "editor/C4ViewportWindow.h" #include "editor/C4Console.h" #include "object/C4Def.h" #include "object/C4Object.h" #include "game/C4FullScreen.h" #include "lib/C4Stat.h" #include "player/C4Player.h" #include "object/C4ObjectMenu.h" #include "gui/C4MouseControl.h" #include "landscape/C4PXS.h" #include "gui/C4GameMessage.h" #include "gui/C4ScriptGuiWindow.h" #include "graphics/C4GraphicsResource.h" #include "game/C4GraphicsSystem.h" #include "landscape/C4Landscape.h" #include "landscape/C4Sky.h" #include "player/C4PlayerList.h" #include "object/C4GameObjects.h" #include "network/C4Network2.h" #include "landscape/fow/C4FoWRegion.h" void C4Viewport::DropFile(const char* fileName, float x, float y) { Game.DropFile(fileName, GetViewX()+x/Zoom, GetViewY()+y/Zoom); } bool C4Viewport::UpdateOutputSize() { if (!pWindow) return false; // Output size C4Rect rect; #ifdef USE_GTK GtkAllocation allocation; gtk_widget_get_allocation(GTK_WIDGET(pWindow->render_widget), &allocation); // Use only size of drawing area without scrollbars rect.x = allocation.x; rect.y = allocation.y; rect.Wdt = allocation.width; rect.Hgt = allocation.height; #else if (!pWindow->GetSize(&rect)) return false; #endif OutX=rect.x; OutY=rect.y; ViewWdt=rect.Wdt; ViewHgt=rect.Hgt; ScrollView(0,0); // Scroll bars ScrollBarsByViewPosition(); // Reset menus ResetMenuPositions=true; // update internal GL size if (pWindow && pWindow->pSurface) pWindow->pSurface->UpdateSize(ViewWdt, ViewHgt); // Done return true; } C4Viewport::C4Viewport() { Player = 0; viewX = viewY = 0; targetViewX = targetViewY = 0; ViewWdt = ViewHgt = 0; BorderLeft = BorderTop = BorderRight = BorderBottom = 0; OutX = OutY = ViewWdt = ViewHgt = 0; DrawX = DrawY = 0; Zoom = 1.0; ZoomTarget = 0.0; ViewportOpenFrame = 0; ZoomLimitMin = ZoomLimitMax = 0; // no limit Next = NULL; PlayerLock = true; ResetMenuPositions = false; viewOffsX = viewOffsY = 0; fIsNoOwnerViewport = false; } C4Viewport::~C4Viewport() { DisableFoW(); if (pWindow) { delete pWindow->pSurface; pWindow->Clear(); } } void C4Viewport::DrawOverlay(C4TargetFacet &cgo, const ZoomData &GameZoom) { if (!Game.C4S.Head.Film || !Game.C4S.Head.Replay) { // Player info C4ST_STARTNEW(PInfoStat, "C4Viewport::DrawOverlay: Player Info") DrawPlayerInfo(cgo); C4ST_STOP(PInfoStat) C4ST_STARTNEW(MenuStat, "C4Viewport::DrawOverlay: Menu") DrawMenu(cgo); C4ST_STOP(MenuStat) } // Control overlays (if not film/replay) if (!Game.C4S.Head.Film || !Game.C4S.Head.Replay) { // Mouse control if (::MouseControl.IsViewport(this)) { C4ST_STARTNEW(MouseStat, "C4Viewport::DrawOverlay: Mouse") ::MouseControl.Draw(cgo, GameZoom); // Draw GUI-mouse in EM if active if (pWindow) ::pGUI->RenderMouse(cgo); C4ST_STOP(MouseStat) } } } void C4Viewport::DrawMenu(C4TargetFacet &cgo0) { // Get player C4Player *pPlr = ::Players.Get(Player); // for menus, cgo is using GUI-syntax: TargetX/Y marks the drawing offset; x/y/Wdt/Hgt marks the offset rect C4TargetFacet cgo; cgo.Set(cgo0); cgo.X = 0; cgo.Y = 0; cgo.Wdt = cgo0.Wdt * cgo0.Zoom; cgo.Hgt = cgo0.Hgt * cgo0.Zoom; cgo.TargetX = float(cgo0.X); cgo.TargetY = float(cgo0.Y); cgo.Zoom = 1; pDraw->SetZoom(cgo.X, cgo.Y, cgo.Zoom); // Player eliminated if (pPlr && pPlr->Eliminated) { pDraw->TextOut(FormatString(LoadResStr(pPlr->Surrendered ? "IDS_PLR_SURRENDERED" : "IDS_PLR_ELIMINATED"),pPlr->GetName()).getData(), ::GraphicsResource.FontRegular, 1.0, cgo.Surface,cgo.TargetX+cgo.Wdt/2,cgo.TargetY+2*cgo.Hgt/3,0xfaFF0000,ACenter); return; } // Player cursor object menu if (pPlr && pPlr->Cursor && pPlr->Cursor->Menu) { if (ResetMenuPositions) pPlr->Cursor->Menu->ResetLocation(); // if mouse is dragging, make it transparent to easy construction site drag+drop bool fDragging=false; if (::MouseControl.IsDragging() && ::MouseControl.IsViewport(this)) { fDragging = true; pDraw->ActivateBlitModulation(0x4fffffff); } // draw menu pPlr->Cursor->Menu->Draw(cgo); // reset modulation for dragging if (fDragging) pDraw->DeactivateBlitModulation(); } // Player menu if (pPlr && pPlr->Menu.IsActive()) { if (ResetMenuPositions) pPlr->Menu.ResetLocation(); pPlr->Menu.Draw(cgo); } // Fullscreen menu if (FullScreen.pMenu && FullScreen.pMenu->IsActive()) { if (ResetMenuPositions) FullScreen.pMenu->ResetLocation(); FullScreen.pMenu->Draw(cgo); } // Flag done ResetMenuPositions=false; // restore Zoom pDraw->SetZoom(cgo0.X, cgo0.Y, cgo0.Zoom); } void C4Viewport::Draw(C4TargetFacet &cgo0, bool fDrawGame, bool fDrawOverlay) { #ifdef USE_CONSOLE // No drawing in console mode return; #endif C4TargetFacet cgo; cgo.Set(cgo0); ZoomData GameZoom; GameZoom.X = cgo.X; GameZoom.Y = cgo.Y; GameZoom.Zoom = cgo.Zoom; // Draw landscape borders if (BorderLeft > 0.0f) pDraw->BlitSurfaceTile(::GraphicsResource.fctBackground.Surface, cgo.Surface, DrawX, DrawY, BorderLeft, ViewHgt, -DrawX, -DrawY, NULL); if (BorderTop > 0.0f) pDraw->BlitSurfaceTile(::GraphicsResource.fctBackground.Surface, cgo.Surface, DrawX + BorderLeft, DrawY, ViewWdt - BorderLeft - BorderRight, BorderTop, -DrawX - BorderLeft, -DrawY, NULL); if (BorderRight > 0.0f) pDraw->BlitSurfaceTile(::GraphicsResource.fctBackground.Surface, cgo.Surface, DrawX + ViewWdt - BorderRight, DrawY, BorderRight, ViewHgt, -DrawX - ViewWdt + BorderRight, -DrawY, NULL); 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, NULL); // Compute non-bordered viewport area cgo.X += BorderLeft; cgo.Y += BorderTop; cgo.Wdt -= (BorderLeft + BorderRight) / cgo.Zoom; cgo.Hgt -= (BorderTop + BorderBottom) / cgo.Zoom; cgo.TargetX += BorderLeft / Zoom; cgo.TargetY += BorderTop / Zoom; // Apply Zoom GameZoom.X = cgo.X; GameZoom.Y = cgo.Y; pDraw->SetZoom(GameZoom); // Set clipper to integer bounds around floating point viewport region const FLOAT_RECT clipRect = { DrawX + BorderLeft, DrawX + ViewWdt - BorderRight, DrawY + BorderTop, DrawY + ViewHgt - BorderBottom }; const C4Rect& clipRectInt(clipRect); pDraw->SetPrimaryClipper(clipRectInt.x, clipRectInt.y, clipRectInt.x + clipRectInt.Wdt - 1, clipRectInt.y + clipRectInt.Hgt - 1); last_game_draw_cgo = cgo; if (fDrawGame) { // --- activate FoW here --- // Render FoW only if active for player C4FoWRegion* pFoW = NULL; if (Player != NO_OWNER) { C4Player *pPlr = ::Players.Get(Player); assert(pPlr != NULL); if (pPlr->fFogOfWar) pFoW = this->pFoW.get(); } // Update FoW if (pFoW) { // Viewport region in landscape coordinates const FLOAT_RECT vpRect = { cgo.TargetX, cgo.TargetX + cgo.Wdt, cgo.TargetY, cgo.TargetY + cgo.Hgt }; // Region in which the light is calculated // At the moment, just choose integer coordinates to surround the viewport const C4Rect lightRect(vpRect); if (!lightRect.Wdt || !lightRect.Hgt) { // Do not bother initializing FoW on empty region; would cause errors in drawing proc pFoW = NULL; } else { pFoW->Update(lightRect, vpRect); if (!pFoW->Render()) { // If FoW init fails, do not set it for further drawing pFoW = NULL; } } } pDraw->SetFoW(pFoW); C4ST_STARTNEW(SkyStat, "C4Viewport::Draw: Sky") ::Landscape.GetSky().Draw(cgo); C4ST_STOP(SkyStat) ::Objects.Draw(cgo, Player, -2147483647 - 1 /* INT32_MIN */, 0); // Draw Landscape C4ST_STARTNEW(LandStat, "C4Viewport::Draw: Landscape") ::Landscape.Draw(cgo, pFoW); C4ST_STOP(LandStat) // draw PXS (unclipped!) C4ST_STARTNEW(PXSStat, "C4Viewport::Draw: PXS") ::PXS.Draw(cgo); C4ST_STOP(PXSStat) // Draw objects which are behind the particle plane. const int particlePlane = 900; C4ST_STARTNEW(ObjStat, "C4Viewport::Draw: Objects (1)") ::Objects.Draw(cgo, Player, 1, particlePlane); C4ST_STOP(ObjStat) // Draw global dynamic particles on a specific Plane // to enable scripters to put objects both behind and in front of particles. C4ST_STARTNEW(PartStat, "C4Viewport::Draw: Dynamic Particles") ::Particles.DrawGlobalParticles(cgo); C4ST_STOP(PartStat) // Now the remaining objects in front of the particles (e.g. GUI elements) C4ST_STARTNEW(Obj2Stat, "C4Viewport::Draw: Objects (2)") ::Objects.Draw(cgo, Player, particlePlane + 1, 2147483647 /* INT32_MAX */); C4ST_STOP(Obj2Stat) // Draw everything else without FoW pDraw->SetFoW(NULL); } else { pDraw->DrawBoxDw(cgo.Surface, cgo.X, cgo.Y, cgo.X + cgo.Wdt, cgo.Y + cgo.Hgt, 0xff000000); } // Draw PathFinder if (::GraphicsSystem.ShowPathfinder) Game.PathFinder.Draw(cgo); // Draw overlay if (!Game.C4S.Head.Film || !Game.C4S.Head.Replay) Game.DrawCrewOverheadText(cgo, Player); // Lights overlay if (::GraphicsSystem.ShowLights && pFoW) pFoW->Render(&cgo); if (fDrawOverlay) { // Determine zoom of overlay float fGUIZoom = GetGUIZoom(); // now restore complete cgo range for overlay drawing pDraw->SetZoom(DrawX,DrawY, fGUIZoom); pDraw->SetPrimaryClipper(DrawX,DrawY,DrawX+(ViewWdt-1),DrawY+(ViewHgt-1)); C4TargetFacet gui_cgo; gui_cgo.Set(cgo0); gui_cgo.X = DrawX; gui_cgo.Y = DrawY; gui_cgo.Zoom = fGUIZoom; gui_cgo.Wdt = int(float(ViewWdt)/fGUIZoom); gui_cgo.Hgt = int(float(ViewHgt)/fGUIZoom); gui_cgo.TargetX = GetViewX(); gui_cgo.TargetY = GetViewY(); last_gui_draw_cgo = gui_cgo; // draw custom GUI objects ::Objects.ForeObjects.DrawIfCategory(gui_cgo, Player, C4D_Foreground, false); // Draw overlay C4ST_STARTNEW(OvrStat, "C4Viewport::Draw: Overlay") if (Application.isEditor) Console.EditCursor.Draw(cgo); // Game messages C4ST_STARTNEW(MsgStat, "C4Viewport::DrawOverlay: Messages") pDraw->SetZoom(0, 0, 1.0); ::Messages.Draw(gui_cgo, cgo, Player); C4ST_STOP(MsgStat) // ingame menus C4ST_STARTNEW(GuiWindowStat, "C4Viewport::DrawOverlay: Menus") pDraw->SetZoom(0, 0, 1.0); ::Game.ScriptGuiRoot->DrawAll(gui_cgo, Player); C4ST_STOP(GuiWindowStat) DrawOverlay(gui_cgo, GameZoom); // Netstats if (::GraphicsSystem.ShowNetstatus) ::Network.DrawStatus(gui_cgo); C4ST_STOP(OvrStat) } // Remove zoom n clippers pDraw->SetZoom(0, 0, 1.0); pDraw->NoPrimaryClipper(); } void C4Viewport::BlitOutput() { if (pWindow) { C4Rect rtSrc,rtDst; rtSrc.x = DrawX; rtSrc.y = DrawY; rtSrc.Wdt = ViewWdt; rtSrc.Hgt = ViewHgt; rtDst.x = OutX; rtDst.y = OutY; rtDst.Wdt = ViewWdt; rtDst.Hgt = ViewHgt; pWindow->pSurface->PageFlip(&rtSrc, &rtDst); } } void C4Viewport::Execute() { // Adjust position AdjustZoomAndPosition(); // Current graphics output C4TargetFacet cgo; C4Surface *target = pWindow ? pWindow->pSurface : FullScreen.pSurface; cgo.Set(target,DrawX,DrawY,float(ViewWdt)/Zoom,float(ViewHgt)/Zoom,GetViewX(),GetViewY(),Zoom); pDraw->PrepareRendering(target); // Do not spoil game contents on owner-less viewport bool draw_game = true; if (Player == NO_OWNER) if (!::Application.isEditor && !::Game.DebugMode) if (!::Network.isEnabled() || !::Network.Clients.GetLocal() || !::Network.Clients.GetLocal()->isObserver()) if (::Game.PlayerInfos.GetJoinIssuedPlayerCount() > 0) // free scrolling allowed if the scenario was started explicitely without players to inspect the landscape draw_game = false; // Draw Draw(cgo, draw_game, true); // Blit output BlitOutput(); } /* This method is called whenever the viewport size is changed. Thus, its job is to recalculate the zoom and zoom limits with the new values for ViewWdt and ViewHgt. */ void C4Viewport::CalculateZoom() { // Zoom is only initialized by player or global setting during viewport creation time, because after that // the player may have changed to another preferred zoom. // However, viewports may change multiple times during startup (because of NO_OWNER viewport being deleted // and possible other player joins). So check by frame counter. Zoom changes done in paused mode on the // player init frame will be lost, but that should not be a problem. if(ViewportOpenFrame >= Game.FrameCounter) InitZoom(); C4Player *plr = Players.Get(Player); if (plr) plr->ZoomLimitsToViewport(this); else SetZoomLimits(0.8*std::min(float(ViewWdt)/::Landscape.GetWidth(),float(ViewHgt)/::Landscape.GetHeight()), 8); } void C4Viewport::InitZoom() { C4Player *plr = Players.Get(Player); if (plr) { plr->ZoomToViewport(this, true); } else { ZoomTarget = std::max(float(ViewWdt)/::Landscape.GetWidth(), 1.0f); Zoom = ZoomTarget; } } void C4Viewport::ChangeZoom(float by_factor) { ZoomTarget *= by_factor; if (ZoomLimitMin && ZoomTarget < ZoomLimitMin) ZoomTarget = ZoomLimitMin; if (ZoomLimitMax && ZoomTarget > ZoomLimitMax) ZoomTarget = ZoomLimitMax; } void C4Viewport::SetZoom(float to_value, bool direct) { ZoomTarget = to_value; if (Player != NO_OWNER || !::Application.isEditor) { if (ZoomLimitMin && ZoomTarget < ZoomLimitMin) ZoomTarget = ZoomLimitMin; if (ZoomLimitMax && ZoomTarget > ZoomLimitMax) ZoomTarget = ZoomLimitMax; } // direct: Set zoom without scrolling to it if (direct) Zoom = ZoomTarget; } void C4Viewport::SetZoomLimits(float to_min_zoom, float to_max_zoom) { ZoomLimitMin = to_min_zoom; ZoomLimitMax = to_max_zoom; if (ZoomLimitMax && ZoomLimitMax < ZoomLimitMin) ZoomLimitMax = ZoomLimitMin; ChangeZoom(1); // Constrains zoom to limit. } float C4Viewport::GetZoomByViewRange(int32_t size_x, int32_t size_y) const { // set zoom such that both size_x and size_y are guarantueed to fit into the viewport range // determine whether zoom is to be calculated by x or by y bool zoom_by_y = false; if (size_x && size_y) { zoom_by_y = (size_y * ViewWdt > size_x * ViewHgt); } else if (size_y) { // no x size passed - zoom by y zoom_by_y = true; } else { // 0/0 size passed - zoom to default if (!size_x) size_x = C4VP_DefViewRangeX * 2; zoom_by_y = false; } // zoom calculation if (zoom_by_y) return float(ViewHgt) / size_y; else return float(ViewWdt) / size_x; } void C4Viewport::SetZoom(float zoomValue) { Zoom = zoomValue; // also set target to prevent zoom from changing back ZoomTarget = zoomValue; } void C4Viewport::AdjustZoomAndPosition() { // Move zoom towards target zoom if (ZoomTarget < 0.000001f) CalculateZoom(); // Change Zoom if (Zoom != ZoomTarget) { float DeltaZoom = Zoom / ZoomTarget; if (DeltaZoom<1) DeltaZoom = 1 / DeltaZoom; // Minimal Zoom change factor static const float Z0 = pow(C4GFX_ZoomStep, 1.0f / 8.0f); // We change zoom based on (logarithmic) distance of current zoom // to target zoom. The greater the distance the more we adjust the // zoom in one frame. There is a minimal zoom change Z0 to make sure // we reach ZoomTarget in finite time. float ZoomAdjustFactor = Z0 * pow(DeltaZoom, 1.0f / 8.0f); if (Zoom < ZoomTarget) Zoom = std::min(Zoom * ZoomAdjustFactor, ZoomTarget); if (Zoom > ZoomTarget) Zoom = std::max(Zoom / ZoomAdjustFactor, ZoomTarget); } // Adjust position AdjustPosition(false); } void C4Viewport::AdjustPosition(bool immediate) { if (ViewWdt == 0 || ViewHgt == 0) { // zero-sized viewport, possibly minimized editor window // don't do anything then return; } assert(Zoom>0); assert(ZoomTarget>0); float ViewportScrollBorder = fIsNoOwnerViewport ? 0 : float(C4ViewportScrollBorder); C4Player *pPlr = ::Players.Get(Player); // View position if (PlayerLock && ValidPlr(Player)) { float scrollRange, extraBoundsX, extraBoundsY; scrollRange = extraBoundsX = extraBoundsY = 0; // target view position (landscape coordinates) float targetCenterViewX = fixtof(pPlr->ViewX); float targetCenterViewY = fixtof(pPlr->ViewY); if (pPlr->ViewMode == C4PVM_Scrolling) { extraBoundsX = extraBoundsY = ViewportScrollBorder; } else { scrollRange = std::min(ViewWdt/(10*Zoom),ViewHgt/(10*Zoom)); // if view is close to border, allow scrolling if (targetCenterViewX < ViewportScrollBorder) extraBoundsX = std::min(ViewportScrollBorder - targetCenterViewX, ViewportScrollBorder); else if (targetCenterViewX >= ::Landscape.GetWidth() - ViewportScrollBorder) extraBoundsX = std::min(targetCenterViewX - ::Landscape.GetWidth(), 0) + ViewportScrollBorder; if (targetCenterViewY < ViewportScrollBorder) extraBoundsY = std::min(ViewportScrollBorder - targetCenterViewY, ViewportScrollBorder); else if (targetCenterViewY >= ::Landscape.GetHeight() - ViewportScrollBorder) extraBoundsY = std::min(targetCenterViewY - ::Landscape.GetHeight(), 0) + ViewportScrollBorder; } extraBoundsX = std::max(extraBoundsX, (ViewWdt/Zoom - ::Landscape.GetWidth())/2 + 1); extraBoundsY = std::max(extraBoundsY, (ViewHgt/Zoom - ::Landscape.GetHeight())/2 + 1); // add mouse auto scroll if (pPlr->MouseControl && ::MouseControl.InitCentered && Config.Controls.MouseAutoScroll) { float strength = Config.Controls.MouseAutoScroll/100.0f; targetCenterViewX += strength*(::MouseControl.VpX - ViewWdt/2.0f)/Zoom; targetCenterViewY += strength*(::MouseControl.VpY - ViewHgt/2.0f)/Zoom; } // scroll range if (!immediate) { targetCenterViewX = Clamp(targetCenterViewX, targetCenterViewX - scrollRange, targetCenterViewX + scrollRange); targetCenterViewY = Clamp(targetCenterViewY, targetCenterViewY - scrollRange, targetCenterViewY + scrollRange); } // bounds targetCenterViewX = Clamp(targetCenterViewX, ViewWdt/Zoom/2 - extraBoundsX, ::Landscape.GetWidth() - ViewWdt/Zoom/2 + extraBoundsX); targetCenterViewY = Clamp(targetCenterViewY, ViewHgt/Zoom/2 - extraBoundsY, ::Landscape.GetHeight() - ViewHgt/Zoom/2 + extraBoundsY); targetViewX = targetCenterViewX - ViewWdt/Zoom/2 + viewOffsX; targetViewY = targetCenterViewY - ViewHgt/Zoom/2 + viewOffsY; if (immediate) { // immediate scroll SetViewX(targetViewX); SetViewY(targetViewY); } else { // smooth scroll int32_t smooth = Clamp(Config.General.ScrollSmooth, 1, 50); ScrollView((targetViewX - viewX) / smooth, (targetViewY - viewY) / smooth); } } UpdateBordersX(); UpdateBordersY(); // NO_OWNER can't scroll if (fIsNoOwnerViewport) { viewOffsX=0; viewOffsY=0; } } void C4Viewport::CenterPosition() { // center viewport position on map // set center position SetViewX(::Landscape.GetWidth()/2 + ViewWdt/Zoom/2); SetViewY(::Landscape.GetHeight()/2 + ViewHgt/Zoom/2); } void C4Viewport::UpdateBordersX() { BorderLeft = std::max(-GetViewX() * Zoom, 0.0f); BorderRight = std::max(ViewWdt - ::Landscape.GetWidth() * Zoom + GetViewX() * Zoom, 0.0f); } void C4Viewport::UpdateBordersY() { BorderTop = std::max(-GetViewY() * Zoom, 0.0f); BorderBottom = std::max(ViewHgt - ::Landscape.GetHeight() * Zoom + GetViewY() * Zoom, 0.0f); } void C4Viewport::DrawPlayerInfo(C4TargetFacet &cgo) { C4Facet ccgo; if (!ValidPlr(Player)) return; // Controls DrawPlayerStartup(cgo); } bool C4Viewport::Init(int32_t iPlayer, bool fSetTempOnly) { // Fullscreen viewport initialization // Set Player if (!ValidPlr(iPlayer)) iPlayer = NO_OWNER; Player=iPlayer; ViewportOpenFrame = Game.FrameCounter; if (!fSetTempOnly) fIsNoOwnerViewport = (iPlayer == NO_OWNER); if (Application.isEditor) { // Console viewport initialization // Create window pWindow.reset(new C4ViewportWindow(this)); if (!pWindow->Init(Player)) return false; UpdateOutputSize(); // Disable player lock on unowned viewports if (!ValidPlr(Player)) TogglePlayerLock(); // Don't call Execute right away since it is not yet guaranteed that // the Player has set this as its Viewport, and the drawing routines rely // on that. } else { // Owned viewport: clear any flash message explaining observer menu if (ValidPlr(iPlayer)) ::GraphicsSystem.FlashMessage(""); } EnableFoW(); return true; } void C4Viewport::DisableFoW() { pFoW.reset(); } void C4Viewport::EnableFoW() { if (::Landscape.HasFoW() && Player != NO_OWNER) { pFoW.reset(new C4FoWRegion(::Landscape.GetFoW(), ::Players.Get(Player))); } else { DisableFoW(); } } extern int32_t DrawMessageOffset; void C4Viewport::DrawPlayerStartup(C4TargetFacet &cgo) { C4Player *pPlr; if (!(pPlr = ::Players.Get(Player))) return; if (!pPlr->LocalControl || !pPlr->ShowStartup) return; int32_t iNameHgtOff=0; // Control // unnecessary with the current control sets if (pPlr && pPlr->ControlSet) { C4Facet controlset_facet = pPlr->ControlSet->GetPicture(); if (controlset_facet.Wdt) controlset_facet.Draw(cgo.Surface, cgo.X+(cgo.Wdt-controlset_facet.Wdt)/2, cgo.Y+cgo.Hgt * 2/3 + DrawMessageOffset, 0,0); iNameHgtOff=GfxR->fctKeyboard.Hgt; } // Name pDraw->TextOut(pPlr->GetName(), ::GraphicsResource.FontRegular, 1.0, cgo.Surface, cgo.X+cgo.Wdt/2,cgo.Y+cgo.Hgt*2/3+iNameHgtOff + DrawMessageOffset, pPlr->ColorDw | 0xff000000, ACenter); } void C4Viewport::ScrollView(float byX, float byY) { SetViewX(viewX + byX); SetViewY(viewY + byY); } void C4Viewport::SetViewX(float x) { viewX = x; if (fIsNoOwnerViewport) { if(::Landscape.GetWidth() < ViewWdt / Zoom) { viewX = ::Landscape.GetWidth()/2 - ViewWdt / Zoom / 2; } else { viewX = Clamp(x, 0.0f, ::Landscape.GetWidth() - ViewWdt / Zoom); } } UpdateBordersX(); } void C4Viewport::SetViewY(float y) { viewY = y; if (fIsNoOwnerViewport) { if(::Landscape.GetHeight() < ViewHgt / Zoom) { viewY = ::Landscape.GetHeight()/2 - ViewHgt / Zoom / 2; } else { viewY = Clamp(y, 0.0f, ::Landscape.GetHeight() - ViewHgt / Zoom); } } UpdateBordersY(); } void C4Viewport::SetOutputSize(int32_t iDrawX, int32_t iDrawY, int32_t iOutX, int32_t iOutY, int32_t iOutWdt, int32_t iOutHgt) { int32_t deltaWidth = ViewWdt-iOutWdt; int32_t deltaHeight = ViewHgt-iOutHgt; // update output parameters DrawX=iDrawX; DrawY=iDrawY; OutX=iOutX; OutY=iOutY; ViewWdt=iOutWdt; ViewHgt=iOutHgt; // update view position: Remain centered at previous position // scrolling the view must be done after setting the new view width and height ScrollView(deltaWidth/2, deltaHeight/2); CalculateZoom(); // Reset menus ResetMenuPositions=true; // player uses mouse control? then clip the cursor C4Player *pPlr; if ((pPlr=::Players.Get(Player))) if (pPlr->MouseControl) { ::MouseControl.UpdateClip(); // and inform GUI ::pGUI->SetPreferredDlgRect(C4Rect(iOutX, iOutY, iOutWdt, iOutHgt)); } } void C4Viewport::ClearPointers(C4Object *pObj) { } void C4Viewport::NextPlayer() { C4Player *pPlr; int32_t iPlr; if (!(pPlr = ::Players.Get(Player))) { if (!(pPlr = ::Players.First)) return; } else if (!(pPlr = pPlr->Next)) if (Game.C4S.Head.Film && Game.C4S.Head.Replay) pPlr = ::Players.First; // cycle to first in film mode only; in network obs mode allow NO_OWNER-view if (pPlr) iPlr = pPlr->Number; else iPlr = NO_OWNER; if (iPlr != Player) Init(iPlr, true); } bool C4Viewport::IsViewportMenu(class C4Menu *pMenu) { // check all associated menus // Get player C4Player *pPlr = ::Players.Get(Player); // Player eliminated: No menu if (pPlr && pPlr->Eliminated) return false; // Player cursor object menu if (pPlr && pPlr->Cursor && pPlr->Cursor->Menu == pMenu) return true; // Player menu if (pPlr && pPlr->Menu.IsActive() && &(pPlr->Menu) == pMenu) return true; // Fullscreen menu (if active, only one viewport can exist) if (FullScreen.pMenu && FullScreen.pMenu->IsActive() && FullScreen.pMenu == pMenu) return true; // no match return false; } // C4ViewportList C4ViewportList Viewports; C4ViewportList::C4ViewportList(): FirstViewport(NULL) { ViewportArea.Default(); } C4ViewportList::~C4ViewportList() { } void C4ViewportList::Clear() { C4Viewport *next; while (FirstViewport) { next=FirstViewport->Next; delete FirstViewport; FirstViewport=next; } FirstViewport=NULL; } void C4ViewportList::Execute(bool DrawBackground) { // Background redraw if (DrawBackground) DrawFullscreenBackground(); for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next) { if (cvp->GetWindow()) cvp->GetWindow()->RequestUpdate(); else cvp->Execute(); } } void C4ViewportList::DrawFullscreenBackground() { for (int i=0, iNum=BackgroundAreas.GetCount(); iBlitSurfaceTile(::GraphicsResource.fctBackground.Surface,FullScreen.pSurface,rc.x,rc.y,rc.Wdt,rc.Hgt,-rc.x,-rc.y, NULL); } } bool C4ViewportList::CloseViewport(C4Viewport * cvp) { if (!cvp) return false; // Chop the start of the chain off if (FirstViewport == cvp) { FirstViewport = cvp->Next; delete cvp; StartSoundEffect("UI::CloseViewport"); } // Take out of the chain else for (C4Viewport * prev = FirstViewport; prev; prev = prev->Next) { if (prev->Next == cvp) { prev->Next = cvp->Next; delete cvp; StartSoundEffect("UI::CloseViewport"); } } // Recalculate viewports RecalculateViewports(); // Done return true; } #ifdef USE_WIN32_WINDOWS C4Viewport* C4ViewportList::GetViewport(HWND hwnd) { for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next) if (cvp->pWindow->hWindow==hwnd) return cvp; return NULL; } #endif bool C4ViewportList::CreateViewport(int32_t iPlayer, bool fSilent) { // Create and init new viewport, add to viewport list int32_t iLastCount = GetViewportCount(); C4Viewport *nvp = new C4Viewport; bool fOkay = nvp->Init(iPlayer, false); if (!fOkay) { delete nvp; return false; } C4Viewport *pLast; for (pLast=FirstViewport; pLast && pLast->Next; pLast=pLast->Next) {} if (pLast) pLast->Next=nvp; else FirstViewport=nvp; // Recalculate viewports RecalculateViewports(); // Viewports start off at centered position nvp->CenterPosition(); // Initial player zoom values to viewport (in case they were set early in InitializePlayer, loaded from savegame, etc.) C4Player *plr = ::Players.Get(iPlayer); if (plr) { plr->ZoomToViewport(nvp, true, false, false); plr->ZoomLimitsToViewport(nvp); } // Action sound if (GetViewportCount()!=iLastCount) if (!fSilent) StartSoundEffect("UI::CloseViewport"); return true; } void C4ViewportList::DisableFoW() { for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next) cvp->DisableFoW(); } void C4ViewportList::EnableFoW() { for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next) cvp->EnableFoW(); } void C4ViewportList::ClearPointers(C4Object *pObj) { for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next) cvp->ClearPointers(pObj); } bool C4ViewportList::CloseViewport(int32_t iPlayer, bool fSilent) { // Close all matching viewports int32_t iLastCount = GetViewportCount(); C4Viewport *next,*prev=NULL; for (C4Viewport *cvp=FirstViewport; cvp; cvp=next) { next=cvp->Next; if (cvp->Player==iPlayer || (iPlayer==NO_OWNER && cvp->fIsNoOwnerViewport)) { delete cvp; if (prev) prev->Next=next; else FirstViewport=next; } else prev=cvp; } // Anything was done? if (GetViewportCount()!=iLastCount) { // Recalculate viewports RecalculateViewports(); // Action sound if (!fSilent) StartSoundEffect("UI::CloseViewport"); } return true; } void C4ViewportList::RecalculateViewports() { // Fullscreen only if (Application.isEditor) return; // Sort viewports SortViewportsByPlayerControl(); // Viewport area int32_t iBorderTop = 0; if (Config.Graphics.UpperBoard) iBorderTop = C4UpperBoardHeight; ViewportArea.Set(FullScreen.pSurface,0,iBorderTop, C4GUI::GetScreenWdt(), C4GUI::GetScreenHgt()-iBorderTop); // Redraw flag ::GraphicsSystem.InvalidateBg(); #ifdef _WIN32 // reset mouse clipping ClipCursor(NULL); #else // StdWindow handles this. #endif // reset GUI dlg pos ::pGUI->SetPreferredDlgRect(C4Rect(ViewportArea.X, ViewportArea.Y, ViewportArea.Wdt, ViewportArea.Hgt)); // fullscreen background: First, cover all of screen BackgroundAreas.Clear(); BackgroundAreas.AddRect(C4Rect(ViewportArea.X, ViewportArea.Y, ViewportArea.Wdt, ViewportArea.Hgt)); // Viewports C4Viewport *cvp; int32_t iViews = 0; for (cvp=FirstViewport; cvp; cvp=cvp->Next) iViews++; if (!iViews) return; int32_t iViewsH = (int32_t) sqrt(float(iViews)); int32_t iViewsX = iViews / iViewsH; int32_t iViewsL = iViews % iViewsH; int32_t cViewH,cViewX,ciViewsX; int32_t cViewWdt,cViewHgt,cOffWdt,cOffHgt,cOffX,cOffY; cvp=FirstViewport; for (cViewH=0; cViewHSetOutputSize(rcOut.x,rcOut.y,rcOut.x,rcOut.y,rcOut.Wdt,rcOut.Hgt); cvp=cvp->Next; // clip down area avaiable for background drawing BackgroundAreas.ClipByRect(rcOut); } } // and finally recalculate script menus if (::Game.ScriptGuiRoot) ::Game.ScriptGuiRoot->RequestLayoutUpdate(); } int32_t C4ViewportList::GetViewportCount() { int32_t iResult = 0; for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next) iResult++; return iResult; } C4Viewport* C4ViewportList::GetViewport(int32_t iPlayer, C4Viewport* pPrev) { for (C4Viewport *cvp=pPrev ? pPrev->Next : FirstViewport; cvp; cvp=cvp->Next) if (cvp->Player==iPlayer || (iPlayer==NO_OWNER && cvp->fIsNoOwnerViewport)) return cvp; return NULL; } int32_t C4ViewportList::GetAudibility(int32_t iX, int32_t iY, int32_t *iPan, int32_t iAudibilityRadius, int32_t *outPlayer) { // default audibility radius if (!iAudibilityRadius) iAudibilityRadius = C4AudibilityRadius; // Accumulate audibility by viewports int32_t iAudible=0; *iPan = 0; for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next) { float distanceToCenterOfViewport = Distance(cvp->GetViewCenterX(),cvp->GetViewCenterY(),iX,iY); int32_t audibility = Clamp(100 - 100 * distanceToCenterOfViewport / C4AudibilityRadius, 0, 100); if (audibility > iAudible) { iAudible = audibility; if (outPlayer) *outPlayer = cvp->Player; } *iPan += (iX-(cvp->GetViewCenterX())) / 5; } *iPan = Clamp(*iPan, -100, 100); return iAudible; } void C4ViewportList::SortViewportsByPlayerControl() { // Sort bool fSorted; C4Player *pPlr1,*pPlr2; C4Viewport *pView,*pNext,*pPrev; do { fSorted = true; for (pPrev=NULL,pView=FirstViewport; pView && (pNext = pView->Next); pView=pNext) { // Get players pPlr1 = ::Players.Get(pView->Player); pPlr2 = ::Players.Get(pNext->Player); // Swap order if (pPlr1 && pPlr2 && pPlr1->ControlSet && pPlr2->ControlSet && ( pPlr1->ControlSet->GetLayoutOrder() > pPlr2->ControlSet->GetLayoutOrder() )) { if (pPrev) pPrev->Next = pNext; else FirstViewport = pNext; pView->Next = pNext->Next; pNext->Next = pView; pPrev = pNext; pNext = pView; fSorted = false; } // Don't swap else { pPrev = pView; } } } while (!fSorted); } bool C4ViewportList::ViewportNextPlayer() { // safety: switch valid? if ((!Game.C4S.Head.Film || !Game.C4S.Head.Replay) && !GetViewport(NO_OWNER)) return false; // do switch then C4Viewport *vp = GetFirstViewport(); if (!vp) return false; vp->NextPlayer(); return true; } bool C4ViewportList::FreeScroll(C4Vec2D vScrollBy) { // safety: move valid? if ((!Game.C4S.Head.Replay || !Game.C4S.Head.Film) && !GetViewport(NO_OWNER)) return false; C4Viewport *vp = GetFirstViewport(); if (!vp) return false; // move then (old static code crap...) static int32_t vp_vx=0; static int32_t vp_vy=0; static int32_t vp_vf=0; int32_t dx=vScrollBy.x; int32_t dy=vScrollBy.y; if (Game.FrameCounter-vp_vf < 5) { dx += vp_vx; dy += vp_vy; } vp_vx=dx; vp_vy=dy; vp_vf=Game.FrameCounter; vp->ScrollView(dx, dy); return true; } bool C4ViewportList::ViewportZoomOut() { for (C4Viewport *vp = FirstViewport; vp; vp = vp->Next) vp->ChangeZoom(1.0f/C4GFX_ZoomStep); return true; } bool C4ViewportList::ViewportZoomIn() { for (C4Viewport *vp = FirstViewport; vp; vp = vp->Next) vp->ChangeZoom(C4GFX_ZoomStep); return true; } void C4ViewportList::MouseMoveToViewport(int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam) { // Pass on to mouse controlled viewport for (C4Viewport *cvp=FirstViewport; cvp; cvp=cvp->Next) if (::MouseControl.IsViewport(cvp)) ::MouseControl.Move( iButton, Clamp(iX-cvp->OutX,0,cvp->ViewWdt-1), Clamp(iY-cvp->OutY,0,cvp->ViewHgt-1), dwKeyParam ); }