Door texture problem

Started by Sandy, February 05, 2018, 05:20:58 PM

Previous topic - Next topic

Sandy

Hi. i'm trying to make a double door mod. and no matter how big a texture i load up, no matter how big a <size> and <drawSize> i set, the resulting door's picture is only one tile wide, always.
it will take up the space i specified in <size> and <drawSize>, like (2,3),(3,3), but the actual image is the (1,1) door at the center of the taken up space.
if i set it (3,3), the door will take all the nine tiles, but the pic will only appear in the center of it.
i have tried different textures, with different size and drawsize, but to no avail.
so i think the door is hardcoded to appear in (1,1) tile, and its "slide to open" animation.

Can anyone tell me where is the code part(probably inside Building_Door thingClass) corresponding to the door appearance is, please?
thanks in advance..

P.S. im not a coder, so if u can explain in plain english, i would appreciate that..

Sandy

ok, this below is the Building_Door code

using System;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
using Verse;
using Verse.AI;
using Verse.AI.Group;
using Verse.Sound;

namespace RimWorld
{
public class Building_Door : Building
{
public CompPowerTrader powerComp;

private bool openInt;

private bool holdOpenInt;

private int lastFriendlyTouchTick = -9999;

protected int ticksUntilClose;

protected int visualTicksOpen;

private bool freePassageWhenClearedReachabilityCache;

private const float BaseDoorOpenTicks = 45f;

private const int AutomaticCloseDelayTicks = 60;

private const int ApproachCloseDelayTicks = 300;

private const int MaxTicksSinceFriendlyTouchToAutoClose = 301;

private const float VisualDoorOffsetStart = 0f;

private const float VisualDoorOffsetEnd = 0.45f;

public bool Open
{
get
{
return this.openInt;
}
}

public bool HoldOpen
{
get
{
return this.holdOpenInt;
}
}

public bool FreePassage
{
get
{
return this.openInt && (this.holdOpenInt || !this.WillCloseSoon);
}
}

public bool WillCloseSoon
{
get
{
if (!base.Spawned)
{
return true;
}
if (!this.openInt)
{
return true;
}
if (this.holdOpenInt)
{
return false;
}
if (this.ticksUntilClose > 0 && this.ticksUntilClose <= 60 && this.CanCloseAutomatically)
{
return true;
}
for (int i = 0; i < 5; i++)
{
IntVec3 c = base.Position + GenAdj.CardinalDirectionsAndInside[i];
if (c.InBounds(base.Map))
{
List<Thing> thingList = c.GetThingList(base.Map);
for (int j = 0; j < thingList.Count; j++)
{
Pawn pawn = thingList[j] as Pawn;
if (pawn != null && !pawn.HostileTo(this) && !pawn.Downed)
{
if (pawn.Position == base.Position || (pawn.pather.MovingNow && pawn.pather.nextCell == base.Position))
{
return true;
}
}
}
}
}
return false;
}
}

public bool BlockedOpenMomentary
{
get
{
List<Thing> thingList = base.Position.GetThingList(base.Map);
for (int i = 0; i < thingList.Count; i++)
{
Thing thing = thingList[i];
if (thing.def.category == ThingCategory.Item || thing.def.category == ThingCategory.Pawn)
{
return true;
}
}
return false;
}
}

public bool DoorPowerOn
{
get
{
return this.powerComp != null && this.powerComp.PowerOn;
}
}

public bool SlowsPawns
{
get
{
return !this.DoorPowerOn || this.TicksToOpenNow > 20;
}
}

public int TicksToOpenNow
{
get
{
float num = 45f / this.GetStatValue(StatDefOf.DoorOpenSpeed, true);
if (this.DoorPowerOn)
{
num *= 0.25f;
}
return Mathf.RoundToInt(num);
}
}

private bool CanCloseAutomatically
{
get
{
return this.DoorPowerOn && this.FriendlyTouchedRecently;
}
}

private bool FriendlyTouchedRecently
{
get
{
return Find.TickManager.TicksGame < this.lastFriendlyTouchTick + 301;
}
}

private int VisualTicksToOpen
{
get
{
return this.TicksToOpenNow;
}
}

public override bool FireBulwark
{
get
{
return !this.Open && base.FireBulwark;
}
}

public override void PostMake()
{
base.PostMake();
this.powerComp = base.GetComp<CompPowerTrader>();
}

public override void SpawnSetup(Map map, bool respawningAfterLoad)
{
base.SpawnSetup(map, respawningAfterLoad);
this.powerComp = base.GetComp<CompPowerTrader>();
this.ClearReachabilityCache(map);
if (this.BlockedOpenMomentary)
{
this.DoorOpen(60);
}
}

public override void DeSpawn()
{
Map map = base.Map;
base.DeSpawn();
this.ClearReachabilityCache(map);
}

public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look<bool>(ref this.openInt, "open", false, false);
Scribe_Values.Look<bool>(ref this.holdOpenInt, "holdOpen", false, false);
Scribe_Values.Look<int>(ref this.lastFriendlyTouchTick, "lastFriendlyTouchTick", 0, false);
if (Scribe.mode == LoadSaveMode.LoadingVars && this.openInt)
{
this.visualTicksOpen = this.VisualTicksToOpen;
}
}

public override void SetFaction(Faction newFaction, Pawn recruiter = null)
{
base.SetFaction(newFaction, recruiter);
if (base.Spawned)
{
this.ClearReachabilityCache(base.Map);
}
}

public override void Tick()
{
base.Tick();
if (this.FreePassage != this.freePassageWhenClearedReachabilityCache)
{
this.ClearReachabilityCache(base.Map);
}
if (!this.openInt)
{
if (this.visualTicksOpen > 0)
{
this.visualTicksOpen--;
}
if ((Find.TickManager.TicksGame + this.thingIDNumber.HashOffset()) % 375 == 0)
{
GenTemperature.EqualizeTemperaturesThroughBuilding(this, 1f, false);
}
}
else if (this.openInt)
{
if (this.visualTicksOpen < this.VisualTicksToOpen)
{
this.visualTicksOpen++;
}
if (!this.holdOpenInt)
{
if (base.Map.thingGrid.CellContains(base.Position, ThingCategory.Pawn))
{
this.ticksUntilClose = 60;
}
else
{
this.ticksUntilClose--;
if (this.ticksUntilClose <= 0 && this.CanCloseAutomatically)
{
this.DoorTryClose();
}
}
}
if ((Find.TickManager.TicksGame + this.thingIDNumber.HashOffset()) % 22 == 0)
{
GenTemperature.EqualizeTemperaturesThroughBuilding(this, 1f, false);
}
}
}

public void FriendlyTouched()
{
this.lastFriendlyTouchTick = Find.TickManager.TicksGame;
}

public void Notify_PawnApproaching(Pawn p)
{
if (!p.HostileTo(this))
{
this.FriendlyTouched();
}
if (this.PawnCanOpen(p))
{
base.Map.fogGrid.Notify_PawnEnteringDoor(this, p);
if (!this.SlowsPawns)
{
this.DoorOpen(300);
}
}
}

public bool CanPhysicallyPass(Pawn p)
{
return this.FreePassage || this.PawnCanOpen(p);
}

public virtual bool PawnCanOpen(Pawn p)
{
Lord lord = p.GetLord();
return (lord != null && lord.LordJob != null && lord.LordJob.CanOpenAnyDoor(p)) || (p.IsWildMan() && !p.mindState.wildManEverReachedOutside) || base.Faction == null || (p.guest != null && p.guest.Released) || GenAI.MachinesLike(base.Faction, p);
}

public override bool BlocksPawn(Pawn p)
{
return !this.openInt && !this.PawnCanOpen(p);
}

protected void DoorOpen(int ticksToClose = 60)
{
this.ticksUntilClose = ticksToClose;
if (!this.openInt)
{
this.openInt = true;
if (this.DoorPowerOn)
{
this.def.building.soundDoorOpenPowered.PlayOneShot(new TargetInfo(base.Position, base.Map, false));
}
else
{
this.def.building.soundDoorOpenManual.PlayOneShot(new TargetInfo(base.Position, base.Map, false));
}
}
}

protected void DoorTryClose()
{
if (this.holdOpenInt || this.BlockedOpenMomentary)
{
return;
}
this.openInt = false;
if (this.DoorPowerOn)
{
this.def.building.soundDoorClosePowered.PlayOneShot(new TargetInfo(base.Position, base.Map, false));
}
else
{
this.def.building.soundDoorCloseManual.PlayOneShot(new TargetInfo(base.Position, base.Map, false));
}
}

public void StartManualOpenBy(Pawn opener)
{
this.DoorOpen(60);
}

public void StartManualCloseBy(Pawn closer)
{
this.DoorTryClose();
}

public override void Draw()
{
base.Rotation = Building_Door.DoorRotationAt(base.Position, base.Map);
float num = Mathf.Clamp01((float)this.visualTicksOpen / (float)this.VisualTicksToOpen);
float d = 0.45f * num;
for (int i = 0; i < 2; i++)
{
Vector3 vector = default(Vector3);
Mesh mesh;
if (i == 0)
{
vector = new Vector3(0f, 0f, -1f);
mesh = MeshPool.plane10;
}
else
{
vector = new Vector3(0f, 0f, 1f);
mesh = MeshPool.plane10Flip;
}
Rot4 rotation = base.Rotation;
rotation.Rotate(RotationDirection.Clockwise);
vector = rotation.AsQuat * vector;
Vector3 vector2 = this.DrawPos;
vector2.y = Altitudes.AltitudeFor(AltitudeLayer.DoorMoveable);
vector2 += vector * d;
Graphics.DrawMesh(mesh, vector2, base.Rotation.AsQuat, this.Graphic.MatAt(base.Rotation, null), 0);
}
base.Comps_PostDraw();
}

private static int AlignQualityAgainst(IntVec3 c, Map map)
{
if (!c.InBounds(map))
{
return 0;
}
if (!c.Walkable(map))
{
return 9;
}
List<Thing> thingList = c.GetThingList(map);
for (int i = 0; i < thingList.Count; i++)
{
Thing thing = thingList[i];
if (typeof(Building_Door).IsAssignableFrom(thing.def.thingClass))
{
return 1;
}
Thing thing2 = thing as Blueprint;
if (thing2 != null)
{
if (thing2.def.entityDefToBuild.passability == Traversability.Impassable)
{
return 9;
}
if (typeof(Building_Door).IsAssignableFrom(thing.def.thingClass))
{
return 1;
}
}
}
return 0;
}

/*public static Rot4 DoorRotationAt(IntVec3 loc, Map map)
{
int num = 0;
int num2 = 0;
num += Building_Door.AlignQualityAgainst(loc + IntVec3.East, map);
num += Building_Door.AlignQualityAgainst(loc + IntVec3.West, map);
num2 += Building_Door.AlignQualityAgainst(loc + IntVec3.North, map);
num2 += Building_Door.AlignQualityAgainst(loc + IntVec3.South, map);
if (num >= num2)
{
return Rot4.North;
}
return Rot4.East;
}*/

[DebuggerHidden]
public override IEnumerable<Gizmo> GetGizmos()
{
foreach (Gizmo g in base.GetGizmos())
{
yield return g;
}
if (base.Faction == Faction.OfPlayer)
{
yield return new Command_Toggle
{
defaultLabel = "CommandToggleDoorHoldOpen".Translate(),
defaultDesc = "CommandToggleDoorHoldOpenDesc".Translate(),
hotKey = KeyBindingDefOf.Misc3,
icon = TexCommand.HoldOpen,
isActive = (() => this.$this.holdOpenInt),
toggleAction = delegate
{
this.$this.holdOpenInt = !this.$this.holdOpenInt;
}
};
}
}

private void ClearReachabilityCache(Map map)
{
map.reachability.ClearCache();
this.freePassageWhenClearedReachabilityCache = this.FreePassage;
}
}
}

