AI/ActionSystem/Actions/Guest/SatisfyNeedsAction.cs
using System;
namespace HC3;
public sealed class SatisfyNeedsAction : BehaviorTreeAction
{
private NeedSystem Needs => Guest.Needs;
private Building _targetBuilding;
private Guest Guest => Agent as Guest;
private bool _forced;
private readonly (Need need, float priority)[] _needScratch = new (Need, float)[16];
public void Force( Building building )
{
_forced = true;
_targetBuilding = building;
}
public override float Score()
{
if ( !Guest.IsValid() || Guest.Money <= 0 ) return 0f;
if ( Guest.SuspicionLevel >= Guest.ArrestSuspicionThreshold )
return 0f;
if ( _targetBuilding.IsValid() ) return 300f;
// Find actionable needs sorted by priority, try buildings for each
// Uses pre-allocated buffer to avoid LINQ/allocation overhead
int needCount = 0;
foreach ( var n in Needs.GetNeeds() )
{
if ( !n.IsActionable() )
continue;
if ( Guest.Delinquency > n.Template.DelinquencyThreshold )
continue;
if ( needCount >= _needScratch.Length )
break;
_needScratch[needCount].need = n;
_needScratch[needCount].priority = n.GetPriority() + Game.Random.Float( -0.2f, 0.2f );
needCount++;
}
// Sort descending by priority (insertion sort, small N)
for ( int i = 1; i < needCount; i++ )
{
var key = _needScratch[i];
int j = i - 1;
while ( j >= 0 && _needScratch[j].priority < key.priority )
{
_needScratch[j + 1] = _needScratch[j];
j--;
}
_needScratch[j + 1] = key;
}
for ( int i = 0; i < needCount; i++ )
{
var building = BuildingSelector.FindBestBuildingForNeed( Guest, _needScratch[i].need, Random );
if ( building.IsValid() )
{
_targetBuilding = building;
return 100f;
}
}
return 0;
}
public override void StopAction()
{
base.StopAction();
// make sure we're deregistered and unloaded from anything we might be part of
// (I suppose maybe nodes should handle this but that seems kinda complex to cover?)
if ( _targetBuilding.IsValid() )
{
_targetBuilding.Unload( Guest );
if ( _targetBuilding is BasicRide ride )
{
ride.RemoveFromQueue( Guest );
}
}
_forced = false;
_targetBuilding = null;
}
protected override Node BuildTree()
{
var entrance = _targetBuilding.GetEntrance();
var ride = _targetBuilding as BasicRide;
var steps = new List<Node>();
if ( !_forced )
{
steps.Add( new MoveToNode( Agent, entrance, $"Going to {_targetBuilding.Title}" ) );
}
// Queue if there's a ride
if ( ride.IsValid() )
{
steps.Add( new QueueNode( ride, Guest ) );
}
steps.Add( new UseBuildingNode( _targetBuilding, Guest, Needs ) );
return new SequenceNode( steps );
}
}