mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-24 00:00:13 +00:00
feat: profession crafting improvements and combat sound fixes
- Suppress spell sounds for profession/tradeskill spells (crafting is silent) - Add craft quantity UI to profession trainer: recipe selector, quantity input, Create button, and Stop button for active queue - Known recipes show Create button to cast directly from trainer window - Craft queue auto-recasts on CREATE_ITEM completion, cancels on failure - Fix missing combat sounds: player spell impacts on enemies, enemy spell cast sounds targeting player, instant melee ability weapon sounds
This commit is contained in:
parent
502d506a44
commit
7b03d5363b
3 changed files with 278 additions and 59 deletions
|
|
@ -2091,18 +2091,20 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) {
|
|||
std::string cmd = buf.substr(1, sp - 1);
|
||||
for (char& c : cmd) c = std::tolower(c);
|
||||
int detected = -1;
|
||||
bool isReply = false;
|
||||
if (cmd == "s" || cmd == "say") detected = 0;
|
||||
else if (cmd == "y" || cmd == "yell" || cmd == "shout") detected = 1;
|
||||
else if (cmd == "p" || cmd == "party") detected = 2;
|
||||
else if (cmd == "g" || cmd == "guild") detected = 3;
|
||||
else if (cmd == "w" || cmd == "whisper" || cmd == "tell" || cmd == "t") detected = 4;
|
||||
else if (cmd == "r" || cmd == "reply") { detected = 4; isReply = true; }
|
||||
else if (cmd == "raid" || cmd == "rsay" || cmd == "ra") detected = 5;
|
||||
else if (cmd == "o" || cmd == "officer" || cmd == "osay") detected = 6;
|
||||
else if (cmd == "bg" || cmd == "battleground") detected = 7;
|
||||
else if (cmd == "rw" || cmd == "raidwarning") detected = 8;
|
||||
else if (cmd == "i" || cmd == "instance") detected = 9;
|
||||
else if (cmd.size() == 1 && cmd[0] >= '1' && cmd[0] <= '9') detected = 10; // /1, /2 etc.
|
||||
if (detected >= 0 && (selectedChatType != detected || detected == 10)) {
|
||||
if (detected >= 0 && (selectedChatType != detected || detected == 10 || isReply)) {
|
||||
// For channel shortcuts, also update selectedChannelIdx
|
||||
if (detected == 10) {
|
||||
int chanIdx = cmd[0] - '1'; // /1 -> index 0, /2 -> index 1, etc.
|
||||
|
|
@ -2114,8 +2116,16 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) {
|
|||
selectedChatType = detected;
|
||||
// Strip the prefix, keep only the message part
|
||||
std::string remaining = buf.substr(sp + 1);
|
||||
// For whisper, first word after /w is the target
|
||||
if (detected == 4) {
|
||||
// /r reply: pre-fill whisper target from last whisper sender
|
||||
if (detected == 4 && isReply) {
|
||||
std::string lastSender = gameHandler.getLastWhisperSender();
|
||||
if (!lastSender.empty()) {
|
||||
strncpy(whisperTargetBuffer, lastSender.c_str(), sizeof(whisperTargetBuffer) - 1);
|
||||
whisperTargetBuffer[sizeof(whisperTargetBuffer) - 1] = '\0';
|
||||
}
|
||||
// remaining is the message — don't extract a target from it
|
||||
} else if (detected == 4) {
|
||||
// For whisper, first word after /w is the target
|
||||
size_t msgStart = remaining.find(' ');
|
||||
if (msgStart != std::string::npos) {
|
||||
std::string wTarget = remaining.substr(0, msgStart);
|
||||
|
|
@ -2576,6 +2586,8 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
|
|||
uint64_t closestHostileUnitGuid = 0;
|
||||
float closestQuestGoT = 1e30f;
|
||||
uint64_t closestQuestGoGuid = 0;
|
||||
float closestGoT = 1e30f;
|
||||
uint64_t closestGoGuid = 0;
|
||||
const uint64_t myGuid = gameHandler.getPlayerGuid();
|
||||
for (const auto& [guid, entity] : gameHandler.getEntityManager().getEntities()) {
|
||||
auto t = entity->getType();
|
||||
|
|
@ -2598,16 +2610,8 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
|
|||
heightOffset = 0.3f;
|
||||
}
|
||||
} else if (t == game::ObjectType::GAMEOBJECT) {
|
||||
// For GOs with no renderer instance yet, use a tight fallback
|
||||
// sphere so invisible/unloaded doodads aren't accidentally clicked.
|
||||
hitRadius = 1.2f;
|
||||
heightOffset = 1.0f;
|
||||
// Quest objective GOs should be easier to click.
|
||||
auto go = std::static_pointer_cast<game::GameObject>(entity);
|
||||
if (questObjectiveGoEntries.count(go->getEntry())) {
|
||||
hitRadius = 2.2f;
|
||||
heightOffset = 1.2f;
|
||||
}
|
||||
hitRadius = 2.5f;
|
||||
heightOffset = 1.2f;
|
||||
}
|
||||
hitCenter = core::coords::canonicalToRender(
|
||||
glm::vec3(entity->getX(), entity->getY(), entity->getZ()));
|
||||
|
|
@ -2626,12 +2630,18 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
|
|||
closestHostileUnitGuid = guid;
|
||||
}
|
||||
}
|
||||
if (t == game::ObjectType::GAMEOBJECT && !questObjectiveGoEntries.empty()) {
|
||||
auto go = std::static_pointer_cast<game::GameObject>(entity);
|
||||
if (questObjectiveGoEntries.count(go->getEntry())) {
|
||||
if (hitT < closestQuestGoT) {
|
||||
closestQuestGoT = hitT;
|
||||
closestQuestGoGuid = guid;
|
||||
if (t == game::ObjectType::GAMEOBJECT) {
|
||||
if (hitT < closestGoT) {
|
||||
closestGoT = hitT;
|
||||
closestGoGuid = guid;
|
||||
}
|
||||
if (!questObjectiveGoEntries.empty()) {
|
||||
auto go = std::static_pointer_cast<game::GameObject>(entity);
|
||||
if (questObjectiveGoEntries.count(go->getEntry())) {
|
||||
if (hitT < closestQuestGoT) {
|
||||
closestQuestGoT = hitT;
|
||||
closestQuestGoGuid = guid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2643,12 +2653,23 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
|
|||
}
|
||||
}
|
||||
|
||||
// Prefer quest objective GOs over hostile monsters when both are hittable.
|
||||
// Priority: quest GO > closer of (GO, hostile unit) > closest anything.
|
||||
if (closestQuestGoGuid != 0) {
|
||||
closestGuid = closestQuestGoGuid;
|
||||
closestType = game::ObjectType::GAMEOBJECT;
|
||||
} else if (closestGoGuid != 0 && closestHostileUnitGuid != 0) {
|
||||
// Both a GO and hostile unit were hit — prefer whichever is closer.
|
||||
if (closestGoT <= closestHostileUnitT) {
|
||||
closestGuid = closestGoGuid;
|
||||
closestType = game::ObjectType::GAMEOBJECT;
|
||||
} else {
|
||||
closestGuid = closestHostileUnitGuid;
|
||||
closestType = game::ObjectType::UNIT;
|
||||
}
|
||||
} else if (closestGoGuid != 0) {
|
||||
closestGuid = closestGoGuid;
|
||||
closestType = game::ObjectType::GAMEOBJECT;
|
||||
} else if (closestHostileUnitGuid != 0) {
|
||||
// Prefer hostile monsters over nearby gameobjects/others when right-click picking.
|
||||
closestGuid = closestHostileUnitGuid;
|
||||
closestType = game::ObjectType::UNIT;
|
||||
}
|
||||
|
|
@ -5951,6 +5972,28 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
|||
message = "";
|
||||
isChannelCommand = true;
|
||||
}
|
||||
} else if (cmdLower == "r" || cmdLower == "reply") {
|
||||
switchChatType = 4;
|
||||
std::string lastSender = gameHandler.getLastWhisperSender();
|
||||
if (lastSender.empty()) {
|
||||
game::MessageChatData sysMsg;
|
||||
sysMsg.type = game::ChatType::SYSTEM;
|
||||
sysMsg.language = game::ChatLanguage::UNIVERSAL;
|
||||
sysMsg.message = "No one has whispered you yet.";
|
||||
gameHandler.addLocalChatMessage(sysMsg);
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
target = lastSender;
|
||||
strncpy(whisperTargetBuffer, target.c_str(), sizeof(whisperTargetBuffer) - 1);
|
||||
whisperTargetBuffer[sizeof(whisperTargetBuffer) - 1] = '\0';
|
||||
if (spacePos != std::string::npos) {
|
||||
message = command.substr(spacePos + 1);
|
||||
type = game::ChatType::WHISPER;
|
||||
} else {
|
||||
message = "";
|
||||
}
|
||||
isChannelCommand = true;
|
||||
}
|
||||
|
||||
// Check for emote commands
|
||||
|
|
@ -13624,6 +13667,7 @@ void GameScreen::renderTrainerWindow(game::GameHandler& gameHandler) {
|
|||
}
|
||||
|
||||
const auto& trainer = gameHandler.getTrainerSpells();
|
||||
const bool isProfessionTrainer = (trainer.trainerType == 2);
|
||||
|
||||
// NPC name
|
||||
auto npcEntity = gameHandler.getEntityManager().getEntity(trainer.trainerGuid);
|
||||
|
|
@ -13844,11 +13888,21 @@ void GameScreen::renderTrainerWindow(game::GameHandler& gameHandler) {
|
|||
logCount++;
|
||||
}
|
||||
|
||||
if (!canTrain) ImGui::BeginDisabled();
|
||||
if (ImGui::SmallButton("Train")) {
|
||||
gameHandler.trainSpell(spell->spellId);
|
||||
if (isProfessionTrainer && alreadyKnown) {
|
||||
// Profession trainer: known recipes show "Create" button to craft
|
||||
bool isCasting = gameHandler.isCasting();
|
||||
if (isCasting) ImGui::BeginDisabled();
|
||||
if (ImGui::SmallButton("Create")) {
|
||||
gameHandler.castSpell(spell->spellId, 0);
|
||||
}
|
||||
if (isCasting) ImGui::EndDisabled();
|
||||
} else {
|
||||
if (!canTrain) ImGui::BeginDisabled();
|
||||
if (ImGui::SmallButton("Train")) {
|
||||
gameHandler.trainSpell(spell->spellId);
|
||||
}
|
||||
if (!canTrain) ImGui::EndDisabled();
|
||||
}
|
||||
if (!canTrain) ImGui::EndDisabled();
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
|
@ -13952,6 +14006,79 @@ void GameScreen::renderTrainerWindow(game::GameHandler& gameHandler) {
|
|||
}
|
||||
}
|
||||
if (!hasTrainable) ImGui::EndDisabled();
|
||||
|
||||
// Profession trainer: craft quantity controls
|
||||
if (isProfessionTrainer) {
|
||||
ImGui::Separator();
|
||||
static int craftQuantity = 1;
|
||||
static uint32_t selectedCraftSpell = 0;
|
||||
|
||||
// Show craft queue status if active
|
||||
int queueRemaining = gameHandler.getCraftQueueRemaining();
|
||||
if (queueRemaining > 0) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f),
|
||||
"Crafting... %d remaining", queueRemaining);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Stop")) {
|
||||
gameHandler.cancelCraftQueue();
|
||||
gameHandler.cancelCast();
|
||||
}
|
||||
} else {
|
||||
// Spell selector + quantity input
|
||||
// Build list of known (craftable) spells
|
||||
std::vector<const game::TrainerSpell*> craftable;
|
||||
for (const auto& spell : trainer.spells) {
|
||||
if (isKnown(spell.spellId)) {
|
||||
craftable.push_back(&spell);
|
||||
}
|
||||
}
|
||||
if (!craftable.empty()) {
|
||||
// Combo box for recipe selection
|
||||
const char* previewName = "Select recipe...";
|
||||
for (const auto* sp : craftable) {
|
||||
if (sp->spellId == selectedCraftSpell) {
|
||||
const std::string& n = gameHandler.getSpellName(sp->spellId);
|
||||
if (!n.empty()) previewName = n.c_str();
|
||||
break;
|
||||
}
|
||||
}
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x * 0.55f);
|
||||
if (ImGui::BeginCombo("##CraftSelect", previewName)) {
|
||||
for (const auto* sp : craftable) {
|
||||
const std::string& n = gameHandler.getSpellName(sp->spellId);
|
||||
const std::string& r = gameHandler.getSpellRank(sp->spellId);
|
||||
char label[128];
|
||||
if (!r.empty())
|
||||
snprintf(label, sizeof(label), "%s (%s)##%u",
|
||||
n.empty() ? "???" : n.c_str(), r.c_str(), sp->spellId);
|
||||
else
|
||||
snprintf(label, sizeof(label), "%s##%u",
|
||||
n.empty() ? "???" : n.c_str(), sp->spellId);
|
||||
if (ImGui::Selectable(label, sp->spellId == selectedCraftSpell)) {
|
||||
selectedCraftSpell = sp->spellId;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(50.0f);
|
||||
ImGui::InputInt("##CraftQty", &craftQuantity, 0, 0);
|
||||
if (craftQuantity < 1) craftQuantity = 1;
|
||||
if (craftQuantity > 99) craftQuantity = 99;
|
||||
ImGui::SameLine();
|
||||
bool canCraft = selectedCraftSpell != 0 && !gameHandler.isCasting();
|
||||
if (!canCraft) ImGui::BeginDisabled();
|
||||
if (ImGui::Button("Create")) {
|
||||
if (craftQuantity == 1) {
|
||||
gameHandler.castSpell(selectedCraftSpell, 0);
|
||||
} else {
|
||||
gameHandler.startCraftQueue(selectedCraftSpell, craftQuantity);
|
||||
}
|
||||
}
|
||||
if (!canCraft) ImGui::EndDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue