Making custom follower NPC and some insight into the process

+

Guest 2364765

Guest
Making custom follower NPC and some insight into the process

Having recently released my custom companion NPC mod I guessed that some people may be interested in the actual know-how of the process.

Since achieving this level of mod complexity isn't exactly easy or intuitive, documenting this may prove beneficial to fellow modders (given you're going to bother doing this in first place).

For starters i'd like to thank @KNGRSM and other fine folk who've sticked around this community for helping me develop this mod.
Since we've got all of this behind us, we can start.

Prerequisites:
*Basic knowledge of building a DLC using mod editor
*Mod editor (courtesy of @Sarcen)
*W3Strings encoder (courtesy of @rmemr)

Getting started:
This mod consists of two parts: First one being actual DLC (needed to load new stuff into the game) and a classic script mod which just drives the logic behind our NPC and bends (okay - hacks) few things so they would cooperate with us.

Firstly, let's take a look at file structure of the finished DLC:

And now let's run through each file and what it does:
victoriadlc.reddlc - this is a DLC definition file, required by the game to load DLC and it's contents into the game.
victoria.w2ent - our custom NPC entity.
victoria_dialogue.w2scene - actual "dialogue" scene file, which is played when we interact with our custom NPC.
def_item_weapons_victoria - item(weapon) definition file, containing single weapon used by our NPC, needs a variant for core and NG.
victoriadlc.w2phase - crypto-mounter for our scene file, there's a direct relationship between both.
victoriadlc.w2quest - simple quest file which is needed for mounting w2phase which mounts our scene (and this also needs to be mounted in DLC definition, yadda, yadda, yadda...)

It would take too long to go into detail for each of the files, but here are key pointers:
victoriadlc.reddlc:
Under your CDLCDefinition entry you most likely want to add variable requiredByGameSave and set it to false, in other case users won't be able to load their existing saves which they made after installing your mod if the mod has been uninstalled. Another noteworthy variable is visibleInDLCMenu which is just a neat feature if you want your DLC/Mod to be listed along other other "offical" DLCs.

victoria.w2ent
I've based my NPC on Tamara (though Ves most likely would've been a better choice). You most likely want to take a copy of NPC from \quests\ folder since they're ones that are actually used in game and have properly configured AI'n stuff - less work for you to do.
Once you got a working and separate (and mounted via DLC definition) character entity, you can edit it to your heart's content. Display name property has to be set both in your NPC template and inside "FlatCompiledData" property link in order to change name of your NPC.

victoria_dialogue.w2scene
This is my dialogue/scene file. There's not much to explain about this, because in certain parts system is really flexibe and in others it's wonky beyond my comprehension. Basic rule to have custom w2scene work is to properly setup chain of mounting from dlc definition -> w2quest -> w2phase -> w2scene.
You need to set same actorTags variable inside w2phase and w2scene in order to have the game properly recognize which scene is tied to what tag. At that point plainly adding your set tag to any NPC will make him trigger the dialog scene.
There are some other noteworthy aspects of the dialog system, like the fact that dialogue options can directly call functions from scene_functions.ws, which is very handy.

def_item_weapons_victoria
Not much to say about this one, it's just item definition file like any other. In two flavors, one for core game and other for New Game + (both have to be mounted via separate mounters)

victoriadlc.w2phase
Phase file that mounts or w2scene, as mentioned earlier it's important to change actorTags variable to reflect our desired actor tag.
It directly controls our stubby-quest. Also has some noteworthy features like teleporting player to desired tag-driven destination once dialog is over, etc.

victoriadlc.w2quest
Stubby quest which needs to be mounted and is needed to mount w2phase...i already said that...

================================================== ================================================== ==========================
Script part
As far as scripting part goes, i've edited 4 files:
npc.ws - Some stuff to automatically manage our NPC properties once it's spawned, probably part of this design could be moved to player scripts (or vice versa).
r4player.ws - Base player script with no distinction between Geralt/Ciri (unlike WitcherPlayer being Geralt only) - we hook OnSpawned event to spawn our NPC.
scene_functions.ws - Here we add another function to be called from our dialogue. It is responsible to opening player's stash.

