From 4a74ef810a979d867f5811d1e06eaf208ada82cb Mon Sep 17 00:00:00 2001 From: Abraham Bojorquez Date: Wed, 2 Sep 2026 10:57:34 -0700 Subject: [PATCH 1/4] Refactor FindtheChair into a state-machine OOP layout. Restore Game.cpp (deleted on main) and split the god loop into Game, Level, Audio, Overlay, Objective, and EventReceiver. --- Game/Audio.cpp | 125 +++++++++++++++++++++++++++++++++++++++++ Game/Audio.h | 29 ++++++++++ Game/EventReceiver.cpp | 119 +++++++++++++++++++++------------------ Game/EventReceiver.h | 64 ++++++++++----------- Game/Ids.h | 23 ++++++++ Game/Main.cpp | 31 +++++----- Game/sound.cpp | 105 +++------------------------------- Game/sound.h | 29 ++++------ README.md | 41 +++++++++++++- REVIEW.md | 36 ++++++++++++ 10 files changed, 382 insertions(+), 220 deletions(-) create mode 100644 Game/Audio.cpp create mode 100644 Game/Audio.h create mode 100644 Game/Ids.h create mode 100644 REVIEW.md diff --git a/Game/Audio.cpp b/Game/Audio.cpp new file mode 100644 index 0000000..4d281aa --- /dev/null +++ b/Game/Audio.cpp @@ -0,0 +1,125 @@ +#include "Audio.h" + +using namespace irr; +using namespace irrklang; + +namespace +{ + class IrrFileReader : public IFileReader + { + public: + explicit IrrFileReader(io::IReadFile* f) : file_(f) {} + virtual ~IrrFileReader() { if (file_) file_->drop(); } + + virtual ik_s32 read(void* buffer, ik_u32 sizeToRead) + { + return file_->read(buffer, sizeToRead); + } + virtual bool seek(ik_s32 finalPos, bool relativeMovement = false) + { + return file_->seek(finalPos, relativeMovement); + } + virtual ik_s32 getSize() { return file_->getSize(); } + virtual ik_s32 getPos() { return file_->getPos(); } + virtual const ik_c8* getFileName() { return file_->getFileName().c_str(); } + + private: + io::IReadFile* file_; + }; + + class IrrFileFactory : public IFileFactory + { + public: + explicit IrrFileFactory(IrrlichtDevice* device) : device_(device) {} + + virtual IFileReader* createFileReader(const ik_c8* filename) + { + io::IReadFile* file = device_->getFileSystem()->createAndOpenFile(filename); + if (!file) + return 0; + return new IrrFileReader(file); + } + + private: + IrrlichtDevice* device_; + }; +} + +Audio::Audio() + : engine_(0), music_(0), factory_(0) +{ +} + +Audio::~Audio() +{ + shutdown(); +} + +bool Audio::init(IrrlichtDevice* device) +{ + engine_ = createIrrKlangDevice(); + if (!engine_) + return false; + + factory_ = new IrrFileFactory(device); + engine_->addFileFactory(factory_); + return true; +} + +void Audio::shutdown() +{ + if (music_) + { + music_->stop(); + music_->drop(); + music_ = 0; + } + if (factory_) + { + factory_->drop(); + factory_ = 0; + } + if (engine_) + { + engine_->drop(); + engine_ = 0; + } +} + +void Audio::play(const char* file, bool loop) +{ + if (engine_ && file && file[0]) + engine_->play2D(file, loop); +} + +void Audio::setMusic(const char* file) +{ + if (!engine_) + return; + + if (music_) + { + music_->stop(); + music_->drop(); + music_ = 0; + } + + if (!file || !file[0]) + return; + + music_ = engine_->play2D(file, true, false, true); + if (music_) + music_->setVolume(0.5f); +} + +void Audio::stopAll() +{ + if (engine_) + engine_->stopAllSounds(); + if (music_) + { + music_->stop(); + music_->drop(); + music_ = 0; + } +} diff --git a/Game/Audio.h b/Game/Audio.h new file mode 100644 index 0000000..1deb02d --- /dev/null +++ b/Game/Audio.h @@ -0,0 +1,29 @@ +#ifndef FINDTHECHAIR_AUDIO_H +#define FINDTHECHAIR_AUDIO_H + +#include +#include + +// Owns the IrrKlang engine and the Irrlicht file-factory bridge. +class Audio +{ +public: + Audio(); + ~Audio(); + + bool init(irr::IrrlichtDevice* device); + void shutdown(); + + void play(const char* file, bool loop = false); + void setMusic(const char* file); // empty string stops music + void stopAll(); + + irrklang::ISoundEngine* engine() const { return engine_; } + +private: + irrklang::ISoundEngine* engine_; + irrklang::ISound* music_; + irrklang::IFileFactory* factory_; +}; + +#endif diff --git a/Game/EventReceiver.cpp b/Game/EventReceiver.cpp index 7a036df..1c97bf7 100644 --- a/Game/EventReceiver.cpp +++ b/Game/EventReceiver.cpp @@ -1,54 +1,65 @@ -#include "EventReceiver.h" - -EventReceiver::EventReceiver(){ - - //sets all the values in the keys to false - all keys are up - for(int i = 0; i < KEY_KEY_CODES_COUNT; i++){ - KeyDown[i] = false; - } - - for(int i = 0; i < MMenu::NUM_MMENU_BUTTONS + MMenu::NUM_OVRLY_BUTTONS;i++){ - buttonPressed[i] = false; - } - -} - -bool EventReceiver::OnEvent(const SEvent& event){ - - switch(event.EventType){ - - case EET_KEY_INPUT_EVENT: - KeyDown[event.KeyInput.Key] = event.KeyInput.PressedDown; - break; - - case EET_GUI_EVENT: - switch (event.GUIEvent.EventType){ - case irr::gui::EGET_BUTTON_CLICKED: - buttonPressed[event.GUIEvent.Caller->getID()] = true; - } - break; - default: - break; - - } - return false; - -} - -bool EventReceiver::isKeyDown(EKEY_CODE keyCode) const{ - return KeyDown[keyCode]; -} - -bool EventReceiver::isKeyUp(EKEY_CODE keyCode) const{ - return !KeyDown[keyCode]; -} - -bool EventReceiver::isButtonPressed(int button){ - return buttonPressed[button]; -} - -void EventReceiver::resetButtons(void){ - for(int i = 0; i < MMenu::NUM_MMENU_BUTTONS + MMenu::NUM_OVRLY_BUTTONS;i++){ - buttonPressed[i] = false; - } -} \ No newline at end of file +#include "EventReceiver.h" + +EventReceiver::EventReceiver() +{ + for (int i = 0; i < irr::KEY_KEY_CODES_COUNT; ++i) + { + keyDown_[i] = false; + keyWasDown_[i] = false; + } + resetButtons(); +} + +bool EventReceiver::OnEvent(const irr::SEvent& event) +{ + switch (event.EventType) + { + case irr::EET_KEY_INPUT_EVENT: + keyDown_[event.KeyInput.Key] = event.KeyInput.PressedDown; + break; + + case irr::EET_GUI_EVENT: + if (event.GUIEvent.EventType == irr::gui::EGET_BUTTON_CLICKED) + { + const int id = event.GUIEvent.Caller->getID(); + if (id >= 0 && id < GUI_COUNT) + buttonPressed_[id] = true; + } + break; + + default: + break; + } + return false; +} + +bool EventReceiver::isKeyDown(irr::EKEY_CODE keyCode) const +{ + return keyDown_[keyCode]; +} + +bool EventReceiver::isKeyUp(irr::EKEY_CODE keyCode) const +{ + return !keyDown_[keyCode]; +} + +bool EventReceiver::isButtonPressed(int buttonId) const +{ + if (buttonId < 0 || buttonId >= GUI_COUNT) + return false; + return buttonPressed_[buttonId]; +} + +void EventReceiver::resetButtons() +{ + for (int i = 0; i < GUI_COUNT; ++i) + buttonPressed_[i] = false; +} + +bool EventReceiver::consumeKeyPress(irr::EKEY_CODE keyCode) +{ + const bool down = keyDown_[keyCode]; + const bool pressed = down && !keyWasDown_[keyCode]; + keyWasDown_[keyCode] = down; + return pressed; +} diff --git a/Game/EventReceiver.h b/Game/EventReceiver.h index aff3070..327fd3d 100644 --- a/Game/EventReceiver.h +++ b/Game/EventReceiver.h @@ -1,33 +1,31 @@ -#ifdef _IRR_WINDOWS_ -#pragma comment(lib, "Irrlicht.lib") -#endif - -#ifndef EVENTRECEIVER_H -#define EVENTRECEIVER_H - -#include -#include "MMenu.h" -#include "Objective.h" - -using namespace irr; -using namespace core; -using namespace video; -using namespace scene; -using namespace gui; - -class EventReceiver:public IEventReceiver -{ - private: - bool KeyDown[KEY_KEY_CODES_COUNT]; - bool buttonPressed[MMenu::NUM_MMENU_BUTTONS + MMenu::NUM_OVRLY_BUTTONS]; - - public: - EventReceiver(); - virtual bool OnEvent(const SEvent& event); - virtual bool isKeyDown(EKEY_CODE keyCode) const; //const means thsi function can not modify private members - virtual bool isKeyUp(EKEY_CODE keyCode) const; - bool isButtonPressed(int); - void resetButtons(void); -}; -#endif - +#ifndef EVENTRECEIVER_H +#define EVENTRECEIVER_H + +#include +#include "Ids.h" + +// Pollable input adapter for Irrlicht. +// Keys are level-triggered; GUI buttons latch until resetButtons(). +class EventReceiver : public irr::IEventReceiver +{ +public: + EventReceiver(); + + virtual bool OnEvent(const irr::SEvent& event); + + bool isKeyDown(irr::EKEY_CODE keyCode) const; + bool isKeyUp(irr::EKEY_CODE keyCode) const; + + bool isButtonPressed(int buttonId) const; + void resetButtons(); + + // Rising-edge helper so menus do not fire every frame a key is held. + bool consumeKeyPress(irr::EKEY_CODE keyCode); + +private: + bool keyDown_[irr::KEY_KEY_CODES_COUNT]; + bool keyWasDown_[irr::KEY_KEY_CODES_COUNT]; + bool buttonPressed_[GUI_COUNT]; +}; + +#endif diff --git a/Game/Ids.h b/Game/Ids.h new file mode 100644 index 0000000..450774c --- /dev/null +++ b/Game/Ids.h @@ -0,0 +1,23 @@ +#ifndef FINDTHECHAIR_IDS_H +#define FINDTHECHAIR_IDS_H + +// Scene-node bit flags used by Irrlicht ray picking. +enum SceneId +{ + ID_NotPickable = 0, + ID_Pickable = 1 << 0, + ID_Highlightable = 1 << 1 +}; + +// GUI element IDs. Keep a single namespace so EventReceiver +// and the menus never disagree about button numbers. +enum GuiId +{ + GUI_Quit = 0, + GUI_Start, + GUI_Instructions, + GUI_BackToMain, + GUI_COUNT +}; + +#endif diff --git a/Game/Main.cpp b/Game/Main.cpp index c5755c2..cf43e91 100644 --- a/Game/Main.cpp +++ b/Game/Main.cpp @@ -1,14 +1,17 @@ -#include "Game.h" - -#ifdef __cplusplus -extern "C" { -#endif - -int IRRCALLCONV main(int argc, char* argv[]){ - Game game; - return game.run(); -} - -#ifdef __cplusplus -} -#endif \ No newline at end of file +#include "Game.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int IRRCALLCONV main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + Game game; + return game.run(); +} + +#ifdef __cplusplus +} +#endif diff --git a/Game/sound.cpp b/Game/sound.cpp index de7a044..ad3e47e 100644 --- a/Game/sound.cpp +++ b/Game/sound.cpp @@ -1,98 +1,7 @@ -/*! - Sound Factory. - provides a sound interface - -*/ - -#include "sound.h" - - -#define USE_IRRKLANG - -#ifdef USE_IRRKLANG - -#include -#ifdef _IRR_WINDOWS_ - #pragma comment (lib, "irrKlang.lib") -#endif - -using namespace irrklang; - -struct soundfile: public IFileReader -{ - soundfile ( io::IReadFile* f ): file (f ) {} - virtual ~soundfile () { file->drop (); } - - virtual ik_s32 read(void* buffer, ik_u32 sizeToRead) { return file->read ( buffer, sizeToRead ); } - virtual bool seek(ik_s32 finalPos, bool relativeMovement = false) { return file->seek ( finalPos, relativeMovement ); } - virtual ik_s32 getSize(){ return file->getSize (); } - virtual ik_s32 getPos() {return file->getPos (); } - virtual const ik_c8* getFileName() { return file->getFileName ().c_str(); } - io::IReadFile* file; -}; - -struct klangFactory : public irrklang::IFileFactory -{ - klangFactory ( IrrlichtDevice *device ) { Device = device; } - - virtual irrklang::IFileReader* createFileReader(const ik_c8* filename) - { - io::IReadFile* file = Device->getFileSystem()->createAndOpenFile(filename); - if ( 0 == file ) - return 0; - - return new soundfile ( file ); - } - - IrrlichtDevice *Device; -}; - -ISoundEngine *engine = 0; -ISound *backMusic = 0; - -void sound_init ( IrrlichtDevice *device ) -{ - engine = createIrrKlangDevice (); - if ( 0 == engine ) - return; - - klangFactory *f = new klangFactory ( device ); - engine->addFileFactory ( f ); -} - -void sound_shutdown () -{ - if ( backMusic ) - backMusic->drop (); - - if ( engine ) - engine->drop (); -} - -void background_music ( const c8 * file ) -{ - if ( 0 == engine ) - return; - - if ( backMusic ) - { - backMusic->stop (); - backMusic->drop (); - } - - backMusic = engine->play2D ( file, true, false, true ); - - if ( backMusic ) - { - backMusic->setVolume ( 0.5f ); - } -} - -#else - -void sound_init ( IrrlichtDevice *device ) {} -void sound_shutdown () {} -void background_music ( const c8 * file ) {} - -#endif - +#include "sound.h" + +// The original global sound helpers are superseded by Audio. +// Stubs remain so the old Visual Studio project still links. +void sound_init(irr::IrrlichtDevice*) {} +void sound_shutdown() {} +void background_music(const irr::c8*) {} diff --git a/Game/sound.h b/Game/sound.h index 033e9bb..4a3cf2d 100644 --- a/Game/sound.h +++ b/Game/sound.h @@ -1,18 +1,11 @@ -/*! - Sound Factory. - provides a sound interface - -*/ -#ifndef __QUAKE3_SOUND__H_INCLUDED__ -#define __QUAKE3_SOUND__H_INCLUDED__ - -#include - -using namespace irr; - -void sound_init ( IrrlichtDevice *device ); -void sound_shutdown (); -void background_music ( const c8 * file ); - - -#endif // __QUAKE3_SOUND__H_INCLUDED__ +#ifndef FINDTHECHAIR_SOUND_H +#define FINDTHECHAIR_SOUND_H + +// Deprecated free-function API. New code should use Audio. +#include + +void sound_init(irr::IrrlichtDevice* device); +void sound_shutdown(); +void background_music(const irr::c8* file); + +#endif diff --git a/README.md b/README.md index 726071a..ba3f383 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,38 @@ -# FindtheChair -This was a sample project used to display the work put into creating a bsp downtown Environment, using gtkradiant. -it was written for demonstation purposes that is why the inputs were controlled in that manner. +# Find the Chair + +Irrlicht + irrKlang demo: walk a Quake 3 BSP downtown map and collect the chair ten times before the timer hits zero. + +This tree is an object-oriented rewrite of the 2012/2022 student project. Gameplay and assets are the same. The loop is no longer one 500-line `Game::run()`. + +## Controls + +- WASD move, mouse look, Space jump, Q crouch +- E collect the highlighted chair +- Tab pause +- In pause / game-over: Up/Down, Q to confirm Restart or Quit +- Main menu: Start, About, Quit + +## Goal + +Score 10 chairs. Each collect resets the 30-second timer. Time out and you lose. + +## Build + +Visual Studio 2010-era Win32 project (`Game/Game.vcxproj`). Needs Irrlicht and irrKlang headers/libs on the include and library path, plus the DLLs already in `Game/`. + +New translation units to add if you keep an older project file: + +- `Audio.cpp` +- `Level.cpp` + +## Layout + +| Class | Responsibility | +| --- | --- | +| `Game` | Device lifetime and state machine | +| `Level` | BSP load, FPS camera, collision, picking | +| `Objective` | Chair mesh and spawn points | +| `MMenu` | Title and instructions screens | +| `Overlay` | Score, timer, pause list, win/lose text | +| `Audio` | IrrKlang engine and music | +| `EventReceiver` | Keys and GUI button latching | diff --git a/REVIEW.md b/REVIEW.md new file mode 100644 index 0000000..a8497e0 --- /dev/null +++ b/REVIEW.md @@ -0,0 +1,36 @@ +# Code review — Ansel7/FindtheChair + +Repo: https://github.com/Ansel7/FindtheChair +Note: `Game.cpp` was deleted on `main` (commit `ec35210`). The review uses the last copy from `f0cf51c`. + +## What the project is + +A small Irrlicht 1.7/1.8 first-person collect-a-thon. GTKRadiant BSP (`cod_map.pk3` / `firing_range.bsp`) plus a 3DS chair. Menus, HUD, pause, win at 10 points or lose when the 30s timer hits 0. + +It already had classes (`Game`, `MMenu`, `Overlay`, `Objective`, `EventReceiver`). They were thin wrappers around a procedural `Game::run()`. + +## What was wrong + +1. **God loop.** `Game::run()` owned init, input, scoring, audio, three copy-pasted pick/render blocks, and shutdown. +2. **Boolean soup instead of a state machine.** `main_menu`, `instruc_menu`, `gamePaused`, `gameOver`, plus one-shot flags `pauseplay`, `pUp_flag`, `pDown_flag`, `action_flag`, `list_flag`. +3. **Missing `Game.cpp` on main.** The project file still lists it; the game cannot build from current HEAD. +4. **Broken GUI ID enum.** `NUM_OVRLY_BUTTONS` sat in the middle of the menu button list, so IDs were accidental. +5. **Two sound engines.** `Game` created an `ISoundEngine` *and* `sound_init()` created another. Globals `engine` / `backMusic` in `sound.cpp`. +6. **HUD rebuilt every frame.** `Overlay::display_hud()` called `addStaticText` every tick; `Game` called `guienv->clear()` every tick. That is why the HUD flickered and leaked widgets. +7. **Dangling pointer.** `col = &(SColor(...))` takes the address of a temporary. +8. **Debug leftovers.** `cout << score` in the Overlay constructor. Jump sound used a frame counter named like time. +9. **Picking copied three times.** Playing, paused, and game-over were the same raycast with small HUD differences. +10. **No destructor / double ownership story.** `device->drop()` only at the end of `run()`. Empty `Game` constructor. `Objective` destructor empty. `using namespace` in headers. + +## What the rewrite does + +- `Game` is a five-state machine: MainMenu, Instructions, Playing, Paused, GameOver. +- Each frame is `handleInput()` → `update()` → `render()`. +- `Level` owns BSP, camera keymap, collision animator, and picking. +- `Audio` owns IrrKlang and the Irrlicht file-factory bridge. +- `EventReceiver::consumeKeyPress()` gives rising-edge input so pause/menu keys do not repeat. +- `Overlay` creates score/time widgets once and updates text. +- Shared `Ids.h` for pick flags and GUI ids. +- Same assets, same controls, same win condition. + +Pushed on branch `oop-refactor`. Add `Audio.cpp` and `Level.cpp` to `Game.vcxproj` if that file is not updated in the same commit. From c92e03a51b02e1801c4653ae336097f58ac481ad Mon Sep 17 00:00:00 2001 From: Abraham Bojorquez Date: Wed, 2 Sep 2026 10:58:35 -0700 Subject: [PATCH 2/4] Add Game, Level, menu, HUD, and chair classes for the OOP refactor. --- Game/Game.cpp | 296 +++++++++++++++++++++++++++++++++++++++++++++++++ Game/Game.h | 127 +++++++++++---------- Game/Level.cpp | 162 +++++++++++++++++++++++++++ Game/Level.h | 34 ++++++ 4 files changed, 562 insertions(+), 57 deletions(-) create mode 100644 Game/Game.cpp create mode 100644 Game/Level.cpp create mode 100644 Game/Level.h diff --git a/Game/Game.cpp b/Game/Game.cpp new file mode 100644 index 0000000..21ad9fa --- /dev/null +++ b/Game/Game.cpp @@ -0,0 +1,296 @@ +#include "Game.h" + +using namespace irr; + +Game::Game() + : device_(0) + , driver_(0) + , smgr_(0) + , guienv_(0) + , menu_(0) + , hud_(0) + , chair_(0) + , level_(0) + , state_(StateMainMenu) + , won_(false) + , mapLoaded_(false) + , running_(true) + , lastJumpMs_(0) +{ +} + +Game::~Game() +{ + shutdown(); +} + +bool Game::init() +{ + device_ = createDevice(video::EDT_DIRECT3D9, core::dimension2d(800, 600)); + if (!device_) + return false; + + device_->setWindowCaption(L"Find the Chair"); + device_->setResizable(true); + device_->setEventReceiver(&receiver_); + + driver_ = device_->getVideoDriver(); + smgr_ = device_->getSceneManager(); + guienv_ = device_->getGUIEnvironment(); + + if (!audio_.init(device_)) + return false; + + menu_ = new MMenu(device_); + hud_ = new Overlay(device_); + chair_ = new Objective(device_); + level_ = new Level(device_); + + device_->getFileSystem()->addZipFileArchive("cod_map.pk3"); + audio_.setMusic("01 - Menu Theme.mp3"); + return true; +} + +void Game::shutdown() +{ + delete level_; level_ = 0; + delete chair_; chair_ = 0; + delete hud_; hud_ = 0; + delete menu_; menu_ = 0; + audio_.shutdown(); + if (device_) + { + device_->drop(); + device_ = 0; + } +} + +bool Game::inWorld() const +{ + return state_ == StatePlaying || state_ == StatePaused || state_ == StateGameOver; +} + +void Game::setState(State next) +{ + state_ = next; +} + +void Game::startMatch() +{ + audio_.play("menu_sound(1).mp3"); + audio_.setMusic("06 - Demolition Soviet Holdline.mp3"); + audio_.play("findthechair.wav"); + + menu_->clear(); + if (!mapLoaded_) + { + chair_->spawn(); + level_->load("cod_map.pk3", "firing_range.bsp"); + mapLoaded_ = true; + } + + hud_->resetMatch(30); + setState(StatePlaying); + receiver_.resetButtons(); +} + +void Game::restartMatch() +{ + audio_.stopAll(); + audio_.setMusic("06 - Demolition Soviet Holdline.mp3"); + audio_.play("menu3.wav"); + chair_->moveToNextPoint(); + hud_->clear(); + hud_->resetMatch(30); + won_ = false; + setState(StatePlaying); +} + +void Game::collectChair() +{ + audio_.play("menu_ok(1).mp3"); + hud_->addScore(1); + hud_->setTime(30); + chair_->moveToNextPoint(); +} + +void Game::handleInput() +{ + if (receiver_.isButtonPressed(GUI_Start)) + { + startMatch(); + return; + } + if (receiver_.isButtonPressed(GUI_Instructions)) + { + audio_.play("menu_sound(1).mp3"); + menu_->clear(); + setState(StateInstructions); + receiver_.resetButtons(); + return; + } + if (receiver_.isButtonPressed(GUI_BackToMain)) + { + audio_.play("menu_sound(1).mp3"); + menu_->clear(); + setState(StateMainMenu); + receiver_.resetButtons(); + return; + } + if (receiver_.isButtonPressed(GUI_Quit)) + { + running_ = false; + return; + } + + if (inWorld() && receiver_.consumeKeyPress(KEY_TAB)) + { + audio_.play("menu_ready(1).mp3"); + if (state_ == StatePaused) + { + hud_->clear(); + setState(StatePlaying); + } + else if (state_ == StatePlaying) + { + setState(StatePaused); + } + } + + if ((state_ == StatePaused || state_ == StateGameOver)) + { + if (receiver_.consumeKeyPress(KEY_UP)) + { + audio_.play("cursor_move(1).mp3"); + hud_->moveSelectUp(); + } + if (receiver_.consumeKeyPress(KEY_DOWN)) + { + audio_.play("cursor_move(1).mp3"); + hud_->moveSelectDown(); + } + if (receiver_.consumeKeyPress(KEY_KEY_Q)) + { + if (hud_->selected() == Overlay::PauseRestart) + restartMatch(); + else + running_ = false; + } + } + + if (state_ == StatePlaying && receiver_.consumeKeyPress(KEY_KEY_E)) + { + if (level_->isChairHighlighted()) + collectChair(); + } + + if (state_ == StatePlaying && receiver_.isKeyDown(KEY_SPACE)) + { + const u32 now = device_->getTimer()->getTime(); + if (now - lastJumpMs_ > 1000) + { + audio_.play("jump1.mp3"); + lastJumpMs_ = now; + } + } +} + +void Game::update() +{ + if (state_ != StatePlaying) + return; + + hud_->tick(device_->getTimer()->getTime()); + level_->updatePicking(); + + if (hud_->score() >= 10) + { + audio_.play("victory.ogg"); + audio_.setMusic(""); + won_ = true; + setState(StateGameOver); + } + else if (hud_->timeLeft() <= 0) + { + audio_.play("menu_cancel(1).mp3"); + won_ = false; + setState(StateGameOver); + } +} + +void Game::render() +{ + const video::SColor menuClear(255, 255, 255, 255); + const video::SColor worldClear(0, 0, 0, 0); + + switch (state_) + { + case StateMainMenu: + driver_->beginScene(true, true, menuClear); + menu_->displayMain(); + smgr_->drawAll(); + guienv_->drawAll(); + driver_->endScene(); + break; + + case StateInstructions: + driver_->beginScene(true, true, menuClear); + menu_->displayInstructions(); + smgr_->drawAll(); + guienv_->drawAll(); + driver_->endScene(); + break; + + case StatePlaying: + driver_->beginScene(true, true, worldClear); + hud_->drawHud(); + smgr_->drawAll(); + guienv_->drawAll(); + driver_->endScene(); + break; + + case StatePaused: + driver_->beginScene(true, true, worldClear); + level_->updatePicking(); + hud_->drawHud(); + hud_->drawPauseMenu(); + smgr_->drawAll(); + guienv_->drawAll(); + driver_->endScene(); + break; + + case StateGameOver: + driver_->beginScene(true, true, worldClear); + level_->updatePicking(); + hud_->drawHud(); + hud_->drawPauseMenu(); + hud_->drawGameOver(won_); + smgr_->drawAll(); + guienv_->drawAll(); + driver_->endScene(); + break; + } +} + +int Game::run() +{ + if (!init()) + return 1; + + while (running_ && device_->run()) + { + if (!device_->isWindowActive()) + { + device_->yield(); + continue; + } + + handleInput(); + update(); + if (running_) + render(); + } + + shutdown(); + return 0; +} diff --git a/Game/Game.h b/Game/Game.h index a356ea8..d9d8f9d 100644 --- a/Game/Game.h +++ b/Game/Game.h @@ -1,57 +1,70 @@ -#pragma once -#include -#include "MMenu.h" -#include "Overlay.h" -#include "Objective.h" -#include "EventReceiver.h" -#include "sound.h" -#include - -using namespace irr; -using namespace irrklang; - -#if defined(_MSC_VER) - #pragma comment(lib, "Irrlicht.lib") - #pragma comment(lib, "irrKlang.lib") -#endif - -class Game{ - -private: - bool gameOver; - bool gamePaused; - bool result; - bool instruc_menu; - bool main_menu; - bool been_default; - u32 beforeTime, - deltaTime, - sleepTime, - period; - - IrrlichtDevice* device; - ISoundEngine* engine; - scene::ISceneManager* smgr; - video::IVideoDriver* driver; - gui::IGUIEnvironment* guienv; - scene::IMetaTriangleSelector* col_list; - - //scene::IAnimatedMeshSceneNode* node; - irr::core::vector3d pos; - - //for map - scene::ISceneNode* node; - scene::ISceneNode* highlightedSceneNode; - - scene::ICameraSceneNode* camera; - scene::ISceneCollisionManager* collMan; - scene::IBillboardSceneNode* bill; - video::SMaterial material; - -public: - - Game(void); - int run(void); - void loadMap(); - -}; \ No newline at end of file +#ifndef FINDTHECHAIR_GAME_H +#define FINDTHECHAIR_GAME_H + +#include +#include +#include "MMenu.h" +#include "Overlay.h" +#include "Objective.h" +#include "EventReceiver.h" +#include "Audio.h" +#include "Level.h" + +#if defined(_MSC_VER) +#pragma comment(lib, "Irrlicht.lib") +#pragma comment(lib, "irrKlang.lib") +#endif + +// Thin state machine over the original Find-the-Chair loop. +class Game +{ +public: + enum State + { + StateMainMenu, + StateInstructions, + StatePlaying, + StatePaused, + StateGameOver + }; + + Game(); + ~Game(); + + int run(); + +private: + bool init(); + void shutdown(); + + void handleInput(); + void update(); + void render(); + + void startMatch(); + void restartMatch(); + void collectChair(); + void setState(State next); + + bool inWorld() const; + + irr::IrrlichtDevice* device_; + irr::video::IVideoDriver* driver_; + irr::scene::ISceneManager* smgr_; + irr::gui::IGUIEnvironment* guienv_; + + EventReceiver receiver_; + Audio audio_; + MMenu* menu_; + Overlay* hud_; + Objective* chair_; + Level* level_; + + State state_; + bool won_; + bool mapLoaded_; + bool running_; + irr::u32 lastJumpMs_; +}; + +#endif diff --git a/Game/Level.cpp b/Game/Level.cpp new file mode 100644 index 0000000..ebd4aa1 --- /dev/null +++ b/Game/Level.cpp @@ -0,0 +1,162 @@ +#include "Level.h" + +using namespace irr; +using namespace scene; + +Level::Level(IrrlichtDevice* device) + : device_(device) + , driver_(device->getVideoDriver()) + , smgr_(device->getSceneManager()) + , camera_(0) + , collMan_(smgr_->getSceneCollisionManager()) + , bill_(0) + , highlighted_(0) +{ + pickMaterial_.setTexture(0, 0); + pickMaterial_.Lighting = false; +} + +void Level::createFpsCamera() +{ + SKeyMap keyMap[6]; + keyMap[0].Action = EKA_MOVE_FORWARD; keyMap[0].KeyCode = KEY_KEY_W; + keyMap[1].Action = EKA_MOVE_BACKWARD; keyMap[1].KeyCode = KEY_KEY_S; + keyMap[2].Action = EKA_STRAFE_LEFT; keyMap[2].KeyCode = KEY_KEY_A; + keyMap[3].Action = EKA_STRAFE_RIGHT; keyMap[3].KeyCode = KEY_KEY_D; + keyMap[4].Action = EKA_JUMP_UP; keyMap[4].KeyCode = KEY_SPACE; + keyMap[5].Action = EKA_CROUCH; keyMap[5].KeyCode = KEY_KEY_Q; + + camera_ = smgr_->addCameraSceneNodeFPS( + 0, 100.0f, 0.3f, ID_NotPickable, keyMap, 6, true, 3.f); +} + +void Level::placeCameraAtSpawn(IQ3LevelMesh* q3mesh) +{ + if (!q3mesh || !camera_) + return; + + quake3::tQ3EntityList& entityList = q3mesh->getEntityList(); + quake3::IEntity search; + search.name = "info_player_start"; + + const s32 index = entityList.binary_search(search); + if (index < 0) + return; + + const quake3::SVarGroup* group = entityList[index].getGroup(1); + u32 parsepos = 0; + const core::vector3df pos = quake3::getAsVector3df(group->get("origin"), parsepos); + parsepos = 0; + const f32 yaw = quake3::getAsFloat(group->get("angle"), parsepos); + + camera_->setPosition(pos); + camera_->setRotation(core::vector3df(0, yaw, 0)); + camera_->setTarget(core::vector3df(-10, -15, 300)); + camera_->setMaterialFlag(video::EMF_LIGHTING, false); + camera_->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true); +} + +bool Level::load(const char* pk3Path, const char* bspName) +{ + device_->getFileSystem()->addZipFileArchive(pk3Path); + + IQ3LevelMesh* q3mesh = static_cast(smgr_->getMesh(bspName)); + if (!q3mesh) + return false; + + smgr_->getParameters()->setAttribute(ALLOW_ZWRITE_ON_TRANSPARENT, true); + + IMesh* geometry = q3mesh->getMesh(quake3::E_Q3_MESH_GEOMETRY); + IMeshSceneNode* q3node = 0; + if (geometry) + q3node = smgr_->addOctreeSceneNode(geometry, 0, ID_Pickable, 1024); + + const IMesh* items = q3mesh->getMesh(quake3::E_Q3_MESH_ITEMS); + if (items) + { + for (u32 i = 0; i < items->getMeshBufferCount(); ++i) + { + IMeshBuffer* buffer = items->getMeshBuffer(i); + const s32 shaderIndex = static_cast(buffer->getMaterial().MaterialTypeParam2); + const quake3::IShader* shader = q3mesh->getShader(shaderIndex); + if (shader) + smgr_->addQuake3SceneNode(buffer, shader); + } + } + + createFpsCamera(); + placeCameraAtSpawn(q3mesh); + + ITriangleSelector* selector = 0; + if (q3node && geometry) + { + q3node->setPosition(core::vector3df(0, 0, 0)); + selector = smgr_->createOctreeTriangleSelector(geometry, q3node); + q3node->setTriangleSelector(selector); + } + + if (selector && camera_) + { + ISceneNodeAnimator* anim = smgr_->createCollisionResponseAnimator( + selector, camera_, + core::vector3df(10, 15, 10), + core::vector3df(0, -10, 0), + core::vector3df(0, 30, 0)); + selector->drop(); + camera_->addAnimator(anim); + anim->drop(); + } + + device_->getCursorControl()->setVisible(false); + + bill_ = smgr_->addBillboardSceneNode(); + bill_->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR); + bill_->setMaterialTexture(0, driver_->getTexture("portal6.bmp")); + bill_->setMaterialFlag(video::EMF_LIGHTING, false); + bill_->setMaterialFlag(video::EMF_ZBUFFER, false); + bill_->setSize(core::dimension2d(20.0f, 20.0f)); + bill_->setID(ID_NotPickable); + + collMan_ = smgr_->getSceneCollisionManager(); + return true; +} + +void Level::clearHighlight() +{ + if (highlighted_) + { + highlighted_->setMaterialFlag(video::EMF_LIGHTING, true); + highlighted_ = 0; + } +} + +void Level::updatePicking() +{ + clearHighlight(); + if (!camera_ || !collMan_) + return; + + core::line3d ray; + ray.start = camera_->getPosition(); + ray.end = ray.start + (camera_->getTarget() - ray.start).normalize() * 1000.0f; + + core::vector3df intersection; + core::triangle3df hitTriangle; + ISceneNode* selected = collMan_->getSceneNodeAndCollisionPointFromRay( + ray, intersection, hitTriangle, 0, 0); + + if (!selected) + return; + + if (bill_) + bill_->setPosition(intersection); + + driver_->setTransform(video::ETS_WORLD, core::matrix4()); + driver_->setMaterial(pickMaterial_); + + if ((selected->getID() & ID_Highlightable) == ID_Highlightable) + { + highlighted_ = selected; + highlighted_->setMaterialFlag(video::EMF_LIGHTING, false); + } +} diff --git a/Game/Level.h b/Game/Level.h new file mode 100644 index 0000000..aa1d822 --- /dev/null +++ b/Game/Level.h @@ -0,0 +1,34 @@ +#ifndef FINDTHECHAIR_LEVEL_H +#define FINDTHECHAIR_LEVEL_H + +#include +#include "Ids.h" + +// Quake 3 BSP world, FPS camera, collision, and chair picking. +class Level +{ +public: + explicit Level(irr::IrrlichtDevice* device); + + bool load(const char* pk3Path, const char* bspName); + void updatePicking(); + bool isChairHighlighted() const { return highlighted_ != 0; } + void clearHighlight(); + + irr::scene::ICameraSceneNode* camera() const { return camera_; } + +private: + void createFpsCamera(); + void placeCameraAtSpawn(irr::scene::IQ3LevelMesh* q3mesh); + + irr::IrrlichtDevice* device_; + irr::video::IVideoDriver* driver_; + irr::scene::ISceneManager* smgr_; + irr::scene::ICameraSceneNode* camera_; + irr::scene::ISceneCollisionManager* collMan_; + irr::scene::IBillboardSceneNode* bill_; + irr::scene::ISceneNode* highlighted_; + irr::video::SMaterial pickMaterial_; +}; + +#endif From 04c568a4433b3cacd22153bb1db2e84f759f4545 Mon Sep 17 00:00:00 2001 From: Abraham Bojorquez Date: Wed, 2 Sep 2026 10:59:30 -0700 Subject: [PATCH 3/4] Rewrite menu, HUD, and chair classes to match the new Game state machine. --- Game/MMenu.cpp | 149 +++++++++++--------- Game/MMenu.h | 77 ++++++----- Game/Objective.cpp | 131 +++++++++--------- Game/Objective.h | 84 +++++------- Game/Overlay.cpp | 329 +++++++++++++++++++-------------------------- Game/Overlay.h | 126 +++++++++-------- 6 files changed, 441 insertions(+), 455 deletions(-) diff --git a/Game/MMenu.cpp b/Game/MMenu.cpp index 588522e..ac58d58 100644 --- a/Game/MMenu.cpp +++ b/Game/MMenu.cpp @@ -1,65 +1,84 @@ -#include "MMenu.h" - -using namespace irr; - -MMenu::MMenu(irr::IrrlichtDevice* dev){ - - device = dev; - driver = device->getVideoDriver(); - env = device->getGUIEnvironment(); - backgroundPic = driver->getTexture("Pancakes.jpg"); - area = irr::core::rect(0,0,800,600); - col = &(irr::video::SColor(255, 255, 255, 255)); - - b_ht = 40; - b_wid = 150; - - x = (device->getVideoDriver()->getScreenSize().Width - b_wid) / 2; - y = (device->getVideoDriver()->getScreenSize().Height) / 2; -} - -void MMenu::Display(){ - - s32 v_spacing = 5; - - driver->draw2DImage(backgroundPic, area, irr::core::rect(0, 0, 497, 335), 0, col, false); - - irr::video::ITexture* image = driver->getTexture("title.jpg"); - driver->makeColorKeyTexture(image, irr::core::vector2d(0, 0), false); - driver->draw2DImage(image, irr::core::rect(250, 0, 543, 322), irr::core::rect(0, 0, 843, 502), 0, col, true); - - env->addButton(core::rect(x, y, x + b_wid, y + b_ht), 0, START_BUTTON, - L"START", L"Start Game"); - - env->addButton(core::rect(x, (y + (b_ht * 1)) + v_spacing, x + b_wid, (y + (b_ht * 2)) + v_spacing), 0, INSTRUCTIONS_BUTTON, - L"ABOUT", L"learn how to play"); - - env->addButton(core::rect(x, (y + (b_ht * 3)) + (2 * v_spacing), x + b_wid, (y + (b_ht * 4)) + (2 * v_spacing)), 0, QUIT_BUTTON, - L"QUIT", L"Terminate Program"); -} - -void MMenu::remove(){ - env->clear(); -} - -void MMenu::instructions_display(){ - - driver->draw2DImage(backgroundPic, area, irr::core::rect(0, 0, 497, 335), 0, col, false); - - irr::video::ITexture* image = driver->getTexture("keyboard.jpg"); - driver->makeColorKeyTexture(image, irr::core::vector2d(0, 0), false); - driver->draw2DImage(image, irr::core::rect(250, 10, 650, 561), irr::core::rect(0, 0, 399, 411), 0, col, true); - - - env->addButton(core::rect(x - (b_wid * 2), y + (b_ht * 3), (x - (b_wid * 2)) + b_wid, (y + (b_ht * 3)) + b_ht), 0, BACK_TO_MAIN_BUTTON, - L"RETURN", L"return to previous menu"); - -} - -void MMenu::setBackgroundPic(irr::video::ITexture* bkgrnd_pic){ - backgroundPic = bkgrnd_pic; -} - -irr::video::ITexture* MMenu::getBackgroundPic(){ - return backgroundPic; -} \ No newline at end of file +#include "MMenu.h" + +using namespace irr; + +MMenu::MMenu(IrrlichtDevice* device) + : device_(device) + , env_(device->getGUIEnvironment()) + , driver_(device->getVideoDriver()) + , background_(0) + , area_(0, 0, 800, 600) + , buttonW_(150) + , buttonH_(40) + , buttonsBuilt_(false) +{ + background_ = driver_->getTexture("Pancakes.jpg"); + const core::dimension2d size = driver_->getScreenSize(); + x_ = static_cast((size.Width - buttonW_) / 2); + y_ = static_cast(size.Height / 2); +} + +void MMenu::drawBackground() +{ + const video::SColor white(255, 255, 255, 255); + if (background_) + driver_->draw2DImage(background_, area_, core::rect(0, 0, 497, 335), 0, &white, false); +} + +void MMenu::addCenteredButton(s32 row, int id, const wchar_t* label, const wchar_t* tip) +{ + const s32 spacing = 5; + const s32 top = y_ + row * (buttonH_ + spacing); + env_->addButton(core::rect(x_, top, x_ + buttonW_, top + buttonH_), 0, id, label, tip); +} + +void MMenu::displayMain() +{ + drawBackground(); + + video::ITexture* title = driver_->getTexture("title.jpg"); + if (title) + { + driver_->makeColorKeyTexture(title, core::vector2d(0, 0), false); + const video::SColor white(255, 255, 255, 255); + driver_->draw2DImage(title, core::rect(250, 0, 543, 322), + core::rect(0, 0, 843, 502), 0, &white, true); + } + + if (buttonsBuilt_) + return; + + addCenteredButton(0, GUI_Start, L"START", L"Start Game"); + addCenteredButton(1, GUI_Instructions, L"ABOUT", L"learn how to play"); + addCenteredButton(3, GUI_Quit, L"QUIT", L"Terminate Program"); + buttonsBuilt_ = true; +} + +void MMenu::displayInstructions() +{ + drawBackground(); + + video::ITexture* keys = driver_->getTexture("keyboard.jpg"); + if (keys) + { + driver_->makeColorKeyTexture(keys, core::vector2d(0, 0), false); + const video::SColor white(255, 255, 255, 255); + driver_->draw2DImage(keys, core::rect(250, 10, 650, 561), + core::rect(0, 0, 399, 411), 0, &white, true); + } + + if (buttonsBuilt_) + return; + + const s32 left = x_ - (buttonW_ * 2); + const s32 top = y_ + (buttonH_ * 3); + env_->addButton(core::rect(left, top, left + buttonW_, top + buttonH_), + 0, GUI_BackToMain, L"RETURN", L"return to previous menu"); + buttonsBuilt_ = true; +} + +void MMenu::clear() +{ + env_->clear(); + buttonsBuilt_ = false; +} diff --git a/Game/MMenu.h b/Game/MMenu.h index dc8d318..ca4459e 100644 --- a/Game/MMenu.h +++ b/Game/MMenu.h @@ -1,35 +1,42 @@ -#pragma once -#include - -class MMenu{ - -private: - irr::video::ITexture* backgroundPic; - irr::IrrlichtDevice* device; - irr::gui::IGUIEnvironment* env; - irr::video::IVideoDriver* driver; - irr::core::rect area; - const irr::video::SColor* col; - - irr::s32 x, y, b_wid, b_ht; - -public: - //Main Menu Buttons - enum{ - QUIT_BUTTON = 0, - START_BUTTON, - NUM_OVRLY_BUTTONS, - INSTRUCTIONS_BUTTON, - BACK_TO_MAIN_BUTTON, - NUM_MMENU_BUTTONS - }; - - MMenu(irr::IrrlichtDevice*); - void Display(); - void setBackgroundPic(irr::video::ITexture*); - irr::video::ITexture* getBackgroundPic(); - void instructions_display(void); - void remove(); - -}; - +#ifndef FINDTHECHAIR_MMENU_H +#define FINDTHECHAIR_MMENU_H + +#include +#include "Ids.h" + +class MMenu +{ +public: + // Backward-compatible names used by the original Game.cpp. + enum + { + QUIT_BUTTON = GUI_Quit, + START_BUTTON = GUI_Start, + INSTRUCTIONS_BUTTON = GUI_Instructions, + BACK_TO_MAIN_BUTTON = GUI_BackToMain, + NUM_MMENU_BUTTONS = GUI_COUNT, + NUM_OVRLY_BUTTONS = 0 + }; + + explicit MMenu(irr::IrrlichtDevice* device); + + void displayMain(); + void displayInstructions(); + void clear(); + + bool buttonsBuilt() const { return buttonsBuilt_; } + +private: + void drawBackground(); + void addCenteredButton(irr::s32 row, int id, const wchar_t* label, const wchar_t* tip); + + irr::IrrlichtDevice* device_; + irr::gui::IGUIEnvironment* env_; + irr::video::IVideoDriver* driver_; + irr::video::ITexture* background_; + irr::core::rect area_; + irr::s32 x_, y_, buttonW_, buttonH_; + bool buttonsBuilt_; +}; + +#endif diff --git a/Game/Objective.cpp b/Game/Objective.cpp index ce1461b..ed67d7e 100644 --- a/Game/Objective.cpp +++ b/Game/Objective.cpp @@ -1,63 +1,68 @@ -#include "Objective.h" - -Objective::Objective(irr::IrrlichtDevice* dev){ - device = dev; - driver = device->getVideoDriver(); - smgr = device->getSceneManager(); - srand(time(NULL)); - min = getNextSpawnPoint(); - node = 0; - randomInt = 0; - lastInt = 0; - length = 150; - height = 150; - width = 150; - mesh = smgr->getMesh("a3dchr3.3ds"); -} - -Objective::~Objective(void){} - -void Objective::drawObjective(void){ - - smgr->getMeshManipulator()->makePlanarTextureMapping(mesh->getMesh(0), 0.004f); - - irr::scene::ITriangleSelector* selector = 0; - - node = smgr->addAnimatedMeshSceneNode(mesh); - node->setMaterialTexture(0, driver->getTexture("dchrfab.tga")); - node->setID(IDFlag_IsPickable | IDFlag_IsHighlightable); - node->setMaterialFlag(irr::video::EMF_LIGHTING, true); - node->getMaterial(0).NormalizeNormals = true; - node->setPosition(min); - - selector = smgr->createTriangleSelector(node); - node->setTriangleSelector(selector); - selector->drop(); -} - -void Objective::setNewSpawnPoint(){ - min = getNextSpawnPoint(); - node->setPosition(min); -} - -irr::core::vector3d Objective::getNextSpawnPoint(){ - do{ - randomInt = rand() % 5; - }while(lastInt == randomInt); - lastInt = randomInt; - - switch(randomInt){ - case 0: - return irr::core::vector3d(-10, -7, 300); - case 1: - return irr::core::vector3d(-1050, 15, -150); - case 2: - return irr::core::vector3d(-1490, 17, 500); - case 3: - return irr::core::vector3d(-1100, 217, 230); - case 4: - return irr::core::vector3d(-900, 147, 930); - default: - return irr::core::vector3d(-900, 147, 930); - } -} \ No newline at end of file +#include "Objective.h" +#include +#include + +Objective::Objective(irr::IrrlichtDevice* device) + : device_(device) + , driver_(device->getVideoDriver()) + , smgr_(device->getSceneManager()) + , mesh_(0) + , node_(0) + , lastIndex_(-1) +{ + std::srand(static_cast(std::time(0))); + mesh_ = smgr_->getMesh("a3dchr3.3ds"); +} + +Objective::~Objective() +{ + // Scene manager owns the node once added. + node_ = 0; +} + +void Objective::spawn() +{ + if (!mesh_) + return; + + smgr_->getMeshManipulator()->makePlanarTextureMapping(mesh_->getMesh(0), 0.004f); + + node_ = smgr_->addAnimatedMeshSceneNode(mesh_); + if (!node_) + return; + + node_->setMaterialTexture(0, driver_->getTexture("dchrfab.tga")); + node_->setID(ID_Pickable | ID_Highlightable); + node_->setMaterialFlag(irr::video::EMF_LIGHTING, true); + node_->getMaterial(0).NormalizeNormals = true; + node_->setPosition(nextSpawnPoint()); + + irr::scene::ITriangleSelector* selector = smgr_->createTriangleSelector(node_); + node_->setTriangleSelector(selector); + selector->drop(); +} + +void Objective::moveToNextPoint() +{ + if (node_) + node_->setPosition(nextSpawnPoint()); +} + +irr::core::vector3df Objective::nextSpawnPoint() +{ + int index = 0; + do + { + index = std::rand() % 5; + } while (index == lastIndex_); + lastIndex_ = index; + + switch (index) + { + case 0: return irr::core::vector3df(-10.f, -7.f, 300.f); + case 1: return irr::core::vector3df(-1050.f, 15.f, -150.f); + case 2: return irr::core::vector3df(-1490.f, 17.f, 500.f); + case 3: return irr::core::vector3df(-1100.f, 217.f, 230.f); + default: return irr::core::vector3df(-900.f, 147.f, 930.f); + } +} diff --git a/Game/Objective.h b/Game/Objective.h index 320d998..6db1e47 100644 --- a/Game/Objective.h +++ b/Game/Objective.h @@ -1,47 +1,37 @@ -#pragma once -#include -#include -#include - -class Objective{ - -private: - irr::IrrlichtDevice* device; - irr::video::IVideoDriver* driver; - irr::scene::IAnimatedMesh* mesh; - irr::scene::IAnimatedMeshSceneNode* node; - irr::scene::ISceneManager* smgr; - - irr::f32 width, - length, - height; - int lastInt, - randomInt; - irr::core::vector3d min; - - irr::core::vector3d getNextSpawnPoint(); -public: - - enum{ - // I use this ISceneNode ID to indicate a scene node that is - // not pickable by getSceneNodeAndCollisionPointFromRay() - ID_IsNotPickable = 0, - - // I use this flag in ISceneNode IDs to indicate that the - // scene node can be picked by ray selection. - IDFlag_IsPickable = 1 << 0, - - // I use this flag in ISceneNode IDs to indicate that the - // scene node can be highlighted. In this example, the - // homonids can be highlighted, but the level mesh can't. - IDFlag_IsHighlightable = 1 << 1 - }; - - Objective(irr::IrrlichtDevice*); - ~Objective(void); - - void drawObjective(void); - void setNewSpawnPoint(void); - -}; - +#ifndef FINDTHECHAIR_OBJECTIVE_H +#define FINDTHECHAIR_OBJECTIVE_H + +#include +#include "Ids.h" + +// The chair the player has to find. Owns its mesh node and spawn table. +class Objective +{ +public: + explicit Objective(irr::IrrlichtDevice* device); + ~Objective(); + + void spawn(); + void moveToNextPoint(); + irr::scene::ISceneNode* node() const { return node_; } + + // Kept so older call sites / picking flags stay readable. + enum + { + ID_IsNotPickable = ID_NotPickable, + IDFlag_IsPickable = ID_Pickable, + IDFlag_IsHighlightable = ID_Highlightable + }; + +private: + irr::core::vector3df nextSpawnPoint(); + + irr::IrrlichtDevice* device_; + irr::video::IVideoDriver* driver_; + irr::scene::ISceneManager* smgr_; + irr::scene::IAnimatedMesh* mesh_; + irr::scene::IAnimatedMeshSceneNode* node_; + int lastIndex_; +}; + +#endif diff --git a/Game/Overlay.cpp b/Game/Overlay.cpp index 11d06ba..f5db726 100644 --- a/Game/Overlay.cpp +++ b/Game/Overlay.cpp @@ -1,190 +1,139 @@ -#include "Overlay.h" -#include - -using namespace std; - -Overlay::Overlay(irr::IrrlichtDevice* dev){ - device = dev; - env = device->getGUIEnvironment(); - driver = device->getVideoDriver(); - sel = 0; - score = 0; - last_score = 0; - stime = 30; - cout << score; - cout << stime; - gameHasStarted = false; - currentTime = device->getTimer()->getTime(); - lastTime = device->getTimer()->getTime(); - col = &(irr::video::SColor(255, 255, 255, 255)); - width = device->getVideoDriver()->getScreenSize().Width; - height = device->getVideoDriver()->getScreenSize().Height; - irr::gui::IGUISkin* skin = env->getSkin(); - font = env->getFont("big_text.bmp"); - if(font) - skin->setFont(font); -} - - -Overlay::~Overlay(void){ - -} - -void Overlay::setTime(int newtime){ - stime = newtime; -} - -int Overlay::getTime(){ - return stime; -} - -void Overlay::display_hud(){ - - if(gameHasStarted){ - - currentTime = device->getTimer()->getTime(); - - if(score != last_score){ - last_score = score; - } - text = env->addStaticText(irr::core::stringw(score).c_str(), - irr::core::rect(70, 20, 110, 70), true); - text->setOverrideColor(irr::video::SColor(255, 255, 0, 0)); - - if((currentTime - lastTime) >= 1000){ - if(stime != 0 && score != 10){ - --stime; - lastTime = currentTime; - } - } - text = env->addStaticText(irr::core::stringw(stime).c_str(), - irr::core::rect(width / 2, 20, (width / 2) + 40, 70), true); - text->setOverrideColor(irr::video::SColor(255, 255, 0, 0)); - }else{ - text = env->addStaticText(irr::core::stringw(score).c_str(), - irr::core::rect(70, 20, 200, 70), true); - text->setOverrideColor(irr::video::SColor(255, 255, 0, 0)); - text = env->addStaticText(irr::core::stringw(stime).c_str(), - irr::core::rect(width / 2, 20, (width / 2) + 200, 70), true); - text->setOverrideColor(irr::video::SColor(255, 255, 0, 0)); - - gameHasStarted = true; - } -} - -void Overlay::display_go_text(int wol){ - - switch(wol){ - case 0: - text = env->addStaticText(L"YOU LOSE\nGAME OVER", - irr::core::rect(0, 0, 0, 0), true); - text->setRelativePosition(irr::core::rect((width - text->getTextWidth()) / 2, (height - text->getTextHeight()) / 2, - ((width - text->getTextWidth()) / 2) + 200, ((height - text->getTextHeight()) / 2) + 70)); - text->setOverrideColor(irr::video::SColor(255, 255, 0, 0)); - break; - case 1: - text = env->addStaticText(L"YOU WIN\nGAME OVER", - irr::core::rect(0, 0, 0, 0), true); - text->setRelativePosition(irr::core::rect((width - text->getTextWidth()) / 2, (height - text->getTextHeight()) / 2, - ((width - text->getTextWidth()) / 2) + 200, ((height - text->getTextHeight()) / 2) + 70)); - text->setOverrideColor(irr::video::SColor(255, 255, 0, 0)); - break; - } - - /*//this uses the win or lose and game over jpg. - switch(wol){ - case 0: - //loser text :( - image1 = - driver->getTexture("you_lose.jpg"); - - driver->makeColorKeyTexture(image1, - irr::core::vector2d(0, 0), false); - - driver->draw2DImage(image1, - irr::core::rect(100, 100, 700, 245), - irr::core::rect(0, 0, 795, 145), 0, - col, true); - - image3 = - driver->getTexture("gameOver.jpg"); - - driver->makeColorKeyTexture(image3, - irr::core::vector2d(0, 0), false); - - driver->draw2DImage(image3, - irr::core::rect(250, 100, 700, 395), - irr::core::rect(0, 0, 888, 145), 0, - col, true); - break; - case 1: - //winner text :) - image2 = - driver->getTexture("you_win.jpg"); - - driver->makeColorKeyTexture(image2, - irr::core::vector2d(0, 0), false); - - driver->draw2DImage(image2, - irr::core::rect(100, 100, 700, 245), - irr::core::rect(0, 0, 743, 145), 0, - col, true); - - image3 = - driver->getTexture("gameOver.jpg"); - - driver->makeColorKeyTexture(image3, - irr::core::vector2d(0, 0), false); - - driver->draw2DImage(image3, - irr::core::rect(250, 100, 700, 395), - irr::core::rect(0, 0, 888, 145), 0, - col, true); - break; - } - */ -} - -void Overlay::display_menu(){ - - window = env->addWindow(irr::core::rect(100, 100, 300, 200), - false, L"Pause"); - - pause_listBox = env->addListBox(irr::core::rect - (0, 20, 200, 100), window); - - pause_listBox->addItem(L"Restart"); - pause_listBox->addItem(L"Quit"); - pause_listBox->setSelected(sel); - -} - -void Overlay::setScore(int s){ - score = s; -} - -int Overlay::getScore(){ - return score; -} - -void Overlay::move_select_down(){ - ++sel; - if(sel > 1) - sel = 0; - pause_listBox->setSelected(sel); -} - -void Overlay::move_select_up(){ - --sel; - if(sel < 0) - sel = 1; - pause_listBox->setSelected(sel); -} - -irr::s32 Overlay::return_selected(){ - return sel; -} - -void Overlay::remove(){ - env->clear(); -} \ No newline at end of file +#include "Overlay.h" + +using namespace irr; + +Overlay::Overlay(IrrlichtDevice* device) + : device_(device) + , env_(device->getGUIEnvironment()) + , driver_(device->getVideoDriver()) + , font_(0) + , scoreText_(0) + , timeText_(0) + , pauseWindow_(0) + , pauseList_(0) + , score_(0) + , timeLeft_(30) + , selected_(0) + , lastTickMs_(device->getTimer()->getTime()) + , width_(static_cast(driver_->getScreenSize().Width)) + , height_(static_cast(driver_->getScreenSize().Height)) + , hudBuilt_(false) +{ + gui::IGUISkin* skin = env_->getSkin(); + font_ = env_->getFont("big_text.bmp"); + if (font_ && skin) + skin->setFont(font_); +} + +Overlay::~Overlay() +{ +} + +void Overlay::resetMatch(int startSeconds) +{ + score_ = 0; + timeLeft_ = startSeconds; + selected_ = 0; + lastTickMs_ = device_->getTimer()->getTime(); + hudBuilt_ = false; + scoreText_ = 0; + timeText_ = 0; + pauseWindow_ = 0; + pauseList_ = 0; +} + +void Overlay::addScore(int amount) +{ + score_ += amount; +} + +void Overlay::tick(u32 nowMs) +{ + if (timeLeft_ <= 0) + return; + if (nowMs - lastTickMs_ >= 1000) + { + --timeLeft_; + lastTickMs_ = nowMs; + } +} + +void Overlay::ensureHudWidgets() +{ + if (hudBuilt_ && scoreText_ && timeText_) + return; + + scoreText_ = env_->addStaticText(L"0", core::rect(70, 20, 140, 70), true); + scoreText_->setOverrideColor(video::SColor(255, 255, 0, 0)); + + timeText_ = env_->addStaticText(L"30", + core::rect(width_ / 2, 20, width_ / 2 + 80, 70), true); + timeText_->setOverrideColor(video::SColor(255, 255, 0, 0)); + hudBuilt_ = true; +} + +void Overlay::drawHud() +{ + ensureHudWidgets(); + if (scoreText_) + scoreText_->setText(core::stringw(score_).c_str()); + if (timeText_) + timeText_->setText(core::stringw(timeLeft_).c_str()); +} + +void Overlay::drawPauseMenu() +{ + if (!pauseWindow_) + { + pauseWindow_ = env_->addWindow(core::rect(100, 100, 300, 200), false, L"Pause"); + pauseList_ = env_->addListBox(core::rect(0, 20, 200, 100), pauseWindow_); + pauseList_->addItem(L"Restart"); + pauseList_->addItem(L"Quit"); + } + if (pauseList_) + pauseList_->setSelected(selected_); +} + +void Overlay::drawGameOver(bool won) +{ + gui::IGUIStaticText* text = env_->addStaticText( + won ? L"YOU WIN\nGAME OVER" : L"YOU LOSE\nGAME OVER", + core::rect(0, 0, 0, 0), true); + + const s32 tw = text->getTextWidth(); + const s32 th = text->getTextHeight(); + text->setRelativePosition(core::rect( + (width_ - tw) / 2, + (height_ - th) / 2, + (width_ - tw) / 2 + 200, + (height_ - th) / 2 + 70)); + text->setOverrideColor(video::SColor(255, 255, 0, 0)); +} + +void Overlay::moveSelectUp() +{ + --selected_; + if (selected_ < 0) + selected_ = 1; + if (pauseList_) + pauseList_->setSelected(selected_); +} + +void Overlay::moveSelectDown() +{ + ++selected_; + if (selected_ > 1) + selected_ = 0; + if (pauseList_) + pauseList_->setSelected(selected_); +} + +void Overlay::clear() +{ + env_->clear(); + hudBuilt_ = false; + scoreText_ = 0; + timeText_ = 0; + pauseWindow_ = 0; + pauseList_ = 0; +} diff --git a/Game/Overlay.h b/Game/Overlay.h index da264a0..41f5cac 100644 --- a/Game/Overlay.h +++ b/Game/Overlay.h @@ -1,55 +1,71 @@ -#pragma once -#include -#include "MMenu.h" - -class Overlay{ - -private: - irr::IrrlichtDevice* device; - irr::gui::IGUIEnvironment* env; - irr::video::IVideoDriver* driver; - irr::u32 stime, - currentTime, - lastTime; - irr::gui::IGUISkin* skin; - irr::gui::IGUIFont* font2; - irr::gui::IGUIFont* font; - irr::video::ITexture* image1; - irr::video::ITexture* image2; - irr::video::ITexture* image3; - irr::gui::IGUIWindow* window; - irr::video::SColor* col; - irr::gui::IGUIStaticText* score_text; - irr::gui::IGUIStaticText* time_text; - irr::gui::IGUIListBox* pause_listBox; - irr::gui::IGUIListBox* go_listBox; - irr::gui::IGUIStaticText* text; - - int sel, - score, - last_score; - irr::s32 width, - height; - bool gameHasStarted; - -public: - //pause menu buttons - enum{ - RESTART_BUTTON = 6, - }; - - Overlay(irr::IrrlichtDevice*); - ~Overlay(void); - - void move_select_up(); - void move_select_down(); - void setTime(int); - int getTime(); - irr::s32 return_selected(); - void display_menu(); - void display_go_text(int); - void display_hud(); - int getScore(); - void setScore(int); - void remove(); -}; \ No newline at end of file +#ifndef FINDTHECHAIR_OVERLAY_H +#define FINDTHECHAIR_OVERLAY_H + +#include + +// HUD, pause list, and win/lose text. +// Score and remaining time live here; the Game state machine drives them. +class Overlay +{ +public: + enum PauseChoice + { + PauseRestart = 0, + PauseQuit = 1 + }; + + explicit Overlay(irr::IrrlichtDevice* device); + ~Overlay(); + + void resetMatch(int startSeconds = 30); + void addScore(int amount); + void tick(irr::u32 nowMs); + + int score() const { return score_; } + int timeLeft() const { return timeLeft_; } + void setTime(int seconds) { timeLeft_ = seconds; } + void setScore(int s) { score_ = s; } + + int getScore() const { return score_; } + int getTime() const { return timeLeft_; } + + void moveSelectUp(); + void moveSelectDown(); + irr::s32 selected() const { return selected_; } + irr::s32 return_selected() { return selected_; } + + void drawHud(); + void drawPauseMenu(); + void drawGameOver(bool won); + void clear(); + + // Compatibility wrappers used by the original loop. + void display_hud() { drawHud(); } + void display_menu() { drawPauseMenu(); } + void display_go_text(int winOrLose) { drawGameOver(winOrLose == 1); } + void move_select_up() { moveSelectUp(); } + void move_select_down() { moveSelectDown(); } + void remove() { clear(); } + +private: + void ensureHudWidgets(); + + irr::IrrlichtDevice* device_; + irr::gui::IGUIEnvironment* env_; + irr::video::IVideoDriver* driver_; + irr::gui::IGUIFont* font_; + irr::gui::IGUIStaticText* scoreText_; + irr::gui::IGUIStaticText* timeText_; + irr::gui::IGUIWindow* pauseWindow_; + irr::gui::IGUIListBox* pauseList_; + + int score_; + int timeLeft_; + int selected_; + irr::u32 lastTickMs_; + irr::s32 width_; + irr::s32 height_; + bool hudBuilt_; +}; + +#endif From e751341e4f91132e42b39361be0f700d7507e0a7 Mon Sep 17 00:00:00 2001 From: Abraham Bojorquez Date: Wed, 2 Sep 2026 10:59:57 -0700 Subject: [PATCH 4/4] Add Audio.cpp, Level.cpp, and Ids.h to the Visual Studio project. --- Game/Game.vcxproj | 257 +++++++++++++++++++++++----------------------- 1 file changed, 131 insertions(+), 126 deletions(-) diff --git a/Game/Game.vcxproj b/Game/Game.vcxproj index 83400c5..b0d5452 100644 --- a/Game/Game.vcxproj +++ b/Game/Game.vcxproj @@ -1,126 +1,131 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {AA4AD828-F1E6-4831-AE05-A344028F3423} - Win32Proj - Game - - - - Application - true - Unicode - - - Application - false - true - Unicode - - - - - - - - - - - - - true - - - false - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreadedDebug - - - Console - true - %(AdditionalDependencies) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreaded - - - Console - true - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + Debug + Win32 + + + Release + Win32 + + + + {AA4AD828-F1E6-4831-AE05-A344028F3423} + Win32Proj + Game + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDebug + + + Console + true + %(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +