Initial commit: wowee native WoW 3.3.5a client

This commit is contained in:
Kelsi 2026-02-02 12:24:50 -08:00
commit ce6cb8f38e
147 changed files with 32347 additions and 0 deletions

View file

@ -0,0 +1,45 @@
#pragma once
#include <string>
#include <vector>
namespace wowee {
namespace pipeline { class AssetManager; }
namespace audio {
class MusicManager {
public:
MusicManager();
~MusicManager();
bool initialize(pipeline::AssetManager* assets);
void shutdown();
void playMusic(const std::string& mpqPath, bool loop = true);
void stopMusic(float fadeMs = 2000.0f);
void crossfadeTo(const std::string& mpqPath, float fadeMs = 3000.0f);
void update(float deltaTime);
bool isPlaying() const { return playing; }
bool isInitialized() const { return assetManager != nullptr; }
const std::string& getCurrentTrack() const { return currentTrack; }
private:
void stopCurrentProcess();
pipeline::AssetManager* assetManager = nullptr;
std::string currentTrack;
std::string tempFilePath;
pid_t playerPid = -1;
bool playing = false;
// Crossfade state
bool crossfading = false;
std::string pendingTrack;
float fadeTimer = 0.0f;
float fadeDuration = 0.0f;
};
} // namespace audio
} // namespace wowee

View file

@ -0,0 +1,96 @@
#pragma once
#include "auth/srp.hpp"
#include "auth/auth_packets.hpp"
#include <memory>
#include <string>
#include <functional>
namespace wowee {
namespace network { class TCPSocket; class Packet; }
namespace auth {
struct Realm;
// Authentication state
enum class AuthState {
DISCONNECTED,
CONNECTED,
CHALLENGE_SENT,
CHALLENGE_RECEIVED,
PROOF_SENT,
AUTHENTICATED,
REALM_LIST_REQUESTED,
REALM_LIST_RECEIVED,
FAILED
};
// Authentication callbacks
using AuthSuccessCallback = std::function<void(const std::vector<uint8_t>& sessionKey)>;
using AuthFailureCallback = std::function<void(const std::string& reason)>;
using RealmListCallback = std::function<void(const std::vector<Realm>& realms)>;
class AuthHandler {
public:
AuthHandler();
~AuthHandler();
// Connection
bool connect(const std::string& host, uint16_t port = 3724);
void disconnect();
bool isConnected() const;
// Authentication
void authenticate(const std::string& username, const std::string& password);
// Realm list
void requestRealmList();
const std::vector<Realm>& getRealms() const { return realms; }
// State
AuthState getState() const { return state; }
const std::vector<uint8_t>& getSessionKey() const { return sessionKey; }
// Callbacks
void setOnSuccess(AuthSuccessCallback callback) { onSuccess = callback; }
void setOnFailure(AuthFailureCallback callback) { onFailure = callback; }
void setOnRealmList(RealmListCallback callback) { onRealmList = callback; }
// Update (call each frame)
void update(float deltaTime);
private:
void sendLogonChallenge();
void handleLogonChallengeResponse(network::Packet& packet);
void sendLogonProof();
void handleLogonProofResponse(network::Packet& packet);
void sendRealmListRequest();
void handleRealmListResponse(network::Packet& packet);
void handlePacket(network::Packet& packet);
void setState(AuthState newState);
void fail(const std::string& reason);
std::unique_ptr<network::TCPSocket> socket;
std::unique_ptr<SRP> srp;
AuthState state = AuthState::DISCONNECTED;
std::string username;
std::string password;
ClientInfo clientInfo;
std::vector<uint8_t> sessionKey;
std::vector<Realm> realms;
// Callbacks
AuthSuccessCallback onSuccess;
AuthFailureCallback onFailure;
RealmListCallback onRealmList;
// Receive buffer
std::vector<uint8_t> receiveBuffer;
};
} // namespace auth
} // namespace wowee

View file

@ -0,0 +1,43 @@
#pragma once
#include <cstdint>
namespace wowee {
namespace auth {
// Authentication server opcodes
enum class AuthOpcode : uint8_t {
LOGON_CHALLENGE = 0x00,
LOGON_PROOF = 0x01,
RECONNECT_CHALLENGE = 0x02,
RECONNECT_PROOF = 0x03,
REALM_LIST = 0x10,
};
// LOGON_CHALLENGE response status codes
enum class AuthResult : uint8_t {
SUCCESS = 0x00,
UNKNOWN0 = 0x01,
UNKNOWN1 = 0x02,
ACCOUNT_BANNED = 0x03,
ACCOUNT_INVALID = 0x04,
PASSWORD_INVALID = 0x05,
ALREADY_ONLINE = 0x06,
OUT_OF_CREDIT = 0x07,
BUSY = 0x08,
BUILD_INVALID = 0x09,
BUILD_UPDATE = 0x0A,
INVALID_SERVER = 0x0B,
ACCOUNT_SUSPENDED = 0x0C,
ACCESS_DENIED = 0x0D,
SURVEY = 0x0E,
PARENTAL_CONTROL = 0x0F,
LOCK_ENFORCED = 0x10,
TRIAL_EXPIRED = 0x11,
BATTLE_NET = 0x12,
};
const char* getAuthResultString(AuthResult result);
} // namespace auth
} // namespace wowee

View file

@ -0,0 +1,109 @@
#pragma once
#include "auth/auth_opcodes.hpp"
#include "network/packet.hpp"
#include <string>
#include <vector>
#include <cstdint>
namespace wowee {
namespace auth {
// Client build and version information
struct ClientInfo {
uint8_t majorVersion = 3;
uint8_t minorVersion = 3;
uint8_t patchVersion = 5;
uint16_t build = 12340; // 3.3.5a
std::string game = "WoW";
std::string platform = "x86";
std::string os = "Win";
std::string locale = "enUS";
uint32_t timezone = 0;
};
// LOGON_CHALLENGE packet builder
class LogonChallengePacket {
public:
static network::Packet build(const std::string& account, const ClientInfo& info = ClientInfo());
};
// LOGON_CHALLENGE response data
struct LogonChallengeResponse {
AuthResult result;
std::vector<uint8_t> B; // Server public ephemeral (32 bytes)
std::vector<uint8_t> g; // Generator (variable, usually 1 byte)
std::vector<uint8_t> N; // Prime modulus (variable, usually 256 bytes)
std::vector<uint8_t> salt; // Salt (32 bytes)
uint8_t securityFlags;
bool isSuccess() const { return result == AuthResult::SUCCESS; }
};
// LOGON_CHALLENGE response parser
class LogonChallengeResponseParser {
public:
static bool parse(network::Packet& packet, LogonChallengeResponse& response);
};
// LOGON_PROOF packet builder
class LogonProofPacket {
public:
static network::Packet build(const std::vector<uint8_t>& A,
const std::vector<uint8_t>& M1);
};
// LOGON_PROOF response data
struct LogonProofResponse {
uint8_t status;
std::vector<uint8_t> M2; // Server proof (20 bytes)
bool isSuccess() const { return status == 0; }
};
// LOGON_PROOF response parser
class LogonProofResponseParser {
public:
static bool parse(network::Packet& packet, LogonProofResponse& response);
};
// Realm data structure
struct Realm {
uint8_t icon;
uint8_t lock;
uint8_t flags;
std::string name;
std::string address;
float population;
uint8_t characters;
uint8_t timezone;
uint8_t id;
// Version info (conditional - only if flags & 0x04)
uint8_t majorVersion = 0;
uint8_t minorVersion = 0;
uint8_t patchVersion = 0;
uint16_t build = 0;
bool hasVersionInfo() const { return (flags & 0x04) != 0; }
};
// REALM_LIST packet builder
class RealmListPacket {
public:
static network::Packet build();
};
// REALM_LIST response data
struct RealmListResponse {
std::vector<Realm> realms;
};
// REALM_LIST response parser
class RealmListResponseParser {
public:
static bool parse(network::Packet& packet, RealmListResponse& response);
};
} // namespace auth
} // namespace wowee

55
include/auth/big_num.hpp Normal file
View file

@ -0,0 +1,55 @@
#pragma once
#include <vector>
#include <cstdint>
#include <string>
#include <openssl/bn.h>
namespace wowee {
namespace auth {
// Wrapper around OpenSSL BIGNUM for big integer arithmetic
class BigNum {
public:
BigNum();
explicit BigNum(uint32_t value);
explicit BigNum(const std::vector<uint8_t>& bytes, bool littleEndian = true);
~BigNum();
// Copy/move operations
BigNum(const BigNum& other);
BigNum& operator=(const BigNum& other);
BigNum(BigNum&& other) noexcept;
BigNum& operator=(BigNum&& other) noexcept;
// Factory methods
static BigNum fromRandom(int bytes);
static BigNum fromHex(const std::string& hex);
static BigNum fromDecimal(const std::string& dec);
// Arithmetic operations
BigNum add(const BigNum& other) const;
BigNum subtract(const BigNum& other) const;
BigNum multiply(const BigNum& other) const;
BigNum mod(const BigNum& modulus) const;
BigNum modPow(const BigNum& exponent, const BigNum& modulus) const;
// Comparison
bool equals(const BigNum& other) const;
bool isZero() const;
// Conversion
std::vector<uint8_t> toArray(bool littleEndian = true, int minSize = 0) const;
std::string toHex() const;
std::string toDecimal() const;
// Direct access (for advanced operations)
BIGNUM* getBN() { return bn; }
const BIGNUM* getBN() const { return bn; }
private:
BIGNUM* bn;
};
} // namespace auth
} // namespace wowee

27
include/auth/crypto.hpp Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include <vector>
#include <cstdint>
#include <string>
namespace wowee {
namespace auth {
class Crypto {
public:
static std::vector<uint8_t> sha1(const std::vector<uint8_t>& data);
static std::vector<uint8_t> sha1(const std::string& data);
/**
* HMAC-SHA1 message authentication code
*
* @param key Secret key
* @param data Data to authenticate
* @return 20-byte HMAC-SHA1 hash
*/
static std::vector<uint8_t> hmacSHA1(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& data);
};
} // namespace auth
} // namespace wowee

53
include/auth/rc4.hpp Normal file
View file

@ -0,0 +1,53 @@
#pragma once
#include <cstdint>
#include <vector>
namespace wowee {
namespace auth {
/**
* RC4 Stream Cipher
*
* Used for encrypting/decrypting World of Warcraft packet headers.
* Only the packet headers are encrypted; packet bodies remain plaintext.
*
* Implementation based on standard RC4 algorithm with 256-byte state.
*/
class RC4 {
public:
RC4();
~RC4() = default;
/**
* Initialize the RC4 cipher with a key
*
* @param key Key bytes for initialization
*/
void init(const std::vector<uint8_t>& key);
/**
* Process bytes through the RC4 cipher
* Encrypts or decrypts data in-place (RC4 is symmetric)
*
* @param data Pointer to data to process
* @param length Number of bytes to process
*/
void process(uint8_t* data, size_t length);
/**
* Drop the first N bytes of keystream
* WoW protocol requires dropping first 1024 bytes
*
* @param count Number of bytes to drop
*/
void drop(size_t count);
private:
uint8_t state[256]; // RC4 state array
uint8_t x; // First index
uint8_t y; // Second index
};
} // namespace auth
} // namespace wowee

75
include/auth/srp.hpp Normal file
View file

@ -0,0 +1,75 @@
#pragma once
#include "auth/big_num.hpp"
#include <vector>
#include <cstdint>
#include <string>
namespace wowee {
namespace auth {
// SRP6a implementation for World of Warcraft authentication
// Based on the original wowee JavaScript implementation
class SRP {
public:
SRP();
~SRP() = default;
// Initialize with username and password
void initialize(const std::string& username, const std::string& password);
// Feed server challenge data (B, g, N, salt)
void feed(const std::vector<uint8_t>& B,
const std::vector<uint8_t>& g,
const std::vector<uint8_t>& N,
const std::vector<uint8_t>& salt);
// Get client public ephemeral (A) - send to server
std::vector<uint8_t> getA() const;
// Get client proof (M1) - send to server
std::vector<uint8_t> getM1() const;
// Verify server proof (M2)
bool verifyServerProof(const std::vector<uint8_t>& serverM2) const;
// Get session key (K) - used for encryption
std::vector<uint8_t> getSessionKey() const;
private:
// WoW-specific SRP multiplier (k = 3)
static constexpr uint32_t K_VALUE = 3;
// Helper methods
std::vector<uint8_t> computeAuthHash(const std::string& username,
const std::string& password) const;
void computeClientEphemeral();
void computeSessionKey();
void computeProofs(const std::string& username);
// SRP values
BigNum g; // Generator
BigNum N; // Prime modulus
BigNum k; // Multiplier (3 for WoW)
BigNum s; // Salt
BigNum a; // Client private ephemeral
BigNum A; // Client public ephemeral
BigNum B; // Server public ephemeral
BigNum x; // Salted password hash
BigNum u; // Scrambling parameter
BigNum S; // Shared session key (raw)
// Derived values
std::vector<uint8_t> K; // Interleaved session key (40 bytes)
std::vector<uint8_t> M1; // Client proof (20 bytes)
std::vector<uint8_t> M2; // Expected server proof (20 bytes)
// Stored credentials
std::string stored_username;
std::string stored_password;
bool initialized = false;
};
} // namespace auth
} // namespace wowee

View file

@ -0,0 +1,106 @@
#pragma once
#include "core/window.hpp"
#include "core/input.hpp"
#include <memory>
#include <string>
#include <vector>
namespace wowee {
// Forward declarations
namespace rendering { class Renderer; }
namespace ui { class UIManager; }
namespace auth { class AuthHandler; }
namespace game { class GameHandler; class World; class NpcManager; }
namespace pipeline { class AssetManager; }
namespace core {
enum class AppState {
AUTHENTICATION,
REALM_SELECTION,
CHARACTER_SELECTION,
IN_GAME,
DISCONNECTED
};
class Application {
public:
Application();
~Application();
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
bool initialize();
void run();
void shutdown();
// State management
AppState getState() const { return state; }
void setState(AppState newState);
// Accessors
Window* getWindow() { return window.get(); }
rendering::Renderer* getRenderer() { return renderer.get(); }
ui::UIManager* getUIManager() { return uiManager.get(); }
auth::AuthHandler* getAuthHandler() { return authHandler.get(); }
game::GameHandler* getGameHandler() { return gameHandler.get(); }
game::World* getWorld() { return world.get(); }
pipeline::AssetManager* getAssetManager() { return assetManager.get(); }
// Singleton access
static Application& getInstance() { return *instance; }
// Single-player mode
void startSinglePlayer();
bool isSinglePlayer() const { return singlePlayerMode; }
// Weapon loading (called at spawn and on equipment change)
void loadEquippedWeapons();
// Character skin composite state (saved at spawn for re-compositing on equipment change)
const std::string& getBodySkinPath() const { return bodySkinPath_; }
const std::vector<std::string>& getUnderwearPaths() const { return underwearPaths_; }
uint32_t getSkinTextureSlotIndex() const { return skinTextureSlotIndex_; }
uint32_t getCloakTextureSlotIndex() const { return cloakTextureSlotIndex_; }
private:
void update(float deltaTime);
void render();
void setupUICallbacks();
void spawnPlayerCharacter();
void spawnNpcs();
static Application* instance;
std::unique_ptr<Window> window;
std::unique_ptr<rendering::Renderer> renderer;
std::unique_ptr<ui::UIManager> uiManager;
std::unique_ptr<auth::AuthHandler> authHandler;
std::unique_ptr<game::GameHandler> gameHandler;
std::unique_ptr<game::World> world;
std::unique_ptr<game::NpcManager> npcManager;
std::unique_ptr<pipeline::AssetManager> assetManager;
AppState state = AppState::AUTHENTICATION;
bool running = false;
bool singlePlayerMode = false;
bool playerCharacterSpawned = false;
bool npcsSpawned = false;
float lastFrameTime = 0.0f;
float movementHeartbeatTimer = 0.0f;
// Weapon model ID counter (starting high to avoid collision with character model IDs)
uint32_t nextWeaponModelId_ = 1000;
// Saved at spawn for skin re-compositing
std::string bodySkinPath_;
std::vector<std::string> underwearPaths_;
uint32_t skinTextureSlotIndex_ = 0;
uint32_t cloakTextureSlotIndex_ = 0;
};
} // namespace core
} // namespace wowee

57
include/core/input.hpp Normal file
View file

@ -0,0 +1,57 @@
#pragma once
#include <SDL2/SDL.h>
#include <array>
#include <glm/glm.hpp>
namespace wowee {
namespace core {
class Input {
public:
static Input& getInstance();
void update();
void handleEvent(const SDL_Event& event);
// Keyboard
bool isKeyPressed(SDL_Scancode key) const;
bool isKeyJustPressed(SDL_Scancode key) const;
bool isKeyJustReleased(SDL_Scancode key) const;
// Mouse
bool isMouseButtonPressed(int button) const;
bool isMouseButtonJustPressed(int button) const;
bool isMouseButtonJustReleased(int button) const;
glm::vec2 getMousePosition() const { return mousePosition; }
glm::vec2 getMouseDelta() const { return mouseDelta; }
float getMouseWheelDelta() const { return mouseWheelDelta; }
bool isMouseLocked() const { return mouseLocked; }
void setMouseLocked(bool locked);
private:
Input() = default;
~Input() = default;
Input(const Input&) = delete;
Input& operator=(const Input&) = delete;
static constexpr int NUM_KEYS = SDL_NUM_SCANCODES;
static constexpr int NUM_MOUSE_BUTTONS = 8;
std::array<bool, NUM_KEYS> currentKeyState = {};
std::array<bool, NUM_KEYS> previousKeyState = {};
std::array<bool, NUM_MOUSE_BUTTONS> currentMouseState = {};
std::array<bool, NUM_MOUSE_BUTTONS> previousMouseState = {};
glm::vec2 mousePosition = glm::vec2(0.0f);
glm::vec2 previousMousePosition = glm::vec2(0.0f);
glm::vec2 mouseDelta = glm::vec2(0.0f);
float mouseWheelDelta = 0.0f;
bool mouseLocked = false;
};
} // namespace core
} // namespace wowee

76
include/core/logger.hpp Normal file
View file

@ -0,0 +1,76 @@
#pragma once
#include <string>
#include <iostream>
#include <sstream>
#include <mutex>
namespace wowee {
namespace core {
enum class LogLevel {
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
class Logger {
public:
static Logger& getInstance();
void log(LogLevel level, const std::string& message);
void setLogLevel(LogLevel level);
template<typename... Args>
void debug(Args&&... args) {
log(LogLevel::DEBUG, format(std::forward<Args>(args)...));
}
template<typename... Args>
void info(Args&&... args) {
log(LogLevel::INFO, format(std::forward<Args>(args)...));
}
template<typename... Args>
void warning(Args&&... args) {
log(LogLevel::WARNING, format(std::forward<Args>(args)...));
}
template<typename... Args>
void error(Args&&... args) {
log(LogLevel::ERROR, format(std::forward<Args>(args)...));
}
template<typename... Args>
void fatal(Args&&... args) {
log(LogLevel::FATAL, format(std::forward<Args>(args)...));
}
private:
Logger() = default;
~Logger() = default;
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
template<typename... Args>
std::string format(Args&&... args) {
std::ostringstream oss;
(oss << ... << args);
return oss.str();
}
LogLevel minLevel = LogLevel::DEBUG;
std::mutex mutex;
};
// Convenience macros
#define LOG_DEBUG(...) wowee::core::Logger::getInstance().debug(__VA_ARGS__)
#define LOG_INFO(...) wowee::core::Logger::getInstance().info(__VA_ARGS__)
#define LOG_WARNING(...) wowee::core::Logger::getInstance().warning(__VA_ARGS__)
#define LOG_ERROR(...) wowee::core::Logger::getInstance().error(__VA_ARGS__)
#define LOG_FATAL(...) wowee::core::Logger::getInstance().fatal(__VA_ARGS__)
} // namespace core
} // namespace wowee

54
include/core/window.hpp Normal file
View file

@ -0,0 +1,54 @@
#pragma once
#include <string>
#include <memory>
#include <SDL2/SDL.h>
namespace wowee {
namespace core {
struct WindowConfig {
std::string title = "Wowser Native";
int width = 1920;
int height = 1080;
bool fullscreen = false;
bool vsync = true;
bool resizable = true;
};
class Window {
public:
explicit Window(const WindowConfig& config);
~Window();
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
bool initialize();
void shutdown();
void swapBuffers();
void pollEvents();
bool shouldClose() const { return shouldCloseFlag; }
void setShouldClose(bool value) { shouldCloseFlag = value; }
int getWidth() const { return width; }
int getHeight() const { return height; }
float getAspectRatio() const { return static_cast<float>(width) / height; }
SDL_Window* getSDLWindow() const { return window; }
SDL_GLContext getGLContext() const { return glContext; }
private:
WindowConfig config;
SDL_Window* window = nullptr;
SDL_GLContext glContext = nullptr;
int width;
int height;
bool shouldCloseFlag = false;
};
} // namespace core
} // namespace wowee

129
include/game/character.hpp Normal file
View file

@ -0,0 +1,129 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace game {
/**
* Race IDs (WoW 3.3.5a)
*/
enum class Race : uint8_t {
HUMAN = 1,
ORC = 2,
DWARF = 3,
NIGHT_ELF = 4,
UNDEAD = 5,
TAUREN = 6,
GNOME = 7,
TROLL = 8,
GOBLIN = 9,
BLOOD_ELF = 10,
DRAENEI = 11
};
/**
* Class IDs (WoW 3.3.5a)
*/
enum class Class : uint8_t {
WARRIOR = 1,
PALADIN = 2,
HUNTER = 3,
ROGUE = 4,
PRIEST = 5,
DEATH_KNIGHT = 6,
SHAMAN = 7,
MAGE = 8,
WARLOCK = 9,
DRUID = 11
};
/**
* Gender IDs
*/
enum class Gender : uint8_t {
MALE = 0,
FEMALE = 1
};
/**
* Equipment item data
*/
struct EquipmentItem {
uint32_t displayModel; // Display model ID
uint8_t inventoryType; // Inventory slot type
uint32_t enchantment; // Enchantment/effect ID
bool isEmpty() const { return displayModel == 0; }
};
/**
* Pet data (optional)
*/
struct PetData {
uint32_t displayModel; // Pet display model ID
uint32_t level; // Pet level
uint32_t family; // Pet family ID
bool exists() const { return displayModel != 0; }
};
/**
* Complete character data from SMSG_CHAR_ENUM
*/
struct Character {
// Identity
uint64_t guid; // Character GUID (unique identifier)
std::string name; // Character name
// Basics
Race race; // Character race
Class characterClass; // Character class (renamed from 'class' keyword)
Gender gender; // Character gender
uint8_t level; // Character level (1-80)
// Appearance
uint32_t appearanceBytes; // Custom appearance (skin, hair color, hair style, face)
uint8_t facialFeatures; // Facial features
// Location
uint32_t zoneId; // Current zone ID
uint32_t mapId; // Current map ID
float x; // X coordinate
float y; // Y coordinate
float z; // Z coordinate
// Affiliations
uint32_t guildId; // Guild ID (0 if no guild)
// State
uint32_t flags; // Character flags (PvP, dead, etc.)
// Optional data
PetData pet; // Pet information (if exists)
std::vector<EquipmentItem> equipment; // Equipment (23 slots)
// Helper methods
bool hasGuild() const { return guildId != 0; }
bool hasPet() const { return pet.exists(); }
};
/**
* Get human-readable race name
*/
const char* getRaceName(Race race);
/**
* Get human-readable class name
*/
const char* getClassName(Class characterClass);
/**
* Get human-readable gender name
*/
const char* getGenderName(Gender gender);
} // namespace game
} // namespace wowee

