Jump to content

Investigators of Skyrim - Need help with some ideas and scripts


Recommended Posts

Hello. I finally got some time to work on a mod that i'm very excited about. It's basically a mod that counts the player as a suspect of a crime if they were seen before and/or after committing a crime. Here is a schematic explaining how it works:

Schematic

The idea is to check whether if the player is being spotted before and after a crime is committed. Why? well, in vanilla Skyrim, the player is only found guilty of a crime if they are detected in the act. But what if there is a way to consider the player suspect of a crime moments before and/or after a crime is committed? This will force the player to be cautious and sneaky before attempting some dirty stuff. In the vanilla game you can enter a house, their owners see you and even address to you, you go to their bedrooms and as soon as you can get hidden you can start stealing or murdering without any consequences as long as nobody detects you. With this mod, if people sees you before or after you commit a crime, you will become a suspect and justice will come to you eventually if you are not careful enough.

Now i've made three quests: One for the aliases to get the witness and/or the victim, one that controls the system retrieving the aliases and working with the actions the player does and calling the ambush mechanic when needed, and a third one that manages the ambush. So far the mod works as intended, and it's a game changer.

Currently i need help with these:

1- About the ambush mechanic, is there a way to spawn the characters behind the player? also out of their sight? i searched for a method but couldn't find any, well maybe there are some functions that could do the trick but i couldn't find any examples. Here is the code that i use for the ambush:

Scriptname AmbushQuestScript extends Quest  

Actor Property PlayerRef  Auto  
GlobalVariable Property AmbushTimer  Auto  
GlobalVariable Property MurderBounty  Auto  
GlobalVariable Property StolenGoodsValue  Auto  
GlobalVariable Property BountyValue  Auto  
GlobalVariable Property WillGetAmbush  Auto  
Location Property EastmarchHoldLocation  Auto  
Location Property FalkreathHoldLocation  Auto  
Location Property HaafingarHoldLocation  Auto  
Location Property HjaalmarchHoldLocation  Auto  
Location Property PaleHoldLocation  Auto  
Location Property ReachHoldLocation  Auto  
Location Property RiftHoldLocation  Auto  
Location Property WhiterunHoldLocation  Auto  
Location Property WinterholdHoldLocation  Auto  
Location Property DLC2SolstheimLocation  Auto  
ActorBase Property MercSolitude  Auto  
ActorBase Property GuardSolitude  Auto  
GlobalVariable Property ATimerShort  Auto  
GlobalVariable Property MurderCount  Auto  
GlobalVariable Property TheftCount  Auto  
SPELL Property Invisibility  Auto 
Faction Property CrimeFactionWhiterun  Auto
Faction Property CrimeFactionHaafingar  Auto  
Faction Property CrimeFactionHjaalmarch  Auto  
Faction Property CrimeFactionReach  Auto  
Faction Property CrimeFactionEastmarch  Auto  
Faction Property CrimeFactionWinterhold  Auto  
Faction Property CrimeFactionFalkreath  Auto  
Faction Property CrimeFactionRift  Auto  
Faction Property CrimeFactionPale  Auto  
Faction Property DLC2CrimeRavenRockFaction  Auto  
GlobalVariable Property ATShortMin  Auto  
GlobalVariable Property ATShortMax  Auto  
Actor Property InvSolitude  Auto  
Actor Property InvWhiterun  Auto  
Actor Property InvMorthal  Auto  
Actor Property InvMarkarth  Auto  
Actor Property InvSolstheim  Auto  
Actor Property InvWindhelm  Auto  
Actor Property InvWinterhold  Auto  
Actor Property InvFalkreath  Auto  
Actor Property InvRiften  Auto  
Actor Property InvDawnstar  Auto  
ActorBase Property MercDawnstar  Auto  
ActorBase Property MercFalkreath  Auto  
ActorBase Property MercMarkarth  Auto  
ActorBase Property MercMorthal  Auto  
ActorBase Property MercRiften  Auto  
ActorBase Property MercSolstheim  Auto  
ActorBase Property MercWhiterun  Auto  
ActorBase Property MercWindhelm  Auto  
ActorBase Property MercWinterhold  Auto  
ActorBase Property GuardDawnstar  Auto  
ActorBase Property GuardFalkreath  Auto  
ActorBase Property GuardMarkarth  Auto  
ActorBase Property GuardMorthal  Auto  
ActorBase Property GuardRiften  Auto  
ActorBase Property GuardWhiterun  Auto  
ActorBase Property GuardWindhelm  Auto  
ActorBase Property GuardWinterhold  Auto  
ActorBase Property GuardSolstheim  Auto  
GlobalVariable Property AmbushGoing  Auto  
GlobalVariable Property GotAmbushed  Auto 


Event OnInit()
int Timer =  AmbushTimer.GetValueInt()
	Self.RegisterForSingleUpdate(60)
							Debug.Notification("Eyes are watching you!")
endEvent


Event OnUpdate()
int Timer =  AmbushTimer.GetValueInt()
int TimerShort = Utility.RandomInt(ATShortMin.GetValueInt(), ATShortMax.GetValueInt())

If WillGetAmbush.GetValue() == 1
	Debug.Notification("Checking locationl!")
	If PlayerRef.IsInLocation(EastmarchHoldLocation)
		CrimeFactionEastmarch.modcrimegold(CrimeFactionEastmarch.GetCrimeGold() + StolenGoodsValue.GetValueInt() + BountyValue.GetValueInt(), true)
		Ambush(InvWindhelm, MercWindhelm, GuardWindhelm)

	ElseIf PlayerRef.IsInLocation(FalkreathHoldLocation)
		CrimeFactionFalkreath.modcrimegold(CrimeFactionFalkreath.GetCrimeGold() + StolenGoodsValue.GetValueInt() + BountyValue.GetValueInt(), true)
		Ambush(InvFalkreath, MercFalkreath, GuardFalkreath)

	ElseIf PlayerRef.IsInLocation(HaafingarHoldLocation)
		CrimeFactionHaafingar.modcrimegold(CrimeFactionHaafingar.GetCrimeGold() + StolenGoodsValue.GetValueInt() + BountyValue.GetValueInt(), true)
		Ambush(InvSolitude, MercSolitude, GuardSolitude)	

	ElseIf PlayerRef.IsInLocation(HjaalmarchHoldLocation)
		CrimeFactionHjaalmarch.modcrimegold(CrimeFactionHjaalmarch.GetCrimeGold() + StolenGoodsValue.GetValueInt() + BountyValue.GetValueInt(), true)
		Ambush(InvMorthal, MercMorthal, GuardMorthal)

	ElseIf PlayerRef.IsInLocation(PaleHoldLocation)
		CrimeFactionPale.modcrimegold(CrimeFactionPale.GetCrimeGold() + StolenGoodsValue.GetValueInt() + BountyValue.GetValueInt(), true)
		Ambush(InvDawnstar, MercDawnstar, GuardDawnstar)	

	ElseIf PlayerRef.IsInLocation(ReachHoldLocation)
		CrimeFactionReach.modcrimegold(CrimeFactionReach.GetCrimeGold() + StolenGoodsValue.GetValueInt() + BountyValue.GetValueInt(), true)
		Ambush(InvMarkarth, MercMarkarth, GuardMarkarth)

	ElseIf PlayerRef.IsInLocation(RiftHoldLocation)
		CrimeFactionRift.modcrimegold(CrimeFactionRift.GetCrimeGold() + StolenGoodsValue.GetValueInt() + BountyValue.GetValueInt(), true)
		Ambush(InvRiften, MercRiften, GuardRiften)

	ElseIf PlayerRef.IsInLocation(WhiterunHoldLocation)
		CrimeFactionWhiterun.modcrimegold(CrimeFactionWhiterun.GetCrimeGold() + StolenGoodsValue.GetValueInt() + BountyValue.GetValueInt(), true)
		Ambush(InvWhiterun, MercWhiterun, GuardWhiterun)

	ElseIf PlayerRef.IsInLocation(WinterholdHoldLocation)
		CrimeFactionWinterhold.modcrimegold(CrimeFactionWinterhold.GetCrimeGold() + StolenGoodsValue.GetValueInt() + BountyValue.GetValueInt(), true)
		Ambush(InvWinterhold, MercWinterhold, GuardWinterhold)	

	ElseIf PlayerRef.IsInLocation(DLC2SolstheimLocation)
		DLC2CrimeRavenRockFaction.modcrimegold(DLC2CrimeRavenRockFaction.GetCrimeGold() + StolenGoodsValue.GetValueInt() + BountyValue.GetValueInt(), true)
		Ambush(InvSolstheim, MercSolstheim, GuardSolstheim)
		
	EndIf

EndIf

WillGetAmbush.SetValue(0)
	
EndEvent



Function Ambush(Actor Investigator, ActorBase Bodyguard, ActorBase Guard)
												Debug.Notification("Function processing!")
ObjectReference myGuard = PlayerRef.PlaceAtMe(Guard)
myGuard.MoveTo(PlayerRef, 500.0 * Math.Sin(PlayerRef.GetAngleZ()), 500.0 * Math.Cos(PlayerRef.GetAngleZ()), PlayerRef.GetHeight())

ObjectReference myGuard2 = PlayerRef.PlaceAtMe(Guard)
myGuard2.MoveTo(PlayerRef, 500.0 * Math.Sin(PlayerRef.GetAngleZ()), 500.0 * Math.Cos(PlayerRef.GetAngleZ()), PlayerRef.GetHeight())

ObjectReference myGuard3 = PlayerRef.PlaceAtMe(Guard)
myGuard3.MoveTo(PlayerRef, 500.0 * Math.Sin(PlayerRef.GetAngleZ()), 500.0 * Math.Cos(PlayerRef.GetAngleZ()), PlayerRef.GetHeight())

ObjectReference myBodyGuard = PlayerRef.PlaceAtMe(Bodyguard)
myBodyGuard.MoveTo(PlayerRef, 500.0 * Math.Sin(PlayerRef.GetAngleZ()), 500.0 * Math.Cos(PlayerRef.GetAngleZ()), PlayerRef.GetHeight())

Investigator.MoveTo(PlayerRef, 500.0 * Math.Sin(PlayerRef.GetAngleZ()), 500.0 * Math.Cos(PlayerRef.GetAngleZ()), PlayerRef.GetHeight())
	
	Invisibility.cast(Investigator, myBodyguard)
	Invisibility.cast(Investigator, myGuard)
	Invisibility.cast(Investigator, myGuard2)
	Invisibility.cast(Investigator, myGuard3)
	Invisibility.cast(Investigator, InvSolitude)

(myGuard as Actor).PathToReference(PlayerRef, 0.5)
(myGuard2 as Actor).PathToReference(PlayerRef, 0.5)
(myGuard3 as Actor).PathToReference(PlayerRef, 0.5)
(myBodyGuard as Actor).PathToReference(PlayerRef, 0.5)
Investigator.PathToReference(PlayerRef, 0.5)

		StolenGoodsValue.SetValue(0)
		BountyValue.SetValue(0)
		GotAmbushed.SetValue(1)
		
        utility.wait(1.0)


EndFunction



What this script does is the following: It spawns a delta force team of one investigator (unique actor level 40 spellsword), one bodyguard (leveled actor, level 20-30) and three leveled guards, all of them with the same factions as the guards from the corresponding holds. If after committing a crime (after crossing a certain threshold) the player gets ambushed with a 60 seconds timer (i plan to add a random timer later but right now i'm still testing).

I kinda get how the "moveto" function works, but my question basically is if there is a better way to do this? currently the npcs spawn correctly and i add an invisibility effect to make it more... palatable, but still you can see them spawning mid air for a split second so it looks kinda clunky.

2- Committing any crime doesn't matter as long as the system takes note of it. If i steal something before and/or after being watched i become suspect of theft (SuspectTheft = 1), same if i get detected before and/or after killing someone (SuspectMurder = 1). I add a misc non-playable item (DeadToken) and check if the corpse of the npc i just killed has it, in which case it will be ignored afterwards. I could add more aliases for bodies and witnesses but that feels wrong, like how many? should i add 5 more? ten more just in case? it would be nice if a more organic and simpler solution is found, and here comes the question: is there a reliable way to detect exactly whenever the player kills someone?  Otherwise what i do is to check every second for corpses of npcs that the player has killed. But if there is a way i would appreciate you give me a hand because this would improve my mod a lot. The stealing part is covered as i can detect whenever the player adds a stolen item onto their inventory, trap its value and store it in "StolenGoodsValue", piling them up, only if the player has been seen though, otherwise the vanilla system works. 

3- Another weak spot in my mod is that if i kill a witness to try to avoid becoming a suspect, it still counts as if i was seen so it's useless to do it. I wonder if there is a way to track the npc that saw me? maybe store it somewhere until it dies?

4- Another thing my mod omits, for convenience, is the steal of money, gold, as there would be too many checks for let's say stealing 60 gold at once from a chest. My mod would need to check every single damn coin added and stolen. That's too much. So i let the vanilla game deal with that. It seem obvious but i like to play with a mod that takes the stolen tag away for items under a certain threshold value as long as i'm undetected. The question would be if there is a way to implement this in this particular case where the tag is taken away from items? if a player doesn't play with a mod like that and relies in the vanilla stealing system then there is no need to keep track of the value of stolen goods as these are tracked by the vanilla game. Still something minor to consider.

I could be more specific but don't want to overcharge the thread with scripts and explanations. My mod works as intended, but i need help optimizing some functions.

In general any suggestions/insights/corrections would be appreciated. If you have any questions just ask.

Edited by tomahawk6633
forgot to add some tags
Link to comment
Share on other sites

PlaceAtMe has an abInitiallyDisabled parameter. Set this to true. This will make the placed reference invisible until you want it to be visible. Then use .enable() (after moveto) to make them visible.

Link to comment
Share on other sites

  On 4/15/2025 at 12:00 AM, tomahawk6633 said:

is there a reliable way to detect exactly whenever the player kills someone?  Otherwise what i do is to check every second for corpses of npcs that the player has killed. But if there is a way i would appreciate you give me a hand because this would improve my mod a lot.

Expand  

@dylbill has uncharacteristically missed an opportunity to publicize his magic SKSE plugin which (I assume) allows you to receive all OnDeath( akKiller) events.  Filtering for akKiller == playerRef should be a simple matter.

Link to comment
Share on other sites

  On 4/15/2025 at 2:13 AM, xkkmEl said:

@dylbill has uncharacteristically missed an opportunity to publicize his magic SKSE plugin which (I assume) allows you to receive all OnDeath( akKiller) events.  Filtering for akKiller == playerRef should be a simple matter.

Expand  

Lol true. I will admit I only skimmed the original comment, but you can use my papyrus functions mod to do this: 

Event OnInit()
    ;register this script (self) for when the player kills anyone
    DbSkseEvents.RegisterFormForGlobalEvent("OnDeathGlobal", self, Game.GetPlayer(), 1)
EndEvent
 
Event OnDeathGlobal(Actor Victim, Actor Killer)
    Debug.Notification(Killer.GetDisplayName() + " killed " + Victim.GetDisplayName())
EndEvent
Link to comment
Share on other sites

  On 4/15/2025 at 12:46 AM, dylbill said:

PlaceAtMe has an abInitiallyDisabled parameter. Set this to true. This will make the placed reference invisible until you want it to be visible. Then use .enable() (after moveto) to make them visible.

Expand  

Ah, i missed that, it happens whe you just copy the examples and don't read the arguments. One issue solved.

  On 4/15/2025 at 3:38 AM, dylbill said:

Lol true. I will admit I only skimmed the original comment, but you can use my papyrus functions mod to do this: 

Event OnInit()
    ;register this script (self) for when the player kills anyone
    DbSkseEvents.RegisterFormForGlobalEvent("OnDeathGlobal", self, Game.GetPlayer(), 1)
EndEvent
 
Event OnDeathGlobal(Actor Victim, Actor Killer)
    Debug.Notification(Killer.GetDisplayName() + " killed " + Victim.GetDisplayName())
EndEvent
Expand  

This certainly looks too good to be real. Short and elegant. Will certainly give the fight to implement it. Thanks.

Link to comment
Share on other sites

  On 4/15/2025 at 5:11 AM, tomahawk6633 said:

Ah, i missed that, it happens whe you just copy the examples and don't read the arguments. One issue solved.

This certainly looks too good to be real. Short and elegant. Will certainly give the fight to implement it. Thanks.

Expand  

No problem ๐Ÿ™‚ if you need another example my mod NPC Death Alerts uses that event. Happy modding!

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
ร—
ร—
  • Create New...