Modding VC: basic tutorials and Q&A thread

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
Miniguide: how to start and where to find information

Modding permissions:
Check sticky thread  :arrow:link

Discussion on creating a submod for VC:
kalarhan said:
Sorenerv said:
2. Only Viking Conquest Mod Source may be used in non-Viking Conquest mods. Do not use Viking Conquest binary files such as textures, models, animations, sounds, music, and scenes.

I am wanting to make a small mod (because I am one man) with additional features, new content, OSP's, ....
Imagine that you are creating a Medieval Japan mod. All you want are the scripts for decapitation. You can use them, as long you add the DLC and BWStudios on your credits. It does not need to be a submod.

If you use any of the resources/binaries: any texture, music, sounds, BRF, animation, map, ... you need to turn your game into a submod. That means you will need to post it on this forum section (not the warband one), your players will be required to own the DLC to play your mod, but you are free to change stuff. Make it a total conversion, or a small feature addition. It does not matter. It is a submod of VC, the rest is up to you. You also need to add credits for the main game (VC DLC). Thats it.

Create your own mod folder:
Important: this will isolate your mod changes from the vanilla game

It is a good idea to create your own folder.

Why?
1) You can have many submods for VC, and play them at any time, including your own
2) The vanilla game is still there in case you need to copy files (like a BRF), or play multiplayer, ...
3) Easier to test
4) You can also have backups
5) A new version for the main game won't break your mod. You can merge changes later.

How?
1) Go to your game folder. Steam players, as a example, could see something like this:
Code:
C:\Program Files (x86)\Steam\steamapps\common\MountBlade Warband\Modules
Modules folder contains all your game mods. Native is Warband, you also have DLC for "Viking Conquest", and maybe a few more (mods, NW, etc).

2) Copy your folder "Viking Conquest" and paste it on that same location (CTRL+C and CTRL+V). Windows will create a "Viking Conquest - Copy" folder

3) Rename the folder to something like "VC_mymod" (whatever you like!)

4) Open your folder and edit the file "module.ini" using any text editor (Notepad, ...). Locate this line and update it:
Code:
module_name = Viking_Conquest2023
(example for game version 2.023)
to
Code:
module_name = VC_mymod
5) Your mod version: open file module_constants.py and change this variable
Code:
vc_version = 2023
to your own. Start at 1000 (1.0), unless you want to change the game code to handle smaller versions (like 0.1)
-> note that 2023 is the version when I wrote this (2.023). It will change to 2024, 2025, etc... so just look for the "vc_version" word.
Code:
vc_version = 1000 #my first release! It will display 1.0 on main menu (ingame)! and 1000 on rgl_log.txt
remember to update your module_info.py to point to this folder... and you are ready to create your own features!

Coder:
The Forge is a special subforum dedicated to modding Warband. It has plenty of material in there: guides, tutorials, code samples, tools. It is your first stop and the place you will spend most of your time if you need more info on modding

To start out:
1) Read this newbie guide: https://forums.taleworlds.com/index.php/topic,240255.0.html
2) Learn the ABC of modding: https://forums.taleworlds.com/index.php/topic,142422.0.html
3) Learn the basic structure of the game source: https://forums.taleworlds.com/index.php/board,12.0.html

Time invested: about 2 hours. You should try reading those first, even if you don't understand everything. Re-read them as you meet specific challenges with what you are modding.

Asking questions: Q&A thread is the place to be. Check the OP first, and post your questions there
Link: https://forums.taleworlds.com/index.php/topic,6575.0.html

Important: avoid creating a new thread for your questions, that is why we have the Q&A thread


Sublime API: do you want to type code 10x as fast? Do you want inline help with operations? Then check out this link https://forums.taleworlds.com/index.php/topic,320675.0.html

Lav's version of modules (for native, 1.166): check out the header_operations.py and header_triggers.py. You can actually read and understand those numbers, instead of guessing what they do (Lav's has added comments to explain and organize that stuff!)
http://forums.taleworlds.com/index.php/topic,324874.0.html

WRECK: alternative compiler that will let you do very cool stuff with your code. Visit the official thread to learn more:
https://forums.taleworlds.com/index.php/topic,325102.0.html

Download the game source:
Module Source
For use by modders who wish to create their own modules, based on Viking Conquest Reforged Edition.

TW official Website, Downloads section

Game source with Tweaks applied:
check the thread for the "tweaks tool", you can see how many changes were applied and use them on your own mods
https://forums.taleworlds.com/index.php/topic,348186.0.html
 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
FAQ: code your features

this will include sample cases on how to do code changes


Menus:
Morale after battle if you claim the loot: link
Skip character creation: link

Mission templates:
Gore: link

Scripts:
Dynamic lord titles: link
XP curve for troops: link

Presentations:
Game version for your mod: link
Companions Screen: link

Dialogs:
Marriage proposal to a lady: link

RGL_LOG.TXT and debugging the game:
Logging custom messages: link
 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
FAQ: change the world

this FAQ will be for non code changes (graphics, textures, etc) that are exclusive to VC, or it will link guides from the Forge

Scenes (creating a village, town, etc): scene making in Warband (and VC) is done by a special editor ingame. To know more about it visit the Forge, scenes thread. The OP has links to guides, samples, video guides (Youtube) and many many pages of community created content. You can also post your work there to get feedback!
Link: https://forums.taleworlds.com/index.php/topic,163368.0.html
The Guild's aims:
Compiling fine tutorials and hints
Having constantly updated FAQ
Providing tech support and mentoring for sceners
Collecting OSP scenes
Allowing anyone to post scenes for feedback and discussion
Letting mod devs to post scene requests and hire sceners for their teams
Inspiring!
Armor tutorial by @kraggrim: http://forums.taleworlds.com/index.php/topic,335027.0.html
This tutorial is not about creating your own from scratch, it's about mixing up the
existing armour parts and accessories to create something unique.
Banners: see https://forums.taleworlds.com/index.php/topic,347990.msg8348333.html#msg8348333
 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
