[suggestion]framework to make it easier for different mods to work together

Users who are viewing this thread

sphere

Grandmaster Knight
At this moment, I am using this in a mod I am trying to do, which seems quite useful, which is why I decided to share this idea to see if it can help somebody, or if there are any other ideas that can improve it further.  Must declare beforehand that this is still a rough piece of WIP that is still undergoing changes.  Was thinking that experienced modders could start from this basic idea and develop this into a framework that will make it easier for mods to "merge" in the future.

Context
Only python module system atm.

General situation
Everybody mods the original module_*.py files.  So much so that it is VERY hard for different mods to work together since everybody changes a line here and there (you'll know if you are a modder).  In the end, the only seemingly viable solution will be to use a diff/merging program (like winmerge) to try to ease the task.  But even so, it has the big problems of firstly not safe (prob have to manually check all the differences manually, where diff problem just serve as a search tool) and also unable to cope with updates in individual mods (e.g. If some mod updates, then you have to start from original copy of everything and redo the merging....)

What does this "framework" suggestion offer?
"Framework" may be too big a word.  It is more like some simple standards that will make it easier for mods to cooperate if modders conform to certain simple standards.

The following are some features
  • Main bulk of mod contents do not sit in the original python files. 
    They are merged in on processing via python scripts (see later).  In this way, stuff that don't actually clash (i.e. no clash in constant/scripts/symbol names used by the mods we are merging), can sit in their own .py files and can be easily updated (just copy newer mod .py files over the old one without touching the original module_*.py)
  • Minimal intrusion in the original module_*.py. 
    E.g. for the mod I am working on, the only thing that I added to the original files is a script snippet at the bottom to dynamically merge in the additional stuff during processing.  After that, almost all updates to my mod are to my own script files.  Unless there are drastic changes that requires replacement or reordering of the stuff in the original module files, there is no need to touch the original files ever again.

Some existing disadvantages
  • Slightly larger processing cost
    The content from mods are merged in everytime the original module_*.py file is imported into another python module.



Example 1: Merging constants from different mods

Easiest of all to merge is the module_constants.py.
Note: MUST MAKE SURE THERE ARE NO SYMBOL OR VALUE CLASH IN THE FIRST PLACE.

For example, this is the sample content of the fc_constants.py for a mod I am working on
Code:
# force_commander_start

# copy the block below (excluding the lines with "block start" and "block end") and paste to bottom of module_constants.py

## block start
## force_commander_start
#try:
#  from fc_constants import *
#except:
#  print "error importing from fc_constants"
#  pass
# force_commander_end
## block end

########################################################
##  PARTY SLOTS            #############################
########################################################

########################################################
# Force Commmander
# mission is a higher level state machine that controls the party strategy to achieve certain goals
# task is a simple action that works towards a mission goal

#slot definitions

# slots used for miscellaneous/general party stuff, meant as neutral extension to native
slot_fc_party_name_template		= 411
slot_fc_party_name_noun			= 412
slot_fc_party_name_adjective	= 413
slot_fc_party_home				= 414
slot_fc_party_gold				= 421 # allow party to be an entity holding onto gold

# slots used for task designation and state
slot_fc_party_task				= 500
slot_fc_party_task_0			= 500
slot_fc_party_task_1			= 501
slot_fc_party_task_2			= 502
slot_fc_party_task_3			= 503
slot_fc_party_task_4			= 504
slot_fc_party_task_5			= 505
slot_fc_party_task_6			= 506
slot_fc_party_task_7			= 507
slot_fc_party_task_8			= 508
slot_fc_party_task_timestart	= 590	# time when the task is started
slot_fc_party_task_timeout		= 591	# timeout for the task  (if currenthours - timestart > timeout, the task will end with timeout) Use -1 to indicate no timeout.
slot_fc_party_task_state		= 599	# stores generic task state: fc_ts_???

# force commander task state flag
fc_tsf_mask_success	= 0x0001 # mask for error
fc_tsf_mask_end		= 0x0001 # mask for task has ended

fc_tsf_end		= 0x0001 # task flag for complete (regardless of successs or not)
fc_tsf_timeout	= 0x0002 # task flag for timeout
fc_tsf_success	= 0x1000 # task flag for success

fc_ts_none 		= 0x0000 # task has not been initialized or started
fc_ts_complete	= fc_tsf_end | fc_tsf_success # task is complete
fc_ts_failed	= fc_tsf_end # task has failed
fc_ts_timeout	= fc_tsf_end | fc_tsf_timeout # task has timeout
fc_ts_init 		= 0x0100 # task is initializing
fc_ts_running 	= 0x0200 # task is running
fc_ts_finishing = 0x0400 # task is finishing

# slots used for mission designation and state
slot_fc_party_mission			= 600
slot_fc_party_mission_0			= 600
slot_fc_party_mission_1			= 601
slot_fc_party_mission_2			= 602
slot_fc_party_mission_3			= 603
slot_fc_party_mission_4			= 604
slot_fc_party_mission_5			= 605
slot_fc_party_mission_6			= 606
slot_fc_party_mission_7			= 607
slot_fc_party_mission_8			= 608
slot_fc_party_mission_timestart	= 690	# time when the mission is started
slot_fc_party_mission_timeout	= 691	# timeout for the mission (if currenthours - timestart > timeout, the mission will end with timeout) Use -1 to indicate no timeout.
slot_fc_party_mission_state		= 699	# mission state is specific to each mission's state machine and is up to mission coder
slot_fc_party_mission_custom_fn	= 698	# if slot_fc_party_mission contains fc_mission_custom, this will contain the name of the script to call


# task codes
#   movement
fc_task_none						= 0
fc_task_goto_party					= 1001 # args=(tgt_party, range), moves to within (range) of the (tgt_party)
fc_task_goto_point					= 1002 # args=(x,y,z, range) , moves to within (range) of  the position (x,y,z)
fc_task_escort_party				= 1003 # args=(tgt_party), escorts (tgt_party)

# mission codes
fc_mission_none					= 0 # no mission
fc_mission_custom				= 100 # testing.  see if can delegate mission update call to a custom script by storing the script function name
fc_mission_escort				= 101 # testing.  just escorts a party 

#mission definitions


# Commander

# slots used for commander related data
# commander rights and be hierarchical/indirect.  Script fc_get_command_rights has the final say.
# currently using similar values for party, troop and faction, but can use different values if there are clashes with other mods.

slot_fc_party_commander_type	= 700 # 0 : none, 1 : troop, 2 : party, 3 : faction
slot_fc_party_commander			= 701 # type depends on slot_fc_party_commander_type.  Check commander_type != 0 , for existance of commander.
slot_fc_party_commander_rights	= 702 # If not fc_commandrights_full, it will be some squad which cannot be totally commandered by anybody

slot_fc_troop_commander_type	= 700 # 0 : none, 1 : troop, 2 : party, 3 : faction
slot_fc_troop_commander			= 701 # type depends on slot_fc_party_commander_type.  Check commander_type != 0 , for existance of commander.
slot_fc_troop_commander_rights	= 702 # If not fc_commandrights_full, it will be some squad which cannot be totally commandered by anybody

slot_fc_faction_commander_type	= 700 # 0 : none, 1 : troop, 2 : party, 3 : faction
slot_fc_faction_commander		= 701 # type depends on slot_fc_party_commander_type.  Check commander_type != 0 , for existance of commander.
slot_fc_faction_commander_rights= 702 # If not fc_commandrights_full, it will be some squad which cannot be totally commandered by anybody


# values for slot_fc_party_commander_type
fc_entitytype_none = 0
fc_entitytype_troop = 1
fc_entitytype_party = 2
fc_entitytype_faction = 3
# fc_entitytype_item = 4
# fc_entitytype_partytemplate = 5
fc_entitytype_any = -1


# values for slot_fc_party_commander_type
fc_commandertype_none = fc_entitytype_none
fc_commandertype_troop = fc_entitytype_troop
fc_commandertype_party = fc_entitytype_party
fc_commandertype_faction = fc_entitytype_faction

fc_commandertype_any = fc_entitytype_any

# command rights
#   currently,
#     lowest 4 bits (lowest hex digit) used for view rights
#     next 4 bits (2nd lowest hex digit) used for task rights
#     next 4 bits (3rd lowest hex digit) used for mission rights
#     next 4 bits (highest hex digit) used for general party rights like renaming, composition, disbanding, gold account
fc_commandrights_none			= 0			# no rights to command
fc_commandrights_view_task		= 0x0001	# can view task
fc_commandrights_view_mission	= 0x0002	# can view mission
fc_commandrights_view_gold		= 0x0004	# can view party gold/finance related
fc_commandrights_task			= 0x0010	# can change task
fc_commandrights_mission		= 0x0100	# can change mission
fc_commandrights_compose		= 0x1000	# can exchange troops with party/disband/order to garrison
fc_commandrights_rename			= 0x2000	# can rename party
fc_commandrights_gold			= 0x4000	# can access party account
fc_commandrights_full			= 0xFFFF	# full rights to command

fc_commandrights_view			= fc_commandrights_view_mission | fc_commandrights_view_task 	# full rights to viewing mission/task
fc_commandrights_ally 			= fc_commandrights_view # rights for allies


# Force Commander end
########################################################


########################################################
##  SCENE SLOTS            #############################
########################################################



########################################################
##  TROOP SLOTS            #############################
########################################################
											
########################################################
##  PLAYER SLOTS           #############################
########################################################


########################################################
##  TEAM SLOTS             #############################
########################################################


########################################################
##  QUEST SLOTS            #############################
########################################################


########################################################
##  PARTY TEMPLATE SLOTS   #############################
########################################################


########################################################
##  SCENE PROP SLOTS       #############################
########################################################


########################################################
##  Range constants        #############################
########################################################

fc_nouns_begin = "str_fc_noun_0"
fc_nouns_end = "str_fc_noun_end"
fc_adjectives_begin = "str_fc_adj_0"
fc_adjectives_end = "str_fc_adj_end"

fc_partynametemplates_begin = "str_fc_party_name_noun_s12"
fc_partynametemplates_end = "str_fc_party_name_end"

fc_partynametemplates_wo_leader_begin = "str_fc_party_name_noun_s12"
fc_partynametemplates_wo_leader_end = "str_fc_party_name_ldr_s10_noun_s12"

fc_partynametemplates_w_leader_begin = "str_fc_party_name_ldr_s10_noun_s12"
fc_partynametemplates_w_leader_end = "str_fc_party_name_end"

# force_commander_end

The following code snippet is inserted at the bottom of the modules_constants.py

Code:
# force_commander_start
try:
  from fc_constants import *
except:
  print "error importing from fc_constants"
  pass
# force_commander_end

This will simply import all the symbols defined in fc_constants.py at run-time, including the cases where other .py files import from module_constants.py.  For those who are quick, you can already see how this can be easily extended for other mods.  (unfortunately atm, each new included mod have to include another line/code segment.

For example, say I have 2 other mods with constants defined in mod1_constants.py and mod2_constants.py (assume no clash in content).

The amended code (long version) can be as follow

# force_commander_start
try:
  from fc_constants import *
except:
  print "error importing from fc_constants"
  pass
# force_commander_end

# mod1_start
try:
  from mod1_constants import *
except:
  print "error importing from mod1_constants"
  pass
# mod1_end

# mod2_start
try:
  from mod2_constants import *
except:
  print "error importing from mod2_constants"
  pass
# mod2_end

Or even shorter:
(But note that previous long version has the advantage of keeping content from different mod separate, and thus can have more flexibility and opportunity for automated manipulation in the future)
# force_commander_start
try:
  from fc_constants import *
  from mod1_constants import *
  from mod2_constants import *

except:
  print "error importing from mod constants"
  pass
# force_commander_end



Example 2: Merging lists from different mods (e.g. scripts, game_menus, troops, parties, party_templates etc)

The second example deals with module contents which are contained in simple list.

The following is sample content from fc_scripts.py that I have which contains all the scripts that my mod uses
Code:
# force_commander_start
# # The following segment is to be inserted into module_scripts.py.  Insert it at the bottom and uncomment the whole block
# # Can be used for other mod scripts as well, just change the first "fc_scripts" in the first line "from fc_scripts ..." the mod scripts file.

# try:
  # from fc_scripts import scripts as fc_scripts
  
  # check_duplicates = false # set to true if there are replacements to original entries, false will be more efficient
  
  # if( not check_duplicates ):
	# scripts.extend(fc_scripts) # Use this only if there are no replacements (i.e. no duplicated item names)
  # else:
	  # # Use the following loop to replace existing entries with same id
	  # for i in range (0,len(fc_scripts)-1):
		# find_index = find_object(scripts, fc_scripts[i][0]); # find_object is from header_common.py
		# if( find_index == -1 ):
			# scripts.append(fc_scripts[i])
		# else:
			# scripts[find_index] = fc_scripts[i]  
# except:
  # print "Error importing from fc_scripts"
  # pass

  # # force_commander_end


# force_commander_start
# -*- coding: cp1254 -*-
from header_common import *
from header_operations import *
#from module_constants import *
from module_constants import *
from header_parties import *
from header_skills import *
from header_mission_templates import *
from header_items import *
from header_triggers import *
from header_terrain_types import *
from header_music import *
from ID_animations import *


####################################################################################################################
# scripts is a list of script records.
# Each script record contns the following two fields:
# 1) Script id: The prefix "script_" will be inserted when referencing scripts.
# 2) Operation block: This must be a valid operation block. See header_operations.py for reference.
####################################################################################################################

scripts = [

  ############################################################################
  # Force Commander script to transfer gold between two parties. If there is not enough gold, transfer will fail (reg0 will contain 0)
  # Input: arg1 = party taking the gold, arg2 = party giving the gold, arg3 = amt to transfer
  # Output: reg0 = amt successfully transferred
  ############################################################################
 ("cf_fc_party_transfer_gold_from_party",
   [
    (store_script_param_1, ":party_receiving"),
    (store_script_param_2, ":party_giving"),
    (store_script_param, ":gold_to_transfer",3),
	(assign, reg0, 0),
	(neq, ":gold_to_transfer", 0),
	
	(call_script, "script_fc_party_get_gold", ":party_receiving"), #result in reg0	
	(assign, ":gold_party_receiving", reg0),
	(call_script, "script_fc_party_get_gold", ":party_giving"), #result in reg0	
	(assign, ":gold_party_giving", reg0),
	
	(val_sub, ":gold_party_giving", ":gold_to_transfer"),
	(val_add, ":gold_party_receiving", ":gold_to_transfer"),

	(try_begin),
		(ge, ":gold_party_giving", 0),
		(ge, ":gold_party_receiving", 0),
		
		(call_script, "script_fc_party_set_gold", ":party_receiving", ":gold_party_receiving"),
		(call_script, "script_fc_party_set_gold", ":party_giving", ":gold_party_giving"),
		(assign, reg0, ":gold_to_transfer"),		
	(else_try),
		(assign, reg0, 0), # transfer fails.
	(try_end),
 ]),  # script_newscript

]

# force_commander_end

The following is inserted at the bottom of module_scripts.py
# force_commander_start
# The following segment is to be inserted into module_scripts.py.  Insert it at the bottom and uncomment the whole block
# Can be used for other mod scripts as well, just change the first "fc_scripts" in the first line "from fc_scripts ..." the mod scripts file.
try:
  from fc_scripts import scripts as fc_scripts
 
check_duplicates = false # set to true if there are replacements to original entries, false will be more efficient
 
  if( not check_duplicates ):
scripts.extend(fc_scripts) # Use this only if there are no replacements (i.e. no duplicated item names)
  else:
  # Use the following loop to replace existing entries with same id
  for i in range (0,len(fc_scripts)-1):
find_index = find_object(scripts, fc_scripts[0]); # find_object is from header_common.py
if( find_index == -1 ):
scripts.append(fc_scripts)
else:
scripts[find_index] = fc_scripts 
except:
  print "Error importing from fc_scripts"
  pass
# force_commander_end


Whenever module_script.py is included by other scripts, the above snippet will grab all the scripts defined in fc_scripts.py and append it to the scripts list.
Notice the line  check_duplicates = false shown in red in the above code block.  This is to cater for cases where a mod may want to REPLACE an existing script in the original module_scripts.py (not common, but sometimes it happens for heavy mods).  When the scripts in my mod have no clash with original scripts, I set it to false, which just appends my scripts to the end of existing scripts.  If there are some scripts in original that I want to replace, I'll have to set it to true, so that it does a 1 by 1 check on the script name and if an existing script with same name is found, it will replace it with the new one, or if not found, it will be appended to the end of the list.


Example 3: Merging list with end marker entry (e.g. items)

Some modules have lists that have special marker entries that have special implications for in-game engine or scripts.  When merging stuff into this type of list, we have to preserve these markers (e.g. inserting only before this marker).  For this simple example using module_items.py, the end marker is a dummy item called "items_end".  Handling this is similar to Example 2, except that we have to take special care to preserve the marker item.

For example, the following code block is similar to example 2, with the additions of the two lines marked in green
# force_commander_start
# The following segment is to be inserted into module_items.py.  Insert it at the bottom and uncomment the whole block
# Can be used for other mod scripts as well, just change the first "fc_items" in the first line "from fc_items ..." the mod items file.
try:
  from fc_items import items as fc_items
  item_end = items.pop() # temporary remove item_end
 
  check_duplicates = false # set to true if there are replacements to original entries, false will be more efficient
 
  if( not check_duplicates ):
items.extend(fc_items) # Use this only if there are no replacements (i.e. no duplicated item names)
  else:
  # Use the following loop to replace existing entries with same id
  for i in range (0,len(fc_items)-1):
find_index = find_object(items, fc_items[0]); # find_object is from header_common.py
if( find_index == -1 ):
items.append(fc_items)
else:
items[find_index] = fc_items
 
  items.append(item_end) # push back item_end
except:
  print "Error importing from fc_items"
  pass
# force_commander_end


In other more complex cases where position markers appear in the middle of lists, the code that merge in will have to be updated to do in-position insertion, as opposed to the current method of appending to the back.


Example 4: Strings (with sub-category management)

Managing strings is mostly similar to Examples 2 and 3, but in strings, we often have to manage sub-categories of strings, for example, I keep separate lists of nouns and adjectives that I used to generate random party names in my mod:

fc_str_nouns.py:
Code:
# nouns (used for party names)
nouns = [
	# generic party names
	"Squad",
	"Party",
	"Band",
	"Group",
	"Company",
	"Horde",
	"Host",
	
	
	# animals
	"Antelopes",
	"Armadillos",
	"Badgers",
	"Bats",
	"Bears",
	"Cats",
	"Cheetahs",
	"Cobras",
	"Crocodiles",
	"Dogs",
	"Dolphins",
	"Eagles",
	"Elephants",
	"Foxes",
	"Frogs",
	"Geese",
	"Hares",
	"Hawks",
	"Herons",
	"Hippos",
	"Horses",
	"Hyenas",
	"Leopards",
	"Lions",
	"Lizards",
	"Mooses",
	"Orcas",
	"Ostritches",
	"Owls",
	"Pandas",
	"Panthers",
	"Pelicans",
	"Pigeons",
	"Pigs",
	"Rabbits",
	"Ravens",
	"Rhinos",
	"Rodents",
	"Salamanders",
	"Sharks",
	"Skunks",
	"Snails",
	"Snakes",
	"Stallions",
	"Tigers",
	"Turtles",
	"Whales",
	"Zebras",
	
	# fantastic creatures
	"Angels",
	"Centaurs",
	"Demons",
	"Devils",
	"Dragons",
	"Drakes",
	"Griffins",
	"Serpents",
	"Spirits",
	"Unicorns",
	"Wyverns",
	
	# weapons
	"Battleaxes",
	"Blades",
	"Daggers",
	"Fists",
	"Halberds",
	"Hatchets",
	"Lances",
	"Pikes",
	"Sabers",
	"Scythes",
	"Shields",
	"Sickles",
	"Spears",
	"Staves",
	"Swords",
	"Warhammers",
	
	# clothing
	"Cloaks",
	"Hoods",
	"Robes",
	"Tabards",
	"Mails",
	"Gauntlets",
	"Boots",

	# occupation
	"Knights",
	"Monks",
	"Riders",
	"Scouts",
	"Rangers",
	"Warriors",
	"Pawns",
	"Kings",
	"Queens",
	"Rooks",
	"Bishops",
	"Brothers",
	"Sisters",
	"Children",
] # nouns

# print nouns

fc_str_adjectives.py:
Code:
adjectives=[
	# colors
	"White",
	"Beige",
	"Brown",
	"Tan",
	"Golden",
	"Gray",
	"Black",
	"Blue",
	"Azure",
	"Cyan",
	"Teal",
	"Green",
	"Pink",
	"Lavender",
	"Indigo",
	"Maroon",
	"Crimson",
	"Orange",
	"Yellow",
	
	# pseudo-colors
	"Bright",
	"Dark",
	"Glowing",
	"Dim",

	# materials
	"Ivory",
	"Bone",
	"Blood",
	"Wooden",
	"Oak",
	"Pine",
	"Copper",
	"Bronze",
	"Gold",
	"Silver",
	"Iron",
	"Steel",
	"Mithril",
	"Adamantite",
	"Crystal",
	"Obsidian",

	# elemental
	"Fire",
	"Fiery",
	"Icy",
	"Ice",
	"Frosty",
	
	
	# numeric
	"1st",
	"2nd",
	"3rd",
	"4th",
	"5th",
	"6th",
	"7th",
	"8th",
	"9th",
	"10th",

	# attributes
	"Quick",
	"Sharp",
	"Strong",
	"Nimble",
	"Mighty",
	"Wonderful",
	"Fantastic",
	"Amazing",
	"Silent",
	"Holy",
	"Unholy",
	"Joyful",
	"Merry",
	"Mystical",
	"Musical",
	"Heavenly",

	# actions
	"Raging",
	"Marching",
	"Flying",
	"Charging",
	"Swinging",
	"Singing",
	"Spitting",
	"Dancing",
	"Laughing",
	"Thundering",

] # adjectives


# print adjectives

This is the main strings file for the mod fc_strings.py, which is merged into module_strings.py using methods in previous examples:
Code:
# -*- coding: cp1254 -*-

strings = [
		# other strings snipped out	
]; # strings

	

# generate string entries from list of nouns
try:
  from fc_str_nouns import nouns as fc_nouns

  for i in range (0,len(fc_nouns)-1):
	strings.append(("fc_noun_%d"%(i), fc_nouns[i]))
  strings.append(("fc_noun_end", fc_nouns[0])) # marks end of list. use first value for dummy data
except:
  print "Error processing nouns"
  pass

# generate string entries from list of adjectives
try:
  from fc_str_adjectives import adjectives as fc_adjectives

  for i in range (0,len(fc_adjectives)-1):
	strings.append(("fc_adj_%d"%(i), fc_adjectives[i]))
  strings.append(("fc_adj_end", fc_adjectives[0])) # marks end of list. use first value for dummy data
except:
  print "Error processing adjectives"
  pass

The following is in the constants file fc_constants.py which marks the range for the nouns and adjectives so that it can be used in-game.
snippet from fc_constants.py:
Code:
fc_nouns_begin = "str_fc_noun_0"
fc_nouns_end = "str_fc_noun_end"
fc_adjectives_begin = "str_fc_adj_0"
fc_adjectives_end = "str_fc_adj_end"

The following are snippets from two of my scripts which generate random names for parties, while keeping track of the original references to the components (adjectives/nouns) which will remain persistent as long as the order in fc_str_nouns.py and fc_str_adjectives.py are not messed up (adding to end of the lists there will not affect the order).
Lines which uses the nouns and adjectives are highlighted in blue
  ############################################################################
  # Force Commander Utility script to update a party's name based on noun, adjective and template slot values
  # Input: arg1 = party_no
  # Output:
  ############################################################################
("fc_party_update_name",
  [
(store_script_param_1, ":party"),

(party_get_slot, ":name_template", ":party", slot_fc_party_name_template),
(party_get_slot, ":adj", ":party", slot_fc_party_name_adjective),
(party_get_slot, ":noun", ":party", slot_fc_party_name_noun),



(try_begin),
(ge, ":name_template", 0),
(ge, ":adj", 0),
(ge, ":noun", 0),
# add string offset
(val_add, ":name_template", fc_partynametemplates_begin),
(val_add, ":adj", fc_adjectives_begin),
(val_add, ":noun", fc_nouns_begin),


(party_stack_get_troop_id, ":party_leader", ":party",0), # assume stack 0 is leader
(try_begin),
(ge, ":party_leader", 0),
(str_store_troop_name, s10, ":party_leader"),
(else_try),
(str_store_troop_name, s10, "str_fc_party_name_default_leader_name"), # default if leader not available.
(try_end),

(party_get_slot, ":home", ":party", slot_fc_party_home),
(try_begin),
(ge, ":home", 0), # 0 is player party.
(is_between, ":home", centers_begin, centers_end),
(str_store_party_name, s13, ":home"),
(else_try),
(str_store_string, s13, "str_fc_party_name_default_home_name"), # default if home not available.
(try_end),

(str_store_string, s11, ":adj"),
(str_store_string, s12, ":noun"),


(party_set_name, ":party", ":name_template"),
(else_try),
# no name parts found.  currently do nothing
(try_end),

]),  # fc_party_update_name

 
  ############################################################################
  # Force Commander Utility script to generate a random party name with an adjective and a noun
  # Input: arg1 = party_no
  # Output:
  ############################################################################
("fc_party_set_random_name",
  [
    (store_script_param_1, ":party"),

(store_random_in_range, ":adj", fc_adjectives_begin, fc_adjectives_end),
(val_sub, ":adj", fc_adjectives_begin), # correct offset
(store_random_in_range, ":noun", fc_nouns_begin, fc_nouns_end),
(val_sub, ":noun", fc_nouns_begin), # correct offset


(party_stack_get_troop_id, ":party_leader", ":party",0), # assume stack 0 is leader

(try_begin), # if leader is hero, party has a chance of having hero's name
(troop_is_hero, ":party_leader"),
(store_random_in_range, ":name_template", fc_partynametemplates_begin, fc_partynametemplates_end),
(else_try),
(store_random_in_range, ":name_template", fc_partynametemplates_wo_leader_begin, fc_partynametemplates_wo_leader_end),
(try_end),
(val_sub, ":name_template", fc_partynametemplates_begin), # correct offset

(party_set_slot, ":party", slot_fc_party_name_noun, ":noun"),
(party_set_slot, ":party", slot_fc_party_name_adjective, ":adj"),

(party_set_slot, ":party", slot_fc_party_name_template, ":name_template"),

(call_script, "script_fc_party_update_name", ":party"),
]),  # fc_party_set_random_name


Immediate Improvement Possibility

One possibility for mod files to cooperate could be some standardized function defined to merge in the modded content into main files.  For example, can dictate something like a modmerge() function in all modded files that will merge changes into the main file content by calling it, while abstracting what it does inside (could be adding in stuff, replacing stuff, or even complex things like modifying values of original records without adding/removing any actual list entries)


e.g. In the main module files (at the bottom):
Code:
# force_commander_start
# The following segment is to be inserted into module_items.py.  Insert it at the bottom and uncomment the whole block
# Can be used for other mod scripts as well, just change the first "fc_items" in the first line "from fc_items ..." the mod items file.
try:
  from fc_items import modmerge as fc_modmerge
  fc_modmerge(items)
except:
  print "Error importing from fc_items"
  pass
# force_commander_end

The actual merging process is now moved to fc_items.py which be updated without affecting module_items.py
Code:
#this is the items list defined in mod
items=[
	# blah blah blah
]

def modmerge(orig_items):
  item_end = orig_items.pop() # temporary remove item_end
  
  check_duplicates = false # set to true if there are replacements to original entries, false will be more efficient
  
  if( not check_duplicates ):
	orig_items.extend(items) # Use this only if there are no replacements (i.e. no duplicated item names)
  else:
	  # Use the following loop to replace existing entries with same id
	  for i in range (0,len(items)-1):
		find_index = find_object(orig_items, items[i][0]); # find_object is from header_common.py
		if( find_index == -1 ):
			orig_items.append(fc_items[i])
		else:
			orig_items[find_index] = items[i]
  
  orig_items.append(item_end) # push back item_end



Finally

Though this is not complete, and far from perfect, I hope that it could be the beginning of something good for the modding community.  Any suggestions for improvements will be welcome.  You are also more than welcome to use any ideas presented here for your own mods, or work towards a framework for mods to cooperate.

regards
 
It's really easy to merge small specific mods into any existing one, all you need is ok naming convention and some comments to understand the flow. So standardizing is just not worth the effort for scripters. And they do not care much about unwashed crowd being able to merge mods conveniently anyway :smile:

sphere said:
This is to cater for cases where a mod may want to REPLACE an existing script in the original module_scripts.py (not common, but sometimes it happens for heavy mods). 
It's actually pretty common, or even necessary for every large mod out there.
 
It's not just for merging... it's also for updating stuff like game updates (which updates the module system raws) + individual mod updates.

Just for simple example: I've just updated to 1127 warband.  This is the sum of work I did:

1) Make a copy of Native to Native_Custom
2) Unzip the downloaded module system 1127 archive into Native_Custom/source directory
3) Copy my own scripts (none having same filenames as native) into Native_Custom/source
4) Open affected module_???.py and paste a short code segment at the bottom which copied from the headers in my scripts (This can be automated quite easily as well). i.e. there is no need to fumble through the original file content at all. (even for insertions, it is handled by script code in my script files, so it is not done by hand)
5) edit module_info.py to point to parent directory

