mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-20 05:53:50 +00:00
d9dd7073cf
Previously, client mods adding packet-sending scripts to the spawn area made clients send the associated packets as soon as they inputted their character name when joining a server using those mods. This made the clients either get disconnected for not replying to a handshake first, or it made them get kicked for sending object packets that are disallowed for players who are not logged in. To fix this, LocalPlayer's hasFinishedCharGen() has been replaced with isLoggedIn(), because the former was already returning true when players inputted their names.
474 lines
21 KiB
C++
474 lines
21 KiB
C++
#include "containerextensions.hpp"
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <boost/format.hpp>
|
|
|
|
#include <MyGUI_LanguageManager.h>
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Include additional headers for multiplayer purposes
|
|
*/
|
|
#include "../mwmp/Main.hpp"
|
|
#include "../mwmp/Networking.hpp"
|
|
#include "../mwmp/LocalPlayer.hpp"
|
|
#include "../mwmp/PlayerList.hpp"
|
|
#include "../mwmp/ObjectList.hpp"
|
|
#include "../mwmp/ScriptController.hpp"
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
#include <components/compiler/extensions.hpp>
|
|
#include <components/compiler/opcodes.hpp>
|
|
|
|
#include <components/interpreter/interpreter.hpp>
|
|
#include <components/interpreter/opcodes.hpp>
|
|
|
|
#include <components/misc/stringops.hpp>
|
|
|
|
#include <components/esm/loadskil.hpp>
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
|
|
#include "../mwworld/actionequip.hpp"
|
|
#include "../mwworld/class.hpp"
|
|
#include "../mwworld/containerstore.hpp"
|
|
#include "../mwworld/inventorystore.hpp"
|
|
|
|
#include "../mwmechanics/actorutil.hpp"
|
|
|
|
#include "interpretercontext.hpp"
|
|
#include "ref.hpp"
|
|
|
|
namespace MWScript
|
|
{
|
|
namespace Container
|
|
{
|
|
template<class R>
|
|
class OpAddItem : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute (Interpreter::Runtime& runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
|
runtime.pop();
|
|
|
|
Interpreter::Type_Integer count = runtime[0].mInteger;
|
|
runtime.pop();
|
|
|
|
if (count<0)
|
|
throw std::runtime_error ("second argument for AddItem must be non-negative");
|
|
|
|
// no-op
|
|
if (count == 0)
|
|
return;
|
|
|
|
if(::Misc::StringUtils::ciEqual(item, "gold_005")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_010")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_025")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_100"))
|
|
item = "gold_001";
|
|
|
|
/*
|
|
Start of tes3mp change (major)
|
|
|
|
Disable unilateral item addition on this client and expect the server's reply to our
|
|
packet to do it instead, except for changes to player inventories which still require
|
|
the PlayerInventory to be reworked
|
|
*/
|
|
// Spawn a messagebox (only for items added to player's inventory and if player is talking to someone)
|
|
if (ptr == MWBase::Environment::get().getWorld ()->getPlayerPtr() )
|
|
{
|
|
MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore(ptr).add(item, count, ptr);
|
|
/*
|
|
End of tes3mp change (major)
|
|
*/
|
|
|
|
// The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory
|
|
std::string msgBox;
|
|
std::string itemName = itemPtr.getClass().getName(itemPtr);
|
|
if (count == 1)
|
|
{
|
|
msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}");
|
|
msgBox = boost::str(boost::format(msgBox) % itemName);
|
|
}
|
|
else
|
|
{
|
|
msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage61}");
|
|
msgBox = boost::str(boost::format(msgBox) % count % itemName);
|
|
}
|
|
MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only);
|
|
}
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Send an ID_CONTAINER packet every time an item is added to a Ptr
|
|
that doesn't belong to a DedicatedPlayer
|
|
*/
|
|
else if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() &&
|
|
(!ptr.getClass().isActor() || !mwmp::PlayerList::isDedicatedPlayer(ptr)))
|
|
{
|
|
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
|
|
objectList->reset();
|
|
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
|
|
objectList->cell = *ptr.getCell()->getCell();
|
|
objectList->action = mwmp::BaseObjectList::ADD;
|
|
objectList->containerSubAction = mwmp::BaseObjectList::NONE;
|
|
|
|
mwmp::BaseObject baseObject = objectList->getBaseObject(ptr);
|
|
mwmp::ContainerItem containerItem;
|
|
containerItem.refId = item;
|
|
containerItem.count = count;
|
|
containerItem.charge = -1;
|
|
containerItem.enchantmentCharge = -1;
|
|
containerItem.soul = "";
|
|
|
|
baseObject.containerItems.push_back(containerItem);
|
|
objectList->addObject(baseObject);
|
|
objectList->sendContainer();
|
|
}
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
}
|
|
};
|
|
|
|
template<class R>
|
|
class OpGetItemCount : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute (Interpreter::Runtime& runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
|
runtime.pop();
|
|
|
|
if(::Misc::StringUtils::ciEqual(item, "gold_005")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_010")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_025")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_100"))
|
|
item = "gold_001";
|
|
|
|
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr);
|
|
|
|
runtime.push (store.count(item));
|
|
}
|
|
};
|
|
|
|
template<class R>
|
|
class OpRemoveItem : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute (Interpreter::Runtime& runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
|
runtime.pop();
|
|
|
|
Interpreter::Type_Integer count = runtime[0].mInteger;
|
|
runtime.pop();
|
|
|
|
if (count<0)
|
|
throw std::runtime_error ("second argument for RemoveItem must be non-negative");
|
|
|
|
// no-op
|
|
if (count == 0)
|
|
return;
|
|
|
|
if(::Misc::StringUtils::ciEqual(item, "gold_005")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_010")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_025")
|
|
|| ::Misc::StringUtils::ciEqual(item, "gold_100"))
|
|
item = "gold_001";
|
|
|
|
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr);
|
|
|
|
std::string itemName;
|
|
for (MWWorld::ConstContainerStoreIterator iter(store.cbegin()); iter != store.cend(); ++iter)
|
|
if (::Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), item))
|
|
itemName = iter->getClass().getName(*iter);
|
|
|
|
/*
|
|
Start of tes3mp change (major)
|
|
|
|
Disable unilateral item removal on this client and expect the server's reply to our
|
|
packet to do it instead, except for changes to player inventories which still require
|
|
the PlayerInventory to be reworked
|
|
*/
|
|
int numRemoved = 0;
|
|
|
|
if (ptr == MWMechanics::getPlayer())
|
|
numRemoved = store.remove(item, count, ptr);
|
|
|
|
// Spawn a messagebox (only for items removed from player's inventory)
|
|
if ((numRemoved > 0)
|
|
&& (ptr == MWMechanics::getPlayer()))
|
|
{
|
|
/*
|
|
End of tes3mp change (major)
|
|
*/
|
|
|
|
// The two GMST entries below expand to strings informing the player of what, and how many of it has been removed from their inventory
|
|
std::string msgBox;
|
|
|
|
if(numRemoved > 1)
|
|
{
|
|
msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}");
|
|
msgBox = boost::str (boost::format(msgBox) % numRemoved % itemName);
|
|
}
|
|
else
|
|
{
|
|
msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage62}");
|
|
msgBox = boost::str (boost::format(msgBox) % itemName);
|
|
}
|
|
MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only);
|
|
}
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Send an ID_CONTAINER packet every time an item is removed from a Ptr
|
|
that doesn't belong to a DedicatedPlayer
|
|
*/
|
|
else if (mwmp::Main::get().getLocalPlayer()->isLoggedIn() &&
|
|
(!ptr.getClass().isActor() || !mwmp::PlayerList::isDedicatedPlayer(ptr)))
|
|
{
|
|
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
|
|
objectList->reset();
|
|
objectList->packetOrigin = ScriptController::getPacketOriginFromContextType(runtime.getContext().getContextType());
|
|
objectList->cell = *ptr.getCell()->getCell();
|
|
objectList->action = mwmp::BaseObjectList::REMOVE;
|
|
objectList->containerSubAction = mwmp::BaseObjectList::NONE;
|
|
|
|
mwmp::BaseObject baseObject = objectList->getBaseObject(ptr);
|
|
mwmp::ContainerItem containerItem;
|
|
containerItem.refId = item;
|
|
containerItem.count = 0;
|
|
containerItem.actionCount = count;
|
|
containerItem.charge = -1;
|
|
containerItem.enchantmentCharge = -1;
|
|
containerItem.soul = "";
|
|
|
|
baseObject.containerItems.push_back(containerItem);
|
|
objectList->addObject(baseObject);
|
|
objectList->sendContainer();
|
|
}
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
}
|
|
};
|
|
|
|
template <class R>
|
|
class OpEquip : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute(Interpreter::Runtime &runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
|
runtime.pop();
|
|
|
|
MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
|
|
MWWorld::ContainerStoreIterator it = invStore.begin();
|
|
for (; it != invStore.end(); ++it)
|
|
{
|
|
if (::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item))
|
|
break;
|
|
}
|
|
if (it == invStore.end())
|
|
{
|
|
it = ptr.getClass().getContainerStore (ptr).add (item, 1, ptr);
|
|
std::cerr << "Implicitly adding one " << item << " to container "
|
|
"to fulfil requirements of Equip instruction" << std::endl;
|
|
}
|
|
|
|
if (ptr == MWMechanics::getPlayer())
|
|
MWBase::Environment::get().getWindowManager()->useItem(*it, true);
|
|
else
|
|
{
|
|
std::shared_ptr<MWWorld::Action> action = it->getClass().use(*it, true);
|
|
action->execute(ptr, true);
|
|
}
|
|
}
|
|
};
|
|
|
|
template <class R>
|
|
class OpGetArmorType : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute(Interpreter::Runtime &runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
Interpreter::Type_Integer location = runtime[0].mInteger;
|
|
runtime.pop();
|
|
|
|
int slot;
|
|
switch (location)
|
|
{
|
|
case 0:
|
|
slot = MWWorld::InventoryStore::Slot_Helmet;
|
|
break;
|
|
case 1:
|
|
slot = MWWorld::InventoryStore::Slot_Cuirass;
|
|
break;
|
|
case 2:
|
|
slot = MWWorld::InventoryStore::Slot_LeftPauldron;
|
|
break;
|
|
case 3:
|
|
slot = MWWorld::InventoryStore::Slot_RightPauldron;
|
|
break;
|
|
case 4:
|
|
slot = MWWorld::InventoryStore::Slot_Greaves;
|
|
break;
|
|
case 5:
|
|
slot = MWWorld::InventoryStore::Slot_Boots;
|
|
break;
|
|
case 6:
|
|
slot = MWWorld::InventoryStore::Slot_LeftGauntlet;
|
|
break;
|
|
case 7:
|
|
slot = MWWorld::InventoryStore::Slot_RightGauntlet;
|
|
break;
|
|
case 8:
|
|
slot = MWWorld::InventoryStore::Slot_CarriedLeft; // shield
|
|
break;
|
|
case 9:
|
|
slot = MWWorld::InventoryStore::Slot_LeftGauntlet;
|
|
break;
|
|
case 10:
|
|
slot = MWWorld::InventoryStore::Slot_RightGauntlet;
|
|
break;
|
|
default:
|
|
throw std::runtime_error ("armor index out of range");
|
|
}
|
|
|
|
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
|
|
MWWorld::ConstContainerStoreIterator it = invStore.getSlot (slot);
|
|
|
|
if (it == invStore.end() || it->getTypeName () != typeid(ESM::Armor).name())
|
|
{
|
|
runtime.push(-1);
|
|
return;
|
|
}
|
|
|
|
int skill = it->getClass().getEquipmentSkill (*it) ;
|
|
if (skill == ESM::Skill::HeavyArmor)
|
|
runtime.push(2);
|
|
else if (skill == ESM::Skill::MediumArmor)
|
|
runtime.push(1);
|
|
else if (skill == ESM::Skill::LightArmor)
|
|
runtime.push(0);
|
|
else
|
|
runtime.push(-1);
|
|
}
|
|
};
|
|
|
|
template <class R>
|
|
class OpHasItemEquipped : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute(Interpreter::Runtime &runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
std::string item = runtime.getStringLiteral (runtime[0].mInteger);
|
|
runtime.pop();
|
|
|
|
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
|
|
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
|
{
|
|
MWWorld::ConstContainerStoreIterator it = invStore.getSlot (slot);
|
|
if (it != invStore.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), item))
|
|
{
|
|
runtime.push(1);
|
|
return;
|
|
}
|
|
}
|
|
runtime.push(0);
|
|
}
|
|
};
|
|
|
|
template <class R>
|
|
class OpHasSoulGem : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute(Interpreter::Runtime &runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
const std::string &name = runtime.getStringLiteral (runtime[0].mInteger);
|
|
runtime.pop();
|
|
|
|
int count = 0;
|
|
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
|
|
for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(MWWorld::ContainerStore::Type_Miscellaneous);
|
|
it != invStore.cend(); ++it)
|
|
{
|
|
if (::Misc::StringUtils::ciEqual(it->getCellRef().getSoul(), name))
|
|
count += it->getRefData().getCount();
|
|
}
|
|
runtime.push(count);
|
|
}
|
|
};
|
|
|
|
template <class R>
|
|
class OpGetWeaponType : public Interpreter::Opcode0
|
|
{
|
|
public:
|
|
|
|
virtual void execute(Interpreter::Runtime &runtime)
|
|
{
|
|
MWWorld::Ptr ptr = R()(runtime);
|
|
|
|
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore (ptr);
|
|
MWWorld::ConstContainerStoreIterator it = invStore.getSlot (MWWorld::InventoryStore::Slot_CarriedRight);
|
|
if (it == invStore.end() || it->getTypeName () != typeid(ESM::Weapon).name())
|
|
{
|
|
runtime.push(-1);
|
|
return;
|
|
}
|
|
|
|
runtime.push(it->get<ESM::Weapon>()->mBase->mData.mType);
|
|
}
|
|
};
|
|
|
|
|
|
void installOpcodes (Interpreter::Interpreter& interpreter)
|
|
{
|
|
interpreter.installSegment5 (Compiler::Container::opcodeAddItem, new OpAddItem<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeAddItemExplicit, new OpAddItem<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeGetItemCount, new OpGetItemCount<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeGetItemCountExplicit, new OpGetItemCount<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeRemoveItem, new OpRemoveItem<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeRemoveItemExplicit, new OpRemoveItem<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeEquip, new OpEquip<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeEquipExplicit, new OpEquip<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeGetArmorType, new OpGetArmorType<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeGetArmorTypeExplicit, new OpGetArmorType<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeHasItemEquipped, new OpHasItemEquipped<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeHasItemEquippedExplicit, new OpHasItemEquipped<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeHasSoulGem, new OpHasSoulGem<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeHasSoulGemExplicit, new OpHasSoulGem<ExplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeGetWeaponType, new OpGetWeaponType<ImplicitRef>);
|
|
interpreter.installSegment5 (Compiler::Container::opcodeGetWeaponTypeExplicit, new OpGetWeaponType<ExplicitRef>);
|
|
}
|
|
}
|
|
}
|