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)
r4player.ws (Also in OnSpawned event)
scene_functions.ws
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:
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:
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.
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:
For each of above, you need to provide a w3strings file.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)
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: