Jump to content

Script or API to doppelgang NPCs


Recommended Posts

Posted

For some reason which I don't understand yet, some NPC don't "play along" with my scripts.

 

While still looking into why this happens, I am hoping to circumvent the issue by "doppelganging" them, meaning creating a clone without packages, unique and essential flags, factions or other attributes, but preserving (only) the appearance and outfit. 

 

placeactoratme(target.getactorbase())

 

^^this is not a solution, it just propagates the (still unknown) issues which block the original NPC from responding to my scripts.

 

Is there a mod or function / library which enables this? For FO4, AAF has a number of API functions to duplicate appearance / outfit / decals, is there something similar for skyrim?

 

TYVM

Posted

Coming back to this thread, what is the proper way to clone an NPC?

 

Meaning:

 

clone = target.placeatme(target.getactorbase()) as actor 

 

works fine for unique NPCs, but how to clone an actor coming from a leveled list?

 

It should be

 

placeatme(target.getleveledactorbase())

 

however the CK wiki warns that "Creating an actor in script from a temporary ActorBase will cause a CTD when the temporary ActorBase is garbage collected. To make a copy of an actor, use GetActorBase() instead."

 

I don't know what garbage collection is, but I don't want to introduce random CTDs.

 

So, how can I do it?

 

Thanks

 

cc @zaira

 

LE: found an implementation in PAHE (basically a brute force repeated placeatme(original_actor.getactorbase()) until the placed actor matches the original), have contacted @CliftonJD to get permission to use it.

 

 

Posted

Why is PlaceActorAtMe() no solution for you? You explicitely get send to that function for Leveled Actors from PlaceAtMe() ;) 

 

Edit: I think I've found the problem. The most simple solution I can think of is to create a global variable or property in your script and fill it with your ActorBase once when running your function. Afterwards only use said variable instead of any function to get your ActorBase.This hopefully should keep the ActorBase persistent. Not tested though.

Posted
4 hours ago, Mister X said:

Why is PlaceActorAtMe() no solution for you? You explicitely get send to that function for Leveled Actors from PlaceAtMe() ;) 

 

Edit: I think I've found the problem. The most simple solution I can think of is to create a global variable or property in your script and fill it with your ActorBase once when running your function. Afterwards only use said variable instead of any function to get your ActorBase.This hopefully should keep the ActorBase persistent. Not tested though.

 

 

The problem is that actorbase(), in the case of generic characters which spawn from leveled lists, does not return the same actor which was randomly instantiated initially by the game engine when entering the cell, but another random actor from the same leveled list. Basically you can get a male hunter instead of a female hunter, or different faces or equipment combinations, depending on the extent of the underlying leveled lists / leveled item lists.

 

Posted
5 hours ago, Mister X said:

Why is PlaceActorAtMe() no solution for you? You explicitely get send to that function for Leveled Actors from PlaceAtMe() ;) 

 

Edit: I think I've found the problem. The most simple solution I can think of is to create a global variable or property in your script and fill it with your ActorBase once when running your function. Afterwards only use said variable instead of any function to get your ActorBase.This hopefully should keep the ActorBase persistent. Not tested though.

original pahcore from layam used that encounter zone with its own property in the script then attaches the clone produced by it to another property in the script using force ref to, however the reason to try to use the leveled actorbase for this is to avoid copying the naked actor (or any other new outfit assigned) to all respawns of that actor base.

 

however if you're only trying to hang the clone, would you be trying to strip the clone, or would the original actor outfit suffice? assuming you don't care what the clone is wearing as it hangs there, then its best to use that method with an exclusion on the property reference the clone is attached to---do Not choose to allow saving data or again you'll run into the saved data effecting the actor base. for example original pahcore and blabla's final sl extension both saved the data creating the friendly bandits bug on all new spawns of that actor base, basically that occurs with the cleaned factions of the clone getting cleaned of the original base.

6 hours ago, SAC said:

it should be

 



placeatme(target.getleveledactorbase())

 

however the CK wiki warns that "Creating an actor in script from a temporary ActorBase will cause a CTD when the temporary ActorBase is garbage collected. To make a copy of an actor, use GetActorBase() instead."

 

I don't know what garbage collection is, but I don't want to introduce random CTDs.

 

yes, i went thru extensive testing on that when it was said to cure the naked bandits bug. basically the garbage collector happens everytime you save/quit//reload. the garbage collector changes the resulting clone of the leveled actor base to something else. sometimes it changes the sex of the npc, other times it changes the appearance or the voicetype, sometimes it deletes it entirely, while most commonly it will change into something completely different such as a sabrecat from a human. the result can range from unpredictable to completely unstable save loads.

6 hours ago, SAC said:

LE: found an implementation in PAHE (basically a brute force repeated placeatme(original_actor.getactorbase()) until the placed actor matches the original),

the leveled actorbase toggle in pahe at best reduces the odds of encountering the same naked actor base with a bastardized mixture of leveled actor base clone, leveled encounter zone clone, and base actor encounter zone. its an attempt at stabilizing the leveled actor base, but since it still involves the original actorbase, it doesn't completely solve the naked bandit bug if/when you still encounter that same actor base

 

the basic reason for that to be attached to a toggle with it default de-activated is that it often fails to find a valid clone more frequently as the user adds more npc variant mods. for example, some of the random wandering wenches and majority of obis bandits will fail valid enslavement and produce none object slaves

 

