This guide is targeted towards developers and contributors who want to create new playable races.
This will not cover the intricacies of ripping models, modifying them, or creating them from scratch. You will need to refer to other guides on Google for help on that.
Additionally, this guide is not comprehensive. Every race is a little different and may be implemented in various ways. Depending on your use case additional work may be necessary.
One of the most difficult parts of creating a new race is putting the models and textures together. Again, this is outside the scope of this guide but you can find more information at the links below. If you get stuck, reach out in our Discord for assistance.
Your models, textures, and any other files need to go into their respective folders within our Hakpak content repository. Refer to the development environment guide above for more detail.
An entry must be added to the racialtypes.2da
file for the new race. This will require adding entries into the tlk as well.
Please refer to this guide on editing this 2da file.
Once the file has been edited, add it to the swlor2_2da
hakpak repository folder.
Your portraits must be added to the portraits.2da file in order for players to select them during character creation.
Please refer to this guide on editing this 2da file.
Once the file has been edited, add it to the swlor2_2da
hakpak repository folder
Add a new entry to the Core/NWScript/Enum/RacialType.cs
file. This should match the ID of the racial type you previously added to the 2da file.
namespace SWLOR.Game.Server.Core.NWScript.Enum
{
public enum RacialType
{
<Other entries omitted for brevity>
MonCalamari = 162,
Ugnaught = 163,
MyNewRace = 164 // 164 is the line number of the 2DA file in this example
}
}
A new class must be added to the Feature/AppearanceDefinition/RacialAppearance/
folder. By default, Human parts are used for the new race. The following example overrides all of the parts but you don't need to do so - it just depends on what race you're doing. The numbers you're entering here must relate back to the IDs of the parts you've imported.
namespace SWLOR.Game.Server.Feature.AppearanceDefinition.RacialAppearance
{
public class MyNewRaceRacialAppearanceDefinition: RacialAppearanceBaseDefinition
{
public override int[] MaleHeads { get; } = { 6, 44, 48, 49, 104, 105, 106, 107, 108, 112, 113, 114, 117, 119, 120, 127 };
public override int[] FemaleHeads { get; } = { 3, 6, 16, 17, 21, 26, 29, 41, 43, 47, 109, 110, 115, 119, 122 };
public override int[] Torsos { get; } = { 204 }; // If this race should use Human parts, you can delete this line (and any subsequent ones)
public override int[] Pelvis { get; } = { 204 };
public override int[] RightBicep { get; } = { 204 };
public override int[] RightForearm { get; } = { 204 };
public override int[] RightHand { get; } = { 204 };
public override int[] RightThigh { get; } = { 204 };
public override int[] RightShin { get; } = { 204 };
public override int[] LeftBicep { get; } = { 204 };
public override int[] LeftForearm { get; } = { 204 };
public override int[] LeftHand { get; } = { 204 };
public override int[] LeftThigh { get; } = { 204 };
public override int[] LeftShin { get; } = { 204 };
}
}
Next, update the file Feature/GuiDefinition/ViewModel/AppearanceEditorViewModel.cs
to include a connection to your appearance definition within the LoadAppearances()
method. This determines the set of options that are available within the UI window used for character appearance customization.
private static void LoadRacialAppearances()
{
_racialAppearances[AppearanceType.Human] = new HumanRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Bothan] = new BothanRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Chiss] = new ChissRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Zabrak] = new ZabrakRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Twilek] = new TwilekRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Mirialan] = new MirialanRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Echani] = new EchaniRacialAppearanceDefinition();
_racialAppearances[AppearanceType.KelDor] = new KelDorRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Cyborg] = new CyborgRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Cathar] = new CatharRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Rodian] = new RodianRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Trandoshan] = new TrandoshanRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Togruta] = new TogrutaRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Wookiee] = new WookieeRacialAppearanceDefinition();
_racialAppearances[AppearanceType.MonCalamari] = new MonCalamariRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Ugnaught] = new UgnaughtRacialAppearanceDefinition();
_racialAppearances[AppearanceType.Droid] = new DroidRacialAppearanceDefinition();
}
Next, update the file Service/Race.cs
to include the default values for your new race. There should be an entry for both the male and female versions of the race. This is done within the LoadRaces()
method. This determines what a brand new character's appearance will be set to on login.
[NWNEventHandler("mod_load")]
public static void LoadRaces()
{
// Male appearances
_defaultRaceAppearancesMale[RacialType.MyNewRace] = new RacialAppearance
{
SkinColorId = 6,
HairColorId = 1,
AppearanceType = AppearanceType.Elf,
HeadId = 40
};
<other entries omitted for brevity>
// Female appearances
_defaultRaceAppearancesFemale[RacialType.MyNewRace] = new RacialAppearance
{
SkinColorId = 6,
HairColorId = 1,
AppearanceType = AppearanceType.Elf,
HeadId = 109
};
}
If the race you're adding thematically speaks another language, you must also incorporate that language into the code base.
To generate a new language, run the SWLOR.CLI
application with the -l
argument (lower case L). This will spit out code into a console window like below.
This code should be copy/pasted to a new Translator class file in the Service/LanguageService/
folder. For example, you can call this file TranslatorMyNewLanguage.cs
An example of how this file could look is below.
using System.Text;
namespace SWLOR.Game.Server.Service.LanguageService
{
public class TranslatorMyNewLanguage : ITranslator
{
public string Translate(string message)
{
var sb = new StringBuilder();
foreach (var ch in message)
{
switch (ch)
{
case 'a': sb.Append("h"); break;
case 'A': sb.Append("H"); break;
case 'b': sb.Append("c"); break;
case 'B': sb.Append("C"); break;
case 'c': sb.Append("l"); break;
case 'C': sb.Append("L"); break;
case 'd': sb.Append("gf"); break;
case 'D': sb.Append("G"); break;
case 'e': sb.Append("c"); break;
case 'E': sb.Append("C"); break;
case 'f': sb.Append("k"); break;
case 'F': sb.Append("K"); break;
case 'g': sb.Append("is"); break;
case 'G': sb.Append("I"); break;
case 'h': sb.Append("w"); break;
case 'H': sb.Append("W"); break;
case 'i': sb.Append("v"); break;
case 'I': sb.Append("V"); break;
case 'j': sb.Append("n"); break;
case 'J': sb.Append("N"); break;
case 'k': sb.Append("x"); break;
case 'K': sb.Append("X"); break;
case 'l': sb.Append("j"); break;
case 'L': sb.Append("J"); break;
case 'm': sb.Append("g"); break;
case 'M': sb.Append("G"); break;
case 'n': sb.Append("'"); break;
case 'N': sb.Append("'"); break;
case 'o': sb.Append("e"); break;
case 'O': sb.Append("E"); break;
case 'p': sb.Append("y"); break;
case 'P': sb.Append("Y"); break;
case 'q': sb.Append("t"); break;
case 'Q': sb.Append("T"); break;
case 'r': sb.Append("q"); break;
case 'R': sb.Append("Q"); break;
case 's': sb.Append("r"); break;
case 'S': sb.Append("R"); break;
case 't': sb.Append("m"); break;
case 'T': sb.Append("M"); break;
case 'u': sb.Append("u"); break;
case 'U': sb.Append("U"); break;
case 'v': sb.Append("d"); break;
case 'V': sb.Append("D"); break;
case 'w': sb.Append("p"); break;
case 'W': sb.Append("P"); break;
case 'x': sb.Append("b"); break;
case 'X': sb.Append("B"); break;
case 'y': sb.Append("z"); break;
case 'Y': sb.Append("Z"); break;
case 'z': sb.Append("a"); break;
case 'Z': sb.Append("A"); break;
default: sb.Append(ch); break;
}
}
return sb.ToString();
}
}
}
Next, add this new translator to Service/Language.cs
in the LoadTranslators()
method.
[NWNEventHandler("mod_load")]
public static void LoadTranslators()
{
_translators = new Dictionary<SkillType, ITranslator>
{
{ SkillType.Bothese, new TranslatorBothese() },
{ SkillType.Catharese, new TranslatorCatharese() },
{ SkillType.Cheunh, new TranslatorCheunh() },
{ SkillType.Dosh, new TranslatorDosh() },
{ SkillType.Droidspeak, new TranslatorDroidspeak() },
{ SkillType.Huttese, new TranslatorHuttese() },
{ SkillType.Mandoa, new TranslatorMandoa() },
{ SkillType.Shyriiwook, new TranslatorShyriiwook() },
{ SkillType.Twileki, new TranslatorTwileki() },
{ SkillType.Zabraki, new TranslatorZabraki() },
{ SkillType.Mirialan, new TranslatorMirialan() },
{ SkillType.MonCalamarian, new TranslatorMonCalamarian() },
{ SkillType.Ugnaught, new TranslatorUgnaught() },
{ SkillType.MyNewRace, new TranslatorMyNewRace() }, // Your new translator entry
};
}
Now the language needs to be added as a skill. Add a new entry to the Service/SkillService/SkillType.cs
file.
[Skill(SkillCategoryType.Languages,
"My New Race", // The viewable name of the skill
20, // Max rank, this should always be 20
true, // Active or not, always true
"Ability to speak the MyNewRace language.", // The viewable description of the skill
false)] // Determines whether the skill counts towards your skill cap. Always false for language skills.
MyNewRace = 27,
Next, the language needs to be given to all players who create a character of this race. Add a new entry to the Feature/PlayerInitialization.cs
file in the InitializeLanguages()
method.
switch (race)
{
case RacialType.Bothan:
languages.Add(SkillType.Bothese);
break;
case RacialType.Chiss:
languages.Add(SkillType.Cheunh);
break;
case RacialType.Zabrak:
languages.Add(SkillType.Zabraki);
break;
case RacialType.Wookiee:
languages.Add(SkillType.Shyriiwook);
break;
case RacialType.Twilek:
languages.Add(SkillType.Twileki);
break;
case RacialType.Cathar:
languages.Add(SkillType.Catharese);
break;
case RacialType.Trandoshan:
languages.Add(SkillType.Dosh);
break;
case RacialType.Cyborg:
languages.Add(SkillType.Droidspeak);
break;
case RacialType.Mirialan:
languages.Add(SkillType.Mirialan);
break;
case RacialType.MonCalamari:
languages.Add(SkillType.MonCalamarian);
break;
case RacialType.Ugnaught:
languages.Add(SkillType.Ugnaught);
break;
case RacialType.MyNewRace: // Add this new entry
languages.Add(SkillType.MyNewRace);
break;
}