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);