User:Fg109

From the CreationKit Wiki
Jump to navigation Jump to search

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 Outfit[edit | edit source]

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 Rotation[edit | edit source]

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 Spell[edit | edit source]

To create a multi-fire spell, you will need to create these things:

  1. An activator using the model of one of the magic projectiles
  2. Another activator that doesn't need any model (just duplicate DummyObject)
  3. An explosion that spawns the second activator
  4. A projectile that creates the explosion
  5. 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 Script[edit | edit source]

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:

  1. A container
  2. A trigger zone that spans the entire length of the shelf (initially disabled)
  3. A dummy marker on each end of the shelf (initially disabled)
  4. 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 Mod[edit | edit source]

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