Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions DataModels/FollowTargetState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,28 @@ public FollowTargetState(TEntity nullEntity, IEqualityComparer<TEntity> comparer

public TEntity AttachmentTarget { get; private set; }

public int AttachmentRevision { get; private set; }

public bool HasSubject => !_comparer.Equals(SelectedSubject, _nullEntity);

public void SelectSubject(TEntity subject)
{
SelectedSubject = subject;
AttachmentTarget = subject;
SetAttachmentTarget(subject);
}

public void ResolveAttachmentTarget(TEntity target)
{
AttachmentTarget = HasSubject ? target : _nullEntity;
SetAttachmentTarget(HasSubject ? target : _nullEntity);
}

private void SetAttachmentTarget(TEntity target)
{
if (_comparer.Equals(AttachmentTarget, target))
return;

AttachmentTarget = target;
AttachmentRevision++;
}
}
}
39 changes: 39 additions & 0 deletions DataModels/TransitStopNameFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;

namespace FirstPersonCameraContinued.DataModels
{
public static class TransitStopNameFormatter
{
public const string DefaultStopName = "Stop";

public static string ChooseStopName(params string?[] candidates)
{
foreach (string? candidate in candidates)
{
string normalized = NormalizeDisplayName(candidate);
if (!string.IsNullOrEmpty(normalized))
return normalized;
}

return DefaultStopName;
}

public static string NormalizeDisplayName(string? name)
{
if (name == null)
return "";

if (string.IsNullOrWhiteSpace(name))
return "";

string trimmedName = name.Trim();
return IsRawAssetLocalizationName(trimmedName) ? "" : trimmedName;
}

private static bool IsRawAssetLocalizationName(string name)
{
return name.StartsWith("Assets.NAME[", StringComparison.OrdinalIgnoreCase)
&& name.EndsWith("]", StringComparison.Ordinal);
}
}
}
2 changes: 1 addition & 1 deletion MonoBehaviours/FirstPersonCameraController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public Entity GetFollowEntity()

public Entity GetAttachmentTarget()
{
return _model.AttachmentTarget;
return _transformer?.GetEntityFollower().ResolveAttachmentTarget() ?? _model.AttachmentTarget;
}

public quaternion GetViewRotation()
Expand Down
79 changes: 65 additions & 14 deletions Systems/FirstPersonCameraActivatedUISystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ private void UpdateLineStationInfo(Entity currentEntity)
float pos = allWaypoints[i].normalizedPosition;
if (pos <= midpointPosition)
{
var (streetName, crossStreet) = GetStopStreetAndCrossStreet(allWaypoints[i].stopEntity);
var (streetName, crossStreet) = GetStripMapStopName(allWaypoints[i].stopEntity, isMetroOrTrain);
displayedStations.Add((streetName, crossStreet, allWaypoints[i].position, allWaypoints[i].stopEntity));
}
}
Expand All @@ -499,14 +499,14 @@ private void UpdateLineStationInfo(Entity currentEntity)

if (include)
{
var (streetName, crossStreet) = GetStopStreetAndCrossStreet(allWaypoints[i].stopEntity);
var (streetName, crossStreet) = GetStripMapStopName(allWaypoints[i].stopEntity, isMetroOrTrain);
displayedStations.Add((streetName, crossStreet, allWaypoints[i].position, allWaypoints[i].stopEntity));
}
}

