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

14 KiB

Perfect World Unity Skill C++→C# Conversion Instructions

Overview

This document contains complete instructions for converting Perfect World C++ skill files to Unity C# format using the Python conversion tool.

Repository Structure

C++ Source Location

perfect-world-source/perfect-world-source/CElement/CElementSkill/skillNN.h

C# Target Location

perfect-world-unity/Assets/PerfectWorld/Scripts/Skills/skillNN.cs

Stub Registry

perfect-world-unity/Assets/PerfectWorld/Scripts/Skills/SkillStubs1.cs

Conversion Pattern (MUST FOLLOW EXACTLY)

File Structure Template

Every skillNN.cs file MUST follow this exact structure:

#define SKILL_CLIENT
using BrewMonster.Scripts.Skills;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using static BrewMonster.PET_EVOLVE_CONFIG;

namespace BrewMonster
{

#if SKILL_SERVER
    public class SkillNN : Skill
    {
        public const int SKILL_ID = NN;

        public SkillNN() : base(SKILL_ID)
        {
        }
    }
#endif

    public class SkillNNStub : SkillStub
    {
        // Static arrays (if present in C++)
        private static readonly int[] RequiredLevelArray = { ... };
        private static readonly int[] RequiredSpArray = { ... };
        private static readonly int[] RequiredItemArray = { ... };
        private static readonly int[] RequiredMoneyArray = { ... };

        // Nested State classes (ONLY if C++ has them under _SKILL_SERVER)
#if SKILL_SERVER
        public class State1 : SkillStub.State
        {
            public int GetTime(Skill skill) => NNN;
            public bool Quit(Skill skill) => false;
            public bool Loop(Skill skill) => false;
            public bool Bypass(Skill skill) => false;
            public void Calculate(Skill skill)
            {
                // Converted C++ code here
            }
            public bool Interrupt(Skill skill) => false;
            public bool Cancel(Skill skill) => true/false;
            public bool Skip(Skill skill) => false;
        }
#endif

        // Constructor
        public SkillNNStub() : base(NN)
        {
            // Field assignments in specific order (see below)
            cls = 0;
            name = "中文名";
            nativename = "中文名";
            icon = "icon.dds";
            max_level = N;
            type = N;
            apcost = N;
            arrowcost = N;
            apgain = N;
            attr = N;
            rank = N;
            eventflag = N;
            is_senior = N;
            posdouble = N;  // Only if present in C++
            clslimit = N;
            time_type = N;
            showorder = N;
            allow_land = true/false;
            allow_air = true/false;
            allow_water = true/false;
            allow_ride = true/false;
            auto_attack = true/false;
            long_range = N;
            restrict_corpse = N;
            allow_forms = N;
            restrict_weapons.Add(N);  // One per weapon
            effect = "effect.sgc";
            range = new Range();
            range.type = N;
            doenchant = 0/1;  // byte type
            dobless = 0/1;    // byte type
            commoncooldown = N;
            commoncooldowntime = N;
            pre_skills = new Dictionary<uint, int>();  // Only if needed
            pre_skills.Add(id, level);  // One per prerequisite
#if SKILL_SERVER
            statestub.Add(new State1());  // One per state
            statestub.Add(new State2());
            statestub.Add(new State3());
#endif
        }

        ~SkillNNStub() { }

        // Public methods
        public float GetMpcost(Skill skill) => NNNf;
        public int GetExecutetime(Skill skill) => NNN;
        public int GetCoolingtime(Skill skill) => NNN;
        public int GetRequiredLevel(Skill skill) => RequiredLevelArray[skill.GetLevel() - 1];
        public int GetRequiredSp(Skill skill) => RequiredSpArray[skill.GetLevel() - 1];
        public int GetRequiredItem(Skill skill) => RequiredItemArray[skill.GetLevel() - 1];
        public int GetRequiredMoney(Skill skill) => RequiredMoneyArray[skill.GetLevel() - 1];
        public float GetRadius(Skill skill) => NNNf;
        public float GetAttackdistance(Skill skill) => (float)(expression);
        public float GetAngle(Skill skill) => (float)(1 - 0.0111111 * N);
        public float GetPraydistance(Skill skill) => expression;

#if SKILL_CLIENT
        public int GetIntroduction(Skill skill, StringBuilder buffer, int length, string format)
        {
            string result = string.Format(format, param1, param2, ...);
            if (result.Length < length)
            {
                buffer.Append(result);
                return result.Length;
            }
            return 0;
        }
#endif

#if SKILL_SERVER
        public int GetEnmity(Skill skill) => NNN;
        public bool StateAttack(Skill skill)
        {
            skill.GetVictim().SetProbability(1.0f * N);
            skill.GetVictim().SetTime(N);
            // ... more victim settings
            return true;
        }
        public bool BlessMe(Skill skill)
        {
            skill.GetVictim().SetProbability(1.0f * N);
            skill.GetVictim().SetValue(N);
            // ... more victim settings
            return true;
        }
        public bool TakeEffect(Skill skill) => true;
        public float GetEffectdistance(Skill skill) => NNNf;
        public int GetAttackspeed(Skill skill) => NNN;
        public float GetHitrate(Skill skill) => NNNf;
        public float GetTalent0(PlayerWrapper player) => NNNf;
        public float GetTalent1(PlayerWrapper player) => player.GetAttackdegree();
#endif
    }
}

