#include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwclass/creature.hpp" #include "../mwclass/npc.hpp" #include "../mwdialogue/dialoguemanagerimp.hpp" #include "../mwgui/inventorywindow.hpp" #include "../mwgui/windowmanagerimp.hpp" #include "../mwinput/inputmanagerimp.hpp" #include "../mwmechanics/activespells.hpp" #include "../mwmechanics/aitravel.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/mechanicsmanagerimp.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwscript/scriptmanagerimp.hpp" #include "../mwstate/statemanagerimp.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/player.hpp" #include "../mwworld/worldimp.hpp" #include "LocalPlayer.hpp" #include "Main.hpp" #include "Networking.hpp" #include "CellController.hpp" #include "GUIController.hpp" #include "MechanicsHelper.hpp" using namespace mwmp; using namespace std; LocalPlayer::LocalPlayer() { deathTime = time(0); receivedCharacter = false; charGenState.currentStage = 0; charGenState.endStage = 1; charGenState.isFinished = false; difficulty = 0; physicsFramerate = 60.0; consoleAllowed = false; bedRestAllowed = true; wildernessRestAllowed = true; waitAllowed = true; ignorePosPacket = false; ignoreJailTeleportation = false; ignoreJailSkillIncreases = false; attack.shouldSend = false; killer.isPlayer = false; killer.refId = ""; killer.name = ""; isChangingRegion = false; jailProgressText = ""; jailEndText = ""; scale = 1; isWerewolf = false; isReceivingInventory = false; isReceivingQuickKeys = false; isPlayingAnimation = false; diedSinceArrestAttempt = false; } LocalPlayer::~LocalPlayer() { } Networking *LocalPlayer::getNetworking() { return mwmp::Main::get().getNetworking(); } MWWorld::Ptr LocalPlayer::getPlayerPtr() { return MWBase::Environment::get().getWorld()->getPlayerPtr(); } void LocalPlayer::update() { static float updateTimer = 0; const float timeoutSec = 0.015; if ((updateTimer += MWBase::Environment::get().getFrameDuration()) >= timeoutSec) { updateTimer = 0; updateCell(); updatePosition(); updateAnimFlags(); updateAttack(); updateEquipment(); updateStatsDynamic(); updateAttributes(); updateSkills(); updateLevel(); updateBounty(); updateReputation(); } } bool LocalPlayer::processCharGen() { MWBase::WindowManager *windowManager = MWBase::Environment::get().getWindowManager(); // If we haven't finished CharGen and we're in a menu, it must be // one of the CharGen menus, so go no further until it's closed if (windowManager->isGuiMode() && !charGenState.isFinished) { return false; } // If the current stage of CharGen is not the last one, // move to the next one else if (charGenState.currentStage < charGenState.endStage) { switch (charGenState.currentStage) { case 0: windowManager->pushGuiMode(MWGui::GM_Name); break; case 1: windowManager->pushGuiMode(MWGui::GM_Race); break; case 2: windowManager->pushGuiMode(MWGui::GM_Class); break; case 3: windowManager->pushGuiMode(MWGui::GM_Birth); break; default: windowManager->pushGuiMode(MWGui::GM_Review); break; } getNetworking()->getPlayerPacket(ID_PLAYER_CHARGEN)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_CHARGEN)->Send(); charGenState.currentStage++; return false; } // If we've reached the last stage of CharGen, send the // corresponding packets and mark CharGen as finished else if (!charGenState.isFinished) { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr ptrPlayer = world->getPlayerPtr(); npc = *ptrPlayer.get()->mBase; birthsign = world->getPlayer().getBirthSign(); LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_PLAYER_BASEINFO to server with my CharGen info"); getNetworking()->getPlayerPacket(ID_PLAYER_BASEINFO)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_BASEINFO)->Send(); // Send stats packets if this is the 2nd round of CharGen that // only happens for new characters if (charGenState.endStage != 1) { updateStatsDynamic(true); updateAttributes(true); updateSkills(true); updateLevel(true); sendClass(); sendSpellbook(); getNetworking()->getPlayerPacket(ID_PLAYER_CHARGEN)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_CHARGEN)->Send(); } // Mark character generation as finished until overridden by a new ID_PLAYER_CHARGEN packet charGenState.isFinished = true; } return true; } bool LocalPlayer::isLoggedIn() { if (charGenState.isFinished && (charGenState.endStage > 1 || receivedCharacter)) return true; return false; } void LocalPlayer::updateStatsDynamic(bool forceUpdate) { if (statsDynamicIndexChanges.size() > 0) statsDynamicIndexChanges.clear(); MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWMechanics::CreatureStats *ptrCreatureStats = &ptrPlayer.getClass().getCreatureStats(ptrPlayer); MWMechanics::DynamicStat health(ptrCreatureStats->getHealth()); MWMechanics::DynamicStat magicka(ptrCreatureStats->getMagicka()); MWMechanics::DynamicStat fatigue(ptrCreatureStats->getFatigue()); static MWMechanics::DynamicStat oldHealth(ptrCreatureStats->getHealth()); static MWMechanics::DynamicStat oldMagicka(ptrCreatureStats->getMagicka()); static MWMechanics::DynamicStat oldFatigue(ptrCreatureStats->getFatigue()); // Update stats when they become 0 or they have changed enough auto needUpdate = [](MWMechanics::DynamicStat &oldVal, MWMechanics::DynamicStat &newVal, int limit) { return oldVal != newVal && (newVal.getCurrent() == 0 || oldVal.getCurrent() == 0 || abs(oldVal.getCurrent() - newVal.getCurrent()) >= limit); }; if (forceUpdate || needUpdate(oldHealth, health, 2)) statsDynamicIndexChanges.push_back(0); if (forceUpdate || needUpdate(oldMagicka, magicka, 4)) statsDynamicIndexChanges.push_back(1); if (forceUpdate || needUpdate(oldFatigue, fatigue, 4)) statsDynamicIndexChanges.push_back(2); if (forceUpdate || statsDynamicIndexChanges.size() > 0) { 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(); exchangeFullInfo = false; getNetworking()->getPlayerPacket(ID_PLAYER_STATS_DYNAMIC)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_STATS_DYNAMIC)->Send(); static bool wasDead = false; if (creatureStats.mDead && !wasDead) { if (MechanicsHelper::isEmptyTarget(killer)) killer = MechanicsHelper::getTarget(getPlayerPtr()); LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_PLAYER_DEATH about myself to server"); getNetworking()->getPlayerPacket(ID_PLAYER_DEATH)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_DEATH)->Send(); MechanicsHelper::clearTarget(killer); } wasDead = creatureStats.mDead; } } void LocalPlayer::updateAttributes(bool forceUpdate) { // Only send attributes if we are not a werewolf, or they will be // overwritten by the werewolf ones if (isWerewolf) return; if (attributeIndexChanges.size() > 0) attributeIndexChanges.clear(); MWWorld::Ptr ptrPlayer = getPlayerPtr(); const MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer); for (int i = 0; i < 8; ++i) { if (ptrNpcStats.getAttribute(i).getBase() != creatureStats.mAttributes[i].mBase || ptrNpcStats.getAttribute(i).getModifier() != creatureStats.mAttributes[i].mMod || ptrNpcStats.getAttribute(i).getDamage() != creatureStats.mAttributes[i].mDamage || ptrNpcStats.getSkillIncrease(i) != npcStats.mSkillIncrease[i] || forceUpdate) { attributeIndexChanges.push_back(i); ptrNpcStats.getAttribute(i).writeState(creatureStats.mAttributes[i]); npcStats.mSkillIncrease[i] = ptrNpcStats.getSkillIncrease(i); } } if (attributeIndexChanges.size() > 0) { exchangeFullInfo = false; getNetworking()->getPlayerPacket(ID_PLAYER_ATTRIBUTE)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_ATTRIBUTE)->Send(); } } void LocalPlayer::updateSkills(bool forceUpdate) { // Only send skills if we are not a werewolf, or they will be // overwritten by the werewolf ones if (isWerewolf) return; if (skillIndexChanges.size() > 0) skillIndexChanges.clear(); MWWorld::Ptr ptrPlayer = getPlayerPtr(); const MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer); for (int i = 0; i < 27; ++i) { // Update a skill if its base value has changed at all or its progress has changed enough if (ptrNpcStats.getSkill(i).getBase() != npcStats.mSkills[i].mBase || ptrNpcStats.getSkill(i).getModifier() != npcStats.mSkills[i].mMod || ptrNpcStats.getSkill(i).getDamage() != npcStats.mSkills[i].mDamage || abs(ptrNpcStats.getSkill(i).getProgress() - npcStats.mSkills[i].mProgress) > 0.75 || forceUpdate) { skillIndexChanges.push_back(i); ptrNpcStats.getSkill(i).writeState(npcStats.mSkills[i]); } } if (skillIndexChanges.size() > 0) { exchangeFullInfo = false; getNetworking()->getPlayerPacket(ID_PLAYER_SKILL)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_SKILL)->Send(); } } void LocalPlayer::updateLevel(bool forceUpdate) { MWWorld::Ptr ptrPlayer = getPlayerPtr(); const MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer); if (ptrNpcStats.getLevel() != creatureStats.mLevel || ptrNpcStats.getLevelProgress() != npcStats.mLevelProgress || forceUpdate) { creatureStats.mLevel = ptrNpcStats.getLevel(); npcStats.mLevelProgress = ptrNpcStats.getLevelProgress(); getNetworking()->getPlayerPacket(ID_PLAYER_LEVEL)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_LEVEL)->Send(); } } void LocalPlayer::updateBounty(bool forceUpdate) { MWWorld::Ptr ptrPlayer = getPlayerPtr(); const MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer); if (ptrNpcStats.getBounty() != npcStats.mBounty || forceUpdate) { npcStats.mBounty = ptrNpcStats.getBounty(); getNetworking()->getPlayerPacket(ID_PLAYER_BOUNTY)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_BOUNTY)->Send(); } } void LocalPlayer::updateReputation(bool forceUpdate) { MWWorld::Ptr ptrPlayer = getPlayerPtr(); const MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer); if (ptrNpcStats.getReputation() != npcStats.mReputation || forceUpdate) { npcStats.mReputation = ptrNpcStats.getReputation(); getNetworking()->getPlayerPacket(ID_PLAYER_REPUTATION)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_REPUTATION)->Send(); } } void LocalPlayer::updatePosition(bool forceUpdate) { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr ptrPlayer = world->getPlayerPtr(); static bool posWasChanged = false; static bool isJumping = false; static bool sentJumpEnd = true; static float oldRot[2] = {0}; position = ptrPlayer.getRefData().getPosition(); bool posIsChanging = (direction.pos[0] != 0 || direction.pos[1] != 0 || direction.rot[0] != 0 || direction.rot[1] != 0 || direction.rot[2] != 0); // Animations can change a player's position without actually creating directional movement, // so update positions accordingly if (!posIsChanging && isPlayingAnimation) { if (MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(ptrPlayer, animation.groupname)) posIsChanging = true; else isPlayingAnimation = false; } if (forceUpdate || posIsChanging || posWasChanged) { oldRot[0] = position.rot[0]; oldRot[1] = position.rot[2]; posWasChanged = posIsChanging; if (!isJumping && !world->isOnGround(ptrPlayer) && !world->isFlying(ptrPlayer)) isJumping = true; getNetworking()->getPlayerPacket(ID_PLAYER_POSITION)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_POSITION)->Send(); } else if (isJumping && world->isOnGround(ptrPlayer)) { isJumping = false; sentJumpEnd = false; } // Packet with jump end position has to be sent one tick after above check else if (!sentJumpEnd) { sentJumpEnd = true; position = ptrPlayer.getRefData().getPosition(); getNetworking()->getPlayerPacket(ID_PLAYER_POSITION)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_POSITION)->Send(); } } void LocalPlayer::updateCell(bool forceUpdate) { const ESM::Cell *ptrCell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->getCell(); // If the LocalPlayer's Ptr cell is different from the LocalPlayer's packet cell, proceed if (forceUpdate || !Main::get().getCellController()->isSameCell(*ptrCell, cell)) { LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_PLAYER_CELL_CHANGE about LocalPlayer to server"); LOG_APPEND(Log::LOG_INFO, "- Moved from %s to %s", cell.getDescription().c_str(), ptrCell->getDescription().c_str()); if (!Misc::StringUtils::ciEqual(cell.mRegion, ptrCell->mRegion)) { LOG_APPEND(Log::LOG_INFO, "- Changed region from %s to %s", cell.mRegion.empty() ? "none" : cell.mRegion.c_str(), ptrCell->mRegion.empty() ? "none" : ptrCell->mRegion.c_str()); isChangingRegion = true; } cell = *ptrCell; previousCellPosition = position; // Make sure the position is updated before a cell packet is sent, or else // cell change events in server scripts will have the wrong player position updatePosition(true); getNetworking()->getPlayerPacket(ID_PLAYER_CELL_CHANGE)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_CELL_CHANGE)->Send(); isChangingRegion = false; } } void LocalPlayer::updateEquipment(bool forceUpdate) { if (equipmentIndexChanges.size() > 0) equipmentIndexChanges.clear(); MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWWorld::InventoryStore &invStore = ptrPlayer.getClass().getInventoryStore(ptrPlayer); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; slot++) { auto &item = equipmentItems[slot]; MWWorld::ContainerStoreIterator it = invStore.getSlot(slot); if (it != invStore.end()) { MWWorld::CellRef &cellRef = it->getCellRef(); if (Misc::StringUtils::ciEqual(cellRef.getRefId(), item.refId) == false || cellRef.getCharge() != item.charge || Utils::compareFloats(cellRef.getEnchantmentCharge(), item.enchantmentCharge, 1.0f) == false || it->getRefData().getCount() != item.count || forceUpdate) { equipmentIndexChanges.push_back(slot); item.refId = it->getCellRef().getRefId(); item.count = it->getRefData().getCount(); item.charge = it->getCellRef().getCharge(); item.enchantmentCharge = it->getCellRef().getEnchantmentCharge(); } } else if (!item.refId.empty()) { equipmentIndexChanges.push_back(slot); item.refId = ""; item.count = 0; item.charge = -1; item.enchantmentCharge = -1; } } if (equipmentIndexChanges.size() > 0) { exchangeFullInfo = false; getNetworking()->getPlayerPacket(ID_PLAYER_EQUIPMENT)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_EQUIPMENT)->Send(); } } void LocalPlayer::updateInventory(bool forceUpdate) { static bool invChanged = false; if (forceUpdate) invChanged = true; MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWWorld::InventoryStore &ptrInventory = ptrPlayer.getClass().getInventoryStore(ptrPlayer); mwmp::Item item; auto setItem = [](Item &item, const MWWorld::Ptr &iter) { item.refId = iter.getCellRef().getRefId(); if (item.refId.find("$dynamic") != string::npos) return true; item.count = iter.getRefData().getCount(); item.charge = iter.getCellRef().getCharge(); item.enchantmentCharge = iter.getCellRef().getEnchantmentCharge(); item.soul = iter.getCellRef().getSoul(); return false; }; if (!invChanged) { for (const auto &itemOld : inventoryChanges.items) { auto result = ptrInventory.begin(); for (; result != ptrInventory.end(); ++result) { if(setItem(item, *result)) continue; if (item == itemOld) break; } if (result == ptrInventory.end()) { invChanged = true; break; } } } if (!invChanged) { for (const auto &iter : ptrInventory) { if(setItem(item, iter)) continue; auto items = inventoryChanges.items; if (find(items.begin(), items.end(), item) == items.end()) { invChanged = true; break; } } } if (!invChanged) return; invChanged = false; sendInventory(); } void LocalPlayer::updateAttack() { if (attack.shouldSend) { if (attack.type == Attack::MAGIC) { attack.spellId = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); if (attack.pressed) attack.success = MechanicsHelper::getSpellSuccess(attack.spellId, getPlayerPtr()); } getNetworking()->getPlayerPacket(ID_PLAYER_ATTACK)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_ATTACK)->Send(); attack.shouldSend = false; } } void LocalPlayer::updateAnimFlags(bool forceUpdate) { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr ptrPlayer = world->getPlayerPtr(); MWMechanics::NpcStats ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer); using namespace MWMechanics; static bool wasRunning = ptrNpcStats.getMovementFlag(CreatureStats::Flag_Run); static bool wasSneaking = ptrNpcStats.getMovementFlag(CreatureStats::Flag_Sneak); static bool wasForceJumping = ptrNpcStats.getMovementFlag(CreatureStats::Flag_ForceJump); static bool wasForceMoveJumping = ptrNpcStats.getMovementFlag(CreatureStats::Flag_ForceMoveJump); bool isRunning = ptrNpcStats.getMovementFlag(CreatureStats::Flag_Run); bool isSneaking = ptrNpcStats.getMovementFlag(CreatureStats::Flag_Sneak); bool isForceJumping = ptrNpcStats.getMovementFlag(CreatureStats::Flag_ForceJump); bool isForceMoveJumping = ptrNpcStats.getMovementFlag(CreatureStats::Flag_ForceMoveJump); isFlying = world->isFlying(ptrPlayer); bool isJumping = !world->isOnGround(ptrPlayer) && !isFlying; // We need to send a new packet at the end of jumping and flying too, // so keep track of what we were doing last frame static bool wasJumping = false; static bool wasFlying = false; MWMechanics::DrawState_ currentDrawState = ptrPlayer.getClass().getNpcStats(ptrPlayer).getDrawState(); static MWMechanics::DrawState_ lastDrawState = ptrPlayer.getClass().getNpcStats(ptrPlayer).getDrawState(); if (wasRunning != isRunning || wasSneaking != isSneaking || wasForceJumping != isForceJumping || wasForceMoveJumping != isForceMoveJumping || lastDrawState != currentDrawState || wasJumping || isJumping || wasFlying != isFlying || forceUpdate) { wasSneaking = isSneaking; wasRunning = isRunning; wasForceJumping = isForceJumping; wasForceMoveJumping = isForceMoveJumping; lastDrawState = currentDrawState; wasFlying = isFlying; wasJumping = isJumping; 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_ForceJump, isJumping); movementFlags = __SETFLAG(CreatureStats::Flag_ForceMoveJump, isForceMoveJumping); #undef __SETFLAG if (currentDrawState == MWMechanics::DrawState_Nothing) drawState = 0; else if (currentDrawState == MWMechanics::DrawState_Weapon) drawState = 1; else if (currentDrawState == MWMechanics::DrawState_Spell) drawState = 2; if (isJumping) updatePosition(true); // fix position after jump; getNetworking()->getPlayerPacket(ID_PLAYER_ANIM_FLAGS)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_ANIM_FLAGS)->Send(); } } void LocalPlayer::addItems() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); MWWorld::ContainerStore &ptrStore = ptrPlayer.getClass().getContainerStore(ptrPlayer); for (const auto &item : inventoryChanges.items) { // Skip bound items if (MWBase::Environment::get().getMechanicsManager()->isBoundItem(item.refId)) continue; try { MWWorld::ManualRef itemRef(esmStore, item.refId, item.count); MWWorld::Ptr itemPtr = itemRef.getPtr(); if (item.charge != -1) itemPtr.getCellRef().setCharge(item.charge); if (item.enchantmentCharge != -1) itemPtr.getCellRef().setEnchantmentCharge(item.enchantmentCharge); if (!item.soul.empty()) itemPtr.getCellRef().setSoul(item.soul); ptrStore.add(itemPtr, item.count, ptrPlayer); } catch (std::exception&) { LOG_APPEND(Log::LOG_INFO, "- Ignored addition of invalid inventory item %s", item.refId.c_str()); } } updateInventoryWindow(); } void LocalPlayer::addSpells() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWMechanics::Spells &ptrSpells = ptrPlayer.getClass().getCreatureStats(ptrPlayer).getSpells(); for (const auto &spell : spellbookChanges.spells) // Only add spells that are ensured to exist if (MWBase::Environment::get().getWorld()->getStore().get().search(spell.mId)) ptrSpells.add(spell.mId); else LOG_APPEND(Log::LOG_INFO, "- Ignored addition of invalid spell %s", spell.mId.c_str()); } void LocalPlayer::addJournalItems() { for (const auto &journalItem : journalChanges.journalItems) { MWWorld::Ptr ptrFound; if (journalItem.type == JournalItem::ENTRY) { ptrFound = MWBase::Environment::get().getWorld()->searchPtr(journalItem.actorRefId, false); if (!ptrFound) ptrFound = getPlayerPtr(); } try { if (journalItem.type == JournalItem::ENTRY) { if (journalItem.hasTimestamp) { MWBase::Environment::get().getJournal()->addEntry(journalItem.quest, journalItem.index, ptrFound, journalItem.timestamp.daysPassed, journalItem.timestamp.month, journalItem.timestamp.day); } else { MWBase::Environment::get().getJournal()->addEntry(journalItem.quest, journalItem.index, ptrFound); } } else MWBase::Environment::get().getJournal()->setJournalIndex(journalItem.quest, journalItem.index); } catch (std::exception&) { LOG_APPEND(Log::LOG_INFO, "- Ignored addition of invalid journal quest %s", journalItem.quest.c_str()); } } } void LocalPlayer::addTopics() { auto &env = MWBase::Environment::get(); for (const auto &topic : topicChanges.topics) { std::string topicId = topic.topicId; // If we're using a translated version of Morrowind, translate this topic from English into our language if (env.getWindowManager()->getTranslationDataStorage().hasTranslation()) topicId = env.getWindowManager()->getTranslationDataStorage().getLocalizedTopicId(topicId); env.getDialogueManager()->addTopic(topicId); if (env.getWindowManager()->containsMode(MWGui::GM_Dialogue)) env.getDialogueManager()->updateActorKnownTopics(); } } void LocalPlayer::removeItems() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWWorld::ContainerStore &ptrStore = ptrPlayer.getClass().getContainerStore(ptrPlayer); for (const auto &item : inventoryChanges.items) ptrStore.remove(item.refId, item.count, ptrPlayer); } void LocalPlayer::removeSpells() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWMechanics::Spells &ptrSpells = ptrPlayer.getClass().getCreatureStats(ptrPlayer).getSpells(); MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); for (const auto &spell : spellbookChanges.spells) { ptrSpells.remove(spell.mId); if (spell.mId == wm->getSelectedSpell()) wm->unsetSelectedSpell(); } } void LocalPlayer::resurrect() { creatureStats.mDead = false; MWWorld::Ptr ptrPlayer = getPlayerPtr(); if (resurrectType == mwmp::RESURRECT_TYPE::IMPERIAL_SHRINE) MWBase::Environment::get().getWorld()->teleportToClosestMarker(ptrPlayer, "divinemarker"); else if (resurrectType == mwmp::RESURRECT_TYPE::TRIBUNAL_TEMPLE) MWBase::Environment::get().getWorld()->teleportToClosestMarker(ptrPlayer, "templemarker"); ptrPlayer.getClass().getCreatureStats(ptrPlayer).resurrect(); // The player could have died from a hand-to-hand attack, so reset their fatigue // as well if (creatureStats.mDynamic[2].mMod < 1) creatureStats.mDynamic[2].mMod = 1; creatureStats.mDynamic[2].mCurrent = creatureStats.mDynamic[2].mMod; MWMechanics::DynamicStat fatigue; fatigue.readState(creatureStats.mDynamic[2]); ptrPlayer.getClass().getCreatureStats(ptrPlayer).setFatigue(fatigue); // If this player had a weapon or spell readied when dying, they will still have it // readied but be unable to use it unless we clear it here ptrPlayer.getClass().getNpcStats(ptrPlayer).setDrawState(MWMechanics::DrawState_Nothing); // Record that the player has died since the last attempt was made to arrest them, // used to make guards lenient enough to attempt an arrest again diedSinceArrestAttempt = true; deathTime = time(0); LOG_APPEND(Log::LOG_INFO, "- diedSinceArrestAttempt is now true"); // Record that we are no longer a known werewolf, to avoid being attacked infinitely MWBase::Environment::get().getWorld()->setGlobalInt("pcknownwerewolf", 0); // Ensure we unequip any items with constant effects that can put us into an infinite // death loop static const int damageEffects[5] = { ESM::MagicEffect::DrainHealth, ESM::MagicEffect::FireDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::SunDamage }; for (const auto &damageEffect : damageEffects) MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, damageEffect); Main::get().getNetworking()->getPlayerPacket(ID_PLAYER_RESURRECT)->setPlayer(this); Main::get().getNetworking()->getPlayerPacket(ID_PLAYER_RESURRECT)->Send(); updateStatsDynamic(true); } void LocalPlayer::closeInventoryWindows() { if (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Container) || MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Inventory)) MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->finishDragDrop(); } void LocalPlayer::updateInventoryWindow() { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); } void LocalPlayer::setCharacter() { receivedCharacter = true; MWBase::World *world = MWBase::Environment::get().getWorld(); // Ignore invalid races if (world->getStore().get().search(npc.mRace) != 0) { MWBase::Environment::get().getWorld()->getPlayer().setBirthSign(birthsign); if (resetStats) { MWBase::Environment::get().getMechanicsManager()->setPlayerRace(npc.mRace, npc.isMale(), npc.mHead, npc.mHair); setEquipment(); } else { ESM::NPC player = *world->getPlayerPtr().get()->mBase; player.mRace = npc.mRace; player.mHead = npc.mHead; player.mHair = npc.mHair; player.setIsMale(npc.isMale()); world->createRecord(player); MWBase::Environment::get().getMechanicsManager()->playerLoaded(); } MWBase::Environment::get().getWindowManager()->getInventoryWindow()->rebuildAvatar(); } else { LOG_APPEND(Log::LOG_INFO, "- Character update was ignored due to invalid race %s", npc.mRace.c_str()); } } void LocalPlayer::setDynamicStats() { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr ptrPlayer = world->getPlayerPtr(); MWMechanics::CreatureStats *ptrCreatureStats = &ptrPlayer.getClass().getCreatureStats(ptrPlayer); MWMechanics::DynamicStat dynamicStat; for (int i = 0; i < 3; ++i) { dynamicStat = ptrCreatureStats->getDynamic(i); dynamicStat.setBase(creatureStats.mDynamic[i].mBase); dynamicStat.setCurrent(creatureStats.mDynamic[i].mCurrent); ptrCreatureStats->setDynamic(i, dynamicStat); } } void LocalPlayer::setAttributes() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWMechanics::NpcStats *ptrNpcStats = &ptrPlayer.getClass().getNpcStats(ptrPlayer); MWMechanics::AttributeValue attributeValue; for (int attributeIndex = 0; attributeIndex < 8; ++attributeIndex) { // If the server wants to clear our attribute's non-zero modifier, we need to remove // the spell effect causing it, to avoid an infinite loop where the effect keeps resetting // the modifier if (creatureStats.mAttributes[attributeIndex].mMod == 0 && ptrNpcStats->getAttribute(attributeIndex).getModifier() > 0) { ptrNpcStats->getActiveSpells().purgeEffectByArg(ESM::MagicEffect::FortifyAttribute, attributeIndex); MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(ptrPlayer); // Is the modifier for this attribute still higher than 0? If so, unequip items that // fortify the attribute if (ptrNpcStats->getAttribute(attributeIndex).getModifier() > 0) { MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::FortifyAttribute, attributeIndex, -1); mwmp::Main::get().getGUIController()->refreshGuiMode(MWGui::GM_Inventory); } } attributeValue.readState(creatureStats.mAttributes[attributeIndex]); ptrNpcStats->setAttribute(attributeIndex, attributeValue); ptrNpcStats->setSkillIncrease(attributeIndex, npcStats.mSkillIncrease[attributeIndex]); } } void LocalPlayer::setSkills() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWMechanics::NpcStats *ptrNpcStats = &ptrPlayer.getClass().getNpcStats(ptrPlayer); MWMechanics::SkillValue skillValue; for (int skillIndex = 0; skillIndex < 27; ++skillIndex) { // If the server wants to clear our skill's non-zero modifier, we need to remove // the spell effect causing it, to avoid an infinite loop where the effect keeps resetting // the modifier if (npcStats.mSkills[skillIndex].mMod == 0 && ptrNpcStats->getSkill(skillIndex).getModifier() > 0) { ptrNpcStats->getActiveSpells().purgeEffectByArg(ESM::MagicEffect::FortifySkill, skillIndex); MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(ptrPlayer); // Is the modifier for this skill still higher than 0? If so, unequip items that // fortify the skill if (ptrNpcStats->getSkill(skillIndex).getModifier() > 0) { MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::FortifySkill, -1, skillIndex); mwmp::Main::get().getGUIController()->refreshGuiMode(MWGui::GM_Inventory); } } skillValue.readState(npcStats.mSkills[skillIndex]); ptrNpcStats->setSkill(skillIndex, skillValue); } } void LocalPlayer::setLevel() { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr ptrPlayer = world->getPlayerPtr(); MWMechanics::NpcStats *ptrNpcStats = &ptrPlayer.getClass().getNpcStats(ptrPlayer); ptrNpcStats->setLevel(creatureStats.mLevel); ptrNpcStats->setLevelProgress(npcStats.mLevelProgress); } void LocalPlayer::setBounty() { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr ptrPlayer = world->getPlayerPtr(); MWMechanics::NpcStats *ptrNpcStats = &ptrPlayer.getClass().getNpcStats(ptrPlayer); ptrNpcStats->setBounty(npcStats.mBounty); } void LocalPlayer::setReputation() { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr ptrPlayer = world->getPlayerPtr(); MWMechanics::NpcStats *ptrNpcStats = &ptrPlayer.getClass().getNpcStats(ptrPlayer); ptrNpcStats->setReputation(npcStats.mReputation); } void LocalPlayer::setPosition() { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr ptrPlayer = world->getPlayerPtr(); // If we're ignoring this position packet because of an invalid cell change, // don't make the next one get ignored as well if (ignorePosPacket) ignorePosPacket = false; else { world->getPlayer().setTeleported(true); world->moveObject(ptrPlayer, position.pos[0], position.pos[1], position.pos[2]); world->rotateObject(ptrPlayer, position.rot[0], position.rot[1], position.rot[2]); world->setInertialForce(ptrPlayer, osg::Vec3f(0.f, 0.f, 0.f)); } updatePosition(true); // Make sure we update our draw state, or we'll end up with the wrong one updateAnimFlags(true); } void LocalPlayer::setMomentum() { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr ptrPlayer = world->getPlayerPtr(); world->setInertialForce(ptrPlayer, momentum.asVec3()); } void LocalPlayer::setCell() { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr ptrPlayer = world->getPlayerPtr(); ESM::Position pos; // To avoid crashes, close container windows this player may be in closeInventoryWindows(); world->getPlayer().setTeleported(true); int x = cell.mData.mX; int y = cell.mData.mY; if (cell.isExterior()) { world->indexToPosition(x, y, pos.pos[0], pos.pos[1], true); pos.pos[2] = 0; pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; world->changeToExteriorCell(pos, true); world->fixPosition(ptrPlayer); } else if (world->findExteriorPosition(cell.mName, pos)) { world->changeToExteriorCell(pos, true); world->fixPosition(ptrPlayer); } else { try { world->findInteriorPosition(cell.mName, pos); world->changeToInteriorCell(cell.mName, pos, true); } // If we've been sent to an invalid interior, ignore the incoming // packet about our position in that cell catch (std::exception&) { LOG_APPEND(Log::LOG_INFO, "%s", "- Cell doesn't exist on this client"); ignorePosPacket = true; } } updateCell(true); } void LocalPlayer::setClass() { LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Received ID_PLAYER_CLASS from server"); if (charClass.mId.empty()) // custom class { charClass.mData.mIsPlayable = 0x1; MWBase::Environment::get().getMechanicsManager()->setPlayerClass(charClass); MWBase::Environment::get().getWindowManager()->setPlayerClass(charClass); } else { const ESM::Class *existingCharClass = MWBase::Environment::get().getWorld()->getStore().get().search(charClass.mId); if (existingCharClass) { MWBase::Environment::get().getMechanicsManager()->setPlayerClass(charClass.mId); MWBase::Environment::get().getWindowManager()->setPlayerClass(charClass); } else LOG_APPEND(Log::LOG_INFO, "- Ignored invalid default class %s", charClass.mId.c_str()); } } void LocalPlayer::setEquipment() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWWorld::InventoryStore &ptrInventory = ptrPlayer.getClass().getInventoryStore(ptrPlayer); for (int slot = 0; slot < MWWorld::InventoryStore::Slots; slot++) { mwmp::Item ¤tItem = equipmentItems[slot]; if (!currentItem.refId.empty()) { auto it = find_if(ptrInventory.begin(), ptrInventory.end(), [¤tItem](const MWWorld::Ptr &itemPtr) { return Misc::StringUtils::ciEqual(itemPtr.getCellRef().getRefId(), currentItem.refId); }); // If the item is not in our inventory, add it as long as it's not a bound item if (it == ptrInventory.end()) { if (!MWBase::Environment::get().getMechanicsManager()->isBoundItem(currentItem.refId)) { try { auto addIter = ptrInventory.ContainerStore::add(currentItem.refId.c_str(), currentItem.count, ptrPlayer); ptrInventory.equip(slot, addIter, ptrPlayer); } catch (std::exception&) { LOG_APPEND(Log::LOG_INFO, "- Ignored addition of invalid equipment item %s", currentItem.refId.c_str()); } } } else { // Don't try to equip an item that is already equipped if (ptrInventory.getSlot(slot) != it) ptrInventory.equip(slot, it, ptrPlayer); } } else ptrInventory.unequipSlot(slot, ptrPlayer); } MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updatePlayer(); } void LocalPlayer::setInventory() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWWorld::ContainerStore &ptrStore = ptrPlayer.getClass().getContainerStore(ptrPlayer); // Ensure no item is being drag and dropped MWBase::Environment::get().getWindowManager()->finishDragDrop(); // Clear items in inventory ptrStore.clear(); // Proceed by adding items addItems(); // Don't automatically setEquipment() here, or the player could end // up getting a new set of their starting clothes, or other items // supposed to no longer exist // // Instead, expect server scripts to do that manually } void LocalPlayer::setSpellbook() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWMechanics::Spells &ptrSpells = ptrPlayer.getClass().getCreatureStats(ptrPlayer).getSpells(); // Clear spells in spellbook, while ignoring abilities, powers, etc. while (true) { MWMechanics::Spells::TIterator iter = ptrSpells.begin(); for (; iter != ptrSpells.end(); iter++) { const ESM::Spell *spell = iter->first; if (spell->mData.mType == ESM::Spell::ST_Spell) { ptrSpells.remove(spell->mId); break; } } if (iter == ptrSpells.end()) break; } // Proceed by adding spells addSpells(); } void LocalPlayer::setQuickKeys() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Received ID_PLAYER_QUICKKEYS from server"); for (const auto &quickKey : quickKeyChanges.quickKeys) { LOG_APPEND(Log::LOG_INFO, "- slot: %i, type: %i, itemId: %s", quickKey.slot, quickKey.type, quickKey.itemId.c_str()); if (quickKey.type == QuickKey::ITEM || quickKey.type == QuickKey::ITEM_MAGIC) { MWWorld::InventoryStore &ptrInventory = ptrPlayer.getClass().getInventoryStore(ptrPlayer); auto it = find_if(ptrInventory.begin(), ptrInventory.end(), [&quickKey](const MWWorld::Ptr &inventoryItem) { return Misc::StringUtils::ciEqual(inventoryItem.getCellRef().getRefId(), quickKey.itemId); }); if (it != ptrInventory.end()) MWBase::Environment::get().getWindowManager()->setQuickKey(quickKey.slot, quickKey.type, (*it)); } else if (quickKey.type == QuickKey::MAGIC) { MWMechanics::Spells &ptrSpells = ptrPlayer.getClass().getCreatureStats(ptrPlayer).getSpells(); bool hasSpell = false; MWMechanics::Spells::TIterator iter = ptrSpells.begin(); for (; iter != ptrSpells.end(); iter++) { const ESM::Spell *spell = iter->first; if (Misc::StringUtils::ciEqual(spell->mId, quickKey.itemId)) { hasSpell = true; break; } } if (hasSpell) MWBase::Environment::get().getWindowManager()->setQuickKey(quickKey.slot, quickKey.type, 0, quickKey.itemId); } else MWBase::Environment::get().getWindowManager()->setQuickKey(quickKey.slot, quickKey.type, 0); } } void LocalPlayer::setFactions() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer); LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Received ID_PLAYER_FACTION from server\n- action: %i", factionChanges.action); for (const auto &faction : factionChanges.factions) { const ESM::Faction *esmFaction = MWBase::Environment::get().getWorld()->getStore().get().search(faction.factionId); if (!esmFaction) { LOG_APPEND(Log::LOG_INFO, "- Ignored invalid faction %s", faction.factionId.c_str()); continue; } // If the player isn't in this faction, make them join it if (!ptrNpcStats.isInFaction(faction.factionId)) ptrNpcStats.joinFaction(faction.factionId); if (factionChanges.action == mwmp::FactionChanges::RANK) { // While the faction rank is different in the packet than in the NpcStats, // adjust the NpcStats accordingly while (faction.rank != ptrNpcStats.getFactionRanks().at(faction.factionId)) { if (faction.rank > ptrNpcStats.getFactionRanks().at(faction.factionId)) ptrNpcStats.raiseRank(faction.factionId); else ptrNpcStats.lowerRank(faction.factionId); } } else if (factionChanges.action == mwmp::FactionChanges::EXPULSION) { // If the expelled state is different in the packet than in the NpcStats, // adjust the NpcStats accordingly if (faction.isExpelled != ptrNpcStats.getExpelled(faction.factionId)) { if (faction.isExpelled) ptrNpcStats.expell(faction.factionId); else ptrNpcStats.clearExpelled(faction.factionId); } } else if (factionChanges.action == mwmp::FactionChanges::REPUTATION) ptrNpcStats.setFactionReputation(faction.factionId, faction.reputation); } } void LocalPlayer::setKills() { LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Received ID_WORLD_KILL_COUNT with the following kill counts:"); std::string debugMessage = ""; for (const auto &kill : killChanges.kills) { if (Log::GetLevel() <= Log::LOG_INFO) { if (!debugMessage.empty()) debugMessage += ", "; debugMessage += kill.refId + ": " + std::to_string(kill.number); } MWBase::Environment::get().getMechanicsManager()->setDeaths(kill.refId, kill.number); } LOG_APPEND(Log::LOG_INFO, "- %s", debugMessage.c_str()); } void LocalPlayer::setBooks() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer); for (const auto &book : bookChanges.books) ptrNpcStats.flagAsUsed(book.bookId); } void LocalPlayer::setShapeshift() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWBase::Environment::get().getWorld()->scaleObject(ptrPlayer, scale); MWBase::Environment::get().getMechanicsManager()->setWerewolf(ptrPlayer, isWerewolf); } void LocalPlayer::setMarkLocation() { MWWorld::CellStore *ptrCellStore = Main::get().getCellController()->getCellStore(markCell); if (ptrCellStore) MWBase::Environment::get().getWorld()->getPlayer().markPosition(ptrCellStore, markPosition); } void LocalPlayer::setSelectedSpell() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWMechanics::CreatureStats& stats = ptrPlayer.getClass().getCreatureStats(ptrPlayer); MWMechanics::Spells& spells = stats.getSpells(); if (!spells.hasSpell(selectedSpellId)) return; MWBase::Environment::get().getWindowManager()->setSelectedSpell(selectedSpellId, int(MWMechanics::getSpellSuccessChance(selectedSpellId, ptrPlayer))); } void LocalPlayer::sendClass() { MWBase::World *world = MWBase::Environment::get().getWorld(); const ESM::NPC *npcBase = world->getPlayerPtr().get()->mBase; const ESM::Class *esmClass = world->getStore().get().find(npcBase->mClass); if (npcBase->mClass.find("$dynamic") != string::npos) // custom class { charClass.mId = ""; charClass.mName = esmClass->mName; charClass.mDescription = esmClass->mDescription; charClass.mData = esmClass->mData; } else charClass.mId = esmClass->mId; getNetworking()->getPlayerPacket(ID_PLAYER_CHARCLASS)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_CHARCLASS)->Send(); } void LocalPlayer::sendInventory() { LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending entire inventory to server"); MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWWorld::InventoryStore &ptrInventory = ptrPlayer.getClass().getInventoryStore(ptrPlayer); mwmp::Item item; inventoryChanges.items.clear(); for (const auto &iter : ptrInventory) { item.refId = iter.getCellRef().getRefId(); // Skip any items that somehow have clientside-only dynamic IDs if (item.refId.find("$dynamic") != string::npos) continue; // Skip bound items if (MWBase::Environment::get().getMechanicsManager()->isBoundItem(item.refId)) continue; item.count = iter.getRefData().getCount(); item.charge = iter.getCellRef().getCharge(); item.enchantmentCharge = iter.getCellRef().getEnchantmentCharge(); item.soul = iter.getCellRef().getSoul(); inventoryChanges.items.push_back(item); } inventoryChanges.action = InventoryChanges::SET; getNetworking()->getPlayerPacket(ID_PLAYER_INVENTORY)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_INVENTORY)->Send(); } void LocalPlayer::sendItemChange(const mwmp::Item& item, unsigned int action) { LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending item change for %s with action %i, count %i", item.refId.c_str(), action, item.count); inventoryChanges.items.clear(); inventoryChanges.items.push_back(item); inventoryChanges.action = action; getNetworking()->getPlayerPacket(ID_PLAYER_INVENTORY)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_INVENTORY)->Send(); } void LocalPlayer::sendItemChange(const MWWorld::Ptr& itemPtr, int count, unsigned int action) { mwmp::Item item = MechanicsHelper::getItem(itemPtr, count); sendItemChange(item, action); } void LocalPlayer::sendItemChange(const std::string& refId, int count, unsigned int action) { LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending item change for %s with action %i, count %i", refId.c_str(), action, count); inventoryChanges.items.clear(); mwmp::Item item; item.refId = refId; item.count = count; item.charge = -1; item.enchantmentCharge = -1; item.soul = ""; inventoryChanges.items.push_back(item); inventoryChanges.action = action; getNetworking()->getPlayerPacket(ID_PLAYER_INVENTORY)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_INVENTORY)->Send(); } void LocalPlayer::sendSpellbook() { MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWMechanics::Spells &ptrSpells = ptrPlayer.getClass().getCreatureStats(ptrPlayer).getSpells(); spellbookChanges.spells.clear(); // Send spells in spellbook, while ignoring abilities, powers, etc. for (const auto &spell : ptrSpells) { if (spell.first->mData.mType == ESM::Spell::ST_Spell) spellbookChanges.spells.push_back(*spell.first); } spellbookChanges.action = SpellbookChanges::SET; getNetworking()->getPlayerPacket(ID_PLAYER_SPELLBOOK)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_SPELLBOOK)->Send(); } void LocalPlayer::sendSpellChange(std::string id, unsigned int action) { // Skip any bugged spells that somehow have clientside-only dynamic IDs if (id.find("$dynamic") != string::npos) return; spellbookChanges.spells.clear(); ESM::Spell spell; spell.mId = id; spellbookChanges.spells.push_back(spell); spellbookChanges.action = action; getNetworking()->getPlayerPacket(ID_PLAYER_SPELLBOOK)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_SPELLBOOK)->Send(); } void LocalPlayer::sendQuickKey(unsigned short slot, int type, const std::string& itemId) { quickKeyChanges.quickKeys.clear(); mwmp::QuickKey quickKey; quickKey.slot = slot; quickKey.type = type; quickKey.itemId = itemId; LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_PLAYER_QUICKKEYS", itemId.c_str()); LOG_APPEND(Log::LOG_INFO, "- slot: %i, type: %i, itemId: %s", quickKey.slot, quickKey.type, quickKey.itemId.c_str()); quickKeyChanges.quickKeys.push_back(quickKey); getNetworking()->getPlayerPacket(ID_PLAYER_QUICKKEYS)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_QUICKKEYS)->Send(); } void LocalPlayer::sendJournalEntry(const std::string& quest, int index, const MWWorld::Ptr& actor) { journalChanges.journalItems.clear(); mwmp::JournalItem journalItem; journalItem.type = JournalItem::ENTRY; journalItem.quest = quest; journalItem.index = index; journalItem.actorRefId = actor.getCellRef().getRefId(); journalItem.hasTimestamp = false; journalChanges.journalItems.push_back(journalItem); getNetworking()->getPlayerPacket(ID_PLAYER_JOURNAL)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_JOURNAL)->Send(); } void LocalPlayer::sendJournalIndex(const std::string& quest, int index) { journalChanges.journalItems.clear(); mwmp::JournalItem journalItem; journalItem.type = JournalItem::INDEX; journalItem.quest = quest; journalItem.index = index; journalChanges.journalItems.push_back(journalItem); getNetworking()->getPlayerPacket(ID_PLAYER_JOURNAL)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_JOURNAL)->Send(); } void LocalPlayer::sendFactionRank(const std::string& factionId, int rank) { factionChanges.factions.clear(); factionChanges.action = FactionChanges::RANK; mwmp::Faction faction; faction.factionId = factionId; faction.rank = rank; factionChanges.factions.push_back(faction); getNetworking()->getPlayerPacket(ID_PLAYER_FACTION)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_FACTION)->Send(); } void LocalPlayer::sendFactionExpulsionState(const std::string& factionId, bool isExpelled) { factionChanges.factions.clear(); factionChanges.action = FactionChanges::EXPULSION; mwmp::Faction faction; faction.factionId = factionId; faction.isExpelled = isExpelled; factionChanges.factions.push_back(faction); getNetworking()->getPlayerPacket(ID_PLAYER_FACTION)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_FACTION)->Send(); } void LocalPlayer::sendFactionReputation(const std::string& factionId, int reputation) { factionChanges.factions.clear(); factionChanges.action = FactionChanges::REPUTATION; mwmp::Faction faction; faction.factionId = factionId; faction.reputation = reputation; factionChanges.factions.push_back(faction); getNetworking()->getPlayerPacket(ID_PLAYER_FACTION)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_FACTION)->Send(); } void LocalPlayer::sendTopic(const std::string& topicId) { topicChanges.topics.clear(); mwmp::Topic topic; // For translated versions of the game, make sure we translate the topic back into English first if (MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().hasTranslation()) topic.topicId = MWBase::Environment::get().getWindowManager()->getTranslationDataStorage().topicID(topicId); else topic.topicId = topicId; LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_PLAYER_TOPIC with topic %s", topic.topicId.c_str()); topicChanges.topics.push_back(topic); getNetworking()->getPlayerPacket(ID_PLAYER_TOPIC)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_TOPIC)->Send(); } void LocalPlayer::sendKill(const std::string& refId, int number) { killChanges.kills.clear(); mwmp::Kill kill; kill.refId = refId; kill.number = number; LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_WORLD_KILL_COUNT with refId %s, number %i", refId.c_str(), number); killChanges.kills.push_back(kill); getNetworking()->getPlayerPacket(ID_WORLD_KILL_COUNT)->setPlayer(this); getNetworking()->getPlayerPacket(ID_WORLD_KILL_COUNT)->Send(); } void LocalPlayer::sendBook(const std::string& bookId) { bookChanges.books.clear(); mwmp::Book book; book.bookId = bookId; LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_PLAYER_BOOK with book %s", book.bookId.c_str()); bookChanges.books.push_back(book); getNetworking()->getPlayerPacket(ID_PLAYER_BOOK)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_BOOK)->Send(); } void LocalPlayer::sendWerewolfState(bool werewolfState) { isWerewolf = werewolfState; LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_PLAYER_SHAPESHIFT with isWerewolf of %s", isWerewolf ? "true" : "false"); getNetworking()->getPlayerPacket(ID_PLAYER_SHAPESHIFT)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_SHAPESHIFT)->Send(); } void LocalPlayer::sendMarkLocation(const ESM::Cell& newMarkCell, const ESM::Position& newMarkPosition) { miscellaneousChangeType = mwmp::MISCELLANEOUS_CHANGE_TYPE::MARK_LOCATION; markCell = newMarkCell; markPosition = newMarkPosition; getNetworking()->getPlayerPacket(ID_PLAYER_MISCELLANEOUS)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_MISCELLANEOUS)->Send(); } void LocalPlayer::sendSelectedSpell(const std::string& newSelectedSpellId) { miscellaneousChangeType = mwmp::MISCELLANEOUS_CHANGE_TYPE::SELECTED_SPELL; selectedSpellId = newSelectedSpellId; getNetworking()->getPlayerPacket(ID_PLAYER_MISCELLANEOUS)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_MISCELLANEOUS)->Send(); } void LocalPlayer::sendItemUse(const MWWorld::Ptr& itemPtr) { usedItem.refId = itemPtr.getCellRef().getRefId(); usedItem.count = itemPtr.getRefData().getCount(); usedItem.charge = itemPtr.getCellRef().getCharge(); usedItem.enchantmentCharge = itemPtr.getCellRef().getEnchantmentCharge(); usedItem.soul = itemPtr.getCellRef().getSoul(); getNetworking()->getPlayerPacket(ID_PLAYER_ITEM_USE)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_ITEM_USE)->Send(); } void LocalPlayer::sendCellStates() { LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_PLAYER_CELL_STATE to server"); getNetworking()->getPlayerPacket(ID_PLAYER_CELL_STATE)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_CELL_STATE)->Send(); } void LocalPlayer::clearCellStates() { cellStateChanges.cellStates.clear(); } void LocalPlayer::clearCurrentContainer() { currentContainer.refId = ""; currentContainer.refNum = 0; currentContainer.mpNum = 0; } void LocalPlayer::storeCellState(const ESM::Cell& cell, int stateType) { std::vector::iterator iter; for (iter = cellStateChanges.cellStates.begin(); iter != cellStateChanges.cellStates.end(); ) { // If there's already a cell state recorded for this particular cell, // remove it if (cell.getDescription() == (*iter).cell.getDescription()) iter = cellStateChanges.cellStates.erase(iter); else ++iter; } CellState cellState; cellState.cell = cell; cellState.type = stateType; cellStateChanges.cellStates.push_back(cellState); } void LocalPlayer::storeCurrentContainer(const MWWorld::Ptr &container) { currentContainer.refId = container.getCellRef().getRefId(); currentContainer.refNum = container.getCellRef().getRefNum().mIndex; currentContainer.mpNum = container.getCellRef().getMpNum(); } void LocalPlayer::playAnimation() { MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(getPlayerPtr(), animation.groupname, animation.mode, animation.count, animation.persist); isPlayingAnimation = true; } void LocalPlayer::playSpeech() { MWBase::Environment::get().getSoundManager()->say(getPlayerPtr(), sound); MWBase::WindowManager *winMgr = MWBase::Environment::get().getWindowManager(); if (winMgr->getSubtitlesEnabled()) winMgr->messageBox(MWBase::Environment::get().getDialogueManager()->getVoiceCaption(sound), MWGui::ShowInDialogueMode_Never); }