WeaponScriptToWiki.py: Difference between revisions

Jump to navigation Jump to search
Created page with "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 def parse_file(file_path): variables = {} with open(file_path, "r"..."
 
No edit summary
 
Line 17: Line 17:
         return f.read()
         return f.read()


# Parse key-value pairs from script file
# Parse key-value pairs from script file (Source-style weapon script)
def parse_file(file_path):
def parse_file(file_path):
     variables = {}
     variables = {}
Line 28: Line 28:
     return variables
     return variables


# Retrieve the printnameenglish value from the vietnam_english.txt file
# Generic lookup from vietnam_english.txt, stripping any leading '#'
def get_printname_english(printname):
def lookup_english_token(token_key):
     file_path = r"C:\Program Files (x86)\Steam\steamapps\common\Military Conflict - Vietnam\vietnam\resource\vietnam_english.txt"
     file_path = r"C:\Program Files (x86)\Steam\steamapps\common\Military Conflict - Vietnam\vietnam\resource\vietnam_english.txt"
      
     key = token_key.lstrip('#')
    # Remove the leading "#" from printname
    printname = printname.lstrip('#')
 
    # Try opening the file with different encodings
     encodings = ["utf-8", "utf-16", "latin-1"]
     encodings = ["utf-8", "utf-16", "latin-1"]
     for enc in encodings:
     for enc in encodings:
Line 42: Line 38:
                 for line in f:
                 for line in f:
                     line = line.strip()
                     line = line.strip()
                     # Ensure the line is in the format we expect (e.g. "weapon_ammobox" "Ammunition Box")
                     m = re.match(r'^\s*"(.+?)"\s+"(.+?)"\s*$', line)
                    match = re.match(r'^\s*"(.+?)"\s+"(.+?)"\s*$', line)
                     if m:
                     if match:
                         k, v = m.groups()
                         key, value = match.groups()
                         if k.lower() == key.lower():
                        # If the key matches the printname, return the corresponding value
                             return v
                         if key.lower() == printname.lower():
                             return value
         except UnicodeDecodeError:
         except UnicodeDecodeError:
             continue # Try the next encoding if decoding fails
             continue
    print(f"Error: Unable to decode the file or find the key '{printname}'")
     return None
     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
# Get the origin value and process it
def get_origin(origin):
def get_origin(origin):
    # Remove leading "#" and any other unwanted characters
     origin = origin.lstrip('#').replace("_", " ").title()
     origin = origin.lstrip('#').replace("_", " ").title()
     return origin
     return origin


# Preprocess calculations for the template
# -------------------------
def preprocess_calculations(variables, file_name, folder_path):
# 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 = {}
     calculations = {}


    # Retrieve damage-related values (set default to 0 if not found)
     damage_generic = float(variables.get("damagegeneric", "0"))
     damage_generic = float(variables.get("damagegeneric", "0"))
     damage_head_multiplier = float(variables.get("damageheadmultiplier", "1"))
     damage_head_multiplier = float(variables.get("damageheadmultiplier", "1"))
Line 72: Line 170:
     damage_arm_multiplier = float(variables.get("damagearmmultiplier", "1"))
     damage_arm_multiplier = float(variables.get("damagearmmultiplier", "1"))


    # Calculate damage multipliers (only if damage_generic is greater than 0)
     if damage_generic > 0:
     if damage_generic > 0:
         calculations["damagegenericxdamageheadmultiplier"] = round(damage_generic * damage_head_multiplier, 2)
         calculations["damagegenericxdamageheadmultiplier"] = round(damage_generic * damage_head_multiplier, 2)
Line 84: Line 181:
         calculations["damagegenericxdamagestomachmultiplier"] = "N/A"
         calculations["damagegenericxdamagestomachmultiplier"] = "N/A"
         calculations["damagegenericxdamagelegmultiplier"] = "N/A"
         calculations["damagegenericxdamagelegmultiplier"] = "N/A"
         calculations["damagegenericxdamagearmmultiplier"] = "N/A"
         calculations["damagegenericxdamagearmultiplier"] = "N/A"


    # Bayonet check
     has_bayonet = variables.get("hasbayonet", "0") == "1"
     has_bayonet = variables.get("hasbayonet", "0") == "1"
     calculations["hasbayonet"] = "YES" if has_bayonet else "NO"
     calculations["hasbayonet"] = "YES" if has_bayonet else "NO"


    # Rifle grenade file check
     file_base_name = os.path.splitext(file_name)[0]
     file_base_name = os.path.splitext(file_name)[0]
     rifle_grenade_file = os.path.join(folder_path, f"{file_base_name}_riflegrenade.txt")
     rifle_grenade_file = os.path.join(folder_path, f"{file_base_name}_riflegrenade.txt")
