Creating Custom Couriers

From the CreationKit Wiki
Jump to navigation Jump to search


Achtung.png This tutorial was submitted in 2012 and was written for Skyrim LE. Proceed with caution. The major script below will not work on later versions of the game.


This tutorial outlines the steps to make a custom courier that (sort of) works in a manner similar to the existing ones, but will track you down anywhere.

It is fairly complicated and, like the existing couriers, driven by a quest.

Components[edit | edit source]

NPCs[edit | edit source]

  1. Make a copy of the NPC you want to use from Actors - Actor. Right click on the NPC you want to use and click Duplicate. Give the NPC a Unique ID, Name, and Short Name.
  2. Under the tab "AI Data" set the following attributes: Aggression - Unaggressive, Confidence - Cowardly, Assistance - Helps Nobody
  3. For Aggro Radius Behavior - All setting should be set to 0.
  4. Make as many variations as you want, and give them a name easy to find (Like prefixing it with WICC (World Interactions Custom Courier))
  5. Remember to Save frequently, as CK is prone to crashing.

Containers[edit | edit source]

  1. In the Object Window, expand World Objects and click on Container.
  2. Choose a container and duplicate it, give it a unique ID, at this point it's best to set a naming convention so object is easy to filter. Suggestion: WICCC prefix (WI Custom Courier Container)
  3. In the Item List, delete all items from the chest.
  4. Save your changes.

Global Variable[edit | edit source]

  1. From the Object Window, expand Miscellaneous and click on Global
  2. Right click in the Editor ID field and select "New"
  3. Give your custom global variable a unique ID, follow your naming convention. This will be used to count the number of items in your container, more on this later.
  4. Save your changes.

The Cell[edit | edit source]

Next, we're going to make a Cell in the game world for our created objects to exist.

  1. From the Cell View Window, select the World Space - Interiors
  2. Click on any interior inside of EditorID and begin typing "WICourierCell"
  3. When it appears on the list, right click on it and press Duplicate Cell.
  4. Single-click the EditorID entry of your duplicate and set a name.
  5. Double-click on the EditorID to load the cell in the Render Window.
  6. Clean up your duplicated Cell - click on the Courier and press the delete key, do the same with the chest.
  7. Place an instance of your NPC in the Cell by dragging it from the Object Window into the Render Window.
  8. Set your NPC to "Initially Disabled" by double clicking the Actor object in the Render Window and clicking the checkbox in the Reference Window.
  9. Place an instance of your custom container in the Cell by dragging it from the Object window into the Render Window.
  10. Delete the existing Xmarker (red x on the floor) from the Cell by clicking it and pressing the delete key.
  11. In the Object Window expand WorldObjects, and click on Static. Search for Xmarker.
  12. Drag and drop XMarker from the Object Window into the Render Window. Open the object and give it a unique name.
  13. From the Object Window, in Items, select Misc Items. Search for Charcoal, create a duplicate and give it unique name.(WICCDropPoint for example).
  14. From the Ojbect Window, in the Miscellaneous category, select Keyword. Create a Keyword like "WICCSpawnPointKeyword"
  15. Go to World Objects, Activator, and create an XMarkerActivator, attach your keyword to the Activator from the Keywords table inside of the Activator you created.
  16. Place copies of this Activator in hard to see spots of major city Cells. Start with Solitude, the world Space is SolitudeWorld.
  17. Navigate to a place, preferably under the floor. Drag the Activator from the Object Window into the Render Window. It's small and hard to see, but the Object Window count column should increase when you do it correctly.
  18. Continue with other major cities. This forces the courier to spawn outside the walls of the town if the player is inside, which is a nice way of preventing the courier from spawning directly in front of the player.
  19. Save your work.

The Quest[edit | edit source]

Build a quest that starts game enabled and allows repeated stages,

  • Make an alias for each of your Custom Courier types, picking them in the render window.
  • Make an alias for each of the containers, also picking them in the window.
  • Make an alias "Courier" and an alias "Container" and pick the courier and container you want to use by default.
  • The last alias is one for the cell marker.

Packages[edit | edit source]

The Courier alias should have to following packages attached to it, in this order:

  1. A travel package, targeted on the player, with the conditions:
    GetDistance (Courier to Player) > 1000.0
    GetGlobalValue(Your global item count variable) >= 1.0
  2. A follow player package with just the Item Count test.
  3. A "Flee from target" package set to the player with no conditions.

This will make the courier travel to the player and then follow him so long as there is something in the container. Once the container is empty, the courier will then leave.

Quest Stages[edit | edit source]

The Quest stages and their associated fragments should be:

Stage 0

; Startup Stage - Return here when Deliveries complete.
;Debug.Notification("Custom Courier in his home cell")
        UnregisterForUpdate() ; Make sure to cancel previous delivery's updates, so we're not registered twice.
        RegisterForUpdate(20)
        CourierScript.StateChange("Waiting")

Stage 100

; Item added to courier box. Look for a place to start.
; Check optional references now.
; Debug.Notification("Custom Courier has a letter!")

CourierScript.StateChange("Seeking")

Stage 200

; Courier found the player. Make Delivery

CourierScript.StateChange("Delivering")
DeliverScene.Start()

Stage 300

; Delivery Complete. Courier will run away now.

CourierScript.StateChange("Departing")

Delivery Scene[edit | edit source]

Create a scene to define the sort of behavior you want your couriers to have when they reach the player.

In the delivery scene, be sure to create an appropriate dialog topic, and have the scene itself transfer the objects with this fragment when the scene ends:

CourierScript.GiveItemsToPlayer()


Quest Script[edit | edit source]

Here is the Courier Script I used:

Scriptname WICustomCourierScript extends Quest  

import Utility

ReferenceAlias Property CourierCellMarker Auto

ReferenceAlias Property Courier Auto ; The courier currently in use. Alternate versions below.
ReferenceAlias Property WolfCourier Auto
ReferenceAlias Property SpiderCourier Auto
ReferenceAlias Property WerewolfCourier Auto
ReferenceAlias Property DeerCourier Auto
ReferenceAlias Property FoxCourier Auto
ReferenceAlias Property FlameCourier Auto
ReferenceAlias Property DwarfSpiderCourier Auto

ReferenceAlias Property ContainerAlias Auto
GlobalVariable Property ItemCount  Auto ; Current container and item count. Alternate versions below.
ReferenceAlias Property DwLgContainer Auto
int property DwLgCount Auto
ReferenceAlias Property DwMedContainer Auto
int Property DwMedCount Auto
ReferenceAlias Property DwSmContainer Auto
int Property DwSmCount Auto
ReferenceAlias Property HiveContainer Auto
int property HiveCount Auto
ReferenceAlias Property BarrelContainer Auto
int property BarrelCount Auto
ReferenceAlias Property CoffinContainer Auto
int property CoffinCount Auto

Bool Function DisableDeliveries(Bool DisableMe = True) ; Allows you to turn off deliveries, in case
; you want to fiddle with the containers and courier settings without interruption.
    If DisableMe
        While GetState() != "Waiting" ; wait until the courier is finished with any deliveries.
; Just want to make sure it stays disabled once it is.
        EndWhile
        GoToState("Disabled")
        return True
    else
;Debug.Notification("Enabling Courier")
        GoToState("Waiting") ; Courier will enable itself when it has something to deliver.
        Return False
    endif
EndFunction

Function SetRandomContainer() ; Picks a random container
    int Which = RandomInt(1,6)
    if Which == 1
        ChangeContainer("Hive")
    elseif Which == 2
        ChangeContainer("Coffin")
    elseif Which == 3
        ChangeContainer("Barrel")
    elseif Which == 4
        ChangeContainer("Small")
    elseif Which == 5
        ChangeContainer("Medium")
    else
        ChangeContainer("Large")
    endif
EndFunction

Function SetRandomCourier() ; Picks a random courier.
    int Which = RandomInt(1,7)
    if Which == 1
        ChangeCourier("Wolf")
    elseif Which == 2
        ChangeCourier("Spider")
    elseif Which == 3
        ChangeCourier("Werewolf")
    elseif Which == 4
        ChangeCourier("Deer")
    elseif Which == 5
        ChangeCourier("Fox")
    elseif Which == 6
        ChangeCourier("Flame")
    else
        ChangeCourier("Dwemer")
    endif
EndFunction

