1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-28 20:45:33 +00:00
openmw-tes3mp/apps/openmw/mwmp/LocalActor.cpp
David Cernat 1950748dae [Client] Always use autoEquip on newly initialized LocalActors
Previously, it was possible for a Container packet to clear all equipment slots for an actor without the actor's authority then sending an equipment packet to correct that, due to packet loss or sudden disconnection, leading to actors not wearing any equipment as soon as they acquired a new authority.

This ensures that newly initialized LocalActors will now autoEquip.
2019-12-06 12:21:30 +02:00

312 lines
9.4 KiB
C++

#include <components/openmw-mp/TimedLog.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()
{
hasSentData = false;
posWasChanged = false;
equipmentChanged = false;
wasRunning = false;
wasSneaking = false;
wasForceJumping = false;
wasForceMoveJumping = false;
wasFlying = false;
attack.type = Attack::MELEE;
attack.shouldSend = false;
attack.instant = false;
attack.pressed = false;
cast.type = Cast::REGULAR;
cast.shouldSend = false;
cast.instant = false;
cast.pressed = false;
killer.isPlayer = false;
killer.refId = "";
killer.name = "";
creatureStats.mDead = false;
}
LocalActor::~LocalActor()
{
}
void LocalActor::update(bool forceUpdate)
{
updateStatsDynamic(forceUpdate);
updateEquipment(forceUpdate, false);
if (forceUpdate || !creatureStats.mDead)
{
updatePosition(forceUpdate);
updateAnimFlags(forceUpdate);
updateAnimPlay();
updateSpeech();
updateAttackOrCast();
}
hasSentData = true;
}
void LocalActor::updateCell()
{
LOG_MESSAGE_SIMPLE(TimedLog::LOG_VERBOSE, "Sending ID_ACTOR_CELL_CHANGE about %s %i-%i in cell %s to server",
refId.c_str(), refNum, mpNum, cell.getDescription().c_str());
LOG_APPEND(TimedLog::LOG_VERBOSE, "- Moved to cell %s", ptr.getCell()->getCell()->getDescription().c_str());
cell = *ptr.getCell()->getCell();
position = ptr.getRefData().getPosition();
isFollowerCellChange = false;
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, bool sendImmediately)
{
if (!ptr.getClass().hasInventoryStore(ptr))
return;
MWWorld::InventoryStore &invStore = ptr.getClass().getInventoryStore(ptr);
// If we've never sent any data, autoEquip the actor just in case its inventory
// slots have been cleared by a previous Container packet
if (!hasSentData)
invStore.autoEquip(ptr);
if (forceUpdate)
equipmentChanged = true;
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; slot++)
{
auto &item = equipmentItems[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 = -1;
item.enchantmentCharge = -1;
}
}
if (equipmentChanged)
{
if (sendImmediately)
sendEquipment();
else
mwmp::Main::get().getNetworking()->getActorList()->addEquipmentActor(*this);
equipmentChanged = false;
}
}
void LocalActor::updateAttackOrCast()
{
if (attack.shouldSend)
{
mwmp::Main::get().getNetworking()->getActorList()->addAttackActor(*this);
attack.shouldSend = false;
}
else if (cast.shouldSend)
{
mwmp::Main::get().getNetworking()->getActorList()->addCastActor(*this);
cast.shouldSend = false;
}
}
void LocalActor::sendEquipment()
{
ActorList actorList;
actorList.cell = cell;
actorList.addActor(*this);
Main::get().getNetworking()->getActorPacket(ID_ACTOR_EQUIPMENT)->setActorList(&actorList);
Main::get().getNetworking()->getActorPacket(ID_ACTOR_EQUIPMENT)->Send();
}
void LocalActor::sendDeath(char newDeathState)
{
deathState = newDeathState;
if (MechanicsHelper::isEmptyTarget(killer))
killer = MechanicsHelper::getTarget(ptr);
LOG_MESSAGE_SIMPLE(TimedLog::LOG_INFO, "Sending ID_ACTOR_DEATH about %s %i-%i in cell %s to server\n- deathState: %d",
refId.c_str(), refNum, mpNum, cell.getDescription().c_str(), deathState);
ActorList actorList;
actorList.cell = cell;
actorList.addActor(*this);
Main::get().getNetworking()->getActorPacket(ID_ACTOR_DEATH)->setActorList(&actorList);
Main::get().getNetworking()->getActorPacket(ID_ACTOR_DEATH)->Send();
MechanicsHelper::clearTarget(killer);
}
MWWorld::Ptr LocalActor::getPtr()
{
return ptr;
}
void LocalActor::setPtr(const MWWorld::Ptr& newPtr)
{
ptr = newPtr;
refId = ptr.getCellRef().getRefId();
refNum = 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();
}