WeaponScriptToWiki.py
import os import re
- Utility functions for conversions
def kg_to_g(kg):
return round(kg * 1000, 2)
def kg_to_grains(kg):
return round(kg * 15432.36, 2)
def kg_to_lbs(kg):
return round(kg * 2.20462, 2)
- Load the template file
def load_template(template_path):
with open(template_path, "r", encoding="utf-8") as f:
return f.read()
- Parse key-value pairs from script file (Source-style weapon script)
def parse_file(file_path):
variables = {}
with open(file_path, "r", encoding="utf-8") as f:
for line in f:
match = re.match(r'\s*"(.+?)"\s+"(.+?)"', line)
if match:
key, value = match.groups()
variables[key.lower()] = value
return variables
- Generic lookup from vietnam_english.txt, stripping any leading '#'
def lookup_english_token(token_key):
file_path = r"C:\Program Files (x86)\Steam\steamapps\common\Military Conflict - Vietnam\vietnam\resource\vietnam_english.txt"
key = token_key.lstrip('#')
encodings = ["utf-8", "utf-16", "latin-1"]
for enc in encodings:
try:
with open(file_path, "r", encoding=enc) as f:
for line in f:
line = line.strip()
m = re.match(r'^\s*"(.+?)"\s+"(.+?)"\s*$', line)
if m:
k, v = m.groups()
if k.lower() == key.lower():
return v
except UnicodeDecodeError:
continue
return None
- Retrieve the printnameenglish value (kept for existing behavior)
def get_printname_english(printname):
return lookup_english_token(printname)
- Get the origin value and process it
def get_origin(origin):
origin = origin.lstrip('#').replace("_", " ").title()
return origin
- -------------------------
- Loadout parsing utilities
- -------------------------
CLASSES = {"assault", "medic", "gunner", "sniper", "engineer", "radioman"} FACTIONS = {"US", "VC"}
def parse_vietnam_loadout(loadout_path):
"""
Parse a KeyValues-style loadout and build a mapping:
weapon_name -> {"factions": set(...), "classes": set(...)}
We only consider weapons listed under the "weapons" tree.
"""
mapping = {}
if not os.path.exists(loadout_path):
return mapping
pending_key = None stack = [] cur_faction = None cur_class = None in_weapons_block = False
def push(key):
nonlocal cur_faction, cur_class, in_weapons_block
stack.append(key)
if key == "weapons":
in_weapons_block = True
if key in FACTIONS:
cur_faction = key
if key in CLASSES:
cur_class = key
def pop():
nonlocal cur_faction, cur_class, in_weapons_block
if not stack:
return
key = stack.pop()
if key in FACTIONS and cur_faction == key:
cur_faction = None
if key in CLASSES and cur_class == key:
cur_class = None
if key == "weapons" and "weapons" not in stack:
in_weapons_block = False
with open(loadout_path, "r", encoding="utf-8", errors="ignore") as f:
for raw in f:
line = raw.strip()
# Strip comments
if "//" in line:
line = line[: line.index("//")].rstrip()
if not line:
continue
m_key_only = re.fullmatch(r'"([^"]+)"', line)
if m_key_only:
pending_key = m_key_only.group(1)
continue
if line == "{":
if pending_key is not None:
push(pending_key)
pending_key = None
else:
push("__anon__")
continue
if line == "}":
pop()
continue
m_pair = re.fullmatch(r'"([^"]+)"\s+"([^"]*)"', line)
if m_pair and in_weapons_block and cur_faction and cur_class:
key = m_pair.group(1)
if key.startswith("weapon_"):
w = mapping.setdefault(key, {"factions": set(), "classes": set()})
w["factions"].add(cur_faction)
w["classes"].add(cur_class)
continue
m_inline_open = re.fullmatch(r'"([^"]+)"\s*\{', line)
if m_inline_open:
push(m_inline_open.group(1))
continue
return mapping
def file_contains_weapon(path, weapon_key):
"""
Simple presence check for a weapon key in a text file.
Uses a word-boundary regex to reduce false positives.
"""
if not os.path.exists(path):
return False
try:
with open(path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
pattern = r'\b' + re.escape(weapon_key) + r'\b'
return re.search(pattern, content) is not None
except Exception:
return False
def preprocess_calculations(variables, file_name, folder_path, loadout_main, loadout_zombie, loadout_special, paths):
calculations = {}
damage_generic = float(variables.get("damagegeneric", "0"))
damage_head_multiplier = float(variables.get("damageheadmultiplier", "1"))
damage_chest_multiplier = float(variables.get("damagechestmultiplier", "1"))
damage_stomach_multiplier = float(variables.get("damagestomachmultiplier", "1"))
damage_leg_multiplier = float(variables.get("damagelegmultiplier", "1"))
damage_arm_multiplier = float(variables.get("damagearmmultiplier", "1"))
if damage_generic > 0:
calculations["damagegenericxdamageheadmultiplier"] = round(damage_generic * damage_head_multiplier, 2)
calculations["damagegenericxdamagechestmultiplier"] = round(damage_generic * damage_chest_multiplier, 2)
calculations["damagegenericxdamagestomachmultiplier"] = round(damage_generic * damage_stomach_multiplier, 2)
calculations["damagegenericxdamagelegmultiplier"] = round(damage_generic * damage_leg_multiplier, 2)
calculations["damagegenericxdamagearmmultiplier"] = round(damage_generic * damage_arm_multiplier, 2)
else:
calculations["damagegenericxdamageheadmultiplier"] = "N/A"
calculations["damagegenericxdamagechestmultiplier"] = "N/A"
calculations["damagegenericxdamagestomachmultiplier"] = "N/A"
calculations["damagegenericxdamagelegmultiplier"] = "N/A"
calculations["damagegenericxdamagearmultiplier"] = "N/A"
has_bayonet = variables.get("hasbayonet", "0") == "1"
calculations["hasbayonet"] = "YES" if has_bayonet else "NO"
file_base_name = os.path.splitext(file_name)[0]
rifle_grenade_file = os.path.join(folder_path, f"{file_base_name}_riflegrenade.txt")
has_rifle_grenade = os.path.exists(rifle_grenade_file)
calculations["hasriflegrenade"] = "YES" if has_rifle_grenade else "NO"
bullet_weight = float(variables.get("bullet_weight", "0"))
weight = float(variables.get("weight", "0"))
calculations["bullet_weight"] = kg_to_g(bullet_weight) calculations["bullet_weightingr"] = kg_to_grains(bullet_weight) calculations["weightinlbs"] = kg_to_lbs(weight)
calculations["extrabulletchamber"] = variables.get("extrabulletchamber", "0")
calculations["filename"] = file_base_name
printname = variables.get("printname", "").lstrip("#")
printname_english = get_printname_english(printname)
calculations["printnameenglish"] = printname_english if printname_english else printname
origin = variables.get("origin", "")
calculations["origin"] = get_origin(origin)
# Caliber / primary_ammo via ammo_id_display lookup
ammo_id_display = variables.get("ammo_id_display", "").strip()
ammo_display_name = None
if ammo_id_display:
ammo_display_name = lookup_english_token(ammo_id_display)
if ammo_display_name:
calculations["primary_ammo"] = ammo_display_name
calculations["caliber"] = ammo_display_name
else:
calculations["primary_ammo"] = variables.get("primary_ammo", "N/A")
calculations["caliber"] = variables.get("primary_ammo", "N/A")
# Presence in various files in_main = file_base_name in loadout_main in_zombie = file_base_name in loadout_zombie in_special = file_base_name in loadout_special in_gamemodes = file_contains_weapon(paths["gamemodes_path"], file_base_name)
# Faction logic: prefer main; else zombie; else special
factions_set = set()
classes_list = []
if in_main:
factions_set = set(loadout_main[file_base_name]["factions"])
classes_list = list(loadout_main[file_base_name]["classes"])
elif in_zombie:
factions_set = set(loadout_zombie[file_base_name]["factions"])
classes_list = [] # replace class block for non-main
elif in_special:
factions_set = set(loadout_special[file_base_name]["factions"])
classes_list = [] # replace class block for non-main
# faction string for placeholder
if len(factions_set) == 1:
calculations["faction"] = next(iter(factions_set))
elif len(factions_set) > 1:
calculations["faction"] = "/".join(sorted(factions_set))
else:
calculations["faction"] = "N/A"
calculations["factions_set"] = factions_set calculations["classes_list"] = classes_list calculations["in_main_loadout"] = in_main calculations["in_zombie_loadout"] = in_zombie calculations["in_special_loadout"] = in_special calculations["in_gamemodes"] = in_gamemodes
# Build category lines if not in main loadout
if not in_main:
category_lines = []
if in_gamemodes:
category_lines.append('Gun Game
')
if in_zombie:
category_lines.append('Zombies
')
if in_special:
category_lines.append('Special Loadout
')
calculations["category_lines"] = "".join(category_lines)
else:
calculations["category_lines"] = ""
return calculations
- Case-sensitive mapping for image filenames by class, and display labels
CLASS_IMAGE_MAP = {
"assault": "Class_Assault.png", "medic": "Class_medic.png", "gunner": "Class_Gunner.png", "sniper": "Class_sniper.png", "engineer": "Class_Engineer.png", "radioman": "Class_radioman.png",
}
- Enforced output order
CLASS_ORDER = ["assault", "medic", "gunner", "sniper", "engineer", "radioman"]
def build_classes_markup(classes_list):
if not classes_list:
return 'File:Class unknown.png unknown
'
parts = []
present = set(classes_list)
for cls in CLASS_ORDER:
if cls in present:
img = CLASS_IMAGE_MAP.get(cls, f"Class_{cls}.png")
label = cls.capitalize()
parts.append(f'[[File:{img}|50px]] [[{label}]]
')
return "".join(parts)
def replace_placeholders(template, variables, calculations):
result = template
classes_pattern = r'\[\[File:Class_""class""\.png\|50px\]\] \[\[""class""\]\]
'
# If not in main loadout and we have categories, replace with category lines;
# otherwise, render the ordered classes markup.
if (not calculations.get("in_main_loadout", False)) and calculations.get("category_lines"):
result = re.sub(classes_pattern, calculations["category_lines"], result)
else:
result = re.sub(classes_pattern, build_classes_markup(calculations.get("classes_list", [])), result)
for placeholder in re.findall(r'""([^"]+)""', result):
key_lower = placeholder.lower()
if key_lower == "class":
continue
if key_lower == "filename":
value = calculations["filename"]
elif key_lower == "bullet_weightingr":
value = str(calculations["bullet_weightingr"])
elif key_lower == "extrabulletchamber":
extra_bullet_chamber = calculations.get("extrabulletchamber", "0")
value = "" if extra_bullet_chamber == "0" else "[[]]"
else:
value = calculations.get(key_lower, variables.get(key_lower, "N/A"))
result = result.replace(f'""{placeholder}""', str(value))
# Handle ammo column +/- marker
result = re.sub(
r"(\d+)/(\d+)(\[\[\]\])?",
lambda m: m.group(1)
+ (
f"[[+{calculations['extrabulletchamber']}]]"
if calculations["extrabulletchamber"] == "2"
else f"+1" if calculations["extrabulletchamber"] == "1" else ""
)
+ "/"
+ m.group(2),
result,
)
# Remove faction flags that don't apply
factions_set = calculations.get("factions_set", set())
if "US" not in factions_set:
result = result.replace('
', )
if "VC" not in factions_set:
result = result.replace('
', )
return result
def process_files(template, scripts_path, output_path, loadout_main, loadout_zombie, loadout_special, paths):
for root, _, files in os.walk(scripts_path):
for file in files:
if not (file.endswith(".txt") and file.startswith("weapon_")):
continue
input_file = os.path.join(root, file)
file_name = os.path.basename(file)
variables = parse_file(input_file)
calculations = preprocess_calculations(variables, file_name, root, loadout_main, loadout_zombie, loadout_special, paths)
processed_template = replace_placeholders(template, variables, calculations)
output_file = os.path.join(output_path, file_name)
with open(output_file, "w", encoding="utf-8") as f:
f.write(processed_template)
- Paths (edit as needed)
template_path = r"C:\MCV_PROJECTS\wiki\WikiTemplate.txt" scripts_path = r"C:\Program Files (x86)\Steam\steamapps\common\Military Conflict - Vietnam\vietnam\scripts" output_path = r"C:\MCV_PROJECTS\wiki\output" loadout_path = r"C:\Program Files (x86)\Steam\steamapps\common\Military Conflict - Vietnam\vietnam\resource\vietnam_loadout.txt" zombie_loadout_path = r"C:\Program Files (x86)\Steam\steamapps\common\Military Conflict - Vietnam\vietnam\resource\vietnam_loadout_zombie.txt" special_loadout_path = r"C:\Program Files (x86)\Steam\steamapps\common\Military Conflict - Vietnam\vietnam\resource\vietnam_loadout_special.txt" gamemodes_path = r"C:\Program Files (x86)\Steam\steamapps\common\Military Conflict - Vietnam\vietnam\gamemodes.txt"
os.makedirs(output_path, exist_ok=True)
template = load_template(template_path) loadout_main = parse_vietnam_loadout(loadout_path) loadout_zombie = parse_vietnam_loadout(zombie_loadout_path) loadout_special = parse_vietnam_loadout(special_loadout_path)
paths = {
"special_loadout_path": special_loadout_path, "gamemodes_path": gamemodes_path,
}
process_files(template, scripts_path, output_path, loadout_main, loadout_zombie, loadout_special, paths)
def remove_hash_from_files(directory):
if not os.path.exists(directory):
print(f"The directory '{directory}' does not exist.")
return
for filename in os.listdir(directory):
file_path = os.path.join(directory, filename)
if os.path.isfile(file_path) and filename.endswith('.txt'):
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
updated_content = content.replace('#', )
with open(file_path, 'w', encoding='utf-8') as file:
file.write(updated_content)
print(f"Processed file: {filename}")
except Exception as e:
print(f"Error processing file {filename}: {e}")
directory = output_path remove_hash_from_files(directory) print("Processing complete.")