mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 07:40:14 +00:00
Add player death handling, race-aware faction hostility, and all-race texture support
- Death screen with "Release Spirit" button sends CMSG_REPOP_REQUEST - Detect player death/resurrection via health updates (VALUES and CREATE) - Faction hostility map now built per-character race instead of hardcoded Human - CharSections.dbc texture lookup enabled for all races (was Human-only) - Fallback texture paths use race folder names instead of hardcoded Human - Player name in unit frame is clickable for self-targeting
This commit is contained in:
parent
046111d037
commit
7436420cd1
9 changed files with 270 additions and 102 deletions
|
|
@ -82,6 +82,7 @@ private:
|
|||
std::string getPlayerModelPath() const;
|
||||
static const char* mapIdToName(uint32_t mapId);
|
||||
void loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float z);
|
||||
void buildFactionHostilityMap(uint8_t playerRace);
|
||||
void spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation);
|
||||
void despawnOnlineCreature(uint64_t guid);
|
||||
void buildCreatureDisplayLookups();
|
||||
|
|
|
|||
|
|
@ -314,6 +314,10 @@ public:
|
|||
uint64_t getPlayerGuid() const { return playerGuid; }
|
||||
void setPlayerGuid(uint64_t guid) { playerGuid = guid; }
|
||||
|
||||
// Player death state
|
||||
bool isPlayerDead() const { return playerDead_; }
|
||||
void releaseSpirit();
|
||||
|
||||
// ---- Phase 4: Group ----
|
||||
void inviteToGroup(const std::string& playerName);
|
||||
void acceptGroupInvite();
|
||||
|
|
@ -693,6 +697,7 @@ private:
|
|||
uint32_t localPlayerHealth_ = 0;
|
||||
uint32_t localPlayerMaxHealth_ = 0;
|
||||
uint32_t localPlayerLevel_ = 1;
|
||||
bool playerDead_ = false;
|
||||
|
||||
struct NpcAggroEntry {
|
||||
uint64_t guid;
|
||||
|
|
|
|||
|
|
@ -164,6 +164,9 @@ enum class Opcode : uint16_t {
|
|||
SMSG_ITEM_QUERY_SINGLE_RESPONSE = 0x058,
|
||||
CMSG_AUTOEQUIP_ITEM = 0x10A,
|
||||
SMSG_INVENTORY_CHANGE_FAILURE = 0x112,
|
||||
|
||||
// ---- Death/Respawn ----
|
||||
CMSG_REPOP_REQUEST = 0x015A,
|
||||
};
|
||||
|
||||
} // namespace game
|
||||
|
|
|
|||
|
|
@ -1268,5 +1268,11 @@ public:
|
|||
static bool parse(network::Packet& packet, ListInventoryData& data);
|
||||
};
|
||||
|
||||
/** CMSG_REPOP_REQUEST packet builder */
|
||||
class RepopRequestPacket {
|
||||
public:
|
||||
static network::Packet build();
|
||||
};
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ private:
|
|||
void renderQuestDetailsWindow(game::GameHandler& gameHandler);
|
||||
void renderVendorWindow(game::GameHandler& gameHandler);
|
||||
void renderTeleporterPanel();
|
||||
void renderDeathScreen(game::GameHandler& gameHandler);
|
||||
void renderEscapeMenu();
|
||||
void renderSettingsWindow();
|
||||
|
||||
|
|
|
|||
|
|
@ -629,96 +629,7 @@ void Application::setupUICallbacks() {
|
|||
loadOnlineWorldTerrain(mapId, x, y, z);
|
||||
});
|
||||
|
||||
// Load faction hostility map from FactionTemplate.dbc + Faction.dbc
|
||||
if (assetManager && assetManager->isInitialized()) {
|
||||
auto ftDbc = assetManager->loadDBC("FactionTemplate.dbc");
|
||||
auto fDbc = assetManager->loadDBC("Faction.dbc");
|
||||
if (ftDbc && ftDbc->isLoaded()) {
|
||||
// Build set of hostile parent faction IDs from Faction.dbc base reputation
|
||||
// Faction.dbc: field 0=ID, fields 13-16=ReputationBase[4], fields 5-8=ReputationRaceMask[4]
|
||||
// Human = race 1 = raceMask bit 0 (0x1)
|
||||
std::unordered_set<uint32_t> hostileParentFactions;
|
||||
if (fDbc && fDbc->isLoaded()) {
|
||||
for (uint32_t i = 0; i < fDbc->getRecordCount(); i++) {
|
||||
uint32_t factionId = fDbc->getUInt32(i, 0);
|
||||
// Check each of the 4 reputation slots for Human race mask
|
||||
for (int slot = 0; slot < 4; slot++) {
|
||||
uint32_t raceMask = fDbc->getUInt32(i, 2 + slot); // ReputationRaceMask[4] at fields 2-5
|
||||
if (raceMask & 0x1) { // Human race bit
|
||||
int32_t baseRep = fDbc->getInt32(i, 10 + slot); // ReputationBase[4] at fields 10-13
|
||||
if (baseRep < 0) {
|
||||
hostileParentFactions.insert(factionId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_INFO("Faction.dbc: ", hostileParentFactions.size(), " factions hostile to Humans");
|
||||
}
|
||||
|
||||
// Get player faction template data
|
||||
uint32_t playerFriendGroup = 0;
|
||||
uint32_t playerEnemyGroup = 0;
|
||||
uint32_t playerFactionId = 0;
|
||||
for (uint32_t i = 0; i < ftDbc->getRecordCount(); i++) {
|
||||
if (ftDbc->getUInt32(i, 0) == 1) { // Human player faction template
|
||||
playerFriendGroup = ftDbc->getUInt32(i, 4) | ftDbc->getUInt32(i, 3);
|
||||
playerEnemyGroup = ftDbc->getUInt32(i, 5);
|
||||
playerFactionId = ftDbc->getUInt32(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Build hostility map for each faction template
|
||||
std::unordered_map<uint32_t, bool> factionMap;
|
||||
for (uint32_t i = 0; i < ftDbc->getRecordCount(); i++) {
|
||||
uint32_t id = ftDbc->getUInt32(i, 0);
|
||||
uint32_t parentFaction = ftDbc->getUInt32(i, 1);
|
||||
uint32_t factionGroup = ftDbc->getUInt32(i, 3);
|
||||
uint32_t friendGroup = ftDbc->getUInt32(i, 4);
|
||||
uint32_t enemyGroup = ftDbc->getUInt32(i, 5);
|
||||
|
||||
// 1. Symmetric group check (WoW's actual hostility formula)
|
||||
bool hostile = (enemyGroup & playerFriendGroup) != 0
|
||||
|| (factionGroup & playerEnemyGroup) != 0;
|
||||
|
||||
// 2. Monster factionGroup bit (8)
|
||||
if (!hostile && (factionGroup & 8) != 0) {
|
||||
hostile = true;
|
||||
}
|
||||
|
||||
// 3. Individual enemy faction IDs (fields 6-9)
|
||||
if (!hostile && playerFactionId > 0) {
|
||||
for (int e = 6; e <= 9; e++) {
|
||||
if (ftDbc->getUInt32(i, e) == playerFactionId) {
|
||||
hostile = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Parent faction base reputation check (Faction.dbc)
|
||||
if (!hostile && parentFaction > 0) {
|
||||
if (hostileParentFactions.count(parentFaction)) {
|
||||
hostile = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. If explicitly friendly (friendGroup includes player), override to non-hostile
|
||||
if (hostile && (friendGroup & playerFriendGroup) != 0) {
|
||||
hostile = false;
|
||||
}
|
||||
|
||||
factionMap[id] = hostile;
|
||||
}
|
||||
|
||||
uint32_t hostileCount = 0;
|
||||
for (const auto& [fid, h] : factionMap) { if (h) hostileCount++; }
|
||||
gameHandler->setFactionHostileMap(std::move(factionMap));
|
||||
LOG_INFO("Loaded faction hostility: ", hostileCount, "/", ftDbc->getRecordCount(),
|
||||
" hostile (playerFriendGroup=0x", std::hex, playerFriendGroup, std::dec, ")");
|
||||
}
|
||||
}
|
||||
// Faction hostility map is built in buildFactionHostilityMap() when character enters world
|
||||
|
||||
// Creature spawn callback (online mode) - spawn creature models
|
||||
gameHandler->setCreatureSpawnCallback([this](uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation) {
|
||||
|
|
@ -850,16 +761,30 @@ void Application::spawnPlayerCharacter() {
|
|||
LOG_INFO(" Texture ", ti, ": type=", tex.type, " name='", tex.filename, "'");
|
||||
}
|
||||
|
||||
// Look up underwear textures from CharSections.dbc (humans only for now)
|
||||
bool useCharSections = (spRace_ == game::Race::HUMAN);
|
||||
// Look up textures from CharSections.dbc for all races
|
||||
bool useCharSections = true;
|
||||
uint32_t targetRaceId = static_cast<uint32_t>(spRace_);
|
||||
uint32_t targetSexId = (spGender_ == game::Gender::FEMALE) ? 1u : 0u;
|
||||
std::string bodySkinPath = (spGender_ == game::Gender::FEMALE)
|
||||
? "Character\\Human\\Female\\HumanFemaleSkin00_00.blp"
|
||||
: "Character\\Human\\Male\\HumanMaleSkin00_00.blp";
|
||||
std::string pelvisPath = (spGender_ == game::Gender::FEMALE)
|
||||
? "Character\\Human\\Female\\HumanFemaleNakedPelvisSkin00_00.blp"
|
||||
: "Character\\Human\\Male\\HumanMaleNakedPelvisSkin00_00.blp";
|
||||
|
||||
// Race name for fallback texture paths
|
||||
const char* raceFolderName = "Human";
|
||||
switch (spRace_) {
|
||||
case game::Race::HUMAN: raceFolderName = "Human"; break;
|
||||
case game::Race::ORC: raceFolderName = "Orc"; break;
|
||||
case game::Race::DWARF: raceFolderName = "Dwarf"; break;
|
||||
case game::Race::NIGHT_ELF: raceFolderName = "NightElf"; break;
|
||||
case game::Race::UNDEAD: raceFolderName = "Scourge"; break;
|
||||
case game::Race::TAUREN: raceFolderName = "Tauren"; break;
|
||||
case game::Race::GNOME: raceFolderName = "Gnome"; break;
|
||||
case game::Race::TROLL: raceFolderName = "Troll"; break;
|
||||
case game::Race::BLOOD_ELF: raceFolderName = "BloodElf"; break;
|
||||
case game::Race::DRAENEI: raceFolderName = "Draenei"; break;
|
||||
default: break;
|
||||
}
|
||||
const char* genderFolder = (spGender_ == game::Gender::FEMALE) ? "Female" : "Male";
|
||||
std::string raceGender = std::string(raceFolderName) + genderFolder;
|
||||
std::string bodySkinPath = std::string("Character\\") + raceFolderName + "\\" + genderFolder + "\\" + raceGender + "Skin00_00.blp";
|
||||
std::string pelvisPath = std::string("Character\\") + raceFolderName + "\\" + genderFolder + "\\" + raceGender + "NakedPelvisSkin00_00.blp";
|
||||
std::string faceLowerTexturePath;
|
||||
std::vector<std::string> underwearPaths;
|
||||
|
||||
|
|
@ -955,7 +880,7 @@ void Application::spawnPlayerCharacter() {
|
|||
if (!hairTexturePath.empty()) {
|
||||
tex.filename = hairTexturePath;
|
||||
} else if (tex.filename.empty()) {
|
||||
tex.filename = "Character\\Human\\Hair00_00.blp";
|
||||
tex.filename = std::string("Character\\") + raceFolderName + "\\Hair00_00.blp";
|
||||
}
|
||||
} else if (tex.type == 8 && tex.filename.empty()) {
|
||||
if (!underwearPaths.empty()) {
|
||||
|
|
@ -1819,6 +1744,123 @@ void Application::teleportTo(int presetIndex) {
|
|||
LOG_INFO("Teleport to ", preset.label, " complete");
|
||||
}
|
||||
|
||||
void Application::buildFactionHostilityMap(uint8_t playerRace) {
|
||||
if (!assetManager || !assetManager->isInitialized() || !gameHandler) return;
|
||||
|
||||
auto ftDbc = assetManager->loadDBC("FactionTemplate.dbc");
|
||||
auto fDbc = assetManager->loadDBC("Faction.dbc");
|
||||
if (!ftDbc || !ftDbc->isLoaded()) return;
|
||||
|
||||
// Race enum → race mask bit: race 1=0x1, 2=0x2, 3=0x4, 4=0x8, 5=0x10, 6=0x20, 7=0x40, 8=0x80, 10=0x200, 11=0x400
|
||||
uint32_t playerRaceMask = 0;
|
||||
if (playerRace >= 1 && playerRace <= 8) {
|
||||
playerRaceMask = 1u << (playerRace - 1);
|
||||
} else if (playerRace == 10) {
|
||||
playerRaceMask = 0x200; // Blood Elf
|
||||
} else if (playerRace == 11) {
|
||||
playerRaceMask = 0x400; // Draenei
|
||||
}
|
||||
|
||||
// Race → player faction template ID
|
||||
// Human=1, Orc=2, Dwarf=3, NightElf=4, Undead=5, Tauren=6, Gnome=115, Troll=116, BloodElf=1610, Draenei=1629
|
||||
uint32_t playerFtId = 0;
|
||||
switch (playerRace) {
|
||||
case 1: playerFtId = 1; break; // Human
|
||||
case 2: playerFtId = 2; break; // Orc
|
||||
case 3: playerFtId = 3; break; // Dwarf
|
||||
case 4: playerFtId = 4; break; // Night Elf
|
||||
case 5: playerFtId = 5; break; // Undead
|
||||
case 6: playerFtId = 6; break; // Tauren
|
||||
case 7: playerFtId = 115; break; // Gnome
|
||||
case 8: playerFtId = 116; break; // Troll
|
||||
case 10: playerFtId = 1610; break; // Blood Elf
|
||||
case 11: playerFtId = 1629; break; // Draenei
|
||||
default: playerFtId = 1; break;
|
||||
}
|
||||
|
||||
// Build set of hostile parent faction IDs from Faction.dbc base reputation
|
||||
std::unordered_set<uint32_t> hostileParentFactions;
|
||||
if (fDbc && fDbc->isLoaded()) {
|
||||
for (uint32_t i = 0; i < fDbc->getRecordCount(); i++) {
|
||||
uint32_t factionId = fDbc->getUInt32(i, 0);
|
||||
for (int slot = 0; slot < 4; slot++) {
|
||||
uint32_t raceMask = fDbc->getUInt32(i, 2 + slot); // ReputationRaceMask[4] at fields 2-5
|
||||
if (raceMask & playerRaceMask) {
|
||||
int32_t baseRep = fDbc->getInt32(i, 10 + slot); // ReputationBase[4] at fields 10-13
|
||||
if (baseRep < 0) {
|
||||
hostileParentFactions.insert(factionId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_INFO("Faction.dbc: ", hostileParentFactions.size(), " factions hostile to race ", (int)playerRace);
|
||||
}
|
||||
|
||||
// Get player faction template data
|
||||
uint32_t playerFriendGroup = 0;
|
||||
uint32_t playerEnemyGroup = 0;
|
||||
uint32_t playerFactionId = 0;
|
||||
for (uint32_t i = 0; i < ftDbc->getRecordCount(); i++) {
|
||||
if (ftDbc->getUInt32(i, 0) == playerFtId) {
|
||||
playerFriendGroup = ftDbc->getUInt32(i, 4) | ftDbc->getUInt32(i, 3);
|
||||
playerEnemyGroup = ftDbc->getUInt32(i, 5);
|
||||
playerFactionId = ftDbc->getUInt32(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Build hostility map for each faction template
|
||||
std::unordered_map<uint32_t, bool> factionMap;
|
||||
for (uint32_t i = 0; i < ftDbc->getRecordCount(); i++) {
|
||||
uint32_t id = ftDbc->getUInt32(i, 0);
|
||||
uint32_t parentFaction = ftDbc->getUInt32(i, 1);
|
||||
uint32_t factionGroup = ftDbc->getUInt32(i, 3);
|
||||
uint32_t friendGroup = ftDbc->getUInt32(i, 4);
|
||||
uint32_t enemyGroup = ftDbc->getUInt32(i, 5);
|
||||
|
||||
// 1. Symmetric group check
|
||||
bool hostile = (enemyGroup & playerFriendGroup) != 0
|
||||
|| (factionGroup & playerEnemyGroup) != 0;
|
||||
|
||||
// 2. Monster factionGroup bit (8)
|
||||
if (!hostile && (factionGroup & 8) != 0) {
|
||||
hostile = true;
|
||||
}
|
||||
|
||||
// 3. Individual enemy faction IDs (fields 6-9)
|
||||
if (!hostile && playerFactionId > 0) {
|
||||
for (int e = 6; e <= 9; e++) {
|
||||
if (ftDbc->getUInt32(i, e) == playerFactionId) {
|
||||
hostile = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Parent faction base reputation check (Faction.dbc)
|
||||
if (!hostile && parentFaction > 0) {
|
||||
if (hostileParentFactions.count(parentFaction)) {
|
||||
hostile = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. If explicitly friendly (friendGroup includes player), override to non-hostile
|
||||
if (hostile && (friendGroup & playerFriendGroup) != 0) {
|
||||
hostile = false;
|
||||
}
|
||||
|
||||
factionMap[id] = hostile;
|
||||
}
|
||||
|
||||
uint32_t hostileCount = 0;
|
||||
for (const auto& [fid, h] : factionMap) { if (h) hostileCount++; }
|
||||
gameHandler->setFactionHostileMap(std::move(factionMap));
|
||||
LOG_INFO("Faction hostility for race ", (int)playerRace, " (FT ", playerFtId, "): ",
|
||||
hostileCount, "/", ftDbc->getRecordCount(),
|
||||
" hostile (friendGroup=0x", std::hex, playerFriendGroup, ", enemyGroup=0x", playerEnemyGroup, std::dec, ")");
|
||||
}
|
||||
|
||||
void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float z) {
|
||||
if (!renderer || !assetManager || !assetManager->isInitialized()) {
|
||||
LOG_WARNING("Cannot load online terrain: renderer or assets not ready");
|
||||
|
|
@ -1884,6 +1926,14 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
|
||||
showProgress("Loading character model...", 0.05f);
|
||||
|
||||
// Build faction hostility map for this character's race
|
||||
if (gameHandler) {
|
||||
const game::Character* activeChar = gameHandler->getActiveCharacter();
|
||||
if (activeChar) {
|
||||
buildFactionHostilityMap(static_cast<uint8_t>(activeChar->race));
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn player model for online mode
|
||||
if (gameHandler) {
|
||||
const game::Character* activeChar = gameHandler->getActiveCharacter();
|
||||
|
|
|
|||
|
|
@ -2562,7 +2562,14 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
auto unit = std::static_pointer_cast<Unit>(entity);
|
||||
for (const auto& [key, val] : block.fields) {
|
||||
switch (key) {
|
||||
case 24: unit->setHealth(val); break;
|
||||
case 24:
|
||||
unit->setHealth(val);
|
||||
// Detect dead player on login
|
||||
if (block.guid == playerGuid && val == 0) {
|
||||
playerDead_ = true;
|
||||
LOG_INFO("Player logged in dead");
|
||||
}
|
||||
break;
|
||||
case 25: unit->setPower(val); break;
|
||||
case 32: unit->setMaxHealth(val); break;
|
||||
case 33: unit->setMaxPower(val); break;
|
||||
|
|
@ -2659,11 +2666,22 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
|||
if (block.guid == autoAttackTarget) {
|
||||
stopAutoAttack();
|
||||
}
|
||||
// Player death
|
||||
if (block.guid == playerGuid) {
|
||||
playerDead_ = true;
|
||||
stopAutoAttack();
|
||||
LOG_INFO("Player died!");
|
||||
}
|
||||
// Trigger death animation for NPC units
|
||||
if (entity->getType() == ObjectType::UNIT && npcDeathCallback_) {
|
||||
npcDeathCallback_(block.guid);
|
||||
}
|
||||
} else if (oldHealth == 0 && val > 0) {
|
||||
// Player resurrection
|
||||
if (block.guid == playerGuid) {
|
||||
playerDead_ = false;
|
||||
LOG_INFO("Player resurrected!");
|
||||
}
|
||||
// Respawn: health went from 0 to >0, reset animation
|
||||
if (entity->getType() == ObjectType::UNIT && npcRespawnCallback_) {
|
||||
npcRespawnCallback_(block.guid);
|
||||
|
|
@ -2948,6 +2966,15 @@ std::shared_ptr<Entity> GameHandler::getTarget() const {
|
|||
return entityManager.getEntity(targetGuid);
|
||||
}
|
||||
|
||||
void GameHandler::releaseSpirit() {
|
||||
if (!playerDead_) return;
|
||||
if (socket && state == WorldState::IN_WORLD) {
|
||||
auto packet = RepopRequestPacket::build();
|
||||
socket->send(packet);
|
||||
LOG_INFO("Sent CMSG_REPOP_REQUEST (Release Spirit)");
|
||||
}
|
||||
}
|
||||
|
||||
void GameHandler::tabTarget(float playerX, float playerY, float playerZ) {
|
||||
// Rebuild cycle list if stale
|
||||
if (tabCycleStale) {
|
||||
|
|
|
|||
|
|
@ -2112,5 +2112,15 @@ bool ListInventoryParser::parse(network::Packet& packet, ListInventoryData& data
|
|||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Death/Respawn
|
||||
// ============================================================
|
||||
|
||||
network::Packet RepopRequestPacket::build() {
|
||||
network::Packet packet(static_cast<uint16_t>(Opcode::CMSG_REPOP_REQUEST));
|
||||
packet.writeUInt8(0); // auto-release flag (0 = manual)
|
||||
return packet;
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
} // namespace wowee
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
renderGossipWindow(gameHandler);
|
||||
renderQuestDetailsWindow(gameHandler);
|
||||
renderVendorWindow(gameHandler);
|
||||
renderDeathScreen(gameHandler);
|
||||
renderEscapeMenu();
|
||||
renderSettingsWindow();
|
||||
|
||||
|
|
@ -662,8 +663,12 @@ void GameScreen::renderPlayerFrame(game::GameHandler& gameHandler) {
|
|||
playerHp = playerMaxHp;
|
||||
}
|
||||
|
||||
// Name in green (friendly player color)
|
||||
ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), "%s", playerName.c_str());
|
||||
// Name in green (friendly player color) — clickable for self-target
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 1.0f, 0.3f, 1.0f));
|
||||
if (ImGui::Selectable(playerName.c_str(), false, 0, ImVec2(0, 0))) {
|
||||
gameHandler.setTarget(gameHandler.getPlayerGuid());
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Lv %u", playerLevel);
|
||||
|
||||
|
|
@ -2276,6 +2281,66 @@ void GameScreen::renderEscapeMenu() {
|
|||
ImGui::End();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Death Screen
|
||||
// ============================================================
|
||||
|
||||
void GameScreen::renderDeathScreen(game::GameHandler& gameHandler) {
|
||||
if (!gameHandler.isPlayerDead()) return;
|
||||
|
||||
auto* window = core::Application::getInstance().getWindow();
|
||||
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
||||
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
||||
|
||||
// Dark red overlay covering the whole screen
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowSize(ImVec2(screenW, screenH));
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.15f, 0.0f, 0.0f, 0.45f));
|
||||
ImGui::Begin("##DeathOverlay", nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing);
|
||||
ImGui::End();
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// "Release Spirit" dialog centered on screen
|
||||
float dlgW = 280.0f;
|
||||
float dlgH = 100.0f;
|
||||
ImGui::SetNextWindowPos(ImVec2(screenW / 2 - dlgW / 2, screenH * 0.35f), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(dlgW, dlgH), ImGuiCond_Always);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.1f, 0.0f, 0.0f, 0.9f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.6f, 0.1f, 0.1f, 1.0f));
|
||||
|
||||
if (ImGui::Begin("##DeathDialog", nullptr,
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar)) {
|
||||
|
||||
ImGui::Spacing();
|
||||
// Center "You are dead." text
|
||||
const char* deathText = "You are dead.";
|
||||
float textW = ImGui::CalcTextSize(deathText).x;
|
||||
ImGui::SetCursorPosX((dlgW - textW) / 2);
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.2f, 0.2f, 1.0f), "%s", deathText);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Center the Release Spirit button
|
||||
float btnW = 180.0f;
|
||||
ImGui::SetCursorPosX((dlgW - btnW) / 2);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.1f, 0.1f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.15f, 0.15f, 1.0f));
|
||||
if (ImGui::Button("Release Spirit", ImVec2(btnW, 30))) {
|
||||
gameHandler.releaseSpirit();
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Settings Window
|
||||
// ============================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue