Jump to content

[LE] Help with a script to get the value(s) of stolen item(s)


jple

Recommended Posts

Hey folks. New here, working on a mod that overhauls crime gameplay and the Thieves Guild.

 

I'm looking for a way to track and save to a global variable the total value of stolen goods sold to fences (or to other vendors with the perk that allows to sell stolen goods to normal merchants).

The first thing I tried is to look for the way it was done in Oblivion. The Thieves Guild in Oblivion has a TGStolenGoods quest that tracks this exact value and sets the quest's stages according to that value in the quest's script:

 

 

 

ScriptName TGStolenGoodsScript

Float TotalStolen

; TotalStolen is updated in the Generic quest in the Barter topic.

Begin GameMode
    If GetAmountSoldStolen >= 50 && GetQuestRunning TG02Taxes == 1
            SetStage TGStolenGoods 25
    ElseIf GetAmountSoldStolen >= 100 && GetQuestRunning TG03Elven == 1
            SetStage TGStolenGoods 35
    ElseIf GetAmountSoldStolen >= 200  && GetQuestRunning TG04Mistake == 1
            SetStage TGStolenGoods 45
    ElseIf GetAmountSoldStolen >= 300 && GetQuestRunning TG05Misdirection == 1
            SetStage TGStolenGoods 55
    ElseIf GetAmountSoldStolen >= 400  && GetQuestRunning TG06Atonement == 1
            SetStage TGStolenGoods 65
    ElseIf GetAmountSoldStolen >= 500 && GetQuestRunning TG07Lex == 1
            SetStage TGStolenGoods 75
    ElseIf GetAmountSoldStolen >= 600 && GetStage TG08Blind == 1
                SetStage TGStolenGoods 85
    ElseIf GetAmountSoldStolen >= 700 && GetStage TG09Arrow == 1
            SetStage TGStolenGoods 95
    ElseIf GetAmountSoldStolen >= 800 && GetStage TG10Boots == 1
            SetStage TGStolenGoods 105
    ElseIf GetAmountSoldStolen >= 1000 && GetStage TG11Heist == 1
            SetStage TGStolenGoods 115
    EndIf
End

 


The GetAmountSoldStolen function tracks the total value of stolen goods sold and it works as intended in Oblivion.

I've found that this exact function still exists in Skyrim under the same name and is a condition function, so you can't execute it in Papyrus, you can only use it via conditions in the CK or the game's console.

What I planned to do was to use ConsoleUtil (adds Papyrus functions that allow to execute console commands and read the output to a string) to get the desired value. Unfortunately, the function never seems to be modified by selling stolen items, it always stays at 0 value. I figured that the function that modifies the value just never gets called by the engine, so to use that was off the table.

 

Next thing I tried was adding a script to a ReferenceAlias of the fence's merchant chest. In short the script listens for the OnItemAdded event and checks if the item is stolen using GetActorOwner (owner is not the player and is not None). Unfortunately I quickly realized that the owner is None most of the time (figured that the ObjectReference of the item is probably non-persistent).

 

So here's what I ended up with at the end:

 

 

 

Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
    Debug.Trace("LevelingFenceScript: Item " + aiItemCount + "x " + akBaseItem)
    if akBaseItem != Gold001 as Form                                        ;; ignore gold
        AdvanceThievesGuildSkill(akBaseItem.GetGoldValue(), aiItemCount)
    endIf
endEvent

 


This allows me to only track the value of any item(s) sold to a specific fence, regardless of the stolen tag.

 

At this point honestly I don't mind if i just track the amount of stolen items sold, but I have no idea if that's possible either. I'd appreciate any help.

Edited by CzechHero
Link to comment
Share on other sites

There's an ObjectReference SKSE function IsOffLimits() so you could try, If akItemReference.IsOffLimits() to check for stolen items, although this might not work because when items enter a container they lose their reference, unless they are persistent. It might work though, you'd have to test it.

Link to comment
Share on other sites

