Files
test/Documentation/PYTHON_TOOL_STATUS.md
2026-03-13 16:03:47 +07:00

9.7 KiB

Python Skill Conversion Tool - Current Status & Fixes Needed

Tool Location

e:\Projects\convert_skills.py

Current Status

Working: Tool successfully converts C++ skill files to C# format Tested: Skill 390 converted successfully with no linter errors ⚠️ Issues: Some formatting issues need to be fixed (see below)

How to Use

Convert specific skills:

cd e:\Projects
python convert_skills.py 390,391,392,393

Convert all remaining skills:

cd e:\Projects
python convert_skills.py --all

Known Issues & Required Fixes

🔴 CRITICAL FIX 1: Calculate Method Formatting

Problem: Missing semicolons and improper spacing in Calculate method body

Current Output:

public void Calculate(Skill skill)
{
skill.GetPlayer ().SetDecmp (28)
skill.GetPlayer ().SetPray (1)
}

Should Be:

public void Calculate(Skill skill)
{
    skill.GetPlayer().SetDecmp(28);
    skill.GetPlayer().SetPray(1);
}

Fix Location: Line ~136-147 in generate_csharp_state method

Required Changes:

# Current code (BROKEN):
calculate_body = "\n            " + "\n            ".join(line.strip() for line in calc_content.split(';') if line.strip())

# Should be (FIXED):
lines = [line.strip() for line in calc_content.split(';') if line.strip()]
lines = [line + ';' if not line.endswith(';') else line for line in lines]
lines = [re.sub(r'\s+\(', '(', line) for line in lines]  # Remove spaces before (
calculate_body = "\n                " + "\n                ".join(lines)

🔴 CRITICAL FIX 2: Remove Spaces Before Parentheses

Problem: Spaces before () in method calls

Examples:

  • skill.GetPlayer () → Should be skill.GetPlayer()
  • skill.GetLevel () → Should be skill.GetLevel()
  • skill.GetVictim () → Should be skill.GetVictim()

Fix Location: Multiple places - add global regex replacement

Required Changes: Add this to ALL C++ → C# conversion sections:

# After any C++ to C# conversion, add:
content = re.sub(r'\s+\(', '(', content)

Specific locations to add:

  1. In generate_csharp_state method (line ~140)
  2. In method body conversions (line ~380-400)
  3. In StateAttack/BlessMe conversions (line ~420-450)

🟡 IMPORTANT FIX 3: Float Suffix Consistency

Problem: Not all float values have f suffix

Examples:

  • 0 in float method → Should be 0f
  • 1.8 → Should be 1.8f
  • 125 in GetMpcost → Should be 125f

Fix Location: Line ~350-360 in method generation

Required Changes:

# Current:
if method_info['return_type'] == 'float' and not value.endswith('f') and '.' not in value:
    value += 'f'

# Should be:
if method_info['return_type'] == 'float':
    # Check if it's a numeric value
    if re.match(r'^[\d.]+$', value.strip()) and not value.endswith('f'):
        value = value.strip() + 'f'
    # For expressions with parentheses, add f at the end
    elif '(' in value and not value.endswith('f'):
        value = value.strip() + 'f'

🟡 IMPORTANT FIX 4: Complex Expression Parsing

Problem: Complex expressions need better parsing

Example C++:

return (float) (skill->GetPlayer ()->GetRange () + 3 + 0.3 * skill->GetLevel ());

Current Output:

public float GetAttackdistance(Skill skill) => (float)(skill.GetPlayer ().GetRange () + 3 + 0.3 * skill.GetLevel ());

Should Be:

public float GetAttackdistance(Skill skill) => (float)(skill.GetPlayer().GetRange() + 3 + 0.3 * skill.GetLevel());

Fix: Apply space removal regex to all expressions

🟢 MINOR FIX 5: StateAttack/BlessMe Body Conversion

Problem: Complex method bodies need better formatting

Fix Location: Line ~420-450

Required Changes:

# Add after body conversion:
body_lines = []
for line in body.split('\n'):
    line = line.strip()
    if line and 'return' not in line:
        # Remove spaces before parentheses
        line = re.sub(r'\s+\(', '(', line)
        # Ensure semicolon at end
        if not line.endswith(';'):
            line += ';'
        body_lines.append(line)

Complete Fix Implementation

Here's the complete fixed version of the critical sections:

Fixed generate_csharp_state method (line ~90-150):

def generate_csharp_state(self, state_data: Dict) -> str:
    """Generate C# code for a state class."""
    state_num = state_data['num']
    body = state_data['body']
    
    # Parse all methods from state body
    methods = {}
    method_patterns = {
        'GetTime': r'int\s+GetTime\s*\([^)]*\)\s*const\s*\{[^}]*return\s+(\d+)',
        'Quit': r'bool\s+Quit\s*\([^)]*\)\s*const\s*\{[^}]*return\s+(false|true|\d+)',
        'Loop': r'bool\s+Loop\s*\([^)]*\)\s*const\s*\{[^}]*return\s+(false|true|\d+)',
        'Bypass': r'bool\s+Bypass\s*\([^)]*\)\s*const\s*\{[^}]*return\s+(false|true|\d+)',
        'Interrupt': r'bool\s+Interrupt\s*\([^)]*\)\s*const\s*\{[^}]*return\s+(false|true|\d+)',
        'Cancel': r'bool\s+Cancel\s*\([^)]*\)\s*const\s*\{[^}]*return\s+(false|true|\d+)',
        'Skip': r'bool\s+Skip\s*\([^)]*\)\s*const\s*\{[^}]*return\s+(false|true|\d+)',
    }
    
    for method_name, pattern in method_patterns.items():
        match = re.search(pattern, body, re.DOTALL)
        if match:
            value = match.group(1)
            if method_name != 'GetTime':
                if value == '0':
                    value = 'false'
                elif value == '1' or value == 'true':
                    value = 'true'
                elif value == 'false':
                    value = 'false'
            methods[method_name] = value
    
    # Extract Calculate method body
    calc_match = re.search(r'void\s+Calculate\s*\([^)]*\)\s*const\s*\{(.*?)\}', body, re.DOTALL)
    calculate_body = ""
    if calc_match:
        calc_content = calc_match.group(1).strip()
        if calc_content:
            # Convert C++ to C#
            calc_content = calc_content.replace('->', '.')
            # Remove spaces before parentheses
            calc_content = re.sub(r'\s+\(', '(', calc_content)
            # Split by semicolons and process each line
            lines = [line.strip() for line in calc_content.split(';') if line.strip()]
            # Add semicolons back
            lines = [line + ';' if not line.endswith(';') else line for line in lines]
            # Proper indentation
            calculate_body = "\n                " + "\n                ".join(lines)
    
    code = f"""#if SKILL_SERVER
        public class State{state_num} : SkillStub.State
        {{
            public int GetTime(Skill skill) => {methods.get('GetTime', '0')};
            public bool Quit(Skill skill) => {methods.get('Quit', 'false')};
            public bool Loop(Skill skill) => {methods.get('Loop', 'false')};
            public bool Bypass(Skill skill) => {methods.get('Bypass', 'false')};
            public void Calculate(Skill skill)
            {{{calculate_body if calculate_body else ' '}
            }}
            public bool Interrupt(Skill skill) => {methods.get('Interrupt', 'false')};
            public bool Cancel(Skill skill) => {methods.get('Cancel', 'false')};
            public bool Skip(Skill skill) => {methods.get('Skip', 'false')};
        }}
#endif
"""
    return code

Fixed method value processing (add around line ~350):

# For all method values, remove spaces before parentheses
value = re.sub(r'\s+\(', '(', value)

# For float methods, ensure f suffix
if method_info['return_type'] == 'float':
    # If it's a simple number, add f
    if re.match(r'^[\d.]+$', value.strip()) and not value.endswith('f'):
        value = value.strip() + 'f'

Fixed StateAttack/BlessMe conversion (around line ~420):

elif method_name in ['StateAttack', 'BlessMe']:
    # Parse body lines
    body_lines = []
    for line in body.split('\n'):
        line = line.strip()
        if line and 'return' not in line:
            # Remove spaces before parentheses
            line = re.sub(r'\s+\(', '(', line)
            # Convert 1.0 to 1.0f
            line = re.sub(r'(\d+\.\d+)(?!f)', r'\1f', line)
            # Ensure semicolon
            if not line.endswith(';'):
                line += ';'
            body_lines.append(line)
    
    server_methods_code += f"        public bool {method_name}(Skill skill)\n        {{\n"
    for line in body_lines:
        server_methods_code += f"            {line}\n"
    server_methods_code += f"            return true;\n        }}\n"

Testing Checklist

After applying fixes, test with:

# Test single skill
python convert_skills.py 390

# Check output
# 1. Open skill390.cs
# 2. Verify Calculate method has proper semicolons and indentation
# 3. Verify no spaces before parentheses: skill.GetPlayer() not skill.GetPlayer ()
# 4. Verify all float values have f suffix
# 5. Run linter - should be 0 errors

# If all good, proceed with batch
python convert_skills.py 390,391,392,393,394,395,396,397

Next Steps

  1. Apply all fixes above to convert_skills.py
  2. Test on skill 390 again
  3. Verify linter shows 0 errors
  4. Run on batch 390-397 (8 skills)
  5. Verify all 8 skills compile with no errors
  6. Run on all remaining skills with --all flag
  7. Final verification and linter check

Skills Remaining to Convert

Total: ~200+ skills

Priority batches:

  1. 390-439 (50 skills) - Current focus
  2. 440-491 (52 skills)
  3. All other ranges listed in SKILL_CONVERSION_INSTRUCTIONS.md

Success Criteria

All converted skills have:

  • No linter errors
  • Proper semicolons in Calculate methods
  • No spaces before parentheses
  • Float values with f suffix
  • Proper indentation (4 spaces per level)
  • Matching pattern with existing skills (skill65.cs, skill374.cs, etc.)