Add civilian traffic variants, farm tractors and UI scaling; record Workshop 0.0.143
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -69,6 +69,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
button.m_OnClicked.Insert(OnButton);
|
||||
m_Buttons.Insert(button);
|
||||
}
|
||||
ERR_AdminMenuView.FitPanel(GetRootWidget());
|
||||
m_Ready = false; m_Age = 0; m_Poll = 0;
|
||||
GetGame().GetInputManager().AddActionListener(UIConstants.MENU_ACTION_BACK, EActionTrigger.PRESSED, OnBack);
|
||||
m_Bound = true;
|
||||
@@ -89,6 +90,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
override void OnMenuUpdate(float tDelta)
|
||||
{
|
||||
super.OnMenuUpdate(tDelta);
|
||||
ERR_AdminMenuView.FitPanel(GetRootWidget());
|
||||
if (!m_Controller || m_Controller != GetGame().GetPlayerController() || !m_Mode || m_Mode != GetGame().GetGameMode() || ERR_MissionRules.Terminal(m_Mode.Phase())) { Close(); return; }
|
||||
m_Age += tDelta; m_Poll += tDelta;
|
||||
m_ClickDelay = Math.Max(0, m_ClickDelay - tDelta);
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
// Widget presentation only. No settings writes, authorization or transport here.
|
||||
class ERR_AdminMenuView
|
||||
{
|
||||
// Preserve the requested UI scale until the fixed panel would leave the viewport.
|
||||
static void FitPanel(Widget root)
|
||||
{
|
||||
if (!root || !GetGame() || !GetGame().GetWorkspace()) return;
|
||||
ScaleWidget scale = ScaleWidget.Cast(root.FindAnyWidget("PanelScale"));
|
||||
if (!scale) return;
|
||||
float width, height; root.GetScreenSize(width, height);
|
||||
float panelWidth = GetGame().GetWorkspace().DPIScale(1220);
|
||||
float panelHeight = GetGame().GetWorkspace().DPIScale(760);
|
||||
if (width <= 0 || height <= 0 || panelWidth <= 0 || panelHeight <= 0) return;
|
||||
float fit = Math.Min(1, Math.Min(width * 0.96 / panelWidth, height * 0.96 / panelHeight));
|
||||
if (Math.AbsFloat(scale.GetScale() - fit) > 0.001) scale.SetScale(fit);
|
||||
}
|
||||
static bool ShowPage(Widget root, int page)
|
||||
{
|
||||
int group = ERR_AdminMenuPages.Group(page);
|
||||
|
||||
@@ -205,7 +205,7 @@ class ERR_AdminService
|
||||
if (key == "garageChancePercent") return "Parking-site selection chance (0-100%). Default 70%; applies to suitable garages and residential yards. Next round.";
|
||||
if (key == "garageRadiusMetres") return "Spawn radius (250-1500 metres). Distance around players; default 1000 m. Next round.";
|
||||
if (key == "garageSpawnIntervalSeconds") return "Time between spawns (5-60 seconds). Default 5 s. Increase to spread out spawning. Next round.";
|
||||
if (key == "garageVehicleCount") return "0..24 unclaimed cars in garages and residential yards; default 16. Used vehicles stay. Optional Rally cars and bikes. Applies next round.";
|
||||
if (key == "garageVehicleCount") return "0..24 unclaimed cars in garages and residential yards; default 16. Used vehicles stay. Optional Rally cars, farm tractors and bikes. Next round.";
|
||||
if (key == "civilianTrafficCount") return "Set 0..6 civilian cars (default 3). Zero disables new traffic. Unclaimed cars retire only far from all players and out of sight; player-entered cars are preserved.";
|
||||
if (key == "showAllSites") return "0 disabled (default), 1 enabled. Show prepared sites at their positions as question marks; recovered intel identifies their types. Applies immediately to everyone.";
|
||||
if (key == "selfEpinephrine") return "0 disabled (default), 1 enabled. Unconscious players can press Space to use a carried epinephrine injector. Applies immediately; consumes the item and keeps its normal medical effect.";
|
||||
|
||||
@@ -82,6 +82,7 @@ class ERR_CivilianTraffic
|
||||
{
|
||||
protected ERR_GameMode m_Mode;
|
||||
protected ref array<ref ERR_CivilianCar> m_Cars = {};
|
||||
protected ref ERR_ParkedCarCatalog m_Catalog = new ERR_ParkedCarCatalog();
|
||||
protected float m_Next;
|
||||
protected float m_NextSpawn;
|
||||
protected int m_Serial;
|
||||
@@ -205,7 +206,10 @@ class ERR_CivilianTraffic
|
||||
m_PlayerCursor = m_PlayerCursor % observers.Count();
|
||||
vector centre = observers[m_PlayerCursor++];
|
||||
if (!Plan(centre, observers, actors, route, angles)) return false;
|
||||
ResourceName prefab = CarPrefab(Math.RandomInt(0, 3));
|
||||
m_Catalog.Find(false);
|
||||
// Keep the transport van in traffic; premium Rally cars stay in parked pools.
|
||||
ResourceName prefab = CarPrefab(2);
|
||||
if (Math.RandomInt(0, 5) != 0) prefab = m_Catalog.RoadPrefabs[Math.RandomInt(0, m_Catalog.RoadPrefabs.Count())];
|
||||
array<ResourceName> required = {prefab, DriverPrefab(), ERR_ConvoyResources.GroupPrefab(), ERR_Resources.MoveWaypoint()};
|
||||
foreach (ResourceName name : required) { Resource loaded = Resource.Load(name); if (!loaded || !loaded.IsValid()) return false; }
|
||||
// Loading can take time: check every observer again immediately before spawn.
|
||||
|
||||
@@ -24,14 +24,29 @@ class ERR_GarageVehicleRules
|
||||
|| path.EndsWith("/house_village_e_1i06.et") || path.EndsWith("/house_village_e_1i06_brown.et")
|
||||
|| path.EndsWith("/house_village_e_1i06_white.et");
|
||||
}
|
||||
static vector YardOffset(vector low, vector high, int side)
|
||||
static bool Farm(string path)
|
||||
{
|
||||
path.ToLower();
|
||||
return path.EndsWith("/barn_e_01.et") || path.EndsWith("/barn_e_01_old.et")
|
||||
|| path.EndsWith("/barn_e_01_white.et") || path.EndsWith("/barn_e_02.et")
|
||||
|| path.EndsWith("/barn_e_03_closed.et") || path.EndsWith("/barn_e_03_open.et")
|
||||
|| path.EndsWith("/cowshed_e_01_short.et") || path.EndsWith("/cowshed_e_01_long.et");
|
||||
}
|
||||
static bool FarmEnvelope(vector low, vector high)
|
||||
{
|
||||
return ERR_ForceRules.FiniteVector(low) && ERR_ForceRules.FiniteVector(high)
|
||||
&& low[0] >= -1.45 && high[0] <= 1.45 && low[2] >= -2.95 && high[2] <= 2.95
|
||||
&& low[1] >= -0.3 && high[1] <= 3.1
|
||||
&& high[0] > low[0] && high[1] > low[1] && high[2] > low[2];
|
||||
}
|
||||
static vector YardOffset(vector low, vector high, int side, float clearance = 3)
|
||||
{
|
||||
vector centre = (low + high) * 0.5; centre[1] = 0;
|
||||
// Three metres from the wall leaves almost two metres beside the car.
|
||||
if (side == 0) centre[0] = low[0] - 3;
|
||||
else if (side == 1) centre[0] = high[0] + 3;
|
||||
else if (side == 2) centre[2] = low[2] - 3;
|
||||
else centre[2] = high[2] + 3;
|
||||
// Separate farm clearance leaves room beside a taller, wider tractor.
|
||||
if (side == 0) centre[0] = low[0] - clearance;
|
||||
else if (side == 1) centre[0] = high[0] + clearance;
|
||||
else if (side == 2) centre[2] = low[2] - clearance;
|
||||
else centre[2] = high[2] + clearance;
|
||||
return centre;
|
||||
}
|
||||
}
|
||||
@@ -42,6 +57,7 @@ class ERR_GarageSite
|
||||
bool Selected;
|
||||
float NextTry;
|
||||
bool Yard;
|
||||
bool Farm;
|
||||
int Side;
|
||||
}
|
||||
class ERR_ParkedVehicle
|
||||
@@ -121,8 +137,10 @@ class ERR_GarageVehicles
|
||||
}
|
||||
// Sample both ends/sides and roof, not just the hidden centre of a garage.
|
||||
// Open doors and windows can reveal a bay; no distance alone proves it hidden.
|
||||
protected bool Hidden(vector position, vector angles, array<IEntity> actors, IEntity ignored = null)
|
||||
protected bool Hidden(vector position, vector angles, array<IEntity> actors, IEntity ignored = null, bool farm = false)
|
||||
{
|
||||
float halfWidth = 1.1, halfLength = 2.5, roofStep = 1.3;
|
||||
if (farm) { halfWidth = 1.5; halfLength = 3; roofStep = 2.5; }
|
||||
vector matrix[4]; Math3D.AnglesToMatrix(angles, matrix);
|
||||
foreach (IEntity actor : actors)
|
||||
{
|
||||
@@ -135,7 +153,7 @@ class ERR_GarageVehicles
|
||||
{
|
||||
TraceParam ray = new TraceParam();
|
||||
ray.Start = actor.GetOrigin() + "0 1.7 0";
|
||||
ray.End = position + matrix[0] * (x * 1.1) + matrix[2] * (z * 2.5) + Vector(0, 0.5 + height * 1.3, 0);
|
||||
ray.End = position + matrix[0] * (x * halfWidth) + matrix[2] * (z * halfLength) + Vector(0, 0.5 + height * roofStep, 0);
|
||||
ray.Flags = TraceFlags.WORLD | TraceFlags.ENTS; ray.LayerMask = TRACE_LAYER_CAMERA;
|
||||
ray.ExcludeArray = excluded;
|
||||
float fraction = GetGame().GetWorld().TraceMove(ray, null);
|
||||
@@ -150,12 +168,14 @@ class ERR_GarageVehicles
|
||||
if (m_Sites.Count() >= 2048) return false;
|
||||
if (!entity.GetPrefabData()) return true;
|
||||
string prefab = entity.GetPrefabData().GetPrefabName();
|
||||
bool yard = ERR_GarageVehicleRules.Yard(prefab);
|
||||
bool farm = !m_Cars.Tractors.IsEmpty() && ERR_GarageVehicleRules.Farm(prefab);
|
||||
bool yard = farm || ERR_GarageVehicleRules.Yard(prefab);
|
||||
if (!yard && !ERR_GarageVehicleRules.Garage(prefab)) return true;
|
||||
foreach (ERR_GarageSite site : m_Sites) if (site.Garage == entity) return true;
|
||||
ERR_GarageSite added = new ERR_GarageSite(); added.Garage = entity;
|
||||
added.Yard = yard; added.Side = Math.RandomInt(0, 4);
|
||||
added.Selected = Math.RandomInt(0, 100) < m_GarageChance;
|
||||
added.Yard = yard; added.Farm = farm; added.Side = Math.RandomInt(0, 4);
|
||||
int chance = m_GarageChance; if (farm) chance = Math.Min(chance, 15);
|
||||
added.Selected = Math.RandomInt(0, 100) < chance;
|
||||
m_Sites.Insert(added);
|
||||
return true;
|
||||
}
|
||||
@@ -215,7 +235,7 @@ class ERR_GarageVehicles
|
||||
if (!parked.Entity) { parked.Site.Consumed = true; m_Parked.Remove(index); return; }
|
||||
if (InUse(parked)) { Claim(index); return; }
|
||||
if (!known || !ERR_CivilianTrafficRules.Far(parked.Entity.GetOrigin(), observers, m_CleanupRadius)
|
||||
|| !Hidden(parked.Entity.GetOrigin(), parked.Entity.GetYawPitchRoll(), actors, parked.Entity)) { parked.HiddenSince = -1; return; }
|
||||
|| !Hidden(parked.Entity.GetOrigin(), parked.Entity.GetYawPitchRoll(), actors, parked.Entity, parked.Site.Farm)) { parked.HiddenSince = -1; return; }
|
||||
if (parked.HiddenSince < 0) parked.HiddenSince = now;
|
||||
if (!ERR_CivilianTrafficRules.CleanupDue(now, parked.HiddenSince)) return;
|
||||
// Recheck occupancy immediately before deletion. Claimed cars are no longer
|
||||
@@ -230,18 +250,20 @@ class ERR_GarageVehicles
|
||||
foreach (ERR_ParkedVehicle parked : m_Parked) if (parked.Site == site) return true;
|
||||
return false;
|
||||
}
|
||||
protected bool YardGround(vector position, vector transform[4])
|
||||
protected bool YardGround(vector position, vector transform[4], bool farm = false)
|
||||
{
|
||||
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
|
||||
if (!world || !world.GetRoadNetworkManager()) return false;
|
||||
BaseRoad road; float distance;
|
||||
world.GetRoadNetworkManager().GetClosestRoad(position, road, distance, skipNavlinks: true);
|
||||
if (road && (!ERR_ForceRules.Finite(distance) || distance < road.GetWidth() * 0.5 + 4)) return false;
|
||||
float halfWidth = 1.1, halfLength = 2.5;
|
||||
if (farm) { halfWidth = 1.5; halfLength = 3; }
|
||||
float ground = SCR_TerrainHelper.GetTerrainY(position);
|
||||
for (int x = -1; x <= 1; x++)
|
||||
for (int z = -1; z <= 1; z++)
|
||||
{
|
||||
vector sample = position + transform[0] * (x * 1.1) + transform[2] * (z * 2.5);
|
||||
vector sample = position + transform[0] * (x * halfWidth) + transform[2] * (z * halfLength);
|
||||
if (!ERR_Actors.DryGround(sample) || Math.AbsFloat(SCR_TerrainHelper.GetTerrainY(sample) - ground) > 0.18) return false;
|
||||
}
|
||||
return true;
|
||||
@@ -262,7 +284,8 @@ class ERR_GarageVehicles
|
||||
vector low, high; site.Garage.GetBounds(low, high);
|
||||
if (!ERR_ForceRules.FiniteVector(low) || !ERR_ForceRules.FiniteVector(high) || (high - low).Length() > 40) continue;
|
||||
int side = site.Side; site.Side = (site.Side + 1) % 4;
|
||||
offset = ERR_GarageVehicleRules.YardOffset(low, high, side);
|
||||
float clearance = 3; if (site.Farm) clearance = 4;
|
||||
offset = ERR_GarageVehicleRules.YardOffset(low, high, side, clearance);
|
||||
angles[1] = 0; angles[2] = 0;
|
||||
if (side >= 2) angles[0] = angles[0] + 90;
|
||||
}
|
||||
@@ -274,7 +297,7 @@ class ERR_GarageVehicles
|
||||
if (site.Yard)
|
||||
{
|
||||
position = ERR_Actors.Ground(position) + "0 0.08 0";
|
||||
if (!YardGround(position, transform)) return;
|
||||
if (!YardGround(position, transform, site.Farm)) return;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -284,10 +307,11 @@ class ERR_GarageVehicles
|
||||
if (!ERR_ForceRules.Finite(fraction) || fraction >= 1 || floor.TraceNorm[1] < 0.98) return;
|
||||
position = vector.Lerp(floor.Start, floor.End, fraction) + "0 0.08 0";
|
||||
}
|
||||
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), position) || !Clear(position, transform) || !Hidden(position, angles, actors)) return;
|
||||
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), position) || !Clear(position, transform, null, site.Farm) || !Hidden(position, angles, actors, null, site.Farm)) return;
|
||||
m_Pending = site; m_Position = position; m_Angles = angles;
|
||||
m_Prefab = m_Cars.Prefabs[Math.RandomInt(0, m_Cars.Prefabs.Count())];
|
||||
m_PendingBike = m_Spawned % 4 == 3 && m_BikeCandidate < m_Bikes.Prefabs.Count();
|
||||
if (site.Farm) m_Prefab = m_Cars.Tractors[0];
|
||||
m_PendingBike = !site.Farm && m_Spawned % 4 == 3 && m_BikeCandidate < m_Bikes.Prefabs.Count();
|
||||
if (m_PendingBike) m_Prefab = m_Bikes.Prefabs[m_BikeCandidate];
|
||||
m_Stage = 1; return;
|
||||
}
|
||||
@@ -306,10 +330,10 @@ class ERR_GarageVehicles
|
||||
}
|
||||
ERR_GarageSite site = m_Pending; m_Pending = null;
|
||||
if (!site.Garage || site.Consumed || !ERR_Actors.Alive(site.Garage) || !Nearby(m_Position, observers, m_SpawnRadius)
|
||||
|| !ERR_CivilianTrafficRules.Far(m_Position, observers, 35) || !Hidden(m_Position, m_Angles, actors)) return;
|
||||
|| !ERR_CivilianTrafficRules.Far(m_Position, observers, 35) || !Hidden(m_Position, m_Angles, actors, null, site.Farm)) return;
|
||||
vector transform[4]; Math3D.AnglesToMatrix(m_Angles, transform);
|
||||
if (site.Yard && !YardGround(m_Position, transform)) return;
|
||||
if (!Clear(m_Position, transform)) return;
|
||||
if (site.Yard && !YardGround(m_Position, transform, site.Farm)) return;
|
||||
if (!Clear(m_Position, transform, null, site.Farm)) return;
|
||||
m_NextSpawn = Now() + m_SpawnInterval;
|
||||
IEntity entity = ERR_Actors.Spawn(m_Prefab, m_Position, m_Angles);
|
||||
Vehicle vehicle = Vehicle.Cast(entity);
|
||||
@@ -321,7 +345,9 @@ class ERR_GarageVehicles
|
||||
if (seat.GetType() == ECompartmentType.TURRET) armed = true;
|
||||
if (seat.GetType() == ECompartmentType.PILOT && seat.IsCompartmentAccessible() && !seat.GetOccupant()) driver = true;
|
||||
}
|
||||
if (!driver || armed || !ERR_GarageVehicleRules.Envelope(low, high) || !Clear(m_Position, transform, entity))
|
||||
bool fits = ERR_GarageVehicleRules.Envelope(low, high);
|
||||
if (site.Farm) fits = ERR_GarageVehicleRules.FarmEnvelope(low, high);
|
||||
if (!driver || armed || !fits || !Clear(m_Position, transform, entity, site.Farm))
|
||||
{
|
||||
ERR_Actors.DeleteSpawned(entity); if (m_PendingBike) m_BikeCandidate++; return;
|
||||
}
|
||||
@@ -357,11 +383,12 @@ class ERR_GarageVehicles
|
||||
}
|
||||
// Filled oriented body volume includes furniture, doors, other vehicles and
|
||||
// players. The garage itself is deliberately NOT excluded from collision.
|
||||
protected bool Clear(vector position, vector transform[4], IEntity ignored = null)
|
||||
protected bool Clear(vector position, vector transform[4], IEntity ignored = null, bool farm = false)
|
||||
{
|
||||
TraceOBB body = new TraceOBB();
|
||||
body.Start = position; body.End = position + "0 0.01 0";
|
||||
body.Mins = "-1.1 0.12 -2.5"; body.Maxs = "1.1 2 2.5";
|
||||
if (farm) { body.Mins = "-1.5 0.12 -3"; body.Maxs = "1.5 3.2 3"; }
|
||||
for (int i = 0; i < 3; i++) body.Mat[i] = transform[i];
|
||||
body.Flags = TraceFlags.WORLD | TraceFlags.ENTS | TraceFlags.OCEAN;
|
||||
body.Exclude = ignored;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
class ERR_ParkedCarCatalog
|
||||
{
|
||||
ref array<ResourceName> Prefabs = {};
|
||||
ref array<ResourceName> RoadPrefabs = {};
|
||||
ref array<ResourceName> Tractors = {};
|
||||
protected bool m_Checked;
|
||||
static bool Candidate(string path, bool rally)
|
||||
{
|
||||
@@ -13,27 +15,40 @@ class ERR_ParkedCarCatalog
|
||||
if (rally) return path.Contains("_rally_0");
|
||||
return !path.Contains("_rally");
|
||||
}
|
||||
void Find()
|
||||
static bool MovingCandidate(string path)
|
||||
{
|
||||
path.ToLower();
|
||||
// T601 remains usable as a parked car; native AI did not start it in 1.9.0.37.
|
||||
return Candidate(path, false) && !path.Contains("/t601/");
|
||||
}
|
||||
void Find(bool includeRally = true)
|
||||
{
|
||||
if (m_Checked) return;
|
||||
m_Checked = true;
|
||||
Prefabs.Insert(ERR_CivilianTraffic.CarPrefab(0));
|
||||
Prefabs.Insert(ERR_CivilianTraffic.CarPrefab(1));
|
||||
RoadPrefabs.Copy(Prefabs);
|
||||
SearchResourcesFilter filter = new SearchResourcesFilter();
|
||||
filter.fileExtensions = {"et"}; filter.recursive = true;
|
||||
filter.rootPath = "$" + GameProject.GetAddonID("58D0FB3206B6F859") + ":Prefabs/Vehicles/Wheeled";
|
||||
ResourceDatabase.SearchResources(filter, AddRoad);
|
||||
array<string> loaded = {}; GameProject.GetLoadedAddons(loaded);
|
||||
if (!loaded.Contains("6566A2D70498DA6B")) return;
|
||||
if (!includeRally || !loaded.Contains("6566A2D70498DA6B")) return;
|
||||
filter.rootPath = "$" + GameProject.GetAddonID("6566A2D70498DA6B") + ":Prefabs/Vehicles/Wheeled";
|
||||
ResourceDatabase.SearchResources(filter, AddRally);
|
||||
}
|
||||
protected void AddRoad(ResourceName resource, string path = "")
|
||||
{
|
||||
if (Candidate(resource, false) && Prefabs.Count() < 64 && !Prefabs.Contains(resource)) Prefabs.Insert(resource);
|
||||
if (Candidate(resource, false) && Prefabs.Count() < 64 && !Prefabs.Contains(resource))
|
||||
{
|
||||
Prefabs.Insert(resource);
|
||||
if (MovingCandidate(resource)) RoadPrefabs.Insert(resource);
|
||||
}
|
||||
}
|
||||
protected void AddRally(ResourceName resource, string path = "")
|
||||
{
|
||||
if (Candidate(resource, true) && Prefabs.Count() < 64 && !Prefabs.Contains(resource)) Prefabs.Insert(resource);
|
||||
string lower = resource; lower.ToLower();
|
||||
if (lower.EndsWith("/z7211/z7211_red_civ.et") && Tractors.IsEmpty()) Tractors.Insert(resource);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user