SceneRewardReader.cs
using Sandbox;
using System.Text.Json.Nodes;
// Digs the EscapeZone's payout out of a mission scene without loading it.
//
// The job board lives in the hideout, so the mission map isn't in memory
// when the board needs to advertise what a job pays. A SceneFile does
// expose its raw GameObject JSON though, so the number can be read
// straight out of that - which keeps the map itself the single source of
// truth instead of asking for the figure to be typed in twice and kept in
// step by hand.
public static class SceneRewardReader
{
// Matches EscapeZone.EscapeReward's own default. Used when the scene
// has an EscapeZone whose reward was left at the default, since s&box
// omits default values when it serialises.
const int DefaultReward = 1000;
/// <summary>
/// The EscapeReward of the first EscapeZone in this scene, or 0 if the
/// scene is unset or has no escape zone in it.
/// </summary>
public static int ReadEscapeReward( SceneFile scene )
{
if ( scene?.GameObjects is null )
return 0;
foreach ( var go in scene.GameObjects )
{
var reward = FindInObject( go );
if ( reward.HasValue )
return reward.Value;
}
return 0;
}
// Walks the GameObject tree looking for an EscapeZone component.
// Recursive because the escape zone is very unlikely to be a
// root-level object - it'll be nested under whatever the map is
// organised into.
static int? FindInObject( JsonNode node )
{
if ( node is not JsonObject obj )
return null;
if ( obj["Components"] is JsonArray components )
{
foreach ( var component in components )
{
if ( component is not JsonObject c )
continue;
if ( c["__type"]?.GetValue<string>() != nameof( EscapeZone ) )
continue;
return c["EscapeReward"] is JsonValue value && value.TryGetValue<int>( out var reward )
? reward
: DefaultReward;
}
}
if ( obj["Children"] is JsonArray children )
{
foreach ( var child in children )
{
var reward = FindInObject( child );
if ( reward.HasValue )
return reward;
}
}
return null;
}
}