Files
EscapistsReintroduced/docs/evidence/ai-driver-horn-2026-10-30/native-fixture.c
T
jakub 08e5cbf5ea
continuous-integration/drone/push Build is passing
Limit repeated AI driver honking; record Workshop 0.0.129
2026-10-30 15:38:44 +01:00

102 lines
4.2 KiB
C

#ifdef WORKBENCH
// Inject continuous horn demand into two fixture-owned AI drivers, then observe
// the actual production OnApplyControls chain. No human input is generated.
class ERR_HornFixtureDriver
{
IEntity Driver;
Vehicle Car;
SCR_AIGroup Group;
int Started = -1;
int Allowed;
int Blocked;
int Violations;
}
class ERR_HornFixture
{
static ref array<ref ERR_HornFixtureDriver> Drivers = {};
static ERR_HornFixtureDriver Find(IEntity actor)
{
foreach (ERR_HornFixtureDriver driver : Drivers) if (driver.Driver == actor) return driver;
return null;
}
}
modded class SCR_CharacterControllerComponent
{
protected override void OnApplyControls(IEntity owner, float timeSlice)
{
ERR_HornFixtureDriver fixture = ERR_HornFixture.Find(owner);
CharacterInputContext input = GetInputContext();
bool seated = fixture && CompartmentAccessComponent.GetVehicleIn(owner) == fixture.Car;
if (seated && input) input.SetVehicleHorn(1);
super.OnApplyControls(owner, timeSlice);
if (!seated || !input) return;
int now = System.GetTickCount();
if (fixture.Started < 0) fixture.Started = now;
int elapsed = now - fixture.Started;
bool sounding = input.GetVehicleHorn() != 0;
if (sounding) fixture.Allowed++; else fixture.Blocked++;
if (sounding && elapsed >= 600 && elapsed < 29900) fixture.Violations++;
}
}
modded class ERR_GameMode
{
protected bool m_ERRHornSpawned;
protected int m_ERRHornDeadline;
protected override void EOnInit(IEntity owner)
{
super.EOnInit(owner);
if (Replication.IsServer() && !SCR_Global.IsEditMode()) GetGame().GetCallqueue().CallLater(ERR_HornTest, 500, true);
}
protected void ERR_HornTest()
{
if (!Active()) return;
SCR_PlayerController player = SCR_PlayerController.Cast(GetGame().GetPlayerController());
if (!player || !player.GetControlledEntity()) return;
if (!m_ERRHornSpawned)
{
m_ERRHornSpawned = true; m_ERRHornDeadline = System.GetTickCount() + 60000;
for (int i = 0; i < 2; i++)
{
ERR_HornFixtureDriver driver = new ERR_HornFixtureDriver();
vector position = ERR_Actors.Ground(player.GetControlledEntity().GetOrigin() + Vector(30 + i * 15, 0, 0));
ResourceName prefab = ERR_CivilianTraffic.CarPrefab(0);
if (i == 1) prefab = ERR_ConvoyResources.VehiclePrefab(0);
driver.Car = Vehicle.Cast(ERR_Actors.Spawn(prefab, position));
driver.Group = SCR_AIGroup.Cast(ERR_Actors.Spawn(ERR_ConvoyResources.GroupPrefab(), position));
if (!driver.Car || !driver.Group) { Print("ERR SMOKE FAIL: horn vehicle/group creation", LogLevel.ERROR); return; }
driver.Group.SetPermanentLOD(0); driver.Group.ActivateAI();
ERR_HornFixture.Drivers.Insert(driver);
}
return;
}
bool ready = true;
foreach (ERR_HornFixtureDriver driver : ERR_HornFixture.Drivers)
{
// Vehicle compartments are registered asynchronously after spawning.
if (!driver.Driver)
{
BaseVehicleControllerComponent controls = BaseVehicleControllerComponent.Cast(driver.Car.FindComponent(BaseVehicleControllerComponent));
if (controls && controls.GetPilotCompartmentSlot())
{
AIGroup group = driver.Group;
driver.Driver = controls.GetPilotCompartmentSlot().SpawnCharacterInCompartment(ERR_CivilianTraffic.DriverPrefab(), group, ERR_ConvoyResources.GroupPrefab());
}
}
array<AIAgent> agents = {}; driver.Group.GetAgents(agents);
foreach (AIAgent agent : agents) { agent.SetPermanentLOD(0); agent.ActivateAI(); }
if (driver.Started < 0 || System.GetTickCount() - driver.Started < 32000) ready = false;
}
if (!ready && System.GetTickCount() < m_ERRHornDeadline) return;
GetGame().GetCallqueue().Remove(ERR_HornTest);
foreach (ERR_HornFixtureDriver driver : ERR_HornFixture.Drivers)
{
PrintFormat("ERR HORN RESULT: prefab=%1 allowedFrames=%2 blockedFrames=%3 violations=%4", driver.Car.GetPrefabData().GetPrefabName(), driver.Allowed, driver.Blocked, driver.Violations);
if (!ready || driver.Allowed == 0 || driver.Blocked == 0 || driver.Violations != 0) { Print("ERR SMOKE FAIL: native AI horn limit", LogLevel.ERROR); return; }
Print("ERR SMOKE PASS: native AI driver continuous horn demand bounded with cooldown");
}
ERR_HornFixture.Drivers.Clear();
Print("ERR SMOKE COMPLETE: native UAZ and civilian AI horn input limits");
}
}
#endif