1
Fork 0
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

715 lines
20 KiB

#include "PapyrusActor.h"
#include "PapyrusArgs.h"
#include "GameForms.h"
#include "GameData.h"
#include "GameObjects.h"
#include "GameReferences.h"
#include "GameExtraData.h"
#include "GameRTTI.h"
#include "GameThreads.h"
#include "HashUtil.h"
#include "NiExtraData.h"
#include "InternalTasks.h"
#include <set>
class MatchBySlot : public FormMatcher
{
UInt32 m_mask;
public:
MatchBySlot(UInt32 slot) :
m_mask(slot)
{
}
bool Matches(TESForm* pForm) const {
if (pForm) {
BGSBipedObjectForm* pBip = DYNAMIC_CAST(pForm, TESForm, BGSBipedObjectForm);
if (pBip) {
return (pBip->data.parts & m_mask) != 0;
}
}
return false;
}
};
class MatchByForm : public FormMatcher
{
TESForm * m_form;
public:
MatchByForm(TESForm * form) : m_form(form) {}
bool Matches(TESForm* pForm) const { return m_form == pForm; }
};
typedef std::set<TESFaction*> FactionRankSet;
class CollectUniqueFactions : public Actor::FactionVisitor
{
public:
CollectUniqueFactions::CollectUniqueFactions(FactionRankSet * rankSet, SInt8 min, SInt8 max) : m_rankSet(rankSet), m_min(min), m_max(max) { }
virtual bool Accept(TESFaction * faction, SInt8 rank)
{
if(rank >= m_min && rank <= m_max)
m_rankSet->insert(faction);
return false;
}
private:
SInt8 m_min;
SInt8 m_max;
FactionRankSet * m_rankSet;
};
bool CanEquipBothHands(Actor* actor, TESForm * item)
{
BGSEquipType * equipType = DYNAMIC_CAST(item, TESForm, BGSEquipType);
if (!equipType)
return false;
BGSEquipSlot * equipSlot = equipType->GetEquipSlot();
if (!equipSlot)
return false;
// 2H
if (equipSlot == GetEitherHandSlot())
{
return true;
}
// 1H
else if (equipSlot == GetLeftHandSlot() || equipSlot == GetRightHandSlot())
{
return (actor->race->data.raceFlags & TESRace::kRace_CanDualWield) && item->IsWeapon();
}
return false;
}
BGSEquipSlot * GetEquipSlotById(SInt32 slotId)
{
enum
{
kSlotId_Default = 0,
kSlotId_Right = 1,
kSlotId_Left = 2
};
if (slotId == kSlotId_Right)
return GetRightHandSlot();
else if (slotId == kSlotId_Left)
return GetLeftHandSlot();
else
return NULL;
}
SInt32 CalcItemId(TESForm * form, BaseExtraList * extraList)
{
if (!form || !extraList)
return 0;
const char * name = extraList->GetDisplayName(form);
// No name in extra data? Use base form name
if (!name)
{
TESFullName* pFullName = DYNAMIC_CAST(form, TESForm, TESFullName);
if (pFullName)
name = pFullName->name.data;
}
if (!name)
return 0;
return (SInt32)HashUtil::CRC32(name, form->formID & 0x00FFFFFF);
}
namespace papyrusActor
{
TESForm* GetWornForm(Actor* thisActor, UInt32 mask)
{
MatchBySlot matcher(mask);
ExtraContainerChanges* pContainerChanges = static_cast<ExtraContainerChanges*>(thisActor->extraData.GetByType(kExtraData_ContainerChanges));
if (pContainerChanges) {
EquipData eqD = pContainerChanges->FindEquipped(matcher);
return eqD.pForm;
}
return NULL;
}
SInt32 GetWornItemId(Actor* thisActor, UInt32 mask)
{
ExtraContainerChanges* containerChanges = static_cast<ExtraContainerChanges*>(thisActor->extraData.GetByType(kExtraData_ContainerChanges));
if (!containerChanges)
return 0;
MatchBySlot matcher(mask);
EquipData equipData = containerChanges->FindEquipped(matcher);
return CalcItemId(equipData.pForm, equipData.pExtraData);
}
TESForm * GetEquippedObject(Actor * thisActor, UInt32 slot)
{
if(!thisActor) return NULL;
enum
{
kSlotID_Left = 0,
kSlotID_Right,
kSlotID_Voice,
};
if(slot == kSlotID_Voice)
return thisActor->equippedShout;
else
return thisActor->GetEquippedObject(slot == kSlotID_Left);
}
SInt32 GetEquippedItemId(Actor * thisActor, UInt32 slot)
{
enum
{
kSlotID_Left = 0,
kSlotID_Right
};
if (!thisActor)
return NULL;
TESForm * equippedForm = thisActor->GetEquippedObject(slot == kSlotID_Left);
if (!equippedForm)
return 0;
ExtraContainerChanges* containerChanges = static_cast<ExtraContainerChanges*>(thisActor->extraData.GetByType(kExtraData_ContainerChanges));
if (!containerChanges)
return 0;
MatchByForm matcher(equippedForm);
EquipData equipData = containerChanges->FindEquipped(matcher, slot == kSlotID_Right, slot == kSlotID_Left);
return CalcItemId(equipData.pForm, equipData.pExtraData);
}
UInt32 GetSpellCount(Actor* thisActor)
{
if(!thisActor) return NULL;
return thisActor->addedSpells.Length();
}
SpellItem* GetNthSpell(Actor* thisActor, UInt32 n)
{
if(!thisActor) return NULL;
return thisActor->addedSpells.Get(n);
}
#ifdef _AEFFECTS
UInt32 GetNumActiveEffects(Actor* thisActor)
{
if(thisActor)
{
tList<ActiveEffect> * effects = thisActor->magicTarget.GetActiveEffects();
if(effects) {
UInt32 count = effects->Count();
_MESSAGE("Total Effects: %d", count);
return count;
}
}
return 0;
}
ActiveEffect* GetNthActiveEffect(Actor* thisActor, UInt32 n)
{
if(thisActor) {
tList<ActiveEffect> * effects = thisActor->magicTarget.GetActiveEffects();
if(effects) {
UInt32 count = effects->Count();
ActiveEffect * effect = effects->GetNthItem(n);
_MESSAGE("Dumping n: %d Total: %d", n, count); // Test
DumpClass(effect, 20);
return (effects && n < count) ? effect : NULL;
}
}
return NULL;
}
#endif
void EquipItemEx(Actor* thisActor, TESForm* item, SInt32 slotId, bool preventUnequip, bool equipSound)
{
if (!item)
return;
if (!item->Has3D())
return;
EquipManager* equipManager = EquipManager::GetSingleton();
if (!equipManager)
return;
ExtraContainerChanges* containerChanges = static_cast<ExtraContainerChanges*>(thisActor->extraData.GetByType(kExtraData_ContainerChanges));
ExtraContainerChanges::Data* containerData = containerChanges ? containerChanges->data : NULL;
if (!containerData)
return;
// Copy/merge of extraData and container base. Free after use.
InventoryEntryData* entryData = containerData->CreateEquipEntryData(item);
if (!entryData)
return;
BGSEquipSlot * targetEquipSlot = GetEquipSlotById(slotId);
SInt32 itemCount = entryData->countDelta;
// For ammo, use count, otherwise always equip 1
SInt32 equipCount = item->IsAmmo() ? itemCount : 1;
bool isTargetSlotInUse = false;
// Need at least 1 (maybe 2 for dual wield, checked later)
bool hasItemMinCount = itemCount > 0;
BaseExtraList * rightEquipList = NULL;
BaseExtraList * leftEquipList = NULL;
BaseExtraList * curEquipList = NULL;
BaseExtraList * enchantList = NULL;
if (hasItemMinCount)
{
entryData->GetExtraWornBaseLists(&rightEquipList, &leftEquipList);
// Case 1: Already equipped in both hands.
if (leftEquipList && rightEquipList)
{
isTargetSlotInUse = true;
curEquipList = (targetEquipSlot == GetLeftHandSlot()) ? leftEquipList : rightEquipList;
enchantList = NULL;
}
// Case 2: Already equipped in right hand.
else if (rightEquipList)
{
isTargetSlotInUse = targetEquipSlot == GetRightHandSlot();
curEquipList = rightEquipList;
enchantList = NULL;
}
// Case 3: Already equipped in left hand.
else if (leftEquipList)
{
isTargetSlotInUse = targetEquipSlot == GetLeftHandSlot();
curEquipList = leftEquipList;
enchantList = NULL;
}
// Case 4: Not equipped yet.
else
{
isTargetSlotInUse = false;
curEquipList = NULL;
enchantList = entryData->extendDataList->GetNthItem(0);
}
}
// Free temp equip entryData
entryData->Delete();
// Normally EquipManager would update CannotWear, if equip is skipped we do it here
if (isTargetSlotInUse)
{
BSExtraData* xCannotWear = curEquipList->GetByType(kExtraData_CannotWear);
if (xCannotWear && !preventUnequip)
curEquipList->Remove(kExtraData_CannotWear, xCannotWear);
else if (!xCannotWear && preventUnequip)
curEquipList->Add(kExtraData_CannotWear, ExtraCannotWear::Create());
// Slot in use, nothing left to do
return;
}
// For dual wield, prevent that 1 item can be equipped in two hands if its already equipped
bool isEquipped = (rightEquipList || leftEquipList);
if (targetEquipSlot && isEquipped && CanEquipBothHands(thisActor, item))
hasItemMinCount = itemCount > 1;
if (!isTargetSlotInUse && hasItemMinCount)
CALL_MEMBER_FN(equipManager, EquipItem)(thisActor, item, enchantList, equipCount, targetEquipSlot, equipSound, preventUnequip, false, NULL);
}
void EquipItemById(Actor* thisActor, TESForm* item, SInt32 itemId, SInt32 slotId, bool preventUnequip /*unused*/, bool equipSound)
{
if (!item || !item->Has3D() || itemId == 0)
return;
// Can't be improved or enchanted, no need for itemId
if (item->IsAmmo())
{
EquipItemEx(thisActor, item, slotId, preventUnequip, equipSound);
return;
}
EquipManager* equipManager = EquipManager::GetSingleton();
if (!equipManager)
return;
ExtraContainerChanges* containerChanges = static_cast<ExtraContainerChanges*>(thisActor->extraData.GetByType(kExtraData_ContainerChanges));
ExtraContainerChanges::Data* containerData = containerChanges ? containerChanges->data : NULL;
if (!containerData)
return;
InventoryEntryData::EquipData itemData;
containerData->GetEquipItemData(itemData, item, itemId);
BGSEquipSlot * targetEquipSlot = GetEquipSlotById(slotId);
bool isTargetSlotInUse = false;
SInt32 itemCount = itemData.itemCount;
// Need at least 1 (maybe 2 for dual wield, checked later)
bool hasItemMinCount = itemCount > 0;
bool canDualWield = false;
BaseExtraList * newEquipList = itemData.itemExtraList;
if (hasItemMinCount)
{
// Case 1: Type already equipped in both hands.
if (itemData.isTypeWorn && itemData.isTypeWornLeft)
{
isTargetSlotInUse = true;
}
// Case 2: Type already equipped in right hand.
else if (itemData.isTypeWorn)
{
isTargetSlotInUse = targetEquipSlot == GetRightHandSlot() || targetEquipSlot == NULL;
}
// Case 3: Type already equipped in left hand.
else if (itemData.isTypeWornLeft)
{
isTargetSlotInUse = targetEquipSlot == GetLeftHandSlot();
}
// Case 4: Type not equipped yet.
else
{
isTargetSlotInUse = false;
}
}
// This also returns if the target slot is in use by another weapon of the same type.
// Could handle this, but switching them here causes a bug (0 damage) for some reason.
// So we just skip it. Can be handled on the Papyrus side.
if (isTargetSlotInUse || !hasItemMinCount)
return;
bool isItemEquipped = itemData.isItemWorn || itemData.isItemWornLeft;
// Does this item qualify for dual wield?
if (item->IsWeapon() && targetEquipSlot && isItemEquipped && CanEquipBothHands(thisActor, item))
canDualWield = true;
// Not enough items to dual wield, weapon has to swap hands
if (canDualWield && itemCount < 2)
{
BaseExtraList* unequipList = itemData.isItemWornLeft ? itemData.wornLeftExtraList : itemData.wornExtraList;
// Unequip might destroy passed list (return value indicates that).
newEquipList = CALL_MEMBER_FN(equipManager, UnequipItem)(thisActor, item, unequipList, 1, 0, false, false, true, false, NULL) ? NULL : unequipList;
}
CALL_MEMBER_FN(equipManager, EquipItem)(thisActor, item, newEquipList, 1, targetEquipSlot, equipSound, preventUnequip, false, NULL);
}
void UnequipItemEx(Actor* thisActor, TESForm* item, SInt32 slotId, bool preventEquip)
{
if (!item)
return;
if (!item->Has3D())
return;
EquipManager* equipManager = EquipManager::GetSingleton();
if (!equipManager)
return;
ExtraContainerChanges* containerChanges = static_cast<ExtraContainerChanges*>(thisActor->extraData.GetByType(kExtraData_ContainerChanges));
ExtraContainerChanges::Data* containerData = containerChanges ? containerChanges->data : NULL;
if (!containerData)
return;
InventoryEntryData* entryData = containerData->FindItemEntry(item);
if (!entryData)
return;
BGSEquipSlot * targetEquipSlot = GetEquipSlotById(slotId);
SInt32 itemCount = entryData->countDelta;
// For ammo, use count, otherwise always equip 1
SInt32 equipCount = item->IsAmmo() ? itemCount : 1;
BaseExtraList * rightEquipList = NULL;
BaseExtraList * leftEquipList = NULL;
entryData->GetExtraWornBaseLists(&rightEquipList, &leftEquipList);
bool unequipRight = false;
bool unequipLeft = false;
if (targetEquipSlot == GetRightHandSlot())
unequipRight = true;
else if (targetEquipSlot == GetLeftHandSlot())
unequipLeft = true;
else
unequipRight = unequipLeft = true;
if (rightEquipList && unequipRight)
{
BSExtraData* xCannotWear = rightEquipList->GetByType(kExtraData_CannotWear);
if (xCannotWear)
rightEquipList->Remove(kExtraData_CannotWear, xCannotWear);
CALL_MEMBER_FN(equipManager, UnequipItem)(thisActor, item, rightEquipList, equipCount, GetRightHandSlot(), true, preventEquip, true, false, NULL);
}
if (leftEquipList && unequipLeft)
{
BSExtraData* xCannotWear = leftEquipList->GetByType(kExtraData_CannotWear);
if (xCannotWear)
leftEquipList->Remove(kExtraData_CannotWear, xCannotWear);
CALL_MEMBER_FN(equipManager, UnequipItem)(thisActor, item, leftEquipList, equipCount, GetLeftHandSlot(), true, preventEquip, true, false, NULL);
}
}
void QueueNiNodeUpdate(Actor* thisActor)
{
Character * pChar = DYNAMIC_CAST(thisActor, Actor, Character);
if(pChar) {
CALL_MEMBER_FN(pChar, QueueNiNodeUpdate)(false); // False makes this allow weapons to not be auto holstered apparently
}
}
void RegenerateHead(Actor * thisActor)
{
TaskInterface::RegenerateHead(thisActor);
}
void ChangeHeadPart(Actor * thisActor, BGSHeadPart * newPart)
{
if(!thisActor || !newPart)
return;
TESNPC* npc = DYNAMIC_CAST(thisActor->baseForm, TESForm, TESNPC);
if(npc) {
if(newPart->type != BGSHeadPart::kTypeMisc) {
BGSHeadPart * oldPart = npc->GetCurrentHeadPartByType(newPart->type);
// Alters the ActorBase's HeadPart list
CALL_MEMBER_FN(npc, ChangeHeadPart)(newPart);
// Alters the loaded mesh
TaskInterface::ChangeHeadPart(thisActor, oldPart, newPart);
}
}
}
void ReplaceHeadPart(Actor * thisActor, BGSHeadPart * oldPart, BGSHeadPart * newPart)
{
if(!thisActor || !newPart)
return;
TESNPC* npc = DYNAMIC_CAST(thisActor->baseForm, TESForm, TESNPC);
if(npc) {
if(!oldPart) {
oldPart = npc->GetCurrentHeadPartByType(newPart->type);
}
if(newPart->type != BGSHeadPart::kTypeMisc && oldPart && oldPart->type == newPart->type) {
TaskInterface::ChangeHeadPart(thisActor, oldPart, newPart);
}
}
}
void UpdateWeight(Actor * thisActor, float neckDelta)
{
CALL_MEMBER_FN(thisActor, QueueNiNodeUpdate)(true);
TaskInterface::UpdateWeight(thisActor, neckDelta, 0, true);
}
bool IsAIEnabled(Actor * thisActor)
{
if (!thisActor)
return false;
return (thisActor->flags1 & Actor::kFlags_AIEnabled) == Actor::kFlags_AIEnabled;
}
void ResetAI(Actor * thisActor)
{
if (!thisActor)
return;
UInt32 flags = thisActor->flags;
if (!(flags & TESForm::kFlagUnk_0x800) &&
!(flags & TESForm::kFlagIsDeleted))
CALL_MEMBER_FN(thisActor,ResetAI)(0,1);
}
bool IsSwimming(Actor * thisActor)
{
if (!thisActor)
return false;
return (thisActor->actorState.flags04 & ActorState::kState_Swimming) == ActorState::kState_Swimming;
}
void SheatheWeapon(Actor * thisActor)
{
if (thisActor) {
thisActor->DrawSheatheWeapon(false);
}
}
TESObjectREFR * GetFurnitureReference(Actor * thisActor)
{
if(!thisActor)
return NULL;
ActorProcessManager * processManager = thisActor->processManager;
if(!processManager)
return NULL;
MiddleProcess * middleProcess = processManager->middleProcess;
if(!middleProcess)
return NULL;
NiPointer<TESObjectREFR> refr;
UInt32 furnitureHandle = middleProcess->furnitureHandle;
if(furnitureHandle == (*g_invalidRefHandle) || furnitureHandle == 0)
return NULL;
LookupREFRByHandle(furnitureHandle, refr);
return refr;
}
void SetExpressionPhoneme(Actor * thisActor, UInt32 index, float value)
{
TaskInterface::UpdateExpression(thisActor, BSFaceGenAnimationData::kKeyframeType_Phoneme, index, value);
}
void SetExpressionModifier(Actor * thisActor, UInt32 index, float value)
{
TaskInterface::UpdateExpression(thisActor, BSFaceGenAnimationData::kKeyframeType_Modifier, index, value);
}
void ResetExpressionOverrides(Actor * thisActor)
{
TaskInterface::UpdateExpression(thisActor, BSFaceGenAnimationData::kKeyframeType_Reset, 0, 0);
}
VMResultArray<TESFaction*> GetFactions(Actor* thisActor, SInt32 gte, SInt32 lte)
{
VMResultArray<TESFaction*> factions;
if(thisActor) {
if(gte > SCHAR_MAX)
gte = SCHAR_MAX;
if(gte < SCHAR_MIN)
gte = SCHAR_MIN;
if(lte < SCHAR_MIN)
lte = SCHAR_MIN;
if(lte > SCHAR_MAX)
lte = SCHAR_MAX;
FactionRankSet rankSet;
CollectUniqueFactions factionVisitor(&rankSet, gte, lte);
thisActor->VisitFactions(factionVisitor);
for(FactionRankSet::iterator it = rankSet.begin(); it != rankSet.end(); ++it)
factions.push_back(*it);
}
return factions;
}
}
#include "PapyrusVM.h"
#include "PapyrusNativeFunctions.h"
void papyrusActor::RegisterFuncs(VMClassRegistry* registry)
{
registry->RegisterFunction(
new NativeFunction1 <Actor, TESForm*, UInt32>("GetWornForm", "Actor", papyrusActor::GetWornForm, registry));
registry->RegisterFunction(
new NativeFunction1 <Actor, TESForm*, UInt32>("GetEquippedObject", "Actor", papyrusActor::GetEquippedObject, registry));
registry->RegisterFunction(
new NativeFunction0 <Actor, UInt32>("GetSpellCount", "Actor", papyrusActor::GetSpellCount, registry));
registry->RegisterFunction(
new NativeFunction1 <Actor, SpellItem*, UInt32>("GetNthSpell", "Actor", papyrusActor::GetNthSpell, registry));
#ifdef _AEFFECTS
registry->RegisterFunction(
new NativeFunction0 <Actor, UInt32>("GetNumActiveEffects", "Actor", papyrusActor::GetNumActiveEffects, registry));
registry->RegisterFunction(
new NativeFunction1 <Actor, ActiveEffect*, UInt32>("GetNthActiveEffect", "Actor", papyrusActor::GetNthActiveEffect, registry));
#endif
registry->RegisterFunction(
new NativeFunction4 <Actor, void, TESForm*, SInt32, bool, bool>("EquipItemEx", "Actor", papyrusActor::EquipItemEx, registry));
registry->RegisterFunction(
new NativeFunction3 <Actor, void, TESForm*, SInt32, bool>("UnequipItemEx", "Actor", papyrusActor::UnequipItemEx, registry));
registry->RegisterFunction(
new NativeFunction0 <Actor, void>("QueueNiNodeUpdate", "Actor", papyrusActor::QueueNiNodeUpdate, registry));
registry->RegisterFunction(
new NativeFunction1 <Actor, void, BGSHeadPart*>("ChangeHeadPart", "Actor", papyrusActor::ChangeHeadPart, registry));
registry->RegisterFunction(
new NativeFunction2 <Actor, void, BGSHeadPart*, BGSHeadPart*>("ReplaceHeadPart", "Actor", papyrusActor::ReplaceHeadPart, registry));
registry->RegisterFunction(
new NativeFunction0 <Actor, void>("RegenerateHead", "Actor", papyrusActor::RegenerateHead, registry));
registry->RegisterFunction(
new NativeFunction1 <Actor, void, float>("UpdateWeight", "Actor", papyrusActor::UpdateWeight, registry));
registry->RegisterFunction(
new NativeFunction0 <Actor, bool>("IsAIEnabled", "Actor", papyrusActor::IsAIEnabled, registry));
registry->RegisterFunction(
new NativeFunction0 <Actor, void>("ResetAI", "Actor", papyrusActor::ResetAI, registry));
registry->RegisterFunction(
new NativeFunction0 <Actor, bool>("IsSwimming", "Actor", papyrusActor::IsSwimming, registry));
registry->RegisterFunction(
new NativeFunction0 <Actor, void>("SheatheWeapon", "Actor", papyrusActor::SheatheWeapon, registry));
registry->RegisterFunction(
new NativeFunction5 <Actor, void, TESForm*, SInt32, SInt32, bool, bool>("EquipItemById", "Actor", papyrusActor::EquipItemById, registry));
registry->RegisterFunction(
new NativeFunction1 <Actor, SInt32, UInt32>("GetEquippedItemId", "Actor", papyrusActor::GetEquippedItemId, registry));
registry->RegisterFunction(
new NativeFunction1 <Actor, SInt32, UInt32>("GetWornItemId", "Actor", papyrusActor::GetWornItemId, registry));
registry->RegisterFunction(
new NativeFunction0 <Actor, TESObjectREFR*>("GetFurnitureReference", "Actor", papyrusActor::GetFurnitureReference, registry));
registry->RegisterFunction(
new NativeFunction2 <Actor, void, UInt32, float>("SetExpressionPhoneme", "Actor", papyrusActor::SetExpressionPhoneme, registry));
registry->RegisterFunction(
new NativeFunction2 <Actor, void, UInt32, float>("SetExpressionModifier", "Actor", papyrusActor::SetExpressionModifier, registry));
registry->RegisterFunction(
new NativeFunction0 <Actor, void>("ResetExpressionOverrides", "Actor", papyrusActor::ResetExpressionOverrides, registry));
registry->RegisterFunction(
new NativeFunction2 <Actor, VMResultArray<TESFaction*>, SInt32, SInt32>("GetFactions", "Actor", papyrusActor::GetFactions, registry));
}