diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d74410a18..75b59f395 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -8,10 +8,18 @@ #include #include -#include "../mwmp/DedicatedPlayer.hpp" -#include "../mwmp/Networking.hpp" +/* + Start of tes3mp addition + + Include additional headers for multiplayer purposes +*/ #include "../mwmp/Main.hpp" +#include "../mwmp/Networking.hpp" #include "../mwmp/LocalPlayer.hpp" +#include "../mwmp/DedicatedPlayer.hpp" +/* + End of tes3mp addition +*/ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -553,8 +561,16 @@ namespace MWClass void Npc::hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const { + /* + Start of tes3mp addition + + Ignore hits from DedicatedPlayers + */ if (mwmp::Main::get().getNetworking()->isDedicatedPlayer(ptr)) return; + /* + End of tes3mp addition + */ MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -564,15 +580,15 @@ namespace MWClass MWWorld::InventoryStore &inv = getInventoryStore(ptr); MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::Ptr weapon = ((weaponslot != inv.end()) ? *weaponslot : MWWorld::Ptr()); - if (!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name()) + if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name()) weapon = MWWorld::Ptr(); MWMechanics::applyFatigueLoss(ptr, weapon, attackStrength); const float fCombatDistance = store.find("fCombatDistance")->getFloat(); float dist = fCombatDistance * (!weapon.isEmpty() ? - weapon.get()->mBase->mData.mReach : - store.find("fHandToHandReach")->getFloat()); + weapon.get()->mBase->mData.mReach : + store.find("fHandToHandReach")->getFloat()); // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. std::vector targetActors; @@ -582,16 +598,17 @@ namespace MWClass // TODO: Use second to work out the hit angle std::pair result = world->getHitContact(ptr, dist, targetActors); MWWorld::Ptr victim = result.first; - osg::Vec3f hitPosition(result.second); - if (victim.isEmpty()) // Didn't hit anything + osg::Vec3f hitPosition (result.second); + if(victim.isEmpty()) // Didn't hit anything return; const MWWorld::Class &othercls = victim.getClass(); - if (!othercls.isActor()) // Can't hit non-actors + if(!othercls.isActor()) // Can't hit non-actors return; MWMechanics::CreatureStats &otherstats = othercls.getCreatureStats(victim); - if (otherstats.isDead()) // Can't hit dead actors + if(otherstats.isDead()) // Can't hit dead actors return; + if(ptr == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->setEnemy(victim); @@ -601,6 +618,12 @@ namespace MWClass float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill)); + /* + Start of tes3mp addition + + If the attacker is a LocalPlayer and the target is a DedicatedPlayer, + mark that accordingly in the LocalPlayer data + */ if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) { mwmp::Main::get().getLocalPlayer()->attack.success = true; @@ -608,14 +631,26 @@ namespace MWClass if (dedicatedPlayer != nullptr) mwmp::Main::get().getLocalPlayer()->attack.target = dedicatedPlayer->guid; } + /* + End of tes3mp addition + */ - if(Misc::Rng::roll0to99() >= hitchance) + if (Misc::Rng::roll0to99() >= hitchance) { + /* + Start of tes3mp addition + + If this was a failed attack by the LocalPlayer, send a + PlayerAttack packet about it + */ if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) { mwmp::Main::get().getLocalPlayer()->attack.success = false; mwmp::Main::get().getLocalPlayer()->sendAttack(mwmp::Attack::MELEE); } + /* + End of tes3mp addition + */ othercls.onHit(victim, 0.0f, false, weapon, ptr, osg::Vec3f(), false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); @@ -763,13 +798,24 @@ namespace MWClass float knockdownTerm = stats.getAttribute(ESM::Attribute::Agility).getModified() * gmst.iKnockDownOddsMult->getInt() * 0.01f + gmst.iKnockDownOddsBase->getInt(); + /* + Start of tes3mp change (major) + + If the attacker is a DedicatedPlayer with a successful knockdown, apply the knockdown; + otherwise, use default probability roll + */ mwmp::DedicatedPlayer *dedicatedPlayer = mwmp::Players::getPlayer(attacker); bool isDedicated = dedicatedPlayer != nullptr; - bool _knockdown = false; - if(isDedicated) - _knockdown = dedicatedPlayer->attack.knockdown; + bool isDedicatedKnockdown = false; + if (isDedicated) + isDedicatedKnockdown = dedicatedPlayer->attack.knockdown; - if ((!isDedicated && ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) || _knockdown) + if (isDedicatedKnockdown) + stats.setKnockedDown(true); + else if (ishealth && agilityTerm <= damage && knockdownTerm <= Misc::Rng::roll0to99()) + /* + End of tes3mp change (major) + */ stats.setKnockedDown(true); else stats.setHitRecovery(true); // Is this supposed to always occur? @@ -871,7 +917,14 @@ namespace MWClass MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, attacker); } + /* + Start of tes3mp addition + If the victim was a DedicatedPlayer, send a PlayerAttack packet from LocalPlayer + + If the victim was the LocalPlayer, check whether packets should be sent about + their new dynamic stats and position + */ mwmp::DedicatedPlayer *victimPlayer = mwmp::Players::getPlayer(ptr); if (attacker == MWMechanics::getPlayer() && victimPlayer != nullptr) { @@ -888,13 +941,25 @@ namespace MWClass mwmp::Main::get().getLocalPlayer()->updateStatsDynamic(true); mwmp::Main::get().getLocalPlayer()->updatePosition(true); // fix position after getting damage; } + /* + End of tes3mp addition + */ } boost::shared_ptr Npc::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { + /* + Start of tes3mp addition + + Don't display a dialogue screen for two players interacting with each other + */ if (actor == MWMechanics::getPlayer() && mwmp::Main::get().getNetworking()->isDedicatedPlayer(ptr)) return boost::shared_ptr(new MWWorld::FailedAction("Not implemented.")); + /* + End of tes3mp addition + */ + // player got activated by another NPC if(ptr == MWMechanics::getPlayer()) return boost::shared_ptr(new MWWorld::ActionTalk(actor)); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index cfc86f704..d0d774415 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1109,6 +1109,12 @@ namespace MWMechanics iter->second->getCharacterController()->setActive(inProcessingRange); + /* + Start of tes3mp change (minor) + + Instead of merely updating the player character's mAttackingOrSpell here, + send a PlayerAttack packet from LocalPlayer when applicable + */ if (iter->first == player) { bool state = MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell(); @@ -1117,10 +1123,21 @@ namespace MWMechanics if (dstate == DrawState_Weapon) mwmp::Main::get().getLocalPlayer()->prepareAttack(mwmp::Attack::MELEE, state); } + /* + End of tes3mp change (minor) + */ + + /* + Start of tes3mp addition + If this actor is a DedicatedPlayer, update their mAttackingOrSpell + */ mwmp::DedicatedPlayer *dedicatedPlayer = mwmp::Players::getPlayer(iter->first); if (dedicatedPlayer != NULL) dedicatedPlayer->updateActor(iter->second); + /* + End of tes3mp addition + */ // If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player. diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index e1c44acfd..cc7daf622 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1254,8 +1254,16 @@ bool CharacterController::updateWeaponState() if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) { + /* + Start of tes3mp addition + + Send PlayerAttack packet for this spell + */ if (mPtr == getPlayer()) mwmp::Main::get().getLocalPlayer()->prepareAttack(mwmp::Attack::MAGIC, true); + /* + End of tes3mp addition + */ MWMechanics::CastSpell cast(mPtr, NULL); cast.playSpellCastingEffects(spellid); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index ac6562afb..2c8c7bf99 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -117,7 +117,12 @@ namespace MWMechanics int iBlockMinChance = gmst.find("iBlockMinChance")->getInt(); x = std::min(iBlockMaxChance, std::max(iBlockMinChance, x)); + /* + Start of tes3mp change (major) + Only calculate block chance for blockers who are not DedicatedPlayers + and update block state for LocalPlayer + */ mwmp::DedicatedPlayer *dedicatedPlayer = mwmp::Players::getPlayer(blocker); bool isDedicated = dedicatedPlayer != NULL; if (attacker == MWMechanics::getPlayer()) @@ -128,6 +133,9 @@ namespace MWMechanics { if (attacker == MWMechanics::getPlayer()) mwmp::Main::get().getLocalPlayer()->attack.block = true; + /* + End of tes3mp change (major) + */ // Reduce shield durability by incoming damage int shieldhealth = shield->getClass().getItemHealth(*shield); @@ -183,8 +191,17 @@ namespace MWMechanics void projectileHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, const osg::Vec3f& hitPosition, float attackStrength) { + /* + Start of tes3mp addition + + Ignore projectiles fired by DedicatedPlayers + */ if (mwmp::Main::get().getNetworking()->isDedicatedPlayer(attacker)) return; + /* + End of tes3mp addition + */ + MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::Store &gmst = world->getStore().get(); @@ -202,13 +219,31 @@ namespace MWMechanics int skillValue = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); + /* + Start of tes3mp addition + + Mark this as a successful attack for LocalPlayer unless proven otherwise + */ if (attacker == MWBase::Environment::get().getWorld()->getPlayerPtr()) mwmp::Main::get().getLocalPlayer()->attack.success = true; + /* + End of tes3mp addition + */ if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) { + /* + Start of tes3mp addition + + Mark this as a failed attack for LocalPlayer now that the hit roll + has failed + */ if (attacker == getPlayer()) mwmp::Main::get().getLocalPlayer()->attack.success = false; + /* + End of tes3mp addition + */ + victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false); MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker); return; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 34a20a4b0..b9433feee 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -836,17 +836,18 @@ namespace MWMechanics bool fail = false; - // Major change done by tes3mp: - // - // Instead of checking whether the caster is a player or an NPC, - // first check whether it's the LocalPlayer or a DedicatedPlayer and calculate - // calculate the success chance in clients' LocalPlayer::prepareAttack() - // - // TODO: Make this make sense for NPCs too - // - // TODO: See if LocalPlayer being the target and having godmode on - // can be accounted for like it is in OpenMW's corresponding code - + /* + Start of tes3mp change (major) + + Instead of checking whether the caster is a player or an NPC, + first check whether it's the LocalPlayer or a DedicatedPlayer and calculate + calculate the success chance in clients' LocalPlayer::prepareAttack() + + TODO: Make this make sense for NPCs too + + TODO: See if LocalPlayer being the target and having godmode on + can be accounted for like it is in OpenMW's corresponding code + */ mwmp::DedicatedPlayer *dedicatedPlayer = mwmp::Players::getPlayer(mCaster); bool isDedicated = dedicatedPlayer != NULL; @@ -864,6 +865,9 @@ namespace MWMechanics fail = true; } else if (!(mCaster == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState())) + /* + End of tes3mp change (major) + */ { float successChance = getSpellSuccessChance(spell, mCaster); if (Misc::Rng::roll0to99() >= successChance)