I've only been programming for 2 years or so. I'd say the majority of my experience comes from RimWorld and Quill18, who introduced me to RimWorld in the first place.
What didn't you understand? I tried to make it as user-friendly as possible with the example block. Basically you provide the variables, and the mod does the rest.
I meant C#, how long does it take to code something like this? Your XML is definitely easy to understand and well written.
Well figuring out the original code took probably 5 hours, but a lot of that was determining how to properly match up the brainmod traits with the traits the colonists had, and correctly removing the right ones. It was all new territory, and I had no experience with traits at the time. If I had to do it all again it would probably take an hour or two, including time for testing it works. Once you know what you're doing, it doesn't take long to do. Updating the code was pretty easy, since I just needed to change the variables from being defined in code to being defined in XML, but now it's just a matter of adding a new block of XML code vs adding a new C# class and rebuilding the dll.
I suppose the C# looks more complicated/impressive than it actually is if you're looking at it in ILSpy or the like, since the compiler jumbles up IEnumerable code. For example, the source is this:
using System;
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
namespace BrainMod {
public class BrainMod : ThingWithComps, IUsable {
// Used to determine if the brain mod is used for removal
public System.Random rand = new System.Random();
// The current brain mod
private BrainModDef brainMod = DefDatabase<BrainModDef>.GetNamed("BrainMod_BlankStarter");
// The mod author's name (for addons and bug-checking)
private string modAuthor {
get {
if (brainMod.ModAuthor == null) {
return "the mod author";
}
return brainMod.ModAuthor;
}
}
// The current traitDef
private TraitDef TDefInt;
public TraitDef TDef {
get {
if (TDefInt == null) {
TDefInt = TraitDef.Named(brainMod.TDef);
}
return TDefInt;
}
}
// The current trait
private Trait TInt;
public Trait T {
get {
if (TInt == null) {
TInt = new Trait(TraitDef.Named(brainMod.TDef));
if (brainMod.Degree != 0) {
TInt.degree = brainMod.Degree;
}
}
return TInt;
}
}
// Determines what percentage will be removal mods
private double? RemovalPercentInt;
public double RemovalPercent {
get {
if (RemovalPercentInt == null) {
RemovalPercentInt = Mathf.Clamp01((float)brainMod.RemovalPercent);
}
return (double)RemovalPercentInt;
}
}
// Used for brain mods that remove traits
private bool? RemovalInt;
public bool Removal {
get {
if (RemovalInt == null) {
RemovalInt = rand.NextDouble() <= RemovalPercent ? true : false;
}
return (bool)RemovalInt;
}
}
// The list of conflicting traits
public List<Trait> ConflictingTraits {
get {
if (brainMod.ConflictingTraits != null) {
for (int t = 0; t < brainMod.ConflictingTraits.Count; t++) {
if (TraitDef.Named(brainMod.ConflictingTraits[t]) == null) {
Log.Error("(BrainMod) Unknown TraitDef named '" + brainMod.ConflictingTraits[t] + "' found. Please report this issue to " + modAuthor + ".");
}
if (TraitDef.Named(brainMod.ConflictingTraits[t]) != null) {
ConflictingTraits.Add(new Trait(TraitDef.Named(brainMod.ConflictingTraits[t])));
}
}
return ConflictingTraits;
}
return null;
}
}
// Used when a trait conflicts with all traits in a spectrum
public TraitDef ConflictingSpectrum {
get {
if (brainMod.ConflictingSpectrum != null) {
return TraitDef.Named(brainMod.ConflictingSpectrum);
}
return null;
}
}
private JobDef UseBrainMod;
public override string LabelBase {
get {
if (Removal) {
return "nullify " + brainMod.label;
}
else {
return brainMod.label;
}
}
}
// Handle loading
public override void ExposeData() {
base.ExposeData();
Scribe_Deep.LookDeep(ref TInt, "trait");
Scribe_Defs.LookDef(ref TDefInt, "traitDef");
Scribe_Values.LookValue(ref RemovalPercentInt, "removalPercent");
Scribe_Values.LookValue(ref RemovalInt, "removal");
}
public override void PostMake() {
base.PostMake();
brainMod = DefDatabase<BrainModDef>.GetNamed(def.defName);
}
public override Color DrawColor {
get {
if (Removal) {
return Color.grey;
}
return brainMod.graphicData.color;
}
set {
base.DrawColor = value;
}
}
public override void SpawnSetup() {
base.SpawnSetup();
UseBrainMod = DefDatabase<JobDef>.GetNamed("UseBrainMod");
}
public override IEnumerable<FloatMenuOption> GetFloatMenuOptions(Pawn myPawn) {
foreach (FloatMenuOption opt in base.GetFloatMenuOptions(myPawn)) {
yield return opt;
}
Action action_UseBrainMod = delegate {
Job job = new Job(UseBrainMod, this);
myPawn.drafter.TakeOrderedJob(job);
};
if (!Removal) {
for (int t = 0; t < myPawn.story.traits.allTraits.Count; t++) {
if (myPawn.story.traits.allTraits[t].Label == T.Label) {
// This allows the other options to still show (prioritize hauling, etc.)
goto Exit;
}
}
yield return new FloatMenuOption("UseBrainMod".Translate(T.Label), action_UseBrainMod);
}
if (Removal) {
for (int t = 0; t < myPawn.story.traits.allTraits.Count; t++) {
if (myPawn.story.traits.allTraits[t].Label == T.Label) {
yield return new FloatMenuOption("UseRemovalBrainMod".Translate(T.Label), action_UseBrainMod);
break;
}
}
}
Exit:;
}
public void UsedBy(Pawn user) {
// Simplify typing
List<Trait> tList = user.story.traits.allTraits;
// If this is an adding mod, process conflicts and add the trait
if (!Removal) {
// Remove as many conflicting traits as are necessary
if (ConflictingSpectrum != null) {
for (int cs = 0; cs < ConflictingSpectrum.degreeDatas.Count; cs++) {
for (int t = 0; t < tList.Count; t++) {
if (ConflictingSpectrum.degreeDatas[cs].label.EqualsIgnoreCase(tList[t].Label)) {
tList.Remove(tList[t]);
}
}
}
}
if (ConflictingTraits != null) {
for (int a = 0; a < tList.Count; a++) {
for (int b = 0; b < ConflictingTraits.Count; b++) {
if (tList[a].Label == ConflictingTraits[b].Label) {
tList.Remove(tList[a]);
}
}
}
}
tList.Add(T);
}
// If this is a removal mod, remove the trait
if (Removal) {
for (int t = 0; t < tList.Count; t++) {
// Ensure the trait matches and the degree is the same
if (tList[t].Label == T.Label) {
tList.Remove(tList[t]);
}
}
}
if (PawnUtility.ShouldSendNotificationAbout(user)) {
if (!Removal) {
// Determine the message based on conflicts
Messages.Message("BrainModUsed".Translate(user.LabelBaseShort, T.Label), user, MessageSound.Standard);
}
if (Removal) {
Messages.Message("RemovalBrainModUsed".Translate(user.LabelBaseShort, T.Label), user, MessageSound.Standard);
}
}
Destroy();
}
}
}
... which is probably easier to read and understand, or at least get the basic idea of what's going on, vs this snippet of what's returned by ILSpy:
[CompilerGenerated]
private sealed class <GetFloatMenuOptions>d__29 : IEnumerable<FloatMenuOption>, IEnumerable, IEnumerator<FloatMenuOption>, IDisposable, IEnumerator
{
private int <>1__state;
private FloatMenuOption <>2__current;
private int <>l__initialThreadId;
private Pawn myPawn;
public Pawn <>3__myPawn;
public BrainMod <>4__this;
private BrainMod.<>c__DisplayClass29_0 <>8__1;
private Action <action_UseBrainMod>5__2;
private IEnumerator<FloatMenuOption> <>s__3;
private FloatMenuOption <opt>5__4;
private int <t>5__5;
private int <t>5__6;
FloatMenuOption IEnumerator<FloatMenuOption>.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return this.<>2__current;
}
}
[DebuggerHidden]
public <GetFloatMenuOptions>d__29(int <>1__state)
{
this.<>1__state = <>1__state;
this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = this.<>1__state;
if (num == -3 || num == 1)
{
try
{
}
finally
{
this.<>m__Finally1();
}
}
}
bool IEnumerator.MoveNext()
{
bool result;
try
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<>8__1 = new BrainMod.<>c__DisplayClass29_0();
this.<>8__1.<>4__this = this.<>4__this;
this.<>8__1.myPawn = this.myPawn;
this.<>s__3 = this.<>4__this.<>n__0(this.<>8__1.myPawn).GetEnumerator();
this.<>1__state = -3;
break;
case 1:
this.<>1__state = -3;
this.<opt>5__4 = null;
break;
case 2:
this.<>1__state = -1;
goto IL_1FF;
case 3:
this.<>1__state = -1;
goto IL_2F5;
default:
result = false;
return result;
}
if (this.<>s__3.MoveNext())
{
this.<opt>5__4 = this.<>s__3.Current;
this.<>2__current = this.<opt>5__4;
this.<>1__state = 1;
result = true;
return result;
}
this.<>m__Finally1();
this.<>s__3 = null;
this.<action_UseBrainMod>5__2 = new Action(this.<>8__1.<GetFloatMenuOptions>b__0);
bool flag = !this.<>4__this.Removal;
if (flag)
{
this.<t>5__5 = 0;
while (this.<t>5__5 < this.<>8__1.myPawn.story.traits.allTraits.Count)
{
bool flag2 = this.<>8__1.myPawn.story.traits.allTraits[this.<t>5__5].Label == this.<>4__this.T.Label;
if (flag2)
{
goto IL_2F6;
}
int num = this.<t>5__5;
this.<t>5__5 = num + 1;
}
this.<>2__current = new FloatMenuOption("UseBrainMod".Translate(new object[]
{
this.<>4__this.T.Label
}), this.<action_UseBrainMod>5__2, MenuOptionPriority.Medium, null, null);
this.<>1__state = 2;
result = true;
return result;
}
IL_1FF:
bool removal = this.<>4__this.Removal;
if (removal)
{
this.<t>5__6 = 0;
while (this.<t>5__6 < this.<>8__1.myPawn.story.traits.allTraits.Count)
{
bool flag3 = this.<>8__1.myPawn.story.traits.allTraits[this.<t>5__6].Label == this.<>4__this.T.Label;
if (flag3)
{
this.<>2__current = new FloatMenuOption("UseRemovalBrainMod".Translate(new object[]
{
this.<>4__this.T.Label
}), this.<action_UseBrainMod>5__2, MenuOptionPriority.Medium, null, null);
this.<>1__state = 3;
result = true;
return result;
}
int num = this.<t>5__6;
this.<t>5__6 = num + 1;
}
}
IL_2F5:
IL_2F6:
result = false;
}
catch
{
this.System.IDisposable.Dispose();
throw;
}
return result;
}
private void <>m__Finally1()
{
this.<>1__state = -1;
if (this.<>s__3 != null)
{
this.<>s__3.Dispose();
}
}