change weapon for ranged skills

Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
This commit is contained in:
Pavel Okhlopkov 2026-04-10 23:01:16 +03:00
parent fe1dc5e02b
commit 6ba0edc2fb
6 changed files with 176 additions and 4 deletions

View file

@ -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

View file

@ -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;
}