Question 1: How to change morale for loot after battle?

Note: version 2.023

Step 1 -> define what you wish to change in the game: I want to change how much morale I get/lose after a battle if I decide to take all the loot for myself

Important: be clear about what you want

Step 2 -> relate that to a ingame actual feature/dialog/menu for reference: this happens after a battle is won, I get a menu with choices on how to distribute loot with my soldiers. The option I want to change has this text: "Take first claim on all the loot."

Important: copy the exact text, as you need it to search the game source code

Step 3 -> figure out where to start looking: you have the text/string, and you know it is showed on a menu. Now we have two ways to move forward:

a) Do a full search for that string. It should point you to the right spot.
b) Do a search only on module_game_menus.py. Why? A menu code is written inside that file.

Step 4 -> lets start to examine the code to find where we need to change stuff!

a) Search on game_menus resulted on this:

Code:
 (
    "loot_options",0,
    "The battle is over. What do you want to do with the loot?",
    "none",
    [
      (set_background_mesh, "mesh_pic_chest"), #pic book
      #(set_background_mesh, "mesh_pic_extra_navegando"),
      (assign, "$loot_option", 0),
      
      (try_begin),
        (party_get_num_companions, ":party_members", "p_main_party"),
        (le, ":party_members", 3),
        (jump_to_menu, "mnu_total_victory"),	# No loot option for very small party
      (end_try),
      
    ],
    [
      ("loot_0", [], "Share the loot, as is common.",
        [
          (assign, "$loot_option", 0),
          (jump_to_menu, "mnu_total_victory"),
      ]),
      
      ("loot_1", [], "Take first claim on all the loot.",
        [
          (assign, "$loot_option", 1),
          (jump_to_menu, "mnu_total_victory"),
      ]),
      
      ("loot_2", [], "Leave all the loot for your men.",
        [
          (assign, "$loot_option", 2),
          (jump_to_menu, "mnu_total_victory"),
      ]),
      
      ("loot_3", [], "Leave the loot, but use the time to bury the dead.",
        [
          (assign, "$loot_option", 3),
          (jump_to_menu, "mnu_total_victory"),
      ]),
      
    ]
  ),

Note that we have two important things here. A global (variable) with a value, and a jump to another menu call (so we need to follow it).

Code:
("loot_1", [], "Take first claim on all the loot.",
        [
          (assign, "$loot_option", 1),
          (jump_to_menu, "mnu_total_victory"),
See the next menu code (look for "total_victory"):

Code:
 (
    "total_victory", 0,
    "You shouldn't be reading this... {s9}",
    "none",
    [
      (assign, ":warband_updated", 0),
      (try_begin),
        (assign, reg0, 0),
Using the global value we can figure out where it applies to our situtation:

Code:
(else_try),
          (eq, "$loot_option", 1),				#player wants to see all loot
          (store_div, ":accepted_player_share", "$loot_value_before", 6), # ~ 16% for player is accepted
          (store_sub, ":accepted_troop_share", "$loot_value_before", ":accepted_player_share"),
          (store_sub, ":value_diff", ":loot_value_after", ":accepted_troop_share"),
          (try_begin),
            (gt, ":value_diff", 0),
            (store_div, ":morale_bonus", ":value_diff", 230),
            (gt, ":morale_bonus", 0),
            (val_min, ":morale_bonus", 5),
            (call_script, "script_change_party_morale", "p_main_party", ":morale_bonus"),
            (gt, reg1, 0),
            (display_message, "@Your party gained {reg1} morale for the loot you shared.", color_good_news),
          (else_try),
            (le, ":value_diff", 0),
            (store_div, ":morale_penalty", ":value_diff", 200),
            (lt, ":morale_penalty", 0),
            (val_max, ":morale_penalty", -40),
            (call_script, "script_change_party_morale", "p_main_party", ":morale_penalty"),
            (gt, reg1, 0),
            (display_message, "@Your party lost {reg1} morale, because your troops think you keep too much loot for yourself.", color_bad_news),
          (end_try),

Now its time to change it. Compile your code. Test it with the game. And we are done.

Example: change the limit from -40 to -30
Code:
  (val_max, ":morale_penalty", -40),
 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
Question 2: how do I change decapitation (gore) chance? I want more heads flying!

Step 1 -> the what? Gore option allows for decapitation in battle. I want to increase the chance for the player, so I cut lots of heads... off! *Evil Grin*

Step 2 -> relate to the game feature: decapitation only happens in a battle and if I kill a bot with a cutting weapon and by doing a head hit

battle only: we should start by "module_mission_templates", the file that handles scenes and battles
if I kill... : that is a event, so we will need to find the trigger related to this!

Step 3 ->: lets look at module_mission_templates

Note that common triggers are written in the begin of the file and they have a name to help identify what they are about... so how about this one?

Code:
common_gore = [ #6 triggers
  #Decapitation
  (ti_on_agent_hit, 0, 0, [
      (neq, "$goredec_on", 0), # SP only. Gore ON from options menu.
    ],
    
    [
      (assign, ":reg0_backup", reg0),
      (store_trigger_param, ":inflicted_agent_id", 1),
      (store_trigger_param, ":attacker_agent_id", 2),
      (store_trigger_param, ":damage", 3),
      (store_trigger_param, ":hit_bone", 4),
      (store_trigger_param, ":missile_item_kind_no", 5),
      (assign, ":weapon_id", ":reg0_backup"),
      
      (call_script, "script_cf_goredec",
        ":inflicted_agent_id",
        ":hit_bone",
        ":missile_item_kind_no",
        ":damage",
      ":weapon_id"),
      
      (call_script, "script_cf_goredec_probability",
        ":inflicted_agent_id",
        ":attacker_agent_id",
      ":weapon_id"),
      
      (call_script, "script_goredec_special_effects",
      ":inflicted_agent_id"),
      
      
      #avoid wounded (VC-1809); negative courage effect on surrounding agents (phaiak)
      (call_script, "script_change_courage_around_agent",
        -20,
      ":inflicted_agent_id"),
      
      (call_script, "script_goredec_debug_log",
        ":inflicted_agent_id",
      ":attacker_agent_id"),
      
      (assign, reg0, ":reg0_backup"),
  ]),

Ok, we have a good idea about the trigger/event, now we need to search where that hell it calculates the chances (%).

Step 4 -> find the %%%

code is easy to red, not the call to a script "script_cf_goredec_probability". You already know what a script is, and that "cf_" is, so lets open it!

Step 5 -> the first script

Code:
  # "script_goredec_probability"
  # Description: checks conditions for the decapitation chance (%)
  # Input: inflicted_agent_id, attacker_agent_id, weapon_id,
  # Output: none (CAN FAIL)
  ("cf_goredec_probability",
    [(store_script_param_1, ":inflicted_agent_id"),
      (store_script_param_2, ":attacker_agent_id"),
      (store_script_param, ":weapon_id", 3),
Reading the script we find this:

Code:
          #Player bonus
        (else_try),
          (val_add, ":base_chance", "$goredec_player_bonus"),
          (try_begin),
            (neq, ":horse_id", -1),
            (val_add, ":base_chance", 30),
          (try_end),
        (try_end),
So somewhere in the code its define a bonus for the player only, and that value is defined on a global "goredec_player_bonus"! Lets find where.

Step 6 ->

Code:
  # "script_goredec_player_bonus"
  # Description: player bonus based on skills and if he is mounted
  # Input: none
  # Output: $goredec_player_bonus
  ("goredec_player_bonus",
    [(get_player_agent_no, ":player_agent"),
      
      (assign, "$goredec_player_bonus", 5),
      
      (agent_get_troop_id, ":player_troop", ":player_agent"),
      (store_attribute_level, ":player_str", ":player_troop", ca_strength),
      (store_skill_level, ":player_power_strike", "skl_power_strike", ":player_troop"),
      
      (try_begin),
        (ge, ":player_str", 15),
        (val_add, "$goredec_player_bonus", 10),
      (try_end),
      (try_begin),
        (ge, ":player_power_strike", 7),
        (val_add, "$goredec_player_bonus", 10),
      (try_end),
      
      # Debugging
      (try_begin),
        (ge, debug_goredec, 1),
        (assign, reg1, "$goredec_player_bonus"),
        (display_message, "@Player bonus for decap: {reg1}"),
      (try_end),]),

And we have all we need. Note the script has 3 bonus for the player:
a) extra 5% (always)
b) extra 10% (if enought STR)
c) extra 10% (if enough skill)

Change for whatever value you want, compile, test in the game... and have fun!

Note that you can enable special logging to test this feature, but that would be another question!
 

kraggrim

Marquis
WBM&BWF&SNWVC
Best answers
0
There's part of a particular trigger in simple_triggers.py that's giving me some problems:
#overhaul of spawned parties
        (try_begin),
          (neg|party_slot_eq, "p_mierce_spawn_point", slot_party_spawn_target_spawns, 12),
         
          (try_begin),
            (eq, "$bandit_quantity_option", 0),
            (assign, "$bandit_quantity_option", 1),
          (try_end),
         
          (party_set_slot, "p_clyde_coast_spawn_point", slot_party_bandit_type, "pt_steppe_bandits"), #Northmenn
          (party_set_slot, "p_clyde_coast_spawn_point", slot_party_spawn_target_spawns, 12),
          (party_set_slot, "p_clyde_coast_spawn_point", slot_party_spawn_radius, 30),
          (party_set_slot, "p_clyde_coast_spawn_point", slot_party_spawn_flags, spsf_coastal),
I'm setting the target spawns to 0 for certain spawn points, and this is resetting it. Am I OK to disable this? It seems to be a trigger for updating old save-games or something, and anyone using my mod will be starting a new game, but I don't want to remove something important.
 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
kraggrim said:
There's part of a particular trigger in simple_triggers.py that's giving me some problems:
#overhaul of spawned parties
        (try_begin),
          (neg|party_slot_eq, "p_mierce_spawn_point", slot_party_spawn_target_spawns, 12),
         
          (try_begin),
            (eq, "$bandit_quantity_option", 0),
            (assign, "$bandit_quantity_option", 1),
          (try_end),
         
          (party_set_slot, "p_clyde_coast_spawn_point", slot_party_bandit_type, "pt_steppe_bandits"), #Northmenn
          (party_set_slot, "p_clyde_coast_spawn_point", slot_party_spawn_target_spawns, 12),
          (party_set_slot, "p_clyde_coast_spawn_point", slot_party_spawn_radius, 30),
          (party_set_slot, "p_clyde_coast_spawn_point", slot_party_spawn_flags, spsf_coastal),
I'm setting the target spawns to 0 for certain spawn points, and this is resetting it. Am I OK to disable this? It seems to be a trigger for updating old save-games or something, and anyone using my mod will be starting a new game, but I don't want to remove something important.
In scripts, game_start, you have the same code that works as a "database" that defines how the bandit spawn works. This trigger duplicates the code to update old saves (as you said), and in case of any unexpected change (should not happen).

But note that the block has a test, that only executes it if
Code:
(neg|party_slot_eq, "p_mierce_spawn_point", slot_party_spawn_target_spawns, 12),
As any new save, and a already updated one will have it as 12, then the code will be ignored. So for your case it should be already disabled.

Keep in mind that this is duplicate code, so you should have the same values on scripts and on triggers (copy and paste), or put the database on a new script and call it from both places (best solution).

That will let you update your mod settings and keep it compatible with your users saves.

Example: say that 2 months from now you want to decrease the bandits around a town (balance issue)
 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
Question 3: How do I change the dynamic lord titles?

Step 1 -> The what: game applies dynamic titles to lords. So if they change faction, as a example, they will be renamed with that culture title (ex: Jarl to Ealdorman).
I want to give new titles to some culture/kingdom

Step 2 -> Game feature: rename of lords when they change factions.

List of some titles: Jarl, Ealdorman, Ruire, Warlord

Step 3 -> where to look:
"when they change factions": this is a event, so most likey related to a world trigger (module_triggers.py or module_simple_triggers.py), or a script called by that event (module_scripts.py)
we have the list of titles, so we can do a search for them: "Warlord", "Ealdorman"

Step 4 ->: the search for titles tooks us to module_strings.py:

Code:
#cambiado chief 
#norse
  ("faction_title_male_player", "Konungr {s0}"),
  ("faction_title_male_1", "Jarl {s0}"),
  ("faction_title_male_2", "Hersir {s0}"),
#angle saxon
  ("faction_title_male_3", "Cyning {s0}"),
  ("faction_title_male_4", "Ealdorman {s0}"),
  ("faction_title_male_5", "Dryhten {s0}"),
#britons
  ("faction_title_male_6", "Ri {s0}"),
  ("faction_title_female_player", "Gundelic {s0}"),
  ("faction_title_female_1", "Tiern {s0}"),
#irish and pictish
  ("faction_title_female_2", "Ruire {s0}"),
  ("faction_title_female_3", "Ard Tiarna {s0}"),
  ("faction_title_female_4", "Tiarna {s0}"),
#other
  ("faction_title_female_5", "Dux {s0}"),
  ("faction_title_female_6", "Warlord {s0}"),
#fin cambios chief

We can change the titles themselves now, but not how they are applied. So lets see how that works, by searching the source for "faction_title_male_player":

from module_scripts.py

Code:
 # script_troop_set_title_according_to_faction
  # Input: arg1 = troop_no, arg2 = faction_no
  ("troop_set_title_according_to_faction",
    [
      (store_script_param, ":troop_no", 1),
      (store_script_param, ":faction_no", 2),
      #  (faction_get_slot, ":culture", ":faction_no", slot_faction_culture),
      (try_begin),
        (is_between, ":faction_no", kingdoms_begin, kingdoms_end),
        (faction_get_slot, ":faction_leader", ":faction_no", slot_faction_leader),
        (str_store_troop_name_plural, s0, ":troop_no"),
        # (troop_get_type, ":gender", ":troop_no"),
        #  (val_mod, ":gender", 2),
        
        (assign, ":lord_have_fief", 0),
        (try_for_range, ":cur_center", centers_begin, centers_end),
          (neq, ":troop_no", ":faction_leader"), # exclude research for ruler
          (party_slot_ge, ":cur_center", slot_town_lord, 0),
          (party_slot_eq, ":cur_center", slot_town_lord, ":troop_no"),
          (try_begin),
            (party_slot_eq, ":cur_center", slot_party_type, spt_town),
            (val_add, ":lord_have_fief", 1),
          (else_try),
            (party_slot_eq, ":cur_center", slot_party_type, spt_castle),
            (val_add, ":lord_have_fief", 1),
          (else_try),
            #(party_slot_eq, ":cur_center", slot_party_type, spt_village),
            (val_add, ":lord_have_fief", 0), #initially lords with only village low range.
          (try_end),
        (try_end),
        
        #(store_sub, ":title_index", ":faction_no", kingdoms_begin),
        (try_begin),
          (faction_slot_eq, ":faction_no", slot_faction_culture, "fac_culture_norse"),
          (try_begin),
            (eq, ":troop_no", ":faction_leader"), # he is king
            (assign, ":title_index", "str_faction_title_male_player"),
          (else_try),
            (gt, ":lord_have_fief", 0), #he has town or forts.
            (assign, ":title_index", "str_faction_title_male_1"),
          (else_try),
            (assign, ":title_index", "str_faction_title_male_2"),
          (try_end),
        (else_try),
          (this_or_next|faction_slot_eq, ":faction_no", slot_faction_culture, "fac_culture_saxon"),
          (faction_slot_eq, ":faction_no", slot_faction_culture, "fac_culture_angle"),
          (try_begin),
            (eq, ":troop_no", ":faction_leader"), # he is king
            (assign, ":title_index", "str_faction_title_male_3"),
          (else_try),
            (gt, ":lord_have_fief", 0), #he has town or forts.
            (assign, ":title_index", "str_faction_title_male_4"),
          (else_try),
            (assign, ":title_index", "str_faction_title_male_5"),
          (try_end),
        (else_try),
          (faction_slot_eq, ":faction_no", slot_faction_culture, "fac_culture_welsh"),
          (try_begin),
            (eq, ":troop_no", ":faction_leader"), # he is king
            (assign, ":title_index", "str_faction_title_male_6"),
          (else_try),
            (gt, ":lord_have_fief", 0), #he has town or forts.
            (assign, ":title_index", "str_faction_title_female_player"),
          (else_try),
            (assign, ":title_index", "str_faction_title_female_1"),
          (try_end),
        (else_try),
          (this_or_next|faction_slot_eq, ":faction_no", slot_faction_culture, "fac_culture_scotch"),
          (faction_slot_eq, ":faction_no", slot_faction_culture, "fac_culture_irish"),
          (try_begin),
            (eq, ":troop_no", ":faction_leader"), # he is king
            (assign, ":title_index", "str_faction_title_male_3"),
          (else_try),
            (gt, ":lord_have_fief", 0), #he has town or forts.
            (assign, ":title_index", "str_faction_title_male_4"),
          (else_try),
            (assign, ":title_index", "str_faction_title_male_5"),
          (try_end),
        (else_try), #other
          (try_begin),
            (eq, ":troop_no", ":faction_leader"), # he is king
            (assign, ":title_index", "str_faction_title_female_5"),
          (else_try),
            (gt, ":lord_have_fief", 0), #he has town or forts.
            (assign, ":title_index", "str_faction_title_female_5"),
          (else_try),
            (assign, ":title_index", "str_faction_title_female_6"),
          (try_end),
          
          #(eq, ":gender", 0), #male
          ##          (val_add, ":title_index", kingdom_titles_male_begin),
          ##        (else_try),
          ##          (val_add, ":title_index", kingdom_titles_female_begin),
        (try_end),
        (str_store_string, s1, ":title_index"),
        (troop_set_name, ":troop_no", s1),
        (troop_get_slot, ":troop_party", ":troop_no", slot_troop_leaded_party),
        (gt, ":troop_party", 0),
        (str_store_troop_name, s5, ":troop_no"),
        (party_set_name, ":troop_party", "str_s5_s_party"),
      (try_end),
  ]),

Examine the script. You have titles by culture (Norse, Pic, Angle), if the lord has fiefs or not (owns a village/fort/town).

You can now modify this behaviour, including more cultures/factions, changing the titles, etc.

You could make this event happen more often, to update lords that earn fiefs (not just when they change factions).

And create new functions, up to you!

Important: remember if you want to change a title that you should also rename the original troop on module_troops.py
 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
Question 4: how do I change the version displayed ingame?

Step 1-> what: game displays a version number on start screen. How can I change it to my mod version?

Step 2-> game feature: start screen

Step 3-> find it:
Warband screens/UI are defined on module_presentations.py. This is where we can create our own UI layout, while the module_game_menus.py has a fixed one.

So lets locate the code:

Code:
  ("game_start",prsntf_read_only,0,[
      (ti_on_presentation_load,
        [
          (presentation_set_duration, 999999),
          (set_fixed_point_multiplier, 1000),
          
          (assign, ":vc_version", vc_version),
          (store_div, reg1, ":vc_version", 1000),
          (store_mod, reg2, ":vc_version", 1000),
          (try_begin),
            (eq, reg2, 0),
            (str_store_string, s1, "@{reg1}.0"),
          (else_try),
            (lt, reg2, 10),
            (str_store_string, s1, "@{reg1}.00{reg2}"),
          (else_try),
            (lt, reg2, 100),
            (try_begin),
              (store_mod, ":result", reg2, 10),
              (eq,  ":result", 0),
              (val_div, reg2, 10),
            (try_end),
            (str_store_string, s1, "@{reg1}.0{reg2}"),
          (else_try),
            (try_begin),
              (store_mod, ":result", reg2, 100),
              (eq,  ":result", 0),
              (val_div, reg2, 100),
            (else_try),
              (store_mod, ":result", reg2, 10),
              (eq,  ":result", 0),
              (val_div, reg2, 10),
            (try_end),
            (str_store_string, s1, "@{reg1}.{reg2}"),
          (try_end),
          
          (create_text_overlay, "$g_presentation_credits_obj_1", "@{s1} ", tf_center_justify|tf_double_space|tf_vertical_align_center),
          (overlay_set_color, "$g_presentation_credits_obj_1", 0x000000),
          #(overlay_set_color, "$g_presentation_credits_obj_1", 0xFF0000),#We could make the version number red if it is edited and not clean any more
          
          #Show versions in the log
          (set_show_messages, 0),
          (display_message, s1),
          (set_show_messages, 1),
          
          #Stop all sounds (VC-3218)
          (stop_all_sounds, 1),
          
          (position_set_x, pos1, 920),
          (position_set_y, pos1, 50),
          (overlay_set_position, "$g_presentation_credits_obj_1", pos1),             
      ]),
  ]),
Step 4-> change it:

The presentation is doing two things related to the version #.

1) Display on screen
Code:
(create_text_overlay, "$g_presentation_credits_obj_1", "@{s1} ", tf_center_justify|tf_double_space|tf_vertical_align_center),
2) Prints it to rgl_log.txt
Code:
#Show versions in the log
(set_show_messages, 0),
(display_message, s1),
(set_show_messages, 1),
rgl_log.txt sample:
Code:
 ....
 loading time:  11762
 2.023
 Finished All...
Note that {s1} is defined using a Python variable: vc_version
Code:
(assign, ":vc_version", vc_version),
Lets locate where this comes from: module_constants.py
Code:
vc_version = 2023
And now we have it. Change the value for your own mod version (like 1000 for 1.0, 1010 for 1.01, etc).

Note the conversion won't work for version below 1000, so if you want create v0.1 you will need to change how the {s1} is created.
 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
Question 5: How to make marriage simpler?

Nurse explains the base of the mechanic:
Thank you, your lordship. To be considered as a potential groom, you have to be a person of stature, a pledged vassal or a renowned warrior.

At the very least, you should have as much renown as junior lords. Secondly, you must have good relations with the lady, and her father or guardian. Lastly, the most important
component is chemistry. The chemistry between you and the lady will largely depend on your charisma, so being ugly, fat, or having a cheap beer or pickle breath would
hardly come handy.

If you get refused the first time, don't despair, their ladyships are known for playing hard to get, wait a few days and try again.

With high persuasion, and charisma, you will eventually prevail, and the lady will agree to get married.

Then go and talk to her father or guardian, and get an official blessing from him.

Step 1-> what: marriage in VC has many conditions, and with small factions it is hard to get a lady to accept you (even with high persuasion). How can I make this simpler?

Step 2-> game feature: marriage proposal (starts from a dialog) for the lady (not the guardian)

Dialog: "Do you think that we may have a future together, my lady?"


Step 3-> find it:
The event starts from a dialog (conversation), so we can look at module_dialogs.py for that line (step 2).

Code:
  [anyone|plyr,"lady_talk",
    [
      (neg|check_quest_active, "qst_formal_marriage_proposal"),
      (neg|troop_slot_ge, "trp_player", slot_troop_betrothed, active_npcs_begin),
      (neg|troop_slot_ge, "trp_player", slot_troop_spouse, active_npcs_begin),
      
      
      (troop_slot_eq, "$g_talk_troop", slot_troop_met, 2),
      (eq, "$talk_context", tc_courtship),
      (troop_slot_eq, "$g_talk_troop", slot_troop_refused, 0),
    ],
    "Do you think that we may have a future together, my lady?", "lady_marriage_discussion",[
  ]],
Code:
  [anyone,"lady_marriage_discussion",
    [(troop_slot_ge, "trp_player", slot_troop_renown, 500),
      (gt, "$g_talk_troop_relation", 60),
      (call_script, "script_get_kingdom_lady_social_determinants", "$g_talk_troop"),
      (assign, ":guardian", reg0),
      (call_script, "script_troop_get_family_relation_to_troop", ":guardian", "$g_talk_troop"),
      (str_store_troop_name, s4, ":guardian"),
      (call_script, "script_troop_get_romantic_chemistry_with_troop", "$g_talk_troop", "trp_player"),
      (gt, reg0, 8),
    ],
    "Oh {playername}, how happy that would make me! Go ask my {s11} {s4} for permission!", "close_window",[
      (call_script, "script_get_kingdom_lady_social_determinants", "$g_talk_troop"),
      (assign, ":guardian", reg0),
      (str_store_troop_name_link, s12, ":guardian"),
      (str_store_troop_name, s15, "$g_talk_troop"),
      (setup_quest_text, "qst_formal_marriage_proposal"),
      (str_store_string, s2, "str_you_intend_to_ask_s12_for_permission_to_marry_s15"),
      
      (quest_set_slot, "qst_formal_marriage_proposal", slot_quest_target_troop, ":guardian"),
      (quest_set_slot, "qst_formal_marriage_proposal", slot_quest_expiration_days, 30),
      (quest_set_slot, "qst_formal_marriage_proposal", slot_quest_current_state, 0),
      (call_script, "script_start_quest", "qst_formal_marriage_proposal", "$g_talk_troop"),
      (quest_set_slot, "qst_formal_marriage_proposal", slot_quest_giver_troop, "$g_talk_troop"),
      
      #Repeated to ensure strings work correctly
      (call_script, "script_troop_get_family_relation_to_troop", ":guardian", "$g_talk_troop"),
      (str_store_troop_name, s4, ":guardian"),
  ]],
Note that the decision to accept or not the proposal is handled by a script: "script_troop_get_romantic_chemistry_with_troop".

There are also some pre-conditions, like how much renown you have if your are "ambitious". Check all the dialogs "lady_marriage_discussion".


Step 4-> change it:
Now you have alternatives. You can bypass the script and force the OK answer, tweak the conditions to make them easier, remove the random % chance, etc

Example: lets force the lady to say "YES", ignoring the conditions like personality match, etc, by disabling the chemistry script (comment those lines):

Code:
      #(call_script, "script_troop_get_romantic_chemistry_with_troop", "$g_talk_troop", "trp_player"),
      #(gt, reg0, 8),
 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
Question 6: How can I show all companions on the UI report?

Step 1-> what: on your report you have a screen that displays the list of companions, with portrait, basic info, etc. By default it is disabled on storymode and it only shows companions currently at your party. Can I show all of them and add more info, like where can I find them?

Step 2-> game feature: companion report (visual report)

World Map View -> Character button -> Companions button -> View Companions button

Step 3-> find it:
Its a screen/UI, so we can start looking at module_presentations.py. You can use some of the screen strings, or check the list of presentations (there aren't many).

Code:
# "prstn_show_companions"
# VC-2380 Companions Presentations: companions are displayed as
# cards on a deck, with a central portrait that shows extra info
# if you select (click) on one of them
# check module_constants for the configuration variables
("show_companions", 0, 0, [
    (load, [
The comment indicates we can find some extra configurations on module_constants.py, so lets give a look at them:

Code:
# Companions presentation: deck of companions (each is a card) with central portrait and info
companions_prsnt_debug = 0 # 0: OFF, 1: show extra info
companions_prsnt_show_members_not_in_party = 0 # 0: black card, 1: grey out card, no name, 2: grey out card, show name
companions_prsnt_show_lords = 0 # 0: won't show companions that are now lords, 1: shows
companions_prsnt_show_storyline_mode = 0 # 0:disable for storyline, 1: enable this
OK, so we can change some of the behaviour with this flags!
1) Add debug info (for testing the feature, disable it for release)
2) Decide how to display the cards of companions not in your party. #1 will do it for us now
3) Want to display ex-companions that are now lords? Like a companion that you promoted to become a vassal?
4) Enable the report button for storyline (SPOILERS!!!!), note that location won't work as companions are controlled by special conditions in this mode!

OK so we solved problem one (show all companions), now we need to figure out HOW to add more information (their current location): lets go back to the presentation code and find where it writes the text for the central card/portrait

On the click event we have this code:
Code:
            # central portrait
            (create_mesh_overlay_with_tableau_material, reg11, -1, "tableau_troop_note_mesh", ":troop_id"),
            (set_fixed_point_multiplier, 1000),
            (position_set_x, pos1, 390),(position_set_y, pos1, 270),
            (overlay_set_position, reg11, pos1),
            (position_set_x, pos1, 600),(position_set_y, pos1, 600),
            (overlay_set_size, reg11, s1),
            
            (call_script, "script_npc_morale", ":troop_id"),
            (str_store_troop_name, s1, ":troop_id"),
            (str_store_string, s1, "str_com_morale_reg0"),
            (troop_get_slot, reg12, ":troop_id", slot_troop_player_relation),
            (str_store_string, s1, "str_com_relation_reg12"),
            (troop_get_slot, reg12, ":troop_id", 16),
            (try_begin),
              (eq, reg12, 1),
              (str_store_string, s1, "str_com_catholic"),
            (else_try),
              (str_store_string, s1, "str_com_pagan"),
            (try_end),
            
            (create_text_overlay, reg13, s1, tf_double_space),
            (position_set_x, pos1, 405),(position_set_y, pos1, 45),
            (overlay_set_position, reg13, pos1),
            (position_set_x, pos1, font_normal),(position_set_y, pos1, font_normal),
            (overlay_set_size, reg13, pos1),
            (overlay_set_color, reg13, 0x000000),
What it does? Draws the portrait, writes the name, relation, morale, religion. Now lets add the town!

Step 4-> change it:

module_constants.py
Code:
companions_prsnt_show_members_not_in_party = 1 # 0: black card, 1: grey out card, no name, 2: grey out card, show name
slot with location of a npc: slot_troop_cur_center. So lets add this info to {s1}, which is later printed in the screen:

Code:
		(troop_get_slot, reg12, ":troop_id", slot_troop_cur_center),
		(try_begin),
			(ge, reg12, 0),
			(str_store_party_name, s2, reg12),
			(try_begin),
				(eq, ":troop_id", "trp_npc7"), # dwyney goes to Ancient Stones
				(str_store_string, s1, "@{s1}^^At: Ancient Stones"), 
			(else_try),
				(eq, ":troop_id", "trp_npc1"), # caio goes to the Roman Wall=Hadrian Wall
				(str_store_string, s1, "@{s1}^^At: Hadrian Wall"), 
			(else_try),
				(str_store_string, s1, "@{s1}^^At: {s2}"), 
			(try_end),
		(try_end),
And we are done, all possible companions are displayed and you know where they are in case you need to visit a tavern!

Note that this shouldn't be used on storyline, as that mode has special conditions
 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
Question 7: How can I change the XP curve for troops?

Step 1-> what: recruited troops (your army) have a XP curve/requirement before they can be upgraded to a higher tier. Example: a recruit to a spearman. They gain XP from training (skill), from combat (killing), battle reward, etc

Step 2-> game feature:
Open your party screen (army in VC). Troops ready to be upgraded have a button for that. It costs cash ($$$).

We need to figure out how the game calculates how much XP a particular troops requires.

Step 3-> find it:
You have two options. Change the configuration on modules.ini, or the script responsible for the XP curve. Lets focus on the script:

module_scripts.py: its the same base script from native, but with extra options
Code:
  # script_game_get_upgrade_xp
  # This script is called from game engine for calculating needed troop upgrade exp
  # Input:
  # param1: troop_id,
  # Output: reg0 = needed exp for upgrade
  ("game_get_upgrade_xp",
    [
      (store_script_param_1, ":troop_id"),
      
      (assign, ":needed_upgrade_xp", 0),
      #formula : int needed_upgrade_xp = 2 * (30 + 0.006f * level_boundaries[troops[troop_id].level + 3]);
      (store_character_level, ":troop_level", ":troop_id"),
      (try_begin),
        (eq,"$easy_levelling", 0),
        (try_begin),
          (ge, ":troop_level", 28),
          (store_add, ":needed_upgrade_xp", ":troop_level", 4),# was 8
        (else_try),
          (store_add, ":needed_upgrade_xp", ":troop_level", 8), #high level troops more difficult to upgrade -10
        (try_end),
      (else_try),
        (eq,"$easy_levelling", 1),
        (try_begin),
          (ge, ":troop_level", 28),
          (store_add, ":needed_upgrade_xp", ":troop_level", 2),# was 8
        (else_try),
          (store_add, ":needed_upgrade_xp", ":troop_level", 4), #high level troops more difficult to upgrade -10
        (try_end),
      (else_try),
        (eq,"$easy_levelling", 2),
        (try_begin),
          (le, ":troop_level", 23),
          (store_add, ":needed_upgrade_xp", ":troop_level", 8),# was 8
        (else_try),
          (store_add, ":needed_upgrade_xp", ":troop_level", 10), #high level troops more difficult to upgrade -10
        (try_end),
      (try_end),
      (get_level_boundary, reg0, ":needed_upgrade_xp"),
      (val_mul, reg0, 6),
      (val_div, reg0, 1000),
      (val_add, reg0, 30),
      (try_begin),
        (gt, ":troop_level", 23),
        (try_begin),
          (eq,"$easy_levelling", 1),
          (val_mul, reg0, 6),
          (val_div, reg0, 5),
        (else_try),
          (eq,"$easy_levelling", 0),
          (val_mul, reg0, 4),
          (val_div, reg0, 3),
        (else_try),
          (eq, "$easy_levelling", 2),
          (val_mul, reg0, 3), #high level troops more difficult to upgrade -- was 3
        (try_end),
      (try_end),
      (try_begin),
        (eq, "$easy_levelling", 2),
        (is_between, ":troop_id", bandits_begin, bandits_end),
        (val_mul, reg0, 2),
      (else_try),
        (this_or_next|eq,"$easy_levelling", 1),
        (eq,"$easy_levelling", 0),
        (is_between, ":troop_id", bandits_begin, bandits_end),
        (val_mul, reg0, 2),
        (val_div, reg0, 3),
      (try_end),
      (set_trigger_result, reg0),
  ]),

Reading the code we see a few things:
1) Global "$easy_levelling" changes the curve! If you search for it you will see that its defined on the options menu, so a player can choose between CASUAL, normal or Hardcore XP.

value 0 := NORMAL XP
value 1 := BEGINNER XP
value 2 := HARDCORE XP

2) Troops in VC are split in tiers by level. For XP we have
  Troops level 1 to 23. Example: recruits are level 12
  Troops 24 to 27. Example: Norse Veteran are level 27
  Troops higher/equal than 28 (elites). Example: Norse Bodyguard are level 31

3) Bandit troops have a special condition

Step 4-> change it:
You can now change this script to make it easier (less XP) or harder (more XP) to any tiers or game difficulty

 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
Question 8: How to use rgl_log to debug the game?

Step 1-> what: rgl_log.txt prints information from the game, in special errors when using invalid commands. Its a important tool to help identify bugs and how to fix them.

Important: new modders make a mistake here. They ignore this tool, find things that don't work on their mods and look at a empty rgl_log.txt and don't understand what went wrong. This file is not magic, you need to add the messages as well!

Use rgl_log.txt if you want to have a non buggy and easy to test mod!


Step 2-> game feature:
This is mostly technical, so lets examine how we can use this:

1) Operations
Code:
display_message                     = 1106  # (display_message, <string_id>,[hex_colour_code]),
                                            # Display a string message using provided color (hex-coded 0xRRGGBB).
set_show_messages                   = 1107  # (set_show_messages, <value>),
                                            # Suppresses (value = 0) or enables (value = 1) game messages, including those generated by the game engine.
display_message prints the text to the screen and rgl_log.txt
set_show_messages can be used to hide the text from the screen (we will need this for debugging long texts)


2) Quick example:
Code:
      (try_begin),
        (eq, debug_goredec, 1),
        (assign, reg1, ":hit_bone"),
        (display_message, "@{!}DEBUG: cf_goredec OK on bone: {reg1}"),
      (try_end),
Note that we have a try block (isolate), it checks if it should print that test message (only if debug_goredec == 1), assigns a value to reg1 and prints to the screen/rgl_log.txt

3) Using a keyboard command to fire a debug:
module_triggers.py
Code:
  (0, 0, 0,
    [
      (key_clicked, key_d),#key_v
      (key_is_down, key_left_control),
    ],
    [
      (display_message, "@CTRL+D was used on the world map!"),
    ]),
You can use this for quick tests. Few examples:
1) Print on rgl_log.txt how much money each lord has (testing balance)
2) Information on a particular town (values from slots)
3) Information on the player party (army composition)
and so on.

You can see a example of this by checking this trigger in VC:
Code:
 # Testing season Duck season
  (0, 0, 0,
    [
      (try_begin),#VC-946

4) Using a trigger for automatic logging:
See a example on this trigger:
Code:
      # Logger for open-beta RE
      (24*7, 0, 0,
This was used to test bandits around the world. Its lists how many are around, where they are, how many groups were destroyed, the player strenght and so on. You can create a new game, use infinite camp and let the world run from months and look at the data to see if there are balance issues.


5) Turning debuff ON and OFF
You should have a easy way to turn these messages ON and OFF.
ON when doing your tests
OFF when buildig code for players

Few alternatives:
a) My favourite is using a Python variable for each feature. That way you have fine control over what is being logged.
Example: module_constants.py
Code:
# Decapitation system
debug_goredec = 0 # 0: OFF, 1: show extra info, 2: skip checks
0: production, what players should use
1: display the calculations for balancing the feature. How the chance of a decapitation is working, how many total in a battle, etc
2: disables chance tests to force decapitations. Used to test the visual effects.


b) Enable logging when cheats are ON. This is how Native usually does it
Code:
     (try_begin),
        (gt, "$cheat_mode", 0),
        (assign, reg4, "$talk_context"),
        (display_message, "@{!}DEBUG -- Talk context: {reg4}"),
      (try_end),

You must enable "edit mode" for this to work. Its a option on the game launcher, that among other things, allows you to edit scenes, and prints all these extra messages on rgl_log.txt



 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
Alright modders, this should be enough to start exploring the VC code  :mrgreen:

If anyone has more questions on how to do things (exclusive to VC features), or if you need help with a specific issue, feel free to post your questions here.

Cheers!
 

kalarhan

Python Saint
Count
WBNWVCWF&S
Best answers
31
DrunkenFrenchman said:
Hey everyone, I've noticed that I always use the same banners when I'm playing VC and was wondering if anyone had an easy way to allow players to access vanilla banners in VC so there is more variety fpr the player. Not necessarily historically accurate but I need a change of pace.
TLDR: How to mod banners in the game?

The screenshoot thread has 3 posts talking about this subject:
motomataru said:
If you're going to mod devices, you should be aware that we split them out in VC to avoid that banner-painted-on-a-shield look from Native. It would have been great to have the banner texture and folds "effect" just overlay the clean shield design, but we didn't get that far. Anyway, the file naming convention is:

ethnic_[12].dds  scene prop banners
ethnic_[12]a.dds  clean version for shields
ethnic_[12]b.dds  flag versions

If one completely changes the device, one needs to update all three to match...

Kingdom banners follow a similar scheme, but not the convention.

kalarhan said:
kraggrim said:
Is it as simple as that? I tried re-centering the blue ship banner used in reiksmarshal's second screenshot and in game it then showed half of a different banner.
create a layer for the templates. You can see them on vikings_2a.dds. They are the reference for your shield center and sizes.

I changed the position of the raven and inverted just for fun. I am terrible with image editing, but I was able to install gimp-dds, edit it and export back to the game in a couple minutes. Someone with a artist background can create new stuff for sure  :smile:


Tutorial in the Forge: http://forums.taleworlds.com/index.php/topic,295488.0.html
Banner OSP pack (sample): http://forums.taleworlds.com/index.php/topic,279661.0.html

Have fun modding! And don't forget to share it with us  :iamamoron:
kraggrim said:
kalarhan said:
you can download a DDS plugin (or Nvidia free software) and edit the textures. It is quite easy to do it. Then you post it on a thread as a sub-mod for other players  :smile:

You can replace banners too and combine resources from other OSP, plenty of banners to choose in the Forge subforums!

Create one with your avatar, that would be a scary looking shield!  :twisted:
Is it as simple as that? I tried re-centering the blue ship banner used in reiksmarshal's second screenshot and in game it then showed half of a different banner.

Edit: Oops, just noticed my modified texture ended up looking like this after I saved it:
No wonder it looked off!

Edit 2:Ok I had to save it as no mip-maps, now it works fine.  I'd upload a picture but my internet is being weird. Should just be a simple cut/paste/fill job on photoshop/gimp (with the DDS plugin) reiksmarshal.
You will need to either replace the game banners with vanilla (replace them over a old one) or add new banners to the game (which is possible with the modules).
 

izaktj

Knight
Best answers
0
I like how there's a bunch of comments in Spanish, it's rare to see a spanish speaking developers.
 

mike56

Master Knight
WBNWWF&SM&BVC
Best answers
0
Bearomir said:
I like how there's a bunch of comments in Spanish, it's rare to see a spanish speaking developers.
Most of the Viking Conquest team are from Spain  :smile:
 

izaktj

Knight
Best answers
0
mike56 said:
Bearomir said:
I like how there's a bunch of comments in Spanish, it's rare to see a spanish speaking developers.
Most of the Viking Conquest team are from Spain  :smile:
Oh no way, that's pretty cool.
Han hecho un buen trabajo, buen mod!