From 995d20348f26fdfcd6b4bd5d00b25d7841160ad2 Mon Sep 17 00:00:00 2001 From: David Cernat Date: Sun, 23 Sep 2018 02:30:31 +0300 Subject: [PATCH] [General] Always use correct ranged weapon & ammo for ranged attack sync Previously, the player's currently selected weapon was being used in ranged attacks as in the original melee-oriented attack sync, which meant that shooting one type of projectile and then equipping another while the old projectile was still in the air turned the old projectile into the new projectile upon impact. Additionally, avoid running most of the code in MechanicsHelper::assignAttackTarget() for non-hitting melee and ranged attacks. --- apps/openmw/mwclass/creature.cpp | 1 + apps/openmw/mwclass/npc.cpp | 1 + apps/openmw/mwmechanics/combat.cpp | 7 +- apps/openmw/mwmp/MechanicsHelper.cpp | 89 ++++++++++++++----- apps/openmw/mwrender/weaponanimation.cpp | 46 +++++++--- components/openmw-mp/Base/BaseStructs.hpp | 6 +- .../Packets/Actor/PacketActorAttack.cpp | 24 +++-- .../Packets/Player/PacketPlayerAttack.cpp | 22 +++-- 8 files changed, 144 insertions(+), 52 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 37916b69c..c041ccd38 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -307,6 +307,7 @@ namespace MWClass if (localAttack) { + localAttack->isHit = true; localAttack->success = true; localAttack->hitPosition = MechanicsHelper::getPositionFromVector(hitPosition); MechanicsHelper::assignAttackTarget(localAttack, victim); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 9409d5e53..bb5705980 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -635,6 +635,7 @@ namespace MWClass if (localAttack) { + localAttack->isHit = true; localAttack->success = true; localAttack->hitPosition = MechanicsHelper::getPositionFromVector(hitPosition); MechanicsHelper::assignAttackTarget(localAttack, victim); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 52404d2ff..e5c6825dc 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -220,7 +220,7 @@ namespace MWMechanics Ignore projectiles fired by DedicatedPlayers and DedicatedActors If fired by LocalPlayers and LocalActors, get the associated LocalAttack and set its type - to RANGED + to RANGED while also marking it as a hit */ if (mwmp::PlayerList::isDedicatedPlayer(attacker) || mwmp::Main::get().getCellController()->isDedicatedActor(attacker)) return; @@ -228,7 +228,10 @@ namespace MWMechanics mwmp::Attack *localAttack = MechanicsHelper::getLocalAttack(attacker); if (localAttack) + { localAttack->type = mwmp::Attack::RANGED; + localAttack->isHit = true; + } /* End of tes3mp addition */ @@ -329,7 +332,7 @@ namespace MWMechanics appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition, true); if (localAttack) - localAttack->applyProjectileEnchantment = appliedEnchantment; + localAttack->applyAmmoEnchantment = appliedEnchantment; } /* End of tes3mp change (minor) diff --git a/apps/openmw/mwmp/MechanicsHelper.cpp b/apps/openmw/mwmp/MechanicsHelper.cpp index f10db42a7..17935c329 100644 --- a/apps/openmw/mwmp/MechanicsHelper.cpp +++ b/apps/openmw/mwmp/MechanicsHelper.cpp @@ -203,11 +203,12 @@ void MechanicsHelper::assignAttackTarget(Attack* attack, const MWWorld::Ptr& tar void MechanicsHelper::resetAttack(Attack* attack) { + attack->isHit = false; attack->success = false; attack->knockdown = false; attack->block = false; attack->applyWeaponEnchantment = false; - attack->applyProjectileEnchantment = false; + attack->applyAmmoEnchantment = false; attack->hitPosition.pos[0] = attack->hitPosition.pos[1] = attack->hitPosition.pos[2] = 0; attack->target.guid = RakNet::RakNetGUID(); attack->target.refId.clear(); @@ -254,53 +255,85 @@ void MechanicsHelper::processAttack(Attack attack, const MWWorld::Ptr& attacker) victim = controller->getDedicatedActor(attack.target.refNum, attack.target.mpNum)->getPtr(); } - if (attack.type == attack.MELEE || attack.type == attack.RANGED) + if (attack.isHit && (attack.type == attack.MELEE || attack.type == attack.RANGED)) { bool isRanged = attack.type == attack.RANGED; - MWWorld::Ptr weapon; - MWWorld::Ptr projectile; + MWWorld::Ptr weaponPtr; + MWWorld::Ptr ammoPtr; - // Get the weapon used; if using hand-to-hand, the weapon is equal to inv.end() + bool usedTempRangedWeapon = false; + bool usedTempRangedAmmo = false; + + // Get the attacker's current weapon + // + // Note: if using hand-to-hand, the weapon is equal to inv.end() if (attacker.getClass().hasInventoryStore(attacker)) { MWWorld::InventoryStore &inventoryStore = attacker.getClass().getInventoryStore(attacker); MWWorld::ContainerStoreIterator weaponSlot = inventoryStore.getSlot( MWWorld::InventoryStore::Slot_CarriedRight); - weapon = weaponSlot != inventoryStore.end() ? *weaponSlot : MWWorld::Ptr(); + weaponPtr = weaponSlot != inventoryStore.end() ? *weaponSlot : MWWorld::Ptr(); + // Is the currently selected weapon different from the one recorded for this ranged attack? + // If so, try to find the correct one in the attacker's inventory and use it here. If it + // no longer exists, add it back temporarily. if (isRanged) { - // TODO: Fix for when arrows, bolts and throwing weapons have just run out - MWWorld::ContainerStoreIterator projectileSlot = inventoryStore.getSlot( - MWWorld::InventoryStore::Slot_Ammunition); - projectile = projectileSlot != inventoryStore.end() ? *projectileSlot : MWWorld::Ptr(); + if (!weaponPtr || !Misc::StringUtils::ciEqual(weaponPtr.getCellRef().getRefId(), attack.rangedWeaponId)) + { + weaponPtr = inventoryStore.search(attack.rangedWeaponId); + + if (!weaponPtr) + { + weaponPtr = *attacker.getClass().getContainerStore(attacker).add(attack.rangedWeaponId, 1, attacker); + usedTempRangedWeapon = true; + } + } + + if (!attack.rangedAmmoId.empty()) + { + MWWorld::ContainerStoreIterator ammoSlot = inventoryStore.getSlot( + MWWorld::InventoryStore::Slot_Ammunition); + ammoPtr = ammoSlot != inventoryStore.end() ? *ammoSlot : MWWorld::Ptr(); + + if (!ammoPtr || !Misc::StringUtils::ciEqual(ammoPtr.getCellRef().getRefId(), attack.rangedAmmoId)) + { + ammoPtr = inventoryStore.search(attack.rangedAmmoId); + + if (!ammoPtr) + { + ammoPtr = *attacker.getClass().getContainerStore(attacker).add(attack.rangedAmmoId, 1, attacker); + usedTempRangedAmmo = true; + } + } + } } - if (!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name()) - weapon = MWWorld::Ptr(); + if (!weaponPtr.isEmpty() && weaponPtr.getTypeName() != typeid(ESM::Weapon).name()) + weaponPtr = MWWorld::Ptr(); } - if (!weapon.isEmpty()) + if (!weaponPtr.isEmpty()) { - LOG_APPEND(Log::LOG_VERBOSE, "- weapon: %s\n- isRanged: %s\n- applyWeaponEnchantment: %s\n- applyProjectileEnchantment: %s", - weapon.getCellRef().getRefId().c_str(), isRanged ? "true" : "false", attack.applyWeaponEnchantment ? "true" : "false", - attack.applyProjectileEnchantment ? "true" : "false"); + LOG_APPEND(Log::LOG_VERBOSE, "- weapon: %s\n- isRanged: %s\n- applyWeaponEnchantment: %s\n- applyAmmoEnchantment: %s", + weaponPtr.getCellRef().getRefId().c_str(), isRanged ? "true" : "false", attack.applyWeaponEnchantment ? "true" : "false", + attack.applyAmmoEnchantment ? "true" : "false"); if (attack.applyWeaponEnchantment) { MWMechanics::CastSpell cast(attacker, victim, isRanged); cast.mHitPosition = attack.hitPosition.asVec3(); - cast.cast(weapon, false); + cast.cast(weaponPtr, false); } - if (isRanged && !projectile.isEmpty() && attack.applyProjectileEnchantment) + if (isRanged && !ammoPtr.isEmpty() && attack.applyAmmoEnchantment) { MWMechanics::CastSpell cast(attacker, victim, isRanged); cast.mHitPosition = attack.hitPosition.asVec3(); - cast.cast(projectile, false); + cast.cast(ammoPtr, false); } } @@ -308,7 +341,7 @@ void MechanicsHelper::processAttack(Attack attack, const MWWorld::Ptr& attacker) { bool isHealthDamage = true; - if (weapon.isEmpty()) + if (weaponPtr.isEmpty()) { if (attacker.getClass().isBipedal(attacker)) { @@ -318,11 +351,23 @@ void MechanicsHelper::processAttack(Attack attack, const MWWorld::Ptr& attacker) } if (!isRanged) - MWMechanics::blockMeleeAttack(attacker, victim, weapon, attack.damage, 1); + MWMechanics::blockMeleeAttack(attacker, victim, weaponPtr, attack.damage, 1); - victim.getClass().onHit(victim, attack.damage, isHealthDamage, weapon, attacker, attack.hitPosition.asVec3(), + victim.getClass().onHit(victim, attack.damage, isHealthDamage, weaponPtr, attacker, attack.hitPosition.asVec3(), attack.success); } + + // Remove temporary items that may have been added above for ranged attacks + if (isRanged && attacker.getClass().hasInventoryStore(attacker)) + { + MWWorld::InventoryStore &inventoryStore = attacker.getClass().getInventoryStore(attacker); + + if (usedTempRangedWeapon) + inventoryStore.remove(weaponPtr, 1, attacker); + + if (usedTempRangedAmmo) + inventoryStore.remove(ammoPtr, 1, attacker); + } } else if (attack.type == attack.MAGIC) { diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index f3750389b..65b5a9945 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -102,26 +102,37 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor) void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) { + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end()) + return; + if (weapon->getTypeName() != typeid(ESM::Weapon).name()) + return; + /* Start of tes3mp addition If this is an attack by a LocalPlayer or LocalActor, record its attackStrength and - prepare an attack packet for sending + rangedWeaponId and prepare an attack packet for sending. + + Unlike melee attacks, ranged attacks require the weapon and ammo IDs to be recorded + because players and actors can have multiple projectiles in the air at the same time. If it's an attack by a DedicatedPlayer or DedicatedActor, apply the attackStrength - from their latest attack packet + from their latest attack packet. */ mwmp::Attack *localAttack = MechanicsHelper::getLocalAttack(actor); if (localAttack) { localAttack->attackStrength = attackStrength; + localAttack->rangedWeaponId = weapon->getCellRef().getRefId(); localAttack->shouldSend = true; } else { mwmp::Attack *dedicatedAttack = MechanicsHelper::getDedicatedAttack(actor); - + if (dedicatedAttack) attackStrength = dedicatedAttack->attackStrength; } @@ -129,13 +140,6 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) End of tes3mp addition */ - MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); - MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weapon == inv.end()) - return; - if (weapon->getTypeName() != typeid(ESM::Weapon).name()) - return; - // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1)); @@ -147,6 +151,17 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) { + /* + Start of tes3mp addition + + If this is a local attack, clear the rangedAmmoId used for it + */ + if (localAttack) + localAttack->rangedAmmoId = ""; + /* + End of tes3mp addition + */ + // Thrown weapons get detached now osg::Node* weaponNode = getWeaponNode(); if (!weaponNode) @@ -176,6 +191,17 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) if (!mAmmunition) return; + /* + Start of tes3mp addition + + If this is a local attack, record the rangedAmmoId used for it + */ + if (localAttack) + localAttack->rangedAmmoId = ammo->getCellRef().getRefId(); + /* + End of tes3mp addition + */ + osg::ref_ptr ammoNode = mAmmunition->getNode(); osg::NodePathList nodepaths = ammoNode->getParentalNodePaths(); if (nodepaths.empty()) diff --git a/components/openmw-mp/Base/BaseStructs.hpp b/components/openmw-mp/Base/BaseStructs.hpp index bbbdd51d1..958fe165e 100644 --- a/components/openmw-mp/Base/BaseStructs.hpp +++ b/components/openmw-mp/Base/BaseStructs.hpp @@ -78,11 +78,15 @@ namespace mwmp std::string spellId; // id of spell (e.g. "fireball") std::string itemId; + std::string rangedWeaponId; + std::string rangedAmmoId; + ESM::Position hitPosition; float damage; float attackStrength; + bool isHit; bool success; bool block; @@ -90,7 +94,7 @@ namespace mwmp bool instant; bool knockdown; bool applyWeaponEnchantment; - bool applyProjectileEnchantment; + bool applyAmmoEnchantment; bool shouldSend; }; diff --git a/components/openmw-mp/Packets/Actor/PacketActorAttack.cpp b/components/openmw-mp/Packets/Actor/PacketActorAttack.cpp index d15ecfcbe..3612b843d 100644 --- a/components/openmw-mp/Packets/Actor/PacketActorAttack.cpp +++ b/components/openmw-mp/Packets/Actor/PacketActorAttack.cpp @@ -40,19 +40,25 @@ void PacketActorAttack::Actor(BaseActor &actor, bool send) } else { - RW(actor.attack.damage, send); - RW(actor.attack.block, send); - RW(actor.attack.knockdown, send); - RW(actor.attack.applyWeaponEnchantment, send); - + RW(actor.attack.isHit, send); + if (actor.attack.type == mwmp::Attack::RANGED) - { - RW(actor.attack.applyProjectileEnchantment, send); RW(actor.attack.attackStrength, send); - } - if (actor.attack.success || actor.attack.applyWeaponEnchantment || actor.attack.applyProjectileEnchantment) + if (actor.attack.isHit) { + RW(actor.attack.damage, send); + RW(actor.attack.block, send); + RW(actor.attack.knockdown, send); + RW(actor.attack.applyWeaponEnchantment, send); + + if (actor.attack.type == mwmp::Attack::RANGED) + { + RW(actor.attack.applyAmmoEnchantment, send); + RW(actor.attack.rangedWeaponId, send); + RW(actor.attack.rangedAmmoId, send); + } + RW(actor.attack.hitPosition.pos[0], send); RW(actor.attack.hitPosition.pos[1], send); RW(actor.attack.hitPosition.pos[2], send); diff --git a/components/openmw-mp/Packets/Player/PacketPlayerAttack.cpp b/components/openmw-mp/Packets/Player/PacketPlayerAttack.cpp index 70b1bd5a3..31cc86a94 100644 --- a/components/openmw-mp/Packets/Player/PacketPlayerAttack.cpp +++ b/components/openmw-mp/Packets/Player/PacketPlayerAttack.cpp @@ -41,19 +41,25 @@ void PacketPlayerAttack::Packet(RakNet::BitStream *bs, bool send) } else { - RW(player->attack.damage, send); - RW(player->attack.block, send); - RW(player->attack.knockdown, send); - RW(player->attack.applyWeaponEnchantment, send); + RW(player->attack.isHit, send); if (player->attack.type == mwmp::Attack::RANGED) - { - RW(player->attack.applyProjectileEnchantment, send); RW(player->attack.attackStrength, send); - } - if (player->attack.success || player->attack.applyWeaponEnchantment || player->attack.applyProjectileEnchantment) + if (player->attack.isHit) { + RW(player->attack.damage, send); + RW(player->attack.block, send); + RW(player->attack.knockdown, send); + RW(player->attack.applyWeaponEnchantment, send); + + if (player->attack.type == mwmp::Attack::RANGED) + { + RW(player->attack.applyAmmoEnchantment, send); + RW(player->attack.rangedWeaponId, send); + RW(player->attack.rangedAmmoId, send); + } + RW(player->attack.hitPosition.pos[0], send); RW(player->attack.hitPosition.pos[1], send); RW(player->attack.hitPosition.pos[2], send);