mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-15 08:53:51 +00:00
change weapon for ranged skills
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
This commit is contained in:
parent
fe1dc5e02b
commit
6ba0edc2fb
6 changed files with 176 additions and 4 deletions
|
|
@ -74,6 +74,10 @@ public:
|
|||
bool isWeaponsSheathed() const { return weaponsSheathed_; }
|
||||
void toggleWeaponsSheathed() { weaponsSheathed_ = !weaponsSheathed_; }
|
||||
|
||||
// Ranged weapon swap: temporarily show ranged weapon in right hand
|
||||
void showRangedWeapon(bool show);
|
||||
bool isShowingRanged() const { return showingRanged_; }
|
||||
|
||||
// Saved skin state accessors (used by game_screen.cpp for equipment re-compositing)
|
||||
const std::string& getBodySkinPath() const { return bodySkinPath_; }
|
||||
const std::vector<std::string>& getUnderwearPaths() const { return underwearPaths_; }
|
||||
|
|
@ -96,6 +100,7 @@ private:
|
|||
uint32_t cloakTextureSlotIndex_ = 0;
|
||||
|
||||
bool weaponsSheathed_ = false;
|
||||
bool showingRanged_ = false;
|
||||
};
|
||||
|
||||
} // namespace core
|
||||
|
|
|
|||
|
|
@ -943,6 +943,10 @@ public:
|
|||
using MeleeSwingCallback = std::function<void(uint32_t spellId)>;
|
||||
void setMeleeSwingCallback(MeleeSwingCallback cb) { meleeSwingCallback_ = std::move(cb); }
|
||||
|
||||
// Ranged weapon swap callback — show=true: swap to ranged weapon, false: back to melee
|
||||
using RangedWeaponSwapCallback = std::function<void(bool show)>;
|
||||
void setRangedWeaponSwapCallback(RangedWeaponSwapCallback cb) { rangedWeaponSwapCallback_ = std::move(cb); }
|
||||
|
||||
// Spell cast animation callbacks — true=start cast/channel, false=finish/cancel
|
||||
// guid: caster (may be player or another unit), isChannel: channel vs regular cast
|
||||
// castType: DIRECTED (unit target), OMNI (self/no target), AREA (ground AoE)
|
||||
|
|
@ -2399,6 +2403,13 @@ public:
|
|||
auto& knockBackCallbackRef() { return knockBackCallback_; }
|
||||
auto& lootWindowCallbackRef() { return lootWindowCallback_; }
|
||||
auto& meleeSwingCallbackRef() { return meleeSwingCallback_; }
|
||||
auto& rangedWeaponSwapCallbackRef() { return rangedWeaponSwapCallback_; }
|
||||
void suppressNextMeleeSwingAnim() { suppressMeleeSwingAnim_ = true; }
|
||||
bool consumeSuppressMeleeSwingAnim() {
|
||||
bool v = suppressMeleeSwingAnim_;
|
||||
suppressMeleeSwingAnim_ = false;
|
||||
return v;
|
||||
}
|
||||
auto& mountCallbackRef() { return mountCallback_; }
|
||||
auto& npcAggroCallbackRef() { return npcAggroCallback_; }
|
||||
auto& npcDeathCallbackRef() { return npcDeathCallback_; }
|
||||
|
|
@ -3436,6 +3447,8 @@ private:
|
|||
AppearanceChangedCallback appearanceChangedCallback_;
|
||||
GhostStateCallback ghostStateCallback_;
|
||||
MeleeSwingCallback meleeSwingCallback_;
|
||||
RangedWeaponSwapCallback rangedWeaponSwapCallback_;
|
||||
bool suppressMeleeSwingAnim_ = false;
|
||||
// lastMeleeSwingMs_ moved to CombatHandler
|
||||
SpellCastAnimCallback spellCastAnimCallback_;
|
||||
SpellCastFailedCallback spellCastFailedCallback_;
|
||||
|
|
|
|||
|
|
@ -324,6 +324,7 @@ bool AppearanceComposer::loadWeaponM2(const std::string& m2Path, pipeline::M2Mod
|
|||
}
|
||||
|
||||
void AppearanceComposer::loadEquippedWeapons() {
|
||||
showingRanged_ = false;
|
||||
if (!renderer_ || !renderer_->getCharacterRenderer() || !assetManager_ || !assetManager_->isInitialized())
|
||||
return;
|
||||
if (!gameHandler_) return;
|
||||
|
|
@ -354,9 +355,12 @@ void AppearanceComposer::loadEquippedWeapons() {
|
|||
for (const auto& ws : weaponSlots) {
|
||||
charRenderer->detachWeapon(charInstanceId, ws.attachmentId);
|
||||
}
|
||||
charRenderer->detachWeapon(charInstanceId, 1); // ranged may also use right hand
|
||||
return;
|
||||
}
|
||||
|
||||
bool rightHandFilled = false;
|
||||
|
||||
for (const auto& ws : weaponSlots) {
|
||||
const auto& equipSlot = inventory.getEquipSlot(ws.slot);
|
||||
|
||||
|
|
@ -421,8 +425,123 @@ void AppearanceComposer::loadEquippedWeapons() {
|
|||
weaponModel, weaponModelId, texturePath);
|
||||
if (ok) {
|
||||
LOG_INFO("Equipped weapon: ", m2Path, " at attachment ", ws.attachmentId);
|
||||
if (ws.attachmentId == 1) rightHandFilled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// --- RANGED slot (bow, gun, crossbow, thrown) ---
|
||||
// Show ranged weapon in right hand when main hand is empty.
|
||||
const auto& rangedSlot = inventory.getEquipSlot(game::EquipSlot::RANGED);
|
||||
if (!rightHandFilled && !rangedSlot.empty() && rangedSlot.item.displayInfoId != 0) {
|
||||
uint32_t displayInfoId = rangedSlot.item.displayInfoId;
|
||||
int32_t recIdx = displayInfoDbc->findRecordById(displayInfoId);
|
||||
if (recIdx >= 0) {
|
||||
const auto* idiL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("ItemDisplayInfo") : nullptr;
|
||||
std::string modelName = displayInfoDbc->getString(static_cast<uint32_t>(recIdx), idiL ? (*idiL)["LeftModel"] : 1);
|
||||
std::string textureName = displayInfoDbc->getString(static_cast<uint32_t>(recIdx), idiL ? (*idiL)["LeftModelTexture"] : 3);
|
||||
|
||||
if (!modelName.empty()) {
|
||||
std::string modelFile = modelName;
|
||||
{
|
||||
size_t dotPos = modelFile.rfind('.');
|
||||
if (dotPos != std::string::npos) {
|
||||
modelFile = modelFile.substr(0, dotPos) + ".m2";
|
||||
} else {
|
||||
modelFile += ".m2";
|
||||
}
|
||||
}
|
||||
|
||||
std::string m2Path = "Item\\ObjectComponents\\Weapon\\" + modelFile;
|
||||
pipeline::M2Model weaponModel;
|
||||
if (!loadWeaponM2(m2Path, weaponModel)) {
|
||||
m2Path = "Item\\ObjectComponents\\Shield\\" + modelFile;
|
||||
loadWeaponM2(m2Path, weaponModel);
|
||||
}
|
||||
|
||||
if (weaponModel.vertices.size() > 0) {
|
||||
std::string texturePath;
|
||||
if (!textureName.empty()) {
|
||||
texturePath = "Item\\ObjectComponents\\Weapon\\" + textureName + ".blp";
|
||||
if (!assetManager_->fileExists(texturePath)) {
|
||||
texturePath = "Item\\ObjectComponents\\Shield\\" + textureName + ".blp";
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t weaponModelId = entitySpawner_->allocateWeaponModelId();
|
||||
bool ok = charRenderer->attachWeapon(charInstanceId, 1,
|
||||
weaponModel, weaponModelId, texturePath);
|
||||
if (ok) {
|
||||
LOG_INFO("Equipped ranged weapon: ", m2Path, " at attachment 1 (right hand)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppearanceComposer::showRangedWeapon(bool show) {
|
||||
if (show == showingRanged_) return;
|
||||
showingRanged_ = show;
|
||||
|
||||
if (!renderer_ || !renderer_->getCharacterRenderer() || !gameHandler_ || !assetManager_ || !assetManager_->isInitialized())
|
||||
return;
|
||||
|
||||
auto* charRenderer = renderer_->getCharacterRenderer();
|
||||
uint32_t charInstanceId = renderer_->getCharacterInstanceId();
|
||||
if (charInstanceId == 0) return;
|
||||
|
||||
if (!show) {
|
||||
// Swap back to normal melee weapons
|
||||
loadEquippedWeapons();
|
||||
return;
|
||||
}
|
||||
|
||||
auto& inventory = gameHandler_->getInventory();
|
||||
const auto& rangedSlot = inventory.getEquipSlot(game::EquipSlot::RANGED);
|
||||
if (rangedSlot.empty() || rangedSlot.item.displayInfoId == 0) return;
|
||||
|
||||
auto displayInfoDbc = assetManager_->loadDBC("ItemDisplayInfo.dbc");
|
||||
if (!displayInfoDbc) return;
|
||||
|
||||
uint32_t displayInfoId = rangedSlot.item.displayInfoId;
|
||||
int32_t recIdx = displayInfoDbc->findRecordById(displayInfoId);
|
||||
if (recIdx < 0) return;
|
||||
|
||||
const auto* idiL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("ItemDisplayInfo") : nullptr;
|
||||
std::string modelName = displayInfoDbc->getString(static_cast<uint32_t>(recIdx), idiL ? (*idiL)["LeftModel"] : 1);
|
||||
std::string textureName = displayInfoDbc->getString(static_cast<uint32_t>(recIdx), idiL ? (*idiL)["LeftModelTexture"] : 3);
|
||||
if (modelName.empty()) return;
|
||||
|
||||
std::string modelFile = modelName;
|
||||
{
|
||||
size_t dotPos = modelFile.rfind('.');
|
||||
if (dotPos != std::string::npos)
|
||||
modelFile = modelFile.substr(0, dotPos) + ".m2";
|
||||
else
|
||||
modelFile += ".m2";
|
||||
}
|
||||
|
||||
std::string m2Path = "Item\\ObjectComponents\\Weapon\\" + modelFile;
|
||||
pipeline::M2Model weaponModel;
|
||||
if (!loadWeaponM2(m2Path, weaponModel)) {
|
||||
m2Path = "Item\\ObjectComponents\\Shield\\" + modelFile;
|
||||
if (!loadWeaponM2(m2Path, weaponModel)) return;
|
||||
}
|
||||
|
||||
std::string texturePath;
|
||||
if (!textureName.empty()) {
|
||||
texturePath = "Item\\ObjectComponents\\Weapon\\" + textureName + ".blp";
|
||||
if (!assetManager_->fileExists(texturePath))
|
||||
texturePath = "Item\\ObjectComponents\\Shield\\" + textureName + ".blp";
|
||||
}
|
||||
|
||||
// Detach current right-hand weapon and attach ranged weapon
|
||||
charRenderer->detachWeapon(charInstanceId, 1);
|
||||
uint32_t weaponModelId = entitySpawner_->allocateWeaponModelId();
|
||||
bool ok = charRenderer->attachWeapon(charInstanceId, 1, weaponModel, weaponModelId, texturePath);
|
||||
if (ok) {
|
||||
LOG_INFO("Swapped to ranged weapon: ", m2Path, " at attachment 1 (right hand)");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace core
|
||||
|
|
|
|||
|
|
@ -980,14 +980,23 @@ void Application::setState(AppState newState) {
|
|||
if (renderer) {
|
||||
// Ranged auto-attack spells: Auto Shot (75), Shoot (5019), Throw (2764)
|
||||
if (spellId == 75 || spellId == 5019 || spellId == 2764) {
|
||||
if (appearanceComposer_ && !appearanceComposer_->isShowingRanged())
|
||||
appearanceComposer_->showRangedWeapon(true);
|
||||
if (auto* ac = renderer->getAnimationController()) ac->triggerRangedShot();
|
||||
} else if (spellId != 0) {
|
||||
if (appearanceComposer_ && appearanceComposer_->isShowingRanged())
|
||||
appearanceComposer_->showRangedWeapon(false);
|
||||
if (auto* ac = renderer->getAnimationController()) ac->triggerSpecialAttack(spellId);
|
||||
} else {
|
||||
if (appearanceComposer_ && appearanceComposer_->isShowingRanged())
|
||||
appearanceComposer_->showRangedWeapon(false);
|
||||
if (auto* ac = renderer->getAnimationController()) ac->triggerMeleeSwing();
|
||||
}
|
||||
}
|
||||
});
|
||||
gameHandler->setRangedWeaponSwapCallback([this](bool show) {
|
||||
if (appearanceComposer_) appearanceComposer_->showRangedWeapon(show);
|
||||
});
|
||||
gameHandler->setKnockBackCallback([this](float vcos, float vsin, float hspeed, float vspeed) {
|
||||
if (renderer && renderer->getCameraController()) {
|
||||
renderer->getCameraController()->applyKnockBack(vcos, vsin, hspeed, vspeed);
|
||||
|
|
@ -1216,6 +1225,10 @@ void Application::update(float deltaTime) {
|
|||
appearanceComposer_->setWeaponsSheathed(false);
|
||||
appearanceComposer_->loadEquippedWeapons();
|
||||
}
|
||||
// Swap back to melee weapon when auto-attack stops
|
||||
if (!autoAttacking && wasAutoAttacking_ && appearanceComposer_ && appearanceComposer_->isShowingRanged()) {
|
||||
appearanceComposer_->showRangedWeapon(false);
|
||||
}
|
||||
wasAutoAttacking_ = autoAttacking;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -206,14 +206,21 @@ void CombatHandler::startAutoAttack(uint64_t targetGuid) {
|
|||
owner_.dismount();
|
||||
}
|
||||
|
||||
// Client-side melee range gate to avoid starting "swing forever" loops when
|
||||
// Client-side range gate to avoid starting "swing forever" loops when
|
||||
// target is already clearly out of range.
|
||||
if (auto target = owner_.getEntityManager().getEntity(targetGuid)) {
|
||||
float dx = owner_.movementInfoRef().x - target->getLatestX();
|
||||
float dy = owner_.movementInfoRef().y - target->getLatestY();
|
||||
float dz = owner_.movementInfoRef().z - target->getLatestZ();
|
||||
float dist3d = std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (dist3d > 8.0f) {
|
||||
// Use longer range limit when a ranged weapon is equipped
|
||||
const auto& rangedSlot = owner_.getInventory().getEquipSlot(game::EquipSlot::RANGED);
|
||||
bool hasRangedWeapon = !rangedSlot.empty() &&
|
||||
(rangedSlot.item.inventoryType == game::InvType::RANGED_BOW ||
|
||||
rangedSlot.item.inventoryType == game::InvType::RANGED_GUN ||
|
||||
rangedSlot.item.inventoryType == game::InvType::THROWN);
|
||||
float maxRange = hasRangedWeapon ? 40.0f : 8.0f;
|
||||
if (dist3d > maxRange) {
|
||||
if (autoAttackRangeWarnCooldown_ <= 0.0f) {
|
||||
owner_.addSystemChatMessage("Target is too far away.");
|
||||
autoAttackRangeWarnCooldown_ = 1.25f;
|
||||
|
|
@ -443,7 +450,14 @@ void CombatHandler::handleAttackerStateUpdate(network::Packet& packet) {
|
|||
lastMeleeSwingMs_ = static_cast<uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count());
|
||||
if (owner_.meleeSwingCallbackRef()) owner_.meleeSwingCallbackRef()(0);
|
||||
// Skip melee animation if a ranged shot was just triggered from
|
||||
// SMSG_SPELL_GO (Auto Shot / Shoot / Throw). The ranged animation
|
||||
// is already playing; firing the melee callback here would override it.
|
||||
if (owner_.consumeSuppressMeleeSwingAnim()) {
|
||||
LOG_DEBUG("Suppressed melee swing anim — ranged shot already triggered");
|
||||
} else {
|
||||
if (owner_.meleeSwingCallbackRef()) owner_.meleeSwingCallbackRef()(0);
|
||||
}
|
||||
}
|
||||
if (!isPlayerAttacker && owner_.npcSwingCallbackRef()) {
|
||||
owner_.npcSwingCallbackRef()(data.attackerGuid);
|
||||
|
|
|
|||
|
|
@ -1025,8 +1025,16 @@ void SpellHandler::handleSpellGo(network::Packet& packet) {
|
|||
if (!owner_.isProfessionSpell(data.spellId))
|
||||
playSpellCastSound(data.spellId);
|
||||
|
||||
// Instant melee abilities → trigger attack animation
|
||||
// Ranged auto-attack spells (Auto Shot, Shoot, Throw) complete as timed
|
||||
// casts and are NOT classified as instant melee abilities, so trigger the
|
||||
// ranged shot animation explicitly here.
|
||||
uint32_t sid = data.spellId;
|
||||
if (sid == 75 || sid == 5019 || sid == 2764) {
|
||||
if (owner_.meleeSwingCallbackRef()) owner_.meleeSwingCallbackRef()(sid);
|
||||
owner_.suppressNextMeleeSwingAnim();
|
||||
}
|
||||
|
||||
// Instant melee abilities → trigger attack animation
|
||||
bool isMeleeAbility = false;
|
||||
if (!owner_.isProfessionSpell(sid)) {
|
||||
owner_.loadSpellNameCache();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue