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_attackbool(0/1 in C++) →byte(0/1) for:doenchant,doblessint→int(no change)float→floatwithfsuffix (e.g.,125→125f,1.5→1.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:
GetEnmityStateAttackBlessMeTakeEffectGetEffectdistanceGetAttackspeedGetHitrateGetTalent0GetTalent1
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.0→1.0f0.6→0.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:
0→0f1.8→1.8f125→125f(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 SkillNNclass exists under#if SKILL_SERVERSkillNNStub : SkillStubexists, callsbase(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