then build module.

How much work do other people do to update their mods with the game? (If there are more efficient methods, I'll gladly give up my method)

And they do not care much about unwashed crowd being able to merge mods conveniently anyway
Frankly speaking, I agree totally. But then, sometimes I  would like to have standard stuff like maybe item mod packages that can be easily merged into my own mod.  If the item mods are done properly (no conflicting file names etc), merging them to work with new game version will be a breeze.  Also will be easy to merge any latest updates.

Still, its my view.  I'll argue for the sake of hoping that something useful come out (for me and then the rest, in that order :wink: ), but not if it is just a difference of perspectives and values.
 
No thanks.  I'd rather I'm the only person who can modify my mod.  This would lead to tons of people "creating" new mods that aren't actual new mods. 
 
Thanks for the tips. This should be useful to modders to keep things organized and port to new versions easier.
 
Eh... to make any serious mod, you've generally got to rewrite giant sections of the Taleworlds code.  It's not just a matter of "insert here". 

This kind of approach would work for mini-mods where a one-liner include could bring their code into some Taleworlds code, but it simply won't work for big projects.  I certainly don't mind the idea of stuff being designed so that coder nubs can just select some includes and voila, have a working mod, but don't kid yourself- that's as far as it can go.

The bigger problem, from where I sit, is all these "mini" projects that are giant and full of features that aren't necessarily what I'd want to use, but aren't designed for easy removal / modification of their features.  My general rule about looking at OSP snippets is that anything that's over 300 lines, I pretty much just ignore, because it's probably more bother to fix it to match my gameplay requirements than to code from scratch.
 
Back
Top Bottom