Line 96: Line 191:
     calculations["hasriflegrenade"] = "YES" if has_rifle_grenade else "NO"
     calculations["hasriflegrenade"] = "YES" if has_rifle_grenade else "NO"


    # Weight conversions
     bullet_weight = float(variables.get("bullet_weight", "0"))
     bullet_weight = float(variables.get("bullet_weight", "0"))
     weight = float(variables.get("weight", "0"))
     weight = float(variables.get("weight", "0"))
    print(f"DEBUG: Bullet weight in kg: {bullet_weight}")  # Debug print


     calculations["bullet_weight"] = kg_to_g(bullet_weight)
     calculations["bullet_weight"] = kg_to_g(bullet_weight)
     calculations["bullet_weightingr"] = kg_to_grains(bullet_weight) # Ensure conversion to grains
     calculations["bullet_weightingr"] = kg_to_grains(bullet_weight)
     calculations["weightinlbs"] = kg_to_lbs(weight)
     calculations["weightinlbs"] = kg_to_lbs(weight)


    # ExtraBulletChamber check
     calculations["extrabulletchamber"] = variables.get("extrabulletchamber", "0")
     calculations["extrabulletchamber"] = variables.get("extrabulletchamber", "0")


    # File name replacement (ensure base name without extension)
     calculations["filename"] = file_base_name
     calculations["filename"] = file_base_name


    # Get the printname and replace with the appropriate english translation if found
     printname = variables.get("printname", "").lstrip("#")
     printname = variables.get("printname", "").lstrip("#") # Strip the leading "#" here as well
     printname_english = get_printname_english(printname)
     printname_english = get_printname_english(printname)
     if printname_english:
     calculations["printnameenglish"] = printname_english if printname_english else printname
        calculations["printnameenglish"] = printname_english
    else:
        calculations["printnameenglish"] = printname


    # Get the origin and process it
     origin = variables.get("origin", "")
     origin = variables.get("origin", "")
     calculations["origin"] = get_origin(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]]<br>')
        if in_zombie:
            category_lines.append('[[Zombies]]<br>')
        if in_special:
            category_lines.append('[[Special Loadout]]<br>')
        calculations["category_lines"] = "".join(category_lines)
    else:
        calculations["category_lines"] = ""


     return calculations
     return calculations


# Replace placeholders in the template
# 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|50px]] <b>[[unknown]]</b><br>'
    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]] <b>[[{label}]]</b><br>')
    return "".join(parts)
 
def replace_placeholders(template, variables, calculations):
def replace_placeholders(template, variables, calculations):
     result = template
     result = template


     # Replace placeholders with variables or calculations
    classes_pattern = r'\[\[File:Class_""class""\.png\|50px\]\] <b>\[\[""class""\]\]</b><br>'
 
     # 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):
     for placeholder in re.findall(r'""([^"]+)""', result):
         # Check if placeholder is "FILENAME" and replace it with the file base name
         key_lower = placeholder.lower()
         if placeholder.lower() == "filename":
        if key_lower == "class":
            continue
 
         if key_lower == "filename":
             value = calculations["filename"]
             value = calculations["filename"]
         elif placeholder.lower() == "bullet_weightingr": # Ensure correct grains conversion handling
         elif key_lower == "bullet_weightingr":
             value = str(calculations["bullet_weightingr"]) # Correct grains replacement
             value = str(calculations["bullet_weightingr"])
         elif placeholder.lower() == "extrabulletchamber":
         elif key_lower == "extrabulletchamber":
            # Only add [[]] if extra_bullet_chamber is not "0"
             extra_bullet_chamber = calculations.get("extrabulletchamber", "0")
             extra_bullet_chamber = calculations.get("extrabulletchamber", "0")
             if extra_bullet_chamber == "0":
             value = "" if extra_bullet_chamber == "0" else "[[]]"
                value = ""  # Remove the [[]] if the chamber is not present
            else:
                value = "[[]]" # Keep the [[]] if ExtraBulletChamber is set
         else:
         else:
             value = calculations.get(placeholder.lower(), variables.get(placeholder.lower(), "N/A"))
             value = calculations.get(key_lower, variables.get(key_lower, "N/A"))
        # Replace the placeholder correctly (ensuring the exact match with quotes)
         result = result.replace(f'""{placeholder}""', str(value))
         result = result.replace(f'""{placeholder}""', str(value))


     # Handle ammo column, ensuring the proper format (no extra [[]] and correct ammo count)
     # Handle ammo column +/- marker
     result = re.sub(r"(\d+)/(\d+)(\[\[\]\])?", lambda m: m.group(1) + (
     result = re.sub(
        f"[[+{calculations['extrabulletchamber']}]]" if calculations["extrabulletchamber"] == "2" else
        r"(\d+)/(\d+)(\[\[\]\])?",
        f"[[+1]]" if calculations["extrabulletchamber"] == "1" else "")  
        lambda m: m.group(1)
         + "/" + m.group(2), result)
        + (
            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('[[File:Flag_us_new.png|50px]]', '')
    if "VC" not in factions_set:
        result = result.replace('[[File:Flag_vc_new.png|50px]]', '')


     return result
     return result


# Process all script files
def process_files(template, scripts_path, output_path, loadout_main, loadout_zombie, loadout_special, paths):
def process_files(template, scripts_path, output_path):
     for root, _, files in os.walk(scripts_path):
     for root, _, files in os.walk(scripts_path):
         for file in files:
         for file in files:
             if not file.endswith(".txt"):
             if not (file.endswith(".txt") and file.startswith("weapon_")):
                 continue
                 continue


Line 166: Line 355:
             file_name = os.path.basename(file)
             file_name = os.path.basename(file)
             variables = parse_file(input_file)
             variables = parse_file(input_file)
             calculations = preprocess_calculations(variables, file_name, root)
             calculations = preprocess_calculations(variables, file_name, root, loadout_main, loadout_zombie, loadout_special, paths)
             processed_template = replace_placeholders(template, variables, calculations)
             processed_template = replace_placeholders(template, variables, calculations)


            # Save processed template to output
             output_file = os.path.join(output_path, file_name)
             output_file = os.path.join(output_path, file_name)
             with open(output_file, "w", encoding="utf-8") as f:
             with open(output_file, "w", encoding="utf-8") as f:
                 f.write(processed_template)
                 f.write(processed_template)


# Paths
# Paths (edit as needed)
template_path = r"C:\MCV\wiki\WikiTemplate.txt"
template_path = r"C:\MCV_PROJECTS\wiki\WikiTemplate.txt"
scripts_path = r"C:\Program Files (x86)\Steam\steamapps\common\Military Conflict - Vietnam\vietnam\scripts"
scripts_path = r"C:\Program Files (x86)\Steam\steamapps\common\Military Conflict - Vietnam\vietnam\scripts"
output_path = r"C:\MCV\wiki\output"
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)
os.makedirs(output_path, exist_ok=True)


# Main execution
template = load_template(template_path)
template = load_template(template_path)
process_files(template, scripts_path, output_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):
def remove_hash_from_files(directory):
    # Check if the directory exists
     if not os.path.exists(directory):
     if not os.path.exists(directory):
         print(f"The directory '{directory}' does not exist.")
         print(f"The directory '{directory}' does not exist.")
         return
         return
   
    # Iterate through all files in the directory
     for filename in os.listdir(directory):
     for filename in os.listdir(directory):
        # Full path of the file
         file_path = os.path.join(directory, filename)
         file_path = os.path.join(directory, filename)
       
        # Process only text files
         if os.path.isfile(file_path) and filename.endswith('.txt'):
         if os.path.isfile(file_path) and filename.endswith('.txt'):
             try:
             try:
                # Read the file contents
                 with open(file_path, 'r', encoding='utf-8') as file:
                 with open(file_path, 'r', encoding='utf-8') as file:
                     content = file.read()
                     content = file.read()
               
                # Remove '#' characters
                 updated_content = content.replace('#', '')
                 updated_content = content.replace('#', '')
               
                # Write the updated content back to the file
                 with open(file_path, 'w', encoding='utf-8') as file:
                 with open(file_path, 'w', encoding='utf-8') as file:
                     file.write(updated_content)
                     file.write(updated_content)
               
                 print(f"Processed file: {filename}")
                 print(f"Processed file: {filename}")
             except Exception as e:
             except Exception as e:
                 print(f"Error processing file {filename}: {e}")
                 print(f"Error processing file {filename}: {e}")


# Define the directory
directory = output_path
directory = r"C:\MCV\wiki\output"
 
# Call the function
remove_hash_from_files(directory)
remove_hash_from_files(directory)
print("Processing complete.")
print("Processing complete.")

Latest revision as of 00:50, 3 September 2025

import os import re

  1. 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)
  1. Load the template file

def load_template(template_path):

   with open(template_path, "r", encoding="utf-8") as f:
       return f.read()
  1. 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
  1. 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
  1. Retrieve the printnameenglish value (kept for existing behavior)

def get_printname_english(printname):

   return lookup_english_token(printname)
  1. Get the origin value and process it

def get_origin(origin):

   origin = origin.lstrip('#').replace("_", " ").title()
   return origin
  1. -------------------------
  2. Loadout parsing utilities
  3. -------------------------

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
  1. 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",

}

  1. 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)
  1. 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.")