diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 55331d933..a5c3a3b2a 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -908,23 +908,8 @@ namespace MWGui window->onFrame(frameDuration); } - /* - Start of tes3mp change (major) - - Custom GUI elements added by TES3MP often cause a crash here when their - mMainWidget becomes null, so a temporary fix has been added until a - more appropriate solution is researched - */ if (!mCurrentModals.empty()) - { - if (mCurrentModals.back()->mMainWidget != 0) - mCurrentModals.back()->onFrame(frameDuration); - else - mCurrentModals.pop_back(); - } - /* - End of tes3mp change (major) - */ + mCurrentModals.back()->onFrame(frameDuration); mKeyboardNavigation->onFrame(); diff --git a/apps/openmw/mwmp/DedicatedActor.cpp b/apps/openmw/mwmp/DedicatedActor.cpp index b49cc6dce..bc5d95890 100644 --- a/apps/openmw/mwmp/DedicatedActor.cpp +++ b/apps/openmw/mwmp/DedicatedActor.cpp @@ -168,6 +168,11 @@ void DedicatedActor::setEquipment() for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { + int count = equipedItems[slot].count; + + // If we've somehow received a corrupted item with a count lower than 0, ignore it + if (count < 0) continue; + MWWorld::ContainerStoreIterator it = invStore.getSlot(slot); const string &packetRefId = equipmentItems[slot].refId; @@ -188,8 +193,6 @@ void DedicatedActor::setEquipment() if (packetRefId.empty() || equal) continue; - int count = equipmentItems[slot].count; - if (hasItem(packetRefId, packetCharge)) equipItem(packetRefId, packetCharge); else diff --git a/apps/openmw/mwmp/GUIController.cpp b/apps/openmw/mwmp/GUIController.cpp index 57921ce34..da1f0ce7f 100644 --- a/apps/openmw/mwmp/GUIController.cpp +++ b/apps/openmw/mwmp/GUIController.cpp @@ -60,6 +60,15 @@ void mwmp::GUIController::cleanUp() mChat = nullptr; } +void mwmp::GUIController::refreshGuiMode(MWGui::GuiMode guiMode) +{ + if (MWBase::Environment::get().getWindowManager()->containsMode(guiMode)) + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(guiMode); + MWBase::Environment::get().getWindowManager()->pushGuiMode(guiMode); + } +} + void mwmp::GUIController::setupChat(const Settings::Manager &mgr) { assert(mChat == nullptr); @@ -120,8 +129,13 @@ void mwmp::GUIController::setChatVisible(bool chatVisible) void mwmp::GUIController::showDialogList(const mwmp::BasePlayer::GUIMessageBox &guiMessageBox) { MWBase::WindowManager *windowManager = MWBase::Environment::get().getWindowManager(); - windowManager->removeDialog(mListBox); - mListBox = nullptr; + + if (mListBox != NULL) + { + windowManager->removeDialog(mListBox); + windowManager->removeCurrentModal(mListBox); + mListBox = NULL; + } std::vector list; diff --git a/apps/openmw/mwmp/GUIController.hpp b/apps/openmw/mwmp/GUIController.hpp index 730e4fd7f..1f496a731 100644 --- a/apps/openmw/mwmp/GUIController.hpp +++ b/apps/openmw/mwmp/GUIController.hpp @@ -37,6 +37,9 @@ namespace mwmp GUIController(); ~GUIController(); void cleanUp(); + + void refreshGuiMode(MWGui::GuiMode guiMode); + void setupChat(const Settings::Manager &manager); void printChatMessage(const mwmp::Chat &chat); diff --git a/apps/openmw/mwmp/LocalPlayer.cpp b/apps/openmw/mwmp/LocalPlayer.cpp index f3300eff5..1ef0b822c 100644 --- a/apps/openmw/mwmp/LocalPlayer.cpp +++ b/apps/openmw/mwmp/LocalPlayer.cpp @@ -35,6 +35,7 @@ #include "Main.hpp" #include "Networking.hpp" #include "CellController.hpp" +#include "GUIController.hpp" #include "MechanicsHelper.hpp" using namespace mwmp; @@ -428,6 +429,7 @@ void LocalPlayer::updateEquipment(bool forceUpdate) { auto &item = equipmentItems[slot]; MWWorld::ContainerStoreIterator it = invStore.getSlot(slot); + if (it != invStore.end()) { if (!::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), equipmentItems[slot].refId) || forceUpdate) @@ -753,6 +755,61 @@ void LocalPlayer::removeSpells() } } +void LocalPlayer::resurrect() +{ + creatureStats.mDead = false; + + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr ptrPlayer = getPlayerPtr(); + + switch (player->resurrectType) + { + case ResurrectType::Regular: + break; + case ResurrectType::ImperialShrine: + world->teleportToClosestMarker(ptrPlayer, "divinemarker"); + break; + case ResurrectType::TribunalTemple: + world->teleportToClosestMarker(ptrPlayer, "templemarker"); + break; + } + + 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; + + LOG_APPEND(Log::LOG_INFO, "- diedSinceArrestAttempt is now true"); + + // Ensure we unequip any items with constant effects that can put us into an infinite + // death loop + MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::DrainHealth); + MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::FireDamage); + MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::FrostDamage); + MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::ShockDamage); + MechanicsHelper::unequipItemsByEffect(ptrPlayer, ESM::Enchantment::ConstantEffect, ESM::MagicEffect::SunDamage); + + 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) || @@ -781,47 +838,67 @@ void LocalPlayer::setDynamicStats() void LocalPlayer::setAttributes() { - MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr ptrPlayer = world->getPlayerPtr(); + MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWMechanics::CreatureStats *ptrCreatureStats = &ptrPlayer.getClass().getCreatureStats(ptrPlayer); MWMechanics::AttributeValue attributeValue; - for (int i = 0; i < 8; ++i) + 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[i].mMod == 0 && ptrCreatureStats->getAttribute(i).getModifier() > 0) - ptrCreatureStats->getActiveSpells().purgeEffectByArg(ESM::MagicEffect::FortifyAttribute, i); + if (creatureStats.mAttributes[attributeIndex].mMod == 0 && ptrCreatureStats->getAttribute(attributeIndex).getModifier() > 0) + { + ptrCreatureStats->getActiveSpells().purgeEffectByArg(ESM::MagicEffect::FortifyAttribute, attributeIndex); + MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(ptrPlayer); - attributeValue.readState(creatureStats.mAttributes[i]); - ptrCreatureStats->setAttribute(i, attributeValue); + // Is the modifier for this attribute still higher than 0? If so, unequip items that + // fortify the attribute + if (ptrCreatureStats->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]); + ptrCreatureStats->setAttribute(attributeIndex, attributeValue); } } void LocalPlayer::setSkills() { - MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr ptrPlayer = world->getPlayerPtr(); + MWWorld::Ptr ptrPlayer = getPlayerPtr(); MWMechanics::NpcStats *ptrNpcStats = &ptrPlayer.getClass().getNpcStats(ptrPlayer); MWMechanics::SkillValue skillValue; - for (int i = 0; i < 27; ++i) + 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[i].mMod == 0 && ptrNpcStats->getSkill(i).getModifier() > 0) - ptrNpcStats->getActiveSpells().purgeEffectByArg(ESM::MagicEffect::FortifySkill, i); + 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[i]); - ptrNpcStats->setSkill(i, skillValue); + skillValue.readState(npcStats.mSkills[skillIndex]); + ptrNpcStats->setSkill(skillIndex, skillValue); } - for (int i = 0; i < 8; ++i) - ptrNpcStats->setSkillIncrease(i, npcStats.mSkillIncrease[i]); + for (int attributeIndex = 0; attributeIndex < 8; ++attributeIndex) + ptrNpcStats->setSkillIncrease(attributeIndex, npcStats.mSkillIncrease[attributeIndex]); ptrNpcStats->setLevelProgress(npcStats.mLevelProgress); } @@ -918,6 +995,8 @@ void LocalPlayer::setCell() 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; @@ -926,12 +1005,15 @@ void LocalPlayer::setClass() } else { - MWBase::Environment::get().getMechanicsManager()->setPlayerClass(charClass.mId); - - const ESM::Class *existingCharClass = MWBase::Environment::get().getWorld()->getStore().get().find(charClass.mId); + 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()); } } @@ -1074,8 +1156,18 @@ 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); @@ -1145,18 +1237,18 @@ void LocalPlayer::setShapeshift() void LocalPlayer::sendClass() { MWBase::World *world = MWBase::Environment::get().getWorld(); - const ESM::NPC *cpl = world->getPlayerPtr().get()->mBase; - const ESM::Class *cls = world->getStore().get().find(cpl->mClass); + const ESM::NPC *npcBase = world->getPlayerPtr().get()->mBase; + const ESM::Class *esmClass = world->getStore().get().find(npcBase->mClass); - if (cpl->mClass.find("$dynamic") != string::npos) // custom class + if (npcBase->mClass.find("$dynamic") != string::npos) // custom class { charClass.mId = ""; - charClass.mName = cls->mName; - charClass.mDescription = cls->mDescription; - charClass.mData = cls->mData; + charClass.mName = esmClass->mName; + charClass.mDescription = esmClass->mDescription; + charClass.mData = esmClass->mData; } else - charClass.mId = cls->mId; + charClass.mId = esmClass->mId; getNetworking()->getPlayerPacket(ID_PLAYER_CHARCLASS)->setPlayer(this); getNetworking()->getPlayerPacket(ID_PLAYER_CHARCLASS)->Send(); diff --git a/apps/openmw/mwmp/LocalPlayer.hpp b/apps/openmw/mwmp/LocalPlayer.hpp index 442260bd0..22fb7b4ba 100644 --- a/apps/openmw/mwmp/LocalPlayer.hpp +++ b/apps/openmw/mwmp/LocalPlayer.hpp @@ -46,6 +46,8 @@ namespace mwmp void removeItems(); void removeSpells(); + void resurrect(); + void closeInventoryWindows(); void setDynamicStats(); diff --git a/apps/openmw/mwmp/MechanicsHelper.cpp b/apps/openmw/mwmp/MechanicsHelper.cpp index 37c728958..f2eb70c03 100644 --- a/apps/openmw/mwmp/MechanicsHelper.cpp +++ b/apps/openmw/mwmp/MechanicsHelper.cpp @@ -213,3 +213,45 @@ void MechanicsHelper::processAttack(Attack attack, const MWWorld::Ptr& attacker) LOG_APPEND(Log::LOG_VERBOSE, " - success: %d", attack.success); } } + +bool MechanicsHelper::doesEffectListContainEffect(const ESM::EffectList& effectList, short effectId, short attributeId, short skillId) +{ + for (const auto &effect : effectList.mList) + { + if (effect.mEffectID == effectId) + { + if (attributeId == -1 || effect.mAttribute == attributeId) + { + if (skillId == -1 || effect.mSkill == skillId) + { + return true; + } + } + } + } + + return false; +} + +void MechanicsHelper::unequipItemsByEffect(const MWWorld::Ptr& ptr, short enchantmentType, short effectId, short attributeId, short skillId) +{ + MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::InventoryStore &ptrInventory = ptr.getClass().getInventoryStore(ptr); + + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; slot++) + { + if (ptrInventory.getSlot(slot) != ptrInventory.end()) + { + MWWorld::ConstContainerStoreIterator itemIterator = ptrInventory.getSlot(slot); + std::string enchantmentName = itemIterator->getClass().getEnchantment(*itemIterator); + + if (!enchantmentName.empty()) + { + const ESM::Enchantment* enchantment = world->getStore().get().find(enchantmentName); + + if (enchantment->mData.mType == enchantmentType && doesEffectListContainEffect(enchantment->mEffects, effectId, attributeId, skillId)) + ptrInventory.unequipSlot(slot, ptr); + } + } + } +} diff --git a/apps/openmw/mwmp/MechanicsHelper.hpp b/apps/openmw/mwmp/MechanicsHelper.hpp index e4248241f..b707c13fc 100644 --- a/apps/openmw/mwmp/MechanicsHelper.hpp +++ b/apps/openmw/mwmp/MechanicsHelper.hpp @@ -23,6 +23,9 @@ namespace MechanicsHelper bool getSpellSuccess(std::string spellId, const MWWorld::Ptr& caster); void processAttack(mwmp::Attack attack, const MWWorld::Ptr& attacker); + + bool doesEffectListContainEffect(const ESM::EffectList& effectList, short effectId, short attributeId = -1, short skillId = -1); + void unequipItemsByEffect(const MWWorld::Ptr& ptr, short enchantmentType, short effectId, short attributeId = -1, short skillId = -1); } diff --git a/apps/openmw/mwmp/processors/player/ProcessorPlayerResurrect.hpp b/apps/openmw/mwmp/processors/player/ProcessorPlayerResurrect.hpp index f70554c2e..a315bd06f 100644 --- a/apps/openmw/mwmp/processors/player/ProcessorPlayerResurrect.hpp +++ b/apps/openmw/mwmp/processors/player/ProcessorPlayerResurrect.hpp @@ -27,53 +27,7 @@ namespace mwmp { LOG_APPEND(Log::LOG_INFO, "- Packet was about me with resurrectType of %i", (int) player->resurrectType); - player->creatureStats.mDead = false; - - MWBase::World *world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr playerPtr = world->getPlayerPtr(); - - - switch(player->resurrectType) - { - case ResurrectType::Regular: - break; - case ResurrectType::ImperialShrine: - world->teleportToClosestMarker(playerPtr, "divinemarker"); - break; - case ResurrectType::TribunalTemple: - world->teleportToClosestMarker(playerPtr, "templemarker"); - break; - } - - - playerPtr.getClass().getCreatureStats(playerPtr).resurrect(); - - // The player could have died from a hand-to-hand attack, so reset their fatigue - // as well - if (player->creatureStats.mDynamic[2].mMod < 1) - player->creatureStats.mDynamic[2].mMod = 1; - - player->creatureStats.mDynamic[2].mCurrent = player->creatureStats.mDynamic[2].mMod; - MWMechanics::DynamicStat fatigue; - fatigue.readState(player->creatureStats.mDynamic[2]); - playerPtr.getClass().getCreatureStats(playerPtr).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 - playerPtr.getClass().getNpcStats(playerPtr).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 - player->diedSinceArrestAttempt = true; - - LOG_APPEND(Log::LOG_INFO, "- diedSinceArrestAttempt is now true"); - - packet.setPlayer(player); - packet.Send(serverAddr); - - static_cast(player)->updateStatsDynamic(true); - Main::get().getNetworking()->getPlayerPacket(ID_PLAYER_STATS_DYNAMIC)->setPlayer(player); - Main::get().getNetworking()->getPlayerPacket(ID_PLAYER_STATS_DYNAMIC)->Send(serverAddr); + static_cast(player)->resurrect(); } else if (player != 0) {