/* 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 "Keyboard.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" // +--------------------------------------------------------------------+ 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; } Color Game::GetScreenColor() { return screen_color; } void Game::SetScreenColor(Color c) { screen_color = c; if (screen) screen->SetBackgroundColor(c); } int Game::GetScreenWidth() { if (video) return video->Width(); return 0; } int Game::GetScreenHeight() { if (video) return video->Height(); return 0; } // +--------------------------------------------------------------------+ 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"); } // +====================================================================+ // WndProc // +====================================================================+ #ifndef WM_MOUSEWHEEL #define WM_MOUSEWHEEL 0x20A #endif LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM uParam, LPARAM lParam) { switch (message) { case WM_SYSKEYDOWN: if (uParam == VK_TAB || uParam == VK_F4) return DefWindowProc(hwnd, message, uParam, lParam); return 0; case WM_MENUCHAR: return MNC_CLOSE << 16; case WM_ACTIVATEAPP: // Keep track of whether or not the app is in the foreground if (game) game->Activate(uParam?true:false); break; case WM_PAINT: if (!game || !game->OnPaint()) return DefWindowProc(hwnd, message, uParam, lParam); break; case WM_SETCURSOR: if (game && game->ShowMouse()) { return DefWindowProc(hwnd, message, uParam, lParam); } else { // hide the windows mouse cursor SetCursor(NULL); return 1; } break; case WM_ENTERSIZEMOVE: // Halt frame movement while the app is sizing or moving if (game) game->Pause(true); break; case WM_SIZE: // Pick up possible changes to window style due to maximize, etc. if (game && game->hwnd != NULL ) { game->window_style = GetWindowLong(game->hwnd, GWL_STYLE ); if (uParam == SIZE_MINIMIZED) { game->Pause(true); // Pause while we're minimized game->is_minimized = true; game->is_maximized = false; } else if (uParam == SIZE_MAXIMIZED) { if (game->is_minimized) game->Pause(false); // Unpause since we're no longer minimized game->is_minimized = false; game->is_maximized = true; game->ResizeVideo(); } else if (uParam == SIZE_RESTORED) { if (game->is_maximized) { game->is_maximized = false; game->ResizeVideo(); } else if (game->is_minimized) { game->Pause(false); // Unpause since we're no longer minimized game->is_minimized = false; game->ResizeVideo(); } else { // If we're neither maximized nor minimized, the window size // is changing by the user dragging the window edges. In this // case, we don't reset the device yet -- we wait until the // user stops dragging, and a WM_EXITSIZEMOVE message comes. } } } break; case WM_EXITSIZEMOVE: if (game) { game->Pause(false); game->ResizeVideo(); } break; case WM_ENTERMENULOOP: if (game) game->Pause(true); break; case WM_EXITMENULOOP: if (game) game->Pause(false); break; /* case WM_HELP: if (game) return game->OnHelp(); break; */ case WM_KEYDOWN: BufferKey(uParam); return 0; case WM_DESTROY: PostQuitMessage(0); break; case WM_MOUSEMOVE: Mouse::x = LOWORD(lParam); Mouse::y = HIWORD(lParam); break; case WM_LBUTTONDOWN: Mouse::l = 1; break; case WM_LBUTTONDBLCLK: Mouse::l = 2; break; case WM_LBUTTONUP: Mouse::l = 0; break; case WM_MBUTTONDOWN: Mouse::m = 1; break; case WM_MBUTTONDBLCLK: Mouse::m = 2; break; case WM_MBUTTONUP: Mouse::m = 0; break; case WM_RBUTTONDOWN: Mouse::r = 1; break; case WM_RBUTTONDBLCLK: Mouse::r = 2; break; case WM_RBUTTONUP: Mouse::r = 0; break; case WM_MOUSEWHEEL: { int w = (int) (uParam >> 16); if (w > 32000) w -= 65536; Mouse::w += w; } break; case WM_CLOSE: if (game) // && game->Server()) game->Exit(); break; default: return DefWindowProc(hwnd, message, uParam, lParam); } return 0; } // +====================================================================+ const int MAX_KEY_BUF = 512; static int vkbuf[MAX_KEY_BUF]; static int vkshiftbuf[MAX_KEY_BUF]; static int vkins = 0; static int vkext = 0; void FlushKeys() { Keyboard::FlushKeys(); vkins = vkext = 0; } void BufferKey(int vkey) { if (vkey < 1) return; int shift = 0; if (GetAsyncKeyState(VK_SHIFT)) shift |= 1; if (GetAsyncKeyState(VK_CONTROL)) shift |= 2; if (GetAsyncKeyState(VK_MENU)) shift |= 4; vkbuf[vkins] = vkey; vkshiftbuf[vkins++] = shift; if (vkins >= MAX_KEY_BUF) vkins = 0; if (vkins == vkext) { vkext++; if (vkext >= MAX_KEY_BUF) vkext = 0; } } int GetKey() { if (vkins == vkext) return 0; int result = vkbuf[vkext++]; if (vkext >= MAX_KEY_BUF) vkext = 0; return result; } int GetKeyPlus(int& key, int& shift) { if (vkins == vkext) return 0; key = vkbuf[vkext]; shift = vkshiftbuf[vkext++]; if (vkext >= MAX_KEY_BUF) vkext = 0; return key; } // +====================================================================+ Clock* Game::GetClock() { return &clock; } DWORD Game::Frame() { return frame_number; }