211
include/game/entity.hpp Normal file
View file

@ -0,0 +1,211 @@
#pragma once
#include <cstdint>
#include <string>
#include <map>
#include <memory>
namespace wowee {
namespace game {
/**
* Object type IDs for WoW 3.3.5a
*/
enum class ObjectType : uint8_t {
OBJECT = 0,
ITEM = 1,
CONTAINER = 2,
UNIT = 3,
PLAYER = 4,
GAMEOBJECT = 5,
DYNAMICOBJECT = 6,
CORPSE = 7
};
/**
* Object type masks for update packets
*/
enum class TypeMask : uint16_t {
OBJECT = 0x0001,
ITEM = 0x0002,
CONTAINER = 0x0004,
UNIT = 0x0008,
PLAYER = 0x0010,
GAMEOBJECT = 0x0020,
DYNAMICOBJECT = 0x0040,
CORPSE = 0x0080
};
/**
* Update types for SMSG_UPDATE_OBJECT
*/
enum class UpdateType : uint8_t {
VALUES = 0, // Partial update (changed fields only)
MOVEMENT = 1, // Movement update
CREATE_OBJECT = 2, // Create new object (full data)
CREATE_OBJECT2 = 3, // Create new object (alternate format)
OUT_OF_RANGE_OBJECTS = 4, // Objects left view range
NEAR_OBJECTS = 5 // Objects entered view range
};
/**
* Base entity class for all game objects
*/
class Entity {
public:
Entity() = default;
explicit Entity(uint64_t guid) : guid(guid) {}
virtual ~Entity() = default;
// GUID access
uint64_t getGuid() const { return guid; }
void setGuid(uint64_t g) { guid = g; }
// Position
float getX() const { return x; }
float getY() const { return y; }
float getZ() const { return z; }
float getOrientation() const { return orientation; }
void setPosition(float px, float py, float pz, float o) {
x = px;
y = py;
z = pz;
orientation = o;
}
// Object type
ObjectType getType() const { return type; }
void setType(ObjectType t) { type = t; }
// Fields (for update values)
void setField(uint16_t index, uint32_t value) {
fields[index] = value;
}
uint32_t getField(uint16_t index) const {
auto it = fields.find(index);
return (it != fields.end()) ? it->second : 0;
}
bool hasField(uint16_t index) const {
return fields.find(index) != fields.end();
}
const std::map<uint16_t, uint32_t>& getFields() const {
return fields;
}
protected:
uint64_t guid = 0;
ObjectType type = ObjectType::OBJECT;
// Position
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
float orientation = 0.0f;
// Update fields (dynamic values)
std::map<uint16_t, uint32_t> fields;
};
/**
* Unit entity (NPCs, creatures, players)
*/
class Unit : public Entity {
public:
Unit() { type = ObjectType::UNIT; }
explicit Unit(uint64_t guid) : Entity(guid) { type = ObjectType::UNIT; }
// Name
const std::string& getName() const { return name; }
void setName(const std::string& n) { name = n; }
// Health
uint32_t getHealth() const { return health; }
void setHealth(uint32_t h) { health = h; }
uint32_t getMaxHealth() const { return maxHealth; }
void setMaxHealth(uint32_t h) { maxHealth = h; }
// Level
uint32_t getLevel() const { return level; }
void setLevel(uint32_t l) { level = l; }
protected:
std::string name;
uint32_t health = 0;
uint32_t maxHealth = 0;
uint32_t level = 1;
};
/**
* Player entity
*/
class Player : public Unit {
public:
Player() { type = ObjectType::PLAYER; }
explicit Player(uint64_t guid) : Unit(guid) { type = ObjectType::PLAYER; }
// Name
const std::string& getName() const { return name; }
void setName(const std::string& n) { name = n; }
protected:
std::string name;
};
/**
* GameObject entity (doors, chests, etc.)
*/
class GameObject : public Entity {
public:
GameObject() { type = ObjectType::GAMEOBJECT; }
explicit GameObject(uint64_t guid) : Entity(guid) { type = ObjectType::GAMEOBJECT; }
uint32_t getDisplayId() const { return displayId; }
void setDisplayId(uint32_t id) { displayId = id; }
protected:
uint32_t displayId = 0;
};
/**
* Entity manager for tracking all entities in view
*/
class EntityManager {
public:
// Add entity
void addEntity(uint64_t guid, std::shared_ptr<Entity> entity);
// Remove entity
void removeEntity(uint64_t guid);
// Get entity
std::shared_ptr<Entity> getEntity(uint64_t guid) const;
// Check if entity exists
bool hasEntity(uint64_t guid) const;
// Get all entities
const std::map<uint64_t, std::shared_ptr<Entity>>& getEntities() const {
return entities;
}
// Clear all entities
void clear() {
entities.clear();
}
// Get entity count
size_t getEntityCount() const {
return entities.size();
}
private:
std::map<uint64_t, std::shared_ptr<Entity>> entities;
};
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,310 @@
#pragma once
#include "game/world_packets.hpp"
#include "game/character.hpp"
#include "game/inventory.hpp"
#include <memory>
#include <string>
#include <vector>
#include <functional>
#include <cstdint>
namespace wowee {
namespace network { class WorldSocket; class Packet; }
namespace game {
/**
* World connection state
*/
enum class WorldState {
DISCONNECTED, // Not connected
CONNECTING, // TCP connection in progress
CONNECTED, // Connected, waiting for challenge
CHALLENGE_RECEIVED, // Received SMSG_AUTH_CHALLENGE
AUTH_SENT, // Sent CMSG_AUTH_SESSION, encryption initialized
AUTHENTICATED, // Received SMSG_AUTH_RESPONSE success
READY, // Ready for character/world operations
CHAR_LIST_REQUESTED, // CMSG_CHAR_ENUM sent
CHAR_LIST_RECEIVED, // SMSG_CHAR_ENUM received
ENTERING_WORLD, // CMSG_PLAYER_LOGIN sent
IN_WORLD, // In game world
FAILED // Connection or authentication failed
};
/**
* World connection callbacks
*/
using WorldConnectSuccessCallback = std::function<void()>;
using WorldConnectFailureCallback = std::function<void(const std::string& reason)>;
/**
* GameHandler - Manages world server connection and game protocol
*
* Handles:
* - Connection to world server
* - Authentication with session key from auth server
* - RC4 header encryption
* - Character enumeration
* - World entry
* - Game packets
*/
class GameHandler {
public:
GameHandler();
~GameHandler();
/**
* Connect to world server
*
* @param host World server hostname/IP
* @param port World server port (default 8085)
* @param sessionKey 40-byte session key from auth server
* @param accountName Account name (will be uppercased)
* @param build Client build number (default 12340 for 3.3.5a)
* @return true if connection initiated
*/
bool connect(const std::string& host,
uint16_t port,
const std::vector<uint8_t>& sessionKey,
const std::string& accountName,
uint32_t build = 12340);
/**
* Disconnect from world server
*/
void disconnect();
/**
* Check if connected to world server
*/
bool isConnected() const;
/**
* Get current connection state
*/
WorldState getState() const { return state; }
/**
* Request character list from server
* Must be called when state is READY or AUTHENTICATED
*/
void requestCharacterList();
/**
* Get list of characters (available after CHAR_LIST_RECEIVED state)
*/
const std::vector<Character>& getCharacters() const { return characters; }
/**
* Select and log in with a character
* @param characterGuid GUID of character to log in with
*/
void selectCharacter(uint64_t characterGuid);
/**
* Get current player movement info
*/
const MovementInfo& getMovementInfo() const { return movementInfo; }
/**
* Send a movement packet
* @param opcode Movement opcode (CMSG_MOVE_START_FORWARD, etc.)
*/
void sendMovement(Opcode opcode);
/**
* Update player position
* @param x X coordinate
* @param y Y coordinate
* @param z Z coordinate
*/
void setPosition(float x, float y, float z);
/**
* Update player orientation
* @param orientation Facing direction in radians
*/
void setOrientation(float orientation);
/**
* Get entity manager (for accessing entities in view)
*/
EntityManager& getEntityManager() { return entityManager; }
const EntityManager& getEntityManager() const { return entityManager; }
/**
* Send a chat message
* @param type Chat type (SAY, YELL, WHISPER, etc.)
* @param message Message text
* @param target Target name (for whispers, empty otherwise)
*/
void sendChatMessage(ChatType type, const std::string& message, const std::string& target = "");
/**
* Get chat history (recent messages)
* @param maxMessages Maximum number of messages to return (0 = all)
* @return Vector of chat messages
*/
std::vector<MessageChatData> getChatHistory(size_t maxMessages = 50) const;
/**
* Add a locally-generated chat message (e.g., emote feedback)
*/
void addLocalChatMessage(const MessageChatData& msg);
// Inventory
Inventory& getInventory() { return inventory; }
const Inventory& getInventory() const { return inventory; }
// Targeting
void setTarget(uint64_t guid);
void clearTarget();
uint64_t getTargetGuid() const { return targetGuid; }
std::shared_ptr<Entity> getTarget() const;
bool hasTarget() const { return targetGuid != 0; }
void tabTarget(float playerX, float playerY, float playerZ);
/**
* Set callbacks
*/
void setOnSuccess(WorldConnectSuccessCallback callback) { onSuccess = callback; }
void setOnFailure(WorldConnectFailureCallback callback) { onFailure = callback; }
/**
* Update - call regularly (e.g., each frame)
*
* @param deltaTime Time since last update in seconds
*/
void update(float deltaTime);
private:
/**
* Handle incoming packet from world server
*/
void handlePacket(network::Packet& packet);
/**
* Handle SMSG_AUTH_CHALLENGE from server
*/
void handleAuthChallenge(network::Packet& packet);
/**
* Handle SMSG_AUTH_RESPONSE from server
*/
void handleAuthResponse(network::Packet& packet);
/**
* Handle SMSG_CHAR_ENUM from server
*/
void handleCharEnum(network::Packet& packet);
/**
* Handle SMSG_LOGIN_VERIFY_WORLD from server
*/
void handleLoginVerifyWorld(network::Packet& packet);
/**
* Handle SMSG_ACCOUNT_DATA_TIMES from server
*/
void handleAccountDataTimes(network::Packet& packet);
/**
* Handle SMSG_MOTD from server
*/
void handleMotd(network::Packet& packet);
/**
* Handle SMSG_PONG from server
*/
void handlePong(network::Packet& packet);
/**
* Handle SMSG_UPDATE_OBJECT from server
*/
void handleUpdateObject(network::Packet& packet);
/**
* Handle SMSG_DESTROY_OBJECT from server
*/
void handleDestroyObject(network::Packet& packet);
/**
* Handle SMSG_MESSAGECHAT from server
*/
void handleMessageChat(network::Packet& packet);
/**
* Send CMSG_PING to server (heartbeat)
*/
void sendPing();
/**
* Send CMSG_AUTH_SESSION to server
*/
void sendAuthSession();
/**
* Generate random client seed
*/
uint32_t generateClientSeed();
/**
* Change state with logging
*/
void setState(WorldState newState);
/**
* Fail connection with reason
*/
void fail(const std::string& reason);
// Network
std::unique_ptr<network::WorldSocket> socket;
// State
WorldState state = WorldState::DISCONNECTED;
// Authentication data
std::vector<uint8_t> sessionKey; // 40-byte session key from auth server
std::string accountName; // Account name
uint32_t build = 12340; // Client build (3.3.5a)
uint32_t clientSeed = 0; // Random seed generated by client
uint32_t serverSeed = 0; // Seed from SMSG_AUTH_CHALLENGE
// Characters
std::vector<Character> characters; // Character list from SMSG_CHAR_ENUM
// Movement
MovementInfo movementInfo; // Current player movement state
uint32_t movementTime = 0; // Movement timestamp counter
// Inventory
Inventory inventory;
// Entity tracking
EntityManager entityManager; // Manages all entities in view
// Chat
std::vector<MessageChatData> chatHistory; // Recent chat messages
size_t maxChatHistory = 100; // Maximum chat messages to keep
// Targeting
uint64_t targetGuid = 0;
std::vector<uint64_t> tabCycleList;
int tabCycleIndex = -1;
bool tabCycleStale = true;
// Heartbeat
uint32_t pingSequence = 0; // Ping sequence number (increments)
float timeSinceLastPing = 0.0f; // Time since last ping sent (seconds)
float pingInterval = 30.0f; // Ping interval (30 seconds)
uint32_t lastLatency = 0; // Last measured latency (milliseconds)
// Callbacks
WorldConnectSuccessCallback onSuccess;
WorldConnectFailureCallback onFailure;
};
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,99 @@
#pragma once
#include <cstdint>
#include <string>
#include <array>
namespace wowee {
namespace game {
enum class ItemQuality : uint8_t {
POOR = 0, // Grey
COMMON = 1, // White
UNCOMMON = 2, // Green
RARE = 3, // Blue
EPIC = 4, // Purple
LEGENDARY = 5, // Orange
};
enum class EquipSlot : uint8_t {
HEAD = 0, NECK, SHOULDERS, SHIRT, CHEST,
WAIST, LEGS, FEET, WRISTS, HANDS,
RING1, RING2, TRINKET1, TRINKET2,
BACK, MAIN_HAND, OFF_HAND, RANGED, TABARD,
BAG1, BAG2, BAG3, BAG4,
NUM_SLOTS // = 23
};
struct ItemDef {
uint32_t itemId = 0;
std::string name;
std::string subclassName; // "Sword", "Mace", "Shield", etc.
ItemQuality quality = ItemQuality::COMMON;
uint8_t inventoryType = 0;
uint32_t stackCount = 1;
uint32_t maxStack = 1;
uint32_t bagSlots = 0;
// Stats
int32_t armor = 0;
int32_t stamina = 0;
int32_t strength = 0;
int32_t agility = 0;
int32_t intellect = 0;
int32_t spirit = 0;
uint32_t displayInfoId = 0;
};
struct ItemSlot {
ItemDef item;
bool empty() const { return item.itemId == 0; }
};
class Inventory {
public:
static constexpr int BACKPACK_SLOTS = 16;
static constexpr int NUM_EQUIP_SLOTS = 23;
static constexpr int NUM_BAG_SLOTS = 4;
static constexpr int MAX_BAG_SIZE = 36;
Inventory();
// Backpack
const ItemSlot& getBackpackSlot(int index) const;
bool setBackpackSlot(int index, const ItemDef& item);
bool clearBackpackSlot(int index);
int getBackpackSize() const { return BACKPACK_SLOTS; }
// Equipment
const ItemSlot& getEquipSlot(EquipSlot slot) const;
bool setEquipSlot(EquipSlot slot, const ItemDef& item);
bool clearEquipSlot(EquipSlot slot);
// Extra bags
int getBagSize(int bagIndex) const;
const ItemSlot& getBagSlot(int bagIndex, int slotIndex) const;
bool setBagSlot(int bagIndex, int slotIndex, const ItemDef& item);
// Utility
int findFreeBackpackSlot() const;
bool addItem(const ItemDef& item);
// Test data
void populateTestItems();
private:
std::array<ItemSlot, BACKPACK_SLOTS> backpack{};
std::array<ItemSlot, NUM_EQUIP_SLOTS> equipment{};
struct BagData {
int size = 0;
std::array<ItemSlot, MAX_BAG_SIZE> slots{};
};
std::array<BagData, NUM_BAG_SLOTS> bags{};
};
const char* getQualityName(ItemQuality quality);
const char* getEquipSlotName(EquipSlot slot);
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,57 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <unordered_map>
#include <glm/glm.hpp>
namespace wowee {
namespace pipeline { class AssetManager; }
namespace rendering { class CharacterRenderer; }
namespace game {
class EntityManager;
struct NpcSpawnDef {
std::string name;
std::string m2Path;
uint32_t level;
uint32_t health;
glm::vec3 glPosition; // GL world coords (pre-converted)
float rotation; // radians around Z
float scale;
bool isCritter; // critters don't do humanoid emotes
};
struct NpcInstance {
uint64_t guid;
uint32_t renderInstanceId;
float emoteTimer; // countdown to next random emote
float emoteEndTimer; // countdown until emote animation finishes
bool isEmoting;
bool isCritter;
};
class NpcManager {
public:
void initialize(pipeline::AssetManager* am,
rendering::CharacterRenderer* cr,
EntityManager& em,
const glm::vec3& playerSpawnGL);
void update(float deltaTime, rendering::CharacterRenderer* cr);
private:
void loadCreatureModel(pipeline::AssetManager* am,
rendering::CharacterRenderer* cr,
const std::string& m2Path,
uint32_t modelId);
std::vector<NpcInstance> npcs;
std::unordered_map<std::string, uint32_t> loadedModels; // path -> modelId
uint64_t nextGuid = 0xF1300000DEAD0001ULL;
uint32_t nextModelId = 100;
};
} // namespace game
} // namespace wowee

55
include/game/opcodes.hpp Normal file
View file

@ -0,0 +1,55 @@
#pragma once
#include <cstdint>
namespace wowee {
namespace game {
// World of Warcraft 3.3.5a opcodes
enum class Opcode : uint16_t {
// Client to Server
CMSG_PING = 0x1DC,
CMSG_AUTH_SESSION = 0x1ED,
CMSG_CHAR_ENUM = 0x037,
CMSG_PLAYER_LOGIN = 0x03D,
// Movement
CMSG_MOVE_START_FORWARD = 0x0B5,
CMSG_MOVE_START_BACKWARD = 0x0B6,
CMSG_MOVE_STOP = 0x0B7,
CMSG_MOVE_START_STRAFE_LEFT = 0x0B8,
CMSG_MOVE_START_STRAFE_RIGHT = 0x0B9,
CMSG_MOVE_STOP_STRAFE = 0x0BA,
CMSG_MOVE_JUMP = 0x0BB,
CMSG_MOVE_START_TURN_LEFT = 0x0BC,
CMSG_MOVE_START_TURN_RIGHT = 0x0BD,
CMSG_MOVE_STOP_TURN = 0x0BE,
CMSG_MOVE_SET_FACING = 0x0DA,
CMSG_MOVE_FALL_LAND = 0x0C9,
CMSG_MOVE_START_SWIM = 0x0CA,
CMSG_MOVE_STOP_SWIM = 0x0CB,
CMSG_MOVE_HEARTBEAT = 0x0EE,
// Server to Client
SMSG_AUTH_CHALLENGE = 0x1EC,
SMSG_AUTH_RESPONSE = 0x1EE,
SMSG_CHAR_ENUM = 0x03B,
SMSG_PONG = 0x1DD,
SMSG_LOGIN_VERIFY_WORLD = 0x236,
SMSG_ACCOUNT_DATA_TIMES = 0x209,
SMSG_FEATURE_SYSTEM_STATUS = 0x3ED,
SMSG_MOTD = 0x33D,
// Entity/Object updates
SMSG_UPDATE_OBJECT = 0x0A9,
SMSG_DESTROY_OBJECT = 0x0AA,
// Chat
CMSG_MESSAGECHAT = 0x095,
SMSG_MESSAGECHAT = 0x096,
// TODO: Add more opcodes as needed
};
} // namespace game
} // namespace wowee

18
include/game/player.hpp Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include <glm/glm.hpp>
namespace wowee {
namespace game {
class Player {
public:
void setPosition(const glm::vec3& pos) { position = pos; }
const glm::vec3& getPosition() const { return position; }
private:
glm::vec3 position = glm::vec3(0.0f);
};
} // namespace game
} // namespace wowee

18
include/game/world.hpp Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include <cstdint>
namespace wowee {
namespace game {
class World {
public:
World() = default;
~World() = default;
void update(float deltaTime);
void loadMap(uint32_t mapId);
};
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,566 @@
#pragma once
#include "network/packet.hpp"
#include "game/opcodes.hpp"
#include "game/entity.hpp"
#include <vector>
#include <cstdint>
#include <string>
#include <map>
namespace wowee {
namespace game {
/**
* SMSG_AUTH_CHALLENGE data (from server)
*
* Sent by world server immediately after TCP connect
* Contains server seed/salt for authentication hash
*/
struct AuthChallengeData {
uint32_t unknown1; // Always seems to be 0x00000001
uint32_t serverSeed; // Random seed from server
// Note: 3.3.5a has additional data after this
bool isValid() const { return unknown1 != 0; }
};
/**
* CMSG_AUTH_SESSION packet builder
*
* Client authentication to world server using session key from auth server
*/
class AuthSessionPacket {
public:
/**
* Build CMSG_AUTH_SESSION packet
*
* @param build Client build number (12340 for 3.3.5a)
* @param accountName Account name (uppercase)
* @param clientSeed Random 4-byte seed generated by client
* @param sessionKey 40-byte session key from auth server
* @param serverSeed 4-byte seed from SMSG_AUTH_CHALLENGE
* @return Packet ready to send
*/
static network::Packet build(uint32_t build,
const std::string& accountName,
uint32_t clientSeed,
const std::vector<uint8_t>& sessionKey,
uint32_t serverSeed);
private:
/**
* Compute authentication hash
*
* SHA1(account + [0,0,0,0] + clientSeed + serverSeed + sessionKey)
*
* @param accountName Account name
* @param clientSeed Client seed
* @param serverSeed Server seed
* @param sessionKey 40-byte session key
* @return 20-byte SHA1 hash
*/
static std::vector<uint8_t> computeAuthHash(
const std::string& accountName,
uint32_t clientSeed,
uint32_t serverSeed,
const std::vector<uint8_t>& sessionKey);
};
/**
* SMSG_AUTH_CHALLENGE response parser
*/
class AuthChallengeParser {
public:
static bool parse(network::Packet& packet, AuthChallengeData& data);
};
/**
* SMSG_AUTH_RESPONSE result codes
*/
enum class AuthResult : uint8_t {
OK = 0x00, // Success, proceed to character screen
FAILED = 0x01, // Generic failure
REJECT = 0x02, // Reject
BAD_SERVER_PROOF = 0x03, // Bad server proof
UNAVAILABLE = 0x04, // Unavailable
SYSTEM_ERROR = 0x05, // System error
BILLING_ERROR = 0x06, // Billing error
BILLING_EXPIRED = 0x07, // Billing expired
VERSION_MISMATCH = 0x08, // Version mismatch
UNKNOWN_ACCOUNT = 0x09, // Unknown account
INCORRECT_PASSWORD = 0x0A, // Incorrect password
SESSION_EXPIRED = 0x0B, // Session expired
SERVER_SHUTTING_DOWN = 0x0C, // Server shutting down
ALREADY_LOGGING_IN = 0x0D, // Already logging in
LOGIN_SERVER_NOT_FOUND = 0x0E, // Login server not found
WAIT_QUEUE = 0x0F, // Wait queue
BANNED = 0x10, // Banned
ALREADY_ONLINE = 0x11, // Already online
NO_TIME = 0x12, // No game time
DB_BUSY = 0x13, // DB busy
SUSPENDED = 0x14, // Suspended
PARENTAL_CONTROL = 0x15, // Parental control
LOCKED_ENFORCED = 0x16 // Account locked
};
/**
* SMSG_AUTH_RESPONSE data (from server)
*/
struct AuthResponseData {
AuthResult result;
bool isSuccess() const { return result == AuthResult::OK; }
};
/**
* SMSG_AUTH_RESPONSE parser
*/
class AuthResponseParser {
public:
static bool parse(network::Packet& packet, AuthResponseData& data);
};
/**
* Get human-readable string for auth result
*/
const char* getAuthResultString(AuthResult result);
// Forward declare Character
struct Character;
/**
* CMSG_CHAR_ENUM packet builder
*
* Request list of characters on account
*/
class CharEnumPacket {
public:
/**
* Build CMSG_CHAR_ENUM packet
*
* This packet has no body - just the opcode
*/
static network::Packet build();
};
/**
* SMSG_CHAR_ENUM response data (from server)
*
* Contains list of all characters on the account
*/
struct CharEnumResponse {
std::vector<Character> characters;
bool isEmpty() const { return characters.empty(); }
size_t count() const { return characters.size(); }
};
/**
* SMSG_CHAR_ENUM response parser
*/
class CharEnumParser {
public:
static bool parse(network::Packet& packet, CharEnumResponse& response);
};
/**
* CMSG_PLAYER_LOGIN packet builder
*
* Select character and enter world
*/
class PlayerLoginPacket {
public:
/**
* Build CMSG_PLAYER_LOGIN packet
*
* @param characterGuid GUID of character to log in with
*/
static network::Packet build(uint64_t characterGuid);
};
/**
* SMSG_LOGIN_VERIFY_WORLD data (from server)
*
* Confirms successful world entry with initial position and map info
*/
struct LoginVerifyWorldData {
uint32_t mapId; // Map ID where character spawned
float x, y, z; // Initial position coordinates
float orientation; // Initial orientation (facing direction)
bool isValid() const { return mapId != 0xFFFFFFFF; }
};
/**
* SMSG_LOGIN_VERIFY_WORLD parser
*/
class LoginVerifyWorldParser {
public:
static bool parse(network::Packet& packet, LoginVerifyWorldData& data);
};
/**
* SMSG_ACCOUNT_DATA_TIMES data (from server)
*
* Contains timestamps for account data (macros, keybindings, etc.)
*/
struct AccountDataTimesData {
uint32_t serverTime; // Current server time (Unix timestamp)
uint8_t unknown; // Unknown (always 1?)
uint32_t accountDataTimes[8]; // Timestamps for 8 account data slots
bool isValid() const { return true; }
};
/**
* SMSG_ACCOUNT_DATA_TIMES parser
*/
class AccountDataTimesParser {
public:
static bool parse(network::Packet& packet, AccountDataTimesData& data);
};
/**
* SMSG_MOTD data (from server)
*
* Message of the Day from server
*/
struct MotdData {
std::vector<std::string> lines; // MOTD text lines
bool isEmpty() const { return lines.empty(); }
size_t lineCount() const { return lines.size(); }
};
/**
* SMSG_MOTD parser
*/
class MotdParser {
public:
static bool parse(network::Packet& packet, MotdData& data);
};
/**
* CMSG_PING packet builder
*
* Heartbeat packet sent periodically to keep connection alive
*/
class PingPacket {
public:
/**
* Build CMSG_PING packet
*
* @param sequence Sequence number (increments with each ping)
* @param latency Client-side latency estimate in milliseconds
* @return Packet ready to send
*/
static network::Packet build(uint32_t sequence, uint32_t latency);
};
/**
* SMSG_PONG data (from server)
*
* Response to CMSG_PING, echoes back the sequence number
*/
struct PongData {
uint32_t sequence; // Sequence number from CMSG_PING
bool isValid() const { return true; }
};
/**
* SMSG_PONG parser
*/
class PongParser {
public:
static bool parse(network::Packet& packet, PongData& data);
};
/**
* Movement flags for player movement
*/
enum class MovementFlags : uint32_t {
NONE = 0x00000000,
FORWARD = 0x00000001,
BACKWARD = 0x00000002,
STRAFE_LEFT = 0x00000004,
STRAFE_RIGHT = 0x00000008,
TURN_LEFT = 0x00000010,
TURN_RIGHT = 0x00000020,
PITCH_UP = 0x00000040,
PITCH_DOWN = 0x00000080,
WALKING = 0x00000100,
ONTRANSPORT = 0x00000200,
LEVITATING = 0x00000400,
ROOT = 0x00000800,
FALLING = 0x00001000,
FALLINGFAR = 0x00002000,
SWIMMING = 0x00200000,
ASCENDING = 0x00400000,
CAN_FLY = 0x00800000,
FLYING = 0x01000000,
};
/**
* Movement info structure
*
* Contains all movement-related data sent in movement packets
*/
struct MovementInfo {
uint32_t flags = 0; // Movement flags
uint16_t flags2 = 0; // Extra movement flags
uint32_t time = 0; // Movement timestamp (milliseconds)
float x = 0.0f; // Position X
float y = 0.0f; // Position Y
float z = 0.0f; // Position Z
float orientation = 0.0f; // Facing direction (radians)
// Optional fields (based on flags)
float pitch = 0.0f; // Pitch angle (swimming/flying)
uint32_t fallTime = 0; // Time falling (milliseconds)
float jumpVelocity = 0.0f; // Jump vertical velocity
float jumpSinAngle = 0.0f; // Jump horizontal sin
float jumpCosAngle = 0.0f; // Jump horizontal cos
float jumpXYSpeed = 0.0f; // Jump horizontal speed
bool hasFlag(MovementFlags flag) const {
return (flags & static_cast<uint32_t>(flag)) != 0;
}
};
/**
* Movement packet builder
*
* Builds CMSG_MOVE_* packets with movement info
*/
class MovementPacket {
public:
/**
* Build a movement packet
*
* @param opcode Movement opcode (CMSG_MOVE_START_FORWARD, etc.)
* @param info Movement info
* @return Packet ready to send
*/
static network::Packet build(Opcode opcode, const MovementInfo& info);
};
// Forward declare Entity types
class Entity;
class EntityManager;
enum class ObjectType : uint8_t;
enum class UpdateType : uint8_t;
/**
* Update block for a single object in SMSG_UPDATE_OBJECT
*/
struct UpdateBlock {
UpdateType updateType;
uint64_t guid = 0;
ObjectType objectType;
// Movement data (for MOVEMENT updates)
bool hasMovement = false;
float x = 0.0f, y = 0.0f, z = 0.0f, orientation = 0.0f;
// Field data (for VALUES and CREATE updates)
std::map<uint16_t, uint32_t> fields;
};
/**
* SMSG_UPDATE_OBJECT data
*
* Contains all update blocks in the packet
*/
struct UpdateObjectData {
uint32_t blockCount = 0;
std::vector<UpdateBlock> blocks;
// Out-of-range GUIDs (for OUT_OF_RANGE_OBJECTS)
std::vector<uint64_t> outOfRangeGuids;
};
/**
* SMSG_UPDATE_OBJECT parser
*
* Parses object updates from server
*/
class UpdateObjectParser {
public:
/**
* Parse SMSG_UPDATE_OBJECT packet
*
* @param packet Packet to parse
* @param data Output data
* @return true if successful
*/
static bool parse(network::Packet& packet, UpdateObjectData& data);
private:
/**
* Parse a single update block
*
* @param packet Packet to read from
* @param block Output block
* @return true if successful
*/
static bool parseUpdateBlock(network::Packet& packet, UpdateBlock& block);
/**
* Parse movement block
*
* @param packet Packet to read from
* @param block Output block
* @return true if successful
*/
static bool parseMovementBlock(network::Packet& packet, UpdateBlock& block);
/**
* Parse update fields (mask + values)
*
* @param packet Packet to read from
* @param block Output block
* @return true if successful
*/
static bool parseUpdateFields(network::Packet& packet, UpdateBlock& block);
/**
* Read packed GUID from packet
*
* @param packet Packet to read from
* @return GUID value
*/
static uint64_t readPackedGuid(network::Packet& packet);
};
/**
* SMSG_DESTROY_OBJECT data
*/
struct DestroyObjectData {
uint64_t guid = 0;
bool isDeath = false; // true if unit died, false if despawned
};
/**
* SMSG_DESTROY_OBJECT parser
*/
class DestroyObjectParser {
public:
static bool parse(network::Packet& packet, DestroyObjectData& data);
};
/**
* Chat message types
*/
enum class ChatType : uint8_t {
SAY = 0,
PARTY = 1,
RAID = 2,
GUILD = 3,
OFFICER = 4,
YELL = 5,
WHISPER = 6,
WHISPER_INFORM = 7,
EMOTE = 8,
TEXT_EMOTE = 9,
SYSTEM = 10,
MONSTER_SAY = 11,
MONSTER_YELL = 12,
MONSTER_EMOTE = 13,
CHANNEL = 14,
CHANNEL_JOIN = 15,
CHANNEL_LEAVE = 16,
CHANNEL_LIST = 17,
CHANNEL_NOTICE = 18,
CHANNEL_NOTICE_USER = 19,
AFK = 20,
DND = 21,
IGNORED = 22,
SKILL = 23,
LOOT = 24,
BATTLEGROUND = 25,
BATTLEGROUND_LEADER = 26,
RAID_LEADER = 27,
RAID_WARNING = 28,
ACHIEVEMENT = 29,
GUILD_ACHIEVEMENT = 30
};
/**
* Chat language IDs
*/
enum class ChatLanguage : uint32_t {
UNIVERSAL = 0,
ORCISH = 1,
DARNASSIAN = 2,
TAURAHE = 3,
DWARVISH = 6,
COMMON = 7,
DEMONIC = 8,
TITAN = 9,
THALASSIAN = 10,
DRACONIC = 11,
KALIMAG = 12,
GNOMISH = 13,
TROLL = 14,
GUTTERSPEAK = 33,
DRAENEI = 35,
ZOMBIE = 36,
GNOMISH_BINARY = 37,
GOBLIN_BINARY = 38,
ADDON = 0xFFFFFFFF // Used for addon communication
};
/**
* CMSG_MESSAGECHAT packet builder
*/
class MessageChatPacket {
public:
/**
* Build CMSG_MESSAGECHAT packet
*
* @param type Chat type (SAY, YELL, etc.)
* @param language Language ID
* @param message Message text
* @param target Target name (for whispers, empty otherwise)
* @return Packet ready to send
*/
static network::Packet build(ChatType type,
ChatLanguage language,
const std::string& message,
const std::string& target = "");
};
/**
* SMSG_MESSAGECHAT data
*/
struct MessageChatData {
ChatType type;
ChatLanguage language;
uint64_t senderGuid = 0;
std::string senderName;
uint64_t receiverGuid = 0;
std::string receiverName;
std::string message;
std::string channelName; // For channel messages
uint8_t chatTag = 0; // Player flags (AFK, DND, GM, etc.)
bool isValid() const { return !message.empty(); }
};
/**
* SMSG_MESSAGECHAT parser
*/
class MessageChatParser {
public:
static bool parse(network::Packet& packet, MessageChatData& data);
};
/**
* Get human-readable string for chat type
*/
const char* getChatTypeString(ChatType type);
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,32 @@
#pragma once
#include <string>
#include <cstdint>
#include <unordered_map>
#include <vector>
namespace wowee {
namespace game {
struct ZoneInfo {
uint32_t id;
std::string name;
std::vector<std::string> musicPaths; // MPQ paths to music files
};
class ZoneManager {
public:
void initialize();
uint32_t getZoneId(int tileX, int tileY) const;
const ZoneInfo* getZoneInfo(uint32_t zoneId) const;
std::string getRandomMusic(uint32_t zoneId) const;
private:
// tile key = tileX * 100 + tileY
std::unordered_map<int, uint32_t> tileToZone;
std::unordered_map<uint32_t, ZoneInfo> zones;
};
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,43 @@
#pragma once
#include <vector>
#include <cstdint>
#include <string>
namespace wowee {
namespace network {
class Packet {
public:
Packet() = default;
explicit Packet(uint16_t opcode);
Packet(uint16_t opcode, const std::vector<uint8_t>& data);
void writeUInt8(uint8_t value);
void writeUInt16(uint16_t value);
void writeUInt32(uint32_t value);
void writeUInt64(uint64_t value);
void writeString(const std::string& value);
void writeBytes(const uint8_t* data, size_t length);
uint8_t readUInt8();
uint16_t readUInt16();
uint32_t readUInt32();
uint64_t readUInt64();
float readFloat();
std::string readString();
uint16_t getOpcode() const { return opcode; }
const std::vector<uint8_t>& getData() const { return data; }
size_t getReadPos() const { return readPos; }
size_t getSize() const { return data.size(); }
void setReadPos(size_t pos) { readPos = pos; }
private:
uint16_t opcode = 0;
std::vector<uint8_t> data;
size_t readPos = 0;
};
} // namespace network
} // namespace wowee

View file

@ -0,0 +1,32 @@
#pragma once
#include <string>
#include <functional>
#include <vector>
#include <cstdint>
namespace wowee {
namespace network {
class Packet;
class Socket {
public:
virtual ~Socket() = default;
virtual bool connect(const std::string& host, uint16_t port) = 0;
virtual void disconnect() = 0;
virtual bool isConnected() const = 0;
virtual void send(const Packet& packet) = 0;
virtual void update() = 0;
using PacketCallback = std::function<void(const Packet&)>;
void setPacketCallback(PacketCallback callback) { packetCallback = callback; }
protected:
PacketCallback packetCallback;
};
} // namespace network
} // namespace wowee

View file

@ -0,0 +1,31 @@
#pragma once
#include "network/socket.hpp"
#include <sys/socket.h>
namespace wowee {
namespace network {
class TCPSocket : public Socket {
public:
TCPSocket();
~TCPSocket() override;
bool connect(const std::string& host, uint16_t port) override;
void disconnect() override;
bool isConnected() const override { return connected; }
void send(const Packet& packet) override;
void update() override;
private:
void tryParsePackets();
size_t getExpectedPacketSize(uint8_t opcode);
int sockfd = -1;
bool connected = false;
std::vector<uint8_t> receiveBuffer;
};
} // namespace network
} // namespace wowee

View file

@ -0,0 +1,92 @@
#pragma once
#include "network/socket.hpp"
#include "network/packet.hpp"
#include "auth/rc4.hpp"
#include <functional>
#include <vector>
#include <cstdint>
namespace wowee {
namespace network {
/**
* World Server Socket
*
* Handles WoW 3.3.5a world server protocol with RC4 header encryption.
*
* Key Differences from Auth Server:
* - Outgoing: 6-byte header (2 bytes size + 4 bytes opcode, big-endian)
* - Incoming: 4-byte header (2 bytes size + 2 bytes opcode, big-endian)
* - Headers are RC4-encrypted after CMSG_AUTH_SESSION
* - Packet bodies remain unencrypted
* - Size field is payload size only (does NOT include header)
*/
class WorldSocket : public Socket {
public:
WorldSocket();
~WorldSocket() override;
bool connect(const std::string& host, uint16_t port) override;
void disconnect() override;
bool isConnected() const override;
/**
* Send a world packet
* Automatically encrypts 6-byte header if encryption is enabled
*
* @param packet Packet to send
*/
void send(const Packet& packet) override;
/**
* Update socket - receive data and parse packets
* Should be called regularly (e.g., each frame)
*/
void update();
/**
* Set callback for complete packets
*
* @param callback Function to call when packet is received
*/
void setPacketCallback(std::function<void(const Packet&)> callback) {
packetCallback = callback;
}
/**
* Initialize RC4 encryption for packet headers
* Must be called after CMSG_AUTH_SESSION before further communication
*
* @param sessionKey 40-byte session key from auth server
*/
void initEncryption(const std::vector<uint8_t>& sessionKey);
/**
* Check if header encryption is enabled
*/
bool isEncryptionEnabled() const { return encryptionEnabled; }
private:
/**
* Try to parse complete packets from receive buffer
*/
void tryParsePackets();
int sockfd = -1;
bool connected = false;
bool encryptionEnabled = false;
// RC4 ciphers for header encryption/decryption
auth::RC4 encryptCipher; // For outgoing headers
auth::RC4 decryptCipher; // For incoming headers
// Receive buffer
std::vector<uint8_t> receiveBuffer;
// Packet callback
std::function<void(const Packet&)> packetCallback;
};
} // namespace network
} // namespace wowee

View file

@ -0,0 +1,210 @@
#pragma once
#include <vector>
#include <string>
#include <cstdint>
#include <array>
namespace wowee {
namespace pipeline {
/**
* ADT chunk coordinates
*/
struct ADTCoord {
int32_t x;
int32_t y;
};
/**
* Heightmap for a map chunk (9x9 + 8x8 grid)
*/
struct HeightMap {
std::array<float, 145> heights; // 9x9 outer + 8x8 inner vertices
float getHeight(int x, int y) const;
bool isLoaded() const { return heights[0] != 0.0f || heights[1] != 0.0f; }
};
/**
* Texture layer for a map chunk
*/
struct TextureLayer {
uint32_t textureId; // Index into MTEX array
uint32_t flags; // Layer flags
uint32_t offsetMCAL; // Offset to alpha map in MCAL chunk
uint32_t effectId; // Effect ID (optional)
bool useAlpha() const { return (flags & 0x100) != 0; }
bool compressedAlpha() const { return (flags & 0x200) != 0; }
};
/**
* Map chunk (256x256 units, 1/16 of ADT)
*/
struct MapChunk {
uint32_t flags;
uint32_t indexX;
uint32_t indexY;
uint16_t holes; // 4x4 bitmask for terrain holes (cave entrances, etc.)
float position[3]; // World position (X, Y, Z)
HeightMap heightMap;
std::vector<TextureLayer> layers;
std::vector<uint8_t> alphaMap; // Alpha blend maps for layers
// Normals (compressed)
std::array<int8_t, 145 * 3> normals; // X, Y, Z per vertex
bool hasHeightMap() const { return heightMap.isLoaded(); }
bool hasLayers() const { return !layers.empty(); }
// Check if a quad has a hole (y and x are quad indices 0-7)
bool isHole(int y, int x) const {
int column = y / 2;
int row = x / 2;
int bit = 1 << (column * 4 + row);
return (bit & holes) != 0;
}
};
/**
* Complete ADT terrain tile (16x16 map chunks)
*/
struct ADTTerrain {
bool loaded = false;
uint32_t version = 0;
ADTCoord coord; // ADT coordinates (e.g., 32, 49 for Azeroth)
// 16x16 map chunks (256 total)
std::array<MapChunk, 256> chunks;
// Texture filenames
std::vector<std::string> textures;
// Doodad definitions (M2 models)
std::vector<std::string> doodadNames;
std::vector<uint32_t> doodadIds;
// WMO definitions (buildings)
std::vector<std::string> wmoNames;
std::vector<uint32_t> wmoIds;
// Doodad placement data (from MDDF chunk)
struct DoodadPlacement {
uint32_t nameId; // Index into doodadNames
uint32_t uniqueId;
float position[3]; // X, Y, Z
float rotation[3]; // Rotation in degrees
uint16_t scale; // 1024 = 1.0
uint16_t flags;
};
std::vector<DoodadPlacement> doodadPlacements;
// WMO placement data (from MODF chunk)
struct WMOPlacement {
uint32_t nameId; // Index into wmoNames
uint32_t uniqueId;
float position[3]; // X, Y, Z
float rotation[3]; // Rotation in degrees
float extentLower[3]; // Bounding box
float extentUpper[3];
uint16_t flags;
uint16_t doodadSet;
};
std::vector<WMOPlacement> wmoPlacements;
// Water/liquid data (from MH2O chunk)
struct WaterLayer {
uint16_t liquidType; // Type of liquid (0=water, 1=ocean, 2=magma, 3=slime)
uint16_t flags;
float minHeight;
float maxHeight;
uint8_t x; // X offset within chunk (0-7)
uint8_t y; // Y offset within chunk (0-7)
uint8_t width; // Width in vertices (1-9)
uint8_t height; // Height in vertices (1-9)
std::vector<float> heights; // Height values (width * height)
std::vector<uint8_t> mask; // Render mask (which tiles to render)
};
struct ChunkWater {
std::vector<WaterLayer> layers;
bool hasWater() const { return !layers.empty(); }
};
std::array<ChunkWater, 256> waterData; // Water for each chunk
MapChunk& getChunk(int x, int y) { return chunks[y * 16 + x]; }
const MapChunk& getChunk(int x, int y) const { return chunks[y * 16 + x]; }
bool isLoaded() const { return loaded; }
size_t getTextureCount() const { return textures.size(); }
};
/**
* ADT terrain loader
*
* Loads WoW 3.3.5a ADT (Azeroth Data Tile) terrain files
*/
class ADTLoader {
public:
/**
* Load ADT terrain from byte data
* @param adtData Raw ADT file data
* @return Loaded terrain (check isLoaded())
*/
static ADTTerrain load(const std::vector<uint8_t>& adtData);
private:
// Chunk identifiers (as they appear in file when read as little-endian uint32)
static constexpr uint32_t MVER = 0x4D564552; // Version (ASCII "MVER")
static constexpr uint32_t MHDR = 0x4D484452; // Header (ASCII "MHDR")
static constexpr uint32_t MCIN = 0x4D43494E; // Chunk info (ASCII "MCIN")
static constexpr uint32_t MTEX = 0x4D544558; // Textures (ASCII "MTEX")
static constexpr uint32_t MMDX = 0x4D4D4458; // Doodad names (ASCII "MMDX")
static constexpr uint32_t MMID = 0x4D4D4944; // Doodad IDs (ASCII "MMID")
static constexpr uint32_t MWMO = 0x4D574D4F; // WMO names (ASCII "MWMO")
static constexpr uint32_t MWID = 0x4D574944; // WMO IDs (ASCII "MWID")
static constexpr uint32_t MDDF = 0x4D444446; // Doodad placement (ASCII "MDDF")
static constexpr uint32_t MODF = 0x4D4F4446; // WMO placement (ASCII "MODF")
static constexpr uint32_t MH2O = 0x4D48324F; // Water/liquid (ASCII "MH2O")
static constexpr uint32_t MCNK = 0x4D434E4B; // Map chunk (ASCII "MCNK")
// Sub-chunks within MCNK
static constexpr uint32_t MCVT = 0x4D435654; // Height values (ASCII "MCVT")
static constexpr uint32_t MCNR = 0x4D434E52; // Normals (ASCII "MCNR")
static constexpr uint32_t MCLY = 0x4D434C59; // Layers (ASCII "MCLY")
static constexpr uint32_t MCRF = 0x4D435246; // References (ASCII "MCRF")
static constexpr uint32_t MCSH = 0x4D435348; // Shadow map (ASCII "MCSH")
static constexpr uint32_t MCAL = 0x4D43414C; // Alpha maps (ASCII "MCAL")
static constexpr uint32_t MCLQ = 0x4D434C51; // Liquid (deprecated) (ASCII "MCLQ")
struct ChunkHeader {
uint32_t magic;
uint32_t size;
};
static bool readChunkHeader(const uint8_t* data, size_t offset, size_t dataSize, ChunkHeader& header);
static uint32_t readUInt32(const uint8_t* data, size_t offset);
static uint16_t readUInt16(const uint8_t* data, size_t offset);
static float readFloat(const uint8_t* data, size_t offset);
static void parseMVER(const uint8_t* data, size_t size, ADTTerrain& terrain);
static void parseMTEX(const uint8_t* data, size_t size, ADTTerrain& terrain);
static void parseMMDX(const uint8_t* data, size_t size, ADTTerrain& terrain);
static void parseMWMO(const uint8_t* data, size_t size, ADTTerrain& terrain);
static void parseMDDF(const uint8_t* data, size_t size, ADTTerrain& terrain);
static void parseMODF(const uint8_t* data, size_t size, ADTTerrain& terrain);
static void parseMCNK(const uint8_t* data, size_t size, int chunkIndex, ADTTerrain& terrain);
static void parseMCVT(const uint8_t* data, size_t size, MapChunk& chunk);
static void parseMCNR(const uint8_t* data, size_t size, MapChunk& chunk);
static void parseMCLY(const uint8_t* data, size_t size, MapChunk& chunk);
static void parseMCAL(const uint8_t* data, size_t size, MapChunk& chunk);
static void parseMH2O(const uint8_t* data, size_t size, ADTTerrain& terrain);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,107 @@
#pragma once
#include "pipeline/mpq_manager.hpp"
#include "pipeline/blp_loader.hpp"
#include "pipeline/dbc_loader.hpp"
#include <memory>
#include <string>
#include <map>
#include <mutex>
namespace wowee {
namespace pipeline {
/**
* AssetManager - Unified interface for loading WoW assets
*
* Coordinates MPQ archives, texture loading, and database files
*/
class AssetManager {
public:
AssetManager();
~AssetManager();
/**
* Initialize asset manager
* @param dataPath Path to WoW Data directory
* @return true if initialization succeeded
*/
bool initialize(const std::string& dataPath);
/**
* Shutdown and cleanup
*/
void shutdown();
/**
* Check if asset manager is initialized
*/
bool isInitialized() const { return initialized; }
/**
* Load a BLP texture
* @param path Virtual path to BLP file (e.g., "Textures\\Minimap\\Background.blp")
* @return BLP image (check isValid())
*/
BLPImage loadTexture(const std::string& path);
/**
* Load a DBC file
* @param name DBC file name (e.g., "Map.dbc")
* @return Loaded DBC file (check isLoaded())
*/
std::shared_ptr<DBCFile> loadDBC(const std::string& name);
/**
* Get a cached DBC file
* @param name DBC file name
* @return Cached DBC or nullptr if not loaded
*/
std::shared_ptr<DBCFile> getDBC(const std::string& name) const;
/**
* Check if a file exists in MPQ archives
* @param path Virtual file path
* @return true if file exists
*/
bool fileExists(const std::string& path) const;
/**
* Read raw file data from MPQ archives
* @param path Virtual file path
* @return File contents (empty if not found)
*/
std::vector<uint8_t> readFile(const std::string& path) const;
/**
* Get MPQ manager for direct access
*/
MPQManager& getMPQManager() { return mpqManager; }
const MPQManager& getMPQManager() const { return mpqManager; }
/**
* Get loaded DBC count
*/
size_t getLoadedDBCCount() const { return dbcCache.size(); }
/**
* Clear all cached resources
*/
void clearCache();
private:
bool initialized = false;
std::string dataPath;
MPQManager mpqManager;
mutable std::mutex readMutex;
std::map<std::string, std::shared_ptr<DBCFile>> dbcCache;
/**
* Normalize path for case-insensitive lookup
*/
std::string normalizePath(const std::string& path) const;
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,110 @@
#pragma once
#include <vector>
#include <cstdint>
#include <string>
namespace wowee {
namespace pipeline {
/**
* BLP image format (Blizzard Picture)
*/
enum class BLPFormat {
UNKNOWN = 0,
BLP0 = 1, // Alpha channel only
BLP1 = 2, // DXT compression or uncompressed
BLP2 = 3 // DXT compression with mipmaps
};
/**
* BLP compression type
*/
enum class BLPCompression {
NONE = 0,
PALETTE = 1, // 256-color palette
DXT1 = 2, // DXT1 compression (no alpha or 1-bit alpha)
DXT3 = 3, // DXT3 compression (4-bit alpha)
DXT5 = 4, // DXT5 compression (interpolated alpha)
ARGB8888 = 5 // Uncompressed 32-bit ARGB
};
/**
* Loaded BLP image data
*/
struct BLPImage {
int width = 0;
int height = 0;
int channels = 4;
int mipLevels = 1;
BLPFormat format = BLPFormat::UNKNOWN;
BLPCompression compression = BLPCompression::NONE;
std::vector<uint8_t> data; // RGBA8 pixel data (decompressed)
std::vector<std::vector<uint8_t>> mipmaps; // Mipmap levels
bool isValid() const { return width > 0 && height > 0 && !data.empty(); }
};
/**
* BLP texture loader
*
* Supports BLP0, BLP1, BLP2 formats
* Handles DXT1/3/5 compression and palette formats
*/
class BLPLoader {
public:
/**
* Load BLP image from byte data
* @param blpData Raw BLP file data
* @return Loaded image (check isValid())
*/
static BLPImage load(const std::vector<uint8_t>& blpData);
/**
* Get format name for debugging
*/
static const char* getFormatName(BLPFormat format);
static const char* getCompressionName(BLPCompression compression);
private:
// BLP1 file header — all fields after magic are uint32
// Used by classic WoW through WotLK for many textures
struct BLP1Header {
char magic[4]; // 'BLP1'
uint32_t compression; // 0=JPEG, 1=palette (uncompressed/indexed)
uint32_t alphaBits; // 0, 1, 4, or 8
uint32_t width;
uint32_t height;
uint32_t extra; // Flags/unknown (often 4 or 5)
uint32_t hasMips; // 0 or 1
uint32_t mipOffsets[16];
uint32_t mipSizes[16];
uint32_t palette[256]; // 256-color BGRA palette (for compression=1)
};
// BLP2 file header — compression fields are uint8
// Used by WoW from TBC onwards (coexists with BLP1 in WotLK)
struct BLP2Header {
char magic[4]; // 'BLP2'
uint32_t version; // Always 1
uint8_t compression; // 1=uncompressed/palette, 2=DXTC, 3=A8R8G8B8
uint8_t alphaDepth; // 0, 1, 4, or 8
uint8_t alphaEncoding; // 0=DXT1, 1=DXT3, 7=DXT5
uint8_t hasMips; // Has mipmaps
uint32_t width;
uint32_t height;
uint32_t mipOffsets[16];
uint32_t mipSizes[16];
uint32_t palette[256]; // 256-color BGRA palette (for compression=1)
};
static BLPImage loadBLP1(const uint8_t* data, size_t size);
static BLPImage loadBLP2(const uint8_t* data, size_t size);
static void decompressDXT1(const uint8_t* src, uint8_t* dst, int width, int height);
static void decompressDXT3(const uint8_t* src, uint8_t* dst, int width, int height);
static void decompressDXT5(const uint8_t* src, uint8_t* dst, int width, int height);
static void decompressPalette(const uint8_t* src, uint8_t* dst, const uint32_t* palette, int width, int height, uint8_t alphaDepth = 8);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,135 @@
#pragma once
#include <vector>
#include <map>
#include <string>
#include <cstdint>
#include <memory>
namespace wowee {
namespace pipeline {
/**
* DBC File - WoW Database Client file
*
* DBC files store game database tables (spells, items, maps, creatures, etc.)
* Format: Fixed header + fixed-size records + string block
*/
class DBCFile {
public:
DBCFile();
~DBCFile();
/**
* Load DBC file from byte data
* @param dbcData Raw DBC file data
* @return true if loaded successfully
*/
bool load(const std::vector<uint8_t>& dbcData);
/**
* Check if DBC is loaded
*/
bool isLoaded() const { return loaded; }
/**
* Get record count
*/
uint32_t getRecordCount() const { return recordCount; }
/**
* Get field count (number of 32-bit fields per record)
*/
uint32_t getFieldCount() const { return fieldCount; }
/**
* Get record size in bytes
*/
uint32_t getRecordSize() const { return recordSize; }
/**
* Get string block size
*/
uint32_t getStringBlockSize() const { return stringBlockSize; }
/**
* Get a record by index
* @param index Record index (0 to recordCount-1)
* @return Pointer to record data (recordSize bytes) or nullptr
*/
const uint8_t* getRecord(uint32_t index) const;
/**
* Get a 32-bit integer field from a record
* @param recordIndex Record index
* @param fieldIndex Field index (0 to fieldCount-1)
* @return Field value
*/
uint32_t getUInt32(uint32_t recordIndex, uint32_t fieldIndex) const;
/**
* Get a 32-bit signed integer field from a record
* @param recordIndex Record index
* @param fieldIndex Field index
* @return Field value
*/
int32_t getInt32(uint32_t recordIndex, uint32_t fieldIndex) const;
/**
* Get a float field from a record
* @param recordIndex Record index
* @param fieldIndex Field index
* @return Field value
*/
float getFloat(uint32_t recordIndex, uint32_t fieldIndex) const;
/**
* Get a string field from a record
* @param recordIndex Record index
* @param fieldIndex Field index (contains string offset)
* @return String value
*/
std::string getString(uint32_t recordIndex, uint32_t fieldIndex) const;
/**
* Get string by offset in string block
* @param offset Offset into string block
* @return String value
*/
std::string getStringByOffset(uint32_t offset) const;
/**
* Find a record by ID (assumes first field is ID)
* @param id Record ID to find
* @return Record index or -1 if not found
*/
int32_t findRecordById(uint32_t id) const;
private:
// DBC file header (20 bytes)
struct DBCHeader {
char magic[4]; // 'WDBC'
uint32_t recordCount; // Number of records
uint32_t fieldCount; // Number of fields per record
uint32_t recordSize; // Size of each record in bytes
uint32_t stringBlockSize; // Size of string block
};
bool loaded = false;
uint32_t recordCount = 0;
uint32_t fieldCount = 0;
uint32_t recordSize = 0;
uint32_t stringBlockSize = 0;
std::vector<uint8_t> recordData; // All record data
std::vector<uint8_t> stringBlock; // String block
// Cache for record ID -> index lookup
mutable std::map<uint32_t, uint32_t> idToIndexCache;
mutable bool idCacheBuilt = false;
void buildIdCache() const;
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,187 @@
#pragma once
#include <vector>
#include <string>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
namespace wowee {
namespace pipeline {
/**
* M2 Model Format (WoW Character/Creature Models)
*
* M2 files contain:
* - Skeletal animated meshes
* - Multiple texture units and materials
* - Animation sequences
* - Bone hierarchy
* - Particle emitters, ribbon emitters, etc.
*
* Reference: https://wowdev.wiki/M2
*/
// Animation sequence data
struct M2Sequence {
uint32_t id; // Animation ID
uint32_t variationIndex; // Sub-animation index
uint32_t duration; // Length in milliseconds
float movingSpeed; // Speed during animation
uint32_t flags; // Animation flags
int16_t frequency; // Probability weight
uint32_t replayMin; // Minimum replay delay
uint32_t replayMax; // Maximum replay delay
uint32_t blendTime; // Blend time in ms
glm::vec3 boundMin; // Bounding box
glm::vec3 boundMax;
float boundRadius; // Bounding sphere radius
int16_t nextAnimation; // Next animation in chain
uint16_t aliasNext; // Alias for next animation
};
// Animation track with per-sequence keyframe data
struct M2AnimationTrack {
uint16_t interpolationType = 0; // 0=none, 1=linear, 2=hermite, 3=bezier
int16_t globalSequence = -1; // -1 if not a global sequence
struct SequenceKeys {
std::vector<uint32_t> timestamps; // Milliseconds
std::vector<glm::vec3> vec3Values; // For translation/scale tracks
std::vector<glm::quat> quatValues; // For rotation tracks
};
std::vector<SequenceKeys> sequences; // One per animation sequence
bool hasData() const { return !sequences.empty(); }
};
// Bone data for skeletal animation
struct M2Bone {
int32_t keyBoneId; // Bone ID (-1 = not key bone)
uint32_t flags; // Bone flags
int16_t parentBone; // Parent bone index (-1 = root)
uint16_t submeshId; // Submesh ID
glm::vec3 pivot; // Pivot point
M2AnimationTrack translation; // Position keyframes per sequence
M2AnimationTrack rotation; // Rotation keyframes per sequence
M2AnimationTrack scale; // Scale keyframes per sequence
};
// Vertex with skinning data
struct M2Vertex {
glm::vec3 position;
uint8_t boneWeights[4]; // Bone weights (0-255)
uint8_t boneIndices[4]; // Bone indices
glm::vec3 normal;
glm::vec2 texCoords[2]; // Two UV sets
};
// Texture unit
struct M2Texture {
uint32_t type; // Texture type
uint32_t flags; // Texture flags
std::string filename; // Texture filename (from FileData or embedded)
};
// Render batch (submesh)
struct M2Batch {
uint8_t flags;
int8_t priorityPlane;
uint16_t shader; // Shader ID
uint16_t skinSectionIndex; // Submesh index
uint16_t colorIndex; // Color animation index
uint16_t materialIndex; // Material index
uint16_t materialLayer; // Material layer
uint16_t textureCount; // Number of textures
uint16_t textureIndex; // First texture lookup index
uint16_t textureUnit; // Texture unit
uint16_t transparencyIndex; // Transparency animation index
uint16_t textureAnimIndex; // Texture animation index
// Render data
uint32_t indexStart; // First index
uint32_t indexCount; // Number of indices
uint32_t vertexStart; // First vertex
uint32_t vertexCount; // Number of vertices
// Geoset info (from submesh)
uint16_t submeshId = 0; // Submesh/geoset ID (determines body part group)
uint16_t submeshLevel = 0; // Submesh level (0=base, 1+=LOD/alternate mesh)
};
// Attachment point (bone-anchored position for weapons, effects, etc.)
struct M2Attachment {
uint32_t id; // 0=Head, 1=RightHand, 2=LeftHand, etc.
uint16_t bone; // Bone index
glm::vec3 position; // Offset from bone pivot
};
// Complete M2 model structure
struct M2Model {
// Model metadata
std::string name;
uint32_t version;
glm::vec3 boundMin; // Model bounding box
glm::vec3 boundMax;
float boundRadius; // Bounding sphere
// Geometry data
std::vector<M2Vertex> vertices;
std::vector<uint16_t> indices;
// Skeletal animation
std::vector<M2Bone> bones;
std::vector<M2Sequence> sequences;
// Rendering
std::vector<M2Batch> batches;
std::vector<M2Texture> textures;
std::vector<uint16_t> textureLookup; // Batch texture index lookup
// Attachment points (for weapon/effect anchoring)
std::vector<M2Attachment> attachments;
std::vector<uint16_t> attachmentLookup; // attachment ID → index
// Flags
uint32_t globalFlags;
bool isValid() const {
return !vertices.empty() && !indices.empty();
}
};
class M2Loader {
public:
/**
* Load M2 model from raw file data
*
* @param m2Data Raw M2 file bytes
* @return Parsed M2 model
*/
static M2Model load(const std::vector<uint8_t>& m2Data);
/**
* Load M2 skin file (contains submesh/batch data)
*
* @param skinData Raw M2 skin file bytes
* @param model Model to populate with skin data
* @return True if successful
*/
static bool loadSkin(const std::vector<uint8_t>& skinData, M2Model& model);
/**
* Load external .anim file data into model bone tracks
*
* @param m2Data Original M2 file bytes (contains track headers)
* @param animData Raw .anim file bytes
* @param sequenceIndex Which sequence index this .anim file provides data for
* @param model Model to patch with animation data
*/
static void loadAnimFile(const std::vector<uint8_t>& m2Data,
const std::vector<uint8_t>& animData,
uint32_t sequenceIndex,
M2Model& model);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,109 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <memory>
#include <map>
// Forward declare StormLib handle
typedef void* HANDLE;
namespace wowee {
namespace pipeline {
/**
* MPQManager - Manages MPQ archive loading and file reading
*
* WoW 3.3.5a stores all game assets in MPQ archives.
* This manager loads multiple archives and provides unified file access.
*/
class MPQManager {
public:
MPQManager();
~MPQManager();
/**
* Initialize the MPQ system
* @param dataPath Path to WoW Data directory
* @return true if initialization succeeded
*/
bool initialize(const std::string& dataPath);
/**
* Shutdown and close all archives
*/
void shutdown();
/**
* Load a single MPQ archive
* @param path Full path to MPQ file
* @param priority Priority for file resolution (higher = checked first)
* @return true if archive loaded successfully
*/
bool loadArchive(const std::string& path, int priority = 0);
/**
* Check if a file exists in any loaded archive
* @param filename Virtual file path (e.g., "World\\Maps\\Azeroth\\Azeroth.wdt")
* @return true if file exists
*/
bool fileExists(const std::string& filename) const;
/**
* Read a file from MPQ archives
* @param filename Virtual file path
* @return File contents as byte vector (empty if not found)
*/
std::vector<uint8_t> readFile(const std::string& filename) const;
/**
* Get file size without reading it
* @param filename Virtual file path
* @return File size in bytes (0 if not found)
*/
uint32_t getFileSize(const std::string& filename) const;
/**
* Check if MPQ system is initialized
*/
bool isInitialized() const { return initialized; }
/**
* Get list of loaded archives
*/
const std::vector<std::string>& getLoadedArchives() const { return archiveNames; }
private:
struct ArchiveEntry {
HANDLE handle;
std::string path;
int priority;
};
bool initialized = false;
std::string dataPath;
std::vector<ArchiveEntry> archives;
std::vector<std::string> archiveNames;
/**
* Find archive containing a file
* @param filename File to search for
* @return Archive handle or nullptr if not found
*/
HANDLE findFileArchive(const std::string& filename) const;
/**
* Load patch archives (e.g., patch.MPQ, patch-2.MPQ, etc.)
*/
bool loadPatchArchives();
/**
* Load locale-specific archives
* @param locale Locale string (e.g., "enUS")
*/
bool loadLocaleArchives(const std::string& locale);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,136 @@
#pragma once
#include "pipeline/adt_loader.hpp"
#include <vector>
#include <cstdint>
namespace wowee {
namespace pipeline {
/**
* Vertex format for terrain rendering
*/
struct TerrainVertex {
float position[3]; // X, Y, Z
float normal[3]; // Normal vector
float texCoord[2]; // Base texture coordinates
float layerUV[2]; // Layer texture coordinates
uint8_t chunkIndex; // Which chunk this vertex belongs to
TerrainVertex() : chunkIndex(0) {
position[0] = position[1] = position[2] = 0.0f;
normal[0] = normal[1] = normal[2] = 0.0f;
texCoord[0] = texCoord[1] = 0.0f;
layerUV[0] = layerUV[1] = 0.0f;
}
};
/**
* Triangle index (3 vertices)
*/
using TerrainIndex = uint32_t;
/**
* Renderable terrain mesh for a single map chunk
*/
struct ChunkMesh {
std::vector<TerrainVertex> vertices;
std::vector<TerrainIndex> indices;
// Chunk position in world space
float worldX;
float worldY;
float worldZ;
// Chunk grid coordinates
int chunkX;
int chunkY;
// Texture layer info
struct LayerInfo {
uint32_t textureId;
uint32_t flags;
std::vector<uint8_t> alphaData; // 64x64 alpha map
};
std::vector<LayerInfo> layers;
bool isValid() const { return !vertices.empty() && !indices.empty(); }
size_t getVertexCount() const { return vertices.size(); }
size_t getTriangleCount() const { return indices.size() / 3; }
};
/**
* Complete terrain tile mesh (16x16 chunks)
*/
struct TerrainMesh {
std::array<ChunkMesh, 256> chunks; // 16x16 grid
std::vector<std::string> textures; // Texture filenames
int validChunkCount = 0;
const ChunkMesh& getChunk(int x, int y) const { return chunks[y * 16 + x]; }
ChunkMesh& getChunk(int x, int y) { return chunks[y * 16 + x]; }
};
/**
* Terrain mesh generator
*
* Converts ADT heightmap data into renderable triangle meshes
*/
class TerrainMeshGenerator {
public:
/**
* Generate terrain mesh from ADT data
* @param terrain Loaded ADT terrain data
* @return Generated mesh (check validChunkCount)
*/
static TerrainMesh generate(const ADTTerrain& terrain);
private:
/**
* Generate mesh for a single map chunk
*/
static ChunkMesh generateChunkMesh(const MapChunk& chunk, int chunkX, int chunkY, int tileX, int tileY);
/**
* Generate vertices from heightmap
* WoW heightmap layout: 9x9 outer + 8x8 inner vertices (145 total)
*/
static std::vector<TerrainVertex> generateVertices(const MapChunk& chunk, int chunkX, int chunkY, int tileX, int tileY);
/**
* Generate triangle indices
* Creates triangles that connect the heightmap vertices
* Skips quads that are marked as holes in the chunk
*/
static std::vector<TerrainIndex> generateIndices(const MapChunk& chunk);
/**
* Calculate texture coordinates for vertex
*/
static void calculateTexCoords(TerrainVertex& vertex, int x, int y);
/**
* Convert WoW's compressed normals to float
*/
static void decompressNormal(const int8_t* compressedNormal, float* normal);
/**
* Get height at grid position from WoW's 9x9+8x8 layout
*/
static float getHeightAt(const HeightMap& heightMap, int x, int y);
/**
* Convert grid coordinates to vertex index
*/
static int getVertexIndex(int x, int y);
// Terrain constants
// WoW terrain: 64x64 tiles, each tile = 533.33 yards, each chunk = 33.33 yards
static constexpr float TILE_SIZE = 533.33333f; // One ADT tile = 533.33 yards
static constexpr float CHUNK_SIZE = TILE_SIZE / 16.0f; // One chunk = 33.33 yards (16 chunks per tile)
static constexpr float GRID_STEP = CHUNK_SIZE / 8.0f; // 8 quads per chunk = 4.17 yards per vertex
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,222 @@
#pragma once
#include <vector>
#include <string>
#include <unordered_map>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
namespace wowee {
namespace pipeline {
/**
* WMO (World Model Object) Format
*
* WMO files contain buildings, dungeons, and large structures.
* Structure:
* - Root WMO file: Contains groups, materials, doodad sets
* - Group WMO files: Individual rooms/sections (_XXX.wmo)
*
* Reference: https://wowdev.wiki/WMO
*/
// WMO Material
struct WMOMaterial {
uint32_t flags;
uint32_t shader;
uint32_t blendMode;
uint32_t texture1; // Diffuse texture index
uint32_t color1;
uint32_t texture2; // Environment/detail texture
uint32_t color2;
uint32_t texture3;
uint32_t color3;
float runtime[4]; // Runtime data
};
// WMO Group Info
struct WMOGroupInfo {
uint32_t flags;
glm::vec3 boundingBoxMin;
glm::vec3 boundingBoxMax;
int32_t nameOffset; // Group name in MOGN chunk
};
// WMO Light
struct WMOLight {
uint32_t type; // 0=omni, 1=spot, 2=directional, 3=ambient
uint8_t useAttenuation;
uint8_t pad[3];
glm::vec4 color;
glm::vec3 position;
float intensity;
float attenuationStart;
float attenuationEnd;
float unknown[4];
};
// WMO Doodad Set (collection of M2 models placed in WMO)
struct WMODoodadSet {
char name[20];
uint32_t startIndex; // First doodad in MODD
uint32_t count; // Number of doodads
uint32_t padding;
};
// WMO Doodad Instance
struct WMODoodad {
uint32_t nameIndex; // Index into MODN (doodad names)
glm::vec3 position;
glm::quat rotation; // Quaternion rotation
float scale;
glm::vec4 color; // BGRA color
};
// WMO Fog
struct WMOFog {
uint32_t flags;
glm::vec3 position;
float smallRadius;
float largeRadius;
float endDist;
float startFactor;
glm::vec4 color1; // End fog color
float endDist2;
float startFactor2;
glm::vec4 color2; // Start fog color (blend with color1)
};
// WMO Portal
struct WMOPortal {
uint16_t startVertex;
uint16_t vertexCount;
uint16_t planeIndex;
uint16_t padding;
};
// WMO Portal Plane
struct WMOPortalPlane {
glm::vec3 normal;
float distance;
};
// WMO Group Vertex
struct WMOVertex {
glm::vec3 position;
glm::vec3 normal;
glm::vec2 texCoord;
glm::vec4 color; // Vertex color
};
// WMO Batch (render batch)
struct WMOBatch {
uint32_t startIndex; // First index (this is uint32 in file format)
uint16_t indexCount; // Number of indices
uint16_t startVertex;
uint16_t lastVertex;
uint8_t flags;
uint8_t materialId;
};
// WMO Group (individual room/section)
struct WMOGroup {
uint32_t flags;
glm::vec3 boundingBoxMin;
glm::vec3 boundingBoxMax;
uint16_t portalStart;
uint16_t portalCount;
uint16_t batchCountA;
uint16_t batchCountB;
uint32_t fogIndices[4]; // Fog references
uint32_t liquidType;
uint32_t groupId;
// Geometry
std::vector<WMOVertex> vertices;
std::vector<uint16_t> indices;
std::vector<WMOBatch> batches;
// Portals
std::vector<WMOPortal> portals;
std::vector<glm::vec3> portalVertices;
// BSP tree (for collision - optional)
std::vector<uint8_t> bspNodes;
std::string name;
std::string description;
};
// Complete WMO Model
struct WMOModel {
// Root WMO data
uint32_t version;
uint32_t nGroups;
uint32_t nPortals;
uint32_t nLights;
uint32_t nDoodadNames;
uint32_t nDoodadDefs;
uint32_t nDoodadSets;
glm::vec3 boundingBoxMin;
glm::vec3 boundingBoxMax;
// Materials and textures
std::vector<WMOMaterial> materials;
std::vector<std::string> textures;
std::unordered_map<uint32_t, uint32_t> textureOffsetToIndex; // MOTX offset -> texture array index
// Groups (rooms/sections)
std::vector<WMOGroupInfo> groupInfo;
std::vector<WMOGroup> groups;
// Portals (visibility culling)
std::vector<WMOPortal> portals;
std::vector<WMOPortalPlane> portalPlanes;
std::vector<glm::vec3> portalVertices;
// Lights
std::vector<WMOLight> lights;
// Doodads (M2 models placed in WMO)
// Keyed by byte offset into MODN chunk (nameIndex in MODD references these offsets)
std::unordered_map<uint32_t, std::string> doodadNames;
std::vector<WMODoodad> doodads;
std::vector<WMODoodadSet> doodadSets;
// Fog
std::vector<WMOFog> fogs;
// Group names
std::vector<std::string> groupNames;
bool isValid() const {
return nGroups > 0 && !groups.empty();
}
};
class WMOLoader {
public:
/**
* Load root WMO file
*
* @param wmoData Raw WMO file bytes
* @return Parsed WMO model (without group geometry)
*/
static WMOModel load(const std::vector<uint8_t>& wmoData);
/**
* Load WMO group file
*
* @param groupData Raw WMO group file bytes
* @param model Model to populate with group data
* @param groupIndex Group index to load
* @return True if successful
*/
static bool loadGroup(const std::vector<uint8_t>& groupData,
WMOModel& model,
uint32_t groupIndex);
};
} // namespace pipeline
} // namespace wowee

View file

@ -0,0 +1,52 @@
#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
namespace wowee {
namespace rendering {
struct Ray {
glm::vec3 origin;
glm::vec3 direction;
};
class Camera {
public:
Camera();
void setPosition(const glm::vec3& pos) { position = pos; updateViewMatrix(); }
void setRotation(float yaw, float pitch) { this->yaw = yaw; this->pitch = pitch; updateViewMatrix(); }
void setAspectRatio(float aspect) { aspectRatio = aspect; updateProjectionMatrix(); }
void setFov(float fov) { this->fov = fov; updateProjectionMatrix(); }
const glm::vec3& getPosition() const { return position; }
const glm::mat4& getViewMatrix() const { return viewMatrix; }
const glm::mat4& getProjectionMatrix() const { return projectionMatrix; }
glm::mat4 getViewProjectionMatrix() const { return projectionMatrix * viewMatrix; }
float getAspectRatio() const { return aspectRatio; }
glm::vec3 getForward() const;
glm::vec3 getRight() const;
glm::vec3 getUp() const;
Ray screenToWorldRay(float screenX, float screenY, float screenW, float screenH) const;
private:
void updateViewMatrix();
void updateProjectionMatrix();
glm::vec3 position = glm::vec3(0.0f);
float yaw = 0.0f;
float pitch = 0.0f;
float fov = 45.0f;
float aspectRatio = 16.0f / 9.0f;
float nearPlane = 0.1f;
float farPlane = 200000.0f; // Large draw distance for terrain visibility
glm::mat4 viewMatrix = glm::mat4(1.0f);
glm::mat4 projectionMatrix = glm::mat4(1.0f);
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,130 @@
#pragma once
#include "rendering/camera.hpp"
#include "core/input.hpp"
#include <SDL2/SDL.h>
#include <functional>
namespace wowee {
namespace rendering {
class TerrainManager;
class WMORenderer;
class WaterRenderer;
class CameraController {
public:
CameraController(Camera* camera);
void update(float deltaTime);
void processMouseMotion(const SDL_MouseMotionEvent& event);
void processMouseButton(const SDL_MouseButtonEvent& event);
void setMovementSpeed(float speed) { movementSpeed = speed; }
void setMouseSensitivity(float sensitivity) { mouseSensitivity = sensitivity; }
void setEnabled(bool enabled) { this->enabled = enabled; }
void setTerrainManager(TerrainManager* tm) { terrainManager = tm; }
void setWMORenderer(WMORenderer* wmo) { wmoRenderer = wmo; }
void setWaterRenderer(WaterRenderer* wr) { waterRenderer = wr; }
void processMouseWheel(float delta);
void setFollowTarget(glm::vec3* target);
void reset();
float getMovementSpeed() const { return movementSpeed; }
bool isMoving() const;
float getYaw() const { return yaw; }
bool isThirdPerson() const { return thirdPerson; }
bool isGrounded() const { return grounded; }
bool isJumping() const { return !grounded && verticalVelocity > 0.0f; }
bool isFalling() const { return !grounded && verticalVelocity <= 0.0f; }
bool isSprinting() const;
bool isRightMouseHeld() const { return rightMouseDown; }
bool isSitting() const { return sitting; }
bool isSwimming() const { return swimming; }
const glm::vec3* getFollowTarget() const { return followTarget; }
// Movement callback for sending opcodes to server
using MovementCallback = std::function<void(uint32_t opcode)>;
void setMovementCallback(MovementCallback cb) { movementCallback = std::move(cb); }
void setUseWoWSpeed(bool use) { useWoWSpeed = use; }
private:
Camera* camera;
TerrainManager* terrainManager = nullptr;
WMORenderer* wmoRenderer = nullptr;
WaterRenderer* waterRenderer = nullptr;
// Stored rotation (avoids lossy forward-vector round-trip)
float yaw = 180.0f;
float pitch = -30.0f;
// Movement settings
float movementSpeed = 50.0f;
float sprintMultiplier = 3.0f;
float slowMultiplier = 0.3f;
// Mouse settings
float mouseSensitivity = 0.2f;
bool mouseButtonDown = false;
bool leftMouseDown = false;
bool rightMouseDown = false;
// Third-person orbit camera
bool thirdPerson = false;
float orbitDistance = 15.0f;
float minOrbitDistance = 3.0f;
float maxOrbitDistance = 50.0f;
float zoomSpeed = 2.0f;
glm::vec3* followTarget = nullptr;
// Gravity / grounding
float verticalVelocity = 0.0f;
bool grounded = false;
float eyeHeight = 5.0f;
float lastGroundZ = 0.0f; // Last known ground height (fallback when no terrain)
static constexpr float GRAVITY = -30.0f;
static constexpr float JUMP_VELOCITY = 15.0f;
// Swimming
bool swimming = false;
bool wasSwimming = false;
static constexpr float SWIM_SPEED_FACTOR = 0.67f;
static constexpr float SWIM_GRAVITY = -5.0f;
static constexpr float SWIM_BUOYANCY = 8.0f;
static constexpr float SWIM_SINK_SPEED = -3.0f;
static constexpr float WATER_SURFACE_OFFSET = 1.5f;
// State
bool enabled = true;
bool sitting = false;
bool xKeyWasDown = false;
// Movement state tracking (for sending opcodes on state change)
bool wasMovingForward = false;
bool wasMovingBackward = false;
bool wasStrafingLeft = false;
bool wasStrafingRight = false;
bool wasJumping = false;
bool wasFalling = false;
// Movement callback
MovementCallback movementCallback;
// WoW-correct speeds
bool useWoWSpeed = false;
static constexpr float WOW_RUN_SPEED = 7.0f;
static constexpr float WOW_WALK_SPEED = 2.5f;
static constexpr float WOW_BACK_SPEED = 4.5f;
static constexpr float WOW_GRAVITY = -19.29f;
static constexpr float WOW_JUMP_VELOCITY = 7.96f;
// Default spawn position (in front of Stormwind gate)
glm::vec3 defaultPosition = glm::vec3(-8900.0f, -170.0f, 150.0f);
float defaultYaw = 0.0f; // Look north toward Stormwind gate
float defaultPitch = -5.0f;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,101 @@
#pragma once
#include <memory>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
class Shader;
class Camera;
/**
* Celestial body renderer
*
* Renders sun and moon that move across the sky based on time of day.
* Sun rises at dawn, sets at dusk. Moon is visible at night.
*/
class Celestial {
public:
Celestial();
~Celestial();
bool initialize();
void shutdown();
/**
* Render celestial bodies (sun and moon)
* @param camera Camera for view matrix
* @param timeOfDay Time of day in hours (0-24)
*/
void render(const Camera& camera, float timeOfDay);
/**
* Enable/disable celestial rendering
*/
void setEnabled(bool enabled) { renderingEnabled = enabled; }
bool isEnabled() const { return renderingEnabled; }
/**
* Update celestial bodies (for moon phase cycling)
*/
void update(float deltaTime);
/**
* Set moon phase (0.0 = new moon, 0.25 = first quarter, 0.5 = full, 0.75 = last quarter, 1.0 = new)
*/
void setMoonPhase(float phase);
float getMoonPhase() const { return moonPhase; }
/**
* Enable/disable automatic moon phase cycling
*/
void setMoonPhaseCycling(bool enabled) { moonPhaseCycling = enabled; }
bool isMoonPhaseCycling() const { return moonPhaseCycling; }
/**
* Get sun position in world space
*/
glm::vec3 getSunPosition(float timeOfDay) const;
/**
* Get moon position in world space
*/
glm::vec3 getMoonPosition(float timeOfDay) const;
/**
* Get sun color (changes with time of day)
*/
glm::vec3 getSunColor(float timeOfDay) const;
/**
* Get sun intensity (0-1, fades at dawn/dusk)
*/
float getSunIntensity(float timeOfDay) const;
private:
void createCelestialQuad();
void destroyCelestialQuad();
void renderSun(const Camera& camera, float timeOfDay);
void renderMoon(const Camera& camera, float timeOfDay);
float calculateCelestialAngle(float timeOfDay, float riseTime, float setTime) const;
std::unique_ptr<Shader> celestialShader;
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
bool renderingEnabled = true;
// Moon phase system
float moonPhase = 0.5f; // 0.0-1.0 (0=new, 0.5=full)
bool moonPhaseCycling = true;
float moonPhaseTimer = 0.0f;
static constexpr float MOON_CYCLE_DURATION = 240.0f; // 4 minutes for full cycle
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,180 @@
#pragma once
#include "pipeline/m2_loader.hpp"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <string>
namespace wowee {
namespace pipeline { class AssetManager; }
namespace rendering {
// Forward declarations
class Shader;
class Texture;
class Camera;
// Weapon attached to a character instance at a bone attachment point
struct WeaponAttachment {
uint32_t weaponModelId;
uint32_t weaponInstanceId;
uint32_t attachmentId; // 1=RightHand, 2=LeftHand
uint16_t boneIndex;
glm::vec3 offset;
};
/**
* Character renderer for M2 models with skeletal animation
*
* Features:
* - Skeletal animation with bone transformations
* - Keyframe interpolation (linear position/scale, slerp rotation)
* - Vertex skinning (GPU-accelerated)
* - Texture loading from BLP via AssetManager
*/
class CharacterRenderer {
public:
CharacterRenderer();
~CharacterRenderer();
bool initialize();
void shutdown();
void setAssetManager(pipeline::AssetManager* am) { assetManager = am; }
bool loadModel(const pipeline::M2Model& model, uint32_t id);
uint32_t createInstance(uint32_t modelId, const glm::vec3& position,
const glm::vec3& rotation = glm::vec3(0.0f),
float scale = 1.0f);
void playAnimation(uint32_t instanceId, uint32_t animationId, bool loop = true);
void update(float deltaTime);
void render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
void setInstancePosition(uint32_t instanceId, const glm::vec3& position);
void setInstanceRotation(uint32_t instanceId, const glm::vec3& rotation);
void setActiveGeosets(uint32_t instanceId, const std::unordered_set<uint16_t>& geosets);
void removeInstance(uint32_t instanceId);
/** Attach a weapon model to a character instance at the given attachment point. */
bool attachWeapon(uint32_t charInstanceId, uint32_t attachmentId,
const pipeline::M2Model& weaponModel, uint32_t weaponModelId,
const std::string& texturePath);
/** Detach a weapon from the given attachment point. */
void detachWeapon(uint32_t charInstanceId, uint32_t attachmentId);
size_t getInstanceCount() const { return instances.size(); }
private:
// GPU representation of M2 model
struct M2ModelGPU {
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
pipeline::M2Model data; // Original model data
std::vector<glm::mat4> bindPose; // Inverse bind pose matrices
// Textures loaded from BLP (indexed by texture array position)
std::vector<GLuint> textureIds;
};
// Character instance
struct CharacterInstance {
uint32_t id;
uint32_t modelId;
glm::vec3 position;
glm::vec3 rotation;
float scale;
// Animation state
uint32_t currentAnimationId = 0;
int currentSequenceIndex = -1; // Index into M2Model::sequences
float animationTime = 0.0f;
bool animationLoop = true;
std::vector<glm::mat4> boneMatrices; // Current bone transforms
// Geoset visibility — which submesh IDs to render
// Empty = render all (for non-character models)
std::unordered_set<uint16_t> activeGeosets;
// Weapon attachments (weapons parented to this instance's bones)
std::vector<WeaponAttachment> weaponAttachments;
// Override model matrix (used for weapon instances positioned by parent bone)
bool hasOverrideModelMatrix = false;
glm::mat4 overrideModelMatrix{1.0f};
};
void setupModelBuffers(M2ModelGPU& gpuModel);
void calculateBindPose(M2ModelGPU& gpuModel);
void updateAnimation(CharacterInstance& instance, float deltaTime);
void calculateBoneMatrices(CharacterInstance& instance);
glm::mat4 getBoneTransform(const pipeline::M2Bone& bone, float time, int sequenceIndex);
glm::mat4 getModelMatrix(const CharacterInstance& instance) const;
// Keyframe interpolation helpers
static int findKeyframeIndex(const std::vector<uint32_t>& timestamps, float time);
static glm::vec3 interpolateVec3(const pipeline::M2AnimationTrack& track,
int seqIdx, float time, const glm::vec3& defaultVal);
static glm::quat interpolateQuat(const pipeline::M2AnimationTrack& track,
int seqIdx, float time);
public:
/**
* Build a composited character skin texture by alpha-blending overlay
* layers (e.g. underwear) onto a base skin BLP. Each overlay is placed
* at the correct CharComponentTextureSections region based on its
* filename (pelvis, torso, etc.). Returns the resulting GL texture ID.
*/
GLuint compositeTextures(const std::vector<std::string>& layerPaths);
/**
* Build a composited character skin with explicit region-based equipment overlays.
* @param basePath Body skin texture path
* @param baseLayers Underwear overlay paths (placed by filename keyword)
* @param regionLayers Pairs of (region_index, blp_path) for equipment textures
* @return GL texture ID of the composited result
*/
GLuint compositeWithRegions(const std::string& basePath,
const std::vector<std::string>& baseLayers,
const std::vector<std::pair<int, std::string>>& regionLayers);
/** Load a BLP texture from MPQ and return the GL texture ID (cached). */
GLuint loadTexture(const std::string& path);
/** Replace a loaded model's texture at the given slot with a new GL texture. */
void setModelTexture(uint32_t modelId, uint32_t textureSlot, GLuint textureId);
/** Reset a model's texture slot back to white fallback. */
void resetModelTexture(uint32_t modelId, uint32_t textureSlot);
private:
std::unique_ptr<Shader> characterShader;
pipeline::AssetManager* assetManager = nullptr;
// Texture cache
std::unordered_map<std::string, GLuint> textureCache;
GLuint whiteTexture = 0;
std::unordered_map<uint32_t, M2ModelGPU> models;
std::unordered_map<uint32_t, CharacterInstance> instances;
uint32_t nextInstanceId = 1;
// Maximum bones supported (GPU uniform limit)
static constexpr int MAX_BONES = 200;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,95 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
class Camera;
class Shader;
/**
* @brief Renders procedural animated clouds on a sky dome
*
* Features:
* - Procedural cloud generation using multiple noise layers
* - Two cloud layers at different altitudes
* - Animated wind movement
* - Time-of-day color tinting (orange at sunrise/sunset)
* - Transparency and soft edges
*/
class Clouds {
public:
Clouds();
~Clouds();
/**
* @brief Initialize cloud system (generate mesh and shaders)
* @return true if initialization succeeded
*/
bool initialize();
/**
* @brief Render clouds
* @param camera The camera to render from
* @param timeOfDay Current time (0-24 hours)
*/
void render(const Camera& camera, float timeOfDay);
/**
* @brief Update cloud animation
* @param deltaTime Time since last frame
*/
void update(float deltaTime);
/**
* @brief Enable or disable cloud rendering
*/
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
/**
* @brief Set cloud density (0.0 = clear, 1.0 = overcast)
*/
void setDensity(float density);
float getDensity() const { return density; }
/**
* @brief Set wind speed multiplier
*/
void setWindSpeed(float speed) { windSpeed = speed; }
float getWindSpeed() const { return windSpeed; }
private:
void generateMesh();
void cleanup();
glm::vec3 getCloudColor(float timeOfDay) const;
// OpenGL objects
GLuint vao = 0;
GLuint vbo = 0;
GLuint ebo = 0;
std::unique_ptr<Shader> shader;
// Mesh data
std::vector<glm::vec3> vertices;
std::vector<unsigned int> indices;
int triangleCount = 0;
// Cloud parameters
bool enabled = true;
float density = 0.5f; // Cloud coverage
float windSpeed = 1.0f;
float windOffset = 0.0f; // Accumulated wind movement
// Mesh generation parameters
static constexpr int SEGMENTS = 32; // Horizontal segments
static constexpr int RINGS = 8; // Vertical rings (only upper hemisphere)
static constexpr float RADIUS = 900.0f; // Slightly smaller than skybox
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,88 @@
#pragma once
#include <glm/glm.hpp>
#include <array>
namespace wowee {
namespace rendering {
/**
* Frustum plane
*/
struct Plane {
glm::vec3 normal;
float distance;
Plane() : normal(0.0f), distance(0.0f) {}
Plane(const glm::vec3& n, float d) : normal(n), distance(d) {}
/**
* Calculate signed distance from point to plane
* Positive = in front, Negative = behind
*/
float distanceToPoint(const glm::vec3& point) const {
return glm::dot(normal, point) + distance;
}
};
/**
* View frustum for culling
*
* Six planes: left, right, bottom, top, near, far
*/
class Frustum {
public:
enum Side {
LEFT = 0,
RIGHT,
BOTTOM,
TOP,
NEAR,
FAR
};
Frustum() = default;
/**
* Extract frustum planes from view-projection matrix
* @param viewProjection Combined view * projection matrix
*/
void extractFromMatrix(const glm::mat4& viewProjection);
/**
* Test if point is inside frustum
*/
bool containsPoint(const glm::vec3& point) const;
/**
* Test if sphere is inside or intersecting frustum
* @param center Sphere center
* @param radius Sphere radius
* @return true if sphere is visible (fully or partially inside)
*/
bool intersectsSphere(const glm::vec3& center, float radius) const;
/**
* Test if axis-aligned bounding box intersects frustum
* @param min Box minimum corner
* @param max Box maximum corner
* @return true if box is visible (fully or partially inside)
*/
bool intersectsAABB(const glm::vec3& min, const glm::vec3& max) const;
/**
* Get frustum plane
*/
const Plane& getPlane(Side side) const { return planes[side]; }
private:
std::array<Plane, 6> planes;
/**
* Normalize plane (ensure unit length normal)
*/
void normalizePlane(Plane& plane);
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,85 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
class Camera;
class Shader;
/**
* @brief Renders lens flare effect when looking at the sun
*
* Features:
* - Multiple flare elements (ghosts) along sun-to-center axis
* - Sun glow at sun position
* - Colored flare elements (chromatic aberration simulation)
* - Intensity based on sun visibility and angle
* - Additive blending for realistic light artifacts
*/
class LensFlare {
public:
LensFlare();
~LensFlare();
/**
* @brief Initialize lens flare system
* @return true if initialization succeeded
*/
bool initialize();
/**
* @brief Render lens flare effect
* @param camera The camera to render from
* @param sunPosition World-space sun position
* @param timeOfDay Current time (0-24 hours)
*/
void render(const Camera& camera, const glm::vec3& sunPosition, float timeOfDay);
/**
* @brief Enable or disable lens flare rendering
*/
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
/**
* @brief Set flare intensity multiplier
*/
void setIntensity(float intensity);
float getIntensity() const { return intensityMultiplier; }
private:
struct FlareElement {
float position; // Position along sun-center axis (-1 to 1, 0 = center)
float size; // Size in screen space
glm::vec3 color; // RGB color
float brightness; // Brightness multiplier
};
void generateFlareElements();
void cleanup();
float calculateSunVisibility(const Camera& camera, const glm::vec3& sunPosition) const;
glm::vec2 worldToScreen(const Camera& camera, const glm::vec3& worldPos) const;
// OpenGL objects
GLuint vao = 0;
GLuint vbo = 0;
std::unique_ptr<Shader> shader;
// Flare elements
std::vector<FlareElement> flareElements;
// Parameters
bool enabled = true;
float intensityMultiplier = 1.0f;
// Quad vertices for rendering flare sprites
static constexpr int VERTICES_PER_QUAD = 6;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,105 @@
#pragma once
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
// Forward declarations
class Shader;
class Camera;
/**
* Lightning system for thunder storm effects
*
* Features:
* - Random lightning strikes during rain
* - Screen flash effect
* - Procedural lightning bolts with branches
* - Thunder timing (light then sound delay)
* - Intensity scaling with weather
*/
class Lightning {
public:
Lightning();
~Lightning();
bool initialize();
void shutdown();
void update(float deltaTime, const Camera& camera);
void render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
// Control
void setEnabled(bool enabled);
bool isEnabled() const { return enabled; }
void setIntensity(float intensity); // 0.0 - 1.0 (affects frequency)
float getIntensity() const { return intensity; }
// Trigger manual strike (for testing or scripted events)
void triggerStrike(const glm::vec3& position);
private:
struct LightningBolt {
glm::vec3 startPos;
glm::vec3 endPos;
float lifetime;
float maxLifetime;
std::vector<glm::vec3> segments; // Bolt path
std::vector<glm::vec3> branches; // Branch points
float brightness;
bool active;
};
struct Flash {
float intensity; // 0.0 - 1.0
float lifetime;
float maxLifetime;
bool active;
};
void generateLightningBolt(LightningBolt& bolt);
void generateBoltSegments(const glm::vec3& start, const glm::vec3& end,
std::vector<glm::vec3>& segments, int depth = 0);
void updateBolts(float deltaTime);
void updateFlash(float deltaTime);
void spawnRandomStrike(const glm::vec3& cameraPos);
void renderBolts(const glm::mat4& viewProj);
void renderFlash();
bool enabled = true;
float intensity = 0.5f; // Strike frequency multiplier
// Timing
float strikeTimer = 0.0f;
float nextStrikeTime = 0.0f;
// Active effects
std::vector<LightningBolt> bolts;
Flash flash;
// Rendering
std::unique_ptr<Shader> boltShader;
std::unique_ptr<Shader> flashShader;
unsigned int boltVAO = 0;
unsigned int boltVBO = 0;
unsigned int flashVAO = 0;
unsigned int flashVBO = 0;
// Configuration
static constexpr int MAX_BOLTS = 3;
static constexpr float MIN_STRIKE_INTERVAL = 2.0f;
static constexpr float MAX_STRIKE_INTERVAL = 8.0f;
static constexpr float BOLT_LIFETIME = 0.15f; // Quick flash
static constexpr float FLASH_LIFETIME = 0.3f;
static constexpr float STRIKE_DISTANCE = 200.0f; // From camera
static constexpr int MAX_SEGMENTS = 64;
static constexpr float BRANCH_PROBABILITY = 0.3f;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,145 @@
#pragma once
#include "pipeline/m2_loader.hpp"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
#include <vector>
#include <string>
namespace wowee {
namespace pipeline {
class AssetManager;
}
namespace rendering {
class Shader;
class Camera;
/**
* GPU representation of an M2 model
*/
struct M2ModelGPU {
struct BatchGPU {
GLuint texture = 0;
uint32_t indexStart = 0; // offset in indices (not bytes)
uint32_t indexCount = 0;
bool hasAlpha = false;
};
GLuint vao = 0;
GLuint vbo = 0;
GLuint ebo = 0;
uint32_t indexCount = 0;
uint32_t vertexCount = 0;
std::vector<BatchGPU> batches;
glm::vec3 boundMin;
glm::vec3 boundMax;
float boundRadius = 0.0f;
std::string name;
bool isValid() const { return vao != 0 && indexCount > 0; }
};
/**
* Instance of an M2 model in the world
*/
struct M2Instance {
uint32_t id = 0; // Unique instance ID
uint32_t modelId;
glm::vec3 position;
glm::vec3 rotation; // Euler angles in degrees
float scale;
glm::mat4 modelMatrix;
void updateModelMatrix();
};
/**
* M2 Model Renderer
*
* Handles rendering of M2 models (doodads like trees, rocks, bushes)
*/
class M2Renderer {
public:
M2Renderer();
~M2Renderer();
bool initialize(pipeline::AssetManager* assets);
void shutdown();
/**
* Load an M2 model to GPU
* @param model Parsed M2 model data
* @param modelId Unique ID for this model
* @return True if successful
*/
bool loadModel(const pipeline::M2Model& model, uint32_t modelId);
/**
* Create an instance of a loaded model
* @param modelId ID of the loaded model
* @param position World position
* @param rotation Rotation in degrees (x, y, z)
* @param scale Scale factor (1.0 = normal)
* @return Instance ID
*/
uint32_t createInstance(uint32_t modelId, const glm::vec3& position,
const glm::vec3& rotation = glm::vec3(0.0f),
float scale = 1.0f);
/**
* Create an instance with a pre-computed model matrix
* Used for WMO doodads where the full transform is computed externally
*/
uint32_t createInstanceWithMatrix(uint32_t modelId, const glm::mat4& modelMatrix,
const glm::vec3& position);
/**
* Render all visible instances
*/
void render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
/**
* Remove a specific instance by ID
* @param instanceId Instance ID returned by createInstance()
*/
void removeInstance(uint32_t instanceId);
/**
* Clear all models and instances
*/
void clear();
// Stats
uint32_t getModelCount() const { return static_cast<uint32_t>(models.size()); }
uint32_t getInstanceCount() const { return static_cast<uint32_t>(instances.size()); }
uint32_t getTotalTriangleCount() const;
uint32_t getDrawCallCount() const { return lastDrawCallCount; }
private:
pipeline::AssetManager* assetManager = nullptr;
std::unique_ptr<Shader> shader;
std::unordered_map<uint32_t, M2ModelGPU> models;
std::vector<M2Instance> instances;
uint32_t nextInstanceId = 1;
uint32_t lastDrawCallCount = 0;
GLuint loadTexture(const std::string& path);
std::unordered_map<std::string, GLuint> textureCache;
GLuint whiteTexture = 0;
// Lighting uniforms
glm::vec3 lightDir = glm::vec3(0.5f, 0.5f, 1.0f);
glm::vec3 ambientColor = glm::vec3(0.4f, 0.4f, 0.45f);
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,32 @@
#pragma once
#include <memory>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
class Shader;
class Texture;
class Material {
public:
Material() = default;
~Material() = default;
void setShader(std::shared_ptr<Shader> shader) { this->shader = shader; }
void setTexture(std::shared_ptr<Texture> texture) { this->texture = texture; }
void setColor(const glm::vec4& color) { this->color = color; }
std::shared_ptr<Shader> getShader() const { return shader; }
std::shared_ptr<Texture> getTexture() const { return texture; }
const glm::vec4& getColor() const { return color; }
private:
std::shared_ptr<Shader> shader;
std::shared_ptr<Texture> texture;
glm::vec4 color = glm::vec4(1.0f);
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,33 @@
#pragma once
#include <vector>
#include <GL/glew.h>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
struct Vertex {
glm::vec3 position;
glm::vec3 normal;
glm::vec2 texCoord;
};
class Mesh {
public:
Mesh() = default;
~Mesh();
void create(const std::vector<Vertex>& vertices, const std::vector<uint32_t>& indices);
void destroy();
void draw() const;
private:
GLuint VAO = 0;
GLuint VBO = 0;
GLuint EBO = 0;
size_t indexCount = 0;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,54 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
namespace wowee {
namespace rendering {
class Shader;
class Camera;
class TerrainRenderer;
class Minimap {
public:
Minimap();
~Minimap();
bool initialize(int size = 200);
void shutdown();
void setTerrainRenderer(TerrainRenderer* tr) { terrainRenderer = tr; }
void render(const Camera& playerCamera, int screenWidth, int screenHeight);
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
void toggle() { enabled = !enabled; }
void setViewRadius(float radius) { viewRadius = radius; }
private:
void renderTerrainToFBO(const Camera& playerCamera);
void renderQuad(int screenWidth, int screenHeight);
TerrainRenderer* terrainRenderer = nullptr;
// FBO for offscreen rendering
GLuint fbo = 0;
GLuint fboTexture = 0;
GLuint fboDepth = 0;
// Screen quad
GLuint quadVAO = 0;
GLuint quadVBO = 0;
std::unique_ptr<Shader> quadShader;
int mapSize = 200;
float viewRadius = 500.0f;
bool enabled = false;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,100 @@
#pragma once
#include <string>
#include <deque>
namespace wowee {
namespace rendering {
class Renderer;
class Camera;
}
namespace rendering {
/**
* Performance HUD for displaying real-time statistics
*
* Shows FPS, frame time, rendering stats, and terrain info
*/
class PerformanceHUD {
public:
PerformanceHUD();
~PerformanceHUD();
/**
* Update HUD with latest frame time
* @param deltaTime Time since last frame in seconds
*/
void update(float deltaTime);
/**
* Render HUD using ImGui
* @param renderer Renderer for accessing stats
* @param camera Camera for position info
*/
void render(const Renderer* renderer, const Camera* camera);
/**
* Enable/disable HUD display
*/
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
/**
* Toggle HUD visibility
*/
void toggle() { enabled = !enabled; }
/**
* Set HUD position
*/
enum class Position {
TOP_LEFT,
TOP_RIGHT,
BOTTOM_LEFT,
BOTTOM_RIGHT
};
void setPosition(Position pos) { position = pos; }
/**
* Enable/disable specific sections
*/
void setShowFPS(bool show) { showFPS = show; }
void setShowRenderer(bool show) { showRenderer = show; }
void setShowTerrain(bool show) { showTerrain = show; }
void setShowCamera(bool show) { showCamera = show; }
void setShowControls(bool show) { showControls = show; }
private:
/**
* Calculate average FPS from frame time history
*/
void calculateFPS();
bool enabled = true; // Enabled by default, press F1 to toggle
Position position = Position::TOP_LEFT;
// Section visibility
bool showFPS = true;
bool showRenderer = true;
bool showTerrain = true;
bool showCamera = true;
bool showControls = true;
// FPS tracking
std::deque<float> frameTimeHistory;
static constexpr size_t MAX_FRAME_HISTORY = 120; // 2 seconds at 60 FPS
float currentFPS = 0.0f;
float averageFPS = 0.0f;
float minFPS = 0.0f;
float maxFPS = 0.0f;
float frameTime = 0.0f;
// Update timing
float updateTimer = 0.0f;
static constexpr float UPDATE_INTERVAL = 0.1f; // Update stats every 0.1s
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,174 @@
#pragma once
#include <memory>
#include <string>
#include <cstdint>
#include <glm/glm.hpp>
namespace wowee {
namespace core { class Window; }
namespace game { class World; class ZoneManager; }
namespace audio { class MusicManager; }
namespace pipeline { class AssetManager; }
namespace rendering {
class Camera;
class CameraController;
class Scene;
class TerrainRenderer;
class TerrainManager;
class PerformanceHUD;
class WaterRenderer;
class Skybox;
class Celestial;
class StarField;
class Clouds;
class LensFlare;
class Weather;
class SwimEffects;
class CharacterRenderer;
class WMORenderer;
class M2Renderer;
class Minimap;
class Renderer {
public:
Renderer();
~Renderer();
bool initialize(core::Window* window);
void shutdown();
void beginFrame();
void endFrame();
void renderWorld(game::World* world);
/**
* Update renderer (camera, etc.)
*/
void update(float deltaTime);
/**
* Load test terrain for debugging
* @param assetManager Asset manager to load terrain data
* @param adtPath Path to ADT file (e.g., "World\\Maps\\Azeroth\\Azeroth_32_49.adt")
*/
bool loadTestTerrain(pipeline::AssetManager* assetManager, const std::string& adtPath);
/**
* Enable/disable terrain rendering
*/
void setTerrainEnabled(bool enabled) { terrainEnabled = enabled; }
/**
* Enable/disable wireframe mode
*/
void setWireframeMode(bool enabled);
/**
* Load terrain tiles around position
* @param mapName Map name (e.g., "Azeroth", "Kalimdor")
* @param centerX Center tile X coordinate
* @param centerY Center tile Y coordinate
* @param radius Load radius in tiles
*/
bool loadTerrainArea(const std::string& mapName, int centerX, int centerY, int radius = 1);
/**
* Enable/disable terrain streaming
*/
void setTerrainStreaming(bool enabled);
/**
* Render performance HUD
*/
void renderHUD();
Camera* getCamera() { return camera.get(); }
CameraController* getCameraController() { return cameraController.get(); }
Scene* getScene() { return scene.get(); }
TerrainRenderer* getTerrainRenderer() const { return terrainRenderer.get(); }
TerrainManager* getTerrainManager() const { return terrainManager.get(); }
PerformanceHUD* getPerformanceHUD() { return performanceHUD.get(); }
WaterRenderer* getWaterRenderer() const { return waterRenderer.get(); }
Skybox* getSkybox() const { return skybox.get(); }
Celestial* getCelestial() const { return celestial.get(); }
StarField* getStarField() const { return starField.get(); }
Clouds* getClouds() const { return clouds.get(); }
LensFlare* getLensFlare() const { return lensFlare.get(); }
Weather* getWeather() const { return weather.get(); }
CharacterRenderer* getCharacterRenderer() const { return characterRenderer.get(); }
WMORenderer* getWMORenderer() const { return wmoRenderer.get(); }
M2Renderer* getM2Renderer() const { return m2Renderer.get(); }
Minimap* getMinimap() const { return minimap.get(); }
const std::string& getCurrentZoneName() const { return currentZoneName; }
// Third-person character follow
void setCharacterFollow(uint32_t instanceId);
glm::vec3& getCharacterPosition() { return characterPosition; }
uint32_t getCharacterInstanceId() const { return characterInstanceId; }
float getCharacterYaw() const { return characterYaw; }
// Emote support
void playEmote(const std::string& emoteName);
void cancelEmote();
bool isEmoteActive() const { return emoteActive; }
static std::string getEmoteText(const std::string& emoteName);
// Targeting support
void setTargetPosition(const glm::vec3* pos);
bool isMoving() const;
private:
core::Window* window = nullptr;
std::unique_ptr<Camera> camera;
std::unique_ptr<CameraController> cameraController;
std::unique_ptr<Scene> scene;
std::unique_ptr<TerrainRenderer> terrainRenderer;
std::unique_ptr<TerrainManager> terrainManager;
std::unique_ptr<PerformanceHUD> performanceHUD;
std::unique_ptr<WaterRenderer> waterRenderer;
std::unique_ptr<Skybox> skybox;
std::unique_ptr<Celestial> celestial;
std::unique_ptr<StarField> starField;
std::unique_ptr<Clouds> clouds;
std::unique_ptr<LensFlare> lensFlare;
std::unique_ptr<Weather> weather;
std::unique_ptr<SwimEffects> swimEffects;
std::unique_ptr<CharacterRenderer> characterRenderer;
std::unique_ptr<WMORenderer> wmoRenderer;
std::unique_ptr<M2Renderer> m2Renderer;
std::unique_ptr<Minimap> minimap;
std::unique_ptr<audio::MusicManager> musicManager;
std::unique_ptr<game::ZoneManager> zoneManager;
pipeline::AssetManager* cachedAssetManager = nullptr;
uint32_t currentZoneId = 0;
std::string currentZoneName;
// Third-person character state
glm::vec3 characterPosition = glm::vec3(0.0f);
uint32_t characterInstanceId = 0;
float characterYaw = 0.0f;
// Character animation state
enum class CharAnimState { IDLE, WALK, RUN, JUMP_START, JUMP_MID, JUMP_END, SIT_DOWN, SITTING, EMOTE, SWIM_IDLE, SWIM };
CharAnimState charAnimState = CharAnimState::IDLE;
void updateCharacterAnimation();
// Emote state
bool emoteActive = false;
uint32_t emoteAnimId = 0;
bool emoteLoop = false;
// Target facing
const glm::vec3* targetPosition = nullptr;
bool terrainEnabled = true;
bool terrainLoaded = false;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,27 @@
#pragma once
#include <vector>
#include <memory>
namespace wowee {
namespace rendering {
class Mesh;
class Scene {
public:
Scene() = default;
~Scene() = default;
void addMesh(std::shared_ptr<Mesh> mesh);
void removeMesh(std::shared_ptr<Mesh> mesh);
void clear();
const std::vector<std::shared_ptr<Mesh>>& getMeshes() const { return meshes; }
private:
std::vector<std::shared_ptr<Mesh>> meshes;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,41 @@
#pragma once
#include <string>
#include <GL/glew.h>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
class Shader {
public:
Shader() = default;
~Shader();
bool loadFromFile(const std::string& vertexPath, const std::string& fragmentPath);
bool loadFromSource(const std::string& vertexSource, const std::string& fragmentSource);
void use() const;
void unuse() const;
void setUniform(const std::string& name, int value);
void setUniform(const std::string& name, float value);
void setUniform(const std::string& name, const glm::vec2& value);
void setUniform(const std::string& name, const glm::vec3& value);
void setUniform(const std::string& name, const glm::vec4& value);
void setUniform(const std::string& name, const glm::mat3& value);
void setUniform(const std::string& name, const glm::mat4& value);
GLuint getProgram() const { return program; }
private:
bool compile(const std::string& vertexSource, const std::string& fragmentSource);
GLint getUniformLocation(const std::string& name) const;
GLuint program = 0;
GLuint vertexShader = 0;
GLuint fragmentShader = 0;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,83 @@
#pragma once
#include <memory>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
class Shader;
class Camera;
/**
* Skybox renderer
*
* Renders an atmospheric sky dome with gradient colors.
* The sky uses a dome/sphere approach for realistic appearance.
*/
class Skybox {
public:
Skybox();
~Skybox();
bool initialize();
void shutdown();
/**
* Render the skybox
* @param camera Camera for view matrix (position is ignored for skybox)
* @param timeOfDay Time of day in hours (0-24), affects sky color
*/
void render(const Camera& camera, float timeOfDay = 12.0f);
/**
* Enable/disable skybox rendering
*/
void setEnabled(bool enabled) { renderingEnabled = enabled; }
bool isEnabled() const { return renderingEnabled; }
/**
* Set time of day (0-24 hours)
* 0 = midnight, 6 = dawn, 12 = noon, 18 = dusk, 24 = midnight
*/
void setTimeOfDay(float time);
float getTimeOfDay() const { return timeOfDay; }
/**
* Enable/disable time progression
*/
void setTimeProgression(bool enabled) { timeProgressionEnabled = enabled; }
bool isTimeProgressionEnabled() const { return timeProgressionEnabled; }
/**
* Update time progression
*/
void update(float deltaTime);
/**
* Get horizon color for fog (public for fog system)
*/
glm::vec3 getHorizonColor(float time) const;
private:
void createSkyDome();
void destroySkyDome();
glm::vec3 getSkyColor(float altitude, float time) const;
glm::vec3 getZenithColor(float time) const;
std::unique_ptr<Shader> skyShader;
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
int indexCount = 0;
float timeOfDay = 12.0f; // Default: noon
float timeSpeed = 1.0f; // 1.0 = 1 hour per real second
bool timeProgressionEnabled = false;
bool renderingEnabled = true;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,76 @@
#pragma once
#include <memory>
#include <vector>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
class Shader;
class Camera;
/**
* Star field renderer
*
* Renders a field of stars across the night sky.
* Stars fade in at dusk and out at dawn.
*/
class StarField {
public:
StarField();
~StarField();
bool initialize();
void shutdown();
/**
* Render the star field
* @param camera Camera for view matrix
* @param timeOfDay Time of day in hours (0-24)
*/
void render(const Camera& camera, float timeOfDay);
/**
* Update star twinkle animation
*/
void update(float deltaTime);
/**
* Enable/disable star rendering
*/
void setEnabled(bool enabled) { renderingEnabled = enabled; }
bool isEnabled() const { return renderingEnabled; }
/**
* Get number of stars
*/
int getStarCount() const { return starCount; }
private:
void generateStars();
void createStarBuffers();
void destroyStarBuffers();
float getStarIntensity(float timeOfDay) const;
std::unique_ptr<Shader> starShader;
struct Star {
glm::vec3 position;
float brightness; // 0.3 to 1.0
float twinklePhase; // 0 to 2π for animation
};
std::vector<Star> stars;
int starCount = 1000;
uint32_t vao = 0;
uint32_t vbo = 0;
float twinkleTime = 0.0f;
bool renderingEnabled = true;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,59 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
class Camera;
class CameraController;
class WaterRenderer;
class Shader;
class SwimEffects {
public:
SwimEffects();
~SwimEffects();
bool initialize();
void shutdown();
void update(const Camera& camera, const CameraController& cc,
const WaterRenderer& water, float deltaTime);
void render(const Camera& camera);
private:
struct Particle {
glm::vec3 position;
glm::vec3 velocity;
float lifetime;
float maxLifetime;
float size;
float alpha;
};
static constexpr int MAX_RIPPLE_PARTICLES = 200;
static constexpr int MAX_BUBBLE_PARTICLES = 150;
std::vector<Particle> ripples;
std::vector<Particle> bubbles;
GLuint rippleVAO = 0, rippleVBO = 0;
GLuint bubbleVAO = 0, bubbleVBO = 0;
std::unique_ptr<Shader> rippleShader;
std::unique_ptr<Shader> bubbleShader;
std::vector<float> rippleVertexData;
std::vector<float> bubbleVertexData;
float rippleSpawnAccum = 0.0f;
float bubbleSpawnAccum = 0.0f;
void spawnRipple(const glm::vec3& pos, const glm::vec3& moveDir, float waterH);
void spawnBubble(const glm::vec3& pos, float waterH);
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,270 @@
#pragma once
#include "pipeline/adt_loader.hpp"
#include "pipeline/terrain_mesh.hpp"
#include "pipeline/m2_loader.hpp"
#include "pipeline/wmo_loader.hpp"
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <memory>
#include <optional>
#include <thread>
#include <mutex>
#include <atomic>
#include <queue>
#include <condition_variable>
#include <glm/glm.hpp>
namespace wowee {
namespace pipeline { class AssetManager; }
namespace rendering { class TerrainRenderer; class Camera; class WaterRenderer; class M2Renderer; class WMORenderer; }
namespace rendering {
/**
* Terrain tile coordinates
*/
struct TileCoord {
int x;
int y;
bool operator==(const TileCoord& other) const {
return x == other.x && y == other.y;
}
struct Hash {
size_t operator()(const TileCoord& coord) const {
return std::hash<int>()(coord.x) ^ (std::hash<int>()(coord.y) << 1);
}
};
};
/**
* Loaded terrain tile data
*/
struct TerrainTile {
TileCoord coord;
pipeline::ADTTerrain terrain;
pipeline::TerrainMesh mesh;
bool loaded = false;
// Tile bounds in world coordinates
float minX, minY, maxX, maxY;
// Instance IDs for cleanup on unload
std::vector<uint32_t> wmoInstanceIds;
std::vector<uint32_t> m2InstanceIds;
std::vector<uint32_t> doodadUniqueIds; // For dedup cleanup on unload
};
/**
* Pre-processed tile data ready for GPU upload (produced by background thread)
*/
struct PendingTile {
TileCoord coord;
pipeline::ADTTerrain terrain;
pipeline::TerrainMesh mesh;
// Pre-loaded M2 data
struct M2Ready {
uint32_t modelId;
pipeline::M2Model model;
std::string path;
};
std::vector<M2Ready> m2Models;
// M2 instance placement data (references modelId from m2Models)
struct M2Placement {
uint32_t modelId;
uint32_t uniqueId;
glm::vec3 position;
glm::vec3 rotation;
float scale;
};
std::vector<M2Placement> m2Placements;
// Pre-loaded WMO data
struct WMOReady {
uint32_t modelId;
pipeline::WMOModel model;
glm::vec3 position;
glm::vec3 rotation;
};
std::vector<WMOReady> wmoModels;
// WMO doodad M2 models (M2s placed inside WMOs)
struct WMODoodadReady {
uint32_t modelId;
pipeline::M2Model model;
glm::vec3 worldPosition; // For frustum culling
glm::mat4 modelMatrix; // Pre-computed world transform
};
std::vector<WMODoodadReady> wmoDoodads;
};
/**
* Terrain manager for multi-tile terrain streaming
*
* Handles loading and unloading terrain tiles based on camera position
*/
class TerrainManager {
public:
TerrainManager();
~TerrainManager();
/**
* Initialize terrain manager
* @param assetManager Asset manager for loading files
* @param terrainRenderer Terrain renderer for GPU upload
*/
bool initialize(pipeline::AssetManager* assetManager, TerrainRenderer* terrainRenderer);
/**
* Update terrain streaming based on camera position
* @param camera Current camera
* @param deltaTime Time since last update
*/
void update(const Camera& camera, float deltaTime);
/**
* Set map name
* @param mapName Map name (e.g., "Azeroth", "Kalimdor")
*/
void setMapName(const std::string& mapName) { this->mapName = mapName; }
/**
* Load a single tile
* @param x Tile X coordinate (0-63)
* @param y Tile Y coordinate (0-63)
* @return true if loaded successfully
*/
bool loadTile(int x, int y);
/**
* Unload a tile
* @param x Tile X coordinate
* @param y Tile Y coordinate
*/
void unloadTile(int x, int y);
/**
* Unload all tiles
*/
void unloadAll();
/**
* Set streaming parameters
*/
void setLoadRadius(int radius) { loadRadius = radius; }
void setUnloadRadius(int radius) { unloadRadius = radius; }
void setStreamingEnabled(bool enabled) { streamingEnabled = enabled; }
void setWaterRenderer(WaterRenderer* renderer) { waterRenderer = renderer; }
void setM2Renderer(M2Renderer* renderer) { m2Renderer = renderer; }
void setWMORenderer(WMORenderer* renderer) { wmoRenderer = renderer; }
/**
* Get terrain height at GL coordinates
* @param glX GL X position
* @param glY GL Y position
* @return Height (GL Z) if terrain loaded at that position, empty otherwise
*/
std::optional<float> getHeightAt(float glX, float glY) const;
/**
* Get statistics
*/
int getLoadedTileCount() const { return static_cast<int>(loadedTiles.size()); }
TileCoord getCurrentTile() const { return currentTile; }
private:
/**
* Get tile coordinates from world position
*/
TileCoord worldToTile(float worldX, float worldY) const;
/**
* Get world bounds for a tile
*/
void getTileBounds(const TileCoord& coord, float& minX, float& minY,
float& maxX, float& maxY) const;
/**
* Build ADT file path
*/
std::string getADTPath(const TileCoord& coord) const;
/**
* Load tiles in radius around current tile
*/
void streamTiles();
/**
* Background thread: prepare tile data (CPU work only, no OpenGL)
*/
std::unique_ptr<PendingTile> prepareTile(int x, int y);
/**
* Main thread: upload prepared tile data to GPU
*/
void finalizeTile(std::unique_ptr<PendingTile> pending);
/**
* Background worker thread loop
*/
void workerLoop();
/**
* Main thread: poll for completed tiles and upload to GPU
*/
void processReadyTiles();
pipeline::AssetManager* assetManager = nullptr;
TerrainRenderer* terrainRenderer = nullptr;
WaterRenderer* waterRenderer = nullptr;
M2Renderer* m2Renderer = nullptr;
WMORenderer* wmoRenderer = nullptr;
std::string mapName = "Azeroth";
// Loaded tiles (keyed by coordinate)
std::unordered_map<TileCoord, std::unique_ptr<TerrainTile>, TileCoord::Hash> loadedTiles;
// Tiles that failed to load (don't retry)
std::unordered_map<TileCoord, bool, TileCoord::Hash> failedTiles;
// Current tile (where camera is)
TileCoord currentTile = {-1, -1};
TileCoord lastStreamTile = {-1, -1};
// Streaming parameters
bool streamingEnabled = true;
int loadRadius = 4; // Load tiles within this radius (9x9 grid, ~2133 units)
int unloadRadius = 6; // Unload tiles beyond this radius (~3200 units, past far clip)
float updateInterval = 0.1f; // Check streaming every 0.1 seconds
float timeSinceLastUpdate = 0.0f;
// Tile size constants (WoW ADT specifications)
// A tile (ADT) = 16x16 chunks = 533.33 units across
// A chunk = 8x8 vertex quads = 33.33 units across
static constexpr float TILE_SIZE = 533.33333f; // One tile = 533.33 units
static constexpr float CHUNK_SIZE = 33.33333f; // One chunk = 33.33 units
// Background loading thread
std::thread workerThread;
std::mutex queueMutex;
std::condition_variable queueCV;
std::queue<TileCoord> loadQueue;
std::queue<std::unique_ptr<PendingTile>> readyQueue;
std::atomic<bool> workerRunning{false};
// Track tiles currently queued or being processed to avoid duplicates
std::unordered_map<TileCoord, bool, TileCoord::Hash> pendingTiles;
// Dedup set for doodad placements across tile boundaries
std::unordered_set<uint32_t> placedDoodadIds;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,193 @@
#pragma once
#include "pipeline/terrain_mesh.hpp"
#include "rendering/shader.hpp"
#include "rendering/texture.hpp"
#include "rendering/camera.hpp"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
#include <string>
namespace wowee {
// Forward declarations
namespace pipeline { class AssetManager; }
namespace rendering {
class Frustum;
/**
* GPU-side terrain chunk data
*/
struct TerrainChunkGPU {
GLuint vao = 0; // Vertex array object
GLuint vbo = 0; // Vertex buffer
GLuint ibo = 0; // Index buffer
uint32_t indexCount = 0; // Number of indices to draw
// Texture IDs for this chunk
GLuint baseTexture = 0;
std::vector<GLuint> layerTextures;
std::vector<GLuint> alphaTextures;
// World position for culling
float worldX = 0.0f;
float worldY = 0.0f;
float worldZ = 0.0f;
// Owning tile coordinates (for per-tile removal)
int tileX = -1, tileY = -1;
// Bounding sphere for frustum culling
float boundingSphereRadius = 0.0f;
glm::vec3 boundingSphereCenter = glm::vec3(0.0f);
bool isValid() const { return vao != 0 && vbo != 0 && ibo != 0; }
};
/**
* Terrain renderer
*
* Handles uploading terrain meshes to GPU and rendering them
*/
class TerrainRenderer {
public:
TerrainRenderer();
~TerrainRenderer();
/**
* Initialize terrain renderer
* @param assetManager Asset manager for loading textures
*/
bool initialize(pipeline::AssetManager* assetManager);
/**
* Shutdown and cleanup GPU resources
*/
void shutdown();
/**
* Load terrain mesh and upload to GPU
* @param mesh Terrain mesh to load
* @param texturePaths Texture file paths from ADT
* @param tileX Tile X coordinate for tracking ownership (-1 = untracked)
* @param tileY Tile Y coordinate for tracking ownership (-1 = untracked)
*/
bool loadTerrain(const pipeline::TerrainMesh& mesh,
const std::vector<std::string>& texturePaths,
int tileX = -1, int tileY = -1);
/**
* Remove all chunks belonging to a specific tile
* @param tileX Tile X coordinate
* @param tileY Tile Y coordinate
*/
void removeTile(int tileX, int tileY);
/**
* Render loaded terrain
* @param camera Camera for view/projection matrices
*/
void render(const Camera& camera);
/**
* Clear all loaded terrain
*/
void clear();
/**
* Set lighting parameters
*/
void setLighting(const float lightDir[3], const float lightColor[3],
const float ambientColor[3]);
/**
* Set fog parameters
*/
void setFog(const float fogColor[3], float fogStart, float fogEnd);
/**
* Enable/disable wireframe rendering
*/
void setWireframe(bool enabled) { wireframe = enabled; }
/**
* Enable/disable frustum culling
*/
void setFrustumCulling(bool enabled) { frustumCullingEnabled = enabled; }
/**
* Enable/disable distance fog
*/
void setFogEnabled(bool enabled) { fogEnabled = enabled; }
bool isFogEnabled() const { return fogEnabled; }
/**
* Get statistics
*/
int getChunkCount() const { return static_cast<int>(chunks.size()); }
int getRenderedChunkCount() const { return renderedChunks; }
int getCulledChunkCount() const { return culledChunks; }
int getTriangleCount() const;
private:
/**
* Upload single chunk to GPU
*/
TerrainChunkGPU uploadChunk(const pipeline::ChunkMesh& chunk);
/**
* Load texture from asset manager
*/
GLuint loadTexture(const std::string& path);
/**
* Create alpha texture from raw alpha data
*/
GLuint createAlphaTexture(const std::vector<uint8_t>& alphaData);
/**
* Check if chunk is in view frustum
*/
bool isChunkVisible(const TerrainChunkGPU& chunk, const Frustum& frustum);
/**
* Calculate bounding sphere for chunk
*/
void calculateBoundingSphere(TerrainChunkGPU& chunk, const pipeline::ChunkMesh& meshChunk);
pipeline::AssetManager* assetManager = nullptr;
std::unique_ptr<Shader> shader;
// Loaded terrain chunks
std::vector<TerrainChunkGPU> chunks;
// Texture cache (path -> GL texture ID)
std::unordered_map<std::string, GLuint> textureCache;
// Lighting parameters
float lightDir[3] = {-0.5f, -1.0f, -0.5f};
float lightColor[3] = {1.0f, 1.0f, 0.9f};
float ambientColor[3] = {0.3f, 0.3f, 0.35f};
// Fog parameters
float fogColor[3] = {0.5f, 0.6f, 0.7f};
float fogStart = 400.0f;
float fogEnd = 800.0f;
// Rendering state
bool wireframe = false;
bool frustumCullingEnabled = true;
bool fogEnabled = true;
int renderedChunks = 0;
int culledChunks = 0;
// Default white texture (fallback)
GLuint whiteTexture = 0;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,31 @@
#pragma once
#include <string>
#include <GL/glew.h>
namespace wowee {
namespace rendering {
class Texture {
public:
Texture() = default;
~Texture();
bool loadFromFile(const std::string& path);
bool loadFromMemory(const unsigned char* data, int width, int height, int channels);
void bind(GLuint unit = 0) const;
void unbind() const;
GLuint getID() const { return textureID; }
int getWidth() const { return width; }
int getHeight() const { return height; }
private:
GLuint textureID = 0;
int width = 0;
int height = 0;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,123 @@
#pragma once
#include <vector>
#include <memory>
#include <optional>
#include <glm/glm.hpp>
namespace wowee {
namespace pipeline {
struct ADTTerrain;
struct LiquidData;
}
namespace rendering {
class Camera;
class Shader;
/**
* Water surface for a single map chunk
*/
struct WaterSurface {
glm::vec3 position; // World position
float minHeight; // Minimum water height
float maxHeight; // Maximum water height
uint8_t liquidType; // 0=water, 1=ocean, 2=magma, 3=slime
// Owning tile coordinates (for per-tile removal)
int tileX = -1, tileY = -1;
// Water layer dimensions within chunk (0-7 offset, 1-8 size)
uint8_t xOffset = 0;
uint8_t yOffset = 0;
uint8_t width = 8; // Width in tiles (1-8)
uint8_t height = 8; // Height in tiles (1-8)
// Height map for water surface ((width+1) x (height+1) vertices)
std::vector<float> heights;
// Render mask (which tiles have water)
std::vector<uint8_t> mask;
// Render data
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
int indexCount = 0;
bool hasHeightData() const { return !heights.empty(); }
};
/**
* Water renderer
*
* Renders water surfaces with transparency and animation.
* Supports multiple liquid types (water, ocean, magma, slime).
*/
class WaterRenderer {
public:
WaterRenderer();
~WaterRenderer();
bool initialize();
void shutdown();
/**
* Load water surfaces from ADT terrain
* @param terrain The ADT terrain data
* @param append If true, add to existing water instead of replacing
* @param tileX Tile X coordinate for tracking ownership (-1 = untracked)
* @param tileY Tile Y coordinate for tracking ownership (-1 = untracked)
*/
void loadFromTerrain(const pipeline::ADTTerrain& terrain, bool append = false,
int tileX = -1, int tileY = -1);
/**
* Remove all water surfaces belonging to a specific tile
* @param tileX Tile X coordinate
* @param tileY Tile Y coordinate
*/
void removeTile(int tileX, int tileY);
/**
* Clear all water surfaces
*/
void clear();
/**
* Render all water surfaces
*/
void render(const Camera& camera, float time);
/**
* Enable/disable water rendering
*/
void setEnabled(bool enabled) { renderingEnabled = enabled; }
bool isEnabled() const { return renderingEnabled; }
/**
* Query the water height at a given world position.
* Returns the highest water surface height at that XY, or nullopt if no water.
*/
std::optional<float> getWaterHeightAt(float glX, float glY) const;
/**
* Get water surface count
*/
int getSurfaceCount() const { return static_cast<int>(surfaces.size()); }
private:
void createWaterMesh(WaterSurface& surface);
void destroyWaterMesh(WaterSurface& surface);
glm::vec4 getLiquidColor(uint8_t liquidType) const;
float getLiquidAlpha(uint8_t liquidType) const;
std::unique_ptr<Shader> waterShader;
std::vector<WaterSurface> surfaces;
bool renderingEnabled = true;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,112 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
class Camera;
class Shader;
/**
* @brief Weather particle system for rain and snow
*
* Features:
* - Rain particles (fast vertical drops)
* - Snow particles (slow floating flakes)
* - Particle recycling for efficiency
* - Camera-relative positioning (follows player)
* - Adjustable intensity (light, medium, heavy)
* - GPU instanced rendering
*/
class Weather {
public:
enum class Type {
NONE,
RAIN,
SNOW
};
Weather();
~Weather();
/**
* @brief Initialize weather system
* @return true if initialization succeeded
*/
bool initialize();
/**
* @brief Update weather particles
* @param camera Camera for particle positioning
* @param deltaTime Time since last frame
*/
void update(const Camera& camera, float deltaTime);
/**
* @brief Render weather particles
* @param camera Camera for rendering
*/
void render(const Camera& camera);
/**
* @brief Set weather type
*/
void setWeatherType(Type type) { weatherType = type; }
Type getWeatherType() const { return weatherType; }
/**
* @brief Set weather intensity (0.0 = none, 1.0 = heavy)
*/
void setIntensity(float intensity);
float getIntensity() const { return intensity; }
/**
* @brief Enable or disable weather
*/
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
/**
* @brief Get active particle count
*/
int getParticleCount() const;
private:
struct Particle {
glm::vec3 position;
glm::vec3 velocity;
float lifetime;
float maxLifetime;
};
void cleanup();
void resetParticles(const Camera& camera);
void updateParticle(Particle& particle, const Camera& camera, float deltaTime);
glm::vec3 getRandomPosition(const glm::vec3& center) const;
// OpenGL objects
GLuint vao = 0;
GLuint vbo = 0; // Instance buffer
std::unique_ptr<Shader> shader;
// Particles
std::vector<Particle> particles;
std::vector<glm::vec3> particlePositions; // For rendering
// Weather parameters
bool enabled = false;
Type weatherType = Type::NONE;
float intensity = 0.5f;
// Particle system parameters
static constexpr int MAX_PARTICLES = 2000;
static constexpr float SPAWN_VOLUME_SIZE = 100.0f; // Size of spawn area around camera
static constexpr float SPAWN_HEIGHT = 80.0f; // Height above camera to spawn
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,262 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
#include <vector>
#include <string>
#include <optional>
namespace wowee {
namespace pipeline {
struct WMOModel;
struct WMOGroup;
class AssetManager;
}
namespace rendering {
class Camera;
class Shader;
/**
* WMO (World Model Object) Renderer
*
* Renders buildings, dungeons, and large structures from WMO files.
* Features:
* - Multi-material rendering
* - Batched rendering per group
* - Frustum culling
* - Portal visibility (future)
* - Dynamic lighting support (future)
*/
class WMORenderer {
public:
WMORenderer();
~WMORenderer();
/**
* Initialize renderer and create shaders
* @param assetManager Asset manager for loading textures (optional)
*/
bool initialize(pipeline::AssetManager* assetManager = nullptr);
/**
* Cleanup GPU resources
*/
void shutdown();
/**
* Load WMO model and create GPU resources
* @param model WMO model with geometry data
* @param id Unique identifier for this WMO instance
* @return True if successful
*/
bool loadModel(const pipeline::WMOModel& model, uint32_t id);
/**
* Unload WMO model and free GPU resources
* @param id WMO model identifier
*/
void unloadModel(uint32_t id);
/**
* Create a WMO instance in the world
* @param modelId WMO model to instantiate
* @param position World position
* @param rotation Rotation (euler angles in radians)
* @param scale Uniform scale
* @return Instance ID
*/
uint32_t createInstance(uint32_t modelId, const glm::vec3& position,
const glm::vec3& rotation = glm::vec3(0.0f),
float scale = 1.0f);
/**
* Remove WMO instance
* @param instanceId Instance to remove
*/
void removeInstance(uint32_t instanceId);
/**
* Remove all instances
*/
void clearInstances();
/**
* Render all WMO instances
* @param camera Camera for view/projection matrices
* @param view View matrix
* @param projection Projection matrix
*/
void render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
/**
* Get number of loaded models
*/
uint32_t getModelCount() const { return loadedModels.size(); }
/**
* Get number of active instances
*/
uint32_t getInstanceCount() const { return instances.size(); }
/**
* Get total triangle count (all instances)
*/
uint32_t getTotalTriangleCount() const;
/**
* Get total draw call count (last frame)
*/
uint32_t getDrawCallCount() const { return lastDrawCalls; }
/**
* Enable/disable wireframe rendering
*/
void setWireframeMode(bool enabled) { wireframeMode = enabled; }
/**
* Enable/disable frustum culling
*/
void setFrustumCulling(bool enabled) { frustumCulling = enabled; }
/**
* Get floor height at a GL position via ray-triangle intersection
*/
std::optional<float> getFloorHeight(float glX, float glY, float glZ) const;
/**
* Check wall collision and adjust position
* @param from Starting position
* @param to Desired position
* @param adjustedPos Output adjusted position (pushed away from walls)
* @return true if collision occurred
*/
bool checkWallCollision(const glm::vec3& from, const glm::vec3& to, glm::vec3& adjustedPos) const;
/**
* Check if a position is inside any WMO
* @param outModelId If not null, receives the model ID of the WMO
* @return true if inside a WMO
*/
bool isInsideWMO(float glX, float glY, float glZ, uint32_t* outModelId = nullptr) const;
private:
/**
* WMO group GPU resources
*/
struct GroupResources {
GLuint vao = 0;
GLuint vbo = 0;
GLuint ebo = 0;
uint32_t indexCount = 0;
uint32_t vertexCount = 0;
glm::vec3 boundingBoxMin;
glm::vec3 boundingBoxMax;
// Material batches (start index, count, material ID)
struct Batch {
uint32_t startIndex; // First index in EBO
uint32_t indexCount; // Number of indices to draw
uint8_t materialId; // Material/texture reference
};
std::vector<Batch> batches;
// Collision geometry (positions only, for floor raycasting)
std::vector<glm::vec3> collisionVertices;
std::vector<uint16_t> collisionIndices;
};
/**
* Loaded WMO model data
*/
struct ModelData {
uint32_t id;
std::vector<GroupResources> groups;
glm::vec3 boundingBoxMin;
glm::vec3 boundingBoxMax;
// Texture handles for this model (indexed by texture path order)
std::vector<GLuint> textures;
// Material texture indices (materialId -> texture index)
std::vector<uint32_t> materialTextureIndices;
// Material blend modes (materialId -> blendMode; 1 = alpha-test cutout)
std::vector<uint32_t> materialBlendModes;
uint32_t getTotalTriangles() const {
uint32_t total = 0;
for (const auto& group : groups) {
total += group.indexCount / 3;
}
return total;
}
};
/**
* WMO instance in the world
*/
struct WMOInstance {
uint32_t id;
uint32_t modelId;
glm::vec3 position;
glm::vec3 rotation; // Euler angles (radians)
float scale;
glm::mat4 modelMatrix;
void updateModelMatrix();
};
/**
* Create GPU resources for a WMO group
*/
bool createGroupResources(const pipeline::WMOGroup& group, GroupResources& resources);
/**
* Render a single group
*/
void renderGroup(const GroupResources& group, const ModelData& model,
const glm::mat4& modelMatrix,
const glm::mat4& view, const glm::mat4& projection);
/**
* Check if group is visible in frustum
*/
bool isGroupVisible(const GroupResources& group, const glm::mat4& modelMatrix,
const Camera& camera) const;
/**
* Load a texture from path
*/
GLuint loadTexture(const std::string& path);
// Shader
std::unique_ptr<Shader> shader;
// Asset manager for loading textures
pipeline::AssetManager* assetManager = nullptr;
// Texture cache (path -> texture ID)
std::unordered_map<std::string, GLuint> textureCache;
// Default white texture
GLuint whiteTexture = 0;
// Loaded models (modelId -> ModelData)
std::unordered_map<uint32_t, ModelData> loadedModels;
// Active instances
std::vector<WMOInstance> instances;
uint32_t nextInstanceId = 1;
// Rendering state
bool wireframeMode = false;
bool frustumCulling = true;
uint32_t lastDrawCalls = 0;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,72 @@
#pragma once
#include "auth/auth_handler.hpp"
#include <string>
#include <functional>
namespace wowee { namespace ui {
/**
* Authentication screen UI
*
* Allows user to enter credentials and connect to auth server
*/
class AuthScreen {
public:
AuthScreen();
/**
* Render the UI
* @param authHandler Reference to auth handler
*/
void render(auth::AuthHandler& authHandler);
/**
* Set callback for successful authentication
*/
void setOnSuccess(std::function<void()> callback) { onSuccess = callback; }
/**
* Set callback for single-player mode
*/
void setOnSinglePlayer(std::function<void()> callback) { onSinglePlayer = callback; }
/**
* Check if authentication is in progress
*/
bool isAuthenticating() const { return authenticating; }
/**
* Get status message
*/
const std::string& getStatusMessage() const { return statusMessage; }
private:
// UI state
char hostname[256] = "127.0.0.1";
char username[256] = "";
char password[256] = "";
int port = 3724;
bool authenticating = false;
bool showPassword = false;
// Status
std::string statusMessage;
bool statusIsError = false;
// Callbacks
std::function<void()> onSuccess;
std::function<void()> onSinglePlayer;
/**
* Attempt authentication
*/
void attemptAuth(auth::AuthHandler& authHandler);
/**
* Update status message
*/
void setStatus(const std::string& message, bool isError = false);
};
}} // namespace wowee::ui

View file

@ -0,0 +1,66 @@
#pragma once
#include "game/game_handler.hpp"
#include <imgui.h>
#include <string>
#include <functional>
namespace wowee { namespace ui {
/**
* Character selection screen UI
*
* Displays character list and allows user to select one to play
*/
class CharacterScreen {
public:
CharacterScreen();
/**
* Render the UI
* @param gameHandler Reference to game handler
*/
void render(game::GameHandler& gameHandler);
/**
* Set callback for character selection
* @param callback Function to call when character is selected (receives character GUID)
*/
void setOnCharacterSelected(std::function<void(uint64_t)> callback) {
onCharacterSelected = callback;
}
/**
* Check if a character has been selected
*/
bool hasSelection() const { return characterSelected; }
/**
* Get selected character GUID
*/
uint64_t getSelectedGuid() const { return selectedCharacterGuid; }
private:
// UI state
int selectedCharacterIndex = -1;
bool characterSelected = false;
uint64_t selectedCharacterGuid = 0;
// Status
std::string statusMessage;
// Callbacks
std::function<void(uint64_t)> onCharacterSelected;
/**
* Update status message
*/
void setStatus(const std::string& message);
/**
* Get faction color based on race
*/
ImVec4 getFactionColor(game::Race race) const;
};
}} // namespace wowee::ui

104
include/ui/game_screen.hpp Normal file
View file

@ -0,0 +1,104 @@
#pragma once
#include "game/game_handler.hpp"
#include "game/inventory.hpp"
#include "ui/inventory_screen.hpp"
#include <imgui.h>
#include <string>
namespace wowee { namespace ui {
/**
* In-game screen UI
*
* Displays player info, entity list, chat, and game controls
*/
class GameScreen {
public:
GameScreen();
/**
* Render the UI
* @param gameHandler Reference to game handler
*/
void render(game::GameHandler& gameHandler);
/**
* Check if chat input is active
*/
bool isChatInputActive() const { return chatInputActive; }
private:
// Chat state
char chatInputBuffer[512] = "";
bool chatInputActive = false;
int selectedChatType = 0; // 0=SAY, 1=YELL, 2=PARTY, etc.
// UI state
bool showEntityWindow = true;
bool showChatWindow = true;
bool showPlayerInfo = true;
bool refocusChatInput = false;
/**
* Render player info window
*/
void renderPlayerInfo(game::GameHandler& gameHandler);
/**
* Render entity list window
*/
void renderEntityList(game::GameHandler& gameHandler);
/**
* Render chat window
*/
void renderChatWindow(game::GameHandler& gameHandler);
/**
* Send chat message
*/
void sendChatMessage(game::GameHandler& gameHandler);
/**
* Get chat type name
*/
const char* getChatTypeName(game::ChatType type) const;
/**
* Get chat type color
*/
ImVec4 getChatTypeColor(game::ChatType type) const;
/**
* Render player unit frame (top-left)
*/
void renderPlayerFrame(game::GameHandler& gameHandler);
/**
* Render target frame
*/
void renderTargetFrame(game::GameHandler& gameHandler);
/**
* Process targeting input (Tab, Escape, click)
*/
void processTargetInput(game::GameHandler& gameHandler);
/**
* Rebuild character geosets from current equipment state
*/
void updateCharacterGeosets(game::Inventory& inventory);
/**
* Re-composite character skin texture from current equipment
*/
void updateCharacterTextures(game::Inventory& inventory);
/**
* Inventory screen
*/
InventoryScreen inventoryScreen;
};
}} // namespace wowee::ui

View file

@ -0,0 +1,56 @@
#pragma once
#include "game/inventory.hpp"
#include <imgui.h>
namespace wowee {
namespace ui {
class InventoryScreen {
public:
void render(game::Inventory& inventory);
bool isOpen() const { return open; }
void toggle() { open = !open; }
void setOpen(bool o) { open = o; }
/// Returns true if equipment changed since last call, and clears the flag.
bool consumeEquipmentDirty() { bool d = equipmentDirty; equipmentDirty = false; return d; }
private:
bool open = false;
bool bKeyWasDown = false;
bool equipmentDirty = false;
// Drag-and-drop held item state
bool holdingItem = false;
game::ItemDef heldItem;
enum class HeldSource { NONE, BACKPACK, EQUIPMENT };
HeldSource heldSource = HeldSource::NONE;
int heldBackpackIndex = -1;
game::EquipSlot heldEquipSlot = game::EquipSlot::NUM_SLOTS;
void renderEquipmentPanel(game::Inventory& inventory);
void renderBackpackPanel(game::Inventory& inventory);
// Slot rendering with interaction support
enum class SlotKind { BACKPACK, EQUIPMENT };
void renderItemSlot(game::Inventory& inventory, const game::ItemSlot& slot,
float size, const char* label,
SlotKind kind, int backpackIndex,
game::EquipSlot equipSlot);
void renderItemTooltip(const game::ItemDef& item);
// Held item helpers
void pickupFromBackpack(game::Inventory& inv, int index);
void pickupFromEquipment(game::Inventory& inv, game::EquipSlot slot);
void placeInBackpack(game::Inventory& inv, int index);
void placeInEquipment(game::Inventory& inv, game::EquipSlot slot);
void cancelPickup(game::Inventory& inv);
game::EquipSlot getEquipSlotForType(uint8_t inventoryType, game::Inventory& inv);
void renderHeldItem();
static ImVec4 getQualityColor(game::ItemQuality quality);
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,73 @@
#pragma once
#include "auth/auth_handler.hpp"
#include <imgui.h>
#include <string>
#include <functional>
namespace wowee { namespace ui {
/**
* Realm selection screen UI
*
* Displays available realms and allows user to select one
*/
class RealmScreen {
public:
RealmScreen();
/**
* Render the UI
* @param authHandler Reference to auth handler
*/
void render(auth::AuthHandler& authHandler);
/**
* Set callback for realm selection
* @param callback Function to call when realm is selected (receives realm name and address)
*/
void setOnRealmSelected(std::function<void(const std::string&, const std::string&)> callback) {
onRealmSelected = callback;
}
/**
* Check if a realm has been selected
*/
bool hasSelection() const { return realmSelected; }
/**
* Get selected realm info
*/
const std::string& getSelectedName() const { return selectedRealmName; }
const std::string& getSelectedAddress() const { return selectedRealmAddress; }
private:
// UI state
int selectedRealmIndex = -1;
bool realmSelected = false;
std::string selectedRealmName;
std::string selectedRealmAddress;
// Status
std::string statusMessage;
// Callbacks
std::function<void(const std::string&, const std::string&)> onRealmSelected;
/**
* Update status message
*/
void setStatus(const std::string& message);
/**
* Get realm status text
*/
const char* getRealmStatus(uint8_t flags) const;
/**
* Get population color
*/
ImVec4 getPopulationColor(float population) const;
};
}} // namespace wowee::ui

84
include/ui/ui_manager.hpp Normal file
View file

@ -0,0 +1,84 @@
#pragma once
#include "ui/auth_screen.hpp"
#include "ui/realm_screen.hpp"
#include "ui/character_screen.hpp"
#include "ui/game_screen.hpp"
#include <memory>
// Forward declare SDL_Event
union SDL_Event;
namespace wowee {
// Forward declarations
namespace core { class Window; enum class AppState; }
namespace auth { class AuthHandler; }
namespace game { class GameHandler; }
namespace ui {
/**
* UIManager - Manages all UI screens and ImGui rendering
*
* Coordinates screen transitions and rendering based on application state
*/
class UIManager {
public:
UIManager();
~UIManager();
/**
* Initialize ImGui and UI screens
* @param window Window instance for ImGui initialization
*/
bool initialize(core::Window* window);
/**
* Shutdown ImGui and cleanup
*/
void shutdown();
/**
* Update UI state
* @param deltaTime Time since last frame in seconds
*/
void update(float deltaTime);
/**
* Render UI based on current application state
* @param appState Current application state
* @param authHandler Authentication handler reference
* @param gameHandler Game handler reference
*/
void render(core::AppState appState, auth::AuthHandler* authHandler, game::GameHandler* gameHandler);
/**
* Process SDL event for ImGui
* @param event SDL event to process
*/
void processEvent(const SDL_Event& event);
/**
* Get screen instances for callback setup
*/
AuthScreen& getAuthScreen() { return *authScreen; }
RealmScreen& getRealmScreen() { return *realmScreen; }
CharacterScreen& getCharacterScreen() { return *characterScreen; }
GameScreen& getGameScreen() { return *gameScreen; }
private:
core::Window* window = nullptr;
// UI Screens
std::unique_ptr<AuthScreen> authScreen;
std::unique_ptr<RealmScreen> realmScreen;
std::unique_ptr<CharacterScreen> characterScreen;
std::unique_ptr<GameScreen> gameScreen;
// ImGui state
bool imguiInitialized = false;
};
} // namespace ui
} // namespace wowee