Function StoreContainerQuantity() 
; Saves quantity of the current container if changing containers, in case there is something 
; still in there.  When we change back, it will restore the value.
    ObjectReference Current = ContainerAlias.GetRef()
    If Current == DwLgContainer.GetRef()
        DwLgCount = (ItemCount.GetValue() as int)
    elseIf Current == DwSmContainer.GetRef()
        DwSmCount = (ItemCount.GetValue() as int)
    elseIf Current == DwMedContainer.GetRef()
        DwMedCount = (ItemCount.GetValue() as int)
    elseIf Current == HiveContainer.GetRef()
        HiveCount = (ItemCount.GetValue() as int)
    elseIf Current == BarrelContainer.GetRef()
        BarrelCount =( ItemCount.GetValue() as int)
    else ; Only the coffin is left.
        CoffinCount = (ItemCount.GetValue() as int)
    endif
EndFunction

Function ChangeCourier(String Which)

;Debug.Notification("Changing Courier: " + Which)

    While Courier.GetRef().IsEnabled() || Updating 
; If a courier is currently trying to deliver something, wait til it's done.
        Wait(1.0)
    endWhile
    Updating = True ; Don't spawn a new courier or change containers while changing courier types.
    if Which == "Wolf"
        Courier.ForceRefTo(WolfCourier.GetRef())
    elseif Which == "Spider"
        Courier.ForceRefTo(SpiderCourier.GetRef())
    elseif Which == "Werewolf"
        Courier.ForceRefTo(WereWolfCourier.GetRef())
    elseif Which == "Deer"
        Courier.ForceRefTo(DeerCourier.GetRef())
    elseif Which == "Fox"
        Courier.ForceRefTo(FoxCourier.GetRef())
    elseif Which == "Flame"
        Courier.ForceRefTo(FlameCourier.GetRef())
    elseif Which == "Dwemer"
        Courier.ForceRefTo(DwarfSpiderCourier.GetRef())
    else ; Default to Dwarven Spider courier
        Courier.ForceRefTo(DwarfSpiderCourier.GetRef())
    endif
    Updating = False
EndFunction

Function ChangeContainer(String Which) 
; Change to the specified container, and update the Item Count to match it's contents.

;Debug.Notification("Changing Container: " + Which)

    While Courier.GetRef().IsEnabled() || Updating 
; If a courier is currently trying to deliver something, wait til it's done.
        Wait(1.0)
    endWhile
    Updating = True 
; Don't spawn a new courier or change courier types while we're switching containers.
    StoreContainerQuantity() 
; Saves the quantity in the current container, in case a delivery failed.
; We can go back to it later, but that's the responsibility of the mod writer to check.
    if Which == "Hive"
        ContainerAlias.ForceRefTo(HiveContainer.GetRef())
        ItemCount.SetValue(HiveCount)
    elseif Which == "Coffin"
        ContainerAlias.ForceRefTo(CoffinContainer.GetRef())
        ItemCount.SetValue(CoffinCount)
    elseif Which == "Barrel"
        ContainerAlias.ForceRefTo(BarrelContainer.GetRef())
        ItemCount.SetValue(BarrelCount)
    elseif Which == "Small"
        ContainerAlias.ForceRefTo(DwSmContainer.GetRef())
        ItemCount.SetValue(DwSmCount)
    elseif Which == "Medium"
        ContainerAlias.ForceRefTo(DwMedContainer.GetRef())
        ItemCount.SetValue(DwMedCount)
    elseif Which == "Large"
        ContainerAlias.ForceRefTo(DwLgContainer.GetRef())
        ItemCount.SetValue(DwLgCount)
    else ; Default to large Dwarven Box container
        ContainerAlias.ForceRefTo(DwLgContainer.GetRef())
        ItemCount.SetValue(DwLgCount)
    endif
    Updating = False
EndFunction

function addItemToContainer(form FormToAdd, int countToAdd = 1)
    ContainerAlias.GetRef().addItem(FormToAdd, countToAdd) ;add parameter object to container
    ItemCount.Value += 1
endFunction

function addRefToContainer(objectReference objectRefToAdd)
    ContainerAlias.GetRef().addItem(objectRefToAdd) ;add parameter object to container
    ItemCount.Value += 1
endFunction

function addAliasToContainer(ReferenceAlias refAliasToAdd)
    addRefToContainer(( refAliasToAdd.getRef() as ObjectReference))
EndFunction

function GiveItemsToPlayer()
    ItemCount.SetValue(0)
    ContainerAlias.GetRef().RemoveAllItems(Game.GetPlayer())
    Debug.Notification("Item(s) Added.")
EndFunction

ObjectReference Function FindBeaminLocation() 

; Used if we are not in an area with preset courier spawn points. Release 3 bouncers

    ObjectReference PlaceTarget = Game.GetPlayer().PlaceAtMe(MarkerType,1)
    ObjectReference[] Bouncer = new ObjectReference[3]
    PlaceTarget.MoveTo(Game.GetPlayer(),5000.0,5000.0,5000.0)
    Bouncer[0] = PlaceTarget.PlaceAtme(BouncerType,1)
    PlaceTarget.MoveTo(Game.GetPlayer(),-5000.0,-5000.0,5000.0)
    Bouncer[1] = PlaceTarget.PlaceAtme(BouncerType,1)
    PlaceTarget.MoveTo(Game.GetPlayer(),-5000.0,5000.0,5000.0) ; Place them at 3 corners of a square.
    Bouncer[2] = PlaceTarget.PlaceAtme(BouncerType,1)
    Wait(5.0) ; Let them fall to the ground. We don't want to bounce our poor courier around too much.
    ObjectReference sorthold
    if Game.GetPlayer().GetDistance(Bouncer[0]) > Game.GetPlayer().GetDistance(Bouncer[2])
        SortHold = Bouncer[2]
        Bouncer[2] = Bouncer[0]
        Bouncer[0] = SortHold
    endif
    if Game.GetPlayer().GetDistance(Bouncer[0]) > Game.GetPlayer().GetDistance(Bouncer[1])
        SortHold = Bouncer[0]
        Bouncer[0] = Bouncer[1]
        Bouncer[1] = SortHold
    endif
    if Game.GetPlayer().GetDistance(Bouncer[1]) > Game.GetPlayer().GetDistance(Bouncer[2])
        SortHold = Bouncer[2]
        Bouncer[2] = Bouncer[1]
        Bouncer[1] = SortHold
    endif
    ObjectReference Loc1 = Bouncer[0]
    ObjectReference Loc2 = Bouncer[1]
    ObjectReference Loc3 = Bouncer[2]
    ObjectReference BILoc

    if Loc3 && Loc3.GetDistance(Game.GetPlayer()) <= 10000.0 ; Didn't fall through the world
                    BiLoc = Loc3
    endif
    if !BiLoc && Loc2 && Loc2.GetDistance(Game.GetPlayer()) <= 10000.0 ; Ditto previous comment
                    BiLoc = Loc2
    endif
    if !BiLoc
            BiLoc = Loc1
    endif
    return BiLoc
EndFunction

Function StateChange(String Which)
    GoToState(Which)
EndFunction

Bool Updating = False

Event OnUpdate()
    GoToState("Waiting") ; Starts us in the waiting state, in case we somehow got in the empty state.
EndEvent

State Waiting
    Event OnUpdate() ; Make sure all variables are reset.
    Updating = False
    WhereToGo = None
           Courier.Getref().MoveTo(CourierCellMarker.GetRef())
    Courier.GetRef().Disable()
            If ItemCount.value >= 1.0
        SetStage(100)
            endif
    EndEvent
EndState

State Seeking
   Event OnUpdate() ; First, find a map marker near the player.

    if Courier.GetRef().GetDistance(Game.GetPlayer()) <= 500.0
        WhereToGo = None ; Clear out the property for the next delivery.
        SetStage(200)
    elseif Courier.GetRef().IsEnabled() && Courier.GetRef().GetDistance(Game.GetPlayer()) > 30000.0 
; Player has eluded the courier. Put it away.
        WhereToGo = None ; If at first you don't succeed...
            Courier.Getref().MoveTo(CourierCellMarker.GetRef())
            Courier.Getref().Disable() ; Go to sleep until the next update cycle.
    endif
    if !Updating && Courier.Getref().IsDisabled()
; Courier is not in the world yet, and we're not already trying to place one.
        Updating = True
        If !WhereToGo ; If we haven't updated yet this delivery
            if ReloadOptionals.IsRunning()
                ReloadOptionals.Stop()
                Wait (1.0)
            Endif
            ReloadOptionals.Start() 
; Force a reload of the optional aliases in case we're in a city cell.
            ReloadOptionals.SetStage(0)
            Wait(2.0)
        endif
        ObjectReference Loc1 = Gvar.Near
        ObjectReference Loc2 = Gvar.Mid
        ObjectReference Loc3 = Gvar.Far
        if Loc3
            WhereToGo = Loc3
        endif
        if Loc2 && (!Loc3 || (Loc3 && Game.GetPlayer().GetDistance(Loc3) > 10000.0))
            WhereToGo = Loc2
        endif
        if Loc1 && (!Loc2 || (Loc2 && Game.GetPlayer().GetDistance(Loc2) > 10000.0))
            WhereToGo = Loc1
        Endif

        if !WhereToGo || Game.GetPlayer().GetDistance(WhereToGo) < 900.0 
; No location found yet, or it's too close. (Player is in a cell with no viable markers)
            WhereToGo = FindBeaminLocation()
            if WhereToGo ; Found a spot!
                Courier.Getref().MoveTo(WhereToGo)
                Courier.Getref().Enable()
            endif
        else
            Courier.Getref().MoveTo(WhereToGo)
            Courier.Getref().Enable()
        endif
        Updating = False
    endif

;float DeltaX = Game.GetPlayer().X - Courier.GetRef().X
;float DeltaY = Game.GetPlayer().Y - Courier.GetRef().Y
;float DeltaZ = Game.GetPlayer().Z - Courier.GetRef().Z

;Debug.Notification("Courier Seeking: "+DeltaX+","+DeltaY+","+DeltaZ) 
; Show the player where the courier is for testing.

   EndEvent
EndState


State Delivering
    Event OnUpdate()
            If ItemCount.value == 0
                    SetStage(300)
            endif
    EndEvent
EndState


State Departing
    Event OnUpdate()
; Debug.Notification("Courier Leaving")
            If !Game.GetPlayer().HasLOS(Courier.GetRef())
               Courier.Getref().MoveTo(CourierCellMarker.GetRef())
               Courier.Getref().Disable()
               Updating = False
               SetStage(0)
            endif
    EndEvent
EndState

State Disabled

    Event OnUpdate() ; Courier is out. Come back later.
;        Debug.Notification("Courier Disabled")
    EndEvent

EndState

ObjectReference Property WhereToGo Auto
Activator Property BouncerType Auto
Activator property MarkerType Auto
Quest Property ReloadOptionals  Auto  

CourierSpawnPointScript Property Gvar Auto

Loader Quest[edit | edit source]

Since there is no way apparently to make an alias recheck it's conditionals and reload on an "always running" quest, We will need to make a second quest to load the spawn points if the player is in a town.

Note the Quest property ReloadOptionals - That will be a quest that has 3 aliases for the closest, next closest, and third closest spawn points in the nearby areas. ("GVAR" is the same quest, but cast to CourierSpawnPointScript for easy access to the properties.) They are all "Find Matching", "Closest", and "In Loaded Area". Their conditions are:

  • Nearest:
    HasKeyword (the keyword we defined above) == 1.0
  • NextNearest:
    HasKeyword (the keyword we defined above) == 1.0
    GetIsAliasRef (Nearest) != 1.0
  • ThirdNearest:
    HasKeyword (the keyword we defined above) == 1.0
    GetIsAliasRef (Nearest) != 1.0
    GetIsAliasRef (NextNearest) != 1.0

The quest loads these aliases into three properties, Near, Mid, and Far with a script like this:

Scriptname CourierSpawnPointScript extends Quest  

ObjectReference Property Near Auto
ReferenceAlias Property aNear Auto

ObjectReference Property Mid Auto
ReferenceAlias Property aMid Auto

ObjectReference Property Far Auto
ReferenceAlias Property aFar Auto

Event OnInit()
	Utility.Wait(5.0)
	Near = aNear.GetRef()
	Mid = aMid.GetRef()
	far = aFar.GetRef()
EndEvent

Assign ANear, AMid and AFar to the above mentioned aliases.

To use the custom courier, create a property for your script type in the quest and call one of the add functions, depending on whether you're using an alias or an objectreference or something else. You can change the courier type by setting up the Change courier function and you can switch containers if you want for a variety of different behaviors.

Uses[edit | edit source]

In addition to simple couriers, this could be adapted to spawn encounters with hostile mobs, by simply leaving in the hostile AI, adding a Global WICCPOD {"Place objects on NPC on death"} that kills the delivery scene, and adding a script to the KillableCourier alias that catches the OnDeath event, and places the items from the chest into the "Courier" instead of giving it to the player. (You would also need to make another alias "KillableCourier" that only loads if the WICCPOD property is 1.0, and put code in the Delivery state code that clears the Courier alias once the KillableCourier finds the player, making the Essential property fall off so it can be killed.

See Also[edit | edit source]