Thanks for the tip, had no idea that function exists. It works perfectly when I add a stolen item to the inventory or drop the item in the world.

Unfortunately it doesn't work for items added to containers or items removed from the inventory during bartering, so it seems like you were right with the fact that items lose their reference on entering a container.

Here's what I tried:

 

 

 

ReferenceAlias script referencing the Player:

Scriptname FO_TG_LevelingQuestScript extends ReferenceAlias

Actor Property PlayerRef Auto
GlobalVariable Property FO_TG_StolenTotal Auto

Bool isBartering = false

Event OnInit()

	RegisterForMenu("BarterMenu")

EndEvent

Event OnPlayerLoadGame()

	RegisterForMenu("BarterMenu")

EndEvent

Event OnMenuOpen(String MenuName)

	if MenuName == "BarterMenu"
		Debug.Trace("BarterMenu opened, starting barter tracking")
		isBartering = true
	endIf

EndEvent

Event OnMenuClose(String MenuName)

	if MenuName == "BarterMenu"
		Debug.Trace("BarterMenu closed, stopping barter tracking")
		isBartering = false
	endIf

EndEvent

Event OnItemRemoved(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akDestContainer)

	if isBartering == true
		if akItemReference.IsOffLimits()
			Debug.Trace("A stolen item was sold to a fence.")
		endIf
	endIf

EndEvent

Papyrus log:

[01/24/2021 - 11:26:57AM] Error: Cannot call IsOffLimits() on a None object, aborting function call
stack:
	[alias PlayerAlias on quest FO_TG_LevelingQuest (08019D2E)].FO_TG_LevelingQuestScript.OnItemRemoved() - "FO_TG_LevelingQuestScript.psc" Line ?
[01/24/2021 - 11:26:57AM] warning: Assigning None to a non-object variable named "::temp7"
stack:
	[alias PlayerAlias on quest FO_TG_LevelingQuest (08019D2E)].FO_TG_LevelingQuestScript.OnItemRemoved() - "FO_TG_LevelingQuestScript.psc" Line ?

 

 

 

I'll see if I can think of another solution in the meantime.

 

EDIT: So I recalled that the OnStoryRemoveFromPlayer quest event exists and has an ObjectReference parameter. I'll see if that ObjectReference is persistent.

EDIT2: So I tested that with the quest event then with an alias script which finds the matching reference from the event and takes the ItemRef data, and both work only when dropping the item, not when it's moved to a container. Too bad.

Edited by CzechHero
Link to comment
Share on other sites

Hmm, in that case, you can maybe force your fence to drop items added by the player so that they have an object ref, then pick them up again. Something like this:

Actor Property PlayerRef Auto 

Event OnItemAdded(Form akBaseItem, Int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer) 
   If akSourceContainer == PlayerRef 
      ObjectReference Item = Self.DropObject(akBaseItem, 1) 
      If Item.IsOffLimits() 
          Debug.Trace(Item.GetDisplayName() + " Is Stolen") 
      Endif 
      
      Self.AddItem(Item)
  Endif
EndEvent
Link to comment
Share on other sites

Just tested it and it works perfectly when adding an item to the fence's container from the player's inventory. However when bartering with the fence all items get tagged as stolen. Might be because through bartering the ownership of the item changes to the ActorBase of the fence. Tried changing the ownership of the container to None, that didn't change anything.

 

Well, it's starting to seem like something like this will only be possible with an SKSE plugin. Might have to look up how to make one or abandon this feature.

Edited by CzechHero
Link to comment
Share on other sites

So I finally figured it out! Thanks a lot for the help dylbill.

 

Here's a complete solution on how to check if an item was stolen during bartering/adding to a container:

  1. Prepare a custom cell (no owner npc/faction) with a helper container inside it.
  2. Create a quest with a ReferenceAlias that references the merchant's/fence's trade chest.
  3. Add a script to the ReferenceAlias


    Actor Property PlayerRef Auto
    MiscObject Property Gold001 Auto
    ObjectReference Property FenceChestRef Auto
    
    Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
    
        if akSourceContainer == PlayerRef && akBaseItem != Gold001 as Form                        ;; ensure that it was the player that added the item(s) and the item is not gold
            Debug.Trace("LevelingFenceScript: Item " + aiItemCount + "x " + akBaseItem)
            ObjectReference ref = GetReference()
            ref.RemoveItem(akBaseItem, 1, true, FenceChestRef)                                    ;; move the item to a helper container in a custom neutral cell
            ObjectReference item = FenceChestRef.DropObject(akBaseItem, 1)                        ;; drop the item in the world so we can check if it was stolen
        
            if item.IsOffLimits()                                                                 ;; check if the item was stolen
                Debug.Trace(item.GetDisplayName() + " was stolen")
                FO_TG_StolenTotal.Mod(aiItemCount * akBaseItem.GetGoldValue())                    ;; add to the total value of stolen items sold, TODO: implement a custom function to calc enchanted items value
                Debug.Trace("Total value of stolen items sold: " + FO_TG_StolenTotal.GetValue())
            endIf
          
            ref.AddItem(item)                                                                     ;; add the item back
        endIf
    
    EndEvent
    

     

     

  4. Auto-Fill all properties and pick the reference for the helper container in the custom cell.

Edited by CzechHero
Link to comment
Share on other sites

  • 2 weeks later...

 

So I finally figured it out! Thanks a lot for the help dylbill.

 

Here's a complete solution on how to check if an item was stolen during bartering/adding to a container:

  1. Prepare a custom cell (no owner npc/faction) with a helper container inside it.
  2. Create a quest with a ReferenceAlias that references the merchant's/fence's trade chest.
  3. Add a script to the ReferenceAlias

     

     

    Actor Property PlayerRef Auto
    MiscObject Property Gold001 Auto
    ObjectReference Property FenceChestRef Auto
    
    Event OnItemAdded(Form akBaseItem, int aiItemCount, ObjectReference akItemReference, ObjectReference akSourceContainer)
    
        if akSourceContainer == PlayerRef && akBaseItem != Gold001 as Form                        ;; ensure that it was the player that added the item(s) and the item is not gold
            Debug.Trace("LevelingFenceScript: Item " + aiItemCount + "x " + akBaseItem)
            ObjectReference ref = GetReference()
            ref.RemoveItem(akBaseItem, 1, true, FenceChestRef)                                    ;; move the item to a helper container in a custom neutral cell
            ObjectReference item = FenceChestRef.DropObject(akBaseItem, 1)                        ;; drop the item in the world so we can check if it was stolen
        
            if item.IsOffLimits()                                                                 ;; check if the item was stolen
                Debug.Trace(item.GetDisplayName() + " was stolen")
                FO_TG_StolenTotal.Mod(aiItemCount * akBaseItem.GetGoldValue())                    ;; add to the total value of stolen items sold, TODO: implement a custom function to calc enchanted items value
                Debug.Trace("Total value of stolen items sold: " + FO_TG_StolenTotal.GetValue())
            endIf
          
            ref.AddItem(item)                                                                     ;; add the item back
        endIf
    
    EndEvent
    

     

     

  4. Auto-Fill all properties and pick the reference for the helper container in the custom cell.

 

 

How reliable is this if there is a duplicate item already in the container?

Link to comment
Share on other sites

I'm talking to a store owner. Triggers a script to duplicate all stolen in your backpack.

I'm done talking to the store owner. Script compares what stolen items you still have verse what's missing.

This could be done with a hidden chest like you said or an array within the script. The array method would be sweet and clean.

No bonus to your created skill unless the scripts completes (as that is the last few line of the script). Just incase they exit the game mid transaction.

 

Routines: Inventory count and find stolen items. Push list to array or chest, Inventory count and compare. Skill points added. Chest or array cleared.

Triggers: I'm talking to a shop owner, I'm finished talking to a store owner.

Edited by NexusComa2
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...