and i think this part refers to the "slide to open" animation

public override void Draw()
{
base.Rotation = Building_Door.DoorRotationAt(base.Position, base.Map);
float num = Mathf.Clamp01((float)this.visualTicksOpen / (float)this.VisualTicksToOpen);
float d = 0.45f * num;
for (int i = 0; i < 2; i++)
{
Vector3 vector = default(Vector3);
Mesh mesh;
if (i == 0)
{
vector = new Vector3(0f, 0f, -1f);
mesh = MeshPool.plane10;
}
else
{
vector = new Vector3(0f, 0f, 1f);
mesh = MeshPool.plane10Flip;
}
Rot4 rotation = base.Rotation;
rotation.Rotate(RotationDirection.Clockwise);
vector = rotation.AsQuat * vector;
Vector3 vector2 = this.DrawPos;
vector2.y = Altitudes.AltitudeFor(AltitudeLayer.DoorMoveable);
vector2 += vector * d;
Graphics.DrawMesh(mesh, vector2, base.Rotation.AsQuat, this.Graphic.MatAt(base.Rotation, null), 0);
}
base.Comps_PostDraw();
}


where in the code is the door hardcoded for 1x1 tile? anyone plz help.. thanks in advance..

jamaicancastle

The vector "vector" (a helpful label I'm sure you will agree) is the offset of the door when it's completely open. This is then multiplied by the float "d", which is the percentage of the way the door is open, and then added to the vector "vector2", which is the door's actual drawing position. In order for the door to slide open further, you'll want to change the numbers assigned to "vector" in the if/else block in the middle of Draw().

As for how large the doors are, that seems to be controlled by the call to MeshPool.plane10 and .plane10Flip, which specify a 1x1-sized mesh. It looks like you should be able to make arbitrarily-sized meshes using MeshPool.GridPlane() and .GridPlaneFlip(), respectively, but I'm not really up on how meshes work.

Sandy

Quote from: jamaicancastle on February 06, 2018, 08:55:36 AM
The vector "vector" (a helpful label I'm sure you will agree) is the offset of the door when it's completely open. This is then multiplied by the float "d", which is the percentage of the way the door is open, and then added to the vector "vector2", which is the door's actual drawing position. In order for the door to slide open further, you'll want to change the numbers assigned to "vector" in the if/else block in the middle of Draw().

As for how large the doors are, that seems to be controlled by the call to MeshPool.plane10 and .plane10Flip, which specify a 1x1-sized mesh. It looks like you should be able to make arbitrarily-sized meshes using MeshPool.GridPlane() and .GridPlaneFlip(), respectively, but I'm not really up on how meshes work.

first of all, thanks for the reply.
and ikr, its like the labels are made to make modding doors impossible.. lol
yes, i was just going thru unity docs to understand vectors..
wait, 'd' is not the speed of the door sliding? i read in another forum that 0.45f is related to door opening speeds of different materials, so i thought its the speed factor..
so, "vector" is the target position, "vector2" is the original position and "vector3" controls the sliding animation. am i correct in guessing these?

so mesh pool is the culprit, huh.. its in the list of things i wanted to go thru..
currently, i copied the Building_Door.cs into my Building_DoubleDoor.cs, made a dll file.. somehow while compiling, sharpdevelop doesnt recognise "$" in C#6, so i have to download visual studio, which i'll do tonight.. i was planning on tinkering with that and see how it affects in-game..
now, i'll just go straight to meshpool and try to understand it..

thx man, u saved me alot of time..

also, while doing google search, i came across a "ProjectHaron" door mod, WIP by Jecrell, last update was in Aug, 2017.. it was kinda incomplete now.. i wonder if he will finish it in B18..

jamaicancastle

So there are two sets of terms. "vector" and "vector2" are the names of variables. "Vector2" and "Vector3" are types of variables - in this case, they determine how many directions the vector has. (There's a technical term for this which I'm forgetting.) So basically a Vector2 is a set of two numbers, a Vector3 is a set of three numbers. "vector2", just to cause maximum confusion, is a Vector3. :(

As far as d - the variable above it, num, finds out how many ticks the door has been opening for. Dividing that by the rate of opening yields the percentage of "openness" it has.

Sandy

Quote from: jamaicancastle on February 06, 2018, 08:41:34 PM
So there are two sets of terms. "vector" and "vector2" are the names of variables. "Vector2" and "Vector3" are types of variables - in this case, they determine how many directions the vector has. (There's a technical term for this which I'm forgetting.) So basically a Vector2 is a set of two numbers, a Vector3 is a set of three numbers. "vector2", just to cause maximum confusion, is a Vector3. :(

As far as d - the variable above it, num, finds out how many ticks the door has been opening for. Dividing that by the rate of opening yields the percentage of "openness" it has.
Really thanks man. i'll have to note this down for reference..

also, while decompiling Building_Door.cs, both dnspy and ILspy gives me different codes for the following section,

ILspy version,

[DebuggerHidden]
public override IEnumerable<Gizmo> GetGizmos()
{
foreach (Gizmo g in base.GetGizmos())
{
yield return g;
}
if (base.Faction == Faction.OfPlayer)
{
yield return new Command_Toggle
{
defaultLabel = "CommandToggleDoorHoldOpen".Translate(),
defaultDesc = "CommandToggleDoorHoldOpenDesc".Translate(),
hotKey = KeyBindingDefOf.Misc3,
icon = TexCommand.HoldOpen,
isActive = (() => this.$this.holdOpenInt),
toggleAction = delegate
{
this.$this.holdOpenInt = !this.$this.holdOpenInt;
}
};
}
}


dnspy version,

public override IEnumerable<Gizmo> GetGizmos()
{
foreach (Gizmo g in this.<GetGizmos>__BaseCallProxy0())
{
yield return g;
}
if (base.Faction == Faction.OfPlayer)
{
yield return new Command_Toggle
{
defaultLabel = "CommandToggleDoorHoldOpen".Translate(),
defaultDesc = "CommandToggleDoorHoldOpenDesc".Translate(),
hotKey = KeyBindingDefOf.Misc3,
icon = TexCommand.HoldOpen,
isActive = (() => this.$this.holdOpenInt),
toggleAction = delegate
{
this.$this.holdOpenInt = !this.$this.holdOpenInt;
}
};
}
yield break;
}

which one should i consider? plz help.
also, this part in the both versions gives me error while building,

isActive = (() => this.$this.holdOpenInt),
toggleAction = delegate
{
this.$this.holdOpenInt = !this.$this.holdOpenInt;
}

in Sharpdevelop, it said "$" is a C#6 feature and it doesnt support that. so i downloaded VS 2017 and set it to C#6 while building, but that also gave me same error. if u can tell me how to solve this, will be really helpful..
and really thanks for ur time..

jamaicancastle

The only difference I see between the two is the yield break; at the end, which shouldn't actually do anything. (Normally a break would be somewhere in the middle and prevent anything after it from being run; in this case there isn't anything after it.) You can probably disregard that.

As for the delegate - you should be able to simply refer to holdOpenInt without any preface, since it's part of the same class. My guess is that the this.$this is an artifact of how it's compiled and then decompiled.

Sandy

#7
Quote from: jamaicancastle on February 07, 2018, 10:51:38 AM
The only difference I see between the two is the yield break; at the end, which shouldn't actually do anything. (Normally a break would be somewhere in the middle and prevent anything after it from being run; in this case there isn't anything after it.) You can probably disregard that.

As for the delegate - you should be able to simply refer to holdOpenInt without any preface, since it's part of the same class. My guess is that the this.$this is an artifact of how it's compiled and then decompiled.
Thanks very much. i deleted "$this." before and was able to build it. but didnt know if thats important, so i put the whole gizmo part as comment. now i'll go edit it again.. also,
foreach (Gizmo g in this.<GetGizmos>__BaseCallProxy0())
this line(from dnSpy) is giving errors, so i'll just use the ILSpy line instead of this.

And, i have another doubt. i'm trying to make a door mod. for now, i'm just planning to duplicate the "Building_Door" class and make that work, so i can use it to create a new door later. i was using ILSpy before, but now that i used dnSpy, i noticed something strange.
"Building" is the base class for "Building_Door" and also for my duplicate.
but, look at this pic below. its the original code.

that "base" refers to Verse.Thing.. whereas, in my duplicate, base refers to "Building".
i understand why my duplicated code shows that. but how does the real code works? i need to know this, so i can duplicate the code correctly.

also, how does this line of code works?
base.Map.fogGrid.Notify_PawnEnteringDoor(this, p);

it seems that function is hardcoded to accept only Building_Door as argument. how to change this?
thanks in advance..

jamaicancastle

Quote from: Sandy on February 07, 2018, 11:33:39 AM
also, foreach (Gizmo g in this.<GetGizmos>__BaseCallProxy0())
this line(from dnSpy) is giving errors, so i'll just use the ILSpy line instead of this.
I can't believe I missed that part. What ILSpy has is correct in that case. You want to call base.GetGizmos, the parent class's method. I think in this case the compiler adds something onto the function that ILSpy correctly got rid of, whereas dnSpy didn't.

Gizmos are basically the buttons you see when you select something in-game. In this case that code controls the "hold open" button, but it also wants to go find all of the basic building buttons too, hence the parent call.

Quotethat "base" refers to Verse.Thing.. whereas, in my duplicate, base refers to "Building".
i understand why my duplicated code shows that. but how does the real code works? i need to know this, so i can duplicate the code correctly.
It's a case of multiple inheritance. Building_Door.Spawned inherits from Building.Spawned which inherits from Thing.Spawned. When the IDE compiles, it goes and chases down the reference until it finds the parent that actually has the field in question. Since ILSpy/dnSpy look up the compiled code, they have the final reference in hand rather than the immediate one. For all intents and purposes they should be the same, since (I'm assuming) Building doesn't add its own definition of Spawned.

Quoteit seems that function is hardcoded to accept only Building_Door as argument. how to change this?
You can't change it, but you do have a couple of workarounds. One is to create a new function that does basically the same thing, which sounds annoying and time-intensive. The other is to have your custom class inherit from Building_Door instead of Building, which would allow you to cast the object as a Building_Door, then pass it to that function.

As for what that does - just by the name, I assume it has to do with the "undiscovered" effect on mountains and the like. I don't know if that's ever actually used in the base game (maybe it is in base raids, which I don't do much of) but it shouldn't be an issue for your double doors.

Sandy

Quote from: jamaicancastle on February 07, 2018, 04:24:12 PM
Quoteit seems that function is hardcoded to accept only Building_Door as argument. how to change this?
You can't change it, but you do have a couple of workarounds. One is to create a new function that does basically the same thing, which sounds annoying and time-intensive. The other is to have your custom class inherit from Building_Door instead of Building, which would allow you to cast the object as a Building_Door, then pass it to that function.

As for what that does - just by the name, I assume it has to do with the "undiscovered" effect on mountains and the like. I don't know if that's ever actually used in the base game (maybe it is in base raids, which I don't do much of) but it shouldn't be an issue for your double doors.

really thanks man.
i actually copied the Verse.FogGrid class and edited my custom argument in this function, but no change in syntax in my custom door class, it still said Building_Door. i commented it and built the dll, went in-game and my door's not considered as a door.
so, i'm back to using Building_Door as base class, again.

anyway, i can build the door(for now, only (1x1) door) and it is considered as a door. but its not opening or closing, visually. i mean, pawns seem to open the door and go inside and come out, i can hear the door opening and closing sounds. its just the opening and closing animation is not working.

also, i came upon something strange earlier. i actually went into the rimworld source folder and found Building_Door.cs in there, inside Buildings. i double-clicked it and it opened in Sharpdevelop. and the code inside is mostly different from dnSpy/ILSpy. different in how the variables are named and how the code has understandable comments, that neither dnSpy or ILSpy had/showed. so, i made a new mod with this code, for now.
the door animation not working is an error i already had before, with the other 2 codes. so its not caused by the new code.
if i can make it work, i'll go see how to change it to double door.

also, i wanted to ask u, u know so much about these codes and coding, why didnt YOU make the door mod yet? lack of time or lack of modding interest or its just not possible? sorry if my question offended u in anyway.

jamaicancastle

Well, I already have a number of mod ideas that I'm supposed to be working on, but am ignoring, soooo...  :D

As part of the compiling process, the compiler goes through and strips out a lot of information that isn't relevant to the compiled code - including comments, some of the niceties of layout, and I think a lot of local variable names. ILSpy/dnSpy try to rebuild what they can, such as putting in brackets in an approximation of a standard layout, but since comments are completely cut out of the compiled code, there's nothing it can do to get them back. I often forget there's the example source code since there isn't very much of it and it's easier to cross-reference everything in the decompiler, but definitely use that version if it's there.

As for how I learned all this, it's mostly self-taught, from looking through the code and hanging out on the modding discord channel. I don't actually have a lot of formal programming experience aside from like, a semester of Python back in college.

Sandy

Quote from: jamaicancastle on February 07, 2018, 05:45:02 PM
Well, I already have a number of mod ideas that I'm supposed to be working on, but am ignoring, soooo...  :D

As part of the compiling process, the compiler goes through and strips out a lot of information that isn't relevant to the compiled code - including comments, some of the niceties of layout, and I think a lot of local variable names. ILSpy/dnSpy try to rebuild what they can, such as putting in brackets in an approximation of a standard layout, but since comments are completely cut out of the compiled code, there's nothing it can do to get them back. I often forget there's the example source code since there isn't very much of it and it's easier to cross-reference everything in the decompiler, but definitely use that version if it's there.

As for how I learned all this, it's mostly self-taught, from looking through the code and hanging out on the modding discord channel. I don't actually have a lot of formal programming experience aside from like, a semester of Python back in college.

so, thats an example source code. no wonder there isnt many. but Building_Door.cs is there and thats all i need for now. i was wondering which one to use, but u also answered it. thanks.

i installed discord to join the modding server, but i dont know how to.. currently only in hardcore_sk mod discord. but i want to ask my doubts regarding mods.. can u provide an invite or link, if possible?

jamaicancastle

You can find the general RW discord link in this thread: https://ludeon.com/forums/index.php?topic=27941.0
There's roughly a million channels, one of which is the modding channel. :D

Sandy

Quote from: jamaicancastle on February 07, 2018, 06:00:48 PM
You can find the general RW discord link in this thread: https://ludeon.com/forums/index.php?topic=27941.0
There's roughly a million channels, one of which is the modding channel. :D

as if this door mod quest of mine isnt enough, i have to find one-in-a-million channel?
dang it.. but thanks anyway..

will u help me with the door animation thing?
this is the current code im using..

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.Sound;
using Verse.AI;
using Verse.AI.Group;

namespace SK_DoubleDoor
{
public class Building_DoubleDoor : Building_Door
{
//Links
public CompPowerTrader powerComp;

//Working vars - saved
private bool openInt = false;
private bool holdOpenInt = false; //Player has configured door to be/stay open
private int lastFriendlyTouchTick = -9999;

//Working vars - unsaved
protected int ticksUntilClose = 0;
protected int visualTicksOpen = 0;
private bool freePassageWhenClearedReachabilityCache;

//Constants
private const float BaseDoorOpenTicks = 45;
private const int AutomaticCloseDelayTicks = 60;
private const int ApproachCloseDelayTicks = 300; //Extra long in case pawn is very slow; don't want door to close before they arrive
private const int MaxTicksSinceFriendlyTouchToAutoClose = ApproachCloseDelayTicks + 1;
private const float VisualDoorOffsetStart = 0.0f;
private const float VisualDoorOffsetEnd = 0.45f;

//Properties
public bool Open { get { return openInt; } }
public bool HoldOpen { get { return holdOpenInt; } }
public bool FreePassage
{
get
{
//Not open - never free passage
if( !openInt )
return false;

return holdOpenInt || !WillCloseSoon;
}
}
public bool WillCloseSoon
{
get
{
if( !Spawned )
return true;

//It's already closed
if( !openInt )
return true;

//It's held open -> so it won't be closed soon
if( holdOpenInt )
return false;

//Will close automatically soon
if( ticksUntilClose > 0 && ticksUntilClose <= 60 && CanCloseAutomatically )
return true;

//Check if there is any non-hostile non-downed pawn passing through, he will close the door
for( int i = 0; i < 5; i++ )
{
var c = Position + GenAdj.CardinalDirectionsAndInside[i];

if( !c.InBounds(Map) )
continue;

var things = c.GetThingList(Map);
for( int j = 0; j < things.Count; j++ )
{
var p = things[j] as Pawn;

if( p == null || p.HostileTo(this) || p.Downed )
continue;

if( p.Position == Position || (p.pather.MovingNow && p.pather.nextCell == Position) )
return true;
}
}

return false;
}
}
public bool BlockedOpenMomentary
{
get
{
var thingList = Position.GetThingList(Map);
for( int i=0; i<thingList.Count; i++ )
{
var t = thingList[i];
if( t.def.category == ThingCategory.Item
|| t.def.category == ThingCategory.Pawn)
return true;
}

return false;
}
}
public bool DoorPowerOn
{
get
{
return powerComp != null && powerComp.PowerOn;
}
}
public bool SlowsPawns
{
get
{
return !DoorPowerOn || TicksToOpenNow > 20;
}
}
public int TicksToOpenNow
{
get
{
float ticks = BaseDoorOpenTicks / this.GetStatValue( StatDefOf.DoorOpenSpeed );

if( DoorPowerOn )
ticks *= 0.25f;

return Mathf.RoundToInt(ticks);
}
}
private bool CanCloseAutomatically
{
get
{
return DoorPowerOn && FriendlyTouchedRecently;
}
}
private bool FriendlyTouchedRecently
{
get
{
return Find.TickManager.TicksGame < lastFriendlyTouchTick + MaxTicksSinceFriendlyTouchToAutoClose;
}
}
private int VisualTicksToOpen
{
get
{
//return Mathf.RoundToInt(TicksToOpenNow * 0.85f);
return TicksToOpenNow;
}
}
public override bool FireBulwark
{
get
{
return !Open && base.FireBulwark;
}
}

public override void PostMake()
{
base.PostMake();

powerComp = GetComp<CompPowerTrader>();
}

public override void SpawnSetup(Map map, bool respawningAfterLoad)
{
base.SpawnSetup(map, respawningAfterLoad);

powerComp = GetComp<CompPowerTrader>();
ClearReachabilityCache(map);

// Open the door if we're spawning on top of something
if( BlockedOpenMomentary )
{
DoorOpen();
}
}

public override void DeSpawn()
{
var map = Map;

base.DeSpawn();

ClearReachabilityCache(map);
}

public override void ExposeData()
{
base.ExposeData();

Scribe_Values.Look(ref openInt, "open", defaultValue: false);
Scribe_Values.Look(ref holdOpenInt, "holdOpen", defaultValue: false);
Scribe_Values.Look(ref lastFriendlyTouchTick, "lastFriendlyTouchTick" );

if( Scribe.mode == LoadSaveMode.LoadingVars )
{
if( openInt )
visualTicksOpen = VisualTicksToOpen;
}
}

public override void SetFaction(Faction newFaction, Pawn recruiter = null)
{
base.SetFaction(newFaction, recruiter);

if( Spawned )
ClearReachabilityCache(Map);
}

public override void Tick()
{
base.Tick();

//Check if we should clear the reachability cache
if( FreePassage != freePassageWhenClearedReachabilityCache )
ClearReachabilityCache(Map);

if( !openInt )
{
//Visual - slide door closed
if( visualTicksOpen > 0 )
visualTicksOpen--;

//Equalize temperatures
if( (Find.TickManager.TicksGame + thingIDNumber.HashOffset()) % TemperatureTuning.Door_TempEqualizeIntervalClosed == 0 )
GenTemperature.EqualizeTemperaturesThroughBuilding(this, TemperatureTuning.Door_TempEqualizeRate, twoWay: false);
}
else if( openInt )
{
//Visual - slide door open
if( visualTicksOpen < VisualTicksToOpen )
visualTicksOpen++;

//Count down to closing
if( !holdOpenInt )
{
if( Map.thingGrid.CellContains( Position, ThingCategory.Pawn ) )
ticksUntilClose = AutomaticCloseDelayTicks;
else
{
ticksUntilClose--;

//If the power is on and we were touched recently, close automatically
if( ticksUntilClose <= 0 && CanCloseAutomatically )
DoorTryClose();
}
}

//Equalize temperatures
if( (Find.TickManager.TicksGame + thingIDNumber.HashOffset()) % TemperatureTuning.Door_TempEqualizeIntervalOpen == 0 )
GenTemperature.EqualizeTemperaturesThroughBuilding(this, TemperatureTuning.Door_TempEqualizeRate, twoWay: false);
}
}

public void FriendlyTouched()
{
lastFriendlyTouchTick = Find.TickManager.TicksGame;
}



public void Notify_PawnApproaching( Pawn p )
{
if( !p.HostileTo(this) )
FriendlyTouched();

if( PawnCanOpen(p) )
{
Map.fogGrid.Notify_PawnEnteringDoor(this, p);

//Open automatically before pawn arrives
if( !SlowsPawns )
DoorOpen(ApproachCloseDelayTicks);
}
}

/// <summary>
/// Returns whether p can physically pass through the door without bashing.
/// </summary>
public bool CanPhysicallyPass( Pawn p )
{
return FreePassage || PawnCanOpen(p);
}

/// <summary>
/// Returns whether p can open the door without bashing.
/// </summary>
public virtual bool PawnCanOpen( Pawn p )
{
var lord = p.GetLord();
if( lord != null && lord.LordJob != null && lord.LordJob.CanOpenAnyDoor(p) )
return true;

//This is to avoid situations where a wild man is stuck inside the colony forever right after having a mental break
if( p.IsWildMan() && !p.mindState.wildManEverReachedOutside )
return true;

if( Faction == null )
return true;

if( p.guest != null && p.guest.Released )
return true;

return GenAI.MachinesLike(Faction, p);
}

public override bool BlocksPawn( Pawn p )
{
if( openInt )
return false;
else
return !PawnCanOpen(p);
}

protected void DoorOpen(int ticksToClose = AutomaticCloseDelayTicks)
{
ticksUntilClose = ticksToClose;

if( !openInt )
{
openInt = true;

if( DoorPowerOn )
def.building.soundDoorOpenPowered.PlayOneShot(new TargetInfo(Position, Map));
else
def.building.soundDoorOpenManual.PlayOneShot(new TargetInfo(Position, Map));
}
}


protected void DoorTryClose()
{
if( holdOpenInt || BlockedOpenMomentary )
return;

openInt = false;

if( DoorPowerOn )
def.building.soundDoorClosePowered.PlayOneShot(new TargetInfo(Position, Map));
else
def.building.soundDoorCloseManual.PlayOneShot(new TargetInfo(Position, Map));
}


public void StartManualOpenBy( Pawn opener )
{
DoorOpen();
}

public void StartManualCloseBy( Pawn closer )
{
DoorTryClose();
}

public override void Draw()
{
//Note: It's a bit odd that I'm changing game variables in Draw
//      but this is the easiest way to make this always look right even if
//      conditions change while the game is paused.
Rotation = DoorRotationAt(Position, Map);

//Draw the two moving doors
float pctOpen = Mathf.Clamp01((float)visualTicksOpen / (float)VisualTicksToOpen); //Needs clamp for after game load
float offsetDist = VisualDoorOffsetStart + (VisualDoorOffsetEnd-VisualDoorOffsetStart)*pctOpen;

for( int i=0; i<2; i++ )
{
//Left, then right
Vector3 offsetNormal = new Vector3();
Mesh mesh;
if( i == 0 )
{
offsetNormal = new Vector3(0,0,-1);
mesh = MeshPool.plane10;
}
else
{
offsetNormal = new Vector3(0,0,1);
mesh = MeshPool.plane10Flip;
}


//Work out move direction
Rot4 openDir = Rotation;
openDir.Rotate(RotationDirection.Clockwise);
offsetNormal  = openDir.AsQuat * offsetNormal;

//Position the door
Vector3 doorPos =  DrawPos;
doorPos.y = Altitudes.AltitudeFor(AltitudeLayer.DoorMoveable);
doorPos += offsetNormal * offsetDist;

//Draw!
Graphics.DrawMesh(mesh, doorPos, Rotation.AsQuat, Graphic.MatAt(Rotation), 0 );
}

Comps_PostDraw();
}


private static int AlignQualityAgainst( IntVec3 c, Map map )
{
if( !c.InBounds(map) )
return 0;

//We align against anything unwalkthroughable and against blueprints for unwalkthroughable things
if( !c.Walkable(map) )
return 9;

List<Thing> things = c.GetThingList(map);
for(int i=0; i<things.Count; i++ )
{
Thing t = things[i];

if( typeof(Building_DoubleDoor).IsAssignableFrom(t.def.thingClass) )
return 1;

Thing blue = t as Blueprint;
if( blue != null )
{
if( blue.def.entityDefToBuild.passability == Traversability.Impassable )
return 9;
if( typeof(Building_DoubleDoor).IsAssignableFrom(t.def.thingClass) )
return 1;
}
}

return 0;
}

public static Rot4 DoorRotationAt(IntVec3 loc, Map map)
{
int horVotes = 0;
int verVotes = 0;

horVotes += AlignQualityAgainst( loc + IntVec3.East, map );
horVotes += AlignQualityAgainst( loc + IntVec3.West, map );
verVotes += AlignQualityAgainst( loc + IntVec3.North, map );
verVotes += AlignQualityAgainst( loc + IntVec3.South, map );

if( horVotes >= verVotes )
return Rot4.North;
else
return Rot4.East;
}

public override IEnumerable<Gizmo> GetGizmos()
{
foreach( var g in base.GetGizmos() )
{
yield return g;
}

if( Faction == Faction.OfPlayer )
{
var ro = new Command_Toggle();
ro.defaultLabel = "CommandToggleDoorHoldOpen".Translate();
ro.defaultDesc = "CommandToggleDoorHoldOpenDesc".Translate();
ro.hotKey = KeyBindingDefOf.Misc3;
ro.icon = TexCommand.HoldOpen;
ro.isActive = () => holdOpenInt;
ro.toggleAction = () => holdOpenInt = !holdOpenInt;
yield return ro;
}
}

private void ClearReachabilityCache(Map map)
{
map.reachability.ClearCache();
freePassageWhenClearedReachabilityCache = FreePassage;
}
}

public static class DoorsDebugDrawer
{
public static void DrawDebug()
{
if( !DebugViewSettings.drawDoorsDebug )
return;

var visibleRect = Find.CameraDriver.CurrentViewRect;
var buildings = Find.VisibleMap.listerThings.ThingsInGroup(ThingRequestGroup.BuildingArtificial);

for( int i = 0; i < buildings.Count; i++ )
{
if( !visibleRect.Contains(buildings[i].Position) )
continue;

var door = buildings[i] as Building_DoubleDoor;

if( door != null )
{
Color color;

if( door.FreePassage )
color = new Color(0f, 1f, 0f, 0.5f);
else
color = new Color(1f, 0f, 0f, 0.5f);

CellRenderer.RenderCell(door.Position, SolidColorMaterials.SimpleSolidColorMaterial(color));
}
}
}
}
}

thanks in advance.

jamaicancastle

#14
Well, there aren't that many channels... and they're fairly well-organized. Still a lot though. :P

Anyway. Since you're inheriting from Building_Door, it's probably safest to delete everything you don't need to override. In this case that should be everything except Draw() and the two consts related to it, VisualDoorOffsetStart and VisualDoorOffsetEnd. The latter of those is probably the one you want to change to affect how the door slides open - you may have to experiment with the numbers to get the right result. At a guess, I would say that since you want the door to move .5 grid cells further, it should go to .95f.

The other thing that should have to change is the mesh that I mentioned above inside Draw().