Many Dll Questions

Started by Kirid, April 26, 2014, 04:06:49 AM

Previous topic - Next topic

Kirid

I have a lot of questions about dlls, I'm going to post them all here as opposed to separate threads or within another discussions. Other people are welcome to ask questions, and hopefully no question is too dumb. I've done up to step 5 of writing custom code and I have CAssembly pulled up in ILSpy. Maybe this will help create a few simple FAQ on dlls for the modding page.

First question: What does my 'using' header need to contain?
Most of the CAssembly dlls are only using System;. Only using others only when needed.
Does my modded code need to contain all of these, or just what it uses?   Is it bad to include them if not used, or is it good to include them all in case the code needs it?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using UnityEngine;
//using AI;      //if needed
//using Sound         
//using UI;


Second: When exactly do I overwrite/reitterate existing code, or should I only include what code I want to add? This is an example of trying to make a new SurfaceType.
Original:using System;
public enum SurfaceType
{
Any,
Light,
Heavy,
GrowSoil,
Diggable,
SmoothHard,
SmoothableStone
}


New Surface Type:using System;
public enum SurfaceType
{
Any,
Light,
Heavy,
GrowSoil,
Diggable,
SmoothHard,
SmoothableStone,
        NewSurface
}

ORusing System;
public enum SurfaceType
{
        NewSurface
}

Or would something like this work at all?
You can't rollerskate in a muffalo herd

Cala13er

Every class file in my industrial RIM mod uses this even though I don't use them all:
using UnityEngine;
using AI;
using Sound;
using UI;
using System;
using System.Linq;
using System.Collections.Generic;

Kirid

Quote from: Cala13er on April 26, 2014, 04:35:52 AM
Every class file in my industrial RIM mod uses this even though I don't use them all
(code)
Cool, that's what I thought. Question 1 answered thank you  ;)
You can't rollerskate in a muffalo herd

pawnstorm

QuoteFirst question: What does my 'using' header need to contain?
You should try to only add namespaces you actually use because it's good practice and on large projects it will make compilation slower since it has to run through a lot more namespaces. But it won't make a difference in your application or dll if you add using directives you don't actually use, it just tells the compiler where to look so it doesn't add anything. If you forget one, the compiler will fail to compile your project.

QuoteSecond: When exactly do I overwrite/reitterate existing code, or should I only include what code I want to add?
If you override things, you should include the code you want to keep. So in your example it's the first option.

WorldOfIllusion

For the second question:
Kind of related, if you are extending a class, say for example making a new kind of building that extends the original building class, when you work on adding code to a method already implemented (e.g. SpawnSetup()) its important that you also call base.SpawnSetup(). This will make your new class run the original method in the building class, as well as you're added code.
Code example:
    public class NewBuilding : Building
    {
        public override void SpawnSetup()
        {
            base.SpawnSetup(); // essentially calls Building.SpawnSetup()
            // your code here, e.g.
            stuffYouWantDone();
        }

        private void stuffYouWantDone()
        {
           
        }
    }
Artistically challenged modder seeking artistically talented texturer's help. Please, please, PM me :)

Kirid

I can't seem to compile this code from Visual Studios. I keep getting the error:
Error   1   Program 'C:\Users\Kirid\AppData\Local\Temporary Projects\NewSurface\obj\x86\Release\NewSurface.exe' does not contain a static 'Main' method suitable for an entry point   NewSurface
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using UnityEngine;
using AI;
using Sound;
using UI;

// namespace
// class
// public static void Main(string[] args)

public enum SurfaceType
{
    Any,
    Light,
    Heavy,
    GrowSoil,
    Diggable,
    SmoothHard,
    SmoothableStone,
    New
}

I've written up this code based on answers to the first 2 question, only including what I know is need.
I commented out what I'm not sure is needed or don't know how to type up.
Questions:
  What type of file is SurfaceType (in Cassembly)? Is it just an enumerator like it says?
  Is the error I'm getting due to something I need to change in Visual Studios, or a problem with my code?
  What namespace/class/staticvoidMain or other Method do I need to include to make this a dll that will edit the existing file?
You can't rollerskate in a muffalo herd

pawnstorm

#6
Well, first you need to compile a class library, not an application. You can change it in the project settings somewhere. Secondly, you can't just create something with the same name as in Assembly-Csharp, it'll just create name conflicts.
I don't know if you have any programming knowledge, but if this is your first experience with it it's probably going to be a very steep learning curve if you try to dive right in. Your best bet is probably to look at some of other people's mods and see if you can make little changes to those.

Edit: Maybe it'll help a little if you read a bit about object oriented programming in C#. This seems to be a nice brief yet informative article about it: http://www.c-sharpcorner.com/UploadFile/84c85b/object-oriented-programming-using-C-Sharp-net/

Most of the things you put in your library need to be defined first in an xml file, you can't just create classes and expect Rimworld to use them just cause they're there.

Kirid

#7
Quote from: pawnstorm on April 27, 2014, 12:35:11 PM
Well, first you need to compile a class library, not an application. You can change it in the project settings somewhere. Secondly, you can't just create something with the same name as in Assembly-Csharp, it'll just create name conflicts.
I don't know if you have any programming knowledge, but if this is your first experience with it it's probably going to be a very steep learning curve if you try to dive right in. Your best bet is probably to look at some of other people's mods and see if you can make little changes to those.

Edit: Maybe it'll help a little if you read a bit about object oriented programming in C#. This seems to be a nice brief yet informative article about it: http://www.c-sharpcorner.com/UploadFile/84c85b/object-oriented-programming-using-C-Sharp-net/

Most of the things you put in your library need to be defined first in an xml file, you can't just create classes and expect Rimworld to use them just cause they're there.
Okay I changed it to compile as a class library, that fixed the error, and I have the dll now. Thanks!

This is my first really hands-on experience. I've watched tons of tutorials and I have a C# book by me, so I have a good base understanding of what's going on. But most things fail to properly explain namespaces/classes/methods. It has been a steep learning curve, but reading and tinkering with these dlls has been super educational. I'm going to read more about Object Oriented Programming now.

The code for BattleFormations and PowerSwitches edits already existing classes. This example is just an enumerator, it doesn't seem to be contained inside a class, so I'm not sure what to do.
I'm trying to make a new surface type that will only be supported by sand (for jamieg's sandpit) It seemed like a simple enough first dll mod.
I already edited the TerrainType.xml so that Sand is the only terrain that supports the new surface.
You can't rollerskate in a muffalo herd

pawnstorm

BattleFormations doesn't really edit classes, I extend the Pawn class. It's derived from it, but it's not the same class, it has a different name for one. Which I did have to put in an xml file, otherwise it wouldn't interact with my class in any way. Without the xml reference, my class would just sit there doing nothing, like a sad poem, like a plant without light, like a civil servant behind his desk.

If there's any way to add something to that enumeration within the constraints of the current mod system, it's by finding out where it's used and replacing it there with your own enumeration. But you can't make changes directly to the Assembly-Csharp library. Sometimes you have to dig deep until you find something that leads to a value you can change in some xml file. And other times it just leads nowhere and you have to find some other way to do it :P

Tynan

Quote from: pawnstorm on April 27, 2014, 02:01:41 PMWithout the xml reference, my class would just sit there doing nothing, like a sad poem, like a plant without light, like a civil servant behind his desk.

This made my day.
Tynan Sylvester - @TynanSylvester - Tynan's Blog

Kirid

Changing <thingClass>Pawn</thingClass> to <thingClass>BattleFormations.XPawn</thingClass> is the xml reference you're talking about right? I get that. Just making the class used.
Quote from: pawnstorm on April 27, 2014, 02:01:41 PM
you can't make changes directly to the Assembly-Csharp library. Sometimes you have to dig deep until you find something that leads to a value you can change in some xml file.
This really put things into perspective. Thank you for taking time to explain all this to me, I've learned an incredible amount today. I was able to succesfully edit your code a tiny bit.
You can't rollerskate in a muffalo herd

Kirid

