Here's the class for structure globals
C#:
public class structure : ConsoleSystem
{
[ConsoleSystem.Admin]
public static float minpercentdmg = 0.1f;
[ConsoleSystem.Admin]
public static int framelimit = 1;
[ConsoleSystem.Admin]
public static int maxframeattempt = 1000;
[ConsoleSystem.Admin]
public static void touchall(ref ConsoleSystem.Arg args)
{
using (List<StructureMaster>.Enumerator enumerator = StructureMaster.AllStructures.GetEnumerator())
{
while (enumerator.MoveNext())
{
StructureMaster current = enumerator.Current;
if ((bool) ((Object) current))
current.Touched();
}
}
}
}
Or, in server.cfg and rcon console:
Code:
structure.framelimit
structure.maxframeattempt
structure.minpercentdmg
structure.touchall
Take a look at structure.touchall. It calls a method Touched(), and if you don't know what this console command does, it refreshes all structures on the server, just like it would when the owner enters, setting decay to the fresh state. What does Touched() do:
C#:
public void Touched()
{
this._decayDelayRemaining = this.GetDecayDelay();
StructureMaster.Schedule.Reschedule(this);
}
I won't bore you with chasing GetDecayDelay. It gets the decay delay starting value according to material type. Metal lasts longer than Wood. Touched calls Reschedule:
C#:
public static void Reschedule(StructureMaster master)
{
StaticQueue<StructureMaster.Schedule, StructureMaster>.requeue(master, ref master.regkey);
}
Now, the requeue method is a member of Facepunch.Collections.StaticQueue. It's mind-numbingly dense and hard to read, probably because of the way dotPeek decompiles. I'm sure there's a much easier to use library overlying it. Like any .Net collection it has an iterator class for moving through the collection(s) in it.
So, when does StaticQueue get used? The NetCull class manages Callback hooks for updating state of game objects and has the static methods for instantiating and destroying them. Entity.Destroy() calls NetCull.Destroy(), for example. StructureMaster and EnvDecay classes register callbacks with NetCull when they are instantiated, mostly the same way Fougerite installs hooks for modules into its Hooks class. So it installs a UpdateFunctor type of hook, whatever that is, with RunDecayThink as an argument. This gets called for each StructureMaster, on the beforeEveryUpdate event. Now, look all the way down at the method named Process - which is called by RunDecayThink.
C#:
private static class Callbacks
{
static Callbacks()
{
NetCull.Callbacks.beforeEveryUpdate += new NetCull.UpdateFunctor(StructureMaster.Callbacks.RunDecayThink);
}
private static void RunDecayThink()
{
try
{
StructureMaster.Schedule.Process(structure.maxframeattempt);
}
catch (Exception ex)
{
UnityEngine.Debug.LogException(ex);
}
}
public static void Process(int maxCount = 1)
{
if (Globals.isLoading)
return;
try
{
StaticQueue<StructureMaster.Schedule, StructureMaster>.iterator iter = new StaticQueue<StructureMaster.Schedule, StructureMaster>.iterator(maxCount);
int numDecaying = 0;
StructureMaster v;
bool flag = iter.Start(out v);
while (flag)
flag = !(bool) ((UnityEngine.Object) v) || !iter.Validate(ref v.regkey) ? iter.MissingNext(out v) : StructureMaster.Schedule.ThinkInstance(ref iter, ref v, ref v.regkey, ref numDecaying);
}
finally
{
}
}
There's that StaticQueue thing, and Process gets its iterator with the argument maxCount, to size it. It's going to process this many on this call. RunDecayThink passed structure.maxframeattempt to it, a server global var. If it's not set, the default is 1000 as you can see in its source block. So, what if it was set to zero? There would be no elements, iter.Start would return false, and the loop would not run once. Looks like that's all there is to it.
The other structure global vars are used in the ThinkInstance method that's called inside the iterator loop. Don't care much what they are, since we can bypass the loop and stop decay that way, and save the server having to go through a loop again and again doing work that has no results. But, here's what they do:
C#:
// ThinkInstance first calls DoDecay and returns a decayStatus object
decayStatus = master.DoDecay();
// it's an enum with 4 members. the return is one of these.
protected enum DecayStatus
{
Decaying,
Delaying,
PentUpDecay,
Gone,
}
// in DoDecay, if we can set decayRate we can return Delaying
if ((double) StructureMaster.decayRate <= 0.0)
return StructureMaster.DecayStatus.Delaying;
// this num3 is amount of pent-up decay which accumulates, I think, when the game can't
// keep up with decay processing. Too many structures? This is the relief valve. It's set to 0.1 by default
// could make it a big number and let decay get pent-up forever.
if ((double) num3 < (double) structure.minpercentdmg)
return StructureMaster.DecayStatus.PentUpDecay;
// ThinkInstance next passes the decay Status through a switch statement that selects an action for the
// interator. Are you bored yet? Zzzzzzzzzzz
if (iter.Next(ref key, cmd, out master))
return numDecaying < structure.framelimit;
else
return false;
// and then it returns true or false back to the Process method.
False if the iterator has reached the end. True if the iterator can go to the next element AND numDecaying is less than another global var, structure.framelimit. numDecaying is initialized with value 0. So set this to 0 or -1, and ThinkInstance returns false every time. But if we got here, maxCount was at least 1, so we entered the while loop in the Process method. We got a valid object, so !(bool) StructureMaster is false. iter.Validate will be true on the 1st pass with 1 element, so it will return the result of iter.MissingNext which ought to be false on the 1st pass through a 1 element list. So, ThinkInstance doesn't get called on the last element, which is also the first element of a 1 element list. But it would get called if there was more than 1. And we can make it return false every time by setting structure.framelimit <= 0. Not that we need to.
The rest of ThinkInstance is a bunch of Raycasting at its components, and damage calculations that vary depending on whether it's a door, or a Pillar or Celing carrying weight and somebody was just totally overthinking this thing, I believe. Finally it calls our hook, which we patched in here:
C#:
StructureComponent structureComponent = current;
TakeDamage.Quantity damageQuantity = (TakeDamage.Quantity) dmg;
Hooks.EntityDecay((object) current, dmg);
And that's it. Set structure.maxframeattempt = 0, and the method that calls our Decay hook is never entered, and no decay damage is ever applied to any Structure.