Here are some most important snippets from each file:

npc.ws (At the bottom of OnSpawned event and two functions directly below that event)
//modVictoriaFollower++ - Declare this variable near to other ones on the top
var l_aiTree : CAIFollowAction;
//modVictoriaFollower--

//modVictoriaFollower++
if(((CEntity)this).GetReadableName() == "dlc\victoriadlc\data\characters\npc_entities\main_ npc\victoria.w2ent")
{
this.SetLevel(thePlayer.GetLevel());
this.ForceSetStat(BCS_Vitality, thePlayer.GetStatMax(BCS_Vitality));
this.ForceSetStat(BCS_Stamina, thePlayer.GetStatMax(BCS_Stamina));
((CActor)this).SetHealthPerc(100);
((CGameplayEntity)this).AddAbility( 'Ciri_CombatRegen' );
((CGameplayEntity)this).AddAbility( '_canBeFollower' );
((CGameplayEntity)this).SetFocusModeVisibility(FMV _Interactive, 1);
l_aiTree = new CAIFollowAction in ((CActor)this);
l_aiTree.OnCreated();

l_aiTree.params.moveType = MT_Sprint;
((CActor)this).ForceAIBehavior( l_aiTree, 0 );

AddTimer('ManageCompanionSpeed', 0.016, true);
AddTimer('ManageCompanionDistance', 0.016, true);

this.AddTag('Victoria_Follower');
this.AddTag('victoria_dialog');

if( !FactsDoesExist("VictoriaDialogue") )
{
FactsAdd("VictoriaDialogue");
}
}
//modVictoriaFollower--

//(there's an end bracket here indicating end of OnSpawned event)

//modVictoriaFollower++
timer function ManageCompanionSpeed(dt : float, id : int)
{
var movecomp : CMovingAgentComponent;
var distance : float;
var movespeed : float;
var npcactor : CActor;

//I think originally @Wasteland_Ghost came up with this snippet, or it was @Sarcen

npcactor = ((CActor)this);
distance = VecDistanceSquared(npcactor.GetWorldPosition(), thePlayer.GetWorldPosition());

if(((CActor)this).IsInCombat())
{
//Do nothing.
}
else
{
if( distance < 25 ) {
movespeed = 0.0;
}
else
{
movespeed = MinF((distance - 25) / 100, 1.0) * 10.0;
}
movecomp = this.GetMovingAgentComponent();
movecomp.SetGameplayRelativeMoveSpeed(movespeed);
}
}

timer function ManageCompanionDistance(dt : float, id : int)
{
//Teleport the NPC back to player if it gets lost!
if (VecDistance(thePlayer.GetWorldPosition(), this.GetWorldPosition()) >= 45)
{
if(!IsSwimming() || !IsDiving())
{
if (IsOnGround() && !((CActor)this).IsInCombat())
{
this.Teleport( thePlayer.GetWorldPosition() + theCamera.GetCameraDirection() * -5.0 );
}
}
}
}
//modVictoriaFollower--

r4player.ws (Also in OnSpawned event)
//modVictoriaFollower++ - Declare this variable on top near others
var existingVictoria : CNewNPC;
//modVictoriaFollower--

//modVictoriaFollower++
existingVictoria = (CNewNPC)theGame.GetEntityByTag('Victoria_Follower ');

if(existingVictoria)
{
//We need to check this so if player changes from Geralt to Ciri or vice versa we don't get two followers.
existingVictoria.RemoveTag('Victoria_Follower');
existingVictoria.Destroy();
}
AddTimer('DelayedVictoriaSpawn', 0.033, false);
// Spawn CANNOT be instantaneous - it's not reliable, doesn't always work. Let's wait 2 frames (assuming constant 60 FPS) before calling.
//modVictoriaFollower--

//OnSpawned event ends here