I've written a bunch of code up now, all of it looks pretty nice, but I cant finish it without writing new IEnumerables.
I understand what they do. They use toils to direct what the pawns do.

Where if anywhere can I find the code for all the IEnumerables? I know where all the toils are located.

Does anyone know what this specific little chunk from the Building_Bed does? I need to define a new Building class for my MedBeds.
public override IEnumerable<Command> GetCommands()
{
Building_Bed.<GetCommands>c__IteratorC <GetCommands>c__IteratorC = new Building_Bed.<GetCommands>c__IteratorC();
<GetCommands>c__IteratorC.<>f__this = this;
Building_Bed.<GetCommands>c__IteratorC expr_0E = <GetCommands>c__IteratorC;
expr_0E.$PC = -2;
return expr_0E;
}


This is my failed attempt to basically rewrite the TakeToBed/Rescue JobDriver. I'm wondering if anyone knows how it should be written, or a better way to acheive the same result.
class JobDriver_TakeToMedBed : JobDriver

    {protected Pawn Takee
            get
                return (Pawn)base.CurJob.targetA.Thing;
        protected Building_MedBed DropBed
            get
                return (Building_MedBed)base.CurJob.targetB.Thing;
        public JobDriver_TakeToMedBed(Pawn pawn) : base(pawn)

        protected override IEnumerable<Toil> MakeNewToils()
        {
            //yield return Toils_Goto.GotoThing(TargetIndex.A, PathMode.ClosestTouch);
            //yield return Toils_Haul.StartCarryThing(TargetIndex.A);
            //yield return Toils_Haul.
        }
    }
You can't rollerskate in a muffalo herd

Tynan

#12
The original of that bed code:




public override IEnumerable<Command> GetCommands()
{
Command_Action com = new Command_Action();
com.hotKey = KeyCode.F;
if( !forPrisoners )
{
com.defaultLabel = "CommandBedSetForPrisonersLabel".Translate();
com.icon = ContentFinder<Texture2D>.Get("UI/Commands/ForPrisonersOff");
com.defaultDesc = "CommandBedSetForPrisonersDesc".Translate();
com.activateSound = UISounds.CheckboxTurnedOn;
com.action = ()=>TrySetForPrisonersByInterface(true);
}
else
{
com.defaultLabel = "CommandBedSetForColonistsLabel".Translate();
com.icon = ContentFinder<Texture2D>.Get("UI/Commands/ForPrisonersOn");
com.defaultDesc = "CommandBedSetForColonistsDesc".Translate();
com.activateSound = UISounds.CheckboxTurnedOff;
com.action = ()=> TrySetForPrisonersByInterface(false);
}
com.groupKey = 91852;

yield return com;
}


Be careful with enumerable code and decompilers. Enumerable-related keywords make the compiler generate and optimize a lot of complex code. Most decompilers seem to hide this code from you for the most part.

To see what these enumerator methods are actually doing you'll have to find the option in your decompiler that makes it show all the code (including compiler-generated). And then try to figure out how to read it. It's a difficult area when you're looking at decompiled code.


Also, for reference, the original TakeToBed JobDriver. Yeah, AI code is hard. Simple things get complex when you have to account for every possible interruption or outcome.


using UnityEngine;
using System.Collections;
using System.Collections.Generic;



namespace AI{
public class JobDriver_TakeToBed : JobDriver
{
//Shortcut properties
protected Pawn Takee {get{return (Pawn)CurJob.targetA.Thing;}}
protected Building_Bed DropBed {get{return (Building_Bed)CurJob.targetB.Thing;}}

public JobDriver_TakeToBed(Pawn pawn) : base(pawn){}

protected override IEnumerable<Toil> MakeNewToils()
{
//Reserve the takee
yield return Toils_Reserve.ReserveTarget( TargetIndex.A, ReservationType.Total );

//Reserve the bed
yield return Toils_Reserve.ReserveTarget( TargetIndex.B, ReservationType.Total );


//Claim the bed for the takee
Toil claimBed = new Toil();
claimBed.initAction = ()=>
{
if( Takee.ownership.ownedBed != DropBed )
Takee.ownership.ClaimBed(DropBed);
};
yield return claimBed;


//Goto takee
yield return Toils_Goto.GotoThing( TargetIndex.A, PathMode.ClosestTouch )
   .FailOnDespawnedOrForbidden(TargetIndex.A, TargetIndex.B)
   .FailOn(()=>DropBed.owner != Takee) //Abandon if takee loses bed ownership
   .FailOn(()=>CurJob.def == JobDefOf.Arrest && !Takee.CanBeArrested())//Abandon arrest if takee is not of a team who is willing to be arrested
   .FailOn(()=>!Takee.CanReach( Takee.ownership.ownedBed, PathMode.OnSquare ) );

//Start carrying the takee
yield return Toils_Haul.StartCarryThing(TargetIndex.A);

//Change takee to prisoner if necessary
Toil makePrisoner = new Toil();
makePrisoner.initAction = ()=>
{
if(  CurJob.def == JobDefOf.Arrest || CurJob.def == JobDefOf.Capture )
{
if( Takee.JailerFaction != Faction.OfColony )
{
Takee.SetJailerFaction( Faction.OfColony );
Takee.inventory.container.RemoveAll(t => t.def == ThingDefOf.DoorKey);
}
}
};
yield return makePrisoner;

yield return Toils_Goto.GotoThing( TargetIndex.B, PathMode.Touch )
.FailOnDestroyed( TargetIndex.A );
//Note no failure conditions here
//Because otherwise it's easy to get wardens to drop prisoners in arbitrary places.
//I'd rather they just go to wherever they were going.
// if( DropBed.owner != Takee )
// return true;
//return !Toils.CanInteractStandard(DropBed);


//Unreserve bed so takee can use it
yield return Toils_Reserve.UnreserveTarget( TargetIndex.B, ReservationType.Total );


//Drop in or near bed
Toil tuckIntoBed = new Toil();
tuckIntoBed.initAction = () =>
{
//Note: We don't stop the task if the bed is destroyed or changes ownership
//because then the wardens drop prisoners at random points and they escape
//So we have to handle some ugly cases here

IntVec3 dropPos;

//Should we place them over the bed?
// Note that we always do for prisoners, while solves issues with dropping a prisoner in a doorway
// When the bed is adjacent the door
if( Takee.Incapacitated || !Takee.IsColonist )
dropPos = DropBed.Position; //This may be drawing on the position of a destroyed bed
else
dropPos = pawn.Position;

Thing unused;
pawn.carryHands.TryDropCarriedThing( dropPos, ThingPlaceMode.Direct, out unused );

//Should we tuck them into bed?
if( Takee.Incapacitated
   && DropBed.owner == Takee  //They could have lost ownership and the last toil would continue
   && !DropBed.destroyed ) //Bed could have been destroyed and the last toil would continue
{
Takee.jobs.Notify_TuckedIntoBed(DropBed);
}

if( Takee.IsPrisonerOfColony )
Find.ConceptTracker.TeachOpportunity(ConceptDefOf.PrisonerTab, Takee, OpportunityType.GoodToKnow );

};
tuckIntoBed.defaultCompleteMode = ToilCompleteMode.Instant;
yield return tuckIntoBed;

}
}}
Tynan Sylvester - @TynanSylvester - Tynan's Blog

Kirid

Quote from: Tynan on April 30, 2014, 01:20:34 AM
The original of that bed code:
Also, for reference, the original TakeToBed JobDriver. Yeah, AI code is hard.
Awesome! Thanks Tynan!  ;D It's hard but I'm having a lot of fun. I got like double the game for my money.
You can't rollerskate in a muffalo herd

StorymasterQ

I see a lot of Toils but no Boils nor Trouble.

Anyway, with this knowledge, can we finally have Pawns that can sleep on any bed? As in, changing their reserved beds to whatever closest to them? This could make for faraway outposts where colonists can come by, spend a day or two there, then go back to the main base.
Maybe even the hotel thing when faction members visit.
I like how this game can result in quotes that would be quite unnerving when said in public, out of context. - Myself

The dubious quotes list is now public. See it here