Critical Conversion Rules

1. Type Mappings (C++ → C#)

  • bool (0/1 in C++) → bool (true/false) for: allow_land, allow_air, allow_water, allow_ride, auto_attack
  • bool (0/1 in C++) → byte (0/1) for: doenchant, dobless
  • intint (no change)
  • floatfloat with f suffix (e.g., 125125f, 1.51.5f)
  • L"string""string" (remove L prefix)
  • std::pair<ID, int>(id, level)pre_skills.Add(id, level);

2. Field Assignment Order (MUST FOLLOW)

cls
name
nativename
icon
max_level
type
apcost
arrowcost
apgain
attr
rank
eventflag
is_senior
posdouble (optional)
clslimit
time_type
showorder
allow_land
allow_air
allow_water
allow_ride
auto_attack
long_range
restrict_corpse
allow_forms
restrict_weapons (multiple Add calls)
effect
range (always: range = new Range(); range.type = N;)
doenchant
dobless
commoncooldown
commoncooldowntime
pre_skills (if needed)
statestub (server-only)

3. State Class Conversion Rules

C++ State Method:

void Calculate (Skill * skill) const
{
    skill->GetPlayer ()->SetDecmp (25);
    skill->GetPlayer ()->SetPray (1);
}

C# State Method:

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

Key conversions:

  • skill->skill.
  • skill->GetPlayer ()skill.GetPlayer()
  • skill->GetLevel ()skill.GetLevel()
  • Remove spaces before ()
  • Each statement MUST end with ;

4. Method Return Value Rules

Float methods MUST have f suffix:

public float GetMpcost(Skill skill) => 125f;  // NOT 125
public float GetRadius(Skill skill) => 0f;    // NOT 0
public float GetHitrate(Skill skill) => 1.8f; // NOT 1.8

Complex expressions:

// C++: return (float) (skill->GetPlayer ()->GetRange () + 3 + 0.3 * skill->GetLevel ());
// C#:
public float GetAttackdistance(Skill skill) => (float)(skill.GetPlayer().GetRange() + 3 + 0.3 * skill.GetLevel());

5. Array Declaration Rules

C++ arrays:

static int array[10] = { 39, 43, 47, 51, 55, 59, 63, 67, 71, 75 };

C# arrays:

private static readonly int[] RequiredLevelArray = { 39, 43, 47, 51, 55, 59, 63, 67, 71, 75 };

Usage:

public int GetRequiredLevel(Skill skill) => RequiredLevelArray[skill.GetLevel() - 1];

6. Server-Only Methods

These methods MUST be wrapped in #if SKILL_SERVER:

  • GetEnmity
  • StateAttack
  • BlessMe
  • TakeEffect
  • GetEffectdistance
  • GetAttackspeed
  • GetHitrate
  • GetTalent0
  • GetTalent1

7. StateAttack / BlessMe Method Conversion

C++ Example:

bool StateAttack (Skill * skill) const
{
    skill->GetVictim ()->SetProbability (1.0 * 100);
    skill->GetVictim ()->SetTime (10000);
    skill->GetVictim ()->SetRatio (0.6);
    skill->GetVictim ()->SetSlow (1);
    return true;
}

C# Conversion:

public bool StateAttack(Skill skill)
{
    skill.GetVictim().SetProbability(1.0f * 100);
    skill.GetVictim().SetTime(10000);
    skill.GetVictim().SetRatio(0.6f);
    skill.GetVictim().SetSlow(1);
    return true;
}

Key points:

  • 1.01.0f
  • 0.60.6f
  • Remove spaces before ()
  • ->.

Skills to Convert

Already Completed (DO NOT CONVERT AGAIN)

  • 1-6, 54-80, 176-179, 187, 226-227, 362-363, 374-389

Remaining Skills to Convert

390-439 (50 skills)
440-491 (52 skills)
896-900 (5 skills)
923-924 (2 skills)
1195 (1 skill)
1815-1819 (5 skills)
1868 (1 skill)
1871-1872 (2 skills)
2206-2211 (6 skills)
2352 (1 skill)
2367-2375 (9 skills)
901-905 (5 skills)
925-926 (2 skills)
1805-1809 (5 skills)
1864-1865 (2 skills)
1873-1874 (2 skills)
1951 (1 skill)
2254-2265 (12 skills)
2452-2453 (2 skills)

Plus earlier commented sets:
7-10, 53, 81, 84-101, 180-184, 228-229, 364-365

Python Tool Usage

Run on specific skills:

python convert_skills.py 390,391,392

Run on all remaining skills:

python convert_skills.py --all

Known Issues to Fix in Python Tool

Issue 1: Calculate Method Formatting

The Calculate method body needs proper semicolons and indentation.

Current bug:

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 needed in Python:

# In generate_csharp_state method, around line 136-147
# Current code splits by ';' but doesn't add them back properly
# Need to:
1. Split by ';'
2. Strip each line
3. Add ';' back to each line
4. Proper indentation (12 spaces for Calculate body)
5. Remove extra spaces before '()'

Issue 2: Space Removal Before Parentheses

Need to remove spaces before () in all method calls.

Current: skill.GetPlayer ()Should be: skill.GetPlayer()

Fix needed:

# Add this regex replacement in all C++ to C# conversions:
content = re.sub(r'\s+\(\)', '()', content)

Issue 3: Float Suffix Consistency

Ensure ALL float values have f suffix.

Examples:

  • 00f
  • 1.81.8f
  • 125125f (in float methods)

Issue 4: Complex Expression Parsing

Some complex expressions in methods like GetAttackdistance need proper parsing.

C++ Example:

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

C# Should be:

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

SkillStubs1.cs Update

After converting skills, MUST update SkillStubs1.cs:

Change from:

//public static SkillNNStub __stub_SkillNNStub = new SkillNNStub();

To:

public static SkillNNStub __stub_SkillNNStub = new SkillNNStub();

Also add in #if SKILL_SERVER section:

public static SkillNN __stub_SkillNN = new SkillNN();

Validation Checklist

For each converted skill, verify:

  • File name: skillNN.cs (lowercase)
  • Namespace: BrewMonster
  • SkillNN class exists under #if SKILL_SERVER
  • SkillNNStub : SkillStub exists, calls base(NN)
  • range = new Range(); range.type = ...; (always present)
  • pre_skills = new Dictionary<uint,int>(); (when needed)
  • restrict_weapons.Add(...) matches C++ ordering
  • All methods in C++ stub are implemented in C# stub
  • Server-only methods wrapped with #if SKILL_SERVER
  • No linter errors
  • Stub added to SkillStubs1.cs

Example: Complete Conversion

C++ (skill374.h):

class Skill374Stub:public SkillStub
{
    Skill374Stub ():SkillStub (374)
    {
        cls = 0;
        name = L"魂·震荡";
        max_level = 1;
        allow_land = 1;
        auto_attack = 1;
        doenchant = false;
        dobless = true;
        restrict_weapons.push_back (0);
        restrict_weapons.push_back (1);
        range.type = 0;
        pre_skills.push_back (std::pair < ID, int >(1, 10));
    }
    float GetMpcost (Skill * skill) const
    {
        return (float) (125);
    }
}

C# (skill374.cs):

public class Skill374Stub : SkillStub
{
    private static readonly int[] RequiredLevelArray = { 89 };
    
    public Skill374Stub() : base(374)
    {
        cls = 0;
        name = "魂·震荡";
        nativename = "魂·震荡";
        max_level = 1;
        allow_land = true;
        auto_attack = true;
        restrict_weapons.Add(0);
        restrict_weapons.Add(1);
        effect = "1震荡.sgc";
        range = new Range();
        range.type = 0;
        doenchant = 0;
        dobless = 1;
        pre_skills = new Dictionary<uint, int>();
        pre_skills.Add(1, 10);
    }
    
    ~Skill374Stub() { }
    
    public float GetMpcost(Skill skill) => 125f;
}

PowerShell Note

PowerShell doesn't support && as command separator. Use ; instead:

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

Final Notes

  • Keep ALL Chinese characters as-is
  • Follow the pattern EXACTLY as shown in existing converted skills (skill65.cs, skill374.cs, etc.)
  • When in doubt, reference existing converted skills
  • Test each batch with linter before proceeding
  • The Python tool should automate 95% of the work, but manual review may be needed for complex methods