Jump to content

[LE] Code optimization questions


Recommended Posts

This works, but it takes ~2-3 seconds, which is long enough for the player to start wondering if it worked at all. I'm sure that there's something I could be doing better, but I don't know what.

 

Also, this is used by both a spell and an activation trigger. So, I'd like to have the code in a single place (a library or something) and reference it from there, but Googling around seems to indicate that this would actually slow things down? Is that right?

 

 

 

Form[] Function GetWornEquipmentButNotHands(Actor akTarget)
{adapted from https://www.creationkit.com/index.php?title=Slot_Masks_-_Armor example}
    Form[] wornForms = new Form[30]
    int index
    int slotsChecked
    slotsChecked += 0x00100000
    slotsChecked += 0x00200000 ;ignore reserved slots
    slotsChecked += 0x80000000
 
    int thisSlot = 0x01
    while (thisSlot < 0x80000000)
        if (Math.LogicalAnd(slotsChecked, thisSlot) != thisSlot) ;only check slots we have not found anything equipped on already
            Armor thisArmor = akTarget.GetWornForm(thisSlot) as Armor
            if (thisArmor)
                wornForms[index] = thisArmor
                index += 1
                slotsChecked += thisArmor.GetSlotMask() ;add all slots this item covers to our slotsChecked variable
            else ;no armor was found on this slot
                slotsChecked += thisSlot
            endif
        endif
        thisSlot *= 2 ;double the number to move on to the next slot
    endWhile
    return wornForms
EndFunction

Function SwapEquipment(Actor akFirst, Actor akSecond, Form[] akListOfItems)
    int idx = 0
    while(idx < akListOfItems.Length)
        akFirst.RemoveItem(akListOfItems[idx], 1, true, akSecond)
        idx += 1
    endWhile
EndFunction

Function EquipPlayer(Form[] akListofItems)
    int idx = 0
    while(idx < akListOfItems.Length)
        Game.GetPlayer().EquipItemEx(akListOfItems[idx], equipSound = false)
        idx += 1
    endWhile
EndFunction

Event OnEffectStart(Actor akTarget, Actor akCaster)
    Form[] targetEquipped = GetWornEquipmentButNotHands(akTarget)
    Form[] casterEquipped = GetWornEquipmentButNotHands(akCaster)
    
    if (akTarget.GetActorBase().GetOutfit().GetNumParts() == 0 && akCaster == Game.GetPlayer())
    ;to prevent item duplication
        SwapEquipment(akTarget, akCaster, targetEquipped)
        SwapEquipment(akCaster, akTarget, casterEquipped)
        EquipPlayer(targetEquipped)
    elseif(akTarget == Game.GetPlayer())
        SwapEquipment(akCaster, akTarget, casterEquipped)
        SwapEquipment(akTarget, akCaster, targetEquipped)
        EquipPlayer(casterEquipped)
    endif
EndEvent

 

 

Edited by jacobpaige
Link to comment
Share on other sites

  • Replies 46
  • Created
  • Last Reply

Top Posters In This Topic

The idea is to trade the gear, excluding weapons, with a target NPC, right?

 

I think the big one is this:

 

Because of the way you've got your slot check set up you're always going to iterate through your Swap and Equip loops thirty times. Because of the way in which Papyrus works, with frame-linked function calls -- which RemoveItem and EquipItem are --, that means at sixty frames per second your script is going to be taking about a second and a half to complete just those (2x Swaps @ 30 iterations of RemoveItem, 1x EquipPlayer, 30 iterations of EquipItemEx, total 90 framelinked calls / 60 frames per second -> 1.5 seconds).

 

Obviously that's very much suboptimal, particularly since the majority of those calls are probably going to be doing nothing. So what you want to do is cut down the number of times those loops are iterated to exactly as many as necessary and no more. Since you're using SKSE there's two approaches I can see offhand:

 

1. Track the number of found items in the slot check function. At the end, before returning, use Utility.CreateFormArray() to create a new form array of exactly that size, then copy up to that index from the 80-size array into it. The array operations aren't frame-linked and so will complete speedily, and then all the rest of your code will proceed faster.

 

2. Instead of using a Form[] array, instead fill a FormList. While this is generally slower than array operations and will need some more horsing around (you'd need to create a couple of dummy FormLists and make sure to Revert() them before filling them) this has some advantages:

 

a, it will automatically resize, so you don't need to worry about that; it'll be exactly as large as it needs to be and no larger;

b, it can be passed directly into the RemoveItem function in the Swap call, eliminating the need for a loop;

c, it doesn't need SKSE;

 

I don't know if EquipItemEx() will work with a FormList. If it does it would save you that loop as well. If not, you can use ToArray() to cast it into a Form[] array and iterate through it reasonably quickly.

 

 

Some smaller ones:

 

Wherever possible move things that only need to happen once out of loops. For example, in your EquipPlayer function, the Game.GetPlayer() call will always return the same answer and so doesn't need to be checked each iteration of the loop. You can store that information in a local variable outside of the loop, reducing the number of calls dramatically. You could even move it into a script variable that is accessible from all the functions, and fill it in an OnInit event.

 

I also prefer downwards counting while loops over upwards ones, like so:

 

int i = Array.Length ;(or int i = FormList.GetSize())

while i > 0

i -= 1

;code goes here

endwhile

 

You can get equivalent speed with an upwards-counting loop but it requires one more variable declaration:

 

int i = 0

int iMax = Array.Length ;(or int iMax = FormList.GetSize())

while i < iMax

;code goes here

i += 1

endwhile

 

So I prefer the downcounts. Also helps that the first thing you write is the increment line and so you're less likely to accidentally infinite loop yourself :tongue:

 

 

 

As far as putting all the code in one place that's actually reasonably straightforward. This is, by the code, something that's only ever intended to be done by the player, so you can put the meat of the code into a player alias in a quest, and then have your activator/magic effect simply trigger it. The speed hit from that is pretty minimal.

 

Also, for the purposes of trying to diagnose code slowdowns, instrument your code: Store a time-started at the beginning of a function, get a time-finished at the end, and output the difference to the debug log (along with data on what function it was, obviously). That will usually help a great deal in isolating particularly slow stretches of script.

Edited by foamyesque
Link to comment
Share on other sites

while (thisSlot < 0x80000000)

shouldn't that be 30? not -2147483648

 

Try

Form[] Function GetWornEquipmentButNotHands(Actor akTarget)
{adapted from https://www.creationkit.com/index.php?title=Slot_Masks_-_Armor example}
    Form[] wornForms = new Form[30]
    int index
    int slotsChecked
    slotsChecked += 0x00100000
    slotsChecked += 0x00200000
    slotsChecked += 0x80000000
 
    int thisSlot = 0x01
    while (thisSlot < 0x80000000)
        if (Math.LogicalAnd(slotsChecked, thisSlot) != thisSlot) ;only check slots we have not found anything equipped on already
            Armor thisArmor = akTarget.GetWornForm(thisSlot) as Armor
            if (thisArmor)
                wornForms[index] = thisArmor
                index += 1
                slotsChecked += thisArmor.GetSlotMask() ;add all slots this item covers to our slotsChecked variable
            else ;no armor was found on this slot
                slotsChecked += thisSlot
            endif
        endif
        thisSlot *= 2 ;double the number to move on to the next slot
    endWhile
    return wornForms
EndFunction

this seems so complicated to me, so I rewrote it, using newer SKSE codes, & optermise the Array by shrinking it.

Form[] Function Search32BodySlots(Actor akActor)
    {it 33 check the Creation Kit or NifSkope
    to account for all possible modded variations
}

    Form[] WornREF = new Form[32]
    Form EquippedForm
    Int nCount
    Int  nIndex = 62
    While(nIndex > 30)
        nIndex -= 1 
        Int SlotMask = Armor.GetMaskForSlot(nIndex)
        EquippedForm = akActor.GetWornForm(SlotMask)
        If(EquippedForm)
            WornREF[nCount] = EquippedForm
            nCount += 1
        EndIf
    EndWhile
   WornREF = utility.ResizeFormArray(WornREF, nCount)
   Return WornREF
EndFunction
Link to comment
Share on other sites

@PeterMartyr: Your approach will result in anything that occupies multiple slots getting stored multiple times in the array, something that the slotmask/flag approach on the Wiki that jacob used avoids. I think your approach will also be slower in the loop iteration, since it doesn't skip already checked slots like that approach does.

Edited by foamyesque
Link to comment
Share on other sites

 

 

The idea is to trade the gear, excluding weapons, with a target NPC, right?

 

I think the big one is this:

 

Because of the way you've got your slot check set up you're always going to iterate through your Swap and Equip loops thirty times. Because of the way in which Papyrus works, with frame-linked function calls -- which RemoveItem and EquipItem are --, that means at sixty frames per second your script is going to be taking about a second and a half to complete just those (2x Swaps @ 30 iterations of RemoveItem, 1x EquipPlayer, 30 iterations of EquipItemEx, total 90 framelinked calls / 60 frames per second -> 1.5 seconds).

 

Obviously that's very much suboptimal, particularly since the majority of those calls are probably going to be doing nothing. So what you want to do is cut down the number of times those loops are iterated to exactly as many as necessary and no more. Since you're using SKSE there's two approaches I can see offhand:

 

1. Track the number of found items in the slot check function. At the end, before returning, use Utility.CreateFormArray() to create a new form array of exactly that size, then copy up to that index from the 80-size array into it. The array operations aren't frame-linked and so will complete speedily, and then all the rest of your code will proceed faster.

 

2. Instead of using a Form[] array, instead fill a FormList. While this is generally slower than array operations and will need some more horsing around (you'd need to create a couple of dummy FormLists and make sure to Revert() them before filling them) this has some advantages:

 

a, it will automatically resize, so you don't need to worry about that; it'll be exactly as large as it needs to be and no larger;

b, it can be passed directly into the RemoveItem function in the Swap call, eliminating the need for a loop;

c, it doesn't need SKSE;

 

I don't know if EquipItemEx() will work with a FormList. If it does it would save you that loop as well. If not, you can use ToArray() to cast it into a Form[] array and iterate through it reasonably quickly.

 

 

Some smaller ones:

 

Wherever possible move things that only need to happen once out of loops. For example, in your EquipPlayer function, the Game.GetPlayer() call will always return the same answer and so doesn't need to be checked each iteration of the loop. You can store that information in a local variable outside of the loop, reducing the number of calls dramatically. You could even move it into a script variable that is accessible from all the functions, and fill it in an OnInit event.

 

I also prefer downwards counting while loops over upwards ones, like so:

 

int i = Array.Length ;(or int i = FormList.GetSize())

while i > 0

i -= 1

;code goes here

endwhile

 

You can get equivalent speed with an upwards-counting loop but it requires one more variable declaration:

 

int i = 0

int iMax = Array.Length ;(or int iMax = FormList.GetSize())

while i < iMax

;code goes here

i += 1

endwhile

 

So I prefer the downcounts. Also helps that the first thing you write is the increment line and so you're less likely to accidentally infinite loop yourself :tongue:

 

 

 

As far as putting all the code in one place that's actually reasonably straightforward. This is, by the code, something that's only ever intended to be done by the player, so you can put the meat of the code into a player alias in a quest, and then have your activator/magic effect simply trigger it. The speed hit from that is pretty minimal.

 

Also, for the purposes of trying to diagnose code slowdowns, instrument your code: Store a time-started at the beginning of a function, get a time-finished at the end, and output the difference to the debug log (along with data on what function it was, obviously). That will usually help a great deal in isolating particularly slow stretches of script.

 

 

I considered using FormList instead, but as far as I could tell from the wiki, I'd need to fill those manually when I created the mod, which wouldn't really work for what I'm trying to do. I assume that this was incorrect?

 

That aside, for some reason, it hadn't even occurred to me that my arrays weren't being resized. Too much Python I suspect. If I can't figure out the FormList thing, I'll resize them before sending them back.

Yeah, I've already moved Game.GetPlayer() into a var in my latest version, along with a few other, minor changes. I may take your advice on moving it out of the event altogether if I work out how to make a library though. It would likely be better.

 

 

Event OnEffectStart(Actor akTarget, Actor akCaster)
	Actor player = Game.GetPlayer()
	Form[] targetEquipped = GetWornEquipmentButNotHands(akTarget)
	Form[] casterEquipped = GetWornEquipmentButNotHands(akCaster)
	
	if (akCaster == player && akTarget.GetActorBase().GetOutfit().GetNumParts() == 0)
	;to prevent item duplication
		SwapEquipment(akTarget, akCaster, targetEquipped)
		SwapEquipment(akCaster, akTarget, casterEquipped)
		EquipPlayer(targetEquipped)
	elseif(akTarget == player)
		SwapEquipment(akCaster, akTarget, casterEquipped)
		SwapEquipment(akTarget, akCaster, targetEquipped)
		EquipPlayer(casterEquipped)
	endif
EndEvent

 

 

 

I'm not sure that downcounting would actually improve things to a significant degree, and upcounting is simply easier to read, and thus maintain.

I was trying to avoid quests and aliasing (mainly because it would add yet another thing to learn and integrate), but I guess I'll have to give up on that. Having all the code in one place just solves too many potential problems. If I'm understanding things correctly, it should also allow me to lock these functions to prevent players from spamming the spell/activation across multiple targets and possibly causing problems.

I haven't been able to get logging to work for some reason. I think it might have something to do with me using MO, but I'm not really sure. When I check the Skyrim.ini in the game files and the MO profile files, they both have it enabled, but no trace, log or profiling messages show up, except some from Toccata (my "volunteer" for testing) for some reason. So, it's also possible that I'm simply doing something wrong.

Link to comment
Share on other sites

I find downcounting generally easier to work with, but it's pretty minor all told. If you find upcounting easier to understand and maintain absolutely use that.

 

FormLists need to be created in the CK but they don't need to have anything added to them at that time. Skyrim supports GetSize(), AddForm(), RemoveAddedForm(), Revert(), GetAt(), Find(), and HasForm() commands for manipulating the data in them. They are often the best way to pass information to and from major game systems, such as alias conditions, or to Add/RemoveItem calls, or things of that nature, where arrays -- because they were retrofitted in after release -- aren't accepted.

 

Putting the code on a quest or alias script isn't particularly hard. Your code could easily go in either. Make sure the quest is set to Start Game Enabled.

 

You'd then want some sort of TriggerSwap(Actor akUser, Actor akTarget) function with your current OnEffectStart code, and then on your spell you'd have a short script with the quest or alias as a property, a check to make sure the caster is the player, and then you go (PropertyName as CustomScript).TriggerSwap(akCaster, akTarget). With minor alterations this will work with the activator as well, assuming you've already got some method of determining the target you're swapping the gear with, as well as potentially be accessible from any other script you might want, such as an MCM menu.

Edited by foamyesque
Link to comment
Share on other sites

I find downcounting generally easier to work with, but it's pretty minor all told. If you find upcounting easier to understand and maintain absolutely use that.

 

FormLists need to be created in the CK but they don't need to have anything added to them at that time. Skyrim supports GetSize(), AddForm(), RemoveAddedForm(), Revert(), GetAt(), Find(), and HasForm() commands for manipulating the data in them. They are often the best way to pass information to and from major game systems, such as alias conditions, or to Add/RemoveItem calls, or things of that nature, where arrays -- because they were retrofitted in after release -- aren't accepted.

 

Putting the code on a quest or alias script isn't particularly hard. Your code could easily go in either. Make sure the quest is set to Start Game Enabled.

 

You'd then want some sort of TriggerSwap(Actor akUser, Actor akTarget) function with your current OnEffectStart code, and then on your spell you'd have a short script with the quest or alias as a property, a check to make sure the caster is the player, and then you go (PropertyName as CustomScript).TriggerSwap(akCaster, akTarget). With minor alterations this will work with the activator as well, assuming you've already got some method of determining the target you're swapping the gear with, as well as potentially be accessible from any other script you might want, such as an MCM menu.

Ah, that would explain why my attempts to use FormList consistently failed. Thanks :)

 

Yeah, I was getting the impression that quests were a kind of go-to/catch-all for this sort of thing. I was just tired of banging my head on the wall for the last several days learning new things (many of which proved irrelevant) for what should have been a fairly simple first mod ;)

 

That's actually already in the script in the form of an if/else (last few lines in the posted scripts above). Though, that section should probably be changed to make the same function call with a flag to indicate which is the player, and all the logic in them moved into a separate function. It was just a low priority fix since I hadn't decided on whether or not to have a library.

Link to comment
Share on other sites

Well, I was wrong. It apparently takes about four seconds, at least three of which are taken up by finding out what both actors are wearing. I did the timing with debug.notification, so it's probably not 100% accurate, but it should be close enough.

 

Here's the latest working version. Any other ideas on how to speed up the function that finds gear?

 

 

FormList Property playerGearForSwapping Auto

FormList Property npcGearForSwapping Auto

Actor Property PlayerREF Auto

Function GetWornEquipmentButNotHands(Actor akTarget, FormList akWornForms)
{adapted from https://www.creationkit.com/index.php?title=Slot_Masks_-_Armor example}
    int index
    int slotsChecked
    slotsChecked += 0x00100000
    slotsChecked += 0x00200000 ;ignore reserved slots
    slotsChecked += 0x80000000
 
    int thisSlot = 0x01
    while (thisSlot < 0x80000000)
        if (Math.LogicalAnd(slotsChecked, thisSlot) != thisSlot) ;only check slots we have not found anything equipped on already
            Armor thisArmor = akTarget.GetWornForm(thisSlot) as Armor
            if (thisArmor)
                akWornForms.AddForm(thisArmor)
				index += 1
                slotsChecked += thisArmor.GetSlotMask() ;add all slots this item covers to our slotsChecked variable
            else ;no armor was found on this slot
                slotsChecked += thisSlot
            endif
        endif
        thisSlot *= 2 ;double the number to move on to the next slot
    endWhile
EndFunction

Function SwapEquipment(Actor akFirst, Actor akSecond, FormList akListOfItems)
	;int idx = 0
	;while(idx < akListOfItems.Length)
		akFirst.RemoveItem(akListOfItems, 1, true, akSecond)
	;	idx += 1
	;endWhile
EndFunction

Function EquipPlayer()
	int idx = 0
	Form[] listOfItems = npcGearForSwapping.ToArray() ;EquipItemEx will not take FormLists
	while(idx < listOfItems.Length)
		PlayerREF.EquipItemEx(listOfItems[idx])
		idx += 1
	endWhile
EndFunction

Function FindAndSwapGear(Actor akNPC)
	GetWornEquipmentButNotHands(PlayerREF, playerGearForSwapping)
	GetWornEquipmentButNotHands(akNPC, npcGearForSwapping)
	
	SwapEquipment(PlayerREF, akNPC, playerGearForSwapping)
	SwapEquipment(akNPC, PlayerREF, npcGearForSwapping)
	
	EquipPlayer()
	
	;cleanup
	playerGearForSwapping.Revert()
	npcGearForSwapping.Revert()
EndFunction

Event OnEffectStart(Actor akTarget, Actor akCaster)
	if (akCaster == PlayerREF && akTarget.GetActorBase().GetOutfit().GetNumParts() == 0)
	;to prevent item duplication
		FindAndSwapGear(akTarget)
	elseif(akTarget == PlayerREF)
		FindAndSwapGear(akCaster)
	endif
EndEvent

 

 

I assume that the problem is all the external calls, but I'm not sure how to do without them. Is there maybe a way I can extend GetWornForm() to return the contents of all the masks rather than just one? Or a function I simply haven't been able to find that will give me a list of equipped items?

 

Also, EquipItemEx will compile if you give it a FormList, but it won't actually do anything with it.

Edited by jacobpaige
Link to comment
Share on other sites

@ foamyesque, I disagree with your logic, where is the individual NifSkope NIf or Creation Kit REF Partitons Stored? To avoid repetition in Jacob Code? He simply skips (ignored) some Slots.

; returns the SlotMask for a single slot from the CK
; can be used with the non-global SlotMask functions above
; and with the Math bit shifting functions
int Property kSlotMask30 =	0x00000001 AutoReadOnly
int Property kSlotMask31 =	0x00000002 AutoReadOnly
int Property kSlotMask32 =	0x00000004 AutoReadOnly
int Property kSlotMask33 =	0x00000008 AutoReadOnly
int Property kSlotMask34 =	0x00000010 AutoReadOnly
int Property kSlotMask35 =	0x00000020 AutoReadOnly
int Property kSlotMask36 =	0x00000040 AutoReadOnly
int Property kSlotMask37 =	0x00000080 AutoReadOnly
int Property kSlotMask38 =	0x00000100 AutoReadOnly
int Property kSlotMask39 =	0x00000200 AutoReadOnly
int Property kSlotMask40 =	0x00000400 AutoReadOnly
int Property kSlotMask41 =	0x00000800 AutoReadOnly
int Property kSlotMask42 =	0x00001000 AutoReadOnly
int Property kSlotMask43 =	0x00002000 AutoReadOnly
int Property kSlotMask44 =	0x00004000 AutoReadOnly
int Property kSlotMask45 =	0x00008000 AutoReadOnly
int Property kSlotMask46 =	0x00010000 AutoReadOnly
int Property kSlotMask47 =	0x00020000 AutoReadOnly
int Property kSlotMask48 =	0x00040000 AutoReadOnly
int Property kSlotMask49 =	0x00080000 AutoReadOnly
int Property kSlotMask50 =	0x00100000 AutoReadOnly
int Property kSlotMask51 =	0x00200000 AutoReadOnly
int Property kSlotMask52 =	0x00400000 AutoReadOnly
int Property kSlotMask53 =	0x00800000 AutoReadOnly
int Property kSlotMask54 =	0x01000000 AutoReadOnly
int Property kSlotMask55 =	0x02000000 AutoReadOnly
int Property kSlotMask56 =	0x04000000 AutoReadOnly
int Property kSlotMask57 =	0x08000000 AutoReadOnly
int Property kSlotMask58 =	0x10000000 AutoReadOnly
int Property kSlotMask59 =	0x20000000 AutoReadOnly
int Property kSlotMask60 =	0x40000000 AutoReadOnly
int Property kSlotMask61 =	0x80000000 AutoReadOnly

I go from Slot 60 missing Slot 61(FX) to Slot 30 backward once.It matches NifSkope & how the Creation Kit itself references Body Slots Partitions. Exactly. My code is shorter has NONE NONE FORMS in the finished Dynamic Array, Yet somehow you claim it Longer and Slower. (Shakes his head disbelieve)

 

Modders pretty much use any Slot they like including the skipped slots mention in the Wikki Code. (Why I didn't skip them)

 

Here AFAIK this one is by Expired who is on the SKSE team I base my code on it, I confess it. I think Expired know better than you, & the the guy who wrote the wikki one. He didn't skip anything.

Function SaveEquipment()
	;Debug.Trace("Saving equipment...")
	Actor actorRef = GetReference() as Actor
	BipedObjects[0] = actorRef.GetEquippedWeapon(true) ; Try weapons first
	BipedObjects[1] = actorRef.GetEquippedWeapon(false)
	if !BipedObjects[0]
		BipedObjects[0] = actorRef.GetEquippedSpell(0) ; No weapon, try spell
	endif
	if !BipedObjects[1]
		BipedObjects[1] = actorRef.GetEquippedShield() ; No weapon try shield
	endif
	if !BipedObjects[1]
		BipedObjects[1] = actorRef.GetEquippedSpell(1) ; No weapon try spell
	endif

	; SKSE check failed
	If XFLMain.SKSEExtended == false
		return
	Endif

	int slot = 1
	int i = 2
	While i < BipedObjects.length
		Form equippedForm = actorRef.GetWornForm(slot)
		if equippedForm
			BipedObjects[i] = equippedForm
		else
			BipedObjects[i] = None
		endif
		slot *= 2
		i += 1
	EndWhile
EndFunction

I think I improve on Expire Code, since the other both Codes store >NONE FORMS< > in an Array of 30+ and then have to compensate for these none forms,(or the papyrus log will get shitty with errors) which makes them faster than mine, (really) but what do I know....... I defer to my betters.

Link to comment
Share on other sites

I've been looking over the documentation for quests and aliases, and it's got me wondering: Would it be a good idea to add OnEquipItem and OnUnequipItem events to the player alias to keep track of the player's equipped items at all times? If I did so, I'd only have to find the npc's equipped items, but I'd also be wasting memory as well as increasing the overhead on a fairly common set of events. I assume that the amount of memory wasted would be fairly trivial since it should just be a set of pointers to objects that were going to be kept in memory anyway, and that the add/remove from FormList actions are quite light, but I wasn't sure.

 

Edit: Actually, it occurs to me that this might create a problem with my own script as it may alter the FormList for the player while I'm using it, thus creating unpredictable behavior, but I'm not sure. Would the equipment swapping script lock the FormList while it was running, or would both events try to happen at the same time and both have access at the same time/deadlock/rotate access in unpredictable ways?

 

Also, if I use an OnStoryCastMagic event, will it trigger any time the player casts magic?

 

 

look up Bethesda body slots partitions here let me post it for you.

; returns the SlotMask for a single slot from the CK
; can be used with the non-global SlotMask functions above
; and with the Math bit shifting functions
int Property kSlotMask30 =	0x00000001 AutoReadOnly
int Property kSlotMask31 =	0x00000002 AutoReadOnly
int Property kSlotMask32 =	0x00000004 AutoReadOnly
int Property kSlotMask33 =	0x00000008 AutoReadOnly
int Property kSlotMask34 =	0x00000010 AutoReadOnly
int Property kSlotMask35 =	0x00000020 AutoReadOnly
int Property kSlotMask36 =	0x00000040 AutoReadOnly
int Property kSlotMask37 =	0x00000080 AutoReadOnly
int Property kSlotMask38 =	0x00000100 AutoReadOnly
int Property kSlotMask39 =	0x00000200 AutoReadOnly
int Property kSlotMask40 =	0x00000400 AutoReadOnly
int Property kSlotMask41 =	0x00000800 AutoReadOnly
int Property kSlotMask42 =	0x00001000 AutoReadOnly
int Property kSlotMask43 =	0x00002000 AutoReadOnly
int Property kSlotMask44 =	0x00004000 AutoReadOnly
int Property kSlotMask45 =	0x00008000 AutoReadOnly
int Property kSlotMask46 =	0x00010000 AutoReadOnly
int Property kSlotMask47 =	0x00020000 AutoReadOnly
int Property kSlotMask48 =	0x00040000 AutoReadOnly
int Property kSlotMask49 =	0x00080000 AutoReadOnly
int Property kSlotMask50 =	0x00100000 AutoReadOnly
int Property kSlotMask51 =	0x00200000 AutoReadOnly
int Property kSlotMask52 =	0x00400000 AutoReadOnly
int Property kSlotMask53 =	0x00800000 AutoReadOnly
int Property kSlotMask54 =	0x01000000 AutoReadOnly
int Property kSlotMask55 =	0x02000000 AutoReadOnly
int Property kSlotMask56 =	0x04000000 AutoReadOnly
int Property kSlotMask57 =	0x08000000 AutoReadOnly
int Property kSlotMask58 =	0x10000000 AutoReadOnly
int Property kSlotMask59 =	0x20000000 AutoReadOnly
int Property kSlotMask60 =	0x40000000 AutoReadOnly
int Property kSlotMask61 =	0x80000000 AutoReadOnly

I go from 60 missing 61 to 30 backward once, where do I repeat myself?

 

 

 

Some armors take up multiple slots. So if you look at one of those armors in say three different slots, then you'll get three copies of that armor.

Edited by jacobpaige
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.

×
×
  • Create New...