Jump to content

How to properly affect all NPCs? [Papyrus]


Hundsfott

Recommended Posts

Hey dear SSE modding community,

 

I am just learning to script in Papyrus (have scripted for Rockstar Games titles before) and have no idea how to start.

Creation Kit as well as Notepad++ with syntax highlighting and all are set up, I have already scripted my first message box and have done damage to the player's health.

I now want to set the health of all NPCs to a certain value, for my next example.

But I dont know how to get all the NPCs.

Could you guys help me out?

 

Here is what I have now:

Event OnInit()
	Debug.MessageBox("Decreasing of Health is now starting.")
	while (true)
		if (Game.GetPlayer().GetActorValuePercentage("health") > 0.1)
			Game.GetPlayer().DamageActorValue("health", 5)
		endif
	EndWhile
EndEvent

I though it should maybe look something like this:

Event OnInit()
	Debug.MessageBox("NPC health is now being set.")
	while (true)
		npcs[] = Game.GetAllActors()
                int counter = 0
                while counter < npcs.size()
                        npcs[counter].SetActorValue("health", 100)
                        counter += 1
                endwhile
	EndWhile
EndEvent

And even if it was as simple as that, I wouldnt want NPCs to be touched twice by the script (for performance reasons), so I would also need some NPC IDs for stuff I want to try out later.

Is anything of this properly possible in Papyrus or am I in over my head? (I am used to C++ and C# using RAGE native functions)

 

BR
HJ

 

Link to comment
Share on other sites

You'll need to use SKSE and something like SkyPal: https://www.nexusmods.com/skyrimspecialedition/mods/43925 as there isn't a native function to get all actors in papyrus. In Skypal, there's a function to get all available objectReferences in the game, and then you can filter those by type.

 

Another option is to use a cloak spell on the player that will affect npc's around you. On the cloak's associated spell, you would do something like put a condition IsInFaction MyHealthCheckFaction == 0 , then in your magic effect script put akTarget.AddToFaction(MyHealthCheckFaction) so it only affects the NPC once. Cloak spells in Skyrim are a bit tricky so look at how a vanilla cloak is setup to get an Idea for how they work.

Link to comment
Share on other sites

You'll need to use SKSE and something like SkyPal: https://www.nexusmods.com/skyrimspecialedition/mods/43925 as there isn't a native function to get all actors in papyrus. In Skypal, there's a function to get all available objectReferences in the game, and then you can filter those by type.

 

Another option is to use a cloak spell on the player that will affect npc's around you. On the cloak's associated spell, you would do something like put a condition IsInFaction MyHealthCheckFaction == 0 , then in your magic effect script put akTarget.AddToFaction(MyHealthCheckFaction) so it only affects the NPC once. Cloak spells in Skyrim are a bit tricky so look at how a vanilla cloak is setup to get an Idea for how they work.

I have SKSE installed, but the functions are split up in so many different files that its quite a hassle searching through all of them.

The way the script hook for Rockstar Games works, is that it offers one single helper file in which the all the natives are listed. So if I search for a functionality, I scroll through the file (or use the native reference DB on the internet).

Is there something like this for SKSE?

The object reference function you mentioned, is this also included in SKSE and where can I find good documentation about it?

 

Thanks for responding so quickly!

Link to comment
Share on other sites

No problem. No it's not included with SKSE, you have to install Skypal, the mod I posted, then you can use the function by doing something like this:

 

Keyword[] Property ActorKeywords Auto
;Fill with actor keywords in the CK such as ActorTypeNPC
Float Property Health Auto

Function SetAllActorsHealth() 
    ObjectReference[] AllGameRefs = SkyPal_References.All()
    ObjectReference[] AllActors = SkyPal_References.Filter_Keywords(AllGameRefs, ActorKeywords) 
    
    Int I = 0 
    Int L = AllActors.Length 
    While I < L 
        (AllActors[I] as Actor).SetActorValue("Health", Health) 
        I += 1
    EndWhile 
EndFunction

Note, that will set All of the actors with the keywords in the array to that health. If you only want actors that are in your area you can use SkyPal_References.Grid() instead of .All()

 

For searching functions in multiple source scripts, I use the program GrepWin: https://tools.stefankueng.com/grepWin.html . You can use it to search all of the SKSE source files or all of the source files in your game. Hope that helps :smile:

Link to comment
Share on other sites

If I do it like this, the NPCs wont be able to die, since their health is being set to a value every millisecond or so.

Do actors have unique IDs (so I could work with maps <ID, key>, the key being a flag for health was set)?

Or can I add attributes to actors, so I could place the flag directly in the object? (so I dont have to use the workaround with the spells)

 

Another question to .Grid() and .All() - if I use .All() and change an actor, does this change stay permanently or are scripts (like setting the health) applied by the game when an actor enters a grid?

 

Thanks for responding so reliably!

 

Edit: the error message of the creation kit are not really helpful^^

I have tried filling the keyword array like this:

Keyword[] Property ActorKeywords Auto
ActorKeywords = new Keyword[1]
ActorKeywords[0] = "ActorTypeNPC"

But the creation kit does not like me doing it.

I have tried it a few different ways (without the new instance, without the "" when assigning the keyword), but none worked.

Are there more sophisticated code samples somewhere (all I can find is basic material, like how to add two integers and such).

 

 

Edit 2:

OK, I figured it out:

Keyword[] Property ActorKeywords Auto
;Fill with actor keywords in the CK such as ActorTypeNPC
Int Property HealthDamage Auto
Int Property FuncCalled Auto
Event OnInit()
	ActorKeywords = new Keyword[1]
	Keyword key1 = Keyword.GetKeyword("ActorTypeNPC")
	ActorKeywords[0] = key1
	HealthDamage = 5
	FuncCalled = 0
	Debug.MessageBox("Decreasing of Health is now starting.")
	while (true)
		SetAllActorsHealth()
		;if (Game.GetPlayer().GetActorValuePercentage("health") > 0.1)
		;	Game.GetPlayer().DamageActorValue("health", 5)
		;endif
	EndWhile
EndEvent

Function SetAllActorsHealth() 
    if (FuncCalled == 0)
		Debug.MessageBox("Function is now starting.")
		FuncCalled = 1
	endif
	
	ObjectReference[] AllGameRefs = SkyPal_References.All()
    ObjectReference[] AllActors = SkyPal_References.Filter_Keywords(AllGameRefs, ActorKeywords) 
    
    Int I = 0 
    Int L = AllActors.Length 
    While I < L 
        if (AllActors[I] as Actor).GetActorValuePercentage("Health" > 0.2)
			(AllActors[I] as Actor).DamageActorValue("Health", HealthDamage)
		endif
        I += 1
    EndWhile 
EndFunction

Edit 3: Marked the open questions above, so it is easier to read.

Edited by hinti21
Link to comment
Share on other sites

If you're wanting to damage actor values, then a cloak spell would be ideal. I don't think using DamageAv affects actors that aren't 3d loaded. Using SetAv will change their base health even if they aren't 3d loaded, but I don't think those changes save between saves, so you'd have re-run the function using OnPlayerLoadGame(). That event only works on a reference alias pointing to the player, or a spell ability that's on the player. Also using a while loop like you're doing is not good practice when it comes to papyrus. It's bad for performance and can bork your scripts. Using a single update loop is the better practice, but as I said using a cloak spell for this case I think would be best.

 

Here's how to do a single update loop if you're interested.

 

 

 

Keyword[] Property ActorKeywords Auto
;Fill with actor keywords in the CK such as ActorTypeNPC
Int Property HealthDamage Auto
Int Property FuncCalled Auto
Bool Property Continue Auto

Event OnInit()
    ActorKeywords = new Keyword[1]
    Keyword key1 = Keyword.GetKeyword("ActorTypeNPC")
    ActorKeywords[0] = key1
    HealthDamage = 5
    FuncCalled = 0
    Debug.MessageBox("Decreasing of Health is now starting.")
    Continue = true
    RegisterForSingleUpdate(1) ;update in 1 second
EndEvent

Event OnUpdate()
    SetAllActorsHealth()
    If Continue == true 
        RegisterForSingleUpdate(1) ;update in 1 second
    Endif 
EndEvent 

Function SetAllActorsHealth() 
    if (FuncCalled == 0)
        Debug.MessageBox("Function is now starting.")
        FuncCalled = 1
    endif
    
    ObjectReference[] AllGameRefs = SkyPal_References.Grid()
    ObjectReference[] AllActors = SkyPal_References.Filter_Keywords(AllGameRefs, ActorKeywords) 
    
    Int I = 0 
    Int L = AllActors.Length 
    While I < L 
        if (AllActors[I] as Actor).GetActorValuePercentage("Health" > 0.2)
            (AllActors[I] as Actor).DamageActorValue("Health", HealthDamage)
        endif
        I += 1
    EndWhile 
EndFunction

Oh, and I would use .grid() for this. Using on all actors in the game is unnecessary and not very good for performance.
Link to comment
Share on other sites

I just wanted to say that dylbill suggestion is the cheapest and safer way to do this ( the cloack approach ).

Your approach has a bunch of hidden dangers, the skyrim game engine lacks a lot from further coding, so unless you know exactly what you are doing do not take the chance without first testing to death, otherwise you will encounter some really nasty surprises which the majority of the times are discovered way too late.

 

Have a happy modding.

Link to comment
Share on other sites

Thanks, guys.

The end goal of this mod will be to make NPCs react more diversely in combat (going into disabled states, actually yielding when yielding, reacting properly to elemental damage, etc.).

Thanks for your advice!

The one loop will be viable for setting the base properties, but I will have to constantly check the health of NPCs, if the are on fire or damaged by frost or lightning, etc.

Is there another, more performant way than the while-loop in papyrus?

My plan was to iterate through all the NPCs on the grid and make a first check if they are not dead and then a second round of checks (is on fire, is health low, is being damaged by, etc.) and after that applying all the stuff.

 

Also: I found speech lines in the CK in each actor object. How do I trigger them? I tried the "say" method, but it wont take "hello" as a topic and wont let me cast a string to a topic either.

Link to comment
Share on other sites

Again, a cloak spell ability on the player would be best for what you want to achieve. That's the native way that skyrim detects npcs. You can specify the radius by setting magnitude on the cloak spell, and then you can use creation kit conditions and or papyrus conditions with OnEffectStart to do your script stuff. For those you would put them on the cloaks "dmg" assoc item 1 spell, (this is set in the cloak's magic effect). For an example of how to use cloaks in this way, check out my mod NPC Death Alert's: https://www.nexusmods.com/skyrimspecialedition/mods/33126. It's a pretty simple mod and the source code is provided.

 

here's a list of ck conditions https://www.creationkit.com/index.php?title=Condition_Functions

and for script functions almost everything is listed here: https://www.creationkit.com/index.php?title=Category:Papyrus

Although not every SKSE function is listed, so it's better to search the source files for those.

Link to comment
Share on other sites

1) The safest and most appropriate plus safe way (is on fire, is health low, is being damaged by, etc.) is to add to them directly in CK a 'Scripted Ability".


2) To (going into disabled states, actually yielding when yielding, reacting properly to elemental damage, etc.).

Here you will need to edit / add all the corresponding 'Conditions' to their quest speech lines, if they have the corresponding lines to say, otherwise you will need to add / record yourself the desirable lines, or use other vanilla speech lines if they exists.


"My plan was to iterate through all the NPCs on the grid"

3) This can't be done safly in this game engine, especially if you intend on doing it randomly in-game in real time.

* By randomly i mean: Not using 'Scenes', 'Story Manager' or pre fixed scenes.


"Also: I found speech lines in the CK in each actor object. How do I trigger them?"

4) Referred to number 2


"I tried the "say" method, but it wont take "hello" as a topic and wont let me cast a string to a topic either."

5) Don't ever use the "SAY" function on npcs, this function is meant to be used ONLY by 'Talking Activators' and/or npcs that have NO SPEECH LINES (this means that the npc will never talk), or by npcs that are not in the same cell with the player, npcs that are not loaded / running at that given time that the function is called to execute the 'SAY' function on an npc that has no speech lines or another reference like a simple xMarker.


Using this function on regular npcs will 100% cause the game to CTD if and when that npc will trigger his in build speech lines.


As you may have understand by now what you want to do is not that simple and requires a lot... a lot of work.


Don't give up and keep on pushing !.


Have a happy modding.

Link to comment
Share on other sites

  • Recently Browsing   0 members

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