edit:

at some later point in the future i'd like to revamp that so that if/when it fails to achieve a valid slave, the result is completely discarded and recloned using the default encounterzone

Posted
7 hours ago, SAC said:

Coming back to this thread, what is the proper way to clone an NPC?

 

Game.ForceThirdPerson()
PlayerRef.SetAlpha(0.0)
MariasUtils.PlaceInFrontOf(PlayerRef,block.GetRef(),20)
clone = PlayerRef.PlaceActorAtMe(Game.GetForm(0x7) as ActorBase)
MariasUtils.PlaceBehind(clone,block.GetRef(),20)
clone.RemoveAllItems()
clone.SetAlpha(1.0)
clone.EquipItem(PrisonerCuffsPlayer,true)

 

This is what I did in ME 1.0 for players head chop scene - so to avoid the kill reload I clone the player.

For generic NPC I would simply restrict this "feature" to unique npcs

 

Posted

Or do a simple trick: Place an execution hood onto the victim (maybe only for non-unique ones) and clone the outfit. So the actor must not be identical and nobody will recognize it (at least for the same races)

 

Or do a 1:1 copy by hand - unify races and nifs. RaceMenu on Nexus contains a plugin which clones appearance - good place to grab some inspirations

Posted

Thank you @CliftonJD @zaira , definitely making solid progress

 

 

actor clone = target.placeatme(target.getactorbase()) as actor 
while actor_base_is_similar(target.getleveledactorbase(), clone.getleveledactorbase()) == false     
        clone.delete()
        clone = target.placeatme(target.getactorbase()) as actor 
endwhile 

 

 

This works perfectly fine, it visibly shuffles through a few clones before settling on the proper one. The process takes 1-2 seconds at most.

 

actor_base_is_similar(actorbase1, actorbase2) is a function which I've taken copy-paste from PAHE (with permission), I won't post it here, anyone can see it in the PAHE scripts / contact @CliftonJD to comment on it. Basically it checks that the original and clone bases match in terms of gender and a number of other criteria.

 

Stuff to do next:

 

- add a safety counter into the while loop so it doesn't get stuck for some reason (PAHE has 50 retries before giving up, as a safety)

- fine tune the process with actor.setalpha() and/or spawn the clones somewhere else and bring back just the "matching winner" so the user doesn't see the cloning selection process

- manually re-adding a schlong (base form xx000D77) or search the original actor for slot 52 for male actors, the unequipped clone somehow loses the schlong in the process (but it might be my unequip script is not subtle enough yet)

- subtleties like overlays replication which should require deep diving into racemenu and/or slavetats APIs, as @zaira mentioned

(actually, FO4 AAF has a cloning API function based on Racemenu, maybe at some point I'll try to replicate it, assuming Racemenu SSE has the same APIs)

- maybe play more with replicating some equipment, but might not be really needed

Form.TempClone() is a new thing to me which I'll have to look into; I guess since I respawn them at every cell visit I don't need persistence anymore, TBD

 

So, perfect, thank you, for now I have all the core information I need, I'll proceed to finetuning and follow-up if needed.

 

Cheers!!

 

Posted
8 hours ago, SAC said:

subtleties like overlays replication which should require deep diving into racemenu and/or slavetats APIs, as @zaira mentioned

(actually, FO4 AAF has a cloning API function based on Racemenu, maybe at some point I'll try to replicate it, assuming Racemenu SSE has the same APIs)

part of the switchActors function:

Spoiler

    If (Game.GetModByName("SlaveTats.esp") != 255)

        If JContainers.isInstalled() && SlaveTats.Version() != ""
            original.enableAI(false)
            int array = JArray.object()
            JValue.retain(array)
            If !SlaveTats.query_applied_tattoos(original, 0, array)
                int index = JArray.count(array)
                debug.trace("[PAHCore] SlavTats: " + index + " entries")
                if index > 0
                    while index > 0
                        index -= 1
                        SlaveTats.add_tattoo(clone, JArray.getObj(array, index), silent = true)
                    endWhile
                    SlaveTats.synchronize_tattoos(clone, true)
                EndIf
                JValue.release(array)
            EndIf
        EndIf
    EndIf

 

 

Posted

 

Hit a snag which @zaira mentioned to me that it might happen - spawning actors with HDT (physics) hair CTDs the game. Understand this is known from LE, it does happen in SSE too.

 

Is there a workaround?

Posted
4 hours ago, zaira said:

Execution Hood

 

:D 

 

True, although I'm not sure the game even has the time to equip the hood between placeatme and equip hood without crashing first due to the hair.

 

But that would mean equipping the hood on everyone, since I don't have a way to pre-check for HDT before spawning the clone, which is not the way I intend the mod to work, esthetically. 

 

I'll do something else, I'll ask the player to choose if they have any HDT haired NPC mods installed when they install my mod + add an MCM option, and program the script to skip the cloning and just havok the original bodies if the user selects this option.

 

LE: Nevermind, I'll try spawn => equip hood => wait some => remove hood, see how it goes

 

Posted
4 hours ago, SAC said:

LE: Nevermind, I'll try spawn => equip hood => wait some => remove hood, see how it goes

 

Bingo, seems to work

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

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