if (firstStationIndex >= 0)
{
var (streetName, crossStreet) = GetStopStreetAndCrossStreet(allWaypoints[firstStationIndex].stopEntity);
var (streetName, crossStreet) = GetStripMapStopName(allWaypoints[firstStationIndex].stopEntity, isMetroOrTrain);
displayedStations.Add((streetName, crossStreet, allWaypoints[firstStationIndex].position, allWaypoints[firstStationIndex].stopEntity));
}
}
Expand Down Expand Up @@ -786,14 +786,15 @@ private LineStationInfo BuildLineStationResult(

private string GetVanillaStopName(Entity stopEntity)
{
if (nameSystem.TryGetCustomName(stopEntity, out var customName))
string customName = GetCustomName(stopEntity);
if (!string.IsNullOrEmpty(customName))
{
return customName;
}

if (BuildingUtils.GetAddress(EntityManager, stopEntity, out var road, out var number))
{
string roadName = AbbreviateSuffix(nameSystem.GetRenderedLabelName(road));
string roadName = AbbreviateSuffix(SafeGetRenderedLabelName(road));
if (!string.IsNullOrEmpty(roadName))
{
return $"{number} {roadName}";
Expand Down Expand Up @@ -900,20 +901,26 @@ private string GetLineColor(Entity routeEntity)

private string FormatStationName(string streetName, string crossStreet, Dictionary<string, int> nameCount, Entity stopEntity)
{
if (nameSystem.TryGetCustomName(stopEntity, out var customName))
string customName = GetCustomName(stopEntity);
if (!string.IsNullOrEmpty(customName))
{
return customName;
}

if (EntityManager.TryGetComponent<Owner>(stopEntity, out var owner) && owner.m_Owner != Entity.Null)
{
if (nameSystem.TryGetCustomName(owner.m_Owner, out var ownerCustomName))
string ownerCustomName = GetCustomName(owner.m_Owner);
if (!string.IsNullOrEmpty(ownerCustomName))
{
return ownerCustomName;
}
}

streetName = TransitStopNameFormatter.NormalizeDisplayName(streetName);
string baseName = GetStreetBaseName(streetName);
if (string.IsNullOrEmpty(baseName))
return TransitStopNameFormatter.DefaultStopName;

if (GetDictionaryValueOrDefault(nameCount, baseName) > 1 && !string.IsNullOrEmpty(crossStreet))
{
string crossBase = GetStreetBaseName(crossStreet);
Expand Down Expand Up @@ -959,8 +966,8 @@ private static int GetDictionaryValueOrDefault(Dictionary<string, int> dictionar
// fallback to stop name
if (string.IsNullOrEmpty(streetName))
{
try { streetName = nameSystem.GetRenderedLabelName(stopEntity); } catch { }
if (string.IsNullOrEmpty(streetName)) streetName = "Stop";
streetName = SafeGetRenderedLabelName(stopEntity);
if (string.IsNullOrEmpty(streetName)) streetName = TransitStopNameFormatter.DefaultStopName;
}

// find cross street
Expand All @@ -982,15 +989,59 @@ private string GetRoadName(Entity roadEdge)
{
if (EntityManager.TryGetComponent<Aggregated>(roadEdge, out var aggregated))
{
try
{
return nameSystem.GetRenderedLabelName(aggregated.m_Aggregate);
}
catch { }
return SafeGetRenderedLabelName(aggregated.m_Aggregate);
}
return "";
}

private (string streetName, string crossStreet) GetStripMapStopName(Entity stopEntity, bool isMetroOrTrain)
{
if (!isMetroOrTrain)
return GetStopStreetAndCrossStreet(stopEntity);

return (
TransitStopNameFormatter.ChooseStopName(
GetCustomName(stopEntity),
GetOwnerCustomName(stopEntity)),
"");
}

private string GetCustomName(Entity entity)
{
if (entity == Entity.Null)
return "";

return nameSystem.TryGetCustomName(entity, out var customName)
? TransitStopNameFormatter.NormalizeDisplayName(customName)
: "";
}

private string GetOwnerCustomName(Entity entity)
{
if (entity == Entity.Null)
return "";

if (EntityManager.TryGetComponent<Owner>(entity, out var owner) && owner.m_Owner != Entity.Null)
return GetCustomName(owner.m_Owner);

return "";
}

private string SafeGetRenderedLabelName(Entity entity)
{
if (entity == Entity.Null)
return "";

try
{
return TransitStopNameFormatter.NormalizeDisplayName(nameSystem.GetRenderedLabelName(entity));
}
catch
{
return "";
}
}

private float3 GetNodePosition(Entity node)
{
if (EntityManager.TryGetComponent<Game.Net.Node>(node, out var nodeComponent))
Expand Down
17 changes: 17 additions & 0 deletions Tests/FollowTargetStateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,21 @@ public void ClearingSubjectAlsoClearsAttachmentTarget()
Assert.Equal(0, state.SelectedSubject);
Assert.Equal(0, state.AttachmentTarget);
}

[Fact]
public void ChangingAttachmentTargetAdvancesAttachmentRevision()
{
var state = new FollowTargetState<int>(0);

state.SelectSubject(42);
int selectedRevision = state.AttachmentRevision;

state.ResolveAttachmentTarget(9001);
int vehicleRevision = state.AttachmentRevision;
state.ResolveAttachmentTarget(9001);

Assert.Equal(42, state.SelectedSubject);
Assert.True(vehicleRevision > selectedRevision);
Assert.Equal(vehicleRevision, state.AttachmentRevision);
}
}
1 change: 1 addition & 0 deletions Tests/FollowTargetStateTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@

<ItemGroup>
<Compile Include="..\DataModels\FollowTargetState.cs" Link="FollowTargetState.cs" />
<Compile Include="..\DataModels\TransitStopNameFormatter.cs" Link="TransitStopNameFormatter.cs" />
</ItemGroup>
</Project>
25 changes: 25 additions & 0 deletions Tests/TransitStopNameFormatterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using FirstPersonCameraContinued.DataModels;
using Xunit;

namespace FirstPersonCameraContinued.Tests;

public class TransitStopNameFormatterTests
{
[Fact]
public void ChooseStopNameUsesCustomNameBeforeTrackAssetLabel()
{
string name = TransitStopNameFormatter.ChooseStopName(
"Custom Stop",
"Assets.NAME[Subway Track]");

Assert.Equal("Custom Stop", name);
}

[Fact]
public void ChooseStopNameIgnoresRawAssetLocalizationLabels()
{
string name = TransitStopNameFormatter.ChooseStopName("Assets.NAME[Subway Track]");

Assert.Equal("Stop", name);
}
}
53 changes: 41 additions & 12 deletions Transformer/CameraTransformer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ internal CameraTransformer( VirtualCameraRig rig, CameraDataModel model )
_model = model;
_manualFinalTransform = new ManualFinalTransform();
_entityFollower = new EntityFollower( _model );
_followEntityFinalTransform = new FollowEntityFinalTransform( _entityFollower );
_followEntityFinalTransform = new FollowEntityFinalTransform( _entityFollower, RefreshScope );
_entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
FinalTransform = _manualFinalTransform;
AddTransforms( );
Expand Down Expand Up @@ -95,9 +95,7 @@ internal CameraTransformer( VirtualCameraRig rig, CameraDataModel model )
}
}

_model.Scope = DetermineScope( );
UpdateEffectToggle( );
OnScopeChanged?.Invoke( ); // Propagate event to listeners
RefreshScope( );
_model.LastFollowEntity = _model.FollowEntity;
};
}
Expand Down Expand Up @@ -136,25 +134,52 @@ private void UpdateEffectToggle()
_model.DisableEffects = _model.Mode == CameraMode.Disabled || _model.Scope != CameraScope.Citizen;
}

private Entity GetScopeEntity()
{
return _model.AttachmentTarget != Entity.Null ? _model.AttachmentTarget : _model.FollowEntity;
}

public void RefreshScope()
{
var previousScope = _model.Scope;
var previousVehicle = _model.ScopeVehicle;
var previousCitizen = _model.ScopeCitizen;

_model.Scope = DetermineScope( );
UpdateEffectToggle( );

if (previousScope != _model.Scope
|| previousVehicle != _model.ScopeVehicle
|| previousCitizen != _model.ScopeCitizen)
{
OnScopeChanged?.Invoke( );
}
}

/// <summary>
/// Determines camera scope from entity type
/// </summary>
/// <returns></returns>
public CameraScope DetermineScope( )
{
if ( _model.FollowEntity == Entity.Null )
var entity = GetScopeEntity( );

_model.ScopeCitizen = null;
_model.ScopeVehicle = VehicleType.Unknown;

if ( entity == Entity.Null )
return CameraScope.Default;

if ( CheckForCitizenScope( out var age ) )
if ( CheckForCitizenScope( entity, out var age ) )
{
_model.ScopeCitizen = age;
return CameraScope.Citizen;
}
else if ( _entityManager.HasComponent<Game.Creatures.Pet>( _model.FollowEntity ) )
else if ( _entityManager.HasComponent<Game.Creatures.Pet>( entity ) )
{
return CameraScope.Pet;
}
else if ( CheckForVehicleScope( out var vehicleType, out _ ) )
else if ( CheckForVehicleScope( entity, out var vehicleType, out _ ) )
{
var isCar = ( vehicleType & VehicleType.Cars ) != 0;
var isVan = ( vehicleType & VehicleType.Vans ) != 0;
Expand All @@ -167,11 +192,11 @@ public CameraScope DetermineScope( )
return CameraScope.Default;
}

private bool CheckForCitizenScope( out CitizenAge citizenAge )
private bool CheckForCitizenScope( Entity entity, out CitizenAge citizenAge )
{
citizenAge = CitizenAge.Teen;

if ( _entityManager.TryGetComponent<Game.Creatures.Resident>( _model.FollowEntity, out var resident ) )
if ( _entityManager.TryGetComponent<Game.Creatures.Resident>( entity, out var resident ) )
{
if ( resident.m_Citizen != Entity.Null &&
_entityManager.TryGetComponent<Citizen>( resident.m_Citizen, out var citizen ) )
Expand All @@ -187,7 +212,11 @@ private bool CheckForCitizenScope( out CitizenAge citizenAge )

public bool CheckForVehicleScope( out VehicleType vehicleType, out string translatedVehicleType )
{
var entity = _model.FollowEntity;
return CheckForVehicleScope( GetScopeEntity( ), out vehicleType, out translatedVehicleType );
}

private bool CheckForVehicleScope( Entity entity, out VehicleType vehicleType, out string translatedVehicleType )
{
var isVehicle = false;

vehicleType = VehicleType.Unknown;
Expand Down Expand Up @@ -343,7 +372,7 @@ public bool CheckForVehicleScope( out VehicleType vehicleType, out string transl

if (_entityManager.HasComponent<Game.Vehicles.PublicTransport>(entity))
{
if (_entityManager.TryGetComponent<Game.Prefabs.PrefabRef>(_model.FollowEntity, out var prefabRefComponent))
if (_entityManager.TryGetComponent<Game.Prefabs.PrefabRef>(entity, out var prefabRefComponent))
{
if (_entityManager.TryGetComponent<Game.Prefabs.PublicTransportVehicleData>(prefabRefComponent.m_Prefab, out var publicTransportVehicleDataComponent))
{
Expand Down
Loading