mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Initial commit: wowee native WoW 3.3.5a client
This commit is contained in:
commit
ce6cb8f38e
147 changed files with 32347 additions and 0 deletions
45
include/audio/music_manager.hpp
Normal file
45
include/audio/music_manager.hpp
Normal 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
|
||||
96
include/auth/auth_handler.hpp
Normal file
96
include/auth/auth_handler.hpp
Normal 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
|
||||
43
include/auth/auth_opcodes.hpp
Normal file
43
include/auth/auth_opcodes.hpp
Normal 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
|
||||
109
include/auth/auth_packets.hpp
Normal file
109
include/auth/auth_packets.hpp
Normal 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
55
include/auth/big_num.hpp
Normal 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
27
include/auth/crypto.hpp
Normal 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
53
include/auth/rc4.hpp
Normal 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
75
include/auth/srp.hpp
Normal 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
|
||||
106
include/core/application.hpp
Normal file
106
include/core/application.hpp
Normal 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
57
include/core/input.hpp
Normal 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
76
include/core/logger.hpp
Normal 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
54
include/core/window.hpp
Normal 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
129
include/game/character.hpp
Normal 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
211
include/game/entity.hpp
Normal 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
|
||||
310
include/game/game_handler.hpp
Normal file
310
include/game/game_handler.hpp
Normal 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
|
||||
99
include/game/inventory.hpp
Normal file
99
include/game/inventory.hpp
Normal 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
|
||||
57
include/game/npc_manager.hpp
Normal file
57
include/game/npc_manager.hpp
Normal 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
55
include/game/opcodes.hpp
Normal 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
18
include/game/player.hpp
Normal 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
18
include/game/world.hpp
Normal 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
|
||||
566
include/game/world_packets.hpp
Normal file
566
include/game/world_packets.hpp
Normal 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
|
||||
32
include/game/zone_manager.hpp
Normal file
32
include/game/zone_manager.hpp
Normal 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
|
||||
43
include/network/packet.hpp
Normal file
43
include/network/packet.hpp
Normal 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
|
||||
32
include/network/socket.hpp
Normal file
32
include/network/socket.hpp
Normal 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
|
||||
31
include/network/tcp_socket.hpp
Normal file
31
include/network/tcp_socket.hpp
Normal 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
|
||||
92
include/network/world_socket.hpp
Normal file
92
include/network/world_socket.hpp
Normal 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
|
||||
210
include/pipeline/adt_loader.hpp
Normal file
210
include/pipeline/adt_loader.hpp
Normal 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
|
||||
107
include/pipeline/asset_manager.hpp
Normal file
107
include/pipeline/asset_manager.hpp
Normal 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
|
||||
110
include/pipeline/blp_loader.hpp
Normal file
110
include/pipeline/blp_loader.hpp
Normal 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
|
||||
135
include/pipeline/dbc_loader.hpp
Normal file
135
include/pipeline/dbc_loader.hpp
Normal 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
|
||||
187
include/pipeline/m2_loader.hpp
Normal file
187
include/pipeline/m2_loader.hpp
Normal 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
|
||||
109
include/pipeline/mpq_manager.hpp
Normal file
109
include/pipeline/mpq_manager.hpp
Normal 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
|
||||
136
include/pipeline/terrain_mesh.hpp
Normal file
136
include/pipeline/terrain_mesh.hpp
Normal 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
|
||||
222
include/pipeline/wmo_loader.hpp
Normal file
222
include/pipeline/wmo_loader.hpp
Normal 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
|
||||
52
include/rendering/camera.hpp
Normal file
52
include/rendering/camera.hpp
Normal 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
|
||||
130
include/rendering/camera_controller.hpp
Normal file
130
include/rendering/camera_controller.hpp
Normal 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
|
||||
101
include/rendering/celestial.hpp
Normal file
101
include/rendering/celestial.hpp
Normal 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
|
||||
180
include/rendering/character_renderer.hpp
Normal file
180
include/rendering/character_renderer.hpp
Normal 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
|
||||
95
include/rendering/clouds.hpp
Normal file
95
include/rendering/clouds.hpp
Normal 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
|
||||
88
include/rendering/frustum.hpp
Normal file
88
include/rendering/frustum.hpp
Normal 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
|
||||
85
include/rendering/lens_flare.hpp
Normal file
85
include/rendering/lens_flare.hpp
Normal 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
|
||||
105
include/rendering/lightning.hpp
Normal file
105
include/rendering/lightning.hpp
Normal 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
|
||||
145
include/rendering/m2_renderer.hpp
Normal file
145
include/rendering/m2_renderer.hpp
Normal 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
|
||||
32
include/rendering/material.hpp
Normal file
32
include/rendering/material.hpp
Normal 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
|
||||
33
include/rendering/mesh.hpp
Normal file
33
include/rendering/mesh.hpp
Normal 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
|
||||
54
include/rendering/minimap.hpp
Normal file
54
include/rendering/minimap.hpp
Normal 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
|
||||
100
include/rendering/performance_hud.hpp
Normal file
100
include/rendering/performance_hud.hpp
Normal 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
|
||||
174
include/rendering/renderer.hpp
Normal file
174
include/rendering/renderer.hpp
Normal 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
|
||||
27
include/rendering/scene.hpp
Normal file
27
include/rendering/scene.hpp
Normal 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
|
||||
41
include/rendering/shader.hpp
Normal file
41
include/rendering/shader.hpp
Normal 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
|
||||
83
include/rendering/skybox.hpp
Normal file
83
include/rendering/skybox.hpp
Normal 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
|
||||
76
include/rendering/starfield.hpp
Normal file
76
include/rendering/starfield.hpp
Normal 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
|
||||
59
include/rendering/swim_effects.hpp
Normal file
59
include/rendering/swim_effects.hpp
Normal 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
|
||||
270
include/rendering/terrain_manager.hpp
Normal file
270
include/rendering/terrain_manager.hpp
Normal 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
|
||||
193
include/rendering/terrain_renderer.hpp
Normal file
193
include/rendering/terrain_renderer.hpp
Normal 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
|
||||
31
include/rendering/texture.hpp
Normal file
31
include/rendering/texture.hpp
Normal 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
|
||||
123
include/rendering/water_renderer.hpp
Normal file
123
include/rendering/water_renderer.hpp
Normal 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
|
||||
112
include/rendering/weather.hpp
Normal file
112
include/rendering/weather.hpp
Normal 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
|
||||
262
include/rendering/wmo_renderer.hpp
Normal file
262
include/rendering/wmo_renderer.hpp
Normal 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
|
||||
72
include/ui/auth_screen.hpp
Normal file
72
include/ui/auth_screen.hpp
Normal 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
|
||||
66
include/ui/character_screen.hpp
Normal file
66
include/ui/character_screen.hpp
Normal 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
104
include/ui/game_screen.hpp
Normal 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
|
||||
56
include/ui/inventory_screen.hpp
Normal file
56
include/ui/inventory_screen.hpp
Normal 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
|
||||
73
include/ui/realm_screen.hpp
Normal file
73
include/ui/realm_screen.hpp
Normal 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
84
include/ui/ui_manager.hpp
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue