mirror of https://github.com/OpenMW/openmw.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
260 lines
12 KiB
C++
260 lines
12 KiB
C++
#include "types.hpp"
|
|
|
|
#include <components/lua/luastate.hpp>
|
|
#include <components/detournavigator/agentbounds.hpp>
|
|
|
|
#include <apps/openmw/mwmechanics/drawstate.hpp>
|
|
#include <apps/openmw/mwmechanics/creaturestats.hpp>
|
|
#include <apps/openmw/mwworld/inventorystore.hpp>
|
|
#include <apps/openmw/mwworld/class.hpp>
|
|
|
|
#include "../luabindings.hpp"
|
|
#include "../localscripts.hpp"
|
|
#include "../luamanagerimp.hpp"
|
|
#include "../stats.hpp"
|
|
|
|
namespace MWLua
|
|
{
|
|
namespace
|
|
{
|
|
class SetEquipmentAction final : public LuaManager::Action
|
|
{
|
|
public:
|
|
using Item = std::variant<std::string, ObjectId>; // recordId or ObjectId
|
|
using Equipment = std::map<int, Item>; // slot to item
|
|
|
|
SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment)
|
|
: Action(state), mActor(actor), mEquipment(std::move(equipment)) {}
|
|
|
|
void apply(WorldView& worldView) const override
|
|
{
|
|
MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, false);
|
|
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
|
|
std::array<bool, MWWorld::InventoryStore::Slots> usedSlots;
|
|
std::fill(usedSlots.begin(), usedSlots.end(), false);
|
|
|
|
static constexpr int anySlot = -1;
|
|
auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool
|
|
{
|
|
auto old_it = slot != anySlot ? store.getSlot(slot) : store.end();
|
|
MWWorld::Ptr itemPtr;
|
|
if (std::holds_alternative<ObjectId>(item))
|
|
{
|
|
itemPtr = worldView.getObjectRegistry()->getPtr(std::get<ObjectId>(item), false);
|
|
if (old_it != store.end() && *old_it == itemPtr)
|
|
return true; // already equipped
|
|
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 ||
|
|
itemPtr.getContainerStore() != static_cast<const MWWorld::ContainerStore*>(&store))
|
|
{
|
|
Log(Debug::Warning) << "Object" << idToString(std::get<ObjectId>(item)) << " is not in inventory";
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const std::string& recordId = std::get<std::string>(item);
|
|
if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId)
|
|
return true; // already equipped
|
|
itemPtr = store.search(recordId);
|
|
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0)
|
|
{
|
|
Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr);
|
|
bool requestedSlotIsAllowed = std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end();
|
|
if (!requestedSlotIsAllowed)
|
|
{
|
|
auto firstAllowed = std::find_if(allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; });
|
|
if (firstAllowed == allowedSlots.end())
|
|
{
|
|
Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr);
|
|
return false;
|
|
}
|
|
slot = *firstAllowed;
|
|
}
|
|
|
|
// TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search.
|
|
MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr);
|
|
if (it == store.end()) // should never happen
|
|
throw std::logic_error("Item not found in container");
|
|
|
|
store.equip(slot, it, actor);
|
|
return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed
|
|
};
|
|
|
|
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
|
{
|
|
auto old_it = store.getSlot(slot);
|
|
auto new_it = mEquipment.find(slot);
|
|
if (new_it == mEquipment.end())
|
|
{
|
|
if (old_it != store.end())
|
|
store.unequipSlot(slot, actor);
|
|
continue;
|
|
}
|
|
if (tryEquipToSlot(slot, new_it->second))
|
|
usedSlots[slot] = true;
|
|
}
|
|
for (const auto& [slot, item] : mEquipment)
|
|
if (slot >= MWWorld::InventoryStore::Slots)
|
|
tryEquipToSlot(anySlot, item);
|
|
}
|
|
|
|
std::string toString() const override { return "SetEquipmentAction"; }
|
|
|
|
private:
|
|
ObjectId mActor;
|
|
Equipment mEquipment;
|
|
};
|
|
}
|
|
|
|
using SelfObject = LocalScripts::SelfObject;
|
|
|
|
void addActorBindings(sol::table actor, const Context& context)
|
|
{
|
|
actor["STANCE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs<std::string_view, MWMechanics::DrawState>({
|
|
{"Nothing", MWMechanics::DrawState::Nothing},
|
|
{"Weapon", MWMechanics::DrawState::Weapon},
|
|
{"Spell", MWMechanics::DrawState::Spell},
|
|
}));
|
|
actor["EQUIPMENT_SLOT"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs<std::string_view, int>({
|
|
{"Helmet", MWWorld::InventoryStore::Slot_Helmet},
|
|
{"Cuirass", MWWorld::InventoryStore::Slot_Cuirass},
|
|
{"Greaves", MWWorld::InventoryStore::Slot_Greaves},
|
|
{"LeftPauldron", MWWorld::InventoryStore::Slot_LeftPauldron},
|
|
{"RightPauldron", MWWorld::InventoryStore::Slot_RightPauldron},
|
|
{"LeftGauntlet", MWWorld::InventoryStore::Slot_LeftGauntlet},
|
|
{"RightGauntlet", MWWorld::InventoryStore::Slot_RightGauntlet},
|
|
{"Boots", MWWorld::InventoryStore::Slot_Boots},
|
|
{"Shirt", MWWorld::InventoryStore::Slot_Shirt},
|
|
{"Pants", MWWorld::InventoryStore::Slot_Pants},
|
|
{"Skirt", MWWorld::InventoryStore::Slot_Skirt},
|
|
{"Robe", MWWorld::InventoryStore::Slot_Robe},
|
|
{"LeftRing", MWWorld::InventoryStore::Slot_LeftRing},
|
|
{"RightRing", MWWorld::InventoryStore::Slot_RightRing},
|
|
{"Amulet", MWWorld::InventoryStore::Slot_Amulet},
|
|
{"Belt", MWWorld::InventoryStore::Slot_Belt},
|
|
{"CarriedRight", MWWorld::InventoryStore::Slot_CarriedRight},
|
|
{"CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft},
|
|
{"Ammunition", MWWorld::InventoryStore::Slot_Ammunition}
|
|
}));
|
|
|
|
actor["stance"] = [](const Object& o)
|
|
{
|
|
const MWWorld::Class& cls = o.ptr().getClass();
|
|
if (cls.isActor())
|
|
return cls.getCreatureStats(o.ptr()).getDrawState();
|
|
else
|
|
throw std::runtime_error("Actor expected");
|
|
};
|
|
|
|
actor["canMove"] = [](const Object& o)
|
|
{
|
|
const MWWorld::Class& cls = o.ptr().getClass();
|
|
return cls.getMaxSpeed(o.ptr()) > 0;
|
|
};
|
|
actor["runSpeed"] = [](const Object& o)
|
|
{
|
|
const MWWorld::Class& cls = o.ptr().getClass();
|
|
return cls.getRunSpeed(o.ptr());
|
|
};
|
|
actor["walkSpeed"] = [](const Object& o)
|
|
{
|
|
const MWWorld::Class& cls = o.ptr().getClass();
|
|
return cls.getWalkSpeed(o.ptr());
|
|
};
|
|
actor["currentSpeed"] = [](const Object& o)
|
|
{
|
|
const MWWorld::Class& cls = o.ptr().getClass();
|
|
return cls.getCurrentSpeed(o.ptr());
|
|
};
|
|
|
|
actor["isOnGround"] = [](const LObject& o)
|
|
{
|
|
return MWBase::Environment::get().getWorld()->isOnGround(o.ptr());
|
|
};
|
|
actor["isSwimming"] = [](const LObject& o)
|
|
{
|
|
return MWBase::Environment::get().getWorld()->isSwimming(o.ptr());
|
|
};
|
|
|
|
actor["inventory"] = sol::overload(
|
|
[](const LObject& o) { return Inventory<LObject>{o}; },
|
|
[](const GObject& o) { return Inventory<GObject>{o}; }
|
|
);
|
|
auto getAllEquipment = [context](const Object& o)
|
|
{
|
|
const MWWorld::Ptr& ptr = o.ptr();
|
|
sol::table equipment(context.mLua->sol(), sol::create);
|
|
if (!ptr.getClass().hasInventoryStore(ptr))
|
|
return equipment;
|
|
|
|
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
|
|
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
|
{
|
|
auto it = store.getSlot(slot);
|
|
if (it == store.end())
|
|
continue;
|
|
context.mWorldView->getObjectRegistry()->registerPtr(*it);
|
|
equipment[slot] = o.getObject(context.mLua->sol(), getId(*it));
|
|
}
|
|
return equipment;
|
|
};
|
|
auto getEquipmentFromSlot = [context](const Object& o, int slot) -> sol::object
|
|
{
|
|
const MWWorld::Ptr& ptr = o.ptr();
|
|
sol::table equipment(context.mLua->sol(), sol::create);
|
|
if (!ptr.getClass().hasInventoryStore(ptr))
|
|
return sol::nil;
|
|
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
|
|
auto it = store.getSlot(slot);
|
|
if (it == store.end())
|
|
return sol::nil;
|
|
context.mWorldView->getObjectRegistry()->registerPtr(*it);
|
|
return o.getObject(context.mLua->sol(), getId(*it));
|
|
};
|
|
actor["equipment"] = sol::overload(getAllEquipment, getEquipmentFromSlot);
|
|
actor["hasEquipped"] = [](const Object& o, const Object& item)
|
|
{
|
|
const MWWorld::Ptr& ptr = o.ptr();
|
|
if (!ptr.getClass().hasInventoryStore(ptr))
|
|
return false;
|
|
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
|
|
return store.isEquipped(item.ptr());
|
|
};
|
|
actor["setEquipment"] = [context](const SelfObject& obj, const sol::table& equipment)
|
|
{
|
|
if (!obj.ptr().getClass().hasInventoryStore(obj.ptr()))
|
|
{
|
|
if (!equipment.empty())
|
|
throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots");
|
|
return;
|
|
}
|
|
SetEquipmentAction::Equipment eqp;
|
|
for (auto& [key, value] : equipment)
|
|
{
|
|
int slot = key.as<int>();
|
|
if (value.is<Object>())
|
|
eqp[slot] = value.as<Object>().id();
|
|
else
|
|
eqp[slot] = value.as<std::string>();
|
|
}
|
|
context.mLuaManager->addAction(std::make_unique<SetEquipmentAction>(context.mLua, obj.id(), std::move(eqp)));
|
|
};
|
|
actor["getPathfindingAgentBounds"] = [context](const LObject& o)
|
|
{
|
|
const DetourNavigator::AgentBounds agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(o.ptr());
|
|
sol::table result = context.mLua->newTable();
|
|
result["shapeType"] = agentBounds.mShapeType;
|
|
result["halfExtents"] = agentBounds.mHalfExtents;
|
|
return result;
|
|
};
|
|
|
|
addActorStatsBindings(actor, context);
|
|
}
|
|
|
|
}
|