/* Starshatter: The Open Source Project Copyright (c) 2021-2022, Starshatter: The Open Source Project Contributors Copyright (c) 2011-2012, Starshatter OpenSource Distribution Contributors Copyright (c) 1997-2006, Destroyer Studios LLC. AUTHOR: John DiCamillo */ #include "MemDebug.h" #include "Game.h" #include "Mouse.h" #include "Universe.h" #include "Screen.h" #include "Window.h" #include "EventDispatch.h" #include "Color.h" #include "DataLoader.h" #include "Panic.h" #include "Pcx.h" #include "Bitmap.h" #include "MachineInfo.h" #include "Video.h" #include "VideoFactory.h" #include "VideoSettings.h" #include "ContentBundle.h" #include "Clock.h" #include "WndProc.h" // +--------------------------------------------------------------------+ Game* game = 0; const double MAX_FRAME_TIME_NORMAL = 1.0 / 5.0; // +--------------------------------------------------------------------+ Game::Game() : world(0), video_factory(0), video(0), video_settings(0), soundcard(0), screen(0), totaltime(0), hInst(0), hwnd(0), status(Game::OK), exit_code(0), window_style(0) { if (!game) { game = this; active = false; paused = false; server = false; show_mouse = false; frame_number = 0; max_frame_length = MAX_FRAME_TIME_NORMAL; video_settings = new(__FILE__,__LINE__) VideoSettings; is_windowed = false; is_active = false; is_device_lost = false; is_minimized = false; is_maximized = false; ignore_size_change = false; is_device_initialized = false; is_device_restored = false; } else status = TOO_MANY; } Game::~Game() { if (game == this) game = 0; delete world; delete screen; delete video_factory; delete video; delete soundcard; delete video_settings; if (status == EXIT) ShowStats(); } // +--------------------------------------------------------------------+ HINSTANCE Game::GetHINST() { if (game) return game->hInst; return 0; } HWND Game::GetHWND() { if (game) return game->hwnd; return 0; } bool Game::IsWindowed() { if (game) return game->is_windowed; return false; } // +--------------------------------------------------------------------+ bool Game::Init(HINSTANCE hi, HINSTANCE hpi, LPSTR cmdline, int nCmdShow) { status = OK; if (status == OK) { Print(" Initializing content...\n"); ContentBundle::GetInstance()->Init(); Print(" Initializing game...\n"); if (!InitGame()) { if (Panic::Panicked()) Panic::Panic("Could not initialize the game."); status = INIT_FAILED; } } return status == OK; } // +--------------------------------------------------------------------+ bool Game::InitVideo() { if (server) return true; // create a video factory, and video object: video_factory = new(__FILE__,__LINE__) VideoFactory(hwnd); if (video_factory) { Print(" Init Video...\n"); Print(" Request %s mode\n", video_settings->GetModeDescription()); video = video_factory->CreateVideo(video_settings); if (video) { if (!video->IsHardware()) { video_factory->DestroyVideo(video); video = 0; Panic::Panic("3D Hardware Not Found"); } // save a copy of the device-specific video settings: else if (video->GetVideoSettings()) { *video_settings = *video->GetVideoSettings(); is_windowed = video_settings->IsWindowed(); } } soundcard = video_factory->CreateSoundCard(); } return (video && video->Status() == Video::VIDEO_OK); } // +--------------------------------------------------------------------+ bool Game::ResetVideo() { if (server) return true; if (!video_factory) return InitVideo(); Print(" Reset Video...\n"); Print(" Request %s mode\n", video_settings->GetModeDescription()); delete screen; if (video && !video->Reset(video_settings)) { video_factory->DestroyVideo(video); video = video_factory->CreateVideo(video_settings); } if (!video || video->Status() != Video::VIDEO_OK) { Panic::Panic("Could not re-create Video Interface."); return false; } Print(" Re-created video object.\n"); // save a copy of the device-specific video settings: if (video->GetVideoSettings()) { *video_settings = *video->GetVideoSettings(); is_windowed = video_settings->IsWindowed(); } Color::UseVideo(video); screen = new(__FILE__,__LINE__) Screen(video); if (!screen) { Panic::Panic("Could not re-create Screen object."); return false; } Print(" Re-created screen object.\n"); if (!screen->SetBackgroundColor(Color::Black)) Print(" WARNING: could not set video background color to Black\n"); screen->ClearAllFrames(true); Print(" Re-established requested video parameters.\n"); Bitmap::CacheUpdate(); Print(" Refreshed texture bitmaps.\n\n"); return true; } // +--------------------------------------------------------------------+ bool Game::ResizeVideo() { if (!video || !video_settings) return false; if (!is_windowed) return false; if (ignore_size_change) return true; HRESULT hr = S_OK; RECT client_old; client_old = client_rect; // Update window properties GetWindowRect(hwnd, &bounds_rect); GetClientRect(hwnd, &client_rect); if (client_old.right - client_old.left != client_rect.right - client_rect.left || client_old.bottom - client_old.top != client_rect.bottom - client_rect.top) { // A new window size will require a new backbuffer // size, so the 3D structures must be changed accordingly. Pause(true); video_settings->is_windowed = true; video_settings->window_width = client_rect.right - client_rect.left; video_settings->window_height = client_rect.bottom - client_rect.top; ::Print("ResizeVideo() %d x %d\n", video_settings->window_width, video_settings->window_height); if (video) { video->Reset(video_settings); } Pause(false); } // save a copy of the device-specific video settings: if (video->GetVideoSettings()) { *video_settings = *video->GetVideoSettings(); is_windowed = video_settings->IsWindowed(); } screen->Resize(video_settings->window_width, video_settings->window_height); return hr == S_OK; } bool Game::ToggleFullscreen() { bool result = false; if (video && video_settings) { Pause(true); ignore_size_change = true; // Toggle the windowed state is_windowed = !is_windowed; video_settings->is_windowed = is_windowed; // Prepare window for windowed/fullscreen change AdjustWindowForChange(); // Reset the 3D device if (!video->Reset(video_settings)) { // reset failed, try to restore... ignore_size_change = false; if (!is_windowed) { // Restore window type to windowed mode is_windowed = !is_windowed; video_settings->is_windowed = is_windowed; AdjustWindowForChange(); SetWindowPos(hwnd, HWND_NOTOPMOST, bounds_rect.left, bounds_rect.top, bounds_rect.right - bounds_rect.left, bounds_rect.bottom - bounds_rect.top, SWP_SHOWWINDOW); } ::Print("Unable to toggle %s fullscreen mode.\n", is_windowed ? "into" : "out of"); } else { ignore_size_change = false; // When moving from fullscreen to windowed mode, it is important to // adjust the window size after resetting the device rather than // beforehand to ensure that you get the window size you want. For // example, when switching from 640x480 fullscreen to windowed with // a 1000x600 window on a 1024x768 desktop, it is impossible to set // the window size to 1000x600 until after the display mode has // changed to 1024x768, because windows cannot be larger than the // desktop. if (is_windowed) { SetWindowPos(hwnd, HWND_NOTOPMOST, bounds_rect.left, bounds_rect.top, bounds_rect.right - bounds_rect.left, bounds_rect.bottom - bounds_rect.top, SWP_SHOWWINDOW); } GetClientRect(hwnd, &client_rect); // Update our copy Pause(false); if (is_windowed) screen->Resize(video_settings->window_width, video_settings->window_height); else screen->Resize(video_settings->fullscreen_mode.width, video_settings->fullscreen_mode.height); result = true; } } return result; } bool Game::AdjustWindowForChange() { if (is_windowed) { // Set windowed-mode style SetWindowLong(hwnd, GWL_STYLE, window_style); if (hmenu != NULL) { SetMenu(hwnd, hmenu); hmenu = NULL; } } else { // Set fullscreen-mode style SetWindowLong(hwnd, GWL_STYLE, WS_POPUP|WS_SYSMENU|WS_VISIBLE); if (hmenu == NULL) { hmenu = GetMenu(hwnd); SetMenu(hwnd, NULL); } } return true; } // +--------------------------------------------------------------------+ bool Game::InitGame() { if (server) { Print(" InitGame() - server mode.\n"); } else { if (!InitVideo() || !video || video->Status() != Video::VIDEO_OK) { if (Panic::Panicked()) Panic::Panic("Could not create the Video Interface."); return false; } Print(" Created video object.\n"); Color::UseVideo(video); screen = new(__FILE__,__LINE__) Screen(video); if (!screen) { if (Panic::Panicked()) Panic::Panic("Could not create the Screen object."); return false; } Print(" Created screen object.\n"); if (!screen->SetBackgroundColor(Color::Black)) Print(" WARNING: could not set video background color to Black\n"); screen->ClearAllFrames(true); Print(" Established requested video parameters.\n\n"); } return true; } // +--------------------------------------------------------------------+ int Game::Run() { MSG msg; status = RUN; Print("\n"); Print("+====================================================================+\n"); Print("| RUN |\n"); Print("+====================================================================+\n"); // Polling messages from event queue until quit clock.Set(); while (status < EXIT && !Panic::Panicked()) { if (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) break; TranslateMessage(&msg); DispatchMessage(&msg); } else { if (GameLoop()) WaitMessage(); } } return exit_code ? exit_code : msg.wParam; } // +--------------------------------------------------------------------+ void Game::Exit() { Print("\n\n*** Game::Exit()\n"); status = EXIT; } // +--------------------------------------------------------------------+ void Game::Activate(bool f) { active = f; if (active && video) video->InvalidateCache(); } // +--------------------------------------------------------------------+ void Game::Pause(bool f) { if (f) { if (soundcard) soundcard->Pause(); paused = true; } else { if (soundcard) soundcard->Resume(); paused = false; } } // +--------------------------------------------------------------------+ bool Game::GameLoop() { bool wait_for_windows_events = true; if (active && !paused) { if (!server) { // Route Events to EventTargets EventDispatch* ed = EventDispatch::GetInstance(); if (ed) ed->Dispatch(); } UpdateWorld(); GameState(); if (!server) { UpdateScreen(); CollectStats(); } wait_for_windows_events = false; } else if (active && paused) { if (GetKey()=='P') Pause(false); } clock.Step(); frame_number++; Mouse::w = 0; return wait_for_windows_events; } // +--------------------------------------------------------------------+ void Game::UpdateWorld() { if (world) world->ExecFrame(clock.Delta()); } // +--------------------------------------------------------------------+ void Game::GameState() { } // +--------------------------------------------------------------------+ void Game::UpdateScreen() { if (!screen || !video) return; if (screen->Refresh()) { video->Present(); } else { Panic::Panic("Screen refresh failed."); } } // +--------------------------------------------------------------------+ Game* Game::GetInstance() { return game; } // +--------------------------------------------------------------------+ void Game::CollectStats() { if (!totaltime) totaltime = clock.RealTime(); if (video) { stats.nframe = video->GetStats().nframe; stats.nverts = video->GetStats().nverts; stats.npolys = video->GetStats().npolys; stats.nlines = video->GetStats().nlines; stats.ncalls += video->GetStats().ncalls; stats.total_verts += stats.nverts; stats.total_polys += stats.npolys; stats.total_lines += stats.nlines; } } // +--------------------------------------------------------------------+ void Game::ShowStats() { if (server) return; totaltime = clock.RealTime() - totaltime; Print("\n"); Print("Performance Data:\n"); Print("-----------------\n"); Print(" Time: %d msec\n", totaltime); Print(" Frames: %d\n", stats.nframe); Print(" Polys Rendered: %d\n", stats.total_polys); Print(" Lines Rendered: %d\n", stats.total_lines); Print(" Verts Rendered: %d\n", stats.total_verts); Print(" Render Calls: %d\n", stats.ncalls); Print("\n"); Print("Performance Statistics:\n"); Print("-----------------------\n"); Print(" Frames/Second: %.2f\n", (stats.nframe * 1000.0) / totaltime); Print(" Polys/Frame: %.2f\n", (double) stats.total_polys / (double) stats.nframe); Print(" Polys/Call: %.2f\n", (double) stats.total_polys / (double) stats.ncalls); Print(" Polys/Second: %.2f\n", (stats.total_polys * 1000.0) / totaltime); Print(" Lines/Second: %.2f\n", (stats.total_lines * 1000.0) / totaltime); Print(" Verts/Second: %.2f\n", (stats.total_verts * 1000.0) / totaltime); Print("\n"); } // +====================================================================+ Clock* Game::GetClock() { return &clock; } DWORD Game::Frame() { return frame_number; }