//modVictoriaFollower++
timer function DelayedVictoriaSpawn(dt : float, id : int)
{
var ent : CEntity;
var template : CEntityTemplate;
var pos, cameraDir, player, posFin, normal, posTemp : Vector;
var rot : EulerAngles;

template = (CEntityTemplate)LoadResource("dlc\victoriadlc\data\characters\npc_entities\main_ npc\victoria.w2ent", true);
pos = thePlayer.GetWorldPosition() + VecRingRand(1.f,2.f);
rot = thePlayer.GetWorldRotation();

ent = theGame.CreateEntity(template, pos, rot);
}
//modVictoriaFollower--

scene_functions.ws
//modVictoriaFollower++
latent storyscene function CarryMyStuff( player: CStoryScenePlayer, merchantTag : CName )
{
theGame.GameplayFactsAdd("stashMode", 1);
theGame.RequestMenuWithBackground( 'InventoryMenu', 'CommonMenu' );
}
//modVictoriaFollower--

With all of this set, we've got two main parts interacting correctly. Now for the finishing touch:
================================================== ================================================== ==========================
Localization
This is by far most systematic and easy (but tedious part). By default Witcher 3 does NOT support displaying unlocalized content in any way (at best it'll display localization key for missing locale).

Localization in Witcher 3 is split into two groups: Localization Keys and localization ID's. Keys are literally tags that are replaced with correct string for each game language, they mostly concern stuff like UI, whilst localization ID's are numerical values which are tied to long string, which naturally implies they are most often used with dialogues and other in-game content like NPC names.

Ideally, to have our mod work finished with top-notch polish, we need some localization - this is why we need @rmemr w3strings encoder.
Usage is mostly straightforward and i won't be explaining it here as basic how-to is provided within application package.
Only protip i can give here is to always use text editor like Notepad++ to edit the .csv files - they are NOT meant to be edited using spreadsheet editors!

One thing you have to remember is that you need to create locale for each of ~15 base game languages, in other case your custom locales will be missing in languages for which you have not provided a w3strings file. If you're feeling lazy, renamed english copy will always do (though arabic/korean/chinese and japanese versions may not support latin alphabet).
Here's an approximated list of languages:
ar - Arabic
br - (Brasil? Portuguese?)
cz- Czech
de - German
en - English
es - Spanish
esmx - Spanish variant?
fr - French
hu - Hungarian
it - Italian
jp - Japanese
kr - Korean
pl - Polish
ru - Russian
zh - Chinese (Not sure whether traditional or simplified)
For each of above, you need to provide a w3strings file.
Another thing is, that if you make your w3strings, place them in your DLC/content folder and restart Mod Editor, it'll automatically read your new translation entries, making it easy to change NPC names or dialogue lines as it'll automatically change them from ID's to your preset locale text.
Last but also very important thing to keep in mind when working with locales, is to set ID numbers and key names to stay unique for your mod. Having them overlap with keys/ids from base game or other mods may break your mod or other mods (well at least as far as localization goes). So i'd advise to not pick values provided in example file of W3Strings encoder!

Additionally if you choose to have your DLC listed in game menu, here are autogenerated localization keys that need to be localized:
option_[localizedNameKey value from your DLC definition] - On/Off toggle name in DLC settings menu.
content_name_[id value from your DLC definition] - DLC name listed on "Installed" DLC list.
================================================== ================================================== ==========================
So far that is all, if any of the topics will require expanding, i'll try to do so to the best of my capabilities.

If you wish to check it out, here's FULL project source, containing DLC mod editor project, localization sources and script files.

if done correctly it should look somewhat close to this:

Enjoy.
 
Last edited by a moderator:
Great work! And thanks for sharing the information! I'm working for some time now on a w2scene encoder which should make it easy to create extensive one on one dialogues. So the tie between w2phase/w2quest and w2scene was particularly interesting for me (even if it won't be part of the encoder).

Please expand on the seamless integration. This should be interesting, too!

(@erxv: sorry for the refused hint :))
 

Guest 2364765

Guest
Well then, here's the part about "seamless" integration for more "immersive" insertion of the mod content:

As you can see, the DLC part of the mod didn't get many changes. Only addition is the new xml file definition for the actual notice item (along with needed mounters, yadda, yadda, yadda...).
def_item_notices.xml is a simple item definition file which contains single item - our notice that gets added to the player's inventory once he takes it from the notice board.

What drives the thing however, is the noticeboard itself and the scripts associated with it.
Scripts part (r4player.ws and npc.ws) went through few substantial changes in order to accomodate new implementation and i won't be explaining everything in full detail, but here are the key points:
r4player.ws
*Added saved variable called "HasSeenVictoriaDLCMessage", When player is spawned (OnSpawned event) i call a check
Code:
if(!HasSeenVictoriaDLCMessage)
which if value is false calls a 3-second delayed timer that shows a greeting popup message and also sets HasSeenVictoriaDLCMessage to true, meaning it won't ever get triggered again on this and following save-games.





My direct function to display popup is:
Code:
theGame.GetGuiManager().ShowUserDialogAdv(0, GetLocStringByKeyExt("victoria_follower_dlc"), GetLocStringByKeyExt("victoriadlc_popup_text"), false, UDB_Ok);
First value is title, second one is actual text, however this function accepts strings rather than localization keys, which is less than ideal for localization (duh), thus we can use function GetLocStringByKeyExt to pull localized string for our game language.
If you're interested in how to get colors, here's a snippet from my locale source:
2119999016| |victoriadlc_popup_text|<font color="#75FF55">Thank you for installing my mod!</font> You can unlock your companion by taking a notice from the board in the village of <font color="#F75414">Arinbjorn</font> on <font color="#F1290E">Skellige</font>.
As you can see it's simple HTML (since scaleform is flash) with hex driven colors, If i recall correctly also bold and italic tags do work. You can use any slightly-advanced graphics program or website like http://www.color-hex.com/ to quickly get hex code for each color you want.

