Turret Projectile DefOf Issue

Started by N7Huntsman, November 02, 2018, 01:14:47 AM

Previous topic - Next topic

N7Huntsman

So, I'm in the process of updating an abandoned A18 turret mod. It's currently at A18, but I was hoping to get it updated to 1.0 for others to use. I've got all the .xml side of things sorted out and there don't seem to be any issues there. However, it's currently throwing the following error when the mod is loaded:

Tried to use an uninitialized DefOf of type DamageDefOf. DefOfs are initialized right after all defs all loaded. Uninitialized DefOfs will return only nulls. (hint: don't use DefOfs as default field values in Defs, try to resolve them in ResolveReferences() instead) Debug info: DirectXmlToObject is currently instantiating an object of type TurretCollection.CompProperties_ProjectileExtraDamage
Verse.Log:Warning(String, Boolean)
RimWorld.DefOfHelper:EnsureInitializedInCtor(Type)
RimWorld.DamageDefOf:.cctor()


While the turrets can successfully load, aim, and fire, the projectiles pass through things (pawns, structures, mountains, etc.) without causing damage and the following error is thrown numerous times:

Exception ticking TC_Bullet_ChainGun46236 (at (105, 0, 137)): System.MissingMethodException: Method not found: 'Verse.Log.Error'.
  at Verse.Projectile.CheckForFreeIntercept (IntVec3 c) [0x00000] in <filename unknown>:0
  at Verse.Projectile.CheckForFreeInterceptBetween (Vector3 lastExactPos, Vector3 newExactPos) [0x00000] in <filename unknown>:0
  at Verse.Projectile.Tick () [0x00000] in <filename unknown>:0
  at TurretCollection.Projectile_Custom.Tick () [0x00000] in <filename unknown>:0
  at Verse.TickList.Tick () [0x00000] in <filename unknown>:0
Verse.Log:Error(String, Boolean)
Verse.TickList:Tick()
Verse.TickManager:DoSingleTick()
Verse.TickManager:TickManagerUpdate()
Verse.Game:UpdatePlay()
Verse.Root_Play:Update()


Digging through the mod's assembly, I've located the file I believe is responsible for the first (and I hope second) error. It reads:

using System;
using RimWorld;
using Verse;
using UnityEngine;

namespace TurretCollection {
    public class CompProperties_ProjectileExtraDamage : CompProperties {
        public string hitText = "TC_Hit";
        public Color hitTextColor = new Color32(255, 153, 102, 255);
        public int damageAmountBase = 1;
        public DamageDef damageDef = DamageDefOf.Bullet;

        public CompProperties_ProjectileExtraDamage() {
            base.compClass = typeof(CompProjectileExtraDamage);
        }
    }

    public class CompProjectileExtraDamage : ThingComp {
        public CompProperties_ProjectileExtraDamage Props => (CompProperties_ProjectileExtraDamage)base.props;


As I don't know any useful amount of C#, I'm hoping someone else can tell me how to best resolve the error. If you need any additional code, or the mod files and source to look at, just let me know.
The Rim is a cruel place.

abaddon16

Background: I have dabbled in C#, I know my way around code, but I'm new (a couple days) to the Rimworld modding scene.

I'm having the same issue as above, but with HediffDefOf. I followed the tutorial here https://ludeon.com/forums/index.php?topic=33219.0 and get the error below immediately on load:


Tried to use an uninitialized DefOf of type HediffDefOf. DefOfs are initialized right after all defs all loaded. Uninitialized DefOfs will return only nulls. (hint: don't use DefOfs as default field values in Defs, try to resolve them in ResolveReferences() instead) Debug info: DirectXmlToObject is currently instantiating an object of type PlagueGun.ThingDef_PlagueBullet
Verse.Log:Warning(String, Boolean)
RimWorld.DefOfHelper:EnsureInitializedInCtor(Type)
RimWorld.HediffDefOf:.cctor()
System.Reflection.MonoCMethod:InternalInvoke(Object, Object[], Exception&)
System.Reflection.MonoCMethod:Invoke(Object, BindingFlags, Binder, Object[], CultureInfo)
System.Reflection.MonoCMethod:Invoke(BindingFlags, Binder, Object[], CultureInfo)
System.Reflection.ConstructorInfo:Invoke(Object[])
System.Activator:CreateInstance(Type, Boolean)
System.Activator:CreateInstance(Type)
Verse.DirectXmlToObject:ObjectFromXml(XmlNode, Boolean)
System.Reflection.MonoMethod:InternalInvoke(Object, Object[], Exception&)
System.Reflection.MonoMethod:Invoke(Object, BindingFlags, Binder, Object[], CultureInfo)
System.Reflection.MethodBase:Invoke(Object, Object[])
Verse.DirectXmlLoader:DefFromNode(XmlNode, LoadableXmlAsset)
Verse.LoadedModManager:ParseAndProcessXML(XmlDocument, Dictionary`2)
Verse.LoadedModManager:LoadAllActiveMods()
Verse.PlayDataLoader:DoPlayLoad()
Verse.PlayDataLoader:LoadAllPlayData(Boolean)
Verse.Root:<Start>m__1()
Verse.LongEventHandler:RunEventFromAnotherThread(Action)
Verse.LongEventHandler:<UpdateCurrentAsynchronousEvent>m__1()


My XML is the same, my C# is the same.. I'm at a loss. I'm guessing since the tutorial is not for [1.0] there were some changes that I'm missing, but it's still confusing (the error, not the guide - the guide was great). The stack trace references a ResolveReferences(), but I can find no documentation with a simple google search. This error says to me that I'm calling on an empty property, and the system knows it will be empty. However, in game the weapon functions as intended - no issues whatsoever. I believe my issue to be a simple lack of understanding at this point.

abaddon16

@OP, as for your error-

The DefOf issue may be an issue? But as in my response, it's not been an issue, so I am feeling it might be more of a warning, and the real issue is the second error.

The second error looks like it comes specifically from the Bullet class file (maybe TC_Bullet_ChainGun46236?) and not the comp file. I might be entirely wrong there, but the actual error is the missing Verse.Log.Error function, likely from the class file not having using Verse;. The rest of it is simply a stack trace. The reason it's throwing the error endlessly is that it keeps trying to use that function every tick to say something is wrong - once every 1/60th of a second at full speed. But you can't see its error because the game is throwing its own error because that function is missing, so you can't see what error the mod is throwing.

So I would say look for the Bullet file, find the line (related to (at (105, 0, 137)) for location, though why there is 3 params, I'll never know), and determine what function it's calling and if it is missing the using Verse; at the top of the file.

Mehni

System.MissingMethodException: Method not found: 'Verse.Log.Error'.

Signature of the method got changed between versions. Did ya try recompiling???

QuoteSo I would say look for the Bullet file, find the line (related to (at (105, 0, 137)) for location, though why there is 3 params, I'll never know)

Because that's not the location in a file. That's the x y z coordinates of an IntVec3: the position of the bullet on the map. Now you know.

As to the DefOfs problem, I can only give a hint: don't use DefOfs as default field values in Defs, try to resolve them in ResolveReferences() instead.

abaddon16

QuoteBecause that's not the location in a file. That's the x y z coordinates of an IntVec3: the position of the bullet on the map. Now you know.
Indeed I do - quite helpful!

QuoteAs to the DefOfs problem, I can only give a hint: don't use DefOfs as default field values in Defs, try to resolve them in ResolveReferences() instead.
This one though... I can't seem to find how that works anywhere. I found a mod github where it's used but... no clue how it works. That mod also had an initialization file that I didn't realize was a thing in this game (I've looked into Minecraft modding so I know there is initialization stages in games) and don't know how it all works. Unfortunately, I can't seem to find the "why" tutorials on ResolveReferences().

Mehni

Can't tell you why either. Probably because there's a chance the Def will be null if not properly resolved prior. There's various stages of initialising a mod and it's possible to add a reference to a def that hasn't been loaded yet. You'd get nullrefexceptions. Better safe than sorry, and it's not like it's a lot of code either:

// RimWorld.CompProperties_Explosive
public override void ResolveReferences(ThingDef parentDef)
{
base.ResolveReferences(parentDef);
if (this.explosiveDamageType == null)
{
this.explosiveDamageType = DamageDefOf.Bomb;
}
}

abaddon16

Brilliant, you are a life saver.

There's a lot to modding I have yet to learn like "is there a specific order defined file names are loaded such as 'preinit.cs' gets loaded first?"... so many questions, so much to learn. It's hard to break apart the bigger mods to see what really makes them tick, there's so many files. But, no better way to learn than to get started I guess.

One day, if I can't find it before then, I'm gojng to do a full code documentation thing like the JavaDocs. Because damn it would be nice to have. And then work on a best practices maybe... all the work to do, just need to get working.

Mehni

As you can read from one of the Stacktraces posted above, it roughly starts at Verse.Root and then goes to Verse.PlayDataLoad.LoadAllPlayData. The call from there to DoPlayLoad is most interesting for us: in there we get LoadAllActiveMods. This loads the xml and everything inheriting from Verse.Mod, loads the XML and finally  applies the xpath patches. After that DoPlayLoad continues resolving references and does a bunch of other stuff, and then shortly before the main menu is loaded all classes with the StaticConstructorOnStartup attribute get called in the main thread.

Therein also lies the explanation for why you get the warning to resolve references. You can load classes with refs to defs the game doesn't understand yet.

Also note that RimWorld heavily makes use of C#'s Reflection and Activator to load and instantiate classes. Good luck finding how "LoadDataFromXmlCustom" gets called using only the Analyze function of your decompiler ;)

lemme throw out a link to the discord too: https://discord.gg/rimworld - more modders there.

And remember: all big mods started out small.

abaddon16

Alright, found a solution for you OP, if Mehni's idea didn't work for you. It's counterintuitive to me, but it works as far as my testing has gone.

Create a class with a [DefOf] tag to hold your DefOfs. Create the variables, static, and leave them be. The below code is my class file to make a tranq gun. `DefsOf_Static` is my version of the class I mentioned above.


namespace NonLethalWeapons {
[DefOf]
class DefOfs_Static{
public static HediffDef Anesthetic;
public static ThoughtDef AteFineMeal;
}

public class ThingDef_TranquilizerBullet:ThingDef {
public float AddHediffChance = 0.45f;
public List<HediffDef> HediffsToAdd = new List<HediffDef> { DefOfs_Static.Anesthetic };
public List<ThoughtDef> ThoughtDefToAdd = new List<ThoughtDef> { DefOfs_Static.AteFineMeal };
}

public class Projectile_TranquilizerBullet:Bullet {
#region Properties
public ThingDef_TranquilizerBullet Def => this.def as ThingDef_TranquilizerBullet;
#endregion Properties

#region Overrides
protected override void Impact(Thing hitThing) {
base.Impact(hitThing);

if(Def!=null && hitThing!=null && hitThing is Pawn hitPawn){
foreach(HediffDef HediffToAdd in Def.HediffsToAdd) {
var rand = Rand.Value;
if(rand<=Def.AddHediffChance) {
var hediffOnPawn = hitPawn?.health?.hediffSet?.GetFirstHediffOfDef(HediffToAdd);
float randomSeverity = Rand.Range(0.1f, 0.3f);
if(hediffOnPawn!=null) hediffOnPawn.Severity+=randomSeverity;
else {
Hediff hediff = HediffMaker.MakeHediff(HediffToAdd, hitPawn, null);
hediff.Severity=randomSeverity;
hitPawn.health.AddHediff(hediff, null, null);
}
hitPawn.needs.mood.thoughts.memories.
}
}
}
}
#endregion Overrides
}
}


For you, you could do the below. I am assuming you have an `Impact` function below the cutoff of the copied code? That also would be an issue that would cause your bullet to not collide. Hope this helps! (or at least helps someone)


using System;
using RimWorld;
using Verse;
using UnityEngine;

namespace TurretCollection {
    [DefOf]                                       //<-----------------------------------------------------
    class DefOfs_Static{
        public static DamageDef Bullet;
    }
    public class CompProperties_ProjectileExtraDamage : CompProperties {
        public string hitText = "TC_Hit";
        public Color hitTextColor = new Color32(255, 153, 102, 255);
        public int damageAmountBase = 1;
        public DamageDef damageDef = DefOfs_Static.Bullet;  //<---------------------------------

        public CompProperties_ProjectileExtraDamage() {
            base.compClass = typeof(CompProjectileExtraDamage);
        }
    }

    public class CompProjectileExtraDamage : ThingComp {
        public CompProperties_ProjectileExtraDamage Props => (CompProperties_ProjectileExtraDamage)base.props;