openmw-tes3coop/apps/openmw/mwmp/LocalActor.cpp
David Cernat 20caea083a [Client] Use correct count for items in equipment packets
Previously, throwing weapon sync was completely broken for players, as the count for their equipped throwing weapons was never set and – as a result – defaulted to a count of 1 on other clients. As a result, any time a player threw a dart, they would then appear as having switched to hand-to-hand for other players.

Moreover, the count of equipped items was mistakenly based on the total count of items with that refId in the inventory. As a result, if – for example – I equipped 1 Daedric Longsword and had 4 others in my inventory, my DedicatedPlayer on other clients would equip a Daedric Longsword with a count of 5. If I was overencumbered by having that many Daedric Longswords on me and then dropped 4 of them, allowing myself to move again, my DedicatedPlayer would still walk around with 5 Daedric Longswords and lack animations due to still being overencumbered on the other clients.

These problems were less prevalent for actors, but their equipment updating code has also been changed to match that of players.
2018-03-12 23:31:37 +02:00

264 lines
8 KiB
C++

#include <components/openmw-mp/Log.hpp>
#include "../mwbase/environment.hpp"
#include "../mwmechanics/mechanicsmanagerimp.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwrender/animation.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/worldimp.hpp"
#include "LocalActor.hpp"
#include "Main.hpp"
#include "Networking.hpp"
#include "ActorList.hpp"
#include "MechanicsHelper.hpp"
using namespace mwmp;
using namespace std;
LocalActor::LocalActor()
{
posWasChanged = false;
equipmentChanged = false;
wasRunning = false;
wasSneaking = false;
wasForceJumping = false;
wasForceMoveJumping = false;
wasFlying = false;
attack.type = Attack::MELEE;
attack.shouldSend = false;
attack.instant = false;
creatureStats.mDead = false;
}
LocalActor::~LocalActor()
{
}
void LocalActor::update(bool forceUpdate)
{
updateStatsDynamic(forceUpdate);
updateEquipment(forceUpdate);
if (forceUpdate || !creatureStats.mDead)
{
updatePosition(forceUpdate);
updateAnimFlags(forceUpdate);
updateAnimPlay();
updateSpeech();
updateAttack();
}
}
void LocalActor::updateCell()
{
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_ACTOR_CELL_CHANGE about %s-%i-%i to server",
refId.c_str(), refNumIndex, mpNum);
LOG_APPEND(Log::LOG_INFO, "- Moved from %s to %s", cell.getDescription().c_str(), ptr.getCell()->getCell()->getDescription().c_str());
cell = *ptr.getCell()->getCell();
position = ptr.getRefData().getPosition();
mwmp::Main::get().getNetworking()->getActorList()->addCellChangeActor(*this);
}
void LocalActor::updatePosition(bool forceUpdate)
{
bool posIsChanging = (direction.pos[0] != 0 || direction.pos[1] != 0 || direction.pos[2] != 0 ||
direction.rot[0] != 0 || direction.rot[1] != 0 || direction.rot[2] != 0);
if (forceUpdate || posIsChanging || posWasChanged)
{
posWasChanged = posIsChanging;
position = ptr.getRefData().getPosition();
mwmp::Main::get().getNetworking()->getActorList()->addPositionActor(*this);
}
}
void LocalActor::updateAnimFlags(bool forceUpdate)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
MWMechanics::CreatureStats ptrCreatureStats = ptr.getClass().getCreatureStats(ptr);
using namespace MWMechanics;
bool isRunning = ptrCreatureStats.getMovementFlag(CreatureStats::Flag_Run);
bool isSneaking = ptrCreatureStats.getMovementFlag(CreatureStats::Flag_Sneak);
bool isForceJumping = ptrCreatureStats.getMovementFlag(CreatureStats::Flag_ForceJump);
bool isForceMoveJumping = ptrCreatureStats.getMovementFlag(CreatureStats::Flag_ForceMoveJump);
isFlying = world->isFlying(ptr);
MWMechanics::DrawState_ currentDrawState = ptr.getClass().getCreatureStats(ptr).getDrawState();
if (wasRunning != isRunning || wasSneaking != isSneaking ||
wasForceJumping != isForceJumping || wasForceMoveJumping != isForceMoveJumping ||
lastDrawState != currentDrawState || wasFlying != isFlying ||
forceUpdate)
{
wasRunning = isRunning;
wasSneaking = isSneaking;
wasForceJumping = isForceJumping;
wasForceMoveJumping = isForceMoveJumping;
lastDrawState = currentDrawState;
wasFlying = isFlying;
movementFlags = 0;
#define __SETFLAG(flag, value) (value) ? (movementFlags | flag) : (movementFlags & ~flag)
movementFlags = __SETFLAG(CreatureStats::Flag_Sneak, isSneaking);
movementFlags = __SETFLAG(CreatureStats::Flag_Run, isRunning);
movementFlags = __SETFLAG(CreatureStats::Flag_ForceJump, isForceJumping);
movementFlags = __SETFLAG(CreatureStats::Flag_ForceMoveJump, isForceMoveJumping);
#undef __SETFLAG
drawState = currentDrawState;
mwmp::Main::get().getNetworking()->getActorList()->addAnimFlagsActor(*this);
}
}
void LocalActor::updateAnimPlay()
{
if (!animation.groupname.empty())
{
mwmp::Main::get().getNetworking()->getActorList()->addAnimPlayActor(*this);
animation.groupname.clear();
}
}
void LocalActor::updateSpeech()
{
if (!sound.empty())
{
mwmp::Main::get().getNetworking()->getActorList()->addSpeechActor(*this);
sound.clear();
}
}
void LocalActor::updateStatsDynamic(bool forceUpdate)
{
MWMechanics::CreatureStats *ptrCreatureStats = &ptr.getClass().getCreatureStats(ptr);
MWMechanics::DynamicStat<float> health(ptrCreatureStats->getHealth());
MWMechanics::DynamicStat<float> magicka(ptrCreatureStats->getMagicka());
MWMechanics::DynamicStat<float> fatigue(ptrCreatureStats->getFatigue());
// Update stats when they become 0 or they have changed enough
//
// Also check for an oldHealth of 0 changing to something else for resurrected NPCs
auto needUpdate = [](MWMechanics::DynamicStat<float> &oldVal, MWMechanics::DynamicStat<float> &newVal, int limit) {
return oldVal != newVal && (newVal.getCurrent() == 0 || oldVal.getCurrent() == 0
|| abs(oldVal.getCurrent() - newVal.getCurrent()) >= limit);
};
if (forceUpdate || needUpdate(oldHealth, health, 3) || needUpdate(oldMagicka, magicka, 7) ||
needUpdate(oldFatigue, fatigue, 7))
{
oldHealth = health;
oldMagicka = magicka;
oldFatigue = fatigue;
health.writeState(creatureStats.mDynamic[0]);
magicka.writeState(creatureStats.mDynamic[1]);
fatigue.writeState(creatureStats.mDynamic[2]);
creatureStats.mDead = ptrCreatureStats->isDead();
mwmp::Main::get().getNetworking()->getActorList()->addStatsDynamicActor(*this);
}
}
void LocalActor::updateEquipment(bool forceUpdate)
{
if (!ptr.getClass().hasInventoryStore(ptr))
return;
if (forceUpdate)
equipmentChanged = true;
MWWorld::InventoryStore &invStore = ptr.getClass().getInventoryStore(ptr);
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; slot++)
{
auto &item = equipedItems[slot];
MWWorld::ContainerStoreIterator it = invStore.getSlot(slot);
if (it != invStore.end())
{
auto &cellRef = it->getCellRef();
if (!::Misc::StringUtils::ciEqual(cellRef.getRefId(), item.refId))
{
equipmentChanged = true;
item.refId = cellRef.getRefId();
item.charge = cellRef.getCharge();
item.enchantmentCharge = it->getCellRef().getEnchantmentCharge();
item.count = it->getRefData().getCount();
}
}
else if (!item.refId.empty())
{
equipmentChanged = true;
item.refId = "";
item.count = 0;
item.charge = 0;
item.enchantmentCharge = -1;
}
}
if (equipmentChanged)
{
mwmp::Main::get().getNetworking()->getActorList()->addEquipmentActor(*this);
equipmentChanged = false;
}
}
void LocalActor::updateAttack()
{
if (attack.shouldSend)
{
if (attack.type == Attack::MAGIC)
{
MWMechanics::CreatureStats &attackerStats = ptr.getClass().getCreatureStats(ptr);
attack.spellId = attackerStats.getSpells().getSelectedSpell();
if (attack.pressed)
attack.success = MechanicsHelper::getSpellSuccess(attack.spellId, ptr);
}
mwmp::Main::get().getNetworking()->getActorList()->addAttackActor(*this);
attack.shouldSend = false;
}
}
MWWorld::Ptr LocalActor::getPtr()
{
return ptr;
}
void LocalActor::setPtr(const MWWorld::Ptr& newPtr)
{
ptr = newPtr;
refId = ptr.getCellRef().getRefId();
refNumIndex = ptr.getCellRef().getRefNum().mIndex;
mpNum = ptr.getCellRef().getMpNum();
lastDrawState = ptr.getClass().getCreatureStats(ptr).getDrawState();
oldHealth = ptr.getClass().getCreatureStats(ptr).getHealth();
oldMagicka = ptr.getClass().getCreatureStats(ptr).getMagicka();
oldFatigue = ptr.getClass().getCreatureStats(ptr).getFatigue();
}