User:Fg109
I was trying to write a tutorial on papyrus basics on my user page, but I realize I absolutely suck at explaining things. I'm just going to post (parts of) scripts that I made and liked.
Saving Actor's OutfitEdit
A function returning a 32 element array containing an actor's equipped (base) items. Requires SKSE.
Form[] Function SaveOutfit(Actor akActor)
Form[] SavedOutfit = new Form[32]
int slot = 1
int i
int j
while (i < 32)
Form TempForm = akActor.GetWornForm(slot)
if (TempForm)
SavedOutfit[j] = TempForm
j += 1
endif
slot *= 2
i += 1
endwhile
Return SavedOutfit
EndFunction
There will probably be empty elements, since the actor probably is not wearing an item in every single slot. Automatically sorted so that empty elements are at the end of the array.
Also some functions to remove duplicate entries if needed.
Form[] Function RemoveDuplicates(Form[] InputArray, Int Size)
Form[] OutputArray = new Form[32]
int i = 0
int j = 0
while (i < Size)
if (ArrayHasForm(OutputArray, InputArray[i]) < 0)
OutputArray[j] = InputArray[i]
j += 1
endif
i += 1
endwhile
Return OutputArray
EndFunction
Int Function ArrayhasForm(Form[] MyArray, Form MyForm)
int i = 0
while (i < MyArray.Length)
if (MyArray[i] == MyForm)
Return i
endif
i += 1
endwhile
Return -1
EndFunction
Converting Local/Global RotationEdit
I wrote a function for converting from local to global rotation. I find that I use it a lot.
Float[] Function ConvertRotation(Float AngleX, Float AngleY, Float AngleZ, Bool FromLocal = True)
Float NewX
Float NewY
if (FromLocal)
NewX = AngleX * Math.Cos(AngleZ) + AngleY * Math.Sin(AngleZ)
NewY = AngleY * Math.Cos(AngleZ) - AngleX * Math.Sin(AngleZ)
else
NewX = AngleX * Math.Cos(AngleZ) - AngleY * Math.Sin(AngleZ)
NewY = AngleY * Math.Cos(AngleZ) + AngleX * Math.Sin(AngleZ)
endif
Float[] Angles = new Float[3]
Angles[0] = NewX
Angles[1] = NewY
Angles[2] = AngleZ
Return Angles
EndFunction
Angle Z stays the same, because "Local" in this case means "What are the new X and Y angles if I turn (change the Z angle) to this heading?"
Multi Fire SpellEdit
To create a multi-fire spell, you will need to create these things:
- An activator using the model of one of the magic projectiles
- Another activator that doesn't need any model (just duplicate DummyObject)
- An explosion that spawns the second activator
- A projectile that creates the explosion
- A spell that uses the projectile
The first activator should have a script with an OnTranslationAlmostComplete event that will place an explosion at itself (not the custom one you made) in order to inflict damage, then disable and delete itself.
The second activator should use this script:
Scriptname MissileTargetScript extends ObjectReference
Activator Property MissileObj Auto
ObjectReference[] Missiles
Int Count
Event OnInit()
Actor Player = Game.GetPlayer()
Missiles = New ObjectReference[128]
Count = Utility.RandomInt(3, 13)
Missiles[0] = Player.PlaceAtMe(MissileObj)
Missiles[0].MoveTo(Player, 75 * Math.Sin(Player.GetAngleZ()), 75 * Math.Cos(Player.GetAngleZ()), \
0.75 * Player.GetHeight())
Float OffsetAngle = 360 / (Count - 1)
int index = 1
while (index < Count)
Missiles[index] = Missiles[0].PlaceAtMe(MissileObj)
Float DistanceXY = 64 * Math.Sin(OffsetAngle * (Index - 1))
Float DistanceZ = 64 * Math.Cos(OffsetAngle * (Index - 1))
Missiles[index].MoveTo(Missiles[0], DistanceXY * Math.Sin(Missiles[0].GetAngleZ() - 90), \
DistanceXY * Math.Cos(Missiles[0].GetAngleZ() - 90), DistanceZ)
Missiles[index].SetAngle(0, 0, Missiles[0].GetAngleZ() + Utility.RandomInt(-45, 45))
index += 1
endwhile
Utility.Wait(0.25)
index = 0
while (index < Count)
SmoothCurve(Missiles[index], Self, 1000)
index += 1
endwhile
Disable()
Delete()
EndEvent
Function SmoothCurve(ObjectReference A, ObjectReference B, Float Speed)
Float AngleZ = A.GetHeadingAngle(B)
if (AngleZ < -45)
Float OffsetAngle = -45.0 - AngleZ
AngleZ = -45.0
A.SetAngle(0, 0, A.GetAngleZ() + OffsetAngle)
elseif (AngleZ > 45)
Float OffsetAngle = 45.0 - AngleZ
AngleZ = 45.0
A.SetAngle(0, 0, A.GetAngleZ() + OffsetAngle)
endif
Float DistanceXY = Math.sqrt((B.X - A.X) * (B.X - A.X) + (B.Y - A.Y) * (B.Y - A.Y))
Float SplineMagnitude = DistanceXY / Math.Cos(AngleZ)
Float AngleZEnd = A.GetAngleZ() + (A.GetHeadingAngle(B) * 2)
A.SplineTranslateTo(B.X, B.Y, B.Z, 0, 0, AngleZEnd, Math.Abs(SplineMagnitude) * 0.75, Speed)
EndFunction
So what happens is you cast your custom spell. The custom spell creates a projectile. When the projectile hits something, it causes an explosion. The explosion would spawn an activator (the second one, our 'missile target'). The missile target would create 3 to 13 missiles (the first activators) and place them in a clock-like arrangement in front of the player. Then the missiles will fire themselves at the target (in a random curved trajectory) and explode when they reach it.
Custom Bookcase ScriptEdit
Re-did the bookcase system in an attempt to make it less resource intensive. And because I don't like how the vanilla system did things by creating duplicates.
This system requires a couple of things:
- A container
- A trigger zone that spans the entire length of the shelf (initially disabled)
- A dummy marker on each end of the shelf (initially disabled)
- A click trigger
The click trigger should use "ActivateLinkedChestDummyScript" and have a linked ref pointing to the container. The trigger zone should have a linked ref pointing towards the container. The container should have a linked ref pointing to the trigger zone, and linked refs to the dummy books using the "BookshelfFirstBook" and "BookshelfLastBook" keywords.
These are the scripts for the container and the trigger zone:
Scriptname fg109_Bookshelf_Container_Script extends ObjectReference
Keyword Property BookShelfFirstBook Auto
Keyword Property BookShelfLastBook Auto
Form[] StoredBooks
Float ShelfLength
Float CalcLength
Int CurrentBooks
Bool FullShelf
Event OnInit()
BlockActivation()
ShelfLength = GetLinkedRef(BookShelfFirstBook).GetDistance(GetLinkedRef(BookShelfLastBook)) \
+ (GetLinkedRef(BookShelfFirstBook).GetHeight())
EndEvent
Auto State Active
Event OnActivate(ObjectReference akActionRef)
GoToState("Inactive")
CurrentBooks = (GetLinkedRef() as fg109_Bookshelf_Trigger_Script).MoveIntoContainer()
StoredBooks = (GetLinkedRef() as fg109_Bookshelf_Trigger_Script).StoredBooks
CalcLength = (GetLinkedRef() as fg109_Bookshelf_Trigger_Script).CalcLength
FullShelf = False
GoToState("Active")
Activate(akActionRef, True)
Utility.Wait(0.5)
GoToState("Inactive")
PlaceOnShelf()
GoToState("Active")
EndEvent
Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
if !(akBaseItem as Book)
RemoveItemSilent(akBaseItem, aiItemCount, akSourceContainer)
Debug.MessageBox("The bookshelf only accepts books.")
elseif (FullShelf)
RemoveItemSilent(akBaseItem, aiItemCount, akSourceContainer)
Debug.MessageBox("The bookshelf is full.")
else
int BookCount = CheckShelfSpace(akBaseItem, aiItemCount)
if (BookCount < aiItemCount)
FullShelf = True
RemoveItemSilent(akBaseItem, aiItemCount - BookCount, akSourceContainer)
endif
AddBooks(akBaseItem, BookCount)
endif
EndEvent
Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)
if (akBaseItem as Book)
RemoveBooks(akBaseItem, aiItemCount)
FullShelf = False
endif
EndEvent
EndState
Function RemoveItemSilent(Form akItem, Int aiCount, ObjectReference akContainer)
GoToState("Inactive")
RemoveItem(akItem, aiCount, True, akContainer)
GoToState("Active")
EndFunction
Int Function CheckShelfSpace(Form MyBook, Int Count)
ObjectReference TempRef = PlaceAtMe(MyBook, 1, False, True)
Float BookWidth = TempRef.GetHeight() + 0.5
TempRef.Delete()
int i = 0
while (i < Count) && (CurrentBooks + i < 128) && (CalcLength <= ShelfLength)
CalcLength += BookWidth
i += 1
endwhile
Return i
EndFunction
Function AddBooks(Form MyBook, Int Count)
int i = 0
CurrentBooks += Count
while (Count)
while (StoredBooks[i])
i += 1
endwhile
StoredBooks[i] = MyBook
Count -= 1
endwhile
EndFunction
Function RemoveBooks(Form MyBook, Int Count)
int i = 0
CurrentBooks -= Count
while (Count)
if (StoredBooks[i] == MyBook)
StoredBooks[i] = None
Count -= 1
endif
i += 1
endwhile
EndFunction
Function PlaceOnShelf()
ObjectReference[] PlacedBooks = new ObjectReference[128]
ObjectReference First = GetLinkedRef(BookShelfFirstBook)
ObjectReference Last = GetLinkedRef(BookShelfLastBook)
Float AngX = First.GetAngleX()
Float AngY = First.GetAngleY()
Float AngZ = First.GetAngleZ()
Float PosX = First.X
Float PosY = First.Y
Float PosZ = First.Z
Float UnitVectorX = (Last.X - PosX) / ShelfLength
Float UnitVectorY = (Last.Y - PosY) / ShelfLength
Float UnitVectorZ = (Last.Z - PosZ) / ShelfLength
Bool PlacedFirst = False
Float OldHeight
Float NewHeight
Float Offset = 0.0
int i = 0
int j = 0
while (CurrentBooks)
if (StoredBooks[i])
PlacedBooks[j] = DropObject(StoredBooks[i])
if (PlacedFirst)
NewHeight = PlacedBooks[j].GetHeight()
Offset += 0.5 * OldHeight + 0.5 * NewHeight + 0.5
OldHeight = NewHeight
else
PlacedFirst = True
OldHeight = PlacedBooks[j].GetHeight()
endif
while !(PlacedBooks[j].Is3DLoaded())
;do nothing
endwhile
PlacedBooks[j].SetMotionType(4)
PlacedBooks[j].TranslateTo(PosX + UnitVectorX * Offset, PosY + UnitVectorY * Offset, \
PosZ + UnitVectorZ * Offset, AngX, AngY, AngZ, 100000.0)
j += 1
CurrentBooks -= 1
endif
i += 1
endwhile
while (j)
j -= 1
PlacedBooks[j].SetMotionType(1, True)
endwhile
EndFunction
Scriptname fg109_Bookshelf_Trigger_Script extends ObjectReference
Form[] Property StoredBooks Auto
Float Property CalcLength Auto
ObjectReference MyContainer
Int CurrentBooks
Bool Looping
Event OnTriggerEnter(ObjectReference akTriggerRef)
if (akTriggerRef.GetBaseObject() as Book)
StoredBooks[CurrentBooks] = akTriggerRef.GetBaseObject()
CalcLength += akTriggerRef.GetHeight() + 0.5
CurrentBooks += 1
MyContainer.AddItem(akTriggerRef)
endif
RegisterForSingleUpdate(0.2)
EndEvent
Event OnUpdate()
Looping = False
Disable()
EndEvent
Int Function MoveIntoContainer()
MyContainer = GetLinkedRef()
StoredBooks = new Form[128]
CurrentBooks = 0
CalcLength = 0.0
Looping = True
RegisterForSingleUpdate(0.2)
Enable()
while (Looping)
Utility.Wait(0.1)
endwhile
Return CurrentBooks
EndFunction
Basic Hunger ModEdit
Give actors an ability with this script:
Scriptname fg109TestMEScript extends ActiveMagicEffect
Faction Property HungerFaction Auto
Int Property UpdateInterval Auto
Formlist Property FoodsHighList Auto
Formlist Property FoodsMidList Auto
Formlist Property FoodsLowList Auto
Keyword Property VendorItemFood Auto
Actor Myself
Event OnEffectStart(Actor akTarget, Actor akCaster)
Myself = akTarget
;if NPC is not in the hunger faction, add him/her in
if (Myself.GetFactionRank(HungerFaction) < 0)
Myself.SetFactionRank(HungerFaction, 0)
endif
;increment "hunger variable" every UpdateInterval seconds
RegisterForUpdate(UpdateInterval)
EndEvent
Event OnUpdate()
if (Myself.GetFactionRank(HungerFaction) < 100)
;increment the "hunger variable" by 1
Myself.ModFactionRank(HungerFaction, 1)
else
;die of hunger
Myself.Kill()
endif
EndEvent
Event OnObjectEquipped(Form akBaseObject, ObjectReference akReference)
if (FoodsHighList.HasForm(akBaseObject))
;we just ate something from FoodsHighList
Myself.ModFactionRank(HungerFaction, -5)
elseif (FoodsMidList.HasForm(akBaseObject))
;we just ate something from FoodsMidList
Myself.ModFactionRank(HungerFaction, -3)
elseif (FoodsLowList.HasForm(akBaseObject))
;we just ate something from the FoodsLowList
Myself.ModFactionRank(HungerFaction, -1)
elseif (akBaseObject.HasKeyword(VendorItemFood))
;a food not in one of the lists (mod added?)
if (akBaseObject.GetGoldValue() < 3)
Myself.ModFactionRank(HungerFaction, -1)
elseif (akBaseObject.GetGoldValue() < 5)
Myself.ModFactionRank(HungerFaction, -3)
else
Myself.ModFactionRank(HungerFaction, -5)
endif
endif
if (Myself.GetFactionRank(HungerFaction) < 0)
;completely full
Myself.SetFactionRank(HungerFaction, 0)
endif
EndEvent