*Changed spawn mechanic - now Victoria is spawned based on two facts (actual ingame facts database accepts any string), first one is "VictoriaDLC" which (if doesn't exist) is added during first player spawn - this indicates DLC is installed and second one "VictoriaUnlocked" Which is added when player picks the notice from noticeboard in Arinbjorn.
IF there is no fact "VictoriaDLC" and "VictoriaUnlocked" Nothing is going to happen (though that most likely means mod is not installed, since "VictoriaDLC" is always added automatically).
Having only "VictoriaDLC" means Victoria will spawn in Arinbjorn as a simple NPC that only stands near the noticeboard.
If both "VictoriaDLC" and "VictoriaUnlocked" are on the player's facts list, she'll always spawn at his side, follow him around, etc.

*Most important change obviously, is adding actual notice to the noticeboard, which is based on delayed timer that's fired during player spawn and it looks more or less like so:
timer function DelayedAddVictoriaNotice(dt : float, id : int)
{
var MyErrand : ErrandDetailsList; //Structure of noticeboard errands!
var boards : array<CEntity>;
var board : W3NoticeBoard;
var k : int;

MyErrand.errandStringKey = "victoriadlc_notice"; //Title of the notice!
MyErrand.newQuestFact = "VictoriaUnlocked"; //Fact that is added once notice is grabbed from the board
MyErrand.requiredFact = "VictoriaDLC"; //Fact that needs to be on player's fact list in order to spawn notice on the board.
MyErrand.forbiddenFact = "NotGoingToHappenNeverEverXyZ1234"; //I imagine this is exact opposite of the above, but leaving empty string doesn't seem to work very well
MyErrand.addedItemName = 'victoria_note'; //Item name from the xml definition file - this is what is added to the inventory.
MyErrand.displayAsFluff = true; //Not sure, most likely the visual representation of the notice on 3d(ingame) board
MyErrand.posX = 10;
MyErrand.posY = 10;
MyErrand.errandPosition = 0;

if(theGame.GetCommonMapManager().GetCurrentArea() == 2) //Check if we're on skellige, no point in trying to do this in wrong region.
{
theGame.GetEntitiesByTag( 'arinbjorn_noticeboard', boards );

for( k = 0; boards.Size() > k; k += 1 )
{
board = (W3NoticeBoard) boards[k];

if( board )
{
board.AddErrand( MyErrand, true ); //True here means it's forcibly added, regardless whether there's enough space for it - pick a board with meaningless notices!
board.UpdateBoard();
board.UpdateInteraction();
}
}
}
}

Important part here is the theGame.GetCommonMapManager().GetCurrentArea() == 2 condition, it's used in more than one place in the code, basically it checks whether player is currently on Skellige. There's no point in doing certain stuff (like spawning "non-companion" Victoria on given coordinates if it isn't on Skellige.
In case you need it, here's a list of regions:
AN_Undefined,
AN_NMLandNovigrad,
AN_Skellige_ArdSkellig,
AN_Kaer_Morhen,
AN_Prologue_Village,
AN_Wyzima,
AN_Island_of_Myst,
AN_Spiral,
AN_Prologue_Village_Winter,
AN_Velen,
AN_CombatTestLevel,
It's an enumerator so integers may be used instead of actual names, meaning AN_Undefined is 0, AN_NMLandNovigrad is 1, etc...

npc.ws
Also did a few changes here, main (and the most important change) is shifting entire set of properties (setting level, AItree, etc) into a separate timer looping function that only applies all of the properties if player has "VictoriaUnlocked" fact. If the fact is present, condition is met, properties are set and timer is removed.
This will cause the actual instance of Victoria that is in Arinbjorn near the noticeboard to start following player immediately, giving him an illusion of incredible complexity/smoothness.

That is all there is to it (more or less). I've explained the general idea and key points so you can't go wrong with this.

Also, if you need it, HERE'S a script for batch compiling all languages using @rmemr w3wtrings encoder.
Enjoy.
 
Last edited by a moderator:

Guest 2364765

Guest
This is really great stuff. I didn't know we could add custom followers.
I imagine we're just short of adding actual quests, though making anything beyond most simplistic quests with current tools would be a suicide.
 
@skacikpl

Very thinly related to this mod, but I was wondering if it's possible to swap NPC models with your Console Extension plugin, just as an example, to look at a guard soldier in Novigrad and swap his model for Vesemir, Eskel, or some other character.
 

Guest 2364765

Guest
@skacikpl

Very thinly related to this mod, but I was wondering if it's possible to swap NPC models with your Console Extension plugin, just as an example, to look at a guard soldier in Novigrad and swap his model for Vesemir, Eskel, or some other character.
No there is no such functionality by default.
 

Guest 2364765

Guest
Is it feasible at all?
Feasible as doable? of feasible as it has any point?
You can already spawn any NPC from the game using spawnfrompath command.
Unless you'd really like to build your custom NPC on the fly i'd say it's a better option, since you'd first have to strip him down (rcapp alike) then add your desired appearance objects. Which also poses a problem because if you add most NPC stuff this way, it'll be red due to lack of color-masking data.


//
In other news: i've made a simple batch script for compiling all of the languages at once, so it isn't AS bothersome as it used to be.
You can get it fromHERE
 

Guest 2364765

Guest
Updated OP with more accurate info on changing NPC name that is shown in game - turns out it has to be set once as "DisplayName" inside CEntityTemplate and for the 2nd time for CEntityTemplate under "FlatCompiledData" link-property (using "Open" option).
 
Hello! This mod look interresting, but can you tell me what happen if you use a horse? Victoria will follow you even if she haven't a horse? Or will she use one (it would be cool if she had her own horse).
Because when i see the videos, i have the feeling that with this NPC, i couldn't really use a horse, if i galop, she would be far away from me.
If you can help me about the matter of the horse, it would be great!
Thanks!

P.S: sorry for the bad english, i'm french.
 

Guest 2364765

Guest
For now she just follows on foot.

Even if you would outrun her within a region, she'll teleport behind you.
 
Sorry for off-topic but I don't want to create new topic for this.

I have one simple question - is it possible to spawn NPCs inside buildings?
 
Top Bottom