Repository: VictorPhilipp/Cities-Skylines-Traffic-Manager-President-Edition
Branch: master
Commit: 9d281aebc56e
Files: 297
Total size: 2.6 MB
Directory structure:
gitextract_wdu8mnk_/
├── .gitattributes
├── .gitignore
├── .gitmodules
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
└── TLM/
├── .vs/
│ └── config/
│ └── applicationhost.config
├── ATTACHING_DEBUGGER.md
├── BUILDING_INSTRUCTIONS.md
├── CSUtil.Commons/
│ ├── ArrowDirection.cs
│ ├── ArrowDirectionUtil.cs
│ ├── Benchmark/
│ │ ├── Benchmark.cs
│ │ ├── BenchmarkProfile.cs
│ │ └── BenchmarkProfileProvider.cs
│ ├── CSUtil.Commons.csproj
│ ├── EnumUtil.cs
│ ├── Log.cs
│ ├── LogicUtil.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── TernaryBool.cs
│ ├── TernaryBoolUtil.cs
│ ├── ToStringExt.cs
│ ├── VectorUtil.cs
│ └── packages.config
├── CSUtil.Redirection/
│ ├── CSUtil.Redirection.csproj
│ ├── MethodInfoExt.cs
│ ├── NetworkExtensions.Framework.Unsafe.csproj.DotSettings
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── RedirectionHelper.cs
│ ├── Redirector.cs
│ ├── Transit.Framework.Redirection.csproj.DotSettings
│ └── Transit.Framework.Unsafe.csproj.DotSettings
├── PR_REVIEW_INSTRUCTIONS.md
├── TLM/
│ ├── CodeProfiler.cs
│ ├── Constants.cs
│ ├── Custom/
│ │ ├── AI/
│ │ │ ├── CustomAmbulanceAI.cs
│ │ │ ├── CustomBuildingAI.cs
│ │ │ ├── CustomBusAI.cs
│ │ │ ├── CustomCarAI.cs
│ │ │ ├── CustomCargoTruckAI.cs
│ │ │ ├── CustomCitizenAI.cs
│ │ │ ├── CustomCommonBuildingAI.cs
│ │ │ ├── CustomFireTruckAI.cs
│ │ │ ├── CustomHumanAI.cs
│ │ │ ├── CustomPassengerCarAI.cs
│ │ │ ├── CustomPoliceCarAI.cs
│ │ │ ├── CustomResidentAI.cs
│ │ │ ├── CustomRoadAI.cs
│ │ │ ├── CustomShipAI.cs
│ │ │ ├── CustomTaxiAI.cs
│ │ │ ├── CustomTouristAI.cs
│ │ │ ├── CustomTrainAI.cs
│ │ │ ├── CustomTramBaseAI.cs
│ │ │ ├── CustomTransportLineAI.cs
│ │ │ ├── CustomVehicleAI.cs
│ │ │ └── README.md
│ │ ├── Data/
│ │ │ ├── CustomVehicle.cs
│ │ │ └── README.md
│ │ ├── Manager/
│ │ │ ├── CustomCitizenManager.cs
│ │ │ ├── CustomNetManager.cs
│ │ │ ├── CustomVehicleManager.cs
│ │ │ └── README.md
│ │ ├── PathFinding/
│ │ │ ├── CustomPathFind.cs
│ │ │ ├── CustomPathFind2.cs
│ │ │ ├── CustomPathManager.cs
│ │ │ ├── README.md
│ │ │ └── StockPathFind.cs
│ │ └── README.md
│ ├── Geometry/
│ │ ├── GeometryCalculationMode.cs
│ │ ├── ISegmentEndId.cs
│ │ ├── Impl/
│ │ │ ├── NodeGeometry.cs
│ │ │ ├── SegmentEndGeometry.cs
│ │ │ ├── SegmentEndId.cs
│ │ │ └── SegmentGeometry.cs
│ │ └── README.md
│ ├── LoadingExtension.cs
│ ├── Manager/
│ │ ├── AbstractCustomManager.cs
│ │ ├── AbstractFeatureManager.cs
│ │ ├── AbstractGeometryObservingManager.cs
│ │ ├── IAdvancedParkingManager.cs
│ │ ├── ICustomDataManager.cs
│ │ ├── ICustomManager.cs
│ │ ├── ICustomSegmentLightsManager.cs
│ │ ├── IExtBuildingManager.cs
│ │ ├── IExtCitizenInstanceManager.cs
│ │ ├── IExtCitizenManager.cs
│ │ ├── IFeatureManager.cs
│ │ ├── IGeometryManager.cs
│ │ ├── IJunctionRestrictionsManager.cs
│ │ ├── ILaneArrowManager.cs
│ │ ├── ILaneConnectionManager.cs
│ │ ├── IManagerFactory.cs
│ │ ├── IOptionsManager.cs
│ │ ├── IParkingRestrictionsManager.cs
│ │ ├── IRoutingManager.cs
│ │ ├── ISegmentEndManager.cs
│ │ ├── ISpeedLimitManager.cs
│ │ ├── ITrafficLightManager.cs
│ │ ├── ITrafficLightSimulationManager.cs
│ │ ├── ITrafficMeasurementManager.cs
│ │ ├── ITrafficPriorityManager.cs
│ │ ├── ITurnOnRedManager.cs
│ │ ├── IUtilityManager.cs
│ │ ├── IVehicleBehaviorManager.cs
│ │ ├── IVehicleRestrictionsManager.cs
│ │ ├── IVehicleStateManager.cs
│ │ ├── Impl/
│ │ │ ├── AdvancedParkingManager.cs
│ │ │ ├── CustomSegmentLightsManager.cs
│ │ │ ├── ExtBuildingManager.cs
│ │ │ ├── ExtCitizenInstanceManager.cs
│ │ │ ├── ExtCitizenManager.cs
│ │ │ ├── GeometryManager.cs
│ │ │ ├── JunctionRestrictionsManager.cs
│ │ │ ├── LaneArrowManager.cs
│ │ │ ├── LaneConnectionManager.cs
│ │ │ ├── ManagerFactory.cs
│ │ │ ├── OptionsManager.cs
│ │ │ ├── ParkingRestrictionsManager.cs
│ │ │ ├── RoutingManager.cs
│ │ │ ├── SegmentEndManager.cs
│ │ │ ├── SpeedLimitManager.cs
│ │ │ ├── TrafficLightManager.cs
│ │ │ ├── TrafficLightSimulationManager.cs
│ │ │ ├── TrafficMeasurementManager.cs
│ │ │ ├── TrafficPriorityManager.cs
│ │ │ ├── TurnOnRedManager.cs
│ │ │ ├── UtilityManager.cs
│ │ │ ├── VehicleBehaviorManager.cs
│ │ │ ├── VehicleRestrictionsManager.cs
│ │ │ └── VehicleStateManager.cs
│ │ └── README.md
│ ├── PrintTransportLines.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── README.md
│ ├── Resources/
│ │ ├── incompatible_mods.txt
│ │ ├── lang.txt
│ │ ├── lang_de.txt
│ │ ├── lang_es.txt
│ │ ├── lang_fr.txt
│ │ ├── lang_it.txt
│ │ ├── lang_ja.txt
│ │ ├── lang_ko.txt
│ │ ├── lang_nl.txt
│ │ ├── lang_pl.txt
│ │ ├── lang_pt.txt
│ │ ├── lang_ru.txt
│ │ ├── lang_template.txt
│ │ ├── lang_zh-tw.txt
│ │ └── lang_zh.txt
│ ├── State/
│ │ ├── ConfigData/
│ │ │ ├── AdvancedVehicleAI.cs
│ │ │ ├── Debug.cs
│ │ │ ├── DynamicLaneSelection.cs
│ │ │ ├── Main.cs
│ │ │ ├── ParkingAI.cs
│ │ │ ├── PathFinding.cs
│ │ │ ├── PriorityRules.cs
│ │ │ └── TimedTrafficLights.cs
│ │ ├── Configuration.cs
│ │ ├── Flags.cs
│ │ ├── GlobalConfig.cs
│ │ ├── Options.cs
│ │ ├── README.md
│ │ └── SerializableDataExtension.cs
│ ├── TLM.csproj
│ ├── TMPE.csproj
│ ├── ThreadingExtension.cs
│ ├── Traffic/
│ │ ├── Data/
│ │ │ ├── ExtBuilding.cs
│ │ │ ├── ExtCitizen.cs
│ │ │ ├── ExtCitizenInstance.cs
│ │ │ ├── PrioritySegment.cs
│ │ │ ├── SegmentEndFlags.cs
│ │ │ ├── SegmentFlags.cs
│ │ │ ├── TurnOnRedSegments.cs
│ │ │ └── VehicleState.cs
│ │ ├── ExtVehicleType.cs
│ │ ├── ISegmentEnd.cs
│ │ ├── Impl/
│ │ │ └── SegmentEnd.cs
│ │ ├── README.md
│ │ └── VehicleJunctionTransitState.cs
│ ├── TrafficLight/
│ │ ├── Data/
│ │ │ └── TrafficLightSimulation.cs
│ │ ├── FlowWaitCalcMode.cs
│ │ ├── ICustomSegmentLight.cs
│ │ ├── ICustomSegmentLights.cs
│ │ ├── ITimedTrafficLights.cs
│ │ ├── ITimedTrafficLightsStep.cs
│ │ ├── Impl/
│ │ │ ├── CustomSegment.cs
│ │ │ ├── CustomSegmentLight.cs
│ │ │ ├── CustomSegmentLights.cs
│ │ │ ├── TimedTrafficLights.cs
│ │ │ └── TimedTrafficLightsStep.cs
│ │ ├── LightMode.cs
│ │ ├── README.md
│ │ ├── StepChangeMetric.cs
│ │ └── TrafficLightSimulationType.cs
│ ├── TrafficManager.cs
│ ├── TrafficManagerMod.cs
│ ├── TrafficManagerMode.cs
│ ├── UI/
│ │ ├── CustomKeyHandler.cs
│ │ ├── IncompatibleModsPanel.cs
│ │ ├── LinearSpriteButton.cs
│ │ ├── MainMenu/
│ │ │ ├── ClearTrafficButton.cs
│ │ │ ├── DebugMenu.cs
│ │ │ ├── DespawnButton.cs
│ │ │ ├── JunctionRestrictionsButton.cs
│ │ │ ├── LaneArrowsButton.cs
│ │ │ ├── LaneConnectorButton.cs
│ │ │ ├── MainMenuPanel.cs
│ │ │ ├── ManualTrafficLightsButton.cs
│ │ │ ├── MenuButton.cs
│ │ │ ├── MenuToolModeButton.cs
│ │ │ ├── ParkingRestrictionsButton.cs
│ │ │ ├── PrioritySignsButton.cs
│ │ │ ├── README.md
│ │ │ ├── SpeedLimitsButton.cs
│ │ │ ├── StatsLabel.cs
│ │ │ ├── TimedTrafficLightsButton.cs
│ │ │ ├── ToggleTrafficLightsButton.cs
│ │ │ ├── VehicleRestrictionsButton.cs
│ │ │ └── VersionLabel.cs
│ │ ├── README.md
│ │ ├── RemoveCitizenInstanceButtonExtender.cs
│ │ ├── RemoveVehicleButtonExtender.cs
│ │ ├── SubTool.cs
│ │ ├── SubTools/
│ │ │ ├── JunctionRestrictionsTool.cs
│ │ │ ├── LaneArrowTool.cs
│ │ │ ├── LaneConnectorTool.cs
│ │ │ ├── ManualTrafficLightsTool.cs
│ │ │ ├── ParkingRestrictionsTool.cs
│ │ │ ├── PrioritySignsTool.cs
│ │ │ ├── README.md
│ │ │ ├── SpeedLimitsTool.cs
│ │ │ ├── TimedTrafficLightsTool.cs
│ │ │ ├── ToggleTrafficLightsTool.cs
│ │ │ └── VehicleRestrictionsTool.cs
│ │ ├── TextureResources.cs
│ │ ├── ToolMode.cs
│ │ ├── TrafficManagerTool.cs
│ │ ├── Translation.cs
│ │ ├── TransportDemandViewMode.cs
│ │ ├── UIBase.cs
│ │ ├── UIMainMenuButton.cs
│ │ └── UITransportDemand.cs
│ ├── Util/
│ │ ├── GenericObservable.cs
│ │ ├── GenericUnsubscriber.cs
│ │ ├── IObservable.cs
│ │ ├── IObserver.cs
│ │ ├── IVisitor.cs
│ │ ├── LoopUtil.cs
│ │ ├── ModsCompatibilityChecker.cs
│ │ ├── README.md
│ │ ├── RedirectionHelper.cs
│ │ ├── SegmentLaneTraverser.cs
│ │ ├── SegmentTraverser.cs
│ │ ├── TextureUtil.cs
│ │ └── TinyDictionary.cs
│ └── packages.config
├── TLM.sln.DotSettings
├── TMPE.CitiesGameBridge/
│ ├── Factory/
│ │ └── ServiceFactory.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── Service/
│ │ ├── BuildingService.cs
│ │ ├── CitizenService.cs
│ │ ├── NetService.cs
│ │ ├── PathService.cs
│ │ ├── SimulationService.cs
│ │ └── VehicleService.cs
│ ├── TMPE.CitiesGameBridge.csproj
│ └── packages.config
├── TMPE.GenericGameBridge/
│ ├── Factory/
│ │ └── IServiceFactory.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── Service/
│ │ ├── IBuildingService.cs
│ │ ├── ICitizenService.cs
│ │ ├── INetService.cs
│ │ ├── IPathService.cs
│ │ ├── ISimulationService.cs
│ │ └── IVehicleService.cs
│ ├── TMPE.GenericGameBridge.csproj
│ └── packages.config
├── TMPE.GlobalConfigGenerator/
│ ├── App.config
│ ├── Generator.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── TMPE.GlobalConfigGenerator.csproj
│ └── packages.config
├── TMPE.SpiralLoopTest/
│ ├── App.config
│ ├── Program.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ └── SpiralLoopTest.csproj
├── TMPE.TestGameBridge/
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── TMPE.TestGameBridge.csproj
│ └── packages.config
├── TMPE.UnitTest/
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── TMPE.UnitTest.csproj
│ ├── Util/
│ │ ├── LogicUtilUnitTest.cs
│ │ └── TinyDictionaryUnitTest.cs
│ └── packages.config
├── TMPE.ruleset
└── TMPE.sln
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# Roslyn cache directories
*.ide/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
#NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# If using the old MSBuild-Integrated Package Restore, uncomment this:
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
/logo/
# MSVS 2017 artifacts
/.vs/slnx.sqlite
/TLM/.vs/TMPE/*
# Dependecies game dlls
/TLM/dependencies
================================================
FILE: .gitmodules
================================================
[submodule "TLM/OptionsFramework"]
path = TLM/OptionsFramework
url = ../OptionsFramework.git
[submodule "TLM/CSUtil.CameraControl"]
path = TLM/CSUtil.CameraControl
url = ../CSUtil.CameraControl.git
================================================
FILE: .travis.yml
================================================
language: csharp
solution: "./TLM/TMPE.sln"
branches:
only:
- master
- stable
notifications:
- email: false
================================================
FILE: CHANGELOG.md
================================================
# Cities: Skylines - Traffic Manager: *President Edition* [](https://discord.gg/faKUnST)
# Changelog
10.18, 29/03/2019
- Bugfix: Parking AI: Cars do not spawn at outside connections (#245)
- Bugfix: Trams perform turns on red (#248)
- Update: Service Radius Adjuster mod by Egi removed from incompatible mods list (#255)
10.17, 23/03/2019
- Introduced new versioning scheme (10.17 instead of 1.10.17)
- Synchronized code and version with stable version
- Updated russian translation (thanks to @vitalii201 for translating) (#207)
- Updated list of incompatible mods (#115)
- Removed stable version from list of incompatible mods (#168)
- Turn-on-red can now be toggled for unpreferred turns between one-ways
- Improved train behavior at shunts: Trains now prefer to stay on their track (#230)
- Fixed and optimized lane selection for u-turns and at dead ends (#101)
- Parking AI: Improved public transport (PT) usage patterns, mixed car/PT paths are now possible (#218)
- Bugfix: Parking AI: Tourist cars despawn because they assume they are at an outside connection (#218)
- Bugfix: Parking AI: Return path calculation did not accept beautification segments (#218)
- Bugfix: Parking AI: Cars/Citizens waiting for a path might jump around (#218)
- Bugfix: Vanilla lane randomization does not work as intended at highway transitions (#112)
- Bugfix: Vehicles change lanes at tollbooths (#225)
- Bugfix: Path-finding: Array index is out of range due to a race condition (#221)
- Bugfix: Citizen not found errors when using walking tours (#219)
- Bugfix: Timed light indicator only visible when any timed light node is selected (#222)
1.10.16, 24/02/2019
- Gameplay: Fixed problem with vehicle despawn after road upgrade/remove (thanks @pcfantasy for implementation suggestion)(#86, #101)
- Gameplay: Fixed problem with vehicles unable to choose lane when u-turn at dead-end (thanks @pcfantasy for implementation and @aubergine10 for neccesary tests)(#101)
- Gameplay: Fixed problem when user couldn't change state of 'Turn on Red' while enabled_by_default option not selected (thanks @Sp3ctre18 for bug confirmation) (#102)
- Gameplay: Added missing logic for noise density calculations (thanks to @pcfantasy for fix) (#66)
- UI: New icons for empty and remove_priority_sign settings (thanks @aubergine10 for those icons) (#75, #77)
- Other: Greatly improved incompatible mod scanner, added dialog to list and unsubscribe incompatible mods (#91)
- Other: Changed mod name in Content Manager to __TM:PE__
- Other: Discord server was set up by @FireController1847 - link in mod description
- Other: Fixed 'silent error' inside log related with "Esc key handler" (#92)
- Contribution: Added project building instructions and PR review
1.10.15, 10/02/2019
- Enhancement: Now you can use Escape key to close Traffic Manager without returning to Pause Menu (thanks to @aubergine10 for suggestion) (#16)
- Gameplay: Updated pathfinding with missing vanilla logic
- Gameplay: Tweaked values in CargoTruckAI path finding (thanks to @pcfantasy for improvement suggestion)
- Gameplay: Tweaked speed multiplier of reckless drivers to get more realistic speed range (thanks to @aubergine10 for suggestion) (#23)
- UI: New icons for cargo and passenger train restriction (thanks to @aubergine10) (#17)
- Translations: Simplified Chinese translation updated (thanks to @Emphasia for translating)
- Other: Added notification if user is still subscribed to old original TM:PE
- [Experimental feature] Turn on red (thanks to @FireController1847 for implementation and to @pcfantasy for source code base)
1.10.14, 27/01/2019
- Bugfix: Added missing Car AI type (postVanAI) - now post vans and post trucks are assigned to service vehicles group
- Bugfix: Vehicles doesn't stop when driving through toll booth - fixes toll booth income too
- Bugfix: Cargo Airport doesn't work (Cargo planes not spawning and not arriving)
- Updated Polish translation
- Updated Korean translation (thanks to @Toothless FLY [ROK]LSh.st for translating)
- Fixed Mod Options layout (text label overlaps slider control if too wide)
1.10.13, 31/10/2018
- Bugfix: Tollbooth fix
1.10.12, 08/12/2018
- Added the option to allow/disallow vehicles to enter a blocked junction at transition and pedestrian crossing nodes (#195)
- Updated Russian translation (thanks to vitalii2011 for translating)
- Bent nodes do not allow for u-turns by default (#170)
- Bugfix: Emergency vehicles pass closed barriers at level crossings
- Bugfix: Bus lines render u-turn where they should not (#207)
- Bugfix: Parking AI: Cims leaving the city despawn their car at public transport stations (#214)
- Bugfix: Crossing restrictions do not work at intersection between road and highway (#212)
1.10.11, 21/07/2018
- U-turn lane connections are represented by appropriate lane arrow (#201)
- Bugfix: Heavy vehicles are unable to u-turn at dead ends (#194)
- Bugfix: Routing & Priority rules do not work properly for acute (< 30°)/obtuse(> 150°) segment angles (#199)
- Bugfix: Buses do not prefer lanes with correct lane arrow (#206)
- Bugfix: Race condition in path-finding might cause paths to be assigned to wrong vehicle/citizen (#205)
- Bugfix: Vehicles are unable to perform u-turns when setting off on multi-lane roads (#197)
1.10.10, 14/07/2018
- Parking AI: Improved park & ride behavior
- Parking AI: Walking paths from parking position to destination building take public transportation into account
- Bugfix: Parking AI causes unnecessary path-findings (#183, thanks to Sipke82 for reporting)
- Bugfix: Prohibiting cims from crossing the road also affect paths where crossing is unnecessary (#168, thanks to aubergine10 for reporting)
1.10.9, 13/07/2018
- Updated for game version 1.10.1-f3
- Re-implemented path-finding algorithm
- Updated French translation (thanks to mjm92150 for translating!)
1.10.8, 01/07/2018
- Updated Korean translation (thanks to @Toothless FLY [ROK]LSh.st for translating)
- Updated Polish translation (thanks to @Krzychu1245 for translating)
- Added button to remove parked vehicles (in options dialog, see maintenance tab)
- Parking AI: Removed check for distance between parked vehicle and target building
- Bugfix: Parking AI: Cims spawn pocket cars when they originate from an outside connection
- Bugfix: Incorrect speed limits returned for pedestrian lanes
- Bugfix: Routing is not updated while the game is paused (thanks to @Oh My Lawwwd! for reporting)
- Bugfix: Vanilla traffic lights are ignored when either the priority signs or timed traffic light features are disabled (thanks to @aubergine10 for reporting)
- Bugfix: Park maintenance vehicles are not recognized as service vehicles
- Bugfix: Cars leaving city state "thinking of a good parking spot" (thanks to @aubergine10 for reporting)
1.10.7, 28/05/2018
- Bugfix: U-turn routing is inconsistent on transport lines vs. bus paths (#137, thanks to @Zorgoth for reporting this issue)
- Bugfix: Junction restrictions for pedestrian crossings are sometimes not preserved (#142, thanks to Anrew and @wizardrazer for reporting this issue)
- Fixed: Geometry subscription feature may cause performance issues (#145)
- Fixed: Parking AI: Transport mode storage causes performance issues during loading (#147, thanks to @hannebambel002 and @oneeyets for reporting and further for providing logs and savegames)
1.10.6, 24/05/2018
- Updated for game version 1.10.0-f3
- Accessibility: New option: Main menu size can be controlled
- Accessibility: New option: GUI and overlay transparency can be controlled
- New option: Penalties for switching between different public transport lines can be toggled
- Cims can now be removed from the game
- Improved window design
- Path-finding: Service vehicles are now allowed to ignore lane arrows right after leaving their source building, thus service buildings should now work properly at dead-end roads with median
- Lane connector can be used on monorail tracks
- Advanced Vehicle AI: Tuned parameters
- Dynamic Lane Selection: Absolute speed measurements are used instead of relative measurements
- Improved randomization for realistic speeds such that vehicles may change their target velocity over time
- Improved vehicle position tracking
- Improved mod compatibility checks
- Parking AI: Improved behavior in situations where vehicles are parked near public transport hubs and road connections are partially unavailable
- Bugfix: Parking AI: Not all possible paths are regarded during path-finding
- Bugfix: Parking AI: Cims become confused when trying to return their abandoned car back home (special thanks to Wildcard-25 for reporting and solving this issue)
- Bugfix: Parking AI: Cims do not search for parking building when road-side parking spaces are found
- Bugfix: Parking AI: Parked vehicles are spawned near the source building even when cims are already en route
- Bugfix: Parking AI: Cims sometimes get stuck in an infinite loop while trying to enter their parked car
- Bugfix: Lane connector does not work for roads with more than ten lanes
- Bugfix: Allowing/Disallowing vehicles to enter a blocked junction does not work for certain junctions
- Updated Korean translation (thanks to @Toothless FLY [ROK]LSh.st for translating)
1.10.5, 06/01/2018
- UI scaling removed
- Simplified Chinese translation updated (thanks to Emphasia for translating)
- Polish translation updated (thanks to @Krzychu1245 for translating)
- Introduced randomization for lane changing costs
- Introduced randomization for "trucks prefer innermost lanes on highways" costs
- Removed unnecessary calculations in path-finding
- Added path-finding costs for public transport transitions
- Pedestrian traffic lights do not show up if crossing the street is prohibited
- Busses are allowed to switch multiple lanes after leaving a bus stop
- Bugfix: Main menu button might be out of view
- Bugfix: Division by zero occurs for low speed roads
- Bugfix: Automatic pedestrian lights at railroad do not work as expected
- Bugfix: Timed traffic lights show up for bicycles (they should not)
- Bugfix: Due to a multi-threading issue junction restrictions may cause the game state to become inconsistent
- Bugfix: Routing rules prevents vehicles from spawning when starting building lies too close to an intersection/road end
- Bugfix: Disabling tutorial message has no effect
- Bugfix: "Stay on lane" feature does not work as intended for certain nodes
1.10.4, 19/10/2017
- Updated for game version 1.9.0-f5
- Added possibility to add priority signs at multiple junctions at once (press Shift)
- Added tutorials (can be disabled in the options window globally)
1.10.3, 18/08/2017
- Bugfix: Setting unlimited speed limit causes vehicles to crawl at low speed (thanks to @sethisuwan for reporting this issue)
- Bugfix: Vehicle-separated traffic lights do not show up for trams & monorails (thanks to @thecitiesdork for reporting this issue)
1.10.2, 17/08/2017
- Updated for game version 1.8.0-f3
- Improved performance
- Bugfix: Pedestrians sometimes ignore red traffic light signals (thanks to @(c)RIKUPI™ for reporting this issue)
- Bugfix: Timed traffic lights do not correctly recognize set vehicle restrictions (thanks to @alborzka for reporting this issue)
1.10.1, 05/08/2017
- Updated Polish, Korean, and Simplified Chinese translations
- Bugfix: Default routing is disabled if the lane connector is used on a subset of all available lanes only
- Bugfix: Parking AI cannot be enabled/disabled
- Bugfix: Lane connection points can connected to themselves
1.10.0, 30/07/2017
- New feature: Dynamic Lane Selection
- New feature: Adaptive step switching
- New feature: Individual vehicles may be removed from the game
- New option: Vehicle restrictions aggression
- New option: Vehicles follow priority rules at junctions with timed traffic lights
- Improved path-finding performance
- Improved traffic measurement engine performance
- Reorganized global configuration file (sorry, your main menu and main button positions are reset)
- The option "Road condition has a bigger impact on vehicle speed" is only shown if the Snowfall DLC is owned
- The flow/wait calculation mode to be used is now configurable via the global configuration file
- Added path-find statistics label
- Added confirmation dialog for "Clear Traffic" button
- Currently active timed traffic light step is remembered
- Trains do not wait for each other anymore near timed traffic lights
- It is now possible to connect train station tracks and outside connections with the lane connector
- Disabling the Parking AI triggers graceful clean up procedure
- Relocated some options
- Improved vehicle state tracking
- Workaround for a base game issue that causes trams to get stuck
- Trains do not longer stop in front of green timed traffic lights
- Vehicles use queue skipping to prioritize path-finding runs that are caused by road modifications
- Adding a vehicle separate light to a timed traffic lights applies the main light configuration
- Parking AI: Vehicles can now find parking spaces at the opposite road side
- Parking AI: Included an improved fallback logic for some edge cases
- Parking AI: Citizens should now be more successful in returning their cars back home
- Parking AI: Tuned parking radius parameters
- Parking AI: If the limit for parked vehicles is reached and parking fails due to it, no alternative parking space is queried
- Vehicle AI: Busses prefer lanes with correct lane arrow over incorrect ones
- Bugfix: Using the bulldozer tool might lead to inconsistent road geometry information
- Bugfix: Citizens that fail to approach their parked car fly towards their target building
- Bugfix: Parking AI: Path-finding fails if cars are parked too far away from a road
- Bugfix: Parking AI: Citizens approaching a car start to float away
- Bugfix: "Heavy vehicles prefer outer lanes on highways" does not work
- Bugfix: The lane connector does not allow connecting all available lane end points at train stations and on bidirectional one-lane train tracks
- Bugfix: Vehicles may get stuck in several situations
- Upgrading to a road with bus lanes now copies an already existing traffic light state to the new traffic light
1.9.6, 28/05/2017
- Updated Simplified Chinese translation
- Bugfix: Vehicles cannot perform u-turns at junctions with only one outgoing segment (thanks to @Sunbird for reporting this issue)
- Bugfix: Path-finding costs for large distances exceed the maximum allowed value (thanks to @Huitsi for reporting this issue)
- Bugfix: Under certain circumstances path-finding at railroad crossings allow switching from road to rail tracks.
1.9.5, 24/05/2017
- Updated for game version 1.7.1-f1
- Updated Polish, Korean and Italian translation
- Language can now be switched without requiring a game restart
- Bugfix: Routing calculation does not work as expected for one-way roads with tram tracks (thanks to @bigblade66, @Battelman2 and @AS_ for reporting and providing extensive information)
- Bugfix: Copying timed traffic lights lead to inconsistent internal states which causes timed traffic lights to be omitted during the save process (thanks to @jakeroot and @t1a2l for reporting this issue)
- Bugfix: In certain situations unnecessary vehicle-seperate traffic lights are being created
- Bugfix: Upgrading a train track segment next to a timed traffic light causes trains to ignore the traffic light
- Hotfix: Cable cars despawn at end-of-line stations
1.9.4, 23/05/2017
- New option: Ban private cars and trucks on bus lanes
- Updated Spanish and French translation
- Optimized path-finding
- Increased path-finding cost for private cars driving on bus lanes
- Increased path-finding cost for disregarding vehicle restrictions
- Bugfix: Path-finding is unable to calculate certain paths after modifying the road network
1.9.3, 22/05/2017
- Disabled notification of route recalculating because some players report crashes
- Removed default vehicle restrictions from bus lanes
- Modified junction restrictions come into effect instantaneously
- UI: Saving a timed step does not reset the timed traffic light to the first state
- Bugfix: AI: Segment traffic data is not taken into account
- Bugfix: Priority rules are not properly obeyed
- Bugfix: Under certain circumstances priority signs cannot be removed
- Bugfix: Path-finding is unable to calculate certain paths
1.9.2, 20/05/2017
- UI: Main menu & UI tools performance improved
- Bugfix: Traffic lights can be removed from junctions that are controlled by a timed traffic light program
1.9.1, 19/05/2017
- Updated French, Dutch and Korean translation
- Bugfix: Using the vanilla traffic light toggling feature crashes the game if TMPE's main menu has not been opened at least once
- Bugfix: AI: More car traffic and less public transportation present than in vanilla
1.9.0, 18/05/2017
- Updated for game version 1.7.0-f5
- New feature: Parking restrictions
- New feature: Speed limits can be set up for individual lanes with the Control key
- New feature: Added timed traffic light and speed limit support for monorails
- New feature: Copy & paste for individual timed traffic lights
- New feature: Rotate individual timed traffic lights
- New feature: Lane customizations may come into effect instantaneously
- Unified traffic light toggling feature with game code
- Performance improvements
- Reworked the way that traffic measurements are performed
- Advanced Vehicle AI: Algorithm updated, performance improved - Possible routing decisions are now being precalculated
- Path-finding cost multiplicator for vehicle restrictions is now configurable in TMPE_GlobalConfig.xml
- UI: More compact, movable main menu UI
- Added support for custom languages
- Added Korean translation (thanks to @Toothless FLY [ROK]LSh.st for translating)
- Updated translations: German, Polish, Russian, Portuguese, Traditional Chinese
- Major code refactorings
- AI: Tuned path-finding parameters
- New option: Main button position can be locked
- New option: Main menu position can be locked
- New option: Added language selection in options dialog
- New option: Customization of lane arrows, lane connections and vehicle restrictions can now come into effect instantaneously
- Bugfix: Cars sometimes get stuck forever when the Advanced Parking AI is activated (thanks to @cmfcmf for reporting this issue)
- Bugfix: Busses do not perform u-turns even if the transport line show u-turns (thanks to @dymanoid for reporting this issue)
- Bugfix: Timed traffic lights do not work as expected on single-direction train tracks (thanks to @DaEgi01 for reporting this issue)
- Bugfix: Vehicle restriction and speed limit signs overlay is displayed on the wrong side of inverted road segments
- Bugfix: Influx statistics value is zero (thanks to @hjo for reporting this issue)
1.8.16, 20/03/2017
- Lane connections can now also be removed by pressing the backspace key
- Improved lane selection for busses if the option "Busses may ignore lane arrows" is activated
- Bugfix: The game sometimes freezes when using the timed traffic light tool
- Bugfix: Lane connections are not correctly removed after modifying/removing a junction
- Bugfix: Selecting a junction for setting up junction restrictions toggles the currently hovered junction restriction icon
1.8.15, 27/01/2017
- Updated for game version 1.6.3-f1
1.8.14, 07/01/2017
- Bugfix: Wait/flow ratio at timed traffic lights is sometimes not correctly calculated
- Bugfix: A deadlock situation can arise at junctions with priority signs such that no vehicle enters the junction
- Bugfix: When adding a junction to a timed traffic light, sometimes light states given by user input are not correctly stored
- Bugfix: Joining two timed traffic lights sets the minimum time to "1" for steps with zero minimum time assigned
- Bugfix: Modifications of timed traffic light states are sometimes not visible while editting the light (but they are applied nonetheless)
- Bugfix: Button background is not always correctly changed after clicking on a button within the main menu
- Tram lanes can now be customized by using the lane connector tool
- Minor performance optimizations for priority sign simulation
1.8.13, 05/01/2017
- Bugfix: Timed traffic ligt data can become corrupt when upgrading a road segment next to a traffic light, leading to faulty UI behavior (thanks to @Brain for reporting this issue)
- Bugfix: The position of the main menu button resets after switching to the free camera mode (thanks to @Impact and @gravage for reporting this issue)
- Bugfix: A division by zero exception can occur when calculating the average number of waiting/floating vehicles
- Improved selection of overlay markers on underground roads (thanks to @Padi for reminding me of that issue)
- Minor performance improvements
1.8.12, 02/01/2017
- Updated for game version 1.6.2-f1
- Bugfix: After leaving the "Manual traffic lights" mode the traffic light simulation is not cleaned up correctly (thanks to @diezelunderwood for reporting this issue)
- Bugfix: Insufficient access rights to log file causes the mod to crash
1.8.11, 02/01/2017
- Bugfix: Speed limits for elevated/underground road segments are sometimes not correctly loaded (thanks to @Pirazel and @[P.A.N] Uf0 for reporting this issue)
1.8.10, 31/12/2016
- Improved path-finding performance (a bit)
- Added a check for invalid road thumbnails in the "custom default speed limits" dialog
1.8.9, 29/12/2016
- It is now possible to set speed limits for metro tracks
- Custom default speed limits may now be defined for train and metro tracks
- Junction restrictions may now be controlled at bend road segments
- Customizable junctions are now highlighted by the lane connector tool
- Improved UI behavior
- Performance improvements
- Bugfix: Selecting a junction to set up priority signs sometimes does not work (thanks to @Artemis *Seven* for reporting this issue)
- Bugfix: Automatic pedestrian lights do not work as expected at junctions with incoming one-ways and on left-hand traffic maps
1.8.8, 25/12/2016
- Bugfix: Taxis are not being used
- Bugfix: Prohibiting u-turns with the junction restriction tool does not work (thanks to @Kisoe for reporting this issue)
- Bugfix: Cars are sometimes floating across the map while trying to park (thanks to @[Delta ²k5] for reporting this issue)
1.8.7, 24/12/2016
- Bugfix: Parking AI: Cims that try to reach their parked car are sometimes teleported to another location where they start to fly through the map in order to reach their car
- Bugfix: Parking AI: Cims owning a parked car do not consider using other means of transportation
- Bugfix: Parking AI: Residents are unable to leave the city through a highway outside connection
- Bugfix: Trains/Trams are sometimes not detected at timed traffic lights
- Advanced AI: Improved lane selection
- The position of the main menu button is now forced inside screen bounds on startup
- Improved overall user interface performance
- Improved overlay behavior
- Improved traffic measurement
- Auto pedestrian lights at timed traffic lights behave more intelligently now
- A timed traffic light step with zero minimum time assigned can now be skipped automatically
- Using the lane connector to create a u-turn now automatically enables the "u-turn allowed" junction restriction
- Updated French translation (thanks to @simon.royer007 for translating)
- Added Italian translation (thanks to @Admix for translating)
1.8.6, 12/12/2016
- Added Korean language (thanks to @Toothless FLY [ROK]LSh.st for translating)
- Updated Chinese language code (zh-cn -> zh) in order to make it compatible with the game (thanks to @Lost丶青柠 for reporting this issue)
1.8.5, 11/12/2016
- Updated to game version 1.6.1-f2
- Removed option "Evacuation busses may only be used to reach a shelter" (CO fixed this issue)
- Bugfix: Average speed limits are not correctly calculated for road segments with bicycle lanes (thanks to @Toothless FLY [ROK]LSh.st for reporting this issue)
1.8.4, 11/12/2016
- New feature: "Stay on lane": By pressing Shift + S in the Lane Connector tool you can now link connected lanes such that vehicles are not allowed to change lanes at this point. Press Shift + S again to restrict "stay on lane" to either road direction.
- U-turns are now only allowed to be performed from the innermost lane
- TMPE now detects if the number of spawned vehicles is reaching its limit (16384). If so, spawning of service/emergency vehicles is prioritized over spawning other vehicles.
- Bugfix: Bicycles cannot change from bicycle lanes to pedestrian lanes
- Bugfix: Travel probabilities set in the "Citizen Lifecycle Rebalance v2.1" mod are not obeyed (thanks to @informmanuel, @shaundoddmusic for reporting this issue)
- Bugfix: Number of tourists seems to drop when activating the mod (statistics were not updated, thanks to @hpp7117, @wjrohn for reporting this issue)
- Bugfix: When loading a second savegame a second main menu button is displayed (thanks to @Cpt. Whitepaw for reporting this issue)
- Bugfix: While path-finding is in progress vehicles do "bungee-jumping" on the current segment (thanks to @mxolsenx, @Howzitworld for reporting this issue)
- Bugfix: Cims leaving the city search for parking spaces near the outside connection which is obviously not required
1.8.3, 4/12/2016
- Bugfix: Despite having the Parking AI activated, cims sometimes still spawn pocket cars.
- Bugfix: When the Parking AI is active, bicycle lanes are not used (thanks to @informmanuel for reporting this issue)
- Tweaked u-turn behavior
- Improved info views
1.8.2, 3/12/2016
- Bugfix: Taxis were not used (thanks to @[Delta ²k5] for reporting)
- Bugfix: Minor UI fix in Default speed limits dialog
1.8.1, 1/12/2016
- Updated translations: Polish, Chinese (simplified)
- Bugfix: Mod crashed when loading a second savegame
1.8.0, 29/11/2016
- Updated to game version 1.6.0-f4
- New feature: Default speed limits
- New feature: Parking AI (replaces "Prohibit cims from spawning pocket cars")
- New option: Heavy vehicles prefer outer lanes on highways
- New option: Realistic speeds
- New option: Evacuation busses may ignore traffic rules (Natural Disasters DLC required)
- New option: Evacuation busses may only be used to reach a shelter (Natural Disasters DLC required)
- AI: Improved lane selection, especially on busy roads
- AI: Improved mean lane speed measurement
- Traffic info view shows parking space demand if Parking AI is activated
- Public transport info view shows transport demand if Parking AI is activated
- Added info texts for citizen and vehicle tool tips if Parking AI is activated
- Extracted internal configuration to XML configuration file
- Changed main menu button due to changes in the game's user interface
- Main menu button is now moveable
- Removed compatibility check for Traffic++ V2 (Traffic++ V2 is no longer compatible with TMPE because maintaining compatibility is no longer feasible due to the high effort)
- Updated translations: German, Portuguese, Russian, Dutch, Chinese (traditional)
1.7.15, 26/10/2016
- Bugfix: Timed traffic lights window disappears when clicking on it with the middle mouse button (thanks to @Nexus and @Mariobro14 for helping me identifying the cause of this bug)
1.7.14, 18/10/2016
- Updated for game version 1.5.2-f3
1.7.13, 15/09/2016
- Implemented a permanent fix to solve problems with stuck vehicles/cims caused by third party mods
- Added a button to reset stuck vehicles/cims (see mod settings menu)
- AI: Improved lane selection algorithm
- Bugfix: AI: Lane merging was not working as expected
- Bugfix: Pedestrian light states were sometimes not being stored correctly (thanks to Filip for pointing out this problem)
1.7.12, 09/09/2016
- AI: Lane changes are reduced on congested road segments
- Timed traffic lights should now correctly detect trains and trams
- Bugfix: GUI: Junction restriction icons sometimes disappear
- Updated Chinese (simplified) translation
1.7.11, 01/09/2016
- Updated to game version 1.5.1-f3
1.7.10, 31/08/2016
- Players can now disable spawning of pocket cars
- Updated Chinese (simplified) translation
- Bugfix: Timed traffic lights were flickering
- Bugfix: Pedestrian traffic lights were not working as expected
- Bugfix: When upgrading/removing/adding a road segment, nearby junction restrictions were removed
- Bugfix: Setting up vehicle restrictions affects trams (thanks to @chem for reporting)
- Bugfix: Manual pedestrian traffic light states were not correctly handled
- Bugfix: Junction restrictions overlay did not show all restricted junctions
1.7.9, 22/08/2016
- In-game traffic light states are now correctly rendered when showing "yellow"
- Removed negative effects on public transport usage
- GUI: Traffic light states do not flicker anymore
- Performance improvements
1.7.8, 18/08/2016:
- Bugfix: Cims sometimes got stuck (thanks to all reports and especially to @Thilawyn for providing a savegame)
- GUI: Improved traffic light arrow display
- Improved performance while saving
1.7.7, 16/08/2016:
- AI: Instead of walking long distances, citizens now use a car
- AI: Citizens will remember their last used mode of transport (e.g. they will not drive to work and come return by bus anymore)
- AI: Increased path-finding costs for traversing over restricted road segments
- Added "110" speed limit
- GUI: Windows are draggable
- GUI: Improved window scaling on lower resolutions
- Improved performance while saving
1.7.6, 14/08/2016:
- New feature: Players may now prohibit cims from crossing the street
- AI: Tuned randomization of lane changing behavior
- AI: Introduced path-finding costs for leaving main highway (should reduce amount of detours taken)
- UI: Clicking with the secondary mouse button now deselects the currently selected node/segment for all tools
- Added the possibility to connect train track lanes with the lane connector (as requested by @pilot.patrick93)
- Moved options from "Change lane arrows" to "Vehicle restrictions" tool
- Updated Russian translation
- Bugfix: AI: At specific junctions, vehicles were not obeying lane connections correctly (thanks to @Mariobro14 for pointing out this problem)
- Bugfix: AI: Path-finding costs for u-turns were not correctly calculated (thanks to @Mariobro14 for pointing out this problem)
- Bugfix: Vehicles were endlessly waiting for each other at junctions with certain priority sign configurations
- Bugfix: AI: Lane changing costs corrected
1.7.5, 07/08/2016:
- Bugfix: AI: Cims were using pocket cars whenever possible
- Bugfix: AI: Path-finding failures led to much less vehicles spawning
- Bugfix: AI: Lane selection at junctions with custom lane connection was not always working properly (e.g. for Network Extensions roads with middle lane)
- Bugfix: While editing a timed traffic light it could happen that the traffic light was deleted
1.7.4, 31/07/2016:
- AI: Switched from relative to absolute traffic density measurement
- AI: Tuned new parameters
- Bugfix: Activated/Disabled features were not loaded correctly
- Bugfix: AI: At specific junctions the lane changer did not work as intended
- Possible fix for OSX performance issues
- Code improvements
- Added French translations (thanks to @simon.royer007 for translating!)
1.7.3, 29/07/2016:
- Added the ability to enable/disable mod features (e.g. for performance reasons)
- Bugfix: Vehicle type determination was incorrect (fixed u-turning trams/trains, stuck vehicles)
- Bugfix: Clicking on a train/tram node with the lane connector tool led to an uncorrectable error (thanks to @noaccount for reporting this problem)
- Further code improvements
1.7.2, 26/07/2016:
- Optimized UI overlay performance
1.7.1, 24/07/2016:
- Reverted "Busses now may only ignore lane arrows if driving on a bus lane" for now
- Bugfix: Trains were not despawning if no path could be calculated
- Workaround for third-party issue: TM:PE now detects if the calculation of total vehicle length fails
1.7.0, 23/07/2016:
- New feature: Traffic++ lane connector
- Busses now may only ignore lane arrows if driving on a bus lane
- Rewritten and simplified vehicle position tracking near timed traffic lights and priority signs for performance reasons
- Improved performance of priority sign rules
- AI: Cims now ignore junctions where pedestrian lights never change to green
- AI: Removed the need to define a lane changing probability
- AI: Tweaked lane changing parameters
- AI: Highway rules are automatically disabled at complex junctions (= more than 1 incoming and more than 1 outgoing roads)
- Improved UI performance if overlays are deactivated
- Simulation accuracy now also controls time intervals between traffic measurements
- Added compatibility detection for the Rainfall mod
- Improved fault-tolerance of the load/save system
- Default wait-flow balance is set to 0.8
- Bugfix: Taxis were allowed to ignore lane arrows
- Bugfix: AI: Highway rules on left-hand traffic maps did not work the same as on right-hand traffic maps
- Bugfix: Upgrading a road segment next to a timed traffic light removed the traffic light leading to an inconsistent state (thanks to @ad.vissers for pointing out this problem)
1.6.22, 29/06/2016:
- AI: Taxis now may not ignore lane arrows and are using bus lanes whenever possible (thanks to @Cochy for pointing out this issue)
- AI: Busses may only ignore lane arrows while driving on a bus lane
- Bugfix: Traffic measurement at timed traffic lights was incorrect
1.6.22, 21/06/2016:
- Speed/vehicle restrictions may now be applied to all road segments between two junctions by holding the shift key
- Reworked how changes in the road network are recognized
- Advanced Vehicle AI: Improved lane selection at junctions where bus lanes end
- Advanced Vehicle AI: Improved lane selection of busses
- Improved automatic pedestrian lights
- Improved separate traffic lights: Traffic lights now control traffic lane-wise
- UI: Sensitivity slider is only available while adding/editing a step or while in test mode
- Bugfix: Lane selection on maps with left-hand traffic was incorrect
- Bugfix: While building in pause mode, changes in the road network were not always recognized causing vehicles to stop/despawn
- Bugfix: Police cars off-duty were ignoring lane arrows
- Bugfix: If public transport stops were near a junction, trams/busses were not counted by timed traffic lights (many thanks to Filip for identifying this problem)
- Bugfix: Trains/Trams were sometimes ignoring timed traffic lights (many thanks to Filip for identifying this problem)
- Bugfix: Building roads with bus lanes caused garbage, bodies, etc. to pile up
1.6.21, 14/06/2016:
- Bugfix: Too few cargo trains were spawning (thanks to @Scratch, @toruk_makto1, @Mr.Miyagi, @mottoh and @Syparo for pointing out this problem)
- Bugfix: Vehicle restrictions did not work as expected (thanks to @nordlaser for pointing out this problem)
1.6.20, 11/06/2016:
- Bugfix: Priority signs were not working correctly (thanks to @mottoth, @Madgemade for pointing out this problem)
1.6.19, 11/06/2016
- Bugfix: Timed traffic lights UI not working as expected (thanks to @Madgemade for pointing out this problem)
1.6.18, 09/06/2016
- Updated for game patch 1.5.0-f4
- Improved performance of priority signs and timed traffic lights
- Players can now select elevated rail segments/nodes
- Trams and trains now follow priority signs
- Improved UI behavior when setting up priority signs
1.6.17, 20/04/2016
- Hotfix for reported path-finding problems
1.6.16, 19/04/2016
- Updated for game patch 1.4.1-f2
1.6.15, 22/03/2016
- Updated for game path 1.4.0-f3
- Possible fix for crashes described by @cosminel1982
- Added traditional Chinese translation
1.6.14, 17/03/2016
- Bugfix: Cargo trucks did not obey vehicle restrictions (thanks to @ad.vissers for pointing out this problem)
- Bugfix: When Advanced AI was deactivated, u-turns did not have costs assigned
1.6.13, 16/03/2016
- Added Dutch translation
- The pedestrian light mode of a traffic light can now be switched back to automatic
- Vehicles approaching a different speed limit change their speed more gradually
- The size of signs and symbols in the overlay is determined by screen resolution height, not by width
- Path-finding: Performance improvements
- Path-finding: Fine-tuned lane changing behaviour
- Bugfix: After loading another savegame, timed traffic lights stopped working for a certain time
- Bugfix: Lane speed calculation corrected
1.6.12, 03/03/2016
- Improved memory usage
- Bugfix: Adding/removing junctions to/from existing timed traffic lights did not work (thanks to @nieksen for pointing out this problem)
- Bugfix: Separate timed traffic lights were sometimes not saved (thanks to @nieksen for pointing out this problem)
- Bugfix: Fixed an initialization error (thanks to @GordonDry for pointing out this problem)
1.6.11, 03/03/2016
- Added Chinese translation
- By pressing "Page up"/"Page down" you can now switch between traffic and default map view
- Size of information icons and signs is now based on your screen resolution
- UI code refactored
1.6.10, 02/03/2016
- Additional controls for vehicle restrictions added
- Bugfix: Clicking on a Traffic Manager overlay resulted in vanilla game components (e.g. houses, vehicles) being activated
1.6.9, 02/03/2016
- Updated for game patch 1.3.2-f1
1.6.8, 01/03/2016
- Path-finding: Major performance improvements
- Updated Japanese translation (thanks to @Akira Ishizaki for translating!)
- Added Spanish translation
1.6.7, 27/02/2016
- Tuned AI parameters
- Improved traffic density measurements
- Improved lane changing near junctions: Reintroduced costs for lane changing before junctions
- Improved vehicle behavior near blocked roads (e.g. while a building is burning)
- Bugfix: Automatic pedestrian lights for outgoing one-ways fixed
- Bugfix: U-turns did not have appropriate costs assigned
- Bugfix: The time span between AI traffic measurements was too high
1.6.6, 27/02/2016
- It should now be easier to select segment ends in order to change lane arrows.
- Priority signs now cannot be setup at outgoing one-ways.
- Updated French translation (thanks to @simon.royer007 for translating!)
- Updated Polish translation (thanks to @Krzychu1245 for translating!)
- Updated Portuguese translation (thanks to @igordeeoliveira for translating!)
- Updated Russian translation (thanks to @FireGames for translating!)
- Bugfix: U-turning vehicles were not obeying the correct directional traffic light (thanks to @t1a2l for pointing out this problem)
1.6.5, 24/02/2016
- Added despawning setting to options dialog
- Improved detection of Traffic++ V2
1.6.4, 23/02/2016
- Minor performance improvements
- Bugfix: Path-finding calculated erroneous traffic density values
- Bugfix: Cims left the bus just to hop on a bus of the same line again (thanks to @kamzik911 for pointing out this problem)
- Bugfix: Despawn control did not work (thanks to @xXHistoricalxDemoXx for pointing out this problem)
- Bugfix: State of new settings was not displayed corretly (thanks to @Lord_Assaultーさま for pointing out this problem)
- Bugfix: Default settings for vehicle restrictions on bus lanes corrected
- Bugfix: Pedestrian lights at railway junctions fixed (they are still invisible but are derived from the car traffic light state automatically)
1.6.3, 22/02/2016
- Bugfix: Using the "Old Town" policy led to vehicles not spawning.
- Bugfix: Planes, cargo trains and ship were sometimes not arriving
- Bugfix: Trams are not doing u-turns anymore
1.6.2, 20/02/2016
- Trams are now obeying speed limits (thanks to @Clausewitz for pointing out the issue)
- Bugfix: Clear traffic sometimes throwed an error
- Bugfix: Vehicle restrctions did not work as expected (thanks to @[Delta ²k5] for pointing out this problem)
- Bugfix: Transition of automatic pedestrian lights fixed
1.6.1, 20/02/2016
- Improved performance
- Bugfix: Fixed UI issues
- Modifying mod options through the main menu now gives an annoying warning message instead of a blank page.
1.6.0, 18/02/2016
- New feature: Separate traffic lights for different vehicle types
- New feature: Vehicle restrictions
- Snowfall compatibility
- Better handling of vehicle bans
- Improved the method for calculating lane traffic densities
- Ambulances, fire trucks and police cars on duty are now ignoring lane arrows
- Timed traffic lights may now be setup at arbitrary nodes on railway tracks
- Reckless drivers now do not enter railroad crossings if the barrier is down
- Option dialog is disabled if accessed through the main menu
- Performance optimizations
- Advanced Vehicle AI: Improved lane spreading
- The option "Vehicles may enter blocked junctions" may now be defined for each junction separately
- Vehicles going straight may now change lanes at junctions
- Vehicles may now perform u-turns at junctions that have an appropriate lane arrow configuration
- Road conditions (snow, maintenance state) may now have a higher impact on vehicle speed (see "Options" menu)
- Emergency vehicles on duty now always aim for the the fastest route
- Bugfix: Path-finding costs for crossing a junction fixed
- Bugfix: Vehicle detection at timed traffic lights did not work as expected
- Bugfix: Not all valid traffic light arrow modes were reachable
1.5.2, 01/02/2016
- Traffic lights may now be added to/removed from underground junctions
- Traffic lights may now be setup at *some* points of railway tracks (there seems to be a game-internal bug that prevents selecting arbitrary railway nodes)
- Display of priority signs, speed limits and timed traffic lights may now be toggled via the options dialog
- Bugfix: Reckless driving does not apply for trains (thanks to @GordonDry for pointing out this problem)
- Bugfix: Manual traffic lights were not working (thanks to @Mas71 for pointing out this problem)
- Bugfix: Pedestrians were ignoring timed traffic lights (thanks to @Hannes8910 for pointing out this problem)
- Bugfix: Sometimes speed limits were not saved (thanks to @cca_mikeman for pointing out this problem)
1.5.1, 31/01/2016
- Trains are now following speed limits
1.5.0, 30/01/2016
- New feature: Speed restrictions (as requested by @Gfurst)
- AI: Parameters tuned
- Code improvements
- Lane arrow changer window is now positioned near the edited junction (as requested by @GordonDry)
- Bugfix: Flowing/Waiting vehicles count corrected
1.4.9, 27/01/2016
- Junctions may now be added to/removed from timed traffic lights after they are created
- When viewing/moving a timed step, the displayed/moved step is now highlighted (thanks to Joe for this idea)
- Performance improvements
- Bugfix (AI): Fixed a division by zero error (thanks to @GordonDry for pointing out this problem)
- Bugfix (AI): Near highway exits vehicles tended to use the outermost lane (thanks to @Zake for pointing out this problem)
- Bugfix: Some lane arrows disappeared on maps using left-hand traffic systems (thanks to @Mas71 for pointing out this problem)
- Bugfix: In lane arrow edit mode, the order of arrows was sometimes incorrect (thanks to @Glowstrontium for pointing out this problem)
- Bugfix: Lane merging in left-hand traffic systems fixed
- Bugfix: Turning priority roads fixed (thanks to @GordonDry for pointing out this problem)
1.4.8, 25/01/2016
- AI: Parameters have been tuned
- AI: Added traffic density measurements
- Performance improvements
- Added translation to Polish (thanks to @Krzychu1245 for working on this!)
- Added translation to Russian (thanks to @FireGames for working on this!)
- Bugfix: After removing a timed or manual light the traffic light was deleted (thanks to @Mas71 for pointing out this problem)
- Bugfix: Segment geometries were not always calculated
- Bugfix: In highway rule mode, lane arrows sometimes flickered
- Bugfix: Some traffic light arrows were sometimes not selectable
1.4.7, 22/01/2016
- Added translation to Portuguese (thanks to @igordeeoliveira for working on this!)
- Reduced mean size of files can become quite big (thanks to @GordonDry for reporting this problem)
- Bugfix: Freight ships/trains were not coming in (thanks to @Mas71 and @clus for reporting this problem)
- Bugfix: The toggle "Vehicles may enter blocked junctions" did not work properly (thanks for @exxonic for reporting this problem)
- Bugfix: If a timed traffic light is being edited the segment geometry information is not updated (thanks to @GordonDry for reporting this problem)
1.4.6, 22/01/2016
- Running average lane speeds are measured now
- Minor fixes
1.4.5, 22/01/2016
- The option "Vehicles may enter blocked junctions" may now be defined for each junction separately
- Bugfix: A deadlock in the path-finding is fixed
- Bugfix: Small timed light sensitivity values (< 0.1) were not saved correctly
- Bugfix: Timed traffic lights were not working for some players
- Refactored segment geometry calculation
1.4.4, 21/01/2016
- Added localization support
1.4.3, 20/01/2016
- Several performance improvements
- Improved calculation of segment geometries
- Improved load balancing
- Police cars, ambulances, fire trucks and hearses are now also controlled by the AI
- Bugfix: Vehicles did not always take the shortest path
- Bugfix: Vehicles disappeared after deleting/upgrading a road segment
- Bugfix: Fixed an error in path-finding cost calculation
- Bugfix: Outgoing roads were treated as ingoing roads when highway rules were activated
1.4.2, 16/01/2016
- Several major performance improvements (thanks to @sci302 for pointing out those issues)
- Improved the way traffic lights are saved/loaded
- Lane-wise traffic density is only measured if Advanced AI is activated
- Bugfix: AI did not consider speed limits/road types during path calculation (thanks to @bhanhart, @sa62039 for pointing out this problem)
- Connecting a city road to a highway road that does not supply enough lanes for merging leads to behavior people do not understand (see manual). Option added to disable highway rules.
- Bugfix: Vehicles were stopping in front of green traffic lights
- Bugfix: Stop/Yield signs were not working properly (thanks to @GordonDry, @Glowstrontium for pointing out this problem)
- Bugfix: Cargo trucks were ignoring the "Heavy ban" policy, they should do now (thanks to @Scratch for pointing out this problem)
1.4.1, 15/01/2016
- Bugfix: Path-finding near junctions fixed
1.4.0, 15/01/2016
- Introducing Advanced Vehicle AI (disabled by default! Go to "Options" and enable it if you want to use it.)
- Bugfix: Traffic lights were popping up in the middle of roads
- Bugfix: Fixed the lane changer for left-hand traffic systems (thanks to @Phishie for pointing out this problem)
- Bugfix: Traffic lights on invalid nodes are not saved anymore
1.3.24, 13/01/2016
- Improved handling of priority signs
- Priority signs: After adding two main road signs the next offered sign is a yield sign
- Priority signs: Vehicles now should notice earlier that they can enter a junction
- Removed the legacy XML file save system
- Invalid (not created) lanes are not saved/loaded anymore
- Added a configuration option that allows vehicles to enter blocked junctions
- Bugfix: Some priority signs were not saved
- Bugfix: Priority signs on deleted segments are now deleted too
- Bugfix: Lane arrows on removed lanes are now removed too
- Bugfix: Adding a priority sign to a junction having more than one main sign creates a yield sign (thanks to @GordonDry for pointing out this problem)
- Bugfix: If reckless driving was set to "The Holy City (0 %)", vehicles blocked intersections with traffic light.
- Bugfix: Traffic light arrow modes were sometimes not correctly saved
1.3.23, 09/01/2016
- Bugfix: Corrected an issue where toggled traffic lights would not be saved/loaded correctly (thanks to @Jeffrios and @AOD_War_2g for pointing out this problem)
- Option added to forget all toggled traffic lights
1.3.22, 08/01/2016
- Added an option allowing busses to ignore lane arrows
- Added an option to display nodes and segments
1.3.21, 06/01/2016
- New feature: Traffic Sensitivity Tuning
- UI improvements: When adding a new step to a timed traffic light the lights are inverted.
- Timed traffic light status symbols should now be less annoying
- Bugfix: Deletion of junctions that were members of a traffic light group is now handled correctly
1.3.20, 04/01/2016
- Bugfix: Timed traffic lights are not saved correctly after upgrading a road nearby
- UI improvements
- New feature: Reckless driving
1.3.19, 04/01/2016
- Timed traffic lights: Absolute minimum time changed to 1
- Timed traffic lights: Velocity of vehicles is being measured to detect traffic jams
- Improved traffic flow measurement
- Improved path finding: Cims may now choose their lanes more independently
- Bugfix: Upgrading a road resets the traffic light arrow mode
1.3.18, 03/01/2016
- Provided a fix for unconnected junctions caused by other mods
- Crosswalk feature removed. If you need to add/remove crosswalks please use the "Crossings" mod.
- UI improvements: You can now switch between activated timed traffic lights without clicking on the menu button again
1.3.17, 03/01/2016
- Bugfix: Timed traffic lights cannot be added again after removal, toggling traffic lights does not work (thanks to @Fabrice, @ChakyHH, @sensual.heathen for pointing out this problem)
- Bugfix: After using the "Manual traffic lights" option, toggling lights does not work (thanks to @Timso113 for pointing out this problem)
1.3.16, 03/01/2016
- Bugfix: Traffic light settings on roads of the Network Extensions mods are not saved (thanks to @Scarface, @martintech and @Sonic for pointing out this problem)
- Improved save data management
1.3.15, 02/01/2016
- Simulation accuracy (and thus performance) is now controllable through the game options dialog
- Bugfix: Vehicles on a priority road sometimes stop without an obvious reason
1.3.14, 01/01/2016
- Improved performance
- UI: Non-timed traffic lights are now automatically removed when adding priority signs to a junction
- Adjusted the adaptive traffic light decision formula (vehicle lengths are considered now)
- Traffic two road segments in front of a timed traffic light is being measured now
1.3.13, 01/01/2016
- Bugfix: Lane arrows are not correctly translated into path finding decisions (thanks to @bvoice360 for pointing out this problem)
- Bugfix: Priority signs are sometimes undeletable (thank to @Blackwolf for pointing out this problem)
- Bugfix: Errors occur when other mods without namespace definitions are loaded (thanks to @Arch Angel for pointing out this problem)
- Connecting a new road segment to a junction that already has priority signs now allows modification of the new priority sign
1.3.12, 30/12/2015
- Bugfix: Priority signs are not editable (thanks to @ningcaohan for pointing out this problem)
1.3.11, 30/12/2015
- Road segments next to a timed traffic light may now be deleted/upgraded/added without leading to deletion of the light
- Priority signs and Timed traffic light state symbols are now visible as soon as the menu is opened
1.3.10, 29/12/2015
- Fixed an issue where timed traffic light groups were not deleted after deleting an adjacent segment
1.3.9, 29/12/2015
- Introduced information icons for timed traffic lights
- Mod is now compatible with "Improved AI" (Lane changer is deactivated if "Improved AI" is active)
1.3.8, 29/12/2015
- Articulated busses are now simulated correctly (thanks to @nieksen for pointing out this problem)
- UI improvements
1.3.7, 28/12/2015
- When setting up a new timed traffic light, yellow lights from the real-world state are not taken over
- When loading another save game via the escape menu, Traffic Manager does not crash
- When loading another save game via the escape menu, Traffic++ detection works as intended
- Lane arrows are saved correctly
1.3.6, 28/12/2015
- Bugfix: wrong flow value taken when comparing flowing vehicles
- Forced node rendering after modifying a crosswalk
1.3.5, 28/12/2015
- Fixed pedestrian traffic Lights (thanks to @Glowstrontium for pointing out this problem)
- Better fix for: Deleting a segment with a timed traffic light does not cause a NullReferenceException
- Adjusted the comparison between flowing (green light) and waiting (red light) traffic
1.3.4, 27/12/2015
- Better traffic jam handling
1.3.3, 27/12/2015
- (Temporary) hotfix: Deleting a segment with a timed traffic light does not cause a NullReferenceException
- If priority signs are located behind the camera they are not rendered anymore
1.3.2, 27/12/2015
- Priority signs are persistently visible when Traffic Manager is in "Add priority sign" mode
- Synchronized traffic light rendering: In-game Traffic lights display the correct color (Thanks to @Fabrice for pointing out this problem)
- Traffic lights switch between green, yellow and red. Not only between green and red.
- UI tool tips are more explanatory and are shown longer.
1.3.1, 26/12/2015
- Minimum time units may be zero now
- Timed traffic lights of deleted/modified junctions get properly disposed
1.3.0, 25/12/2015
- **Adaptive Timed Traffic Lights** (automatically adjusted based on traffic amount)
1.2.0 (iMarbot)
- Updated for 1.2.2-f2 game patch.
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 Svetlozar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Traffic Manager: *President Edition* [](https://discord.gg/faKUnST) [](https://ci.appveyor.com/project/krzychu124/cities-skylines-traffic-manager-president-edition/branch/master)
A mod for **Cities: Skylines** that gives you more control over road and rail traffic in your city.
[Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=1637663252) • [Installation](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/wiki/Installation) • [User Guide](http://www.viathinksoft.de/tmpe/wiki) • [Issue Tracker](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/issues) • [Report a Bug](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/wiki/Report-a-Bug)
# Features
* Timed traffic lights
* Change lane arrows
* Edit lane connections
* Add priority signs
* Junction restrictions
* Toggle u-turns
* Allow "turn on red"
* Enter blocked junctions
* Toggle pedestrian crossings
* Vehicle restrictions
* For roads and trains!
* Customise speed limits
* Toggle despawn
* Clear traffic, stuck cims, etc.
# Changelog
### [10.18](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.17...10.18), 29/03/2019
- Bugfix: Parking AI: Cars do not spawn at outside connections (#245)
- Bugfix: Trams perform turns on red (#248)
- Update: Service Radius Adjuster mod by Egi removed from incompatible mods list (#255)
See [Full Changelog](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/blob/master/CHANGELOG.md) for details of earlier releases.
# Contributing
We welcome contributions from the community!
Contact us:
* [Issue tracker](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/issues)
* [Discord (chat)](https://discord.gg/faKUnST)
* [Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=1637663252)
# License
[MIT License](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/blob/master/LICENSE) (open source)
================================================
FILE: TLM/.vs/config/applicationhost.config
================================================
================================================
FILE: TLM/ATTACHING_DEBUGGER.md
================================================
# Attaching Debugger to Cities.exe
Use this guide to attach a debugger to Cities: Skylines.
> **Notes:**
> * Attaching a debugger can significantly reduce frame rate and cause lots of lag.
> * This has only been tested on Windows.
### Setup
> You only need to follow these steps once to set up your debug environment.
First, let's backup your current ```mono.dll```:
* Navigate to ```<%STEAM%>\steamapps\common\Cities_Skylines\Cities_Data\Mono\```
* Make a backup of ```mono.dll``` (you could just rename it ```mono-backup.dll```)
> The location of ```<%STEAM%>``` is usually ```C:\Program Files (x86)\Steam```
Next, download the following files from [```https://github.com/0xd4d/dnSpy/releases```](https://github.com/0xd4d/dnSpy/releases):
* ```dnSpy-net472.zip```
* ```Unity-debugging-5.x.zip```
Now we apply a new ```mono.dll``` and test the game is working:
* Make sure the game is **not** running
* Open ```Unity-debugging-5.x.zip```:
* Navigate to ```Unity-debugging\unity-5.6.6\win64\``` (note: **```unity-5.6.6```**)
* Copy ```mono.dll``` to ```<%STEAM%>\steamapps\common\Cities_Skylines\Cities_Data\Mono\```
* Run the game to check if it's working:
* If not, delete the downloaded ```mono.dll``` then restore the original version
* You'll have to scour the internet to work out what went wrong, sorry.
* Close the game
Next, add two environment variables:
* Press **Win+R** (_Run dialog appears_):
* Enter ```sysdm.cpl```
* Choose **OK**
* On the **Advanced** tab, choose **Environment Variables...**
* The variables to add are shown below:
1. > **key:** ```DNSPY_UNITY_DBG```
> **value:** ```--debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555,defer=y,no-hide-debugger```
2. > **key:** ```DNSPY_UNITY_DBG2```
> **value:** ```--debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555,suspend=n,no-hide-debugger```
Finally, unarchive **dnSpy**:
* Extract the downloaded ```dnSpy-net472.zip``` to a folder
* It can be anywhere, eg. ```dnSpy/``` on your desktop
### Debugging
> Do this each time you want to debug the game.
First, launch **dnSpy**:
* Run ```dnSpy.exe```
* On the left, in **Assembly Explorer**, remove any ```.dll``` files that are listed
* Tip: Select one, press **Ctrl+A** then **Delete**
Now run the game and attach **dnSpy**:
* Start ```Cities.exe```
* **Alt+Tab** to **dnSpy** app
* Press **F5** (or choose **Start**) and select:
* **Debug Engine:** ```Unity (Connect)```
* **Port:** ```55555```
* Click **OK**:
* You should see an **orange status bar** at the bottom of application with text: ```Running...```
* From the **Debug** menu, choose **Windows -> Modules** _(Ctrl+Shift+A)_
* You should see lots of ```.dll``` files and some ```data-00...``` entries
* **Right-click** on any of them, select **Open All Modules**, then * Click **OK**
* The game may hang for few seconds
* On the left, in **Assembly Explorer**, you should see all ```.dll``` files loaded in-game
* There will be some duplicates
* **Right-click** on any of them, then **Sort Assemblies** to make the list easier to work with
That's it, you are debugging. Now your mods are sure to be bugless :P
### Reverting
If you want to return the game back to normal:
* Exit the game
* Replace the downloaded ```mono.dll`` with your backup of the original ```mono.dll```
* Start the game
I'm sure you can work out how to simplify or automate toggling between the two ```mono.dll``` files :)
### Tips
* Use **Search** tab _(Ctrl+Shift+K)_ for to find class, property, field, method, etc...
* You can right-click a method definition then select **Analyze** to see where it's used
### Notes
* I have no idea why there are duplicated libraries (some sort of protection?)
* Only one copy of each library will have working breakpoints
* After sorting assemblies, it's usually the first instance of a listed file
* Once you know which one it is, you can safely remove the other from Assembly Explorer
* Don't rebuild your mod library with game running, otherwise you'll have to clear Assembly Explorer and open the modules again, which means the duplicates come back
================================================
FILE: TLM/BUILDING_INSTRUCTIONS.md
================================================
# Project building instructions
#### Prerequisites:
* [Git for Windows](https://gitforwindows.org/) / [GitHub Desktop](https://desktop.github.com/)
* one prefered __IDE__ _(Integrated Development Environment)_ to build project:
* Visual Studio 2017 Community (free)
* JetBrains Rider (paid)
* or other similar...
* sources of this repository:
* Git for Windows console:
* use ```git clone https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition``` to download sources of this repository
* after successful cloning type ```cd Cities-Skylines-Traffic-Manager-President-Edition```
* then ```git submodule update --init --recursive``` to fetch dependencies
* Github desktop:
* clone repository using this link ```https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition.git```
* dependencies should be installed automatically
## To build project follow actions:
Open __TMPL.sln__ located at ``` \TLM\``` using preferred __IDE__.
##### Visual Studio:
* use dropdown from __actions bar__ _(dropdown located under Team menu)_ to select _desired_ solution configuration
* fix missing libraries locations - inside __Solution Explorer__ right click on every project and select __Properties__
then __Reference Paths__ and add __Managed__ folder from game directory located under ```\Cities_Data\Managed```
* to build project with selected configuration choose one of actions:
* right click on __TLM__ project from __Solution Explorer__
* use _Build_ menu -> _Build Solution_ __F6__
##### JetBrains Rider:
* currently there is no GUI for adding Reference Paths so you have to create config for every project inside from scratch:
1. Create file with extension ```*.csproj.user```named as project name e.g. ```TLM.csproj.user```
2. Paste below code inside newly created file and replace `````` with correct path
```
\Cities_Data\Managed\
```
* select configuration using dropdown located on _actions bar_
* use __Ctrl+F9__ to build solution, or __right click__ on solution inside __File Explorer (Alt+1)__
================================================
FILE: TLM/CSUtil.Commons/ArrowDirection.cs
================================================
using System;
using System.Collections.Generic;
using System.Text;
namespace CSUtil.Commons {
public enum ArrowDirection {
None = 0,
Left = 1,
Forward = 2,
Right = 3,
Turn = 4
}
}
================================================
FILE: TLM/CSUtil.Commons/ArrowDirectionUtil.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSUtil.Commons {
public class ArrowDirectionUtil {
public static ArrowDirection InvertLeftRight(ArrowDirection dir) {
if (dir == ArrowDirection.Left)
dir = ArrowDirection.Right;
else if (dir == ArrowDirection.Right)
dir = ArrowDirection.Left;
return dir;
}
///
/// Calculates the direction of in relation to ArrowDirection.TURN.
///
/// source direction
/// target direction, relative to
///
public static ArrowDirection MakeAbsolute(ArrowDirection fromDir, ArrowDirection toRelDir) {
if (fromDir == ArrowDirection.None) {
// invalid direction
return ArrowDirection.None;
}
if (toRelDir == ArrowDirection.None) {
// invalid direction
return ArrowDirection.None;
}
if (fromDir == ArrowDirection.Turn) {
// toRelDir is already relative to TURN
return toRelDir;
}
if (toRelDir == ArrowDirection.Turn) {
// toRelDir is fromDir
return fromDir;
}
int fromDirBase0 = (int)fromDir - 1;
int toRelDirBase1 = (int)toRelDir;
/*
* Direction | Base 0 | Base 1
* ==========+========+=======
* Left | 0 | 1
* Forward | 1 | 2
* Right | 2 | 3
*
*
* Direction 1 | Direction 2 | Dir. 1 B0 | Dir. 2 B1 | Sum | (Sum + 1) % 4 | Desired dir.
* ============+=============+===========+===========+=====|===============+=============
* Left | Left | 0 | 1 | 1 | 2 | (Forward, 2)
* Left | Forward | 0 | 2 | 2 | 3 | (Right, 3)
* Left | Right | 0 | 3 | 3 | 0 | (Turn, 4)
* Forward | Left | 1 | 1 | 2 | 3 | (Right, 3)
* Forward | Forward | 1 | 2 | 3 | 0 | (Turn, 4)
* Forward | Right | 1 | 3 | 4 | 1 | (Left, 1)
* Right | Left | 2 | 1 | 3 | 0 | (Turn, 4)
* Right | Forward | 2 | 2 | 4 | 1 | (Left, 1)
* Right | Right | 2 | 3 | 5 | 2 | (Forward, 2)
*/
int ret = (fromDirBase0 + toRelDirBase1 + 1) % 4;
if (ret == 0) {
ret = 4;
}
return (ArrowDirection)ret;
}
}
}
================================================
FILE: TLM/CSUtil.Commons/Benchmark/Benchmark.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
namespace CSUtil.Commons.Benchmark {
public class Benchmark : IDisposable {
private BenchmarkProfile profile;
public Benchmark(string id = null, string postfix = null) {
if (id == null) {
StackFrame frame = new StackFrame(1);
MethodBase method = frame.GetMethod();
id = method.DeclaringType.Name + "#" + method.Name;
}
if (postfix != null) {
id += "#" + postfix;
}
profile = BenchmarkProfileProvider.Instance.GetProfile(id);
profile.Start();
}
public void Dispose() {
profile.Stop();
}
}
}
================================================
FILE: TLM/CSUtil.Commons/Benchmark/BenchmarkProfile.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace CSUtil.Commons.Benchmark {
public class BenchmarkProfile {
public string Id { get; private set; }
private Stopwatch timer;
public int NumBenchmarks { get; private set; } = 0;
public BenchmarkProfile(string id) {
Id = id;
timer = new Stopwatch();
}
public void Start() {
timer.Start();
}
public void Stop() {
if (timer.IsRunning) {
timer.Stop();
++NumBenchmarks;
}
}
public TimeSpan GetElapsedTime() {
return timer.Elapsed;
}
}
}
================================================
FILE: TLM/CSUtil.Commons/Benchmark/BenchmarkProfileProvider.cs
================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSUtil.Commons.Benchmark {
public class BenchmarkProfileProvider {
public static readonly BenchmarkProfileProvider Instance = new BenchmarkProfileProvider();
private IDictionary Profiles = new Dictionary();
public BenchmarkProfile GetProfile(string id) {
BenchmarkProfile profile = null;
Profiles.TryGetValue(id, out profile);
if (profile == null) {
profile = new BenchmarkProfile(id);
Profiles.Add(id, profile);
}
return profile;
}
public void ClearProfiles() {
Profiles.Clear();
}
public string CreateReport() {
string ret = "=== BENCHMARK REPORT ===\n";
ret += "=== ORDERED BY TOTAL TIME ===\n";
List orderedKeys = new List(Profiles.Keys);
orderedKeys.Sort(delegate (string x, string y) {
long xTicks = Profiles[x].GetElapsedTime().Ticks;
long yTicks = Profiles[y].GetElapsedTime().Ticks;
return yTicks.CompareTo(xTicks);
});
ret = CreateReport(ret, orderedKeys);
ret += "\n=== ORDERED BY AVG. TIME ===\n";
orderedKeys = new List(Profiles.Keys);
orderedKeys.Sort(delegate (string x, string y) {
BenchmarkProfile xProfile = Profiles[x];
BenchmarkProfile yProfile = Profiles[y];
if (xProfile.NumBenchmarks <= 0 && yProfile.NumBenchmarks <= 0) {
return 0;
} else if (xProfile.NumBenchmarks > 0 && yProfile.NumBenchmarks <= 0) {
return -1;
} else if (xProfile.NumBenchmarks <= 0 && yProfile.NumBenchmarks > 0) {
return 1;
} else {
float xAvg = (float)xProfile.GetElapsedTime().TotalMilliseconds / (float)xProfile.NumBenchmarks;
float yAvg = (float)yProfile.GetElapsedTime().TotalMilliseconds / (float)yProfile.NumBenchmarks;
return yAvg.CompareTo(xAvg);
}
});
ret = CreateReport(ret, orderedKeys);
return ret;
}
private string CreateReport(string ret, List orderedKeys) {
foreach (string key in orderedKeys) {
BenchmarkProfile profile = Profiles[key];
ret += $"\t{key}: {profile.GetElapsedTime()} ({profile.NumBenchmarks} benchmarks, avg. {(profile.NumBenchmarks <= 0 ? 0f : (float)profile.GetElapsedTime().TotalMilliseconds / (float)profile.NumBenchmarks)})\n";
}
return ret;
}
}
}
================================================
FILE: TLM/CSUtil.Commons/CSUtil.Commons.csproj
================================================
Debug
AnyCPU
{D3ADE06E-F493-4819-865A-3BB44FEEDF01}
Library
Properties
CSUtil.Commons
CSUtil.Commons
v3.5
512
true
full
false
bin\Debug\
DEBUG;TRACE
prompt
4
..\TMPE.ruleset
pdbonly
true
bin\Release\
TRACE
prompt
4
..\TMPE.ruleset
true
bin\Benchmark\
DEBUG;TRACE
full
AnyCPU
prompt
..\TMPE.ruleset
true
bin\PF2_Debug\
DEBUG;TRACE
full
AnyCPU
prompt
..\TMPE.ruleset
..\dependencies\UnityEngine.dll
================================================
FILE: TLM/CSUtil.Commons/EnumUtil.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSUtil.Commons {
public static class EnumUtil {
public static IEnumerable GetValues() {
return Enum.GetValues(typeof(T)).Cast();
}
}
}
================================================
FILE: TLM/CSUtil.Commons/Log.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using UnityEngine;
namespace CSUtil.Commons {
public static class Log {
private enum LogLevel {
Debug,
Info,
Warning,
Error
}
private static object logLock = new object();
private static string logFilename = Path.Combine(Application.dataPath, "TMPE.log"); // TODO refactor log filename to configuration
private static Stopwatch sw = Stopwatch.StartNew();
static Log() {
try {
if (File.Exists(logFilename)) {
File.Delete(logFilename);
}
} catch (Exception) {
}
}
[Conditional("DEBUG")]
public static void _Debug(string s) {
LogToFile(s, LogLevel.Debug);
}
public static void Info(string s) {
LogToFile(s, LogLevel.Info);
}
public static void Warning(string s) {
LogToFile(s, LogLevel.Warning);
}
public static void Error(string s) {
LogToFile(s, LogLevel.Error);
}
private static void LogToFile(string log, LogLevel level) {
try {
Monitor.Enter(logLock);
using (StreamWriter w = File.AppendText(logFilename)) {
w.WriteLine($"[{level.ToString()}] @ {sw.ElapsedTicks} {log}");
if (level == LogLevel.Warning || level == LogLevel.Error) {
w.WriteLine((new System.Diagnostics.StackTrace()).ToString());
w.WriteLine();
}
}
} finally {
Monitor.Exit(logLock);
}
}
}
}
================================================
FILE: TLM/CSUtil.Commons/LogicUtil.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSUtil.Commons {
public static class LogicUtil {
public static bool CheckFlags(uint flags, uint flagMask, uint? expectedResult=null) {
uint res = flags & flagMask;
if (expectedResult == null) {
return res != 0;
} else {
return res == expectedResult;
}
}
}
}
================================================
FILE: TLM/CSUtil.Commons/Properties/AssemblyInfo.cs
================================================
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Util")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Util")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d3ade06e-f493-4819-865a-3bb44feedf01")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.*")]
================================================
FILE: TLM/CSUtil.Commons/TernaryBool.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSUtil.Commons {
public enum TernaryBool {
Undefined = 0,
False = 1,
True = 2
}
}
================================================
FILE: TLM/CSUtil.Commons/TernaryBoolUtil.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSUtil.Commons {
public static class TernaryBoolUtil {
public static bool ToBool(TernaryBool tb) {
if (tb == TernaryBool.Undefined) {
throw new ArgumentException("Cannot determine boolean value for undefined ternary bool");
}
return tb == TernaryBool.True;
}
public static bool? ToOptBool(TernaryBool tb) {
if (tb == TernaryBool.Undefined) {
return null;
}
return tb == TernaryBool.True;
}
public static TernaryBool ToTernaryBool(bool? b) {
switch (b) {
case null:
default:
return TernaryBool.Undefined;
case false:
return TernaryBool.False;
case true:
return TernaryBool.True;
}
}
}
}
================================================
FILE: TLM/CSUtil.Commons/ToStringExt.cs
================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSUtil.Commons {
public static class ToStringExt {
public static string DictionaryToString(this IDictionary element) {
return string.Join(", ", element.Keys.Select(x => $"{ToString(x)}={ToString(element[x])}").ToArray());
}
public static string CollectionToString(this ICollection elements) {
return string.Join(", ", elements.Select(x => ToString(x)).ToArray());
}
public static string ArrayToString(this T[] elements) {
return string.Join(", ", elements.Select(x => ToString(x)).ToArray());
}
public static string ToString(object obj) {
return obj == null ? "" : obj.ToString();
}
}
}
================================================
FILE: TLM/CSUtil.Commons/VectorUtil.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace CSUtil.Commons {
public static class VectorUtil {
public static void ClampRectToScreen(ref Rect rect, Vector2 resolution) {
Log._Debug($"ClampPosToScreen([{rect.x}, {rect.y}, {rect.xMax}, {rect.yMax}], [{resolution.x}, {resolution.y}]) called");
if (rect.x < 0)
rect.x = 0;
if (rect.y < 0)
rect.y = 0;
if (rect.xMax >= resolution.x)
rect.x = resolution.x - rect.width;
if (rect.yMax >= resolution.y)
rect.y = resolution.y - rect.height;
Log._Debug($"ClampPosToScreen() -> [{rect.x}, {rect.y}, {rect.xMax}, {rect.yMax}]");
}
}
}
================================================
FILE: TLM/CSUtil.Commons/packages.config
================================================
================================================
FILE: TLM/CSUtil.Redirection/CSUtil.Redirection.csproj
================================================
Debug
AnyCPU
8.0.30703
2.0
{7DCC08EF-DC85-47A4-BD6F-79FC52C7EF13}
Library
Properties
CSUtil.Redirection
CSUtil.Redirection
v3.5
512
true
full
false
bin\Debug\
DEBUG;TRACE
prompt
4
true
pdbonly
true
bin\Release\
TRACE
prompt
4
true
true
bin\Benchmark\
DEBUG;TRACE
true
full
AnyCPU
prompt
MinimumRecommendedRules.ruleset
{D3ADE06E-F493-4819-865A-3BB44FEEDF01}
CSUtil.Commons
================================================
FILE: TLM/CSUtil.Redirection/MethodInfoExt.cs
================================================
using System;
using System.Reflection;
namespace CSUtil.Redirection {
public static class MethodInfoExt
{
internal static Redirector.MethodRedirection RedirectTo(this MethodInfo originalMethod, MethodInfo newMethod, Assembly redirectionSource)
{
return new Redirector.MethodRedirection(originalMethod, newMethod, redirectionSource);
}
public static bool IsCompatibleWith(this MethodInfo thisMethod, MethodInfo otherMethod)
{
if (thisMethod.ReturnType != otherMethod.ReturnType)
return false;
ParameterInfo[] thisParameters = thisMethod.GetParameters();
ParameterInfo[] otherParameters = otherMethod.GetParameters();
if (thisParameters.Length != otherParameters.Length)
return false;
for (int i = 0; i < thisParameters.Length; i++)
{
if (!otherParameters[i].ParameterType.IsAssignableFrom(thisParameters[i].ParameterType))
{
return false;
}
}
return true;
}
}
}
================================================
FILE: TLM/CSUtil.Redirection/NetworkExtensions.Framework.Unsafe.csproj.DotSettings
================================================
True
================================================
FILE: TLM/CSUtil.Redirection/Properties/AssemblyInfo.cs
================================================
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Transit.Framework.Redirection")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Transit.Framework.Redirection")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("4250c75b-3c80-494f-a5c9-7e49c18ef1bc")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.*")]
================================================
FILE: TLM/CSUtil.Redirection/RedirectionHelper.cs
================================================
/*
The MIT License (MIT)
Copyright (c) 2015 Sebastian Schöner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
using System;
using System.Reflection;
namespace CSUtil.Redirection {
public struct RedirectCallsState
{
public byte a, b, c, d, e;
public ulong f;
}
///
/// Helper class to deal with detours. This version is for Unity 5 x64 on Windows.
/// We provide three different methods of detouring.
///
public static class RedirectionHelper
{
///
/// Redirects all calls from method 'from' to method 'to'.
///
///
///
public static RedirectCallsState RedirectCalls(MethodInfo from, MethodInfo to)
{
// GetFunctionPointer enforces compilation of the method.
var fptr1 = from.MethodHandle.GetFunctionPointer();
var fptr2 = to.MethodHandle.GetFunctionPointer();
return PatchJumpTo(fptr1, fptr2);
}
public static RedirectCallsState RedirectCalls(RuntimeMethodHandle from, RuntimeMethodHandle to)
{
// GetFunctionPointer enforces compilation of the method.
var fptr1 = from.GetFunctionPointer();
var fptr2 = to.GetFunctionPointer();
return PatchJumpTo(fptr1, fptr2);
}
public static void RevertRedirect(MethodInfo from, RedirectCallsState state)
{
try
{
var fptr1 = from.MethodHandle.GetFunctionPointer();
RevertJumpTo(fptr1, state);
}
catch
{
// ignored
}
}
///
/// Primitive patching. Inserts a jump to 'target' at 'site'. Works even if both methods'
/// callers have already been compiled.
///
///
///
public static RedirectCallsState PatchJumpTo(IntPtr site, IntPtr target)
{
RedirectCallsState state = new RedirectCallsState();
// R11 is volatile.
unsafe
{
byte* sitePtr = (byte*)site.ToPointer();
state.a = *sitePtr;
state.b = *(sitePtr + 1);
state.c = *(sitePtr + 10);
state.d = *(sitePtr + 11);
state.e = *(sitePtr + 12);
state.f = *((ulong*)(sitePtr + 2));
*sitePtr = 0x49; // mov r11, target
*(sitePtr + 1) = 0xBB;
*((ulong*)(sitePtr + 2)) = (ulong)target.ToInt64();
*(sitePtr + 10) = 0x41; // jmp r11
*(sitePtr + 11) = 0xFF;
*(sitePtr + 12) = 0xE3;
}
return state;
}
public static void RevertJumpTo(IntPtr site, RedirectCallsState state)
{
unsafe
{
byte* sitePtr = (byte*)site.ToPointer();
*sitePtr = state.a; // mov r11, target
*(sitePtr + 1) = state.b;
*((ulong*)(sitePtr + 2)) = state.f;
*(sitePtr + 10) = state.c; // jmp r11
*(sitePtr + 11) = state.d;
*(sitePtr + 12) = state.e;
}
}
}
}
================================================
FILE: TLM/CSUtil.Redirection/Redirector.cs
================================================
using CSUtil.Commons;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace CSUtil.Redirection {
public abstract class RedirectAttribute : Attribute
{
public RedirectAttribute(Type classType, string methodName, ulong bitSetOption = 0)
{
ClassType = classType;
MethodName = methodName;
BitSetRequiredOption = bitSetOption;
}
public RedirectAttribute(Type classType, ulong bitSetOption = 0)
: this(classType, null, bitSetOption)
{ }
public Type ClassType { get; set; }
public string MethodName { get; set; }
public ulong BitSetRequiredOption { get; set; }
}
///
/// Marks a method for redirection. All marked methods are redirected by calling
/// and reverted by
/// NOTE: only the methods belonging to the same assembly that calls Perform/RevertRedirections are redirected.
///
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class RedirectFromAttribute : RedirectAttribute
{
/// The class of the method that will be redirected
/// The name of the method that will be redirected. If null,
/// the name of the attribute's target method will be used.
public RedirectFromAttribute(Type classType, string methodName, ulong bitSetOption = 0)
: base(classType, methodName, bitSetOption)
{ }
public RedirectFromAttribute(Type classType, ulong bitSetOption = 0)
: base(classType, bitSetOption)
{ }
}
///
/// Marks a method for redirection. All marked methods are redirected by calling
/// and reverted by
/// NOTE: only the methods belonging to the same assembly that calls Perform/RevertRedirections are redirected.
///
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class RedirectToAttribute : RedirectAttribute
{
/// The class of the target method
/// The name of the target method. If null,
/// the name of the attribute's target method will be used.
public RedirectToAttribute(Type classType, string methodName, ulong bitSetOption = 0)
: base(classType, methodName, bitSetOption)
{ }
public RedirectToAttribute(Type classType, ulong bitSetOption = 0)
: base(classType, bitSetOption)
{ }
}
public static class Redirector
{
internal class MethodRedirection : IDisposable
{
private bool _isDisposed = false;
private MethodInfo _originalMethod;
private readonly RedirectCallsState _callsState;
public Assembly RedirectionSource { get; set; }
public MethodRedirection(MethodInfo originalMethod, MethodInfo newMethod, Assembly redirectionSource)
{
_originalMethod = originalMethod;
_callsState = RedirectionHelper.RedirectCalls(_originalMethod, newMethod);
RedirectionSource = redirectionSource;
}
public void Dispose()
{
if (!_isDisposed)
{
RedirectionHelper.RevertRedirect(_originalMethod, _callsState);
_originalMethod = null;
_isDisposed = true;
}
}
public MethodInfo OriginalMethod
{
get
{
return _originalMethod;
}
}
}
private static List s_redirections = new List();
public static void PerformRedirections(ulong bitMask = 0)
{
Assembly callingAssembly = Assembly.GetCallingAssembly();
IEnumerable methods = from type in callingAssembly.GetTypes()
from method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
where method.GetCustomAttributes(typeof(RedirectAttribute), false).Length > 0
select method;
foreach (MethodInfo method in methods)
{
foreach (RedirectAttribute redirectAttr in method.GetCustomAttributes(typeof(RedirectAttribute), false))
{
if (redirectAttr.BitSetRequiredOption != 0 && (bitMask & redirectAttr.BitSetRequiredOption) == 0)
continue;
string originalName = String.IsNullOrEmpty(redirectAttr.MethodName) ? method.Name : redirectAttr.MethodName;
MethodInfo originalMethod = null;
foreach (MethodInfo m in redirectAttr.ClassType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
{
if (m.Name != originalName)
continue;
if (method.IsCompatibleWith(m))
{
originalMethod = m;
break;
}
}
if (originalMethod == null)
{
throw new Exception(string.Format("Redirector: Original method {0} has not been found for redirection", originalName));
}
if (redirectAttr is RedirectFromAttribute)
{
if (!s_redirections.Any(r => r.OriginalMethod == originalMethod))
{
Log.Info(string.Format("Redirector: Detouring method calls from {0}.{1} to {2}.{3} via RedirectFrom",
originalMethod.DeclaringType,
originalMethod.Name,
method.DeclaringType,
method.Name));
s_redirections.Add(originalMethod.RedirectTo(method, callingAssembly));
}
}
if (redirectAttr is RedirectToAttribute)
{
if (!s_redirections.Any(r => r.OriginalMethod == method))
{
Log.Info(string.Format("Redirector: Detouring method calls from {0}.{1} to {2}.{3} via RedirectTo",
method.DeclaringType,
method.Name,
originalMethod.DeclaringType,
originalMethod.Name));
s_redirections.Add(method.RedirectTo(originalMethod, callingAssembly));
}
}
}
}
}
public static void RevertRedirections()
{
Assembly callingAssembly = Assembly.GetCallingAssembly();
for (int i = s_redirections.Count - 1; i >= 0; --i)
{
var redirection = s_redirections[i];
if (Equals(redirection.RedirectionSource, callingAssembly))
{
Log.Info(string.Format("Redirector: Removing redirection {0}", s_redirections[i].OriginalMethod));
s_redirections[i].Dispose();
s_redirections.RemoveAt(i);
}
}
}
}
}
================================================
FILE: TLM/CSUtil.Redirection/Transit.Framework.Redirection.csproj.DotSettings
================================================
True
================================================
FILE: TLM/CSUtil.Redirection/Transit.Framework.Unsafe.csproj.DotSettings
================================================
True
================================================
FILE: TLM/PR_REVIEW_INSTRUCTIONS.md
================================================
## Pull Request Review
### Clone PR as new branch inside repository folder:
#### Github Desktop(Atom-version):
* the PR can be selected from the 'Current Branch' drop-down.
#### Git for windows console
* get __PR__ index - this number with __#__ after __PR__ name
* inside project folder ``` \\TLM\ ```
* type ```git fetch origin pull//head:``` _(skip <>)_ e.g. ```git fetch origin pull/123/head:PR_123```
* to switch to newly created branch:
* type ```git checkout ``` _(skip <>)_ e.g. ```git checkout PR_123```
* or use branch switch menu _(usually bottom right corner of preferred __IDE__)_
If you don't see new branch to select try to refresh available branches:
* __VS 2017:__ _TeamExplorer -> Refresh_
* __JB Rider:__ _VCS -> Git -> Fetch_
### Now newly created branch should be accessible from branch switch menu
================================================
FILE: TLM/TLM/CodeProfiler.cs
================================================
using ColossalFramework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
namespace TrafficManager {
#if TRACE
public class CodeProfiler : Singleton {
private Dictionary watches = new Dictionary();
private Dictionary intervals = new Dictionary();
internal void Start(string name) {
Validate(name);
try {
Monitor.Enter(watches);
watches[name].Start();
} finally {
Monitor.Exit(watches);
}
}
internal void Stop(string name) {
Validate(name);
try {
Monitor.Enter(watches);
watches[name].Stop();
++intervals[name];
} finally {
Monitor.Exit(watches);
}
}
internal void Reset(string name) {
Validate(name);
try {
Monitor.Enter(watches);
watches[name].Reset();
} finally {
Monitor.Exit(watches);
}
}
internal ulong ElapsedNano(string name) {
Validate(name);
try {
Monitor.Enter(watches);
return (ulong)(watches[name].Elapsed.TotalMilliseconds * 1000d * 1000d);
} finally {
Monitor.Exit(watches);
}
}
private void Validate(string name) {
try {
Monitor.Enter(watches);
if (!watches.ContainsKey(name)) {
watches[name] = new Stopwatch();
intervals[name] = 0;
}
} finally {
Monitor.Exit(watches);
}
}
internal void OnLevelUnloading() {
try {
Monitor.Enter(watches);
foreach (KeyValuePair we in watches) {
Log._Debug($"Stopwatch {we.Key}: Total elapsed ns: {ElapsedNano(we.Key)} ms: {ElapsedNano(we.Key) / 1000u / 1000u} s: {ElapsedNano(we.Key) / 1000u / 1000u / 1000u} min: {ElapsedNano(we.Key) / 1000u / 1000u / 1000u / 60u} h: {ElapsedNano(we.Key) / 1000u / 1000u / 1000u / 60u / 60u} intervals: {intervals[we.Key]} avg. ns: {(intervals[we.Key] > 0 ? ("" + (ElapsedNano(we.Key) / intervals[we.Key])) : "n/a")}");
}
watches.Clear();
intervals.Clear();
} finally {
Monitor.Exit(watches);
}
}
}
#endif
}
================================================
FILE: TLM/TLM/Constants.cs
================================================
using GenericGameBridge.Factory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TrafficManager.Manager;
namespace TrafficManager {
public static class Constants {
public static readonly bool[] ALL_BOOL = new bool[] { false, true };
public static IServiceFactory ServiceFactory {
get {
#if UNITTEST
return TestGameBridge.Factory.ServiceFactory.Instance;
#else
return CitiesGameBridge.Factory.ServiceFactory.Instance;
#endif
}
}
public static IManagerFactory ManagerFactory {
get {
return Manager.Impl.ManagerFactory.Instance;
}
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomAmbulanceAI.cs
================================================
using ColossalFramework;
using System;
using System.Collections.Generic;
using System.Text;
using TrafficManager.Custom.PathFinding;
using TrafficManager.Geometry;
using TrafficManager.Manager;
using TrafficManager.Manager.Impl;
using TrafficManager.Traffic;
using TrafficManager.Traffic.Data;
using UnityEngine;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
class CustomAmbulanceAI : CarAI {
public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) {
#if DEBUG
//Log._Debug($"CustomAmbulanceAI.CustomStartPathFind called for vehicle {vehicleID}");
#endif
ExtVehicleType vehicleType = VehicleStateManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0 ? ExtVehicleType.Emergency : ExtVehicleType.Service);
VehicleInfo info = this.m_info;
bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0;
PathUnit.Position startPosA;
PathUnit.Position startPosB;
float startDistSqrA;
float startDistSqrB;
PathUnit.Position endPosA;
PathUnit.Position endPosB;
float endDistSqrA;
float endDistSqrB;
if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) &&
CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) {
if (!startBothWays || startDistSqrA < 10f) {
startPosB = default(PathUnit.Position);
}
if (!endBothWays || endDistSqrA < 10f) {
endPosB = default(PathUnit.Position);
}
uint path;
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = ExtCitizenInstance.ExtPathType.None;
args.extVehicleType = vehicleType;
args.vehicleId = vehicleID;
args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
args.buildIndex = Singleton.instance.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = startPosB;
args.endPosA = endPosA;
args.endPosB = endPosB;
args.vehiclePosition = default(PathUnit.Position);
args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle;
args.vehicleTypes = info.m_vehicleType;
args.maxLength = 20000f;
args.isHeavyVehicle = this.IsHeavyVehicle();
args.hasCombustionEngine = this.CombustionEngine();
args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData);
args.ignoreFlooded = false;
args.ignoreCosts = false;
args.randomParking = false;
args.stablePath = false;
args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
if (CustomPathManager._instance.CreatePath(out path, ref Singleton.instance.m_randomizer, args)) {
// NON-STOCK CODE END
if (vehicleData.m_path != 0u) {
Singleton.instance.ReleasePath(vehicleData.m_path);
}
vehicleData.m_path = path;
vehicleData.m_flags |= Vehicle.Flags.WaitingPath;
return true;
}
} else {
PathfindFailure(vehicleID, ref vehicleData);
}
return false;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomBuildingAI.cs
================================================
using ColossalFramework;
using ColossalFramework.Math;
using CSUtil.Commons.Benchmark;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TrafficManager.Manager;
using TrafficManager.Manager.Impl;
using TrafficManager.State;
using TrafficManager.Traffic;
using TrafficManager.UI;
using UnityEngine;
namespace TrafficManager.Custom.AI {
public class CustomBuildingAI : BuildingAI {
public Color CustomGetColor(ushort buildingID, ref Building data, InfoManager.InfoMode infoMode) {
if (infoMode != InfoManager.InfoMode.None) {
// NON-STOCK CODE START
#if BENCHMARK
using (var bm = new Benchmark()) {
#endif
if (Options.prohibitPocketCars) {
Color? color;
if (AdvancedParkingManager.Instance.GetBuildingInfoViewColor(buildingID, ref data, ref ExtBuildingManager.Instance.ExtBuildings[buildingID], infoMode, out color)) {
return (Color)color;
}
}
#if BENCHMARK
}
#endif
// NON-STOCK CODE END
return Singleton.instance.m_properties.m_neutralColor;
}
if (!this.m_info.m_useColorVariations) {
return this.m_info.m_color0;
}
Randomizer randomizer = new Randomizer((int)buildingID);
switch (randomizer.Int32(4u)) {
case 0:
return this.m_info.m_color0;
case 1:
return this.m_info.m_color1;
case 2:
return this.m_info.m_color2;
case 3:
return this.m_info.m_color3;
default:
return this.m_info.m_color0;
}
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomBusAI.cs
================================================
using ColossalFramework;
using System;
using System.Collections.Generic;
using System.Text;
using TrafficManager.Custom.PathFinding;
using TrafficManager.Geometry;
using TrafficManager.Manager;
using TrafficManager.Traffic;
using TrafficManager.Traffic.Data;
using UnityEngine;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
class CustomBusAI : CarAI {
public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) {
#if DEBUG
//Log._Debug($"CustomBusAI.CustomStartPathFind called for vehicle {vehicleID}");
#endif
VehicleInfo info = this.m_info;
bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0;
PathUnit.Position startPosA;
PathUnit.Position startPosB;
float startDistSqrA;
float startDistSqrB;
PathUnit.Position endPosA;
PathUnit.Position endPosB;
float endDistSqrA;
float endDistSqrB;
if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) &&
CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) {
if (!startBothWays || startDistSqrA < 10f) {
startPosB = default(PathUnit.Position);
}
if (!endBothWays || endDistSqrA < 10f) {
endPosB = default(PathUnit.Position);
}
uint path;
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = ExtCitizenInstance.ExtPathType.None;
args.extVehicleType = ExtVehicleType.Bus;
args.vehicleId = vehicleID;
args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
args.buildIndex = Singleton.instance.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = startPosB;
args.endPosA = endPosA;
args.endPosB = endPosB;
args.vehiclePosition = default(PathUnit.Position);
args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle;
args.vehicleTypes = info.m_vehicleType;
args.maxLength = 20000f;
args.isHeavyVehicle = this.IsHeavyVehicle();
args.hasCombustionEngine = this.CombustionEngine();
args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData);
args.ignoreFlooded = false;
args.randomParking = false;
args.ignoreCosts = false;
args.stablePath = true;
args.skipQueue = true;
if (CustomPathManager._instance.CreatePath(out path, ref Singleton.instance.m_randomizer, args)) {
// NON-STOCK CODE END
if (vehicleData.m_path != 0u) {
Singleton.instance.ReleasePath(vehicleData.m_path);
}
vehicleData.m_path = path;
vehicleData.m_flags |= Vehicle.Flags.WaitingPath;
return true;
}
}
return false;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomCarAI.cs
================================================
#define DEBUGVx
using System;
using System.Collections.Generic;
using ColossalFramework;
using ColossalFramework.Math;
using TrafficManager.Geometry;
using TrafficManager.TrafficLight;
using UnityEngine;
using Random = UnityEngine.Random;
using TrafficManager.Custom.PathFinding;
using TrafficManager.State;
using TrafficManager.Manager;
using TrafficManager.Traffic;
using CSUtil.Commons;
using TrafficManager.Manager.Impl;
using System.Runtime.CompilerServices;
using TrafficManager.Traffic.Data;
using static TrafficManager.Traffic.Data.ExtCitizenInstance;
using CSUtil.Commons.Benchmark;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
public class CustomCarAI : CarAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`)
public void Awake() {
}
///
/// Lightweight simulation step method.
/// This method is occasionally being called for different cars.
///
///
///
///
public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) {
#if DEBUG
bool vehDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId);
bool debug = GlobalConfig.Instance.Debug.Switches[2] && vehDebug;
bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && vehDebug;
#endif
if ((vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0) {
PathManager pathManager = Singleton.instance;
byte pathFindFlags = pathManager.m_pathUnits.m_buffer[vehicleData.m_path].m_pathFindFlags;
// NON-STOCK CODE START
ExtPathState mainPathState = ExtPathState.Calculating;
if ((pathFindFlags & PathUnit.FLAG_FAILED) != 0 || vehicleData.m_path == 0) {
mainPathState = ExtPathState.Failed;
} else if ((pathFindFlags & PathUnit.FLAG_READY) != 0) {
mainPathState = ExtPathState.Ready;
}
#if DEBUG
if (debug)
Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path: {vehicleData.m_path}, mainPathState={mainPathState}");
#endif
ExtSoftPathState finalPathState = ExtSoftPathState.None;
#if BENCHMARK
using (var bm = new Benchmark(null, "UpdateCarPathState")) {
#endif
finalPathState = ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState);
if (Options.prohibitPocketCars && VehicleStateManager.Instance.VehicleStates[vehicleId].vehicleType == ExtVehicleType.PassengerCar) {
ushort driverInstanceId = CustomPassengerCarAI.GetDriverInstanceId(vehicleId, ref vehicleData);
finalPathState = AdvancedParkingManager.Instance.UpdateCarPathState(vehicleId, ref vehicleData, ref Singleton.instance.m_instances.m_buffer[driverInstanceId], ref ExtCitizenInstanceManager.Instance.ExtInstances[driverInstanceId], mainPathState);
#if DEBUG
if (debug)
Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Applied Parking AI logic. Path: {vehicleData.m_path}, mainPathState={mainPathState}, finalPathState={finalPathState}");
#endif
}
#if BENCHMARK
}
#endif
switch (finalPathState) {
case ExtSoftPathState.Ready:
#if DEBUG
if (debug)
Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path-finding succeeded for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- calling CarAI.PathfindSuccess");
#endif
vehicleData.m_pathPositionIndex = 255;
vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath;
vehicleData.m_flags &= ~Vehicle.Flags.Arriving;
this.PathfindSuccess(vehicleId, ref vehicleData);
this.TrySpawn(vehicleId, ref vehicleData);
break;
case ExtSoftPathState.Ignore:
#if DEBUG
if (debug)
Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path-finding result shall be ignored for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- ignoring");
#endif
return;
case ExtSoftPathState.Calculating:
default:
#if DEBUG
if (debug)
Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path-finding result undetermined for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- continue");
#endif
break;
case ExtSoftPathState.FailedHard:
#if DEBUG
if (debug)
Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): HARD path-finding failure for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- calling CarAI.PathfindFailure");
#endif
vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath;
Singleton.instance.ReleasePath(vehicleData.m_path);
vehicleData.m_path = 0u;
this.PathfindFailure(vehicleId, ref vehicleData);
return;
case ExtSoftPathState.FailedSoft:
#if DEBUG
if (debug)
Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): SOFT path-finding failure for vehicle {vehicleId} (finalPathState={finalPathState}). Path: {vehicleData.m_path} -- calling CarAI.InvalidPath");
#endif
// path mode has been updated, repeat path-finding
vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath;
this.InvalidPath(vehicleId, ref vehicleData, vehicleId, ref vehicleData);
break;
}
// NON-STOCK CODE END
} else {
if ((vehicleData.m_flags & Vehicle.Flags.WaitingSpace) != 0) {
this.TrySpawn(vehicleId, ref vehicleData);
}
}
// NON-STOCK CODE START
#if BENCHMARK
using (var bm = new Benchmark(null, "UpdateVehiclePosition")) {
#endif
VehicleStateManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData);
#if BENCHMARK
}
#endif
if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) {
#if BENCHMARK
using (var bm = new Benchmark(null, "LogTraffic")) {
#endif
// Advanced AI traffic measurement
VehicleStateManager.Instance.LogTraffic(vehicleId);
#if BENCHMARK
}
#endif
}
// NON-STOCK CODE END
Vector3 lastFramePosition = vehicleData.GetLastFramePosition();
int lodPhysics;
if (Vector3.SqrMagnitude(physicsLodRefPos - lastFramePosition) >= 1210000f) {
lodPhysics = 2;
} else if (Vector3.SqrMagnitude(Singleton.instance.m_simulationView.m_position - lastFramePosition) >= 250000f) {
lodPhysics = 1;
} else {
lodPhysics = 0;
}
this.SimulationStep(vehicleId, ref vehicleData, vehicleId, ref vehicleData, lodPhysics);
if (vehicleData.m_leadingVehicle == 0 && vehicleData.m_trailingVehicle != 0) {
VehicleManager vehManager = Singleton.instance;
ushort trailerId = vehicleData.m_trailingVehicle;
int numIters = 0;
while (trailerId != 0) {
ushort trailingVehicle = vehManager.m_vehicles.m_buffer[(int)trailerId].m_trailingVehicle;
VehicleInfo info = vehManager.m_vehicles.m_buffer[(int)trailerId].Info;
info.m_vehicleAI.SimulationStep(trailerId, ref vehManager.m_vehicles.m_buffer[(int)trailerId], vehicleId, ref vehicleData, lodPhysics);
trailerId = trailingVehicle;
if (++numIters > 16384) {
CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);
break;
}
}
}
int privateServiceIndex = ItemClass.GetPrivateServiceIndex(this.m_info.m_class.m_service);
int maxBlockCounter = (privateServiceIndex == -1) ? 150 : 100;
if ((vehicleData.m_flags & (Vehicle.Flags.Spawned | Vehicle.Flags.WaitingPath | Vehicle.Flags.WaitingSpace)) == 0 && vehicleData.m_cargoParent == 0) {
Singleton.instance.ReleaseVehicle(vehicleId);
} else if ((int)vehicleData.m_blockCounter >= maxBlockCounter) {
// NON-STOCK CODE START
bool mayDespawn = true;
#if BENCHMARK
using (var bm = new Benchmark(null, "MayDespawn")) {
#endif
mayDespawn = VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData);
#if BENCHMARK
}
#endif
if (mayDespawn) {
// NON-STOCK CODE END
Singleton.instance.ReleaseVehicle(vehicleId);
} // NON-STOCK CODE
}
}
public override bool TrySpawn(ushort vehicleId, ref Vehicle vehicleData) {
if ((vehicleData.m_flags & Vehicle.Flags.Spawned) != (Vehicle.Flags)0) {
return true;
}
if (CustomCarAI.CheckOverlap(vehicleData.m_segment, 0, 1000f)) {
vehicleData.m_flags |= Vehicle.Flags.WaitingSpace;
return false;
}
vehicleData.Spawn(vehicleId);
vehicleData.m_flags &= ~Vehicle.Flags.WaitingSpace;
return true;
}
public void CustomCalculateSegmentPosition(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position nextPosition,
PathUnit.Position prevPosition, uint prevLaneId, byte prevOffset, PathUnit.Position refPosition, uint refLaneId,
byte refOffset, int index, out Vector3 pos, out Vector3 dir, out float maxSpeed) {
var netManager = Singleton.instance;
ushort prevSourceNodeId;
ushort prevTargetNodeId;
if (prevOffset < prevPosition.m_offset) {
prevSourceNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_startNode;
prevTargetNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_endNode;
} else {
prevSourceNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_endNode;
prevTargetNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_startNode;
}
ushort refTargetNodeId;
if (refOffset == 0) {
refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_startNode;
} else {
refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_endNode;
}
#if DEBUG
bool debug = GlobalConfig.Instance.Debug.Switches[21] && (GlobalConfig.Instance.Debug.NodeId <= 0 || refTargetNodeId == GlobalConfig.Instance.Debug.NodeId) && (GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.None || GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.RoadVehicle) && (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId);
if (debug) {
Log._Debug($"CustomCarAI.CustomCalculateSegmentPosition({vehicleId}) called.\n" +
$"\trefPosition.m_segment={refPosition.m_segment}, refPosition.m_offset={refPosition.m_offset}\n" +
$"\tprevPosition.m_segment={prevPosition.m_segment}, prevPosition.m_offset={prevPosition.m_offset}\n" +
$"\tnextPosition.m_segment={nextPosition.m_segment}, nextPosition.m_offset={nextPosition.m_offset}\n" +
$"\trefLaneId={refLaneId}, refOffset={refOffset}\n" +
$"\tprevLaneId={prevLaneId}, prevOffset={prevOffset}\n" +
$"\tprevSourceNodeId={prevSourceNodeId}, prevTargetNodeId={prevTargetNodeId}\n" +
$"\trefTargetNodeId={refTargetNodeId}, refTargetNodeId={refTargetNodeId}\n" +
$"\tindex={index}");
}
#endif
Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData();
Vector3 lastFrameVehiclePos = lastFrameData.m_position;
float sqrVelocity = lastFrameData.m_velocity.sqrMagnitude;
netManager.m_lanes.m_buffer[prevLaneId].CalculatePositionAndDirection(prevOffset * 0.003921569f, out pos, out dir);
float braking = this.m_info.m_braking;
if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != (Vehicle.Flags)0) {
braking *= 2f;
}
// car position on the Bezier curve of the lane
var refVehiclePosOnBezier = netManager.m_lanes.m_buffer[refLaneId].CalculatePosition(refOffset * 0.003921569f);
//ushort currentSegmentId = netManager.m_lanes.m_buffer[prevLaneID].m_segment;
// this seems to be like the required braking force in order to stop the vehicle within its half length.
var crazyValue = 0.5f * sqrVelocity / braking + m_info.m_generatedInfo.m_size.z * 0.5f;
bool withinBrakingDistance = Vector3.Distance(lastFrameVehiclePos, refVehiclePosOnBezier) >= crazyValue - 1f;
if (prevSourceNodeId == refTargetNodeId && withinBrakingDistance) {
// NON-STOCK CODE START (stock code replaced)
#if BENCHMARK
using (var bm = new Benchmark(null, "MayChangeSegment")) {
#endif
//bool isRecklessDriver = VehicleStateManager.Instance.IsRecklessDriver(vehicleId, ref vehicleData); // NON-STOCK CODE
if (!VehicleBehaviorManager.Instance.MayChangeSegment(vehicleId, ref vehicleData, sqrVelocity, ref refPosition, ref netManager.m_segments.m_buffer[refPosition.m_segment], refTargetNodeId, refLaneId, ref prevPosition, prevSourceNodeId, ref netManager.m_nodes.m_buffer[prevSourceNodeId], prevLaneId, ref nextPosition, prevTargetNodeId, out maxSpeed)) { // NON-STOCK CODE
return;
} else {
#if BENCHMARK
using (var bm = new Benchmark(null, "UpdateVehiclePosition")) {
#endif
VehicleStateManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/);
#if BENCHMARK
}
#endif
}
#if BENCHMARK
}
#endif
// NON-STOCK CODE END
}
var segmentInfo = netManager.m_segments.m_buffer[prevPosition.m_segment].Info;
if (segmentInfo.m_lanes != null && segmentInfo.m_lanes.Length > prevPosition.m_lane) {
// NON-STOCK CODE START
float laneSpeedLimit = 1f;
if (!Options.customSpeedLimitsEnabled) {
laneSpeedLimit = segmentInfo.m_lanes[prevPosition.m_lane].m_speedLimit;
} else {
laneSpeedLimit = Constants.ManagerFactory.SpeedLimitManager.GetLockFreeGameSpeedLimit(prevPosition.m_segment, prevPosition.m_lane, prevLaneId, segmentInfo.m_lanes[prevPosition.m_lane]);
}
// NON-STOCK CODE END
maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, laneSpeedLimit, netManager.m_lanes.m_buffer[prevLaneId].m_curve);
} else {
maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f);
}
// NON-STOCK CODE START (stock code replaced)
maxSpeed = Constants.ManagerFactory.VehicleBehaviorManager.CalcMaxSpeed(vehicleId, ref VehicleStateManager.Instance.VehicleStates[vehicleId], this.m_info, prevPosition, ref netManager.m_segments.m_buffer[prevPosition.m_segment], pos, maxSpeed);
// NON-STOCK CODE END
}
public void CustomCalculateSegmentPositionPathFinder(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position position, uint laneId, byte offset, out Vector3 pos, out Vector3 dir, out float maxSpeed) {
var netManager = Singleton.instance;
netManager.m_lanes.m_buffer[laneId].CalculatePositionAndDirection(offset * 0.003921569f, out pos, out dir);
var segmentInfo = netManager.m_segments.m_buffer[position.m_segment].Info;
if (segmentInfo.m_lanes != null && segmentInfo.m_lanes.Length > position.m_lane) {
// NON-STOCK CODE START
float laneSpeedLimit = 1f;
if (!Options.customSpeedLimitsEnabled) {
laneSpeedLimit = segmentInfo.m_lanes[position.m_lane].m_speedLimit;
} else {
laneSpeedLimit = Constants.ManagerFactory.SpeedLimitManager.GetLockFreeGameSpeedLimit(position.m_segment, position.m_lane, laneId, segmentInfo.m_lanes[position.m_lane]);
}
// NON-STOCK CODE END
maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, laneSpeedLimit, netManager.m_lanes.m_buffer[laneId].m_curve);
} else {
maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f);
}
// NON-STOCK CODE START
maxSpeed = VehicleBehaviorManager.Instance.CalcMaxSpeed(vehicleId, ref VehicleStateManager.Instance.VehicleStates[vehicleId], this.m_info, position, ref netManager.m_segments.m_buffer[position.m_segment], pos, maxSpeed);
// NON-STOCK CODE END
}
public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) {
#if DEBUG
bool vehDebug = GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleID;
bool debug = GlobalConfig.Instance.Debug.Switches[2] && vehDebug;
bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && vehDebug;
if (debug)
Log.Warning($"CustomCarAI.CustomStartPathFind({vehicleID}): called for vehicle {vehicleID}, startPos={startPos}, endPos={endPos}, startBothWays={startBothWays}, endBothWays={endBothWays}, undergroundTarget={undergroundTarget}");
#endif
ExtVehicleType vehicleType = ExtVehicleType.None;
#if BENCHMARK
using (var bm = new Benchmark(null, "OnStartPathFind")) {
#endif
vehicleType = VehicleStateManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null);
if (vehicleType == ExtVehicleType.None) {
#if DEBUG
Log.Warning($"CustomCarAI.CustomStartPathFind({vehicleID}): Vehicle {vehicleID} does not have a valid vehicle type!");
#endif
vehicleType = ExtVehicleType.RoadVehicle;
}
#if BENCHMARK
}
#endif
VehicleInfo info = this.m_info;
bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0;
PathUnit.Position startPosA;
PathUnit.Position startPosB;
float startDistSqrA;
float startDistSqrB;
PathUnit.Position endPosA;
PathUnit.Position endPosB;
float endDistSqrA;
float endDistSqrB;
if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) &&
CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) {
if (!startBothWays || startDistSqrA < 10f) {
startPosB = default(PathUnit.Position);
}
if (!endBothWays || endDistSqrA < 10f) {
endPosB = default(PathUnit.Position);
}
uint path;
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = ExtCitizenInstance.ExtPathType.None;
args.extVehicleType = vehicleType;
args.vehicleId = vehicleID;
args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
args.buildIndex = Singleton.instance.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = startPosB;
args.endPosA = endPosA;
args.endPosB = endPosB;
args.vehiclePosition = default(PathUnit.Position);
args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle;
args.vehicleTypes = info.m_vehicleType;
args.maxLength = 20000f;
args.isHeavyVehicle = this.IsHeavyVehicle();
args.hasCombustionEngine = this.CombustionEngine();
args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData);
args.ignoreFlooded = false;
args.ignoreCosts = false;
args.randomParking = false;
args.stablePath = false;
args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
if (CustomPathManager._instance.CreatePath(out path, ref Singleton.instance.m_randomizer, args)) {
#if DEBUG
if (debug)
Log._Debug($"CustomCarAI.CustomStartPathFind({vehicleID}): Path-finding starts for vehicle {vehicleID}, path={path}, extVehicleType={vehicleType}, startPosA.segment={startPosA.m_segment}, startPosA.lane={startPosA.m_lane}, info.m_vehicleType={info.m_vehicleType}, endPosA.segment={endPosA.m_segment}, endPosA.lane={endPosA.m_lane}");
#endif
// NON-STOCK CODE END
if (vehicleData.m_path != 0u) {
Singleton.instance.ReleasePath(vehicleData.m_path);
}
vehicleData.m_path = path;
vehicleData.m_flags |= Vehicle.Flags.WaitingPath;
return true;
}
}
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool CheckOverlap(Segment3 segment, ushort ignoreVehicle, float maxVelocity) {
Log.Error("CustomCarAI.CheckOverlap called");
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static ushort CheckOtherVehicle(ushort vehicleID, ref Vehicle vehicleData, ref Vehicle.Frame frameData, ref float maxSpeed, ref bool blocked, ref Vector3 collisionPush, float maxBraking, ushort otherID, ref Vehicle otherData, Vector3 min, Vector3 max, int lodPhysics) {
Log.Error("CustomCarAI.CheckOtherVehicle called");
return 0;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static ushort CheckCitizen(ushort vehicleID, ref Vehicle vehicleData, Segment3 segment, float lastLen, float nextLen, ref float maxSpeed, ref bool blocked, float maxBraking, ushort otherID, ref CitizenInstance otherData, Vector3 min, Vector3 max) {
Log.Error("CustomCarAI.CheckCitizen called");
return 0;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomCargoTruckAI.cs
================================================
using System;
using ColossalFramework;
using UnityEngine;
using TrafficManager.State;
using TrafficManager.Geometry;
using TrafficManager.Custom.PathFinding;
using TrafficManager.Traffic;
using TrafficManager.Manager;
using CSUtil.Commons;
using TrafficManager.Manager.Impl;
using TrafficManager.Traffic.Data;
using CSUtil.Commons.Benchmark;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
public class CustomCargoTruckAI : CarAI {
public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) {
if ((vehicleData.m_flags & Vehicle.Flags.Congestion) != 0 && VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) {
Singleton.instance.ReleaseVehicle(vehicleId);
return;
}
if ((vehicleData.m_flags & Vehicle.Flags.WaitingTarget) != 0 && (vehicleData.m_waitCounter += 1) > 20) {
RemoveOffers(vehicleId, ref vehicleData);
vehicleData.m_flags &= ~Vehicle.Flags.WaitingTarget;
vehicleData.m_flags |= Vehicle.Flags.GoingBack;
vehicleData.m_waitCounter = 0;
if (!StartPathFind(vehicleId, ref vehicleData)) {
vehicleData.Unspawn(vehicleId);
}
}
base.SimulationStep(vehicleId, ref vehicleData, physicsLodRefPos);
}
// stock code
private static void RemoveOffers(ushort vehicleId, ref Vehicle data) {
if ((data.m_flags & Vehicle.Flags.WaitingTarget) != (Vehicle.Flags)0) {
var offer = default(TransferManager.TransferOffer);
offer.Vehicle = vehicleId;
if ((data.m_flags & Vehicle.Flags.TransferToSource) != (Vehicle.Flags)0) {
Singleton.instance.RemoveIncomingOffer((TransferManager.TransferReason)data.m_transferType, offer);
} else if ((data.m_flags & Vehicle.Flags.TransferToTarget) != (Vehicle.Flags)0) {
Singleton.instance.RemoveOutgoingOffer((TransferManager.TransferReason)data.m_transferType, offer);
}
}
}
public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) {
#if DEBUG
//Log._Debug($"CustomCargoTruckAI.CustomStartPathFind called for vehicle {vehicleID}");
#endif
#if BENCHMARK
using (var bm = new Benchmark(null, "OnStartPathFind")) {
#endif
ExtVehicleType vehicleType = VehicleStateManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null);
if (vehicleType == ExtVehicleType.None) {
#if DEBUG
Log.Warning($"CustomCargoTruck.CustomStartPathFind: Vehicle {vehicleID} does not have a valid vehicle type!");
#endif
}
#if BENCHMARK
}
#endif
if ((vehicleData.m_flags & (Vehicle.Flags.TransferToSource | Vehicle.Flags.GoingBack)) != 0) {
return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget);
}
bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0;
PathUnit.Position startPosA;
PathUnit.Position startPosB;
float startDistSqrA;
float startDistSqrB;
bool startPosFound = CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB);
PathUnit.Position startAltPosA;
PathUnit.Position startAltPosB;
float startAltDistSqrA;
float startAltDistSqrB;
if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, allowUnderground, false, 32f, out startAltPosA, out startAltPosB, out startAltDistSqrA, out startAltDistSqrB)) {
if (!startPosFound || (startAltDistSqrA < startDistSqrA && (Mathf.Abs(endPos.x) > 8000f || Mathf.Abs(endPos.z) > 8000f))) {
startPosA = startAltPosA;
startPosB = startAltPosB;
startDistSqrA = startAltDistSqrA;
startDistSqrB = startAltDistSqrB;
}
startPosFound = true;
}
PathUnit.Position endPosA;
PathUnit.Position endPosB;
float endDistSqrA;
float endDistSqrB;
bool endPosFound = CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB);
PathUnit.Position endAltPosA;
PathUnit.Position endAltPosB;
float endAltDistSqrA;
float endAltDistSqrB;
if (CustomPathManager.FindPathPosition(endPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, undergroundTarget, false, 32f, out endAltPosA, out endAltPosB, out endAltDistSqrA, out endAltDistSqrB)) {
if (!endPosFound || (endAltDistSqrA < endDistSqrA && (Mathf.Abs(endPos.x) > 8000f || Mathf.Abs(endPos.z) > 8000f))) {
endPosA = endAltPosA;
endPosB = endAltPosB;
endDistSqrA = endAltDistSqrA;
endDistSqrB = endAltDistSqrB;
}
endPosFound = true;
}
if (startPosFound && endPosFound) {
CustomPathManager pathMan = CustomPathManager._instance;
if (!startBothWays || startDistSqrA < 10f) {
startPosB = default(PathUnit.Position);
}
if (!endBothWays || endDistSqrA < 10f) {
endPosB = default(PathUnit.Position);
}
NetInfo.LaneType laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.CargoVehicle;
VehicleInfo.VehicleType vehicleTypes = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane;
uint path;
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = ExtCitizenInstance.ExtPathType.None;
args.extVehicleType = ExtVehicleType.CargoVehicle;
args.vehicleId = vehicleID;
args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
args.buildIndex = Singleton.instance.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = startPosB;
args.endPosA = endPosA;
args.endPosB = endPosB;
args.vehiclePosition = default(PathUnit.Position);
args.laneTypes = laneTypes;
args.vehicleTypes = vehicleTypes;
args.maxLength = 20000f;
args.isHeavyVehicle = this.IsHeavyVehicle();
args.hasCombustionEngine = this.CombustionEngine();
args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData);
args.ignoreFlooded = false;
args.ignoreCosts = false;
args.randomParking = false;
args.stablePath = false;
args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
if (pathMan.CreatePath(out path, ref Singleton.instance.m_randomizer, args)) {
// NON-STOCK CODE END
if (vehicleData.m_path != 0u) {
pathMan.ReleasePath(vehicleData.m_path);
}
vehicleData.m_path = path;
vehicleData.m_flags |= Vehicle.Flags.WaitingPath;
return true;
}
}
return false;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomCitizenAI.cs
================================================
using ColossalFramework;
using ColossalFramework.Math;
using System;
using System.Collections.Generic;
using System.Text;
using TrafficManager.Custom.PathFinding;
using TrafficManager.State;
using TrafficManager.Geometry;
using UnityEngine;
using TrafficManager.Traffic;
using TrafficManager.Manager;
using CSUtil.Commons;
using TrafficManager.Manager.Impl;
using TrafficManager.Traffic.Data;
using static TrafficManager.Traffic.Data.ExtCitizenInstance;
using CSUtil.Commons.Benchmark;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
// TODO move Parking AI features from here to a distinct manager
public class CustomCitizenAI : CitizenAI {
public bool CustomStartPathFind(ushort instanceID, ref CitizenInstance citizenData, Vector3 startPos, Vector3 endPos, VehicleInfo vehicleInfo, bool enableTransport, bool ignoreCost) {
return ExtStartPathFind(instanceID, ref citizenData, ref ExtCitizenInstanceManager.Instance.ExtInstances[instanceID], ref ExtCitizenManager.Instance.ExtCitizens[Singleton.instance.m_instances.m_buffer[instanceID].m_citizen], startPos, endPos, vehicleInfo, enableTransport, ignoreCost);
}
public bool ExtStartPathFind(ushort instanceID, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, Vector3 startPos, Vector3 endPos, VehicleInfo vehicleInfo, bool enableTransport, bool ignoreCost) {
#if DEBUG
bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceID) &&
(GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) &&
(GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) &&
(GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding)
;
bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug;
bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug;
if (debug)
Log.Warning($"CustomCitizenAI.ExtStartPathFind({instanceID}): called for citizen {instanceData.m_citizen}, startPos={startPos}, endPos={endPos}, sourceBuilding={instanceData.m_sourceBuilding}, targetBuilding={instanceData.m_targetBuilding}, pathMode={extInstance.pathMode}, enableTransport={enableTransport}, ignoreCost={ignoreCost}");
#endif
// NON-STOCK CODE START
CitizenManager citizenManager = Singleton.instance;
ushort parkedVehicleId = citizenManager.m_citizens.m_buffer[instanceData.m_citizen].m_parkedVehicle;
ushort homeId = citizenManager.m_citizens.m_buffer[instanceData.m_citizen].m_homeBuilding;
CarUsagePolicy carUsageMode = CarUsagePolicy.Allowed;
#if BENCHMARK
using (var bm = new Benchmark(null, "ParkingAI.Preparation")) {
#endif
bool startsAtOutsideConnection = false;
if (Options.prohibitPocketCars) {
switch (extInstance.pathMode) {
case ExtPathMode.RequiresWalkingPathToParkedCar:
case ExtPathMode.CalculatingWalkingPathToParkedCar:
case ExtPathMode.WalkingToParkedCar:
case ExtPathMode.ApproachingParkedCar:
if (parkedVehicleId == 0) {
/*
* Parked vehicle not present but citizen wants to reach it
* -> Reset path mode
*/
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode} but no parked vehicle present. Change to 'None'.");
#endif
extInstance.Reset();
} else {
/*
* Parked vehicle is present and citizen wants to reach it
* -> Prohibit car usage
*/
#if DEBUG
if (fineDebug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'CalculatingWalkingPathToParkedCar'.");
#endif
extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToParkedCar;
carUsageMode = CarUsagePolicy.Forbidden;
}
break;
case ExtPathMode.RequiresWalkingPathToTarget:
case ExtPathMode.CalculatingWalkingPathToTarget:
case ExtPathMode.WalkingToTarget:
/*
* Citizen walks to target
* -> Reset path mode
*/
#if DEBUG
if (fineDebug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'CalculatingWalkingPathToTarget'.");
#endif
extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToTarget;
carUsageMode = CarUsagePolicy.Forbidden;
break;
case ExtPathMode.RequiresCarPath:
case ExtPathMode.RequiresMixedCarPathToTarget:
case ExtPathMode.DrivingToTarget:
case ExtPathMode.DrivingToKnownParkPos:
case ExtPathMode.DrivingToAltParkPos:
case ExtPathMode.CalculatingCarPathToAltParkPos:
case ExtPathMode.CalculatingCarPathToKnownParkPos:
case ExtPathMode.CalculatingCarPathToTarget:
if (parkedVehicleId == 0) {
/*
* Citizen wants to drive to target but parked vehicle is not present
* -> Reset path mode
*/
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode} but no parked vehicle present. Change to 'None'.");
#endif
extInstance.Reset();
} else {
/*
* Citizen wants to drive to target and parked vehicle is present
* -> Force parked car usage
*/
#if DEBUG
if (fineDebug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'RequiresCarPath'.");
#endif
extInstance.pathMode = ExtPathMode.RequiresCarPath;
carUsageMode = CarUsagePolicy.ForcedParked;
startPos = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position; // force to start from the parked car
}
break;
default:
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'None'.");
#endif
extInstance.Reset();
break;
}
startsAtOutsideConnection = Constants.ManagerFactory.ExtCitizenInstanceManager.IsAtOutsideConnection(instanceID, ref instanceData, ref extInstance, startPos);
if (extInstance.pathMode == ExtPathMode.None) {
if ((instanceData.m_flags & CitizenInstance.Flags.OnTour) != CitizenInstance.Flags.None || ignoreCost) {
/*
* Citizen is on a walking tour or is a mascot
* -> Prohibit car usage
*/
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen ignores cost ({ignoreCost}) or is on a walking tour ({(instanceData.m_flags & CitizenInstance.Flags.OnTour) != CitizenInstance.Flags.None}): Setting path mode to 'CalculatingWalkingPathToTarget'");
#endif
carUsageMode = CarUsagePolicy.Forbidden;
extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToTarget;
} else {
/*
* Citizen is not on a walking tour and is not a mascot
* -> Check if citizen is located at an outside connection and make them obey Parking AI restrictions
*/
if (instanceData.m_sourceBuilding != 0) {
ItemClass.Service sourceBuildingService = Singleton.instance.m_buildings.m_buffer[instanceData.m_sourceBuilding].Info.m_class.m_service;
if (startsAtOutsideConnection) {
if (sourceBuildingService == ItemClass.Service.Road) {
if (vehicleInfo != null) {
/*
* Citizen is located at a road outside connection and can spawn a car
* -> Force car usage
*/
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen is located at a road outside connection: Setting path mode to 'RequiresCarPath' and carUsageMode to 'ForcedPocket'");
#endif
extInstance.pathMode = ExtPathMode.RequiresCarPath;
carUsageMode = CarUsagePolicy.ForcedPocket;
} else {
/*
* Citizen is located at a non-road outside connection and cannot spawn a car
* -> Path-finding fails
*/
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen is located at a road outside connection but does not have a car template: ABORTING PATH-FINDING");
#endif
extInstance.Reset();
return false;
}
} else {
/*
* Citizen is located at a non-road outside connection
* -> Prohibit car usage
*/
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen is located at a non-road outside connection: Setting path mode to 'CalculatingWalkingPathToTarget'");
#endif
extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToTarget;
carUsageMode = CarUsagePolicy.Forbidden;
}
}
}
}
}
if ((carUsageMode == CarUsagePolicy.Allowed || carUsageMode == CarUsagePolicy.ForcedParked) && parkedVehicleId != 0) {
/*
* Reuse parked vehicle info
*/
vehicleInfo = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].Info;
/*
* Check if the citizen should return their car back home
*/
if (extInstance.pathMode == ExtPathMode.None && // initiating a new path
homeId != 0 && // home building present
instanceData.m_targetBuilding == homeId // current target is home
) {
/*
* citizen travels back home
* -> check if their car should be returned
*/
if ((extCitizen.lastTransportMode & ExtCitizen.ExtTransportMode.Car) != ExtCitizen.ExtTransportMode.None) {
/*
* citizen travelled by car
* -> return car back home
*/
extInstance.pathMode = ExtCitizenInstance.ExtPathMode.CalculatingWalkingPathToParkedCar;
carUsageMode = CarUsagePolicy.Forbidden;
#if DEBUG
if (fineDebug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen used their car before and is not at home. Forcing to walk to parked car.");
#endif
} else {
/*
* citizen travelled by other means of transport
* -> check distance between home and parked car. if too far away: force to take the car back home
*/
float distHomeToParked = (Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position - Singleton.instance.m_buildings.m_buffer[homeId].m_position).magnitude;
if (distHomeToParked > GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome) {
/*
* force to take car back home
*/
extInstance.pathMode = ExtCitizenInstance.ExtPathMode.CalculatingWalkingPathToParkedCar;
carUsageMode = CarUsagePolicy.Forbidden;
#if DEBUG
if (fineDebug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen wants to go home and parked car is too far away ({distHomeToParked}). Forcing walking to parked car.");
#endif
}
}
}
}
/*
* The following holds:
* - pathMode is now either CalculatingWalkingPathToParkedCar, CalculatingWalkingPathToTarget, RequiresCarPath or None.
* - if pathMode is CalculatingWalkingPathToParkedCar or RequiresCarPath: parked car is present and citizen is not on a walking tour
* - carUsageMode is valid
* - if pathMode is RequiresCarPath: carUsageMode is either ForcedParked or ForcedPocket
*/
/*
* modify path-finding constraints (vehicleInfo, endPos) if citizen is forced to walk
*/
if (extInstance.pathMode == ExtPathMode.CalculatingWalkingPathToParkedCar || extInstance.pathMode == ExtPathMode.CalculatingWalkingPathToTarget) {
/*
* vehicle must not be used since we need a walking path to either
* 1. a parked car or
* 2. the target building
*/
if (extInstance.pathMode == ExtCitizenInstance.ExtPathMode.CalculatingWalkingPathToParkedCar) {
/*
* walk to parked car
* -> end position is parked car
*/
endPos = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position;
#if DEBUG
if (fineDebug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen shall go to parked vehicle @ {endPos}");
#endif
}
}
}
#if BENCHMARK
}
#endif
#if DEBUG
if (fineDebug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen is allowed to drive their car? {carUsageMode}");
#endif
// NON-STOCK CODE END
/*
* semi-stock code: determine path-finding parameters (laneTypes, vehicleTypes, extVehicleType, etc.)
*/
NetInfo.LaneType laneTypes = NetInfo.LaneType.Pedestrian;
VehicleInfo.VehicleType vehicleTypes = VehicleInfo.VehicleType.None;
bool randomParking = false;
bool combustionEngine = false;
ExtVehicleType extVehicleType = ExtVehicleType.None;
if (vehicleInfo != null) {
if (vehicleInfo.m_class.m_subService == ItemClass.SubService.PublicTransportTaxi) {
if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTaxi) == CitizenInstance.Flags.None && Singleton.instance.m_districts.m_buffer[0].m_productionData.m_finalTaxiCapacity != 0u) {
SimulationManager instance = Singleton.instance;
if (instance.m_isNightTime || instance.m_randomizer.Int32(2u) == 0) {
laneTypes |= (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle);
vehicleTypes |= vehicleInfo.m_vehicleType;
extVehicleType = ExtVehicleType.Taxi; // NON-STOCK CODE
// NON-STOCK CODE START
if (Options.prohibitPocketCars) {
extInstance.pathMode = ExtPathMode.TaxiToTarget;
}
// NON-STOCK CODE END
}
}
} else
// NON-STOCK CODE START
if (vehicleInfo.m_vehicleType == VehicleInfo.VehicleType.Car) {
if (carUsageMode != CarUsagePolicy.Forbidden) {
extVehicleType = ExtVehicleType.PassengerCar;
laneTypes |= NetInfo.LaneType.Vehicle;
vehicleTypes |= vehicleInfo.m_vehicleType;
combustionEngine = vehicleInfo.m_class.m_subService == ItemClass.SubService.ResidentialLow;
}
} else if (vehicleInfo.m_vehicleType == VehicleInfo.VehicleType.Bicycle) {
extVehicleType = ExtVehicleType.Bicycle;
laneTypes |= NetInfo.LaneType.Vehicle;
vehicleTypes |= vehicleInfo.m_vehicleType;
}
// NON-STOCK CODE END
}
// NON-STOCK CODE START
ExtPathType extPathType = ExtPathType.None;
PathUnit.Position endPosA = default(PathUnit.Position);
bool calculateEndPos = true;
bool allowRandomParking = true;
#if BENCHMARK
using (var bm = new Benchmark(null, "ParkingAI.Main")) {
#endif
if (Options.prohibitPocketCars) {
// Parking AI
if (extInstance.pathMode == ExtCitizenInstance.ExtPathMode.RequiresCarPath) {
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Setting startPos={startPos} for citizen instance {instanceID}. CurrentDepartureMode={extInstance.pathMode}");
#endif
if (
instanceData.m_targetBuilding == 0 ||
(Singleton.instance.m_buildings.m_buffer[instanceData.m_targetBuilding].m_flags & Building.Flags.IncomingOutgoing) == Building.Flags.None
) {
/*
* the citizen is starting their journey and the target is not an outside connection
* -> find a suitable parking space near the target
*/
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Finding parking space at target for citizen instance {instanceID}. CurrentDepartureMode={extInstance.pathMode} parkedVehicleId={parkedVehicleId}");
#endif
// find a parking space in the vicinity of the target
bool calcEndPos;
Vector3 parkPos;
if (
AdvancedParkingManager.Instance.FindParkingSpaceForCitizen(endPos, vehicleInfo, ref extInstance, homeId, instanceData.m_targetBuilding == homeId, 0, false, out parkPos, ref endPosA, out calcEndPos) &&
extInstance.CalculateReturnPath(parkPos, endPos)
) {
// success
extInstance.pathMode = ExtCitizenInstance.ExtPathMode.CalculatingCarPathToKnownParkPos;
calculateEndPos = calcEndPos; // if true, the end path position still needs to be calculated
allowRandomParking = false; // find a direct path to the calculated parking position
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Finding known parking space for citizen instance {instanceID}, parked vehicle {parkedVehicleId} succeeded and return path {extInstance.returnPathId} ({extInstance.returnPathState}) is calculating. PathMode={extInstance.pathMode}");
#endif
/*if (! extInstance.CalculateReturnPath(parkPos, endPos)) {
// TODO retry?
if (debug)
Log._Debug($"CustomCitizenAI.CustomStartPathFind: [PFFAIL] Could not calculate return path for citizen instance {instanceID}, parked vehicle {parkedVehicleId}. Calling OnPathFindFailed.");
CustomHumanAI.OnPathFindFailure(extInstance);
return false;
}*/
}
}
if (extInstance.pathMode == ExtPathMode.RequiresCarPath) {
/*
* no known parking space found (pathMode has not been updated in the block above)
* -> calculate direct path to target
*/
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen instance {instanceID} is still at CurrentPathMode={extInstance.pathMode} (no parking space found?). Setting it to CalculatingCarPath. parkedVehicleId={parkedVehicleId}");
#endif
extInstance.pathMode = ExtCitizenInstance.ExtPathMode.CalculatingCarPathToTarget;
}
}
/*
* determine path type from path mode
*/
extPathType = extInstance.GetPathType();
extInstance.atOutsideConnection = startsAtOutsideConnection;
/*
* the following holds:
* - pathMode is now either CalculatingWalkingPathToParkedCar, CalculatingWalkingPathToTarget, CalculatingCarPathToTarget, CalculatingCarPathToKnownParkPos or None.
*/
}
#if BENCHMARK
}
#endif
/*
* enable random parking if exact parking space was not calculated yet
*/
if (extVehicleType == ExtVehicleType.PassengerCar || extVehicleType == ExtVehicleType.Bicycle) {
if (allowRandomParking &&
instanceData.m_targetBuilding != 0 &&
(
Singleton.instance.m_buildings.m_buffer[instanceData.m_targetBuilding].Info.m_class.m_service > ItemClass.Service.Office ||
(instanceData.m_flags & CitizenInstance.Flags.TargetIsNode) != 0
)) {
randomParking = true;
}
}
// NON-STOCK CODE END
/*
* determine the path position of the parked vehicle
*/
PathUnit.Position parkedVehiclePathPos = default(PathUnit.Position);
if (parkedVehicleId != 0 && extVehicleType == ExtVehicleType.PassengerCar) {
Vector3 position = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position;
CustomPathManager.FindPathPositionWithSpiralLoop(position, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, false, false, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out parkedVehiclePathPos);
}
bool allowUnderground = (instanceData.m_flags & (CitizenInstance.Flags.Underground | CitizenInstance.Flags.Transition)) != CitizenInstance.Flags.None;
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Requesting path-finding for citizen instance {instanceID}, citizen {instanceData.m_citizen}, extVehicleType={extVehicleType}, extPathType={extPathType}, startPos={startPos}, endPos={endPos}, sourceBuilding={instanceData.m_sourceBuilding}, targetBuilding={instanceData.m_targetBuilding} pathMode={extInstance.pathMode}");
#endif
/*
* determine start & end path positions
*/
bool foundEndPos = !calculateEndPos || FindPathPosition(instanceID, ref instanceData, endPos, Options.prohibitPocketCars && (instanceData.m_targetBuilding == 0 || (Singleton.instance.m_buildings.m_buffer[instanceData.m_targetBuilding].m_flags & Building.Flags.IncomingOutgoing) == Building.Flags.None) ? NetInfo.LaneType.Pedestrian : laneTypes, vehicleTypes, false, out endPosA); // NON-STOCK CODE: with Parking AI enabled, the end position must be a pedestrian position
bool foundStartPos = false;
PathUnit.Position startPosA;
if (Options.prohibitPocketCars && (extInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget || extInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos)) {
/*
* citizen will enter their car now
* -> find a road start position
*/
foundStartPos = CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, laneTypes & ~NetInfo.LaneType.Pedestrian, vehicleTypes, allowUnderground, false, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out startPosA);
} else {
foundStartPos = FindPathPosition(instanceID, ref instanceData, startPos, laneTypes, vehicleTypes, allowUnderground, out startPosA);
}
/*
* start path-finding
*/
if (foundStartPos && // TODO probably fails if vehicle is parked too far away from road
foundEndPos // NON-STOCK CODE
) {
if (enableTransport) {
/*
* public transport usage is allowed for this path
*/
if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) {
/*
* citizen may use public transport
*/
laneTypes |= NetInfo.LaneType.PublicTransport;
uint citizenId = instanceData.m_citizen;
if (citizenId != 0u && (citizenManager.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Evacuating) != Citizen.Flags.None) {
laneTypes |= NetInfo.LaneType.EvacuationTransport;
}
} else if (Options.prohibitPocketCars) { // TODO check for incoming connection
/*
* citizen tried to use public transport but waiting time was too long
* -> add public transport demand for source building
*/
if (instanceData.m_sourceBuilding != 0) {
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen instance {instanceID} cannot uses public transport from building {instanceData.m_sourceBuilding} to {instanceData.m_targetBuilding}. Incrementing public transport demand.");
#endif
ExtBuildingManager.Instance.ExtBuildings[instanceData.m_sourceBuilding].AddPublicTransportDemand((uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandWaitingIncrement, true);
}
}
}
PathUnit.Position dummyPathPos = default(PathUnit.Position);
uint path;
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = extPathType;
args.extVehicleType = extVehicleType;
args.vehicleId = 0;
args.spawned = (instanceData.m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None;
args.buildIndex = Singleton.instance.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = dummyPathPos;
args.endPosA = endPosA;
args.endPosB = dummyPathPos;
args.vehiclePosition = parkedVehiclePathPos;
args.laneTypes = laneTypes;
args.vehicleTypes = vehicleTypes;
args.maxLength = 20000f;
args.isHeavyVehicle = false;
args.hasCombustionEngine = combustionEngine;
args.ignoreBlocked = false;
args.ignoreFlooded = false;
args.ignoreCosts = ignoreCost;
args.randomParking = randomParking;
args.stablePath = false;
args.skipQueue = false;
if ((instanceData.m_flags & CitizenInstance.Flags.OnTour) != 0) {
args.stablePath = true;
args.maxLength = 160000f;
//args.laneTypes &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle);
} else {
args.stablePath = false;
args.maxLength = 20000f;
}
bool res = CustomPathManager._instance.CreatePath(out path, ref Singleton.instance.m_randomizer, args);
// NON-STOCK CODE END
if (res) {
#if DEBUG
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Path-finding starts for citizen instance {instanceID}, path={path}, extVehicleType={extVehicleType}, extPathType={extPathType}, startPosA.segment={startPosA.m_segment}, startPosA.lane={startPosA.m_lane}, laneType={laneTypes}, vehicleType={vehicleTypes}, endPosA.segment={endPosA.m_segment}, endPosA.lane={endPosA.m_lane}, vehiclePos.m_segment={parkedVehiclePathPos.m_segment}, vehiclePos.m_lane={parkedVehiclePathPos.m_lane}, vehiclePos.m_offset={parkedVehiclePathPos.m_offset}");
#endif
if (instanceData.m_path != 0u) {
Singleton.instance.ReleasePath(instanceData.m_path);
}
instanceData.m_path = path;
instanceData.m_flags |= CitizenInstance.Flags.WaitingPath;
return true;
}
}
#if DEBUG
if (Options.prohibitPocketCars) {
if (debug)
Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): CustomCitizenAI.CustomStartPathFind: [PFFAIL] failed for citizen instance {instanceID} (CurrentPathMode={extInstance.pathMode}). startPosA.segment={startPosA.m_segment}, startPosA.lane={startPosA.m_lane}, startPosA.offset={startPosA.m_offset}, endPosA.segment={endPosA.m_segment}, endPosA.lane={endPosA.m_lane}, endPosA.offset={endPosA.m_offset}, foundStartPos={foundStartPos}, foundEndPos={foundEndPos}");
extInstance.Reset();
}
#endif
return false;
}
public bool CustomFindPathPosition(ushort instanceID, ref CitizenInstance citizenData, Vector3 pos, NetInfo.LaneType laneTypes, VehicleInfo.VehicleType vehicleTypes, bool allowUnderground, out PathUnit.Position position) {
return CustomPathManager.FindCitizenPathPosition(pos, laneTypes, vehicleTypes, (citizenData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None, allowUnderground, out position);
}
// stock code
internal static Citizen.AgeGroup GetAgeGroup(Citizen.AgePhase agePhase) {
switch (agePhase) {
case Citizen.AgePhase.Child:
return Citizen.AgeGroup.Child;
case Citizen.AgePhase.Teen0:
case Citizen.AgePhase.Teen1:
return Citizen.AgeGroup.Teen;
case Citizen.AgePhase.Young0:
case Citizen.AgePhase.Young1:
case Citizen.AgePhase.Young2:
return Citizen.AgeGroup.Young;
case Citizen.AgePhase.Adult0:
case Citizen.AgePhase.Adult1:
case Citizen.AgePhase.Adult2:
case Citizen.AgePhase.Adult3:
return Citizen.AgeGroup.Adult;
case Citizen.AgePhase.Senior0:
case Citizen.AgePhase.Senior1:
case Citizen.AgePhase.Senior2:
case Citizen.AgePhase.Senior3:
return Citizen.AgeGroup.Senior;
default:
return Citizen.AgeGroup.Adult;
}
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomCommonBuildingAI.cs
================================================
using ColossalFramework;
using CSUtil.Commons.Benchmark;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TrafficManager.Manager;
using TrafficManager.Manager.Impl;
using TrafficManager.State;
using TrafficManager.Traffic;
using TrafficManager.Traffic.Data;
namespace TrafficManager.Custom.AI {
public class CustomCommonBuildingAI : BuildingAI {
public void CustomSimulationStep(ushort buildingID, ref Building data) {
// NON-STOCK CODE START
#if BENCHMARK
using (var bm = new Benchmark(null, "ExtSimulationStep")) {
#endif
// slowly decrease parking space demand / public transport demand
uint frameIndex = Singleton.instance.m_currentFrameIndex >> 8;
if ((frameIndex & 1u) == 0u) {
ExtSimulationStep(buildingID, ref data, ref ExtBuildingManager.Instance.ExtBuildings[buildingID]);
}
#if BENCHMARK
}
#endif
// NON-STOCK CODE END
base.SimulationStep(buildingID, ref data);
if ((data.m_flags & Building.Flags.Demolishing) != Building.Flags.None) {
uint rand = (uint)(((int)buildingID << 8) / 49152);
uint frameIndexRand = Singleton.instance.m_currentFrameIndex - rand;
if ((data.m_flags & Building.Flags.Collapsed) == Building.Flags.None || data.GetFrameData(frameIndexRand - 256u).m_constructState == 0) {
Singleton.instance.ReleaseBuilding(buildingID);
}
}
}
internal void ExtSimulationStep(ushort buildingID, ref Building data, ref ExtBuilding extBuilding) {
extBuilding.RemoveParkingSpaceDemand(GlobalConfig.Instance.ParkingAI.ParkingSpaceDemandDecrement);
extBuilding.RemovePublicTransportDemand(GlobalConfig.Instance.ParkingAI.PublicTransportDemandDecrement, true);
extBuilding.RemovePublicTransportDemand(GlobalConfig.Instance.ParkingAI.PublicTransportDemandDecrement, false);
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomFireTruckAI.cs
================================================
using ColossalFramework;
using CSUtil.Commons.Benchmark;
using System;
using System.Collections.Generic;
using System.Text;
using TrafficManager.Custom.PathFinding;
using TrafficManager.Geometry;
using TrafficManager.Manager;
using TrafficManager.Manager.Impl;
using TrafficManager.Traffic;
using TrafficManager.Traffic.Data;
using UnityEngine;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
class CustomFireTruckAI : CarAI {
public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) {
#if DEBUG
//Log._Debug($"CustomFireTruckAI.CustomStartPathFind called for vehicle {vehicleID}");
#endif
ExtVehicleType vehicleType = ExtVehicleType.None;
#if BENCHMARK
using (var bm = new Benchmark(null, "OnStartPathFind")) {
#endif
vehicleType = VehicleStateManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0 ? ExtVehicleType.Emergency : ExtVehicleType.Service);
#if BENCHMARK
}
#endif
VehicleInfo info = this.m_info;
bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0;
PathUnit.Position startPosA;
PathUnit.Position startPosB;
float startDistSqrA;
float startDistSqrB;
PathUnit.Position endPosA;
PathUnit.Position endPosB;
float endDistSqrA;
float endDistSqrB;
if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) &&
CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) {
if (!startBothWays || startDistSqrA < 10f) {
startPosB = default(PathUnit.Position);
}
if (!endBothWays || endDistSqrA < 10f) {
endPosB = default(PathUnit.Position);
}
uint path;
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = ExtCitizenInstance.ExtPathType.None;
args.extVehicleType = vehicleType;
args.vehicleId = vehicleID;
args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
args.buildIndex = Singleton.instance.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = startPosB;
args.endPosA = endPosA;
args.endPosB = endPosB;
args.vehiclePosition = default(PathUnit.Position);
args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle;
args.vehicleTypes = info.m_vehicleType;
args.maxLength = 20000f;
args.isHeavyVehicle = this.IsHeavyVehicle();
args.hasCombustionEngine = this.CombustionEngine();
args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData);
args.ignoreFlooded = false;
args.ignoreCosts = false;
args.randomParking = false;
args.stablePath = false;
args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
if (CustomPathManager._instance.CreatePath(out path, ref Singleton.instance.m_randomizer, args)) {
// NON-STOCK CODE END
if (vehicleData.m_path != 0u) {
Singleton.instance.ReleasePath(vehicleData.m_path);
}
vehicleData.m_path = path;
vehicleData.m_flags |= Vehicle.Flags.WaitingPath;
return true;
}
} else {
PathfindFailure(vehicleID, ref vehicleData);
}
return false;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomHumanAI.cs
================================================
using ColossalFramework;
using TrafficManager.State;
using TrafficManager.Geometry;
using TrafficManager.TrafficLight;
using TrafficManager.Manager;
using UnityEngine;
using TrafficManager.Traffic;
using TrafficManager.Custom.PathFinding;
using System;
using TrafficManager.Util;
using ColossalFramework.Math;
using TrafficManager.UI;
using CSUtil.Commons;
using TrafficManager.Manager.Impl;
using System.Runtime.CompilerServices;
using static TrafficManager.Traffic.Data.ExtCitizenInstance;
using TrafficManager.Traffic.Data;
using CSUtil.Commons.Benchmark;
namespace TrafficManager.Custom.AI {
class CustomHumanAI : CitizenAI {
public void CustomSimulationStep(ushort instanceID, ref CitizenInstance instanceData, Vector3 physicsLodRefPos) {
#if DEBUG
bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceID) &&
(GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) &&
(GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) &&
(GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding)
;
bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug;
bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug;
#endif
CitizenManager citizenManager = Singleton.instance;
uint citizenId = instanceData.m_citizen;
if ((instanceData.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != CitizenInstance.Flags.None && (instanceData.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) {
citizenManager.ReleaseCitizenInstance(instanceID);
if (citizenId != 0u) {
citizenManager.ReleaseCitizen(citizenId);
}
return;
}
if ((instanceData.m_flags & CitizenInstance.Flags.WaitingPath) != CitizenInstance.Flags.None) {
PathManager pathManager = Singleton.instance;
byte pathFindFlags = pathManager.m_pathUnits.m_buffer[instanceData.m_path].m_pathFindFlags;
// NON-STOCK CODE START
ExtPathState mainPathState = ExtPathState.Calculating;
if ((pathFindFlags & PathUnit.FLAG_FAILED) != 0 || instanceData.m_path == 0) {
mainPathState = ExtPathState.Failed;
} else if ((pathFindFlags & PathUnit.FLAG_READY) != 0) {
mainPathState = ExtPathState.Ready;
}
#if DEBUG
if (debug)
Log._Debug($"CustomHumanAI.CustomSimulationStep({instanceID}): Path: {instanceData.m_path}, mainPathState={mainPathState}");
#endif
ExtSoftPathState finalPathState = ExtSoftPathState.None;
#if BENCHMARK
using (var bm = new Benchmark(null, "ConvertPathStateToSoftPathState+UpdateCitizenPathState")) {
#endif
finalPathState = ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState);
if (Options.prohibitPocketCars) {
finalPathState = AdvancedParkingManager.Instance.UpdateCitizenPathState(instanceID, ref instanceData, ref ExtCitizenInstanceManager.Instance.ExtInstances[instanceID], ref ExtCitizenManager.Instance.ExtCitizens[citizenId], ref citizenManager.m_citizens.m_buffer[instanceData.m_citizen], mainPathState);
#if DEBUG
if (debug)
Log._Debug($"CustomHumanAI.CustomSimulationStep({instanceID}): Applied Parking AI logic. Path: {instanceData.m_path}, mainPathState={mainPathState}, finalPathState={finalPathState}, extCitizenInstance={ExtCitizenInstanceManager.Instance.ExtInstances[instanceID]}");
#endif
}
#if BENCHMARK
}
#endif
switch (finalPathState) {
case ExtSoftPathState.Ready:
#if DEBUG
if (debug)
Log._Debug($"CustomHumanAI.CustomSimulationStep({instanceID}): Path-finding succeeded for citizen instance {instanceID} (finalPathState={finalPathState}). Path: {instanceData.m_path} -- calling HumanAI.PathfindSuccess");
#endif
if (citizenId == 0 || citizenManager.m_citizens.m_buffer[instanceData.m_citizen].m_vehicle == 0) {
this.Spawn(instanceID, ref instanceData);
}
instanceData.m_pathPositionIndex = 255;
instanceData.m_flags &= ~CitizenInstance.Flags.WaitingPath;
instanceData.m_flags &= ~(CitizenInstance.Flags.HangAround | CitizenInstance.Flags.Panicking | CitizenInstance.Flags.SittingDown | CitizenInstance.Flags.Cheering);
// NON-STOCK CODE START (transferred from ResidentAI.PathfindSuccess)
if (citizenId != 0 && (citizenManager.m_citizens.m_buffer[citizenId].m_flags & (Citizen.Flags.Tourist | Citizen.Flags.MovingIn | Citizen.Flags.DummyTraffic)) == Citizen.Flags.MovingIn) {
StatisticBase statisticBase = Singleton.instance.Acquire(StatisticType.MoveRate);
statisticBase.Add(1);
}
// NON-STOCK CODE END
this.PathfindSuccess(instanceID, ref instanceData);
break;
case ExtSoftPathState.Ignore:
#if DEBUG
if (debug)
Log._Debug($"CustomHumanAI.CustomSimulationStep({instanceID}): Path-finding result shall be ignored for citizen instance {instanceID} (finalPathState={finalPathState}). Path: {instanceData.m_path} -- ignoring");
#endif
return;
case ExtSoftPathState.Calculating:
default:
#if DEBUG
if (debug)
Log._Debug($"CustomHumanAI.CustomSimulationStep({instanceID}): Path-finding result undetermined for citizen instance {instanceID} (finalPathState={finalPathState}). Path: {instanceData.m_path} -- continue");
#endif
break;
case ExtSoftPathState.FailedHard:
#if DEBUG
if (debug)
Log._Debug($"CustomHumanAI.CustomSimulationStep({instanceID}): HARD path-finding failure for citizen instance {instanceID} (finalPathState={finalPathState}). Path: {instanceData.m_path} -- calling HumanAI.PathfindFailure");
#endif
instanceData.m_flags &= ~CitizenInstance.Flags.WaitingPath;
instanceData.m_flags &= ~(CitizenInstance.Flags.HangAround | CitizenInstance.Flags.Panicking | CitizenInstance.Flags.SittingDown | CitizenInstance.Flags.Cheering);
Singleton.instance.ReleasePath(instanceData.m_path);
instanceData.m_path = 0u;
this.PathfindFailure(instanceID, ref instanceData);
return;
case ExtSoftPathState.FailedSoft:
#if DEBUG
if (debug)
Log._Debug($"CustomHumanAI.CustomSimulationStep({instanceID}): SOFT path-finding failure for citizen instance {instanceID} (finalPathState={finalPathState}). Path: {instanceData.m_path} -- calling HumanAI.InvalidPath");
#endif
// path mode has been updated, repeat path-finding
instanceData.m_flags &= ~CitizenInstance.Flags.WaitingPath;
instanceData.m_flags &= ~(CitizenInstance.Flags.HangAround | CitizenInstance.Flags.Panicking | CitizenInstance.Flags.SittingDown | CitizenInstance.Flags.Cheering);
this.InvalidPath(instanceID, ref instanceData);
break;
}
// NON-STOCK CODE END
}
// NON-STOCK CODE START
#if BENCHMARK
using (var bm = new Benchmark(null, "ExtSimulationStep")) {
#endif
if (Options.prohibitPocketCars) {
if (ExtSimulationStep(instanceID, ref instanceData, ref ExtCitizenInstanceManager.Instance.ExtInstances[instanceID], physicsLodRefPos)) {
return;
}
}
#if BENCHMARK
}
#endif
// NON-STOCK CODE END
base.SimulationStep(instanceID, ref instanceData, physicsLodRefPos);
VehicleManager vehicleManager = Singleton.instance;
ushort vehicleId = 0;
if (instanceData.m_citizen != 0u) {
vehicleId = citizenManager.m_citizens.m_buffer[instanceData.m_citizen].m_vehicle;
}
if (vehicleId != 0) {
VehicleInfo vehicleInfo = vehicleManager.m_vehicles.m_buffer[(int)vehicleId].Info;
if (vehicleInfo.m_vehicleType == VehicleInfo.VehicleType.Bicycle) {
vehicleInfo.m_vehicleAI.SimulationStep(vehicleId, ref vehicleManager.m_vehicles.m_buffer[(int)vehicleId], vehicleId, ref vehicleManager.m_vehicles.m_buffer[(int)vehicleId], 0);
vehicleId = 0;
}
}
if (vehicleId == 0 && (instanceData.m_flags & (CitizenInstance.Flags.Character | CitizenInstance.Flags.WaitingPath | CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) == CitizenInstance.Flags.None) {
instanceData.m_flags &= ~(CitizenInstance.Flags.HangAround | CitizenInstance.Flags.Panicking | CitizenInstance.Flags.SittingDown);
CustomArriveAtDestination(instanceID, ref instanceData, false);
citizenManager.ReleaseCitizenInstance(instanceID);
}
}
internal bool ExtSimulationStep(ushort instanceID, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, Vector3 physicsLodRefPos) {
#if DEBUG
bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceID) &&
(GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) &&
(GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) &&
(GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding)
;
bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug;
bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug;
#endif
// check if the citizen has reached a parked car or target
if (extInstance.pathMode == ExtPathMode.WalkingToParkedCar || extInstance.pathMode == ExtPathMode.ApproachingParkedCar) {
ushort parkedVehicleId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].m_parkedVehicle;
if (parkedVehicleId == 0) {
// citizen is reaching their parked car but does not own a parked car
#if DEBUG
if (debug)
Log.Warning($"CustomHumanAI.ExtSimulationStep({instanceID}): Citizen instance {instanceID} was walking to / reaching their parked car ({extInstance.pathMode}) but parked car has disappeared. RESET.");
#endif
extInstance.Reset();
instanceData.m_flags &= ~CitizenInstance.Flags.WaitingPath;
instanceData.m_flags &= ~(CitizenInstance.Flags.HangAround | CitizenInstance.Flags.Panicking | CitizenInstance.Flags.SittingDown | CitizenInstance.Flags.Cheering);
this.InvalidPath(instanceID, ref instanceData);
return true;
} else {
ParkedCarApproachState approachState = AdvancedParkingManager.Instance.CitizenApproachingParkedCarSimulationStep(instanceID, ref instanceData, ref extInstance, physicsLodRefPos, ref Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId]);
switch (approachState) {
case ParkedCarApproachState.None:
default:
break;
case ParkedCarApproachState.Approaching:
// citizen approaches their parked car
return true;
case ParkedCarApproachState.Approached:
// citizen reached their parked car
#if DEBUG
if (fineDebug)
Log._Debug($"CustomHumanAI.CustomSimulationStep({instanceID}): Citizen instance {instanceID} arrived at parked car. PathMode={extInstance.pathMode}");
#endif
if (instanceData.m_path != 0) {
Singleton.instance.ReleasePath(instanceData.m_path);
instanceData.m_path = 0;
}
instanceData.m_flags = instanceData.m_flags & (CitizenInstance.Flags.Created | CitizenInstance.Flags.Cheering | CitizenInstance.Flags.Deleted | CitizenInstance.Flags.Underground | CitizenInstance.Flags.CustomName | CitizenInstance.Flags.Character | CitizenInstance.Flags.BorrowCar | CitizenInstance.Flags.HangAround | CitizenInstance.Flags.InsideBuilding | CitizenInstance.Flags.WaitingPath | CitizenInstance.Flags.TryingSpawnVehicle | CitizenInstance.Flags.CannotUseTransport | CitizenInstance.Flags.Panicking | CitizenInstance.Flags.OnPath | CitizenInstance.Flags.SittingDown | CitizenInstance.Flags.AtTarget | CitizenInstance.Flags.RequireSlowStart | CitizenInstance.Flags.Transition | CitizenInstance.Flags.RidingBicycle | CitizenInstance.Flags.OnBikeLane | CitizenInstance.Flags.CannotUseTaxi | CitizenInstance.Flags.CustomColor | CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating | CitizenInstance.Flags.TargetFlags);
if (!this.StartPathFind(instanceID, ref instanceData)) {
instanceData.Unspawn(instanceID);
extInstance.Reset();
}
return true;
case ParkedCarApproachState.Failure:
#if DEBUG
if (debug)
Log._Debug($"CustomHumanAI.ExtSimulationStep({instanceID}): Citizen instance {instanceID} failed to arrive at parked car. PathMode={extInstance.pathMode}");
#endif
// repeat path-finding
instanceData.m_flags &= ~CitizenInstance.Flags.WaitingPath;
instanceData.m_flags &= ~(CitizenInstance.Flags.HangAround | CitizenInstance.Flags.Panicking | CitizenInstance.Flags.SittingDown | CitizenInstance.Flags.Cheering);
this.InvalidPath(instanceID, ref instanceData);
return true;
}
}
} else if (extInstance.pathMode == ExtCitizenInstance.ExtPathMode.WalkingToTarget ||
extInstance.pathMode == ExtCitizenInstance.ExtPathMode.TaxiToTarget
) {
AdvancedParkingManager.Instance.CitizenApproachingTargetSimulationStep(instanceID, ref instanceData, ref extInstance);
}
return false;
}
///
/// Makes the given citizen instance enter their parked car.
///
/// Citizen instance id
/// Citizen instance data
/// Parked vehicle id
/// Vehicle id
/// true if entering the car succeeded, false otherwise
public static bool EnterParkedCar(ushort instanceID, ref CitizenInstance instanceData, ushort parkedVehicleId, out ushort vehicleId) {
#if DEBUG
bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceID) &&
(GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == instanceData.m_citizen) &&
(GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == instanceData.m_sourceBuilding) &&
(GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == instanceData.m_targetBuilding)
;
bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug;
bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug;
if (debug)
Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}, ..., {parkedVehicleId}) called.");
#endif
VehicleManager vehManager = Singleton.instance;
NetManager netManager = Singleton.instance;
CitizenManager citManager = Singleton.instance;
Vector3 parkedVehPos = vehManager.m_parkedVehicles.m_buffer[parkedVehicleId].m_position;
Quaternion parkedVehRot = vehManager.m_parkedVehicles.m_buffer[parkedVehicleId].m_rotation;
VehicleInfo vehicleInfo = vehManager.m_parkedVehicles.m_buffer[parkedVehicleId].Info;
PathUnit.Position vehLanePathPos;
if (! CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].GetPosition(0, out vehLanePathPos)) {
#if DEBUG
if (debug)
Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Could not get first car path position of citizen instance {instanceID}!");
#endif
vehicleId = 0;
return false;
}
uint vehLaneId = PathManager.GetLaneID(vehLanePathPos);
#if DEBUG
if (fineDebug)
Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Determined vehicle position for citizen instance {instanceID}: seg. {vehLanePathPos.m_segment}, lane {vehLanePathPos.m_lane}, off {vehLanePathPos.m_offset} (lane id {vehLaneId})");
#endif
Vector3 vehLanePos;
float vehLaneOff;
netManager.m_lanes.m_buffer[vehLaneId].GetClosestPosition(parkedVehPos, out vehLanePos, out vehLaneOff);
byte vehLaneOffset = (byte)Mathf.Clamp(Mathf.RoundToInt(vehLaneOff * 255f), 0, 255);
// movement vector from parked vehicle position to road position
Vector3 forwardVector = parkedVehPos + Vector3.ClampMagnitude(vehLanePos - parkedVehPos, 5f);
if (vehManager.CreateVehicle(out vehicleId, ref Singleton.instance.m_randomizer, vehicleInfo, parkedVehPos, TransferManager.TransferReason.None, false, false)) {
// update frame data
Vehicle.Frame frame = vehManager.m_vehicles.m_buffer[(int)vehicleId].m_frame0;
frame.m_rotation = parkedVehRot;
vehManager.m_vehicles.m_buffer[vehicleId].m_frame0 = frame;
vehManager.m_vehicles.m_buffer[vehicleId].m_frame1 = frame;
vehManager.m_vehicles.m_buffer[vehicleId].m_frame2 = frame;
vehManager.m_vehicles.m_buffer[vehicleId].m_frame3 = frame;
vehicleInfo.m_vehicleAI.FrameDataUpdated(vehicleId, ref vehManager.m_vehicles.m_buffer[vehicleId], ref frame);
// update vehicle target position
vehManager.m_vehicles.m_buffer[vehicleId].m_targetPos0 = new Vector4(vehLanePos.x, vehLanePos.y, vehLanePos.z, 2f);
// update other fields
vehManager.m_vehicles.m_buffer[vehicleId].m_flags = (vehManager.m_vehicles.m_buffer[vehicleId].m_flags | Vehicle.Flags.Stopped);
vehManager.m_vehicles.m_buffer[vehicleId].m_path = instanceData.m_path;
vehManager.m_vehicles.m_buffer[vehicleId].m_pathPositionIndex = 0;
vehManager.m_vehicles.m_buffer[vehicleId].m_lastPathOffset = vehLaneOffset;
vehManager.m_vehicles.m_buffer[vehicleId].m_transferSize = (ushort)(instanceData.m_citizen & 65535u);
if (! vehicleInfo.m_vehicleAI.TrySpawn(vehicleId, ref vehManager.m_vehicles.m_buffer[vehicleId])) {
#if DEBUG
if (debug)
Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Could not spawn a {vehicleInfo.m_vehicleType} for citizen instance {instanceID}!");
#endif
return false;
}
// change instances
InstanceID parkedVehInstance = InstanceID.Empty;
parkedVehInstance.ParkedVehicle = parkedVehicleId;
InstanceID vehInstance = InstanceID.Empty;
vehInstance.Vehicle = vehicleId;
Singleton.instance.ChangeInstance(parkedVehInstance, vehInstance);
// set vehicle id for citizen instance
instanceData.m_path = 0u;
citManager.m_citizens.m_buffer[instanceData.m_citizen].SetParkedVehicle(instanceData.m_citizen, 0);
citManager.m_citizens.m_buffer[instanceData.m_citizen].SetVehicle(instanceData.m_citizen, vehicleId, 0u);
// update citizen instance flags
instanceData.m_flags &= ~CitizenInstance.Flags.WaitingPath;
instanceData.m_flags &= ~CitizenInstance.Flags.EnteringVehicle;
instanceData.m_flags &= ~CitizenInstance.Flags.TryingSpawnVehicle;
instanceData.m_flags &= ~CitizenInstance.Flags.BoredOfWaiting;
instanceData.m_waitCounter = 0;
// unspawn citizen instance
instanceData.Unspawn(instanceID);
#if DEBUG
if (fineDebug)
Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Citizen instance {instanceID} is now entering vehicle {vehicleId}. Set vehicle target position to {vehLanePos} (segment={vehLanePathPos.m_segment}, lane={vehLanePathPos.m_lane}, offset={vehLanePathPos.m_offset})");
#endif
return true;
} else {
// failed to find a road position
#if DEBUG
if (debug)
Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Could not find a road position for citizen instance {instanceID} near parked vehicle {parkedVehicleId}!");
#endif
return false;
}
}
public bool CustomCheckTrafficLights(ushort nodeId, ushort segmentId) {
#if DEBUGTTL
bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == nodeId;
#endif
var netManager = Singleton.instance;
var currentFrameIndex = Singleton.instance.m_currentFrameIndex;
var num = (uint)(((int)nodeId << 8) / 32768);
var stepWaitTime = currentFrameIndex - num & 255u;
// NON-STOCK CODE START //
bool customSim = false;
#if BENCHMARK
using (var bm = new Benchmark(null, "GetNodeSimulation")) {
#endif
customSim = Options.timedLightsEnabled && TrafficLightSimulationManager.Instance.HasActiveSimulation(nodeId);
#if BENCHMARK
}
#endif
RoadBaseAI.TrafficLightState pedestrianLightState;
bool startNode = netManager.m_segments.m_buffer[segmentId].m_startNode == nodeId;
ICustomSegmentLights lights = null;
#if BENCHMARK
using (var bm = new Benchmark(null, "GetSegmentLights")) {
#endif
if (customSim) {
lights = CustomSegmentLightsManager.Instance.GetSegmentLights(segmentId, startNode, false);
}
#if BENCHMARK
}
#endif
if (lights == null) {
// NON-STOCK CODE END //
RoadBaseAI.TrafficLightState vehicleLightState;
bool vehicles;
bool pedestrians;
#if DEBUGTTL
if (debug) {
Log._Debug($"CustomHumanAI.CustomCheckTrafficLights({nodeId}, {segmentId}): No custom simulation!");
}
#endif
RoadBaseAI.GetTrafficLightState(nodeId, ref netManager.m_segments.m_buffer[segmentId], currentFrameIndex - num, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians);
if (pedestrianLightState == RoadBaseAI.TrafficLightState.GreenToRed || pedestrianLightState == RoadBaseAI.TrafficLightState.Red) {
if (!pedestrians && stepWaitTime >= 196u) {
RoadBaseAI.SetTrafficLightState(nodeId, ref netManager.m_segments.m_buffer[segmentId], currentFrameIndex - num, vehicleLightState, pedestrianLightState, vehicles, true);
}
return false;
}
// NON-STOCK CODE START //
} else {
if (lights.InvalidPedestrianLight) {
pedestrianLightState = RoadBaseAI.TrafficLightState.Green;
} else {
pedestrianLightState = (RoadBaseAI.TrafficLightState)lights.PedestrianLightState;
}
#if DEBUGTTL
if (debug) {
Log._Debug($"CustomHumanAI.CustomCheckTrafficLights({nodeId}, {segmentId}): Custom simulation! pedestrianLightState={pedestrianLightState}, lights.InvalidPedestrianLight={lights.InvalidPedestrianLight}");
}
#endif
}
// NON-STOCK CODE END //
switch (pedestrianLightState) {
case RoadBaseAI.TrafficLightState.RedToGreen:
if (stepWaitTime < 60u) {
return false;
}
break;
case RoadBaseAI.TrafficLightState.Red:
case RoadBaseAI.TrafficLightState.GreenToRed:
return false;
}
return true;
}
protected void CustomArriveAtDestination(ushort instanceID, ref CitizenInstance citizenData, bool success) {
uint citizenId = citizenData.m_citizen;
if (citizenId != 0) {
CitizenManager citizenMan = Singleton.instance;
citizenMan.m_citizens.m_buffer[citizenId].SetVehicle(citizenId, 0, 0u);
if ((citizenData.m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None) {
if (success) {
ushort targetBuildingId = citizenData.m_targetBuilding;
if (targetBuildingId != 0) {
ushort transportLineId = Singleton.instance.m_nodes.m_buffer[targetBuildingId].m_transportLine;
if (transportLineId != 0) {
TransportInfo info = Singleton.instance.m_lines.m_buffer[transportLineId].Info;
if (info.m_vehicleType == VehicleInfo.VehicleType.None) {
targetBuildingId = (((instanceID & 1) != 0) ? TransportLine.GetPrevStop(targetBuildingId) : TransportLine.GetNextStop(targetBuildingId));
if (targetBuildingId != 0) {
citizenData.m_flags |= CitizenInstance.Flags.OnTour;
((CitizenAI)this).SetTarget(instanceID, ref citizenData, targetBuildingId, true);
} else {
// Unrolled goto statement
if ((citizenData.m_flags & CitizenInstance.Flags.HangAround) != 0 && success) {
return;
}
((CitizenAI)this).SetSource(instanceID, ref citizenData, (ushort)0);
((CitizenAI)this).SetTarget(instanceID, ref citizenData, (ushort)0);
citizenData.Unspawn(instanceID);
}
return;
}
citizenData.m_flags |= CitizenInstance.Flags.OnTour;
this.WaitTouristVehicle(instanceID, ref citizenData, targetBuildingId);
return;
}
}
}
} else {
if (success) {
citizenMan.m_citizens.m_buffer[citizenId].SetLocationByBuilding(citizenId, citizenData.m_targetBuilding);
// NON-STOCK CODE START
Constants.ManagerFactory.ExtCitizenManager.OnArriveAtDestination(citizenId, ref citizenMan.m_citizens.m_buffer[citizenId]);
// NON-STOCK CODE END
}
if (citizenData.m_targetBuilding != 0 && citizenMan.m_citizens.m_buffer[citizenId].CurrentLocation == Citizen.Location.Visit) {
BuildingManager buildingMan = Singleton.instance;
BuildingInfo info = buildingMan.m_buildings.m_buffer[citizenData.m_targetBuilding].Info;
info.m_buildingAI.VisitorEnter(citizenData.m_targetBuilding, ref buildingMan.m_buildings.m_buffer[citizenData.m_targetBuilding], citizenId);
}
}
}
if ((citizenData.m_flags & CitizenInstance.Flags.HangAround) != 0 && success) {
return;
}
((CitizenAI)this).SetSource(instanceID, ref citizenData, (ushort)0);
((CitizenAI)this).SetTarget(instanceID, ref citizenData, (ushort)0);
citizenData.Unspawn(instanceID);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void PathfindFailure(ushort instanceID, ref CitizenInstance data) {
Log.Error($"HumanAI.PathfindFailure is not overriden!");
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void PathfindSuccess(ushort instanceID, ref CitizenInstance data) {
Log.Error($"HumanAI.PathfindSuccess is not overriden!");
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void Spawn(ushort instanceID, ref CitizenInstance data) {
Log.Error($"HumanAI.Spawn is not overriden!");
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void GetBuildingTargetPosition(ushort instanceID, ref CitizenInstance data, float minSqrDistance) {
Log.Error($"HumanAI.GetBuildingTargetPosition is not overriden!");
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void WaitTouristVehicle(ushort instanceID, ref CitizenInstance data, ushort targetBuildingId) {
Log.Error($"HumanAI.InvokeWaitTouristVehicle is not overriden!");
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomPassengerCarAI.cs
================================================
using System;
using ColossalFramework;
using UnityEngine;
using TrafficManager.Custom.PathFinding;
using TrafficManager.Geometry;
using TrafficManager.State;
using TrafficManager.Traffic;
using TrafficManager.Manager;
using ColossalFramework.Math;
using TrafficManager.Util;
using System.Reflection;
using ColossalFramework.Globalization;
using TrafficManager.UI;
using System.Xml;
using System.IO;
using CSUtil.Commons;
using TrafficManager.Manager.Impl;
using TrafficManager.Traffic.Data;
using static TrafficManager.Traffic.Data.ExtCitizenInstance;
using CSUtil.Commons.Benchmark;
using System.Runtime.CompilerServices;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
// TODO move Parking AI features from here to a distinct manager
public class CustomPassengerCarAI : CarAI {
public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) {
if ((vehicleData.m_flags & Vehicle.Flags.Congestion) != 0 && VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) {
Singleton.instance.ReleaseVehicle(vehicleId);
return;
}
base.SimulationStep(vehicleId, ref vehicleData, physicsLodRefPos);
}
public string CustomGetLocalizedStatus(ushort vehicleID, ref Vehicle data, out InstanceID target) {
CitizenManager citizenManager = Singleton.instance;
ushort driverInstanceId = GetDriverInstanceId(vehicleID, ref data);
ushort targetBuildingId = 0;
bool targetIsNode = false;
if (driverInstanceId != 0) {
if ((data.m_flags & Vehicle.Flags.Parking) != (Vehicle.Flags)0) {
uint citizen = citizenManager.m_instances.m_buffer[(int)driverInstanceId].m_citizen;
if (citizen != 0u && citizenManager.m_citizens.m_buffer[citizen].m_parkedVehicle != 0) {
target = InstanceID.Empty;
return Locale.Get("VEHICLE_STATUS_PARKING");
}
}
targetBuildingId = citizenManager.m_instances.m_buffer[(int)driverInstanceId].m_targetBuilding;
targetIsNode = ((citizenManager.m_instances.m_buffer[driverInstanceId].m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None);
}
if (targetBuildingId == 0) {
target = InstanceID.Empty;
return Locale.Get("VEHICLE_STATUS_CONFUSED");
}
string ret;
bool leavingCity = (Singleton.instance.m_buildings.m_buffer[(int)targetBuildingId].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None;
if (leavingCity) {
target = InstanceID.Empty;
ret = Locale.Get("VEHICLE_STATUS_LEAVING");
} else {
target = InstanceID.Empty;
if (targetIsNode) {
target.NetNode = targetBuildingId;
} else {
target.Building = targetBuildingId;
}
ret = Locale.Get("VEHICLE_STATUS_GOINGTO");
}
// NON-STOCK CODE START
#if BENCHMARK
using (var bm = new Benchmark(null, "EnrichLocalizedCarStatus")) {
#endif
if (Options.prohibitPocketCars) {
ret = AdvancedParkingManager.Instance.EnrichLocalizedCarStatus(ret, ref ExtCitizenInstanceManager.Instance.ExtInstances[driverInstanceId]);
}
#if BENCHMARK
}
#endif
// NON-STOCK CODE END
return ret;
}
public static ushort GetDriverInstanceId(ushort vehicleID, ref Vehicle data) { // TODO reverse-redirect
CitizenManager instance = Singleton.instance;
uint curCitUnitId = data.m_citizenUnits;
int numIters = 0;
while (curCitUnitId != 0u) {
uint nextUnit = instance.m_units.m_buffer[curCitUnitId].m_nextUnit;
for (int i = 0; i < 5; i++) {
uint citizenId = instance.m_units.m_buffer[curCitUnitId].GetCitizen(i);
if (citizenId != 0u) {
ushort citInstanceId = instance.m_citizens.m_buffer[citizenId].m_instance;
if (citInstanceId != 0) {
return citInstanceId;
}
}
}
curCitUnitId = nextUnit;
if (++numIters > 524288) {
CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);
break;
}
}
return 0;
}
public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) {
ushort driverInstanceId = CustomPassengerCarAI.GetDriverInstanceId(vehicleID, ref vehicleData);
if (driverInstanceId == 0) {
return false;
}
#if BENCHMARK
using (var bm = new Benchmark(null, "ExtStartPathFind")) {
#endif
return ExtStartPathFind(vehicleID, ref vehicleData, driverInstanceId, ref Singleton.instance.m_instances.m_buffer[(int)driverInstanceId], ref ExtCitizenInstanceManager.Instance.ExtInstances[driverInstanceId], startPos, endPos, startBothWays, endBothWays, undergroundTarget);
#if BENCHMARK
}
#endif
}
public bool ExtStartPathFind(ushort vehicleID, ref Vehicle vehicleData, ushort driverInstanceId, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) {
#if DEBUG
bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleID) &&
(GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == driverExtInstance.instanceId) &&
(GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == driverInstance.m_citizen) &&
(GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == driverInstance.m_sourceBuilding) &&
(GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == driverInstance.m_targetBuilding)
;
bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug;
bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug;
if (debug)
Log.Warning($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): called for vehicle {vehicleID}, driverInstanceId={driverInstanceId}, startPos={startPos}, endPos={endPos}, sourceBuilding={vehicleData.m_sourceBuilding}, targetBuilding={vehicleData.m_targetBuilding} pathMode={driverExtInstance.pathMode}");
#endif
PathUnit.Position startPosA = default(PathUnit.Position);
PathUnit.Position startPosB = default(PathUnit.Position);
PathUnit.Position endPosA = default(PathUnit.Position);
float sqrDistA = 0f;
float sqrDistB;
ushort targetBuildingId = driverInstance.m_targetBuilding;
uint driverCitizenId = driverInstance.m_citizen;
// NON-STOCK CODE START
bool calculateEndPos = true;
bool allowRandomParking = true;
bool movingToParkingPos = false;
bool foundStartingPos = false;
bool skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
ExtPathType extPathType = ExtPathType.None;
#if BENCHMARK
using (var bm = new Benchmark(null, "ParkingAI")) {
#endif
if (Options.prohibitPocketCars) {
//if (driverExtInstance != null) {
#if DEBUG
if (debug)
Log.Warning($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): PathMode={driverExtInstance.pathMode} for vehicle {vehicleID}, driver citizen instance {driverExtInstance.instanceId}!");
#endif
if (driverExtInstance.pathMode == ExtPathMode.RequiresMixedCarPathToTarget) {
driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget;
startBothWays = false;
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): PathMode was RequiresDirectCarPathToTarget: Parking spaces will NOT be searched beforehand. Setting pathMode={driverExtInstance.pathMode}");
#endif
} else if (
driverExtInstance.pathMode != ExtPathMode.ParkingFailed &&
targetBuildingId != 0 &&
(Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None
) {
// target is outside connection
driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget;
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): PathMode was not ParkingFailed and target is outside connection: Setting pathMode={driverExtInstance.pathMode}");
#endif
} else {
if (driverExtInstance.pathMode == ExtPathMode.DrivingToTarget || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos || driverExtInstance.pathMode == ExtPathMode.ParkingFailed) {
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Skipping queue. pathMode={driverExtInstance.pathMode}");
#endif
skipQueue = true;
}
bool allowTourists = false;
bool searchAtCurrentPos = false;
if (driverExtInstance.pathMode == ExtPathMode.ParkingFailed) {
// previous parking attempt failed
driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToAltParkPos;
allowTourists = true;
searchAtCurrentPos = true;
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Vehicle {vehicleID} shall move to an alternative parking position! CurrentPathMode={driverExtInstance.pathMode} FailedParkingAttempts={driverExtInstance.failedParkingAttempts}");
#endif
if (driverExtInstance.parkingPathStartPosition != null) {
startPosA = (PathUnit.Position)driverExtInstance.parkingPathStartPosition;
foundStartingPos = true;
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Setting starting pos for {vehicleID} to segment={startPosA.m_segment}, laneIndex={startPosA.m_lane}, offset={startPosA.m_offset}");
#endif
}
startBothWays = false;
if (driverExtInstance.failedParkingAttempts > GlobalConfig.Instance.ParkingAI.MaxParkingAttempts) {
// maximum number of parking attempts reached
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Reached maximum number of parking attempts for vehicle {vehicleID}! GIVING UP.");
#endif
driverExtInstance.Reset();
// pocket car fallback
//vehicleData.m_flags |= Vehicle.Flags.Parking;
return false;
} else {
#if DEBUG
if (fineDebug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Increased number of parking attempts for vehicle {vehicleID}: {driverExtInstance.failedParkingAttempts}/{GlobalConfig.Instance.ParkingAI.MaxParkingAttempts}");
#endif
}
} else {
driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToKnownParkPos;
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): No parking involved: Setting pathMode={driverExtInstance.pathMode}");
#endif
}
ushort homeId = Singleton.instance.m_citizens.m_buffer[driverCitizenId].m_homeBuilding;
bool calcEndPos;
Vector3 parkPos;
Vector3 returnPos = searchAtCurrentPos ? (Vector3)vehicleData.m_targetPos3 : endPos;
if (AdvancedParkingManager.Instance.FindParkingSpaceForCitizen(returnPos, vehicleData.Info, ref driverExtInstance, homeId, targetBuildingId == homeId, vehicleID, allowTourists, out parkPos, ref endPosA, out calcEndPos)) {
calculateEndPos = calcEndPos;
allowRandomParking = false;
movingToParkingPos = true;
if (!driverExtInstance.CalculateReturnPath(parkPos, returnPos)) {
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Could not calculate return path for citizen instance {driverExtInstance.instanceId}, vehicle {vehicleID}. Resetting instance.");
#endif
driverExtInstance.Reset();
return false;
}
} else if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos) {
// no alternative parking spot found: abort
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): No alternative parking spot found for vehicle {vehicleID}, citizen instance {driverExtInstance.instanceId} with CurrentPathMode={driverExtInstance.pathMode}! GIVING UP.");
#endif
driverExtInstance.Reset();
return false;
} else {
// calculate a direct path to target
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): No alternative parking spot found for vehicle {vehicleID}, citizen instance {driverExtInstance.instanceId} with CurrentPathMode={driverExtInstance.pathMode}! Setting CurrentPathMode to 'CalculatingCarPath'.");
#endif
driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget;
}
}
extPathType = driverExtInstance.GetPathType();
driverExtInstance.atOutsideConnection = Constants.ManagerFactory.ExtCitizenInstanceManager.IsAtOutsideConnection(driverInstanceId, ref driverInstance, ref driverExtInstance, startPos);
}
#if BENCHMARK
}
#endif
NetInfo.LaneType laneTypes = NetInfo.LaneType.Vehicle;
if (!movingToParkingPos) {
laneTypes |= NetInfo.LaneType.Pedestrian;
if (Options.prohibitPocketCars && (driverInstance.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) {
/*
* citizen may use public transport
*/
laneTypes |= NetInfo.LaneType.PublicTransport;
uint citizenId = driverInstance.m_citizen;
if (citizenId != 0u && (Singleton.instance.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Evacuating) != Citizen.Flags.None) {
laneTypes |= NetInfo.LaneType.EvacuationTransport;
}
}
}
// NON-STOCK CODE END
VehicleInfo.VehicleType vehicleTypes = this.m_info.m_vehicleType;
bool allowUnderground = (vehicleData.m_flags & Vehicle.Flags.Underground) != 0;
bool randomParking = false;
bool combustionEngine = this.m_info.m_class.m_subService == ItemClass.SubService.ResidentialLow;
if (allowRandomParking && // NON-STOCK CODE
!movingToParkingPos &&
targetBuildingId != 0 &&
(
Singleton.instance.m_buildings.m_buffer[(int)targetBuildingId].Info.m_class.m_service > ItemClass.Service.Office ||
(driverInstance.m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None
)) {
randomParking = true;
}
#if DEBUG
if (fineDebug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Requesting path-finding for passenger car {vehicleID}, startPos={startPos}, endPos={endPos}, extPathType={extPathType}");
#endif
// NON-STOCK CODE START
if (!foundStartingPos) {
foundStartingPos = CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, vehicleTypes, allowUnderground, false, 32f, out startPosA, out startPosB, out sqrDistA, out sqrDistB);
}
bool foundEndPos = !calculateEndPos || driverInstance.Info.m_citizenAI.FindPathPosition(driverInstanceId, ref driverInstance, endPos, Options.prohibitPocketCars && (targetBuildingId == 0 || (Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_flags & Building.Flags.IncomingOutgoing) == Building.Flags.None) ? NetInfo.LaneType.Pedestrian : (laneTypes | NetInfo.LaneType.Pedestrian), vehicleTypes, undergroundTarget, out endPosA);
// NON-STOCK CODE END
if (foundStartingPos &&
foundEndPos) { // NON-STOCK CODE
if (!startBothWays || sqrDistA < 10f) {
startPosB = default(PathUnit.Position);
}
PathUnit.Position endPosB = default(PathUnit.Position);
SimulationManager simMan = Singleton.instance;
uint path;
PathUnit.Position dummyPathPos = default(PathUnit.Position);
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = extPathType;
args.extVehicleType = ExtVehicleType.PassengerCar;
args.vehicleId = vehicleID;
args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
args.buildIndex = simMan.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = startPosB;
args.endPosA = endPosA;
args.endPosB = endPosB;
args.vehiclePosition = dummyPathPos;
args.laneTypes = laneTypes;
args.vehicleTypes = vehicleTypes;
args.maxLength = 20000f;
args.isHeavyVehicle = this.IsHeavyVehicle();
args.hasCombustionEngine = this.CombustionEngine();
args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData);
args.ignoreFlooded = false;
args.ignoreCosts = false;
args.randomParking = randomParking;
args.stablePath = false;
args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
if (CustomPathManager._instance.CreatePath(out path, ref simMan.m_randomizer, args)) {
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Path-finding starts for passenger car {vehicleID}, path={path}, startPosA.segment={startPosA.m_segment}, startPosA.lane={startPosA.m_lane}, startPosA.offset={startPosA.m_offset}, startPosB.segment={startPosB.m_segment}, startPosB.lane={startPosB.m_lane}, startPosB.offset={startPosB.m_offset}, laneType={laneTypes}, vehicleType={vehicleTypes}, endPosA.segment={endPosA.m_segment}, endPosA.lane={endPosA.m_lane}, endPosA.offset={endPosA.m_offset}, endPosB.segment={endPosB.m_segment}, endPosB.lane={endPosB.m_lane}, endPosB.offset={endPosB.m_offset}");
#endif
// NON-STOCK CODE END
if (vehicleData.m_path != 0u) {
Singleton.instance.ReleasePath(vehicleData.m_path);
}
vehicleData.m_path = path;
vehicleData.m_flags |= Vehicle.Flags.WaitingPath;
return true;
}
}
if (Options.prohibitPocketCars) {
driverExtInstance.Reset();
}
return false;
}
public void CustomUpdateParkedVehicle(ushort parkedId, ref VehicleParked data) {
float x = this.m_info.m_generatedInfo.m_size.x;
float z = this.m_info.m_generatedInfo.m_size.z;
uint ownerCitizenId = data.m_ownerCitizen;
ushort homeID = 0;
if (ownerCitizenId != 0u) {
homeID = Singleton.instance.m_citizens.m_buffer[ownerCitizenId].m_homeBuilding;
}
// NON-STOCK CODE START
if (!AdvancedParkingManager.Instance.TryMoveParkedVehicle(parkedId, ref data, data.m_position, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, homeID)) {
Singleton.instance.ReleaseParkedVehicle(parkedId);
}
// NON-STOCK CODE END
}
public bool CustomParkVehicle(ushort vehicleID, ref Vehicle vehicleData, PathUnit.Position pathPos, uint nextPath, int nextPositionIndex, out byte segmentOffset) {
CitizenManager citizenManager = Singleton.instance;
// TODO remove this:
uint driverCitizenId = 0u;
ushort driverCitizenInstanceId = 0;
ushort targetBuildingId = 0; // NON-STOCK CODE
uint curCitizenUnitId = vehicleData.m_citizenUnits;
int numIterations = 0;
while (curCitizenUnitId != 0u && driverCitizenId == 0u) {
uint nextUnit = citizenManager.m_units.m_buffer[curCitizenUnitId].m_nextUnit;
for (int i = 0; i < 5; i++) {
uint citizenId = citizenManager.m_units.m_buffer[curCitizenUnitId].GetCitizen(i);
if (citizenId != 0u) {
driverCitizenInstanceId = citizenManager.m_citizens.m_buffer[citizenId].m_instance;
if (driverCitizenInstanceId != 0) {
driverCitizenId = citizenManager.m_instances.m_buffer[(int)driverCitizenInstanceId].m_citizen;
// NON-STOCK CODE START
targetBuildingId = citizenManager.m_instances.m_buffer[(int)driverCitizenInstanceId].m_targetBuilding;
// NON-STOCK CODE END
break;
}
}
}
curCitizenUnitId = nextUnit;
if (++numIterations > 524288) {
CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);
break;
}
}
#if BENCHMARK
using (var bm = new Benchmark(null, "ExtParkVehicle")) {
#endif
return ExtParkVehicle(vehicleID, ref vehicleData, driverCitizenId, ref citizenManager.m_citizens.m_buffer[driverCitizenId], driverCitizenInstanceId, ref citizenManager.m_instances.m_buffer[driverCitizenInstanceId], ref ExtCitizenInstanceManager.Instance.ExtInstances[driverCitizenInstanceId], targetBuildingId, pathPos, nextPath, nextPositionIndex, out segmentOffset);
#if BENCHMARK
}
#endif
}
internal bool ExtParkVehicle(ushort vehicleID, ref Vehicle vehicleData, uint driverCitizenId, ref Citizen driverCitizen, ushort driverCitizenInstanceId, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, ushort targetBuildingId, PathUnit.Position pathPos, uint nextPath, int nextPositionIndex, out byte segmentOffset) {
#if DEBUG
bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleID) &&
(GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == driverExtInstance.instanceId) &&
(GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == driverInstance.m_citizen) &&
(GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == driverInstance.m_sourceBuilding) &&
(GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == driverInstance.m_targetBuilding)
;
bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug;
bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug;
#endif
PathManager pathManager = Singleton.instance;
CitizenManager citizenManager = Singleton.instance;
NetManager netManager = Singleton.instance;
VehicleManager vehicleManager = Singleton.instance;
// NON-STOCK CODE START
bool prohibitPocketCars = false;
// NON-STOCK CODE END
if (driverCitizenId != 0u) {
if (Options.prohibitPocketCars && driverCitizenInstanceId != 0) {
prohibitPocketCars = true;
}
uint laneID = PathManager.GetLaneID(pathPos);
segmentOffset = (byte)Singleton.instance.m_randomizer.Int32(1, 254);
Vector3 refPos;
Vector3 vector;
netManager.m_lanes.m_buffer[laneID].CalculatePositionAndDirection((float)segmentOffset * 0.003921569f, out refPos, out vector);
NetInfo info = netManager.m_segments.m_buffer[(int)pathPos.m_segment].Info;
bool isSegmentInverted = (netManager.m_segments.m_buffer[(int)pathPos.m_segment].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None;
bool isPosNegative = info.m_lanes[(int)pathPos.m_lane].m_position < 0f;
vector.Normalize();
Vector3 searchDir;
if (isSegmentInverted != isPosNegative) {
searchDir.x = -vector.z;
searchDir.y = 0f;
searchDir.z = vector.x;
} else {
searchDir.x = vector.z;
searchDir.y = 0f;
searchDir.z = -vector.x;
}
ushort homeID = 0;
if (driverCitizenId != 0u) {
homeID = driverCitizen.m_homeBuilding;
}
Vector3 parkPos = default(Vector3);
Quaternion parkRot = default(Quaternion);
float parkOffset = -1f;
// NON-STOCK CODE START
bool foundParkingSpace = false;
if (prohibitPocketCars) {
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Vehicle {vehicleID} tries to park on a parking position now (flags: {vehicleData.m_flags})! CurrentPathMode={driverExtInstance.pathMode} path={vehicleData.m_path} pathPositionIndex={vehicleData.m_pathPositionIndex} segmentId={pathPos.m_segment} laneIndex={pathPos.m_lane} offset={pathPos.m_offset} nextPath={nextPath} refPos={refPos} searchDir={searchDir} home={homeID} driverCitizenId={driverCitizenId} driverCitizenInstanceId={driverCitizenInstanceId}");
#endif
if (driverExtInstance.pathMode == ExtCitizenInstance.ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtCitizenInstance.ExtPathMode.DrivingToKnownParkPos) {
// try to use previously found parking space
#if DEBUG
if (debug)
Log._Debug($"Vehicle {vehicleID} tries to park on an (alternative) parking position now! CurrentPathMode={driverExtInstance.pathMode} altParkingSpaceLocation={driverExtInstance.parkingSpaceLocation} altParkingSpaceLocationId={driverExtInstance.parkingSpaceLocationId}");
#endif
switch (driverExtInstance.parkingSpaceLocation) {
case ExtCitizenInstance.ExtParkingSpaceLocation.RoadSide:
uint parkLaneID; int parkLaneIndex;
#if DEBUG
if (debug)
Log._Debug($"Vehicle {vehicleID} wants to park road-side @ segment {driverExtInstance.parkingSpaceLocationId}");
#endif
foundParkingSpace = AdvancedParkingManager.Instance.FindParkingSpaceRoadSideForVehiclePos(this.m_info, 0, driverExtInstance.parkingSpaceLocationId, refPos, out parkPos, out parkRot, out parkOffset, out parkLaneID, out parkLaneIndex);
break;
case ExtCitizenInstance.ExtParkingSpaceLocation.Building:
float maxDist = 9999f;
#if DEBUG
if (debug)
Log._Debug($"Vehicle {vehicleID} wants to park @ building {driverExtInstance.parkingSpaceLocationId}");
#endif
foundParkingSpace = AdvancedParkingManager.Instance.FindParkingSpacePropAtBuilding(this.m_info, homeID, 0, driverExtInstance.parkingSpaceLocationId, ref Singleton.instance.m_buildings.m_buffer[driverExtInstance.parkingSpaceLocationId], pathPos.m_segment, refPos, ref maxDist, true, out parkPos, out parkRot, out parkOffset);
break;
default:
#if DEBUG
Log.Error($"No alternative parking position stored for vehicle {vehicleID}! PathMode={driverExtInstance.pathMode}");
#endif
foundParkingSpace = CustomFindParkingSpace(this.m_info, homeID, refPos, searchDir, pathPos.m_segment, out parkPos, out parkRot, out parkOffset);
break;
}
}
}
if (!foundParkingSpace) {
foundParkingSpace = /*prohibitPocketCars ?*/
CustomFindParkingSpace(this.m_info, homeID, refPos, searchDir, pathPos.m_segment, out parkPos, out parkRot, out parkOffset) /*:
FindParkingSpace(homeID, refPos, searchDir, pathPos.m_segment, this.m_info.m_generatedInfo.m_size.x, this.m_info.m_generatedInfo.m_size.z, out parkPos, out parkRot, out parkOffset)*/;
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Found parking space? {foundParkingSpace}. parkPos={parkPos}, parkRot={parkRot}, parkOffset={parkOffset}");
#endif
}
// NON-STOCK CODE END
ushort parkedVehicleId = 0;
bool parkedCarCreated = foundParkingSpace && vehicleManager.CreateParkedVehicle(out parkedVehicleId, ref Singleton.instance.m_randomizer, this.m_info, parkPos, parkRot, driverCitizenId);
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parked car created? {parkedCarCreated}");
#endif
if (foundParkingSpace && parkedCarCreated) {
// we have reached a parking position
#if DEBUG
float sqrDist = (refPos - parkPos).sqrMagnitude;
if (fineDebug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Vehicle {vehicleID} succeeded in parking! CurrentPathMode={driverExtInstance.pathMode} sqrDist={sqrDist}");
#endif
driverCitizen.SetParkedVehicle(driverCitizenId, parkedVehicleId);
if (parkOffset >= 0f) {
segmentOffset = (byte)(parkOffset * 255f);
}
// NON-STOCK CODE START
if (prohibitPocketCars) {
if ((driverExtInstance.pathMode == ExtCitizenInstance.ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtCitizenInstance.ExtPathMode.DrivingToKnownParkPos) && targetBuildingId != 0) {
// decrease parking space demand of target building
ExtBuildingManager.Instance.ExtBuildings[targetBuildingId].ModifyParkingSpaceDemand(parkPos, GlobalConfig.Instance.ParkingAI.MinFoundParkPosParkingSpaceDemandDelta, GlobalConfig.Instance.ParkingAI.MaxFoundParkPosParkingSpaceDemandDelta);
}
//if (driverExtInstance.CurrentPathMode == ExtCitizenInstance.PathMode.DrivingToAltParkPos || driverExtInstance.CurrentPathMode == ExtCitizenInstance.PathMode.DrivingToKnownParkPos) {
// we have reached an (alternative) parking position and succeeded in finding a parking space
driverExtInstance.pathMode = ExtCitizenInstance.ExtPathMode.RequiresWalkingPathToTarget;
driverExtInstance.failedParkingAttempts = 0;
driverExtInstance.parkingSpaceLocation = ExtCitizenInstance.ExtParkingSpaceLocation.None;
driverExtInstance.parkingSpaceLocationId = 0;
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Vehicle {vehicleID} has reached an (alternative) parking position! CurrentPathMode={driverExtInstance.pathMode} position={parkPos}");
#endif
//}
}
} else if (prohibitPocketCars) {
// could not find parking space. vehicle would despawn.
if (
targetBuildingId != 0 &&
(Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None &&
(refPos - Singleton.instance.m_buildings.m_buffer[targetBuildingId].m_position).magnitude <= GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance
) {
// vehicle is at target and target is an outside connection: accept despawn
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Driver citizen instance {driverCitizenInstanceId} wants to park at an outside connection. Aborting.");
#endif
return true;
}
// Find parking space in the vicinity, redo path-finding to the parking space, park the vehicle and do citizen path-finding to the current target
if (!foundParkingSpace && (driverExtInstance.pathMode == ExtCitizenInstance.ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtCitizenInstance.ExtPathMode.DrivingToKnownParkPos) && targetBuildingId != 0) {
// increase parking space demand of target building
if (driverExtInstance.failedParkingAttempts > 1) {
ExtBuildingManager.Instance.ExtBuildings[targetBuildingId].AddParkingSpaceDemand(GlobalConfig.Instance.ParkingAI.FailedParkingSpaceDemandIncrement * (uint)(driverExtInstance.failedParkingAttempts - 1));
}
}
if (!foundParkingSpace) {
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking failed for vehicle {vehicleID}: Could not find parking space. ABORT.");
#endif
++driverExtInstance.failedParkingAttempts;
} else {
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking failed for vehicle {vehicleID}: Parked car could not be created. ABORT.");
#endif
driverExtInstance.failedParkingAttempts = GlobalConfig.Instance.ParkingAI.MaxParkingAttempts + 1;
}
driverExtInstance.pathMode = ExtCitizenInstance.ExtPathMode.ParkingFailed;
driverExtInstance.parkingPathStartPosition = pathPos;
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking failed for vehicle {vehicleID}! (flags: {vehicleData.m_flags}) pathPos segment={pathPos.m_segment}, lane={pathPos.m_lane}, offset={pathPos.m_offset}. Trying to find parking space in the vicinity. FailedParkingAttempts={driverExtInstance.failedParkingAttempts}, CurrentPathMode={driverExtInstance.pathMode} foundParkingSpace={foundParkingSpace}");
#endif
// invalidate paths of all passengers in order to force path recalculation
uint curUnitId = vehicleData.m_citizenUnits;
int numIter = 0;
while (curUnitId != 0u) {
uint nextUnit = citizenManager.m_units.m_buffer[curUnitId].m_nextUnit;
for (int i = 0; i < 5; i++) {
uint curCitizenId = citizenManager.m_units.m_buffer[curUnitId].GetCitizen(i);
if (curCitizenId != 0u) {
ushort citizenInstanceId = citizenManager.m_citizens.m_buffer[curCitizenId].m_instance;
if (citizenInstanceId != 0) {
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Releasing path for citizen instance {citizenInstanceId} sitting in vehicle {vehicleID} (was {citizenManager.m_instances.m_buffer[citizenInstanceId].m_path}).");
#endif
if (citizenInstanceId != driverCitizenInstanceId) {
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Resetting pathmode for passenger citizen instance {citizenInstanceId} sitting in vehicle {vehicleID} (was {ExtCitizenInstanceManager.Instance.ExtInstances[citizenInstanceId].pathMode}).");
#endif
ExtCitizenInstanceManager.Instance.ExtInstances[citizenInstanceId].Reset();
}
if (citizenManager.m_instances.m_buffer[citizenInstanceId].m_path != 0) {
Singleton.instance.ReleasePath(citizenManager.m_instances.m_buffer[citizenInstanceId].m_path);
citizenManager.m_instances.m_buffer[citizenInstanceId].m_path = 0u;
}
}
}
}
curUnitId = nextUnit;
if (++numIter > CitizenManager.MAX_UNIT_COUNT) {
CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);
break;
}
}
return false;
// NON-STOCK CODE END
}
} else {
segmentOffset = pathPos.m_offset;
}
// parking has succeeded
if (driverCitizenId != 0u) {
uint curCitizenUnitId = vehicleData.m_citizenUnits;
int numIter = 0;
while (curCitizenUnitId != 0u) {
uint nextUnit = citizenManager.m_units.m_buffer[curCitizenUnitId].m_nextUnit;
for (int j = 0; j < 5; j++) {
uint citId = citizenManager.m_units.m_buffer[curCitizenUnitId].GetCitizen(j);
if (citId != 0u) {
ushort citizenInstanceId = citizenManager.m_citizens.m_buffer[citId].m_instance;
if (citizenInstanceId != 0) {
// NON-STOCK CODE START
if (prohibitPocketCars) {
if (driverExtInstance.pathMode == ExtCitizenInstance.ExtPathMode.RequiresWalkingPathToTarget) {
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking succeeded: Doing nothing for citizen instance {citizenInstanceId}! path: {citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_path}");
#endif
ExtCitizenInstanceManager.Instance.ExtInstances[citizenInstanceId].pathMode = ExtCitizenInstance.ExtPathMode.RequiresWalkingPathToTarget;
continue;
}
}
// NON-STOCK CODE END
if (pathManager.AddPathReference(nextPath)) {
if (citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_path != 0u) {
pathManager.ReleasePath(citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_path);
}
citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_path = nextPath;
citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_pathPositionIndex = (byte)nextPositionIndex;
citizenManager.m_instances.m_buffer[(int)citizenInstanceId].m_lastPathOffset = segmentOffset;
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking succeeded (default): Setting path of citizen instance {citizenInstanceId} to {nextPath}!");
#endif
}
}
}
}
curCitizenUnitId = nextUnit;
if (++numIter > 524288) {
CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);
break;
}
}
}
if (prohibitPocketCars) {
if (driverExtInstance.pathMode == ExtCitizenInstance.ExtPathMode.RequiresWalkingPathToTarget) {
#if DEBUG
if (debug)
Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parking succeeded (alternative parking spot): Citizen instance {driverExtInstance} has to walk for the remaining path!");
#endif
/*driverExtInstance.CurrentPathMode = ExtCitizenInstance.PathMode.CalculatingWalkingPathToTarget;
if (debug)
Log._Debug($"Setting CurrentPathMode of vehicle {vehicleID} to {driverExtInstance.CurrentPathMode}");*/
}
}
return true;
}
private static bool CustomFindParkingSpace(VehicleInfo vehicleInfo, ushort homeID, Vector3 refPos, Vector3 searchDir, ushort segmentId, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) {
#if DEBUG
bool debug = GlobalConfig.Instance.Debug.Switches[22];
#endif
float searchRadius = Options.prohibitPocketCars ? 32f : 16f;
uint chanceOfParkingOffRoad = 3u;
Vector3 searchMagnitude = refPos + searchDir * 16f;
if (GlobalConfig.RushHourParkingSearchRadius != null) {
searchRadius = (int)GlobalConfig.RushHourParkingSearchRadius;
#if DEBUG
if (debug)
Log._Debug($"RushHour's Improved Parking AI is active. searchRadius={searchRadius}");
#endif
chanceOfParkingOffRoad = 80u;
} else {
#if DEBUG
if (debug)
Log._Debug("RushHour's Improved Parking AI is NOT active.");
#endif
}
/*if (Options.prohibitPocketCars) {
//searchRadius = Mathf.Max(32f, searchRadius);
}*/
Vector3 refPos2 = refPos + searchDir * 16f;
if (Singleton.instance.m_randomizer.Int32(chanceOfParkingOffRoad) == 0) {
float width = vehicleInfo.m_generatedInfo.m_size.x;
float length = vehicleInfo.m_generatedInfo.m_size.z;
if (FindParkingSpaceRoadSide(0, segmentId, refPos, width - 0.2f, length, out parkPos, out parkRot, out parkOffset)) {
if (Options.parkingRestrictionsEnabled) {
Vector3 innerParkPos;
uint laneId;
int laneIndex;
float laneOffset;
if (Singleton.instance.m_segments.m_buffer[segmentId].GetClosestLanePosition(refPos, NetInfo.LaneType.Parking, VehicleInfo.VehicleType.Car, out innerParkPos, out laneId, out laneIndex, out laneOffset)) {
#if BENCHMARK
using (var bm = new Benchmark(null, "IsParkingAllowed.1")) {
#endif
if (ParkingRestrictionsManager.Instance.IsParkingAllowed(segmentId, Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex].m_finalDirection)) {
return true;
}
#if BENCHMARK
}
#endif
}
} else {
return true;
}
}
#if BENCHMARK
using (var bm = new Benchmark(null, "FindParkingSpaceBuilding.1")) {
#endif
if (AdvancedParkingManager.Instance.FindParkingSpaceBuilding(vehicleInfo, homeID, 0, segmentId, refPos2, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, searchRadius, out parkPos, out parkRot, out parkOffset)) {
return true;
}
#if BENCHMARK
}
#endif
} else {
#if BENCHMARK
using (var bm = new Benchmark(null, "FindParkingSpaceBuilding.2")) {
#endif
if (AdvancedParkingManager.Instance.FindParkingSpaceBuilding(vehicleInfo, homeID, 0, segmentId, refPos2, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, searchRadius, out parkPos, out parkRot, out parkOffset)) {
return true;
}
#if BENCHMARK
}
#endif
float width = vehicleInfo.m_generatedInfo.m_size.x;
float length = vehicleInfo.m_generatedInfo.m_size.z;
if (FindParkingSpaceRoadSide(0, segmentId, refPos, width - 0.2f, length, out parkPos, out parkRot, out parkOffset)) {
if (Options.parkingRestrictionsEnabled) {
Vector3 innerParkPos;
uint laneId;
int laneIndex;
float laneOffset;
if (Singleton.instance.m_segments.m_buffer[segmentId].GetClosestLanePosition(refPos, NetInfo.LaneType.Parking, VehicleInfo.VehicleType.Car, out innerParkPos, out laneId, out laneIndex, out laneOffset)) {
#if BENCHMARK
using (var bm = new Benchmark(null, "IsParkingAllowed.2")) {
#endif
if (ParkingRestrictionsManager.Instance.IsParkingAllowed(segmentId, Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex].m_finalDirection)) {
return true;
}
#if BENCHMARK
}
#endif
}
} else {
return true;
}
}
}
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal static bool FindParkingSpaceRoadSide(ushort ignoreParked, ushort requireSegment, Vector3 refPos, float width, float length, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) {
Log.Error("FindParkingSpaceRoadSide is not overridden!");
parkPos = Vector3.zero;
parkRot = Quaternion.identity;
parkOffset = 0f;
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal static bool FindParkingSpace(bool isElectric, ushort homeID, Vector3 refPos, Vector3 searchDir, ushort segment, float width, float length, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) {
Log.Error("FindParkingSpace is not overridden!");
parkPos = Vector3.zero;
parkRot = Quaternion.identity;
parkOffset = 0f;
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal static bool FindParkingSpaceProp(bool isElectric, ushort ignoreParked, PropInfo info, Vector3 position, float angle, bool fixedHeight, Vector3 refPos, float width, float length, ref float maxDistance, ref Vector3 parkPos, ref Quaternion parkRot) {
Log.Error("FindParkingSpaceProp is not overridden!");
return false;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomPoliceCarAI.cs
================================================
using ColossalFramework;
using CSUtil.Commons.Benchmark;
using System;
using System.Collections.Generic;
using System.Text;
using TrafficManager.Custom.PathFinding;
using TrafficManager.Geometry;
using TrafficManager.Manager;
using TrafficManager.Manager.Impl;
using TrafficManager.Traffic;
using TrafficManager.Traffic.Data;
using UnityEngine;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
class CustomPoliceCarAI : CarAI {
public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) {
#if DEBUG
//Log._Debug($"CustomPoliceCarAI.CustomStartPathFind called for vehicle {vehicleID}");
#endif
ExtVehicleType vehicleType = ExtVehicleType.None;
#if BENCHMARK
using (var bm = new Benchmark(null, "OnStartPathFind")) {
#endif
vehicleType = VehicleStateManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0 ? ExtVehicleType.Emergency : ExtVehicleType.Service);
#if BENCHMARK
}
#endif
VehicleInfo info = this.m_info;
bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0;
PathUnit.Position startPosA;
PathUnit.Position startPosB;
float startDistSqrA;
float startDistSqrB;
PathUnit.Position endPosA;
PathUnit.Position endPosB;
float endDistSqrA;
float endDistSqrB;
if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startDistSqrA, out startDistSqrB) &&
CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, undergroundTarget, false, 32f, out endPosA, out endPosB, out endDistSqrA, out endDistSqrB)) {
if (!startBothWays || startDistSqrA < 10f) {
startPosB = default(PathUnit.Position);
}
if (!endBothWays || endDistSqrA < 10f) {
endPosB = default(PathUnit.Position);
}
uint path;
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = ExtCitizenInstance.ExtPathType.None;
args.extVehicleType = vehicleType;
args.vehicleId = vehicleID;
args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
args.buildIndex = Singleton.instance.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = startPosB;
args.endPosA = endPosA;
args.endPosB = endPosB;
args.vehiclePosition = default(PathUnit.Position);
args.laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle;
args.vehicleTypes = info.m_vehicleType;
args.maxLength = 20000f;
args.isHeavyVehicle = this.IsHeavyVehicle();
args.hasCombustionEngine = this.CombustionEngine();
args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData);
args.ignoreFlooded = false;
args.ignoreCosts = false;
args.randomParking = false;
args.stablePath = false;
args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
//Log._Debug($"CustomPoliceCarAI.CustomStartPathFind: Vehicle {vehicleID}, type {vehicleType}");
if (CustomPathManager._instance.CreatePath(out path, ref Singleton.instance.m_randomizer, args)) {
// NON-STOCK CODE END
if (vehicleData.m_path != 0u) {
Singleton.instance.ReleasePath(vehicleData.m_path);
}
vehicleData.m_path = path;
vehicleData.m_flags |= Vehicle.Flags.WaitingPath;
return true;
}
}
return false;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomResidentAI.cs
================================================
using ColossalFramework;
using ColossalFramework.Globalization;
using ColossalFramework.Math;
using CSUtil.Commons;
using CSUtil.Commons.Benchmark;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using TrafficManager.Manager;
using TrafficManager.Manager.Impl;
using TrafficManager.State;
using TrafficManager.Traffic;
using TrafficManager.UI;
using UnityEngine;
using static TrafficManager.Traffic.Data.ExtCitizen;
using static TrafficManager.Traffic.Data.ExtCitizenInstance;
namespace TrafficManager.Custom.AI {
public class CustomResidentAI : ResidentAI {
public string CustomGetLocalizedStatus(ushort instanceID, ref CitizenInstance data, out InstanceID target) {
bool addCustomStatus = false;
String ret = GetStockLocalizedStatus(instanceID, ref data, out addCustomStatus, out target);
// NON-STOCK CODE START
#if BENCHMARK
using (var bm = new Benchmark(null, "EnrichLocalizedCitizenStatus")) {
#endif
if (Options.prohibitPocketCars && addCustomStatus) {
ret = AdvancedParkingManager.Instance.EnrichLocalizedCitizenStatus(ret, ref ExtCitizenInstanceManager.Instance.ExtInstances[instanceID], ref ExtCitizenManager.Instance.ExtCitizens[data.m_citizen]);
}
#if BENCHMARK
}
#endif
// NON-STOCK CODE END
return ret;
}
private String GetStockLocalizedStatus(ushort instanceID, ref CitizenInstance data, out bool addCustomStatus, out InstanceID target) {
if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != CitizenInstance.Flags.None) {
target = InstanceID.Empty;
addCustomStatus = false;
return Locale.Get("CITIZEN_STATUS_CONFUSED");
}
CitizenManager citMan = Singleton.instance;
uint citizenId = data.m_citizen;
bool isStudent = false;
ushort homeId = 0;
ushort workId = 0;
ushort vehicleId = 0;
if (citizenId != 0u) {
homeId = citMan.m_citizens.m_buffer[citizenId].m_homeBuilding;
workId = citMan.m_citizens.m_buffer[citizenId].m_workBuilding;
vehicleId = citMan.m_citizens.m_buffer[citizenId].m_vehicle;
isStudent = ((citMan.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Student) != Citizen.Flags.None);
}
ushort targetBuilding = data.m_targetBuilding;
if (targetBuilding == 0) {
target = InstanceID.Empty;
addCustomStatus = false;
return Locale.Get("CITIZEN_STATUS_CONFUSED");
}
if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != CitizenInstance.Flags.None) {
if (vehicleId != 0) {
VehicleManager vehManager = Singleton.instance;
VehicleInfo vehicleInfo = vehManager.m_vehicles.m_buffer[vehicleId].Info;
if (vehicleInfo.m_class.m_service == ItemClass.Service.Residential && vehicleInfo.m_vehicleType != VehicleInfo.VehicleType.Bicycle) {
if (vehicleInfo.m_vehicleAI.GetOwnerID(vehicleId, ref vehManager.m_vehicles.m_buffer[vehicleId]).Citizen == citizenId) {
target = InstanceID.Empty;
target.NetNode = targetBuilding;
addCustomStatus = true;
return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_DRIVINGTO");
}
} else if (vehicleInfo.m_class.m_service == ItemClass.Service.PublicTransport || vehicleInfo.m_class.m_service == ItemClass.Service.Disaster) {
ushort transportLine = Singleton.instance.m_nodes.m_buffer[targetBuilding].m_transportLine;
if ((data.m_flags & CitizenInstance.Flags.WaitingTaxi) != 0) {
target = InstanceID.Empty;
addCustomStatus = true;
return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_WAITING_TAXI");
}
if (vehManager.m_vehicles.m_buffer[vehicleId].m_transportLine != transportLine) {
target = InstanceID.Empty;
target.NetNode = targetBuilding;
addCustomStatus = true;
return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO");
}
}
}
if ((data.m_flags & CitizenInstance.Flags.OnTour) != 0) {
target = InstanceID.Empty;
target.NetNode = targetBuilding;
addCustomStatus = true;
return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING");
}
target = InstanceID.Empty;
target.NetNode = targetBuilding;
addCustomStatus = true;
return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_GOINGTO");
}
bool isOutsideConnection = (Singleton.instance.m_buildings.m_buffer[(int)targetBuilding].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None;
bool hangsAround = data.m_path == 0u && (data.m_flags & CitizenInstance.Flags.HangAround) != CitizenInstance.Flags.None;
if (vehicleId != 0) {
VehicleManager vehicleMan = Singleton.instance;
VehicleInfo vehicleInfo = vehicleMan.m_vehicles.m_buffer[(int)vehicleId].Info;
if (vehicleInfo.m_class.m_service == ItemClass.Service.Residential && vehicleInfo.m_vehicleType != VehicleInfo.VehicleType.Bicycle) {
if (vehicleInfo.m_vehicleAI.GetOwnerID(vehicleId, ref vehicleMan.m_vehicles.m_buffer[(int)vehicleId]).Citizen == citizenId) {
if (isOutsideConnection) {
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_DRIVINGTO_OUTSIDE");
}
if (targetBuilding == homeId) {
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_DRIVINGTO_HOME");
} else if (targetBuilding == workId) {
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get((!isStudent) ? "CITIZEN_STATUS_DRIVINGTO_WORK" : "CITIZEN_STATUS_DRIVINGTO_SCHOOL");
} else {
target = InstanceID.Empty;
target.Building = targetBuilding;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_DRIVINGTO");
}
}
} else if (vehicleInfo.m_class.m_service == ItemClass.Service.PublicTransport || vehicleInfo.m_class.m_service == ItemClass.Service.Disaster) {
if ((data.m_flags & CitizenInstance.Flags.WaitingTaxi) != CitizenInstance.Flags.None) {
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_WAITING_TAXI");
}
if (isOutsideConnection) {
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_OUTSIDE");
}
if (targetBuilding == homeId) {
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_HOME");
}
if (targetBuilding == workId) {
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get((!isStudent) ? "CITIZEN_STATUS_TRAVELLINGTO_WORK" : "CITIZEN_STATUS_TRAVELLINGTO_SCHOOL");
}
target = InstanceID.Empty;
target.Building = targetBuilding;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO");
}
}
if (isOutsideConnection) {
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_GOINGTO_OUTSIDE");
}
if (targetBuilding == homeId) {
if (hangsAround) {
target = InstanceID.Empty;
addCustomStatus = false;
return Locale.Get("CITIZEN_STATUS_AT_HOME");
}
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_GOINGTO_HOME");
} else if (targetBuilding == workId) {
if (hangsAround) {
target = InstanceID.Empty;
addCustomStatus = false;
return Locale.Get((!isStudent) ? "CITIZEN_STATUS_AT_WORK" : "CITIZEN_STATUS_AT_SCHOOL");
}
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get((!isStudent) ? "CITIZEN_STATUS_GOINGTO_WORK" : "CITIZEN_STATUS_GOINGTO_SCHOOL");
} else {
if (hangsAround) {
target = InstanceID.Empty;
target.Building = targetBuilding;
addCustomStatus = false;
return Locale.Get("CITIZEN_STATUS_VISITING");
}
target = InstanceID.Empty;
target.Building = targetBuilding;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_GOINGTO");
}
}
public VehicleInfo CustomGetVehicleInfo(ushort instanceID, ref CitizenInstance citizenData, bool forceCar, out VehicleInfo trailer) {
#if DEBUG
bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceID) &&
(GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenData.m_citizen) &&
(GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == citizenData.m_sourceBuilding) &&
(GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == citizenData.m_targetBuilding)
;
bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug;
bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug;
#endif
trailer = null;
if (citizenData.m_citizen == 0u) {
return null;
}
// NON-STOCK CODE START
bool forceTaxi = false;
#if BENCHMARK
using (var bm = new Benchmark(null, "forceTaxi")) {
#endif
if (Options.prohibitPocketCars) {
if (ExtCitizenInstanceManager.Instance.ExtInstances[instanceID].pathMode == ExtPathMode.TaxiToTarget) {
forceTaxi = true;
}
}
#if BENCHMARK
}
#endif
// NON-STOCK CODE END
Citizen.AgeGroup ageGroup = CustomCitizenAI.GetAgeGroup(citizenData.Info.m_agePhase);
int carProb;
int bikeProb;
int taxiProb;
// NON-STOCK CODE START
if (forceTaxi) {
carProb = 0;
bikeProb = 0;
taxiProb = 100;
} else
// NON-STOCK CODE END
if (forceCar || (citizenData.m_flags & CitizenInstance.Flags.BorrowCar) != CitizenInstance.Flags.None) {
carProb = 100;
bikeProb = 0;
taxiProb = 0;
} else {
carProb = GetCarProbability(instanceID, ref citizenData, ageGroup);
bikeProb = GetBikeProbability(instanceID, ref citizenData, ageGroup);
taxiProb = GetTaxiProbability(instanceID, ref citizenData, ageGroup);
}
Randomizer randomizer = new Randomizer(citizenData.m_citizen);
bool useCar = randomizer.Int32(100u) < carProb;
bool useBike = !useCar && randomizer.Int32(100u) < bikeProb;
bool useTaxi = !useCar && !useBike && randomizer.Int32(100u) < taxiProb;
bool useElectricCar = false;
if (useCar) {
int electricProb = GetElectricCarProbability(instanceID, ref citizenData, this.m_info.m_agePhase);
useElectricCar = randomizer.Int32(100u) < electricProb;
}
ItemClass.Service service = ItemClass.Service.Residential;
ItemClass.SubService subService = useElectricCar ? ItemClass.SubService.ResidentialLowEco : ItemClass.SubService.ResidentialLow;
if (useTaxi) {
service = ItemClass.Service.PublicTransport;
subService = ItemClass.SubService.PublicTransportTaxi;
}
// NON-STOCK CODE START
VehicleInfo carInfo = null;
#if BENCHMARK
using (var bm = new Benchmark(null, "find-parked-vehicle")) {
#endif
if (Options.prohibitPocketCars && useCar) {
ushort parkedVehicleId = Singleton.instance.m_citizens.m_buffer[citizenData.m_citizen].m_parkedVehicle;
if (parkedVehicleId != 0) {
#if DEBUG
if (debug)
Log._Debug($"CustomResidentAI.CustomGetVehicleInfo({instanceID}): Citizen instance {instanceID} owns a parked vehicle {parkedVehicleId}. Reusing vehicle info.");
#endif
carInfo = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].Info;
}
}
#if BENCHMARK
}
#endif
if (carInfo == null && (useCar || useTaxi)) {
// NON-STOCK CODE END
carInfo = Singleton.instance.GetRandomVehicleInfo(ref randomizer, service, subService, ItemClass.Level.Level1);
}
if (useBike) {
VehicleInfo bikeInfo = Singleton.instance.GetRandomVehicleInfo(ref randomizer, ItemClass.Service.Residential, ItemClass.SubService.ResidentialHigh, (ageGroup != Citizen.AgeGroup.Child) ? ItemClass.Level.Level2 : ItemClass.Level.Level1);
if (bikeInfo != null) {
return bikeInfo;
}
}
if ((useCar || useTaxi) && carInfo != null) {
return carInfo;
}
return null;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int GetTaxiProbability(ushort instanceID, ref CitizenInstance citizenData, Citizen.AgeGroup ageGroup) {
Log.Error("CustomResidentAI.GetTaxiProbability called!");
return 20;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int GetBikeProbability(ushort instanceID, ref CitizenInstance citizenData, Citizen.AgeGroup ageGroup) {
Log.Error("CustomResidentAI.GetBikeProbability called!");
return 20;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int GetCarProbability(ushort instanceID, ref CitizenInstance citizenData, Citizen.AgeGroup ageGroup) {
Log.Error("CustomResidentAI.GetCarProbability called!");
return 20;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int GetElectricCarProbability(ushort instanceID, ref CitizenInstance citizenData, Citizen.AgePhase agePhase) {
Log.Error("CustomResidentAI.GetElectricCarProbability called!");
return 20;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomRoadAI.cs
================================================
using System;
using System.Collections.Generic;
using ColossalFramework;
using TrafficManager.TrafficLight;
using TrafficManager.Geometry;
using UnityEngine;
using ColossalFramework.Math;
using System.Threading;
using TrafficManager.UI;
using TrafficManager.State;
using TrafficManager.Manager;
using TrafficManager.UI.SubTools;
using CSUtil.Commons;
using TrafficManager.Manager.Impl;
using TrafficManager.Geometry.Impl;
using CSUtil.Commons.Benchmark;
using TrafficManager.TrafficLight.Data;
namespace TrafficManager.Custom.AI {
public class CustomRoadAI : RoadBaseAI {
private static ushort lastSimulatedSegmentId = 0;
private static byte trafficMeasurementMod = 0;
public void CustomNodeSimulationStep(ushort nodeId, ref NetNode data) {
if (Options.timedLightsEnabled) {
try {
//tlsMan.SimulationStep();
bool callStockSimStep = true;
#if BENCHMARK
using (var bm = new Benchmark(null, "callStockSimStep")) {
#endif
callStockSimStep = !Options.timedLightsEnabled || !TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId].IsSimulationRunning();
#if BENCHMARK
}
#endif
if (callStockSimStep) {
OriginalSimulationStep(nodeId, ref data);
}
} catch (Exception e) {
Log.Warning($"CustomNodeSimulationStep: An error occurred: {e.ToString()}");
}
} else {
OriginalSimulationStep(nodeId, ref data);
}
}
public void CustomSegmentSimulationStep(ushort segmentID, ref NetSegment data) {
//try {
// TODO check if this is required
uint curLaneId = data.m_lanes;
int numLanes = data.Info.m_lanes.Length;
uint laneIndex = 0;
while (laneIndex < numLanes && curLaneId != 0u) {
Flags.applyLaneArrowFlags(curLaneId);
laneIndex++;
curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane;
}
//SegmentEndManager.Instance.SegmentSimulationStep(segmentID);
/*} catch (Exception e) {
Log.Error($"Error occured while housekeeping segment {segmentID}: " + e.ToString());
}*/
#if BENCHMARK
using (var bm = new Benchmark(null, "traffic-measurement")) {
#endif
if (!Options.isStockLaneChangerUsed()) {
if (segmentID < lastSimulatedSegmentId) {
// segment simulation restart
++trafficMeasurementMod;
if (trafficMeasurementMod >= 4)
trafficMeasurementMod = 0;
}
lastSimulatedSegmentId = segmentID;
bool doTrafficMeasurement = true;
if (Options.simAccuracy == 1 || Options.simAccuracy == 2) {
doTrafficMeasurement = (segmentID & 1) == trafficMeasurementMod;
} else if (Options.simAccuracy >= 3) {
doTrafficMeasurement = (segmentID & 3) == trafficMeasurementMod;
}
if (doTrafficMeasurement) {
//try {
TrafficMeasurementManager.Instance.SimulationStep(segmentID, ref data);
/*} catch (Exception e) {
Log.Error("Error occured while calculating lane traffic density: " + e.ToString());
}*/
}
}
#if BENCHMARK
}
#endif
OriginalSimulationStep(segmentID, ref data);
}
public static void GetTrafficLightState(
#if DEBUG
ushort vehicleId, ref Vehicle vehicleData,
#endif
ushort nodeId, ushort fromSegmentId, byte fromLaneIndex, ushort toSegmentId, ref NetSegment segmentData, uint frame, out RoadBaseAI.TrafficLightState vehicleLightState, out RoadBaseAI.TrafficLightState pedestrianLightState) {
bool callStockMethod = true;
#if BENCHMARK
using (var bm = new Benchmark(null, "callStockMethod")) {
#endif
callStockMethod = !Options.timedLightsEnabled || !TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId].IsSimulationRunning();
#if BENCHMARK
}
#endif
if (callStockMethod) {
RoadBaseAI.GetTrafficLightState(nodeId, ref segmentData, frame, out vehicleLightState, out pedestrianLightState);
} else {
#if BENCHMARK
using (var bm = new Benchmark(null, "GetCustomTrafficLightState")) {
#endif
GetCustomTrafficLightState(
#if DEBUG
vehicleId, ref vehicleData,
#endif
nodeId, fromSegmentId, fromLaneIndex, toSegmentId, out vehicleLightState, out pedestrianLightState, ref TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId]);
#if BENCHMARK
}
#endif
}
}
public static void GetTrafficLightState(
#if DEBUG
ushort vehicleId, ref Vehicle vehicleData,
#endif
ushort nodeId, ushort fromSegmentId, byte fromLaneIndex, ushort toSegmentId, ref NetSegment segmentData, uint frame, out RoadBaseAI.TrafficLightState vehicleLightState, out RoadBaseAI.TrafficLightState pedestrianLightState, out bool vehicles, out bool pedestrians) {
bool callStockMethod = true;
#if BENCHMARK
using (var bm = new Benchmark(null, "callStockMethod")) {
#endif
callStockMethod = !Options.timedLightsEnabled || !TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId].IsSimulationRunning();
#if BENCHMARK
}
#endif
if (callStockMethod) {
RoadBaseAI.GetTrafficLightState(nodeId, ref segmentData, frame, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians);
} else {
#if BENCHMARK
using (var bm = new Benchmark(null, "GetCustomTrafficLightState")) {
#endif
GetCustomTrafficLightState(
#if DEBUG
vehicleId, ref vehicleData,
#endif
nodeId, fromSegmentId, fromLaneIndex, toSegmentId, out vehicleLightState, out pedestrianLightState, ref TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId]);
#if BENCHMARK
}
#endif
vehicles = false;
pedestrians = false;
}
}
// TODO refactor to manager
private static void GetCustomTrafficLightState(
#if DEBUG
ushort vehicleId, ref Vehicle vehicleData,
#endif
ushort nodeId, ushort fromSegmentId, byte fromLaneIndex, ushort toSegmentId, out RoadBaseAI.TrafficLightState vehicleLightState, out RoadBaseAI.TrafficLightState pedestrianLightState, ref TrafficLightSimulation nodeSim) {
// get responsible traffic light
//Log._Debug($"GetTrafficLightState: Getting custom light for vehicle {vehicleId} @ node {nodeId}, segment {fromSegmentId}, lane {fromLaneIndex}.");
SegmentGeometry geometry = SegmentGeometry.Get(fromSegmentId);
if (geometry == null) {
Log.Error($"GetTrafficLightState: No geometry information @ node {nodeId}, segment {fromSegmentId}.");
vehicleLightState = TrafficLightState.Green;
pedestrianLightState = TrafficLightState.Green;
return;
}
// determine node position at `fromSegment` (start/end)
bool isStartNode = geometry.StartNodeId() == nodeId;
ICustomSegmentLights lights = CustomSegmentLightsManager.Instance.GetSegmentLights(fromSegmentId, isStartNode, false);
if (lights != null) {
// get traffic lights state for pedestrians
pedestrianLightState = (lights.PedestrianLightState != null) ? (RoadBaseAI.TrafficLightState)lights.PedestrianLightState : RoadBaseAI.TrafficLightState.Green;
} else {
pedestrianLightState = TrafficLightState.Green;
Log._Debug($"GetTrafficLightState: No pedestrian light @ node {nodeId}, segment {fromSegmentId} found.");
}
ICustomSegmentLight light = lights == null ? null : lights.GetCustomLight(fromLaneIndex);
if (lights == null || light == null) {
//Log.Warning($"GetTrafficLightState: No custom light for vehicle {vehicleId} @ node {nodeId}, segment {fromSegmentId}, lane {fromLaneIndex} found. lights null? {lights == null} light null? {light == null}");
vehicleLightState = RoadBaseAI.TrafficLightState.Green;
return;
}
// get traffic light state from responsible traffic light
if (toSegmentId == fromSegmentId) {
vehicleLightState = Constants.ServiceFactory.SimulationService.LeftHandDrive ? light.LightRight : light.LightLeft;
} else if (geometry.IsLeftSegment(toSegmentId, isStartNode)) {
vehicleLightState = light.LightLeft;
} else if (geometry.IsRightSegment(toSegmentId, isStartNode)) {
vehicleLightState = light.LightRight;
} else {
vehicleLightState = light.LightMain;
}
#if DEBUG
//Log._Debug($"GetTrafficLightState: Getting light for vehicle {vehicleId} @ node {nodeId}, segment {fromSegmentId}, lane {fromLaneIndex}. vehicleLightState={vehicleLightState}, pedestrianLightState={pedestrianLightState}");
#endif
}
public static void CustomSetTrafficLightState(ushort nodeID, ref NetSegment segmentData, uint frame, RoadBaseAI.TrafficLightState vehicleLightState, RoadBaseAI.TrafficLightState pedestrianLightState, bool vehicles, bool pedestrians) {
OriginalSetTrafficLightState(false, nodeID, ref segmentData, frame, vehicleLightState, pedestrianLightState, vehicles, pedestrians);
}
public static void OriginalSetTrafficLightState(bool customCall, ushort nodeID, ref NetSegment segmentData, uint frame, RoadBaseAI.TrafficLightState vehicleLightState, RoadBaseAI.TrafficLightState pedestrianLightState, bool vehicles, bool pedestrians) {
// NON-STOCK CODE START
bool callStockCode = true;
#if BENCHMARK
using (var bm = new Benchmark(null, "callStockCode")) {
#endif
callStockCode = customCall || !Options.timedLightsEnabled || !TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeID].IsSimulationRunning();
#if BENCHMARK
}
#endif
if (callStockCode) {
/// NON-STOCK CODE END ///
int num = (int)pedestrianLightState << 2 | (int)vehicleLightState;
if (segmentData.m_startNode == nodeID) {
if ((frame >> 8 & 1u) == 0u) {
segmentData.m_trafficLightState0 = (byte)((int)(segmentData.m_trafficLightState0 & 240) | num);
} else {
segmentData.m_trafficLightState1 = (byte)((int)(segmentData.m_trafficLightState1 & 240) | num);
}
if (vehicles) {
segmentData.m_flags |= NetSegment.Flags.TrafficStart;
} else {
segmentData.m_flags &= ~NetSegment.Flags.TrafficStart;
}
if (pedestrians) {
segmentData.m_flags |= NetSegment.Flags.CrossingStart;
} else {
segmentData.m_flags &= ~NetSegment.Flags.CrossingStart;
}
} else {
if ((frame >> 8 & 1u) == 0u) {
segmentData.m_trafficLightState0 = (byte)((int)(segmentData.m_trafficLightState0 & 15) | num << 4);
} else {
segmentData.m_trafficLightState1 = (byte)((int)(segmentData.m_trafficLightState1 & 15) | num << 4);
}
if (vehicles) {
segmentData.m_flags |= NetSegment.Flags.TrafficEnd;
} else {
segmentData.m_flags &= ~NetSegment.Flags.TrafficEnd;
}
if (pedestrians) {
segmentData.m_flags |= NetSegment.Flags.CrossingEnd;
} else {
segmentData.m_flags &= ~NetSegment.Flags.CrossingEnd;
}
}
} // NON-STOCK CODE
}
public void CustomClickNodeButton(ushort nodeID, ref NetNode data, int index) {
if ((data.m_flags & NetNode.Flags.Junction) != NetNode.Flags.None &&
Singleton.instance.CurrentMode == InfoManager.InfoMode.TrafficRoutes &&
Singleton.instance.CurrentSubMode == InfoManager.SubInfoMode.WaterPower) {
if (index == -1) {
/*data.m_flags ^= NetNode.Flags.TrafficLights;
data.m_flags |= NetNode.Flags.CustomTrafficLights;*/
// NON-STOCK CODE START
ToggleTrafficLightsTool toggleTool = (ToggleTrafficLightsTool)UIBase.GetTrafficManagerTool(true).GetSubTool(ToolMode.SwitchTrafficLight);
toggleTool.ToggleTrafficLight(nodeID, ref data, false);
// NON-STOCK CODE END
this.UpdateNodeFlags(nodeID, ref data);
Singleton.instance.m_yieldLights.Disable();
} else if (index >= 1 && index <= 8 && (data.m_flags & (NetNode.Flags.TrafficLights | NetNode.Flags.OneWayIn)) == NetNode.Flags.None) {
ushort segmentId = data.GetSegment(index - 1);
if (segmentId != 0) {
NetManager netManager = Singleton.instance;
NetInfo info = netManager.m_segments.m_buffer[(int)segmentId].Info;
if ((info.m_vehicleTypes & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Tram)) != VehicleInfo.VehicleType.None) {
bool flag = netManager.m_segments.m_buffer[(int)segmentId].m_startNode == nodeID;
NetSegment.Flags flags = (!flag) ? NetSegment.Flags.YieldEnd : NetSegment.Flags.YieldStart;
netManager.m_segments.m_buffer[segmentId].m_flags ^= flags;
netManager.m_segments.m_buffer[(int)segmentId].UpdateLanes(segmentId, true);
Singleton.instance.m_yieldLights.Disable();
}
}
}
}
}
public void CustomUpdateLanes(ushort segmentID, ref NetSegment data, bool loading) {
// stock code start
NetManager instance = Singleton.instance;
bool flag = Singleton.instance.m_metaData.m_invertTraffic == SimulationMetaData.MetaBool.True;
Vector3 vector;
Vector3 a;
bool smoothStart;
data.CalculateCorner(segmentID, true, true, true, out vector, out a, out smoothStart);
Vector3 a2;
Vector3 b;
bool smoothEnd;
data.CalculateCorner(segmentID, true, false, true, out a2, out b, out smoothEnd);
Vector3 a3;
Vector3 b2;
data.CalculateCorner(segmentID, true, true, false, out a3, out b2, out smoothStart);
Vector3 vector2;
Vector3 a4;
data.CalculateCorner(segmentID, true, false, false, out vector2, out a4, out smoothEnd);
if ((data.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) {
data.m_cornerAngleStart = (byte)(Mathf.RoundToInt(Mathf.Atan2(a3.z - vector.z, a3.x - vector.x) * 40.7436638f) & 255);
data.m_cornerAngleEnd = (byte)(Mathf.RoundToInt(Mathf.Atan2(a2.z - vector2.z, a2.x - vector2.x) * 40.7436638f) & 255);
} else {
data.m_cornerAngleStart = (byte)(Mathf.RoundToInt(Mathf.Atan2(vector.z - a3.z, vector.x - a3.x) * 40.7436638f) & 255);
data.m_cornerAngleEnd = (byte)(Mathf.RoundToInt(Mathf.Atan2(vector2.z - a2.z, vector2.x - a2.x) * 40.7436638f) & 255);
}
int num = 0;
int num2 = 0;
int num3 = 0;
int num4 = 0;
int num5 = 0;
int num6 = 0;
bool flag2 = false;
bool flag3 = false;
instance.m_nodes.m_buffer[(int)data.m_endNode].CountLanes(data.m_endNode, segmentID, NetInfo.Direction.Forward, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, -data.m_endDirection, ref num, ref num2, ref num3, ref num4, ref num5, ref num6);
if ((instance.m_nodes.m_buffer[(int)data.m_endNode].m_flags & (NetNode.Flags.End | NetNode.Flags.Middle | NetNode.Flags.Bend | NetNode.Flags.Outside)) != NetNode.Flags.None) {
if (num + num2 + num3 == 0) {
flag3 = true;
} else {
flag2 = true;
}
}
int num7 = 0;
int num8 = 0;
int num9 = 0;
int num10 = 0;
int num11 = 0;
int num12 = 0;
bool flag4 = false;
bool flag5 = false;
instance.m_nodes.m_buffer[(int)data.m_startNode].CountLanes(data.m_startNode, segmentID, NetInfo.Direction.Forward, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, -data.m_startDirection, ref num7, ref num8, ref num9, ref num10, ref num11, ref num12);
if ((instance.m_nodes.m_buffer[(int)data.m_startNode].m_flags & (NetNode.Flags.End | NetNode.Flags.Middle | NetNode.Flags.Bend | NetNode.Flags.Outside)) != NetNode.Flags.None) {
if (num7 + num8 + num9 == 0) {
flag5 = true;
} else {
flag4 = true;
}
}
NetLane.Flags flags = NetLane.Flags.None;
if (num4 != 0 && num == 0) {
flags |= (((data.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? NetLane.Flags.EndOneWayLeft : NetLane.Flags.StartOneWayLeft);
}
if (num6 != 0 && num3 == 0) {
flags |= (((data.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? NetLane.Flags.EndOneWayRight : NetLane.Flags.StartOneWayRight);
}
if (num10 != 0 && num7 == 0) {
flags |= (((data.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? NetLane.Flags.StartOneWayLeft : NetLane.Flags.EndOneWayLeft);
}
if (num12 != 0 && num9 == 0) {
flags |= (((data.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? NetLane.Flags.StartOneWayRight : NetLane.Flags.EndOneWayRight);
}
if ((data.m_flags & NetSegment.Flags.YieldStart) != NetSegment.Flags.None) {
flags |= (((data.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? NetLane.Flags.YieldStart : NetLane.Flags.YieldEnd);
}
if ((data.m_flags & NetSegment.Flags.YieldEnd) != NetSegment.Flags.None) {
flags |= (((data.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? NetLane.Flags.YieldEnd : NetLane.Flags.YieldStart);
}
float num13 = 0f;
float num14 = 0f;
uint num15 = 0u;
uint num16 = data.m_lanes;
for (int i = 0; i < this.m_info.m_lanes.Length; i++) {
if (num16 == 0u) {
if (!Singleton.instance.CreateLanes(out num16, ref Singleton.instance.m_randomizer, segmentID, 1)) {
break;
}
if (num15 != 0u) {
instance.m_lanes.m_buffer[num15].m_nextLane = num16;
} else {
data.m_lanes = num16;
}
}
NetInfo.Lane lane = this.m_info.m_lanes[i];
float num17 = lane.m_position / (this.m_info.m_halfWidth * 2f) + 0.5f;
if ((data.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) {
num17 = 1f - num17;
}
Vector3 vector3 = vector + (a3 - vector) * num17;
Vector3 startDir = Vector3.Lerp(a, b2, num17);
Vector3 vector4 = vector2 + (a2 - vector2) * num17;
Vector3 endDir = Vector3.Lerp(a4, b, num17);
vector3.y += lane.m_verticalOffset;
vector4.y += lane.m_verticalOffset;
Vector3 b3;
Vector3 c;
NetSegment.CalculateMiddlePoints(vector3, startDir, vector4, endDir, smoothStart, smoothEnd, out b3, out c);
NetLane.Flags flags2 = (NetLane.Flags)instance.m_lanes.m_buffer[num16].m_flags;
NetLane.Flags flags3 = flags;
flags2 &= ~(NetLane.Flags.Forward | NetLane.Flags.Left | NetLane.Flags.Right | NetLane.Flags.Merge | NetLane.Flags.YieldStart | NetLane.Flags.YieldEnd | NetLane.Flags.StartOneWayLeft | NetLane.Flags.StartOneWayRight | NetLane.Flags.EndOneWayLeft | NetLane.Flags.EndOneWayRight);
if ((byte)(lane.m_finalDirection & NetInfo.Direction.Both) == 2) {
flags3 &= ~NetLane.Flags.YieldEnd;
}
if ((byte)(lane.m_finalDirection & NetInfo.Direction.Both) == 1) {
flags3 &= ~NetLane.Flags.YieldStart;
}
if ((lane.m_vehicleType & VehicleInfo.VehicleType.Monorail) != VehicleInfo.VehicleType.None) {
flags3 &= ~(NetLane.Flags.YieldStart | NetLane.Flags.YieldEnd);
}
flags2 |= flags3;
if (flag) {
flags2 |= NetLane.Flags.Inverted;
} else {
flags2 &= ~NetLane.Flags.Inverted;
}
int num18 = 0;
int num19 = 255;
if ((byte)(lane.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0) {
bool flag6 = (byte)(lane.m_finalDirection & NetInfo.Direction.Forward) != 0 == ((data.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None);
int num20;
int num21;
int num22;
if (flag6) {
num20 = num;
num21 = num2;
num22 = num3;
} else {
num20 = num7;
num21 = num8;
num22 = num9;
}
int num23;
int num24;
if ((byte)(lane.m_finalDirection & NetInfo.Direction.Forward) != 0) {
num23 = lane.m_similarLaneIndex;
num24 = lane.m_similarLaneCount - lane.m_similarLaneIndex - 1;
} else {
num23 = lane.m_similarLaneCount - lane.m_similarLaneIndex - 1;
num24 = lane.m_similarLaneIndex;
}
int num25 = num20 + num21 + num22;
num18 = 255;
num19 = 0;
if (num25 != 0) {
if (lane.m_similarLaneCount > num25 && num25 > 0) {
num18 = num25 * num23 / lane.m_similarLaneCount;
num19 = num25 - num25 * num24 / lane.m_similarLaneCount;
flags2 |= NetLane.Flags.Merge;
if (num18 < num20) {
flags2 |= NetLane.Flags.Left;
}
if (num25 - num19 < num22) {
flags2 |= NetLane.Flags.Right;
}
if (num21 != 0 && num18 < num20 + num21 && num19 > num20) {
flags2 |= NetLane.Flags.Forward;
}
} else {
int num26;
int num27;
if (lane.m_similarLaneCount >= num25) {
num26 = num20;
num27 = num22;
} else {
num26 = num20 * lane.m_similarLaneCount / (num25 + (num21 >> 1));
num27 = num22 * lane.m_similarLaneCount / (num25 + (num21 >> 1));
}
int num28 = num26;
int num29 = lane.m_similarLaneCount - num26 - num27;
int num30 = num27;
if (num29 > 0) {
if (num20 > num26) {
num28++;
}
if (num22 > num27) {
num30++;
}
}
if (num23 < num28) {
int num31 = (num23 * num20 + num28 - 1) / num28;
int num32 = ((num23 + 1) * num20 + num28 - 1) / num28;
if (num32 > num31) {
flags2 |= NetLane.Flags.Left;
num18 = Mathf.Min(num18, num31);
num19 = Mathf.Max(num19, num32);
}
}
if (num23 >= num26 && num24 >= num27 && num21 != 0) {
if (lane.m_similarLaneCount > num25) {
num26++;
}
int num33 = num20 + ((num23 - num26) * num21 + num29 - 1) / num29;
int num34 = num20 + ((num23 + 1 - num26) * num21 + num29 - 1) / num29;
if (num34 > num33) {
flags2 |= NetLane.Flags.Forward;
num18 = Mathf.Min(num18, num33);
num19 = Mathf.Max(num19, num34);
}
}
if (num24 < num30) {
int num35 = num25 - ((num24 + 1) * num22 + num30 - 1) / num30;
int num36 = num25 - (num24 * num22 + num30 - 1) / num30;
if (num36 > num35) {
flags2 |= NetLane.Flags.Right;
num18 = Mathf.Min(num18, num35);
num19 = Mathf.Max(num19, num36);
}
}
if (this.m_highwayRules) {
if ((flags2 & NetLane.Flags.LeftRight) == NetLane.Flags.Left) {
if ((flags2 & NetLane.Flags.Forward) == NetLane.Flags.None || (num21 >= 2 && num20 == 1)) {
num19 = Mathf.Min(num19, num18 + 1);
}
} else if ((flags2 & NetLane.Flags.LeftRight) == NetLane.Flags.Right && ((flags2 & NetLane.Flags.Forward) == NetLane.Flags.None || (num21 >= 2 && num22 == 1))) {
num18 = Mathf.Max(num18, num19 - 1);
}
}
}
}
if (flag6) {
if (flag2) {
flags2 &= ~(NetLane.Flags.Forward | NetLane.Flags.Left | NetLane.Flags.Right);
} else if (flag3) {
flags2 |= NetLane.Flags.Forward;
}
} else if (flag4) {
flags2 &= ~(NetLane.Flags.Forward | NetLane.Flags.Left | NetLane.Flags.Right);
} else if (flag5) {
flags2 |= NetLane.Flags.Forward;
}
}
instance.m_lanes.m_buffer[num16].m_bezier = new Bezier3(vector3, b3, c, vector4);
instance.m_lanes.m_buffer[num16].m_segment = segmentID;
instance.m_lanes.m_buffer[num16].m_flags = (ushort)flags2;
instance.m_lanes.m_buffer[num16].m_firstTarget = (byte)num18;
instance.m_lanes.m_buffer[num16].m_lastTarget = (byte)num19;
num13 += instance.m_lanes.m_buffer[num16].UpdateLength();
num14 += 1f;
num15 = num16;
num16 = instance.m_lanes.m_buffer[num16].m_nextLane;
}
if (num14 != 0f) {
data.m_averageLength = num13 / num14;
} else {
data.m_averageLength = 0f;
}
bool flag7 = false;
if (data.m_averageLength < 11f && (instance.m_nodes.m_buffer[(int)data.m_startNode].m_flags & NetNode.Flags.Junction) != NetNode.Flags.None && (instance.m_nodes.m_buffer[(int)data.m_endNode].m_flags & NetNode.Flags.Junction) != NetNode.Flags.None) {
flag7 = true;
}
num16 = data.m_lanes;
int num37 = 0;
while (num37 < this.m_info.m_lanes.Length && num16 != 0u) {
NetLane.Flags flags4 = (NetLane.Flags)instance.m_lanes.m_buffer[num16].m_flags & ~NetLane.Flags.JoinedJunction;
if (flag7) {
flags4 |= NetLane.Flags.JoinedJunction;
}
instance.m_lanes.m_buffer[num16].m_flags = (ushort)flags4;
num16 = instance.m_lanes.m_buffer[num16].m_nextLane;
num37++;
}
if (!loading) {
int num38 = Mathf.Max((int)((data.m_bounds.min.x - 16f) / 64f + 135f), 0);
int num39 = Mathf.Max((int)((data.m_bounds.min.z - 16f) / 64f + 135f), 0);
int num40 = Mathf.Min((int)((data.m_bounds.max.x + 16f) / 64f + 135f), 269);
int num41 = Mathf.Min((int)((data.m_bounds.max.z + 16f) / 64f + 135f), 269);
for (int j = num39; j <= num41; j++) {
for (int k = num38; k <= num40; k++) {
ushort num42 = instance.m_nodeGrid[j * 270 + k];
int num43 = 0;
while (num42 != 0) {
NetInfo info = instance.m_nodes.m_buffer[(int)num42].Info;
Vector3 position = instance.m_nodes.m_buffer[(int)num42].m_position;
float num44 = Mathf.Max(Mathf.Max(data.m_bounds.min.x - 16f - position.x, data.m_bounds.min.z - 16f - position.z), Mathf.Max(position.x - data.m_bounds.max.x - 16f, position.z - data.m_bounds.max.z - 16f));
if (num44 < 0f) {
info.m_netAI.NearbyLanesUpdated(num42, ref instance.m_nodes.m_buffer[(int)num42]);
}
num42 = instance.m_nodes.m_buffer[(int)num42].m_nextGridNode;
if (++num43 >= 32768) {
CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);
break;
}
}
}
}
if (this.m_info.m_hasPedestrianLanes && (this.m_info.m_hasForwardVehicleLanes || this.m_info.m_hasBackwardVehicleLanes)) {
CheckBuildings(segmentID, ref data);
}
}
// stock code end
// NON-STOCK CODE START
#if BENCHMARK
using (var bm = new Benchmark(null, "applyLaneArrowFlags")) {
#endif
try {
NetManager netManager = Singleton.instance;
// update lane arrows
uint laneId = netManager.m_segments.m_buffer[segmentID].m_lanes;
while (laneId != 0) {
if (!Flags.applyLaneArrowFlags(laneId)) {
Flags.removeLaneArrowFlags(laneId);
}
laneId = netManager.m_lanes.m_buffer[laneId].m_nextLane;
}
} catch (Exception e) {
Log.Error($"Error occured in CustomRoadAI.CustomUpdateLanes @ seg. {segmentID}: " + e.ToString());
}
#if BENCHMARK
}
#endif
// NON-STOCK CODE END
}
public static void CustomGetTrafficLightNodeState(ushort nodeID, ref NetNode nodeData, ushort segmentID, ref NetSegment segmentData, ref NetNode.Flags flags, ref Color color) {
bool customSim = false;
#if BENCHMARK
using (var bm = new Benchmark(null, "customSim")) {
#endif
customSim = Options.timedLightsEnabled && TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeID].IsSimulationRunning();
#if BENCHMARK
}
#endif
uint num = Singleton.instance.m_referenceFrameIndex - 15u;
uint num2 = (uint)(((int)nodeID << 8) / 32768);
uint num3 = num - num2 & 255u;
RoadBaseAI.TrafficLightState trafficLightState;
RoadBaseAI.TrafficLightState trafficLightState2;
RoadBaseAI.GetTrafficLightState(nodeID, ref segmentData, num - num2, out trafficLightState, out trafficLightState2);
color.a = 0.5f;
switch (trafficLightState) {
case RoadBaseAI.TrafficLightState.Green:
color.g = 1f;
break;
case RoadBaseAI.TrafficLightState.RedToGreen:
if (customSim) {
color.r = 1f;
} else {
if (num3 < 45u) {
color.g = 0f;
} else if (num3 < 60u) {
color.r = 1f;
} else {
color.g = 1f;
}
}
break;
case RoadBaseAI.TrafficLightState.Red:
color.g = 0f;
break;
case RoadBaseAI.TrafficLightState.GreenToRed:
if (customSim) {
color.r = 1f;
} else {
if (num3 < 45u) {
color.r = 1f;
} else {
color.g = 0f;
}
}
break;
}
switch (trafficLightState2) {
case RoadBaseAI.TrafficLightState.Green:
color.b = 1f;
break;
case RoadBaseAI.TrafficLightState.RedToGreen:
if (customSim) {
color.b = 0f;
} else {
if (num3 < 45u) {
color.b = 0f;
} else {
color.b = 1f;
}
}
break;
case RoadBaseAI.TrafficLightState.Red:
color.b = 0f;
break;
case RoadBaseAI.TrafficLightState.GreenToRed:
if (customSim) {
color.b = 0f;
} else {
if (num3 < 45u) {
if ((num3 / 8u & 1u) == 1u) {
color.b = 1f;
}
} else {
color.b = 0f;
}
}
break;
}
}
#region stock code
public void OriginalSimulationStep(ushort nodeID, ref NetNode data) { // pure stock code
if ((data.m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None) {
if ((data.m_flags & NetNode.Flags.LevelCrossing) != NetNode.Flags.None) {
TrainTrackBaseAI.LevelCrossingSimulationStep(nodeID, ref data);
} else {
RoadBaseAI.TrafficLightSimulationStep(nodeID, ref data);
}
}
NetManager instance = Singleton.instance;
int num = 0;
if (this.m_noiseAccumulation != 0) {
int num2 = 0;
for (int i = 0; i < 8; i++) {
ushort segment = data.GetSegment(i);
if (segment != 0) {
num += (int)instance.m_segments.m_buffer[(int)segment].m_noiseDensity;
num2++;
}
}
if (num2 != 0) {
num /= num2;
}
}
int num3 = 100 - (num - 100) * (num - 100) / 100;
int num4 = this.m_noiseAccumulation * num3 / 100;
if (num4 != 0) {
Singleton.instance.AddResource(ImmaterialResourceManager.Resource.NoisePollution, num4, data.m_position, this.m_noiseRadius);
}
if ((data.m_problems & Notification.Problem.RoadNotConnected) != Notification.Problem.None && (data.m_flags & NetNode.Flags.Original) != NetNode.Flags.None) {
GuideController properties = Singleton.instance.m_properties;
if (properties != null) {
instance.m_outsideNodeNotConnected.Activate(properties.m_outsideNotConnected, nodeID, Notification.Problem.RoadNotConnected, false);
}
}
}
public void OriginalSimulationStep(ushort segmentID, ref NetSegment data) { // stock + custom code
// base.SimulationStep START
NetManager netManager = Singleton.instance;
Vector3 startNodePos = netManager.m_nodes.m_buffer[data.m_startNode].m_position;
Vector3 endNodePos = netManager.m_nodes.m_buffer[data.m_endNode].m_position;
if (this.HasMaintenanceCost(segmentID, ref data)) {
int maintenanceCost = this.GetMaintenanceCost(startNodePos, endNodePos);
bool simulate = (ulong)(Singleton.instance.m_currentFrameIndex >> 8 & 15u) == (ulong)((long)(segmentID & 15));
if (maintenanceCost != 0) {
if (simulate) {
maintenanceCost = maintenanceCost * 16 / 100 - maintenanceCost / 100 * 15;
} else {
maintenanceCost /= 100;
}
Singleton.instance.FetchResource(EconomyManager.Resource.Maintenance, maintenanceCost, this.m_info.m_class);
}
if (simulate) {
float startNodeElevation = (float)netManager.m_nodes.m_buffer[data.m_startNode].m_elevation;
float endNodeElevation = (float)netManager.m_nodes.m_buffer[data.m_endNode].m_elevation;
if (this.IsUnderground()) {
startNodeElevation = -startNodeElevation;
endNodeElevation = -endNodeElevation;
}
int constructionCost = this.GetConstructionCost(startNodePos, endNodePos, startNodeElevation, endNodeElevation);
if (constructionCost != 0) {
StatisticBase statisticBase = Singleton.instance.Acquire(StatisticType.CityValue);
if (statisticBase != null) {
statisticBase.Add(constructionCost);
}
}
}
}
// base.SimulationStep END
SimulationManager simManager = Singleton.instance;
Notification.Problem problem = Notification.RemoveProblems(data.m_problems, Notification.Problem.Flood | Notification.Problem.Snow);
if ((data.m_flags & NetSegment.Flags.AccessFailed) != NetSegment.Flags.None && Singleton.instance.m_randomizer.Int32(16u) == 0) {
data.m_flags &= ~NetSegment.Flags.AccessFailed;
}
float totalLength = 0f;
uint curLaneId = data.m_lanes;
int laneIndex = 0;
while (laneIndex < this.m_info.m_lanes.Length && curLaneId != 0u) {
NetInfo.Lane lane = this.m_info.m_lanes[laneIndex];
if ((byte)(lane.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0 && (lane.m_vehicleType & ~VehicleInfo.VehicleType.Bicycle) != VehicleInfo.VehicleType.None) {
totalLength += netManager.m_lanes.m_buffer[curLaneId].m_length;
}
curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane;
laneIndex++;
}
int trafficDensity = 0;
int noiseDensity = 0;
if (data.m_trafficBuffer == 65535) {
if ((data.m_flags & NetSegment.Flags.Blocked) == NetSegment.Flags.None) {
data.m_flags |= NetSegment.Flags.Blocked;
data.m_modifiedIndex = simManager.m_currentBuildIndex++;
}
} else {
data.m_flags &= ~NetSegment.Flags.Blocked;
int lengthDenominator = Mathf.RoundToInt(totalLength) << 4;
if (lengthDenominator != 0) {
trafficDensity = (int)((byte)Mathf.Min((int)(data.m_trafficBuffer * 100) / lengthDenominator, 100));
noiseDensity = (int)((byte)Mathf.Min((int)(data.m_noiseBuffer * 250) / lengthDenominator, 100));
}
}
data.m_trafficBuffer = 0;
data.m_noiseBuffer = 0;
if (trafficDensity > (int)data.m_trafficDensity) {
data.m_trafficDensity = (byte)Mathf.Min((int)(data.m_trafficDensity + 5), trafficDensity);
} else if (trafficDensity < (int)data.m_trafficDensity) {
data.m_trafficDensity = (byte)Mathf.Max((int)(data.m_trafficDensity - 5), trafficDensity);
}
if (noiseDensity > (int)data.m_noiseDensity){
data.m_noiseDensity = (byte)Mathf.Min((int)(data.m_noiseDensity + 2), noiseDensity);
} else if (noiseDensity < (int)data.m_noiseDensity){
data.m_noiseDensity = (byte)Mathf.Max((int)(data.m_noiseDensity - 2), noiseDensity);
}
//Vector3 startNodePos = netManager.m_nodes.m_buffer[(int)data.m_startNode].m_position;
//Vector3 endNodePos = netManager.m_nodes.m_buffer[(int)data.m_endNode].m_position;
Vector3 middlePoint = (startNodePos + endNodePos) * 0.5f;
bool flooded = false;
if ((this.m_info.m_setVehicleFlags & Vehicle.Flags.Underground) == 0) {
float waterLevelAtMiddlePoint = Singleton.instance.WaterLevel(VectorUtils.XZ(middlePoint));
// NON-STOCK CODE START
// Rainfall compatibility
float _roadwayFloodedTolerance = LoadingExtension.IsRainfallLoaded ? (float)PlayerPrefs.GetInt("RF_RoadwayFloodedTolerance", 100)/100f : 1f;
if (waterLevelAtMiddlePoint > middlePoint.y + _roadwayFloodedTolerance && waterLevelAtMiddlePoint > 0f) {
flooded = true;
data.m_flags |= NetSegment.Flags.Flooded;
problem = Notification.AddProblems(problem, Notification.Problem.Flood | Notification.Problem.MajorProblem);
Vector3 min = data.m_bounds.min;
Vector3 max = data.m_bounds.max;
RoadBaseAI.FloodParkedCars(min.x, min.z, max.x, max.z);
} else {
data.m_flags &= ~NetSegment.Flags.Flooded;
float _roadwayFloodingTolerance = LoadingExtension.IsRainfallLoaded ? (float)PlayerPrefs.GetInt("RF_RoadwayFloodingTolerance", 50)/100f : 0f;
if (waterLevelAtMiddlePoint > middlePoint.y + _roadwayFloodingTolerance && waterLevelAtMiddlePoint > 0f) {
flooded = true;
problem = Notification.AddProblems(problem, Notification.Problem.Flood);
}
}
// NON-STOCK CODE END
}
DistrictManager districtManager = Singleton.instance;
byte districtId = districtManager.GetDistrict(middlePoint);
DistrictPolicies.CityPlanning cityPlanningPolicies = districtManager.m_districts.m_buffer[(int)districtId].m_cityPlanningPolicies;
int noisePollution = (int)(100 - (data.m_noiseDensity - 100) * (data.m_noiseDensity - 100) / 100);
if ((this.m_info.m_vehicleTypes & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) {
if ((this.m_info.m_setVehicleFlags & Vehicle.Flags.Underground) == 0) {
if (flooded && (data.m_flags & (NetSegment.Flags.AccessFailed | NetSegment.Flags.Blocked)) == NetSegment.Flags.None && simManager.m_randomizer.Int32(10u) == 0) {
TransferManager.TransferOffer offer = default(TransferManager.TransferOffer);
offer.Priority = 4;
offer.NetSegment = segmentID;
offer.Position = middlePoint;
offer.Amount = 1;
Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.FloodWater, offer);
}
int wetness = (int)data.m_wetness;
if (!netManager.m_treatWetAsSnow) {
if (flooded) {
wetness = 255;
} else {
int wetnessDelta = -(wetness + 63 >> 5);
float rainIntensity = Singleton.instance.SampleRainIntensity(middlePoint, false);
if (rainIntensity != 0f) {
int wetnessIncreaseDueToRain = Mathf.RoundToInt(Mathf.Min(rainIntensity * 4000f, 1000f));
wetnessDelta += simManager.m_randomizer.Int32(wetnessIncreaseDueToRain, wetnessIncreaseDueToRain + 99) / 100;
}
wetness = Mathf.Clamp(wetness + wetnessDelta, 0, 255);
}
} else if (this.m_accumulateSnow) {
if (flooded) {
wetness = 128;
} else {
float rainIntensity = Singleton.instance.SampleRainIntensity(middlePoint, false);
if (rainIntensity != 0f) {
int minWetnessDelta = Mathf.RoundToInt(rainIntensity * 400f);
int wetnessDelta = simManager.m_randomizer.Int32(minWetnessDelta, minWetnessDelta + 99) / 100;
if (Singleton.instance.Unlocked(UnlockManager.Feature.Snowplow)) {
wetness = Mathf.Min(wetness + wetnessDelta, 255);
} else {
wetness = Mathf.Min(wetness + wetnessDelta, 128);
}
} else if (Singleton.instance.m_randomizer.Int32(4u) == 0) {
wetness = Mathf.Max(wetness - 1, 0);
}
if (wetness >= 64 && (data.m_flags & (NetSegment.Flags.AccessFailed | NetSegment.Flags.Blocked | NetSegment.Flags.Flooded)) == NetSegment.Flags.None && simManager.m_randomizer.Int32(10u) == 0) {
TransferManager.TransferOffer offer = default(TransferManager.TransferOffer);
offer.Priority = wetness / 50;
offer.NetSegment = segmentID;
offer.Position = middlePoint;
offer.Amount = 1;
Singleton.instance.AddOutgoingOffer(TransferManager.TransferReason.Snow, offer);
}
if (wetness >= 192) {
problem = Notification.AddProblems(problem, Notification.Problem.Snow);
}
districtManager.m_districts.m_buffer[districtId].m_productionData.m_tempSnowCover += (uint)wetness;
}
}
if (wetness != (int)data.m_wetness) {
if (Mathf.Abs((int)data.m_wetness - wetness) > 10) {
data.m_wetness = (byte)wetness;
InstanceID empty = InstanceID.Empty;
empty.NetSegment = segmentID;
netManager.AddSmoothColor(empty);
empty.NetNode = data.m_startNode;
netManager.AddSmoothColor(empty);
empty.NetNode = data.m_endNode;
netManager.AddSmoothColor(empty);
} else {
data.m_wetness = (byte)wetness;
netManager.m_wetnessChanged = 256;
}
}
}
int minConditionDelta;
if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.StuddedTires) != DistrictPolicies.CityPlanning.None) {
noisePollution = noisePollution * 3 + 1 >> 1;
minConditionDelta = Mathf.Min(700, (int)(50 + data.m_trafficDensity * 6));
} else {
minConditionDelta = Mathf.Min(500, (int)(50 + data.m_trafficDensity * 4));
}
if (!this.m_highwayRules) {
int conditionDelta = simManager.m_randomizer.Int32(minConditionDelta, minConditionDelta + 99) / 100;
data.m_condition = (byte)Mathf.Max((int)data.m_condition - conditionDelta, 0);
if (data.m_condition < 192 && (data.m_flags & (NetSegment.Flags.AccessFailed | NetSegment.Flags.Blocked | NetSegment.Flags.Flooded)) == NetSegment.Flags.None && simManager.m_randomizer.Int32(20u) == 0) {
TransferManager.TransferOffer offer = default(TransferManager.TransferOffer);
offer.Priority = (int)((255 - data.m_condition) / 50);
offer.NetSegment = segmentID;
offer.Position = middlePoint;
offer.Amount = 1;
Singleton.instance.AddIncomingOffer(TransferManager.TransferReason.RoadMaintenance, offer);
}
}
}
if (!this.m_highwayRules) {
if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.HeavyTrafficBan) != DistrictPolicies.CityPlanning.None) {
data.m_flags |= NetSegment.Flags.HeavyBan;
} else {
data.m_flags &= ~NetSegment.Flags.HeavyBan;
}
if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.BikeBan) != DistrictPolicies.CityPlanning.None) {
data.m_flags |= NetSegment.Flags.BikeBan;
} else {
data.m_flags &= ~NetSegment.Flags.BikeBan;
}
if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.OldTown) != DistrictPolicies.CityPlanning.None) {
data.m_flags |= NetSegment.Flags.CarBan;
} else {
data.m_flags &= ~NetSegment.Flags.CarBan;
}
}
int noisePollutionResource = this.m_noiseAccumulation * noisePollution / 100;
if (noisePollutionResource != 0) {
float distance = Vector3.Distance(startNodePos, endNodePos);
int relNoiseRadius = Mathf.FloorToInt(distance / this.m_noiseRadius);
for (int i = 0; i < relNoiseRadius; i++) {
Vector3 position3 = Vector3.Lerp(startNodePos, endNodePos, (float)(i + 1) / (float)(relNoiseRadius + 1));
Singleton.instance.AddResource(ImmaterialResourceManager.Resource.NoisePollution, noisePollutionResource, position3, this.m_noiseRadius);
}
}
if (data.m_trafficDensity >= 50 && data.m_averageLength < 25f && (netManager.m_nodes.m_buffer[(int)data.m_startNode].m_flags & (NetNode.Flags.LevelCrossing | NetNode.Flags.TrafficLights)) == NetNode.Flags.TrafficLights && (netManager.m_nodes.m_buffer[(int)data.m_endNode].m_flags & (NetNode.Flags.LevelCrossing | NetNode.Flags.TrafficLights)) == NetNode.Flags.TrafficLights) {
GuideController guideCtrl = Singleton.instance.m_properties;
if (guideCtrl != null) {
Singleton.instance.m_shortRoadTraffic.Activate(guideCtrl.m_shortRoadTraffic, segmentID, false);
}
}
if ((data.m_flags & NetSegment.Flags.Collapsed) != NetSegment.Flags.None) {
GuideController guideCtrl = Singleton.instance.m_properties;
if (guideCtrl != null) {
Singleton.instance.m_roadDestroyed.Activate(guideCtrl.m_roadDestroyed, segmentID, false);
Singleton.instance.m_roadDestroyed2.Activate(guideCtrl.m_roadDestroyed2, this.m_info.m_class.m_service);
}
if ((ulong)(simManager.m_currentFrameIndex >> 8 & 15u) == (ulong)((long)(segmentID & 15))) {
int delta = Mathf.RoundToInt(data.m_averageLength);
StatisticBase statisticBase = Singleton.instance.Acquire(StatisticType.DestroyedLength);
statisticBase.Add(delta);
}
}
data.m_problems = problem;
}
#endregion
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomShipAI.cs
================================================
using ColossalFramework;
using CSUtil.Commons;
using CSUtil.Commons.Benchmark;
using System;
using System.Collections.Generic;
using System.Text;
using TrafficManager.Custom.PathFinding;
using TrafficManager.Geometry;
using TrafficManager.Manager;
using TrafficManager.Traffic;
using TrafficManager.Traffic.Data;
using UnityEngine;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
class CustomShipAI : ShipAI {
public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) {
#if DEBUG
//Log._Debug($"CustomShipAI.CustomStartPathFind called for vehicle {vehicleID}");
#endif
/// NON-STOCK CODE START ///
ExtVehicleType vehicleType = ExtVehicleType.None;
#if BENCHMARK
using (var bm = new Benchmark(null, "vehicleType")) {
#endif
vehicleType = vehicleData.Info.m_vehicleAI is PassengerShipAI ? ExtVehicleType.PassengerShip : ExtVehicleType.CargoVehicle;
#if BENCHMARK
}
#endif
/// NON-STOCK CODE END ///
VehicleInfo info = this.m_info;
PathUnit.Position startPosA;
PathUnit.Position startPosB;
float startSqrDistA;
float startSqrDistB;
PathUnit.Position endPosA;
PathUnit.Position endPosB;
float endSqrDistA;
float endSqrDistB;
if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, info.m_vehicleType, false, false, 64f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB) &&
CustomPathManager.FindPathPosition(endPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, info.m_vehicleType, false, false, 64f, out endPosA, out endPosB, out endSqrDistA, out endSqrDistB)) {
if (!startBothWays || startSqrDistA < 10f) {
startPosB = default(PathUnit.Position);
}
if (!endBothWays || endSqrDistA < 10f) {
endPosB = default(PathUnit.Position);
}
uint path;
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = ExtCitizenInstance.ExtPathType.None;
args.extVehicleType = vehicleType;
args.vehicleId = vehicleID;
args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
args.buildIndex = Singleton.instance.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = startPosB;
args.endPosA = endPosA;
args.endPosB = endPosB;
args.vehiclePosition = default(PathUnit.Position);
args.laneTypes = NetInfo.LaneType.Vehicle;
args.vehicleTypes = info.m_vehicleType;
args.maxLength = 20000f;
args.isHeavyVehicle = false;
args.hasCombustionEngine = false;
args.ignoreBlocked = false;
args.ignoreFlooded = false;
args.ignoreCosts = false;
args.randomParking = false;
args.stablePath = false;
args.skipQueue = false;
if (CustomPathManager._instance.CreatePath(out path, ref Singleton.instance.m_randomizer, args)) {
// NON-STOCK CODE END
if (vehicleData.m_path != 0u) {
Singleton.instance.ReleasePath(vehicleData.m_path);
}
vehicleData.m_path = path;
vehicleData.m_flags |= Vehicle.Flags.WaitingPath;
return true;
}
}
return false;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomTaxiAI.cs
================================================
using ColossalFramework;
using System;
using System.Collections.Generic;
using System.Text;
using TrafficManager.Custom.PathFinding;
using TrafficManager.Geometry;
using TrafficManager.Manager;
using TrafficManager.Traffic;
using TrafficManager.Traffic.Data;
using UnityEngine;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
class CustomTaxiAI : CarAI {
public static ushort GetPassengerInstance(ushort vehicleID, ref Vehicle data) {
CitizenManager instance = Singleton.instance;
uint curUnitId = data.m_citizenUnits;
int numIterations = 0;
while (curUnitId != 0u) {
uint nextUnit = instance.m_units.m_buffer[curUnitId].m_nextUnit;
for (int i = 0; i < 5; i++) {
uint citizenId = instance.m_units.m_buffer[curUnitId].GetCitizen(i);
if (citizenId != 0u) {
ushort citizenInstanceId = instance.m_citizens.m_buffer[citizenId].m_instance;
if (citizenInstanceId != 0) {
return citizenInstanceId;
}
}
}
curUnitId = nextUnit;
if (++numIterations > 524288) {
CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);
break;
}
}
return 0;
}
public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) {
#if DEBUG
//Log._Debug($"CustomTaxiAI.CustomStartPathFind called for vehicle {vehicleID}");
#endif
CitizenManager instance = Singleton.instance;
ushort passengerInstanceId = CustomTaxiAI.GetPassengerInstance(vehicleID, ref vehicleData);
if (passengerInstanceId == 0 || (instance.m_instances.m_buffer[(int)passengerInstanceId].m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None) {
return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget);
}
VehicleInfo info = this.m_info;
CitizenInfo info2 = instance.m_instances.m_buffer[(int)passengerInstanceId].Info;
NetInfo.LaneType laneTypes = NetInfo.LaneType.Vehicle | NetInfo.LaneType.Pedestrian | NetInfo.LaneType.TransportVehicle;
VehicleInfo.VehicleType vehicleTypes = this.m_info.m_vehicleType;
bool allowUnderground = (vehicleData.m_flags & Vehicle.Flags.Underground) != 0;
PathUnit.Position startPosA;
PathUnit.Position startPosB;
float startSqrDistA;
float startSqrDistB;
PathUnit.Position endPosA;
if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB) &&
info2.m_citizenAI.FindPathPosition(passengerInstanceId, ref instance.m_instances.m_buffer[(int)passengerInstanceId], endPos, laneTypes, vehicleTypes, undergroundTarget, out endPosA)) {
if ((instance.m_instances.m_buffer[(int)passengerInstanceId].m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) {
laneTypes |= NetInfo.LaneType.PublicTransport;
uint citizenId = instance.m_instances.m_buffer[passengerInstanceId].m_citizen;
if (citizenId != 0u && (instance.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Evacuating) != Citizen.Flags.None) {
laneTypes |= NetInfo.LaneType.EvacuationTransport;
}
}
if (!startBothWays || startSqrDistA < 10f) {
startPosB = default(PathUnit.Position);
}
PathUnit.Position endPosB = default(PathUnit.Position);
SimulationManager simMan = Singleton.instance;
uint path;
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = ExtCitizenInstance.ExtPathType.None;
args.extVehicleType = ExtVehicleType.Taxi;
args.vehicleId = vehicleID;
args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
args.buildIndex = simMan.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = startPosB;
args.endPosA = endPosA;
args.endPosB = endPosB;
args.vehiclePosition = default(PathUnit.Position);
args.laneTypes = laneTypes;
args.vehicleTypes = vehicleTypes;
args.maxLength = 20000f;
args.isHeavyVehicle = this.IsHeavyVehicle();
args.hasCombustionEngine = this.CombustionEngine();
args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData);
args.ignoreFlooded = false;
args.ignoreCosts = false;
args.randomParking = false;
args.stablePath = false;
args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
if (CustomPathManager._instance.CreatePath(out path, ref simMan.m_randomizer, args)) {
// NON-STOCK CODE END
if (vehicleData.m_path != 0u) {
Singleton.instance.ReleasePath(vehicleData.m_path);
}
vehicleData.m_path = path;
vehicleData.m_flags |= Vehicle.Flags.WaitingPath;
return true;
}
}
return false;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomTouristAI.cs
================================================
using ColossalFramework;
using ColossalFramework.Globalization;
using ColossalFramework.Math;
using CSUtil.Commons;
using CSUtil.Commons.Benchmark;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using TrafficManager.Manager;
using TrafficManager.Manager.Impl;
using TrafficManager.State;
using TrafficManager.Traffic;
using static TrafficManager.Traffic.Data.ExtCitizenInstance;
namespace TrafficManager.Custom.AI {
public class CustomTouristAI : TouristAI {
public string CustomGetLocalizedStatus(ushort instanceID, ref CitizenInstance data, out InstanceID target) {
bool addCustomStatus = false;
String ret = GetStockLocalizedStatus(instanceID, ref data, out addCustomStatus, out target);
// NON-STOCK CODE START
#if BENCHMARK
using (var bm = new Benchmark(null, "EnrichLocalizedCitizenStatus")) {
#endif
if (Options.prohibitPocketCars && addCustomStatus) {
ret = AdvancedParkingManager.Instance.EnrichLocalizedCitizenStatus(ret, ref ExtCitizenInstanceManager.Instance.ExtInstances[instanceID], ref ExtCitizenManager.Instance.ExtCitizens[data.m_citizen]);
}
#if BENCHMARK
}
#endif
// NON-STOCK CODE END
return ret;
}
private String GetStockLocalizedStatus(ushort instanceID, ref CitizenInstance data, out bool addCustomStatus, out InstanceID target) {
if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != CitizenInstance.Flags.None) {
target = InstanceID.Empty;
addCustomStatus = false;
return Locale.Get("CITIZEN_STATUS_CONFUSED");
}
CitizenManager instance = Singleton.instance;
uint citizenId = data.m_citizen;
ushort vehicleId = 0;
if (citizenId != 0u) {
vehicleId = instance.m_citizens.m_buffer[citizenId].m_vehicle;
}
ushort targetBuilding = data.m_targetBuilding;
if (targetBuilding == 0) {
target = InstanceID.Empty;
addCustomStatus = false;
return Locale.Get("CITIZEN_STATUS_CONFUSED");
}
if ((data.m_flags & CitizenInstance.Flags.TargetIsNode) != 0) {
if (vehicleId != 0) {
VehicleManager vehManager = Singleton.instance;
VehicleInfo info = vehManager.m_vehicles.m_buffer[vehicleId].Info;
if (info.m_class.m_service == ItemClass.Service.Residential && info.m_vehicleType != VehicleInfo.VehicleType.Bicycle) {
if (info.m_vehicleAI.GetOwnerID(vehicleId, ref vehManager.m_vehicles.m_buffer[vehicleId]).Citizen == citizenId) {
target = InstanceID.Empty;
target.NetNode = targetBuilding;
addCustomStatus = true;
return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_DRIVINGTO");
}
} else if (info.m_class.m_service == ItemClass.Service.PublicTransport || info.m_class.m_service == ItemClass.Service.Disaster) {
ushort transportLine = Singleton.instance.m_nodes.m_buffer[targetBuilding].m_transportLine;
if ((data.m_flags & CitizenInstance.Flags.WaitingTaxi) != 0) {
target = InstanceID.Empty;
addCustomStatus = true;
return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_WAITING_TAXI");
}
if (vehManager.m_vehicles.m_buffer[vehicleId].m_transportLine != transportLine) {
target = InstanceID.Empty;
target.NetNode = targetBuilding;
addCustomStatus = true;
return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO");
}
}
}
if ((data.m_flags & CitizenInstance.Flags.OnTour) != 0) {
target = InstanceID.Empty;
target.NetNode = targetBuilding;
addCustomStatus = true;
return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING");
}
target = InstanceID.Empty;
target.NetNode = targetBuilding;
addCustomStatus = true;
return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_GOINGTO");
}
bool isOutsideConnection = (Singleton.instance.m_buildings.m_buffer[(int)targetBuilding].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None;
bool hangsAround = data.m_path == 0u && (data.m_flags & CitizenInstance.Flags.HangAround) != CitizenInstance.Flags.None;
if (vehicleId != 0) {
VehicleManager vehManager = Singleton.instance;
VehicleInfo vehicleInfo = vehManager.m_vehicles.m_buffer[(int)vehicleId].Info;
if (vehicleInfo.m_class.m_service == ItemClass.Service.Residential && vehicleInfo.m_vehicleType != VehicleInfo.VehicleType.Bicycle) {
if (vehicleInfo.m_vehicleAI.GetOwnerID(vehicleId, ref vehManager.m_vehicles.m_buffer[(int)vehicleId]).Citizen == citizenId) {
if (isOutsideConnection) {
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_DRIVINGTO_OUTSIDE");
}
target = InstanceID.Empty;
target.Building = targetBuilding;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_DRIVINGTO");
}
} else if (vehicleInfo.m_class.m_service == ItemClass.Service.PublicTransport || vehicleInfo.m_class.m_service == ItemClass.Service.Disaster) {
if (isOutsideConnection) {
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_OUTSIDE");
}
target = InstanceID.Empty;
target.Building = targetBuilding;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO");
}
}
if (isOutsideConnection) {
target = InstanceID.Empty;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_GOINGTO_OUTSIDE");
}
if (hangsAround) {
target = InstanceID.Empty;
target.Building = targetBuilding;
addCustomStatus = false;
return Locale.Get("CITIZEN_STATUS_VISITING");
}
target = InstanceID.Empty;
target.Building = targetBuilding;
addCustomStatus = true;
return Locale.Get("CITIZEN_STATUS_GOINGTO");
}
public VehicleInfo CustomGetVehicleInfo(ushort instanceID, ref CitizenInstance citizenData, bool forceCar, out VehicleInfo trailer) {
#if DEBUG
bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceID) &&
(GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenData.m_citizen) &&
(GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == citizenData.m_sourceBuilding) &&
(GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == citizenData.m_targetBuilding)
;
bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug;
bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug;
#endif
trailer = null;
if (citizenData.m_citizen == 0u) {
return null;
}
// NON-STOCK CODE START
bool forceTaxi = false;
#if BENCHMARK
using (var bm = new Benchmark(null, "forceTaxi")) {
#endif
if (Options.prohibitPocketCars) {
if (ExtCitizenInstanceManager.Instance.ExtInstances[instanceID].pathMode == ExtPathMode.TaxiToTarget) {
forceTaxi = true;
}
}
#if BENCHMARK
}
#endif
// NON-STOCK CODE END
Citizen.Wealth wealthLevel = Singleton.instance.m_citizens.m_buffer[citizenData.m_citizen].WealthLevel;
int carProb;
int bikeProb;
int taxiProb;
// NON-STOCK CODE START
if (forceTaxi) {
carProb = 0;
bikeProb = 0;
taxiProb = 100;
} else
// NON-STOCK CODE END
if (forceCar || (citizenData.m_flags & CitizenInstance.Flags.BorrowCar) != CitizenInstance.Flags.None) {
carProb = 100;
bikeProb = 0;
taxiProb = 0;
} else {
carProb = GetCarProbability();
bikeProb = GetBikeProbability();
taxiProb = GetTaxiProbability();
}
Randomizer randomizer = new Randomizer(citizenData.m_citizen);
bool useCar = randomizer.Int32(100u) < carProb;
bool useBike = !useCar && randomizer.Int32(100u) < bikeProb;
bool useTaxi = !useCar && !useBike && randomizer.Int32(100u) < taxiProb;
bool useCamper = false;
bool useElectricCar = false;
if (useCar) {
int camperProb = this.GetCamperProbability(wealthLevel);
useCamper = randomizer.Int32(100u) < camperProb;
if (!useCamper) {
int electricProb = GetElectricCarProbability(wealthLevel);
useElectricCar = randomizer.Int32(100u) < electricProb;
}
}
ItemClass.Service service = ItemClass.Service.Residential;
ItemClass.SubService subService = useElectricCar ? ItemClass.SubService.ResidentialLowEco : ItemClass.SubService.ResidentialLow;
if (useTaxi) {
service = ItemClass.Service.PublicTransport;
subService = ItemClass.SubService.PublicTransportTaxi;
}
// NON-STOCK CODE START
VehicleInfo carInfo = null;
#if BENCHMARK
using (var bm = new Benchmark(null, "find-parked-vehicle")) {
#endif
if (Options.prohibitPocketCars && useCar && !useTaxi) {
ushort parkedVehicleId = Singleton.instance.m_citizens.m_buffer[citizenData.m_citizen].m_parkedVehicle;
if (parkedVehicleId != 0) {
#if DEBUG
if (debug)
Log._Debug($"CustomTouristAI.CustomGetVehicleInfo({instanceID}): Citizen instance {instanceID} owns a parked vehicle {parkedVehicleId}. Reusing vehicle info.");
#endif
carInfo = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].Info;
}
}
#if BENCHMARK
}
#endif
if (carInfo == null && (useCar || useTaxi)) {
// NON-STOCK CODE END
if (useCamper) {
Randomizer randomizer2 = randomizer;
carInfo = Singleton.instance.GetRandomVehicleInfo(ref randomizer, service, subService, ItemClass.Level.Level2);
if (carInfo == null || carInfo.m_vehicleAI is CarTrailerAI) {
trailer = carInfo;
randomizer = randomizer2;
carInfo = Singleton.instance.GetRandomVehicleInfo(ref randomizer, service, subService, ItemClass.Level.Level1);
}
} else {
carInfo = Singleton.instance.GetRandomVehicleInfo(ref randomizer, service, subService, ItemClass.Level.Level1);
}
}
if (useBike) {
VehicleInfo bikeInfo = Singleton.instance.GetRandomVehicleInfo(ref randomizer, ItemClass.Service.Residential, ItemClass.SubService.ResidentialHigh, ItemClass.Level.Level2);
if (bikeInfo != null) {
return bikeInfo;
}
}
if ((useCar || useTaxi) && carInfo != null) {
return carInfo;
}
return null;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int GetTaxiProbability() {
Log.Error("CustomTouristAI.GetTaxiProbability called!");
return 20;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int GetBikeProbability() {
Log.Error("CustomTouristAI.GetBikeProbability called!");
return 20;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int GetCarProbability() {
Log.Error("CustomTouristAI.GetCarProbability called!");
return 20;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int GetElectricCarProbability(Citizen.Wealth wealth) {
Log.Error("CustomTouristAI.GetElectricCarProbability called!");
return 20;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int GetCamperProbability(Citizen.Wealth wealth) {
Log.Error("CustomTouristAI.GetCamperProbability called!");
return 20;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomTrainAI.cs
================================================
using ColossalFramework;
using ColossalFramework.Math;
using System;
using System.Collections.Generic;
using System.Text;
using TrafficManager.Custom.PathFinding;
using TrafficManager.State;
using TrafficManager.Geometry;
using UnityEngine;
using TrafficManager.Traffic;
using TrafficManager.Manager;
using CSUtil.Commons;
using TrafficManager.Manager.Impl;
using System.Runtime.CompilerServices;
using TrafficManager.Traffic.Data;
using CSUtil.Commons.Benchmark;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
public class CustomTrainAI : TrainAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`)
public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) {
if ((vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0) {
byte pathFindFlags = Singleton.instance.m_pathUnits.m_buffer[vehicleData.m_path].m_pathFindFlags;
if ((pathFindFlags & PathUnit.FLAG_READY) != 0) {
try {
this.PathFindReady(vehicleId, ref vehicleData);
} catch (Exception e) {
Log.Warning($"TrainAI.PathFindReady({vehicleId}) for vehicle {vehicleData.Info?.m_class?.name} threw an exception: {e.ToString()}");
vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath;
Singleton.instance.ReleasePath(vehicleData.m_path);
vehicleData.m_path = 0u;
vehicleData.Unspawn(vehicleId);
return;
}
} else if ((pathFindFlags & PathUnit.FLAG_FAILED) != 0 || vehicleData.m_path == 0) {
vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath;
Singleton.instance.ReleasePath(vehicleData.m_path);
vehicleData.m_path = 0u;
vehicleData.Unspawn(vehicleId);
return;
}
} else {
if ((vehicleData.m_flags & Vehicle.Flags.WaitingSpace) != 0) {
this.TrySpawn(vehicleId, ref vehicleData);
}
}
// NON-STOCK CODE START
#if BENCHMARK
using (var bm = new Benchmark(null, "UpdateVehiclePosition")) {
#endif
VehicleStateManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData);
#if BENCHMARK
}
#endif
if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) {
#if BENCHMARK
using (var bm = new Benchmark(null, "LogTraffic")) {
#endif
// Advanced AI traffic measurement
VehicleStateManager.Instance.LogTraffic(vehicleId);
#if BENCHMARK
}
#endif
}
// NON-STOCK CODE END
bool reversed = (vehicleData.m_flags & Vehicle.Flags.Reversed) != 0;
ushort connectedVehicleId;
if (reversed) {
connectedVehicleId = vehicleData.GetLastVehicle(vehicleId);
} else {
connectedVehicleId = vehicleId;
}
VehicleManager instance = Singleton.instance;
VehicleInfo info = instance.m_vehicles.m_buffer[(int)connectedVehicleId].Info;
info.m_vehicleAI.SimulationStep(connectedVehicleId, ref instance.m_vehicles.m_buffer[(int)connectedVehicleId], vehicleId, ref vehicleData, 0);
if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) {
return;
}
bool newReversed = (vehicleData.m_flags & Vehicle.Flags.Reversed) != 0;
if (newReversed != reversed) {
reversed = newReversed;
if (reversed) {
connectedVehicleId = vehicleData.GetLastVehicle(vehicleId);
} else {
connectedVehicleId = vehicleId;
}
info = instance.m_vehicles.m_buffer[(int)connectedVehicleId].Info;
info.m_vehicleAI.SimulationStep(connectedVehicleId, ref instance.m_vehicles.m_buffer[(int)connectedVehicleId], vehicleId, ref vehicleData, 0);
if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) {
return;
}
newReversed = ((vehicleData.m_flags & Vehicle.Flags.Reversed) != 0);
if (newReversed != reversed) {
Singleton.instance.ReleaseVehicle(vehicleId);
return;
}
}
if (reversed) {
connectedVehicleId = instance.m_vehicles.m_buffer[(int)connectedVehicleId].m_leadingVehicle;
int num2 = 0;
while (connectedVehicleId != 0) {
info = instance.m_vehicles.m_buffer[(int)connectedVehicleId].Info;
info.m_vehicleAI.SimulationStep(connectedVehicleId, ref instance.m_vehicles.m_buffer[(int)connectedVehicleId], vehicleId, ref vehicleData, 0);
if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) {
return;
}
connectedVehicleId = instance.m_vehicles.m_buffer[(int)connectedVehicleId].m_leadingVehicle;
if (++num2 > 16384) {
CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);
break;
}
}
} else {
connectedVehicleId = instance.m_vehicles.m_buffer[(int)connectedVehicleId].m_trailingVehicle;
int num3 = 0;
while (connectedVehicleId != 0) {
info = instance.m_vehicles.m_buffer[(int)connectedVehicleId].Info;
info.m_vehicleAI.SimulationStep(connectedVehicleId, ref instance.m_vehicles.m_buffer[(int)connectedVehicleId], vehicleId, ref vehicleData, 0);
if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) {
return;
}
connectedVehicleId = instance.m_vehicles.m_buffer[(int)connectedVehicleId].m_trailingVehicle;
if (++num3 > 16384) {
CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);
break;
}
}
}
if ((vehicleData.m_flags & (Vehicle.Flags.Spawned | Vehicle.Flags.WaitingPath | Vehicle.Flags.WaitingSpace | Vehicle.Flags.WaitingCargo)) == 0) {
Singleton.instance.ReleaseVehicle(vehicleId);
} else if (vehicleData.m_blockCounter == 255) {
// NON-STOCK CODE START
bool mayDespawn = true;
#if BENCHMARK
using (var bm = new Benchmark(null, "MayDespawn")) {
#endif
mayDespawn = VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData);
#if BENCHMARK
}
#endif
if (mayDespawn) {
// NON-STOCK CODE END
Singleton.instance.ReleaseVehicle(vehicleId);
} // NON-STOCK CODE
}
}
public void CustomSimulationStep(ushort vehicleID, ref Vehicle vehicleData, ref Vehicle.Frame frameData, ushort leaderID, ref Vehicle leaderData, int lodPhysics) {
bool reversed = (leaderData.m_flags & Vehicle.Flags.Reversed) != (Vehicle.Flags)0;
ushort frontVehicleId = (!reversed) ? vehicleData.m_leadingVehicle : vehicleData.m_trailingVehicle;
VehicleInfo vehicleInfo;
if (leaderID != vehicleID) {
vehicleInfo = leaderData.Info;
} else {
vehicleInfo = this.m_info;
}
TrainAI trainAI = vehicleInfo.m_vehicleAI as TrainAI;
if (frontVehicleId != 0) {
frameData.m_position += frameData.m_velocity * 0.4f;
} else {
frameData.m_position += frameData.m_velocity * 0.5f;
}
frameData.m_swayPosition += frameData.m_swayVelocity * 0.5f;
Vector3 posBeforeWheelRot = frameData.m_position;
Vector3 posAfterWheelRot = frameData.m_position;
Vector3 wheelBaseRot = frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f);
if (reversed) {
posBeforeWheelRot -= wheelBaseRot;
posAfterWheelRot += wheelBaseRot;
} else {
posBeforeWheelRot += wheelBaseRot;
posAfterWheelRot -= wheelBaseRot;
}
float acceleration = this.m_info.m_acceleration;
float braking = this.m_info.m_braking;
float curSpeed = frameData.m_velocity.magnitude;
Vector3 beforeRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posBeforeWheelRot;
float beforeRotToTargetPos1DiffSqrMag = beforeRotToTargetPos1Diff.sqrMagnitude;
Quaternion curInvRot = Quaternion.Inverse(frameData.m_rotation);
Vector3 curveTangent = curInvRot * frameData.m_velocity;
Vector3 forward = Vector3.forward;
Vector3 targetMotion = Vector3.zero;
float targetSpeed = 0f;
float motionFactor = 0.5f;
if (frontVehicleId != 0) {
VehicleManager vehMan = Singleton.instance;
Vehicle.Frame frontVehLastFrameData = vehMan.m_vehicles.m_buffer[(int)frontVehicleId].GetLastFrameData();
VehicleInfo frontVehInfo = vehMan.m_vehicles.m_buffer[(int)frontVehicleId].Info;
float attachOffset;
if ((vehicleData.m_flags & Vehicle.Flags.Inverted) != (Vehicle.Flags)0 != reversed) {
attachOffset = this.m_info.m_attachOffsetBack - this.m_info.m_generatedInfo.m_size.z * 0.5f;
} else {
attachOffset = this.m_info.m_attachOffsetFront - this.m_info.m_generatedInfo.m_size.z * 0.5f;
}
float frontAttachOffset;
if ((vehMan.m_vehicles.m_buffer[(int)frontVehicleId].m_flags & Vehicle.Flags.Inverted) != (Vehicle.Flags)0 != reversed) {
frontAttachOffset = frontVehInfo.m_attachOffsetFront - frontVehInfo.m_generatedInfo.m_size.z * 0.5f;
} else {
frontAttachOffset = frontVehInfo.m_attachOffsetBack - frontVehInfo.m_generatedInfo.m_size.z * 0.5f;
}
Vector3 posMinusAttachOffset = frameData.m_position;
if (reversed) {
posMinusAttachOffset += frameData.m_rotation * new Vector3(0f, 0f, attachOffset);
} else {
posMinusAttachOffset -= frameData.m_rotation * new Vector3(0f, 0f, attachOffset);
}
Vector3 frontPosPlusAttachOffset = frontVehLastFrameData.m_position;
if (reversed) {
frontPosPlusAttachOffset -= frontVehLastFrameData.m_rotation * new Vector3(0f, 0f, frontAttachOffset);
} else {
frontPosPlusAttachOffset += frontVehLastFrameData.m_rotation * new Vector3(0f, 0f, frontAttachOffset);
}
Vector3 frontPosMinusWheelBaseRot = frontVehLastFrameData.m_position;
wheelBaseRot = frontVehLastFrameData.m_rotation * new Vector3(0f, 0f, frontVehInfo.m_generatedInfo.m_wheelBase * 0.5f);
if (reversed) {
frontPosMinusWheelBaseRot += wheelBaseRot;
} else {
frontPosMinusWheelBaseRot -= wheelBaseRot;
}
if (Vector3.Dot(vehicleData.m_targetPos1 - vehicleData.m_targetPos0, (Vector3)vehicleData.m_targetPos0 - posAfterWheelRot) < 0f && vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) {
int someIndex = -1;
InvokeUpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posAfterWheelRot, 0, ref leaderData, ref someIndex, 0, 0, Vector3.SqrMagnitude(posAfterWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f);
beforeRotToTargetPos1DiffSqrMag = 0f;
}
float maxAttachDist = Mathf.Max(Vector3.Distance(posMinusAttachOffset, frontPosPlusAttachOffset), 2f);
float one = 1f;
float maxAttachSqrDist = maxAttachDist * maxAttachDist;
float oneSqr = one * one;
int i = 0;
if (beforeRotToTargetPos1DiffSqrMag < maxAttachSqrDist) {
if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) {
InvokeUpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, posAfterWheelRot, posBeforeWheelRot, 0, ref leaderData, ref i, 1, 2, maxAttachSqrDist, oneSqr);
}
while (i < 4) {
vehicleData.SetTargetPos(i, vehicleData.GetTargetPos(i - 1));
i++;
}
beforeRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posBeforeWheelRot;
beforeRotToTargetPos1DiffSqrMag = beforeRotToTargetPos1Diff.sqrMagnitude;
}
if (vehicleData.m_path != 0u) {
NetManager netMan = Singleton.instance;
byte pathPosIndex = vehicleData.m_pathPositionIndex;
byte lastPathOffset = vehicleData.m_lastPathOffset;
if (pathPosIndex == 255) {
pathPosIndex = 0;
}
PathManager pathMan = Singleton.instance;
PathUnit.Position curPathPos;
if (pathMan.m_pathUnits.m_buffer[vehicleData.m_path].GetPosition(pathPosIndex >> 1, out curPathPos)) {
netMan.m_segments.m_buffer[(int)curPathPos.m_segment].AddTraffic(Mathf.RoundToInt(this.m_info.m_generatedInfo.m_size.z * 3f), this.GetNoiseLevel());
PathUnit.Position nextPathPos; // NON-STOCK CODE
if ((pathPosIndex & 1) == 0 || lastPathOffset == 0 || (leaderData.m_flags & Vehicle.Flags.WaitingPath) != (Vehicle.Flags)0) {
uint laneId = PathManager.GetLaneID(curPathPos);
if (laneId != 0u) {
netMan.m_lanes.m_buffer[laneId].ReserveSpace(this.m_info.m_generatedInfo.m_size.z);
}
} else if (pathMan.m_pathUnits.m_buffer[vehicleData.m_path].GetNextPosition(pathPosIndex >> 1, out nextPathPos)) {
// NON-STOCK CODE START
ushort transitNodeId;
if (curPathPos.m_offset < 128) {
transitNodeId = netMan.m_segments.m_buffer[curPathPos.m_segment].m_startNode;
} else {
transitNodeId = netMan.m_segments.m_buffer[curPathPos.m_segment].m_endNode;
}
bool spaceReservationAllowed = true;
#if BENCHMARK
using (var bm = new Benchmark(null, "IsSpaceReservationAllowed")) {
#endif
spaceReservationAllowed = VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, curPathPos, nextPathPos);
#if BENCHMARK
}
#endif
if (spaceReservationAllowed) {
// NON-STOCK CODE END
uint nextLaneId = PathManager.GetLaneID(nextPathPos);
if (nextLaneId != 0u) {
netMan.m_lanes.m_buffer[nextLaneId].ReserveSpace(this.m_info.m_generatedInfo.m_size.z);
}
} // NON-STOCK CODE
}
}
}
beforeRotToTargetPos1Diff = curInvRot * beforeRotToTargetPos1Diff;
float negTotalAttachLen = -((this.m_info.m_generatedInfo.m_wheelBase + frontVehInfo.m_generatedInfo.m_wheelBase) * 0.5f + attachOffset + frontAttachOffset);
bool hasPath = false;
if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) {
float u1;
float u2;
if (Line3.Intersect(posBeforeWheelRot, vehicleData.m_targetPos1, frontPosMinusWheelBaseRot, negTotalAttachLen, out u1, out u2)) {
targetMotion = beforeRotToTargetPos1Diff * Mathf.Clamp(Mathf.Min(u1, u2) / 0.6f, 0f, 2f);
} else {
Line3.DistanceSqr(posBeforeWheelRot, vehicleData.m_targetPos1, frontPosMinusWheelBaseRot, out u1);
targetMotion = beforeRotToTargetPos1Diff * Mathf.Clamp(u1 / 0.6f, 0f, 2f);
}
hasPath = true;
}
if (hasPath) {
if (Vector3.Dot(frontPosMinusWheelBaseRot - posBeforeWheelRot, posBeforeWheelRot - posAfterWheelRot) < 0f) {
motionFactor = 0f;
}
} else {
float frontPosBeforeToAfterWheelRotDist = Vector3.Distance(frontPosMinusWheelBaseRot, posBeforeWheelRot);
motionFactor = 0f;
targetMotion = curInvRot * ((frontPosMinusWheelBaseRot - posBeforeWheelRot) * (Mathf.Max(0f, frontPosBeforeToAfterWheelRotDist - negTotalAttachLen) / Mathf.Max(1f, frontPosBeforeToAfterWheelRotDist * 0.6f)));
}
} else {
float estimatedFrameDist = (curSpeed + acceleration) * (0.5f + 0.5f * (curSpeed + acceleration) / braking);
float maxSpeedAdd = Mathf.Max(curSpeed + acceleration, 2f);
float meanSpeedAdd = Mathf.Max((estimatedFrameDist - maxSpeedAdd) / 2f, 1f);
float maxSpeedAddSqr = maxSpeedAdd * maxSpeedAdd;
float meanSpeedAddSqr = meanSpeedAdd * meanSpeedAdd;
if (Vector3.Dot(vehicleData.m_targetPos1 - vehicleData.m_targetPos0, (Vector3)vehicleData.m_targetPos0 - posAfterWheelRot) < 0f && vehicleData.m_path != 0u && (leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0) {
int someIndex = -1;
InvokeUpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posAfterWheelRot, leaderID, ref leaderData, ref someIndex, 0, 0, Vector3.SqrMagnitude(posAfterWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f);
beforeRotToTargetPos1DiffSqrMag = 0f;
}
int posIndex = 0;
bool flag3 = false;
if ((beforeRotToTargetPos1DiffSqrMag < maxSpeedAddSqr || vehicleData.m_targetPos3.w < 0.01f) && (leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0) {
if (vehicleData.m_path != 0u) {
InvokeUpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, posAfterWheelRot, posBeforeWheelRot, leaderID, ref leaderData, ref posIndex, 1, 4, maxSpeedAddSqr, meanSpeedAddSqr);
}
if (posIndex < 4) {
flag3 = true;
while (posIndex < 4) {
vehicleData.SetTargetPos(posIndex, vehicleData.GetTargetPos(posIndex - 1));
posIndex++;
}
}
beforeRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posBeforeWheelRot;
beforeRotToTargetPos1DiffSqrMag = beforeRotToTargetPos1Diff.sqrMagnitude;
}
if ((leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0 && this.m_info.m_vehicleType != VehicleInfo.VehicleType.Monorail) {
CustomForceTrafficLights(vehicleID, ref vehicleData, curSpeed > 0.1f); // NON-STOCK CODE
}
if (vehicleData.m_path != 0u) {
NetManager netMan = Singleton.instance;
byte pathPosIndex = vehicleData.m_pathPositionIndex;
byte lastPathOffset = vehicleData.m_lastPathOffset;
if (pathPosIndex == 255) {
pathPosIndex = 0;
}
PathManager pathMan = Singleton.instance;
PathUnit.Position curPathPos;
if (pathMan.m_pathUnits.m_buffer[vehicleData.m_path].GetPosition(pathPosIndex >> 1, out curPathPos)) {
netMan.m_segments.m_buffer[curPathPos.m_segment].AddTraffic(Mathf.RoundToInt(this.m_info.m_generatedInfo.m_size.z * 3f), this.GetNoiseLevel());
PathUnit.Position nextPathPos;
if ((pathPosIndex & 1) == 0 || lastPathOffset == 0 || (leaderData.m_flags & Vehicle.Flags.WaitingPath) != (Vehicle.Flags)0) {
uint laneId = PathManager.GetLaneID(curPathPos);
if (laneId != 0u) {
netMan.m_lanes.m_buffer[laneId].ReserveSpace(this.m_info.m_generatedInfo.m_size.z, vehicleID);
}
} else if (pathMan.m_pathUnits.m_buffer[vehicleData.m_path].GetNextPosition(pathPosIndex >> 1, out nextPathPos)) {
// NON-STOCK CODE START
ushort transitNodeId;
if (curPathPos.m_offset < 128) {
transitNodeId = netMan.m_segments.m_buffer[curPathPos.m_segment].m_startNode;
} else {
transitNodeId = netMan.m_segments.m_buffer[curPathPos.m_segment].m_endNode;
}
bool spaceReservationAllowed = true;
#if BENCHMARK
using (var bm = new Benchmark(null, "IsSpaceReservationAllowed")) {
#endif
spaceReservationAllowed = VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, curPathPos, nextPathPos);
#if BENCHMARK
}
#endif
if (spaceReservationAllowed) {
// NON-STOCK CODE END
uint nextLaneId = PathManager.GetLaneID(nextPathPos);
if (nextLaneId != 0u) {
netMan.m_lanes.m_buffer[nextLaneId].ReserveSpace(this.m_info.m_generatedInfo.m_size.z, vehicleID);
}
} // NON-STOCK CODE
}
}
}
float maxSpeed;
if ((leaderData.m_flags & Vehicle.Flags.Stopped) != (Vehicle.Flags)0) {
maxSpeed = 0f;
} else {
maxSpeed = Mathf.Min(vehicleData.m_targetPos1.w, GetMaxSpeed(leaderID, ref leaderData));
}
beforeRotToTargetPos1Diff = curInvRot * beforeRotToTargetPos1Diff;
if (reversed) {
beforeRotToTargetPos1Diff = -beforeRotToTargetPos1Diff;
}
bool blocked = false;
float forwardLen = 0f;
if (beforeRotToTargetPos1DiffSqrMag > 1f) {
forward = VectorUtils.NormalizeXZ(beforeRotToTargetPos1Diff, out forwardLen);
if (forwardLen > 1f) {
Vector3 fwd = beforeRotToTargetPos1Diff;
maxSpeedAdd = Mathf.Max(curSpeed, 2f);
maxSpeedAddSqr = maxSpeedAdd * maxSpeedAdd;
if (beforeRotToTargetPos1DiffSqrMag > maxSpeedAddSqr) {
float num20 = maxSpeedAdd / Mathf.Sqrt(beforeRotToTargetPos1DiffSqrMag);
fwd.x *= num20;
fwd.y *= num20;
}
if (fwd.z < -1f) {
if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) {
Vector3 targetPos0TargetPos1Diff = vehicleData.m_targetPos1 - vehicleData.m_targetPos0;
targetPos0TargetPos1Diff = curInvRot * targetPos0TargetPos1Diff;
if (reversed) {
targetPos0TargetPos1Diff = -targetPos0TargetPos1Diff;
}
if (targetPos0TargetPos1Diff.z < -0.01f) {
if (beforeRotToTargetPos1Diff.z < Mathf.Abs(beforeRotToTargetPos1Diff.x) * -10f) {
if (curSpeed < 0.01f) {
Reverse(leaderID, ref leaderData);
return;
}
fwd.z = 0f;
beforeRotToTargetPos1Diff = Vector3.zero;
maxSpeed = 0f;
} else {
posBeforeWheelRot = posAfterWheelRot + Vector3.Normalize(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) * this.m_info.m_generatedInfo.m_wheelBase;
posIndex = -1;
InvokeUpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, vehicleData.m_targetPos1, leaderID, ref leaderData, ref posIndex, 0, 0, Vector3.SqrMagnitude(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) + 1f, 1f);
}
} else {
posIndex = -1;
InvokeUpdatePathTargetPositions(trainAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posAfterWheelRot, leaderID, ref leaderData, ref posIndex, 0, 0, Vector3.SqrMagnitude(posAfterWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f);
vehicleData.m_targetPos1 = posBeforeWheelRot;
fwd.z = 0f;
beforeRotToTargetPos1Diff = Vector3.zero;
maxSpeed = 0f;
}
}
motionFactor = 0f;
}
forward = VectorUtils.NormalizeXZ(fwd, out forwardLen);
float curve = Mathf.PI / 2f /* 1.57079637f*/ * (1f - forward.z); // <- constant: a bit inaccurate PI/2
if (forwardLen > 1f) {
curve /= forwardLen;
}
maxSpeed = Mathf.Min(maxSpeed, this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1000f, curve));
float targetDist = forwardLen;
maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos2.w, braking));
targetDist += VectorUtils.LengthXZ(vehicleData.m_targetPos2 - vehicleData.m_targetPos1);
maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos3.w, braking));
targetDist += VectorUtils.LengthXZ(vehicleData.m_targetPos3 - vehicleData.m_targetPos2);
maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, 0f, braking));
if (maxSpeed < curSpeed) {
float brake = Mathf.Max(acceleration, Mathf.Min(braking, curSpeed));
targetSpeed = Mathf.Max(maxSpeed, curSpeed - brake);
} else {
float accel = Mathf.Max(acceleration, Mathf.Min(braking, -curSpeed));
targetSpeed = Mathf.Min(maxSpeed, curSpeed + accel);
}
}
} else if (curSpeed < 0.1f && flag3 && vehicleInfo.m_vehicleAI.ArriveAtDestination(leaderID, ref leaderData)) {
leaderData.Unspawn(leaderID);
return;
}
if ((leaderData.m_flags & Vehicle.Flags.Stopped) == (Vehicle.Flags)0 && maxSpeed < 0.1f) {
blocked = true;
}
if (blocked) {
leaderData.m_blockCounter = (byte)Mathf.Min((int)(leaderData.m_blockCounter + 1), 255);
} else {
leaderData.m_blockCounter = 0;
}
if (forwardLen > 1f) {
if (reversed) {
forward = -forward;
}
targetMotion = forward * targetSpeed;
} else {
if (reversed) {
beforeRotToTargetPos1Diff = -beforeRotToTargetPos1Diff;
}
Vector3 vel = Vector3.ClampMagnitude(beforeRotToTargetPos1Diff * 0.5f - curveTangent, braking);
targetMotion = curveTangent + vel;
}
}
Vector3 springs = targetMotion - curveTangent;
Vector3 targetAfterWheelRotMotion = frameData.m_rotation * targetMotion;
Vector3 posAfterWheelRotToTargetDiff = Vector3.Normalize((Vector3)vehicleData.m_targetPos0 - posAfterWheelRot) * (targetMotion.magnitude * motionFactor);
posBeforeWheelRot += targetAfterWheelRotMotion;
posAfterWheelRot += posAfterWheelRotToTargetDiff;
Vector3 targetPos;
if (reversed) {
frameData.m_rotation = Quaternion.LookRotation(posAfterWheelRot - posBeforeWheelRot);
targetPos = posBeforeWheelRot + frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f);
} else {
frameData.m_rotation = Quaternion.LookRotation(posBeforeWheelRot - posAfterWheelRot);
targetPos = posBeforeWheelRot - frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f);
}
frameData.m_velocity = targetPos - frameData.m_position;
if (frontVehicleId != 0) {
frameData.m_position += frameData.m_velocity * 0.6f;
} else {
frameData.m_position += frameData.m_velocity * 0.5f;
}
frameData.m_swayVelocity = frameData.m_swayVelocity * (1f - this.m_info.m_dampers) - springs * (1f - this.m_info.m_springs) - frameData.m_swayPosition * this.m_info.m_springs;
frameData.m_swayPosition += frameData.m_swayVelocity * 0.5f;
frameData.m_steerAngle = 0f;
frameData.m_travelDistance += targetMotion.z;
frameData.m_lightIntensity.x = ((!reversed) ? 5f : 0f);
frameData.m_lightIntensity.y = ((!reversed) ? 0f : 5f);
frameData.m_lightIntensity.z = 0f;
frameData.m_lightIntensity.w = 0f;
frameData.m_underground = ((vehicleData.m_flags & Vehicle.Flags.Underground) != (Vehicle.Flags)0);
frameData.m_transition = ((vehicleData.m_flags & Vehicle.Flags.Transition) != (Vehicle.Flags)0);
//base.SimulationStep(vehicleID, ref vehicleData, ref frameData, leaderID, ref leaderData, lodPhysics);
}
public void TmCalculateSegmentPositionPathFinder(ushort vehicleID, ref Vehicle vehicleData, PathUnit.Position position, uint laneID, byte offset, out Vector3 pos, out Vector3 dir, out float maxSpeed) {
NetManager instance = Singleton.instance;
instance.m_lanes.m_buffer[laneID].CalculatePositionAndDirection((float)offset * 0.003921569f, out pos, out dir);
NetInfo info = instance.m_segments.m_buffer[(int)position.m_segment].Info;
if (info.m_lanes != null && info.m_lanes.Length > (int)position.m_lane) {
float laneSpeedLimit = 1;
#if BENCHMARK
using (var bm = new Benchmark(null, "GetLockFreeGameSpeedLimit")) {
#endif
laneSpeedLimit = Options.customSpeedLimitsEnabled ? SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(position.m_segment, position.m_lane, laneID, info.m_lanes[position.m_lane]) : info.m_lanes[position.m_lane].m_speedLimit;
#if BENCHMARK
}
#endif
maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, laneSpeedLimit, instance.m_lanes.m_buffer[laneID].m_curve);
} else {
maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1f, 0f);
}
}
public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) {
#if DEBUG
//Log._Debug($"CustomTrainAI.CustomStartPathFind called for vehicle {vehicleID}");
#endif
/// NON-STOCK CODE START ///
ExtVehicleType vehicleType = ExtVehicleType.None;
#if BENCHMARK
using (var bm = new Benchmark(null, "OnStartPathFind")) {
#endif
vehicleType = VehicleStateManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null);
if (vehicleType == ExtVehicleType.None) {
#if DEBUG
Log.Warning($"CustomTrainAI.CustomStartPathFind: Vehicle {vehicleID} does not have a valid vehicle type!");
#endif
vehicleType = ExtVehicleType.RailVehicle;
} else if (vehicleType == ExtVehicleType.CargoTrain) {
vehicleType = ExtVehicleType.CargoVehicle;
}
#if BENCHMARK
}
#endif
/// NON-STOCK CODE END ///
VehicleInfo info = this.m_info;
if ((vehicleData.m_flags & Vehicle.Flags.Spawned) == 0 && Vector3.Distance(startPos, endPos) < 100f) {
startPos = endPos;
}
bool allowUnderground;
bool allowUnderground2;
if (info.m_vehicleType == VehicleInfo.VehicleType.Metro) {
allowUnderground = true;
allowUnderground2 = true;
} else {
allowUnderground = ((vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0);
allowUnderground2 = false;
}
PathUnit.Position startPosA;
PathUnit.Position startPosB;
float startSqrDistA;
float startSqrDistB;
PathUnit.Position endPosA;
PathUnit.Position endPosB;
float endSqrDistA;
float endSqrDistB;
if (CustomPathManager.FindPathPosition(startPos, this.m_transportInfo.m_netService, this.m_transportInfo.m_secondaryNetService, NetInfo.LaneType.Vehicle, info.m_vehicleType, VehicleInfo.VehicleType.None, allowUnderground, false, 32f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB) &&
CustomPathManager.FindPathPosition(endPos, this.m_transportInfo.m_netService, this.m_transportInfo.m_secondaryNetService, NetInfo.LaneType.Vehicle, info.m_vehicleType, VehicleInfo.VehicleType.None, allowUnderground2, false, 32f, out endPosA, out endPosB, out endSqrDistA, out endSqrDistB)) {
if (!startBothWays || startSqrDistB > startSqrDistA * 1.2f) {
startPosB = default(PathUnit.Position);
}
if (!endBothWays || endSqrDistB > endSqrDistA * 1.2f) {
endPosB = default(PathUnit.Position);
}
uint path;
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = ExtCitizenInstance.ExtPathType.None;
args.extVehicleType = vehicleType;
args.vehicleId = vehicleID;
args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
args.buildIndex = Singleton.instance.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = startPosB;
args.endPosA = endPosA;
args.endPosB = endPosB;
args.vehiclePosition = default(PathUnit.Position);
args.laneTypes = NetInfo.LaneType.Vehicle;
args.vehicleTypes = info.m_vehicleType;
args.maxLength = 20000f;
args.isHeavyVehicle = false;
args.hasCombustionEngine = false;
args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData);
args.ignoreFlooded = false;
args.ignoreCosts = false;
args.randomParking = false;
args.stablePath = true;
args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
if (CustomPathManager._instance.CreatePath(out path, ref Singleton.instance.m_randomizer, args)) {
// NON-STOCK CODE END
if (vehicleData.m_path != 0u) {
Singleton.instance.ReleasePath(vehicleData.m_path);
}
vehicleData.m_path = path;
vehicleData.m_flags |= Vehicle.Flags.WaitingPath;
return true;
}
}
return false;
}
public void CustomCheckNextLane(ushort vehicleId, ref Vehicle vehicleData, ref float maxSpeed, PathUnit.Position nextPosition, uint nextLaneId, byte nextOffset, PathUnit.Position refPosition, uint refLaneId, byte refOffset, Bezier3 bezier) {
NetManager netManager = Singleton.instance;
ushort nextSourceNodeId;
if (nextOffset < nextPosition.m_offset) {
nextSourceNodeId = netManager.m_segments.m_buffer[(int)nextPosition.m_segment].m_startNode;
} else {
nextSourceNodeId = netManager.m_segments.m_buffer[(int)nextPosition.m_segment].m_endNode;
}
ushort refTargetNodeId;
if (refOffset == 0) {
refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_startNode;
} else {
refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_endNode;
}
#if DEBUG
bool debug = GlobalConfig.Instance.Debug.Switches[21] && (GlobalConfig.Instance.Debug.NodeId <= 0 || refTargetNodeId == GlobalConfig.Instance.Debug.NodeId) && (GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.None || GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.RailVehicle) && (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId);
if (debug) {
Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}) called.\n" +
$"\trefPosition.m_segment={refPosition.m_segment}, refPosition.m_offset={refPosition.m_offset}\n" +
$"\tnextPosition.m_segment={nextPosition.m_segment}, nextPosition.m_offset={nextPosition.m_offset}\n" +
$"\trefLaneId={refLaneId}, refOffset={refOffset}\n" +
$"\tprevLaneId={nextLaneId}, prevOffset={nextOffset}\n" +
$"\tnextSourceNodeId={nextSourceNodeId}\n" +
$"\trefTargetNodeId={refTargetNodeId}, refTargetNodeId={refTargetNodeId}");
}
#endif
Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData();
float sqrVelocity = lastFrameData.m_velocity.sqrMagnitude;
Vector3 lastPosPlusRot = lastFrameData.m_position;
Vector3 lastPosMinusRot = lastFrameData.m_position;
Vector3 rotationAdd = lastFrameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f);
lastPosPlusRot += rotationAdd;
lastPosMinusRot -= rotationAdd;
float breakingDist = 0.5f * sqrVelocity / this.m_info.m_braking;
float distToTargetAfterRot = Vector3.Distance(lastPosPlusRot, bezier.a);
float distToTargetBeforeRot = Vector3.Distance(lastPosMinusRot, bezier.a);
#if DEBUG
if (debug)
Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): lastPos={lastFrameData.m_position} lastPosMinusRot={lastPosMinusRot} lastPosPlusRot={lastPosPlusRot} rotationAdd={rotationAdd} breakingDist={breakingDist} distToTargetAfterRot={distToTargetAfterRot} distToTargetBeforeRot={distToTargetBeforeRot}");
#endif
if (Mathf.Min(distToTargetAfterRot, distToTargetBeforeRot) >= breakingDist - 5f) {
/*VehicleManager vehMan = Singleton.instance;
ushort firstVehicleId = vehicleData.GetFirstVehicle(vehicleId);
if (VehicleBehaviorManager.Instance.MayDespawn(ref vehMan.m_vehicles.m_buffer[firstVehicleId]) || vehMan.m_vehicles.m_buffer[firstVehicleId].m_blockCounter < 100) {*/ // NON-STOCK CODE
#if DEBUG
if (debug)
Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking for free space on lane {nextLaneId}.");
#endif
if (!netManager.m_lanes.m_buffer[nextLaneId].CheckSpace(1000f, vehicleId)) {
#if DEBUG
if (debug)
Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): No space available on lane {nextLaneId}. ABORT.");
#endif
vehicleData.m_flags2 |= Vehicle.Flags2.Yielding;
vehicleData.m_waitCounter = 0;
maxSpeed = 0f;
return;
}
Vector3 bezierMiddlePoint = bezier.Position(0.5f);
Segment3 segment;
if (Vector3.SqrMagnitude(vehicleData.m_segment.a - bezierMiddlePoint) < Vector3.SqrMagnitude(bezier.a - bezierMiddlePoint)) {
segment = new Segment3(vehicleData.m_segment.a, bezierMiddlePoint);
} else {
segment = new Segment3(bezier.a, bezierMiddlePoint);
}
#if DEBUG
if (debug)
Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking for overlap (1). segment.a={segment.a} segment.b={segment.b}");
#endif
if (segment.LengthSqr() >= 3f) {
segment.a += (segment.b - segment.a).normalized * 2.5f;
if (CustomTrainAI.CheckOverlap(vehicleId, ref vehicleData, segment, vehicleId)) {
#if DEBUG
if (debug)
Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Overlap detected (1). segment.LengthSqr()={segment.LengthSqr()} segment.a={segment.a} ABORT.");
#endif
vehicleData.m_flags2 |= Vehicle.Flags2.Yielding;
vehicleData.m_waitCounter = 0;
maxSpeed = 0f;
return;
}
}
segment = new Segment3(bezierMiddlePoint, bezier.d);
#if DEBUG
if (debug)
Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking for overlap (2). segment.a={segment.a} segment.b={segment.b}");
#endif
if (segment.LengthSqr() >= 1f && CustomTrainAI.CheckOverlap(vehicleId, ref vehicleData, segment, vehicleId)) {
#if DEBUG
if (debug)
Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Overlap detected (2). ABORT.");
#endif
vehicleData.m_flags2 |= Vehicle.Flags2.Yielding;
vehicleData.m_waitCounter = 0;
maxSpeed = 0f;
return;
}
//} // NON-STOCK CODE
//if (this.m_info.m_vehicleType != VehicleInfo.VehicleType.Monorail) { // NON-STOCK CODE
if (nextSourceNodeId == refTargetNodeId) {
#if DEBUG
if (debug)
Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking if vehicle is allowed to change segment.");
#endif
float oldMaxSpeed = maxSpeed;
bool mayChange = true;
#if BENCHMARK
using (var bm = new Benchmark(null, "MayChangeSegment")) {
#endif
mayChange = VehicleBehaviorManager.Instance.MayChangeSegment(vehicleId, ref vehicleData, sqrVelocity, ref refPosition, ref netManager.m_segments.m_buffer[refPosition.m_segment], refTargetNodeId, refLaneId, ref nextPosition, refTargetNodeId, ref netManager.m_nodes.m_buffer[refTargetNodeId], nextLaneId, out maxSpeed);
#if BENCHMARK
}
#endif
if (!mayChange) {
#if DEBUG
if (debug)
Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Vehicle is NOT allowed to change segment. ABORT.");
#endif
return;
} else {
#if BENCHMARK
using (var bm = new Benchmark(null, "UpdateVehiclePosition")) {
#endif
VehicleStateManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/);
#if BENCHMARK
}
#endif
}
maxSpeed = oldMaxSpeed;
}
//} // NON-STOCK CODE
}
}
private void CustomForceTrafficLights(ushort vehicleID, ref Vehicle vehicleData, bool reserveSpace) {
uint pathUnitId = vehicleData.m_path;
if (pathUnitId != 0u) {
NetManager netMan = Singleton.instance;
PathManager pathMan = Singleton.instance;
byte pathPosIndex = vehicleData.m_pathPositionIndex;
if (pathPosIndex == 255) {
pathPosIndex = 0;
}
pathPosIndex = (byte)(pathPosIndex >> 1);
bool stopLoop = false; // NON-STOCK CODE
for (int i = 0; i < 6; i++) {
PathUnit.Position position;
if (!pathMan.m_pathUnits.m_buffer[pathUnitId].GetPosition(pathPosIndex, out position)) {
return;
}
// NON-STOCK CODE START
ushort transitNodeId;
if (position.m_offset < 128) {
transitNodeId = netMan.m_segments.m_buffer[position.m_segment].m_startNode;
} else {
transitNodeId = netMan.m_segments.m_buffer[position.m_segment].m_endNode;
}
#if BENCHMARK
using (var bm = new Benchmark(null, "IsSpaceReservationAllowed")) {
#endif
if (Options.timedLightsEnabled) {
// when a TTL is active only reserve space if it shows green
PathUnit.Position nextPos;
if (pathMan.m_pathUnits.m_buffer[pathUnitId].GetNextPosition(pathPosIndex, out nextPos)) {
if (!VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, position, nextPos)) {
stopLoop = true;
}
}
}
#if BENCHMARK
}
#endif
// NON-STOCK CODE END
if (reserveSpace && i >= 1 && i <= 2) {
uint laneID = PathManager.GetLaneID(position);
if (laneID != 0u) {
reserveSpace = netMan.m_lanes.m_buffer[laneID].ReserveSpace(this.m_info.m_generatedInfo.m_size.z, vehicleID);
}
}
ForceTrafficLights(transitNodeId, position); // NON-STOCK CODE
// NON-STOCK CODE START
if (stopLoop) {
return;
}
// NON-STOCK CODE END
if ((pathPosIndex += 1) >= pathMan.m_pathUnits.m_buffer[pathUnitId].m_positionCount) {
pathUnitId = pathMan.m_pathUnits.m_buffer[pathUnitId].m_nextPathUnit;
pathPosIndex = 0;
if (pathUnitId == 0u) {
return;
}
}
}
}
}
// slightly modified version of TrainAI.ForceTrafficLights(PathUnit.Position)
private static void ForceTrafficLights(ushort transitNodeId, PathUnit.Position position) {
NetManager netMan = Singleton.instance;
if ((netMan.m_nodes.m_buffer[(int)transitNodeId].m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None) {
uint frame = Singleton.instance.m_currentFrameIndex;
uint shiftedNodeId = (uint)(((int)transitNodeId << 8) / 32768);
uint rand = frame - shiftedNodeId & 255u;
RoadBaseAI.TrafficLightState vehicleLightState;
RoadBaseAI.TrafficLightState pedestrianLightState;
bool vehicles;
bool pedestrians;
RoadBaseAI.GetTrafficLightState(transitNodeId, ref netMan.m_segments.m_buffer[(int)position.m_segment], frame - shiftedNodeId, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians);
if (!vehicles && rand >= 196u) {
vehicles = true;
RoadBaseAI.SetTrafficLightState(transitNodeId, ref netMan.m_segments.m_buffer[(int)position.m_segment], frame - shiftedNodeId, vehicleLightState, pedestrianLightState, vehicles, pedestrians);
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
protected static bool CheckOverlap(ushort vehicleID, ref Vehicle vehicleData, Segment3 segment, ushort ignoreVehicle) {
Log.Error("CustomTrainAI.CheckOverlap (1) called.");
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
protected static ushort CheckOverlap(ushort vehicleID, ref Vehicle vehicleData, Segment3 segment, ushort ignoreVehicle, ushort otherID, ref Vehicle otherData, ref bool overlap, Vector3 min, Vector3 max) {
Log.Error("CustomTrainAI.CheckOverlap (2) called.");
return 0;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void InitializePath(ushort vehicleID, ref Vehicle vehicleData) {
Log.Error("CustomTrainAI.InitializePath called");
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void InvokeUpdatePathTargetPositions(TrainAI trainAI, ushort vehicleID, ref Vehicle vehicleData, Vector3 refPos1, Vector3 refPos2, ushort leaderID, ref Vehicle leaderData, ref int index, int max1, int max2, float minSqrDistanceA, float minSqrDistanceB) {
Log.Error($"CustomTrainAI.InvokeUpdatePathTargetPositions called! trainAI={trainAI}");
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void Reverse(ushort leaderID, ref Vehicle leaderData) {
Log.Error("CustomTrainAI.Reverse called");
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static float GetMaxSpeed(ushort leaderID, ref Vehicle leaderData) {
Log.Error("CustomTrainAI.GetMaxSpeed called");
return 0f;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static float CalculateMaxSpeed(float targetDist, float targetSpeed, float maxBraking) {
Log.Error("CustomTrainAI.CalculateMaxSpeed called");
return 0f;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomTramBaseAI.cs
================================================
using ColossalFramework;
using ColossalFramework.Math;
using System;
using System.Collections.Generic;
using System.Text;
using TrafficManager.Custom.PathFinding;
using TrafficManager.State;
using TrafficManager.Geometry;
using UnityEngine;
using TrafficManager.Manager;
using TrafficManager.Traffic;
using CSUtil.Commons;
using System.Runtime.CompilerServices;
using TrafficManager.Manager.Impl;
using TrafficManager.Traffic.Data;
using CSUtil.Commons.Benchmark;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
class CustomTramBaseAI : TramBaseAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`)
public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) {
if ((vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0) {
byte pathFindFlags = Singleton.instance.m_pathUnits.m_buffer[vehicleData.m_path].m_pathFindFlags;
if ((pathFindFlags & PathUnit.FLAG_READY) != 0) {
try {
this.PathfindSuccess(vehicleId, ref vehicleData);
this.PathFindReady(vehicleId, ref vehicleData);
} catch (Exception e) {
Log.Warning($"TramBaseAI.PathFindSuccess/PathFindReady({vehicleId}) threw an exception: {e.ToString()}");
vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath;
Singleton.instance.ReleasePath(vehicleData.m_path);
vehicleData.m_path = 0u;
this.PathfindFailure(vehicleId, ref vehicleData);
return;
}
} else if ((pathFindFlags & PathUnit.FLAG_FAILED) != 0 || vehicleData.m_path == 0) {
vehicleData.m_flags &= ~Vehicle.Flags.WaitingPath;
Singleton.instance.ReleasePath(vehicleData.m_path);
vehicleData.m_path = 0u;
this.PathfindFailure(vehicleId, ref vehicleData);
return;
}
} else {
if ((vehicleData.m_flags & Vehicle.Flags.WaitingSpace) != 0) {
this.TrySpawn(vehicleId, ref vehicleData);
}
}
// NON-STOCK CODE START
#if BENCHMARK
using (var bm = new Benchmark(null, "UpdateVehiclePosition")) {
#endif
VehicleStateManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData);
#if BENCHMARK
}
#endif
if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) {
#if BENCHMARK
using (var bm = new Benchmark(null, "LogTraffic")) {
#endif
// Advanced AI traffic measurement
VehicleStateManager.Instance.LogTraffic(vehicleId);
#if BENCHMARK
}
#endif
}
// NON-STOCK CODE END
VehicleManager instance = Singleton.instance;
VehicleInfo info = instance.m_vehicles.m_buffer[(int)vehicleId].Info;
info.m_vehicleAI.SimulationStep(vehicleId, ref instance.m_vehicles.m_buffer[(int)vehicleId], vehicleId, ref vehicleData, 0);
if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) {
return;
}
ushort trailingVehicle = instance.m_vehicles.m_buffer[(int)vehicleId].m_trailingVehicle;
int num = 0;
while (trailingVehicle != 0) {
info = instance.m_vehicles.m_buffer[(int)trailingVehicle].Info;
info.m_vehicleAI.SimulationStep(trailingVehicle, ref instance.m_vehicles.m_buffer[(int)trailingVehicle], vehicleId, ref vehicleData, 0);
if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) {
return;
}
trailingVehicle = instance.m_vehicles.m_buffer[(int)trailingVehicle].m_trailingVehicle;
if (++num > 16384) {
CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);
break;
}
}
if ((vehicleData.m_flags & (Vehicle.Flags.Spawned | Vehicle.Flags.WaitingPath | Vehicle.Flags.WaitingSpace | Vehicle.Flags.WaitingCargo)) == 0) {
Singleton.instance.ReleaseVehicle(vehicleId);
} else if (vehicleData.m_blockCounter == 255) {
// NON-STOCK CODE START
bool mayDespawn = true;
#if BENCHMARK
using (var bm = new Benchmark(null, "MayDespawn")) {
#endif
mayDespawn = VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData);
#if BENCHMARK
}
#endif
if (mayDespawn) {
// NON-STOCK CODE END
Singleton.instance.ReleaseVehicle(vehicleId);
} // NON-STOCK CODE
}
}
private static void InitializePath(ushort vehicleID, ref Vehicle vehicleData) {
Log.Error("CustomTrainAI.InitializePath called");
}
public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) {
#if DEBUG
//Log._Debug($"CustomTramBaseAI.CustomStartPathFind called for vehicle {vehicleID}");
#endif
#if BENCHMARK
using (var bm = new Benchmark(null, "OnStartPathFind")) {
#endif
VehicleStateManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null);
#if BENCHMARK
}
#endif
VehicleInfo info = this.m_info;
bool allowUnderground;
bool allowUnderground2;
if (info.m_vehicleType == VehicleInfo.VehicleType.Metro) {
allowUnderground = true;
allowUnderground2 = true;
} else {
allowUnderground = ((vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != 0);
allowUnderground2 = false;
}
PathUnit.Position startPosA;
PathUnit.Position startPosB;
float startSqrDistA;
float startSqrDistB;
PathUnit.Position endPosA;
PathUnit.Position endPosB;
float endSqrDistA;
float endSqrDistB;
if (CustomPathManager.FindPathPosition(startPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle, info.m_vehicleType, allowUnderground, false, 32f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB) &&
CustomPathManager.FindPathPosition(endPos, ItemClass.Service.Road, NetInfo.LaneType.Vehicle, info.m_vehicleType, allowUnderground2, false, 32f, out endPosA, out endPosB, out endSqrDistA, out endSqrDistB)) {
if (!startBothWays || startSqrDistB > startSqrDistA * 1.2f) {
startPosB = default(PathUnit.Position);
}
if (!endBothWays || endSqrDistB > endSqrDistA * 1.2f) {
endPosB = default(PathUnit.Position);
}
uint path;
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = ExtCitizenInstance.ExtPathType.None;
args.extVehicleType = ExtVehicleType.Tram;
args.vehicleId = vehicleID;
args.spawned = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0;
args.buildIndex = Singleton.instance.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = startPosB;
args.endPosA = endPosA;
args.endPosB = endPosB;
args.vehiclePosition = default(PathUnit.Position);
args.laneTypes = NetInfo.LaneType.Vehicle;
args.vehicleTypes = info.m_vehicleType;
args.maxLength = 20000f;
args.isHeavyVehicle = false;
args.hasCombustionEngine = false;
args.ignoreBlocked = this.IgnoreBlocked(vehicleID, ref vehicleData);
args.ignoreFlooded = false;
args.ignoreCosts = false;
args.randomParking = false;
args.stablePath = true;
args.skipQueue = true;
if (CustomPathManager._instance.CreatePath(out path, ref Singleton.instance.m_randomizer, args)) {
// NON-STOCK CODE END
if (vehicleData.m_path != 0u) {
Singleton.instance.ReleasePath(vehicleData.m_path);
}
vehicleData.m_path = path;
vehicleData.m_flags |= Vehicle.Flags.WaitingPath;
return true;
}
}
return false;
}
public void CustomCalculateSegmentPosition(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position nextPosition, PathUnit.Position prevPosition, uint prevLaneId, byte prevOffset, PathUnit.Position refPosition, uint refLaneId, byte refOffset, int index, out Vector3 pos, out Vector3 dir, out float maxSpeed) {
NetManager netManager = Singleton.instance;
ushort prevSourceNodeId;
ushort prevTargetNodeId;
if (prevOffset < prevPosition.m_offset) {
prevSourceNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_startNode;
prevTargetNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_endNode;
} else {
prevSourceNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_endNode;
prevTargetNodeId = netManager.m_segments.m_buffer[prevPosition.m_segment].m_startNode;
}
ushort refTargetNodeId;
if (refOffset == 0) {
refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_startNode;
} else {
refTargetNodeId = netManager.m_segments.m_buffer[(int)refPosition.m_segment].m_endNode;
}
#if DEBUG
bool debug = GlobalConfig.Instance.Debug.Switches[21] && (GlobalConfig.Instance.Debug.NodeId <= 0 || refTargetNodeId == GlobalConfig.Instance.Debug.NodeId) && (GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.None || GlobalConfig.Instance.Debug.ExtVehicleType == ExtVehicleType.Tram) && (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId);
if (debug) {
Log._Debug($"CustomTramBaseAI.CustomCalculateSegmentPosition({vehicleId}) called.\n" +
$"\trefPosition.m_segment={refPosition.m_segment}, refPosition.m_offset={refPosition.m_offset}\n" +
$"\tprevPosition.m_segment={prevPosition.m_segment}, prevPosition.m_offset={prevPosition.m_offset}\n" +
$"\tnextPosition.m_segment={nextPosition.m_segment}, nextPosition.m_offset={nextPosition.m_offset}\n" +
$"\trefLaneId={refLaneId}, refOffset={refOffset}\n" +
$"\tprevLaneId={prevLaneId}, prevOffset={prevOffset}\n" +
$"\tprevSourceNodeId={prevSourceNodeId}, prevTargetNodeId={prevTargetNodeId}\n" +
$"\trefTargetNodeId={refTargetNodeId}, refTargetNodeId={refTargetNodeId}\n" +
$"\tindex={index}");
}
#endif
Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData();
float sqrVelocity = lastFrameData.m_velocity.sqrMagnitude;
netManager.m_lanes.m_buffer[prevLaneId].CalculatePositionAndDirection((float)prevOffset * 0.003921569f, out pos, out dir);
Vector3 b = netManager.m_lanes.m_buffer[refLaneId].CalculatePosition((float)refOffset * 0.003921569f);
Vector3 a = lastFrameData.m_position;
Vector3 a2 = lastFrameData.m_position;
Vector3 b2 = lastFrameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f);
a += b2;
a2 -= b2;
float crazyValue = 0.5f * sqrVelocity / this.m_info.m_braking;
float a3 = Vector3.Distance(a, b);
float b3 = Vector3.Distance(a2, b);
if (Mathf.Min(a3, b3) >= crazyValue - 1f) {
// dead stock code
/*Segment3 segment;
segment.a = pos;
if (prevOffset < prevPosition.m_offset) {
segment.b = pos + dir.normalized * this.m_info.m_generatedInfo.m_size.z;
} else {
segment.b = pos - dir.normalized * this.m_info.m_generatedInfo.m_size.z;
}*/
if (prevSourceNodeId == refTargetNodeId) {
#if BENCHMARK
using (var bm = new Benchmark(null, "MayChangeSegment")) {
#endif
if (!VehicleBehaviorManager.Instance.MayChangeSegment(vehicleId, ref vehicleData, sqrVelocity, ref refPosition, ref netManager.m_segments.m_buffer[refPosition.m_segment], refTargetNodeId, refLaneId, ref prevPosition, prevSourceNodeId, ref netManager.m_nodes.m_buffer[prevSourceNodeId], prevLaneId, ref nextPosition, prevTargetNodeId, out maxSpeed)) {
return;
} else {
#if BENCHMARK
using (var bm = new Benchmark(null, "UpdateVehiclePosition")) {
#endif
VehicleStateManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/);
#if BENCHMARK
}
#endif
}
#if BENCHMARK
}
#endif
}
}
NetInfo info = netManager.m_segments.m_buffer[(int)prevPosition.m_segment].Info;
if (info.m_lanes != null && info.m_lanes.Length > (int)prevPosition.m_lane) {
float speedLimit = 1;
#if BENCHMARK
using (var bm = new Benchmark(null, "GetLockFreeGameSpeedLimit")) {
#endif
speedLimit = Options.customSpeedLimitsEnabled ? SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(prevPosition.m_segment, prevPosition.m_lane, prevLaneId, info.m_lanes[prevPosition.m_lane]) : info.m_lanes[prevPosition.m_lane].m_speedLimit;
#if BENCHMARK
}
#endif
maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, speedLimit, netManager.m_lanes.m_buffer[prevLaneId].m_curve);
} else {
maxSpeed = this.CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f);
}
}
public void CustomCalculateSegmentPositionPathFinder(ushort vehicleID, ref Vehicle vehicleData, PathUnit.Position position, uint laneID, byte offset, out Vector3 pos, out Vector3 dir, out float maxSpeed) {
NetManager instance = Singleton.instance;
instance.m_lanes.m_buffer[laneID].CalculatePositionAndDirection((float)offset * 0.003921569f, out pos, out dir);
NetInfo info = instance.m_segments.m_buffer[(int)position.m_segment].Info;
if (info.m_lanes != null && info.m_lanes.Length > (int)position.m_lane) {
float speedLimit = 1;
#if BENCHMARK
using (var bm = new Benchmark(null, "GetLockFreeGameSpeedLimit")) {
#endif
speedLimit = Options.customSpeedLimitsEnabled ? SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(position.m_segment, position.m_lane, laneID, info.m_lanes[position.m_lane]) : info.m_lanes[position.m_lane].m_speedLimit;
#if BENCHMARK
}
#endif
maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, speedLimit, instance.m_lanes.m_buffer[laneID].m_curve);
} else {
maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1f, 0f);
}
}
public void CustomSimulationStep(ushort vehicleID, ref Vehicle vehicleData, ref Vehicle.Frame frameData, ushort leaderID, ref Vehicle leaderData, int lodPhysics) {
#if DEBUG
bool debug = GlobalConfig.Instance.Debug.Switches[16] && GlobalConfig.Instance.Debug.NodeId == vehicleID;
#endif
ushort leadingVehicle = vehicleData.m_leadingVehicle;
uint currentFrameIndex = Singleton.instance.m_currentFrameIndex;
VehicleInfo leaderInfo;
if (leaderID != vehicleID) {
leaderInfo = leaderData.Info;
} else {
leaderInfo = this.m_info;
}
TramBaseAI tramBaseAI = leaderInfo.m_vehicleAI as TramBaseAI;
if (leadingVehicle != 0) {
frameData.m_position += frameData.m_velocity * 0.4f;
} else {
frameData.m_position += frameData.m_velocity * 0.5f;
}
frameData.m_swayPosition += frameData.m_swayVelocity * 0.5f;
Vector3 wheelBaseRot = frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f);
Vector3 posAfterWheelRot = frameData.m_position + wheelBaseRot;
Vector3 posBeforeWheelRot = frameData.m_position - wheelBaseRot;
float acceleration = this.m_info.m_acceleration;
float braking = this.m_info.m_braking;
float curSpeed = frameData.m_velocity.magnitude;
Vector3 afterRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posAfterWheelRot;
float afterRotToTargetPos1DiffSqrMag = afterRotToTargetPos1Diff.sqrMagnitude;
Quaternion curInvRot = Quaternion.Inverse(frameData.m_rotation);
Vector3 curveTangent = curInvRot * frameData.m_velocity;
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): ================================================");
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): leadingVehicle={leadingVehicle} frameData.m_position={frameData.m_position} frameData.m_swayPosition={frameData.m_swayPosition} wheelBaseRot={wheelBaseRot} posAfterWheelRot={posAfterWheelRot} posBeforeWheelRot={posBeforeWheelRot} acceleration={acceleration} braking={braking} curSpeed={curSpeed} afterRotToTargetPos1Diff={afterRotToTargetPos1Diff} afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag} curInvRot={curInvRot} curveTangent={curveTangent} this.m_info.m_generatedInfo.m_wheelBase={this.m_info.m_generatedInfo.m_wheelBase}");
}
#endif
Vector3 forward = Vector3.forward;
Vector3 targetMotion = Vector3.zero;
float targetSpeed = 0f;
float motionFactor = 0.5f;
float turnAngle = 0f;
if (leadingVehicle != 0) {
VehicleManager vehMan = Singleton.instance;
Vehicle.Frame leadingVehLastFrameData = vehMan.m_vehicles.m_buffer[(int)leadingVehicle].GetLastFrameData();
VehicleInfo leadingVehInfo = vehMan.m_vehicles.m_buffer[(int)leadingVehicle].Info;
float attachOffset;
if ((vehicleData.m_flags & Vehicle.Flags.Inverted) != (Vehicle.Flags)0) {
attachOffset = this.m_info.m_attachOffsetBack - this.m_info.m_generatedInfo.m_size.z * 0.5f;
} else {
attachOffset = this.m_info.m_attachOffsetFront - this.m_info.m_generatedInfo.m_size.z * 0.5f;
}
float leadingAttachOffset;
if ((vehMan.m_vehicles.m_buffer[(int)leadingVehicle].m_flags & Vehicle.Flags.Inverted) != (Vehicle.Flags)0) {
leadingAttachOffset = leadingVehInfo.m_attachOffsetFront - leadingVehInfo.m_generatedInfo.m_size.z * 0.5f;
} else {
leadingAttachOffset = leadingVehInfo.m_attachOffsetBack - leadingVehInfo.m_generatedInfo.m_size.z * 0.5f;
}
Vector3 curPosMinusRotAttachOffset = frameData.m_position - frameData.m_rotation * new Vector3(0f, 0f, attachOffset);
Vector3 leadingPosPlusRotAttachOffset = leadingVehLastFrameData.m_position + leadingVehLastFrameData.m_rotation * new Vector3(0f, 0f, leadingAttachOffset);
wheelBaseRot = leadingVehLastFrameData.m_rotation * new Vector3(0f, 0f, leadingVehInfo.m_generatedInfo.m_wheelBase * 0.5f);
Vector3 leadingPosBeforeWheelRot = leadingVehLastFrameData.m_position - wheelBaseRot;
if (Vector3.Dot(vehicleData.m_targetPos1 - vehicleData.m_targetPos0, (Vector3)vehicleData.m_targetPos0 - posBeforeWheelRot) < 0f && vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) {
int someIndex = -1;
InvokeUpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posBeforeWheelRot, 0, ref leaderData, ref someIndex, 0, 0, Vector3.SqrMagnitude(posBeforeWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f);
afterRotToTargetPos1DiffSqrMag = 0f;
}
float attachRotDist = Mathf.Max(Vector3.Distance(curPosMinusRotAttachOffset, leadingPosPlusRotAttachOffset), 2f);
float one = 1f;
float attachRotSqrDist = attachRotDist * attachRotDist;
float oneSqr = one * one;
int i = 0;
if (afterRotToTargetPos1DiffSqrMag < attachRotSqrDist) {
if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) {
InvokeUpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, posBeforeWheelRot, posAfterWheelRot, 0, ref leaderData, ref i, 1, 2, attachRotSqrDist, oneSqr);
}
while (i < 4) {
vehicleData.SetTargetPos(i, vehicleData.GetTargetPos(i - 1));
i++;
}
afterRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posAfterWheelRot;
afterRotToTargetPos1DiffSqrMag = afterRotToTargetPos1Diff.sqrMagnitude;
}
afterRotToTargetPos1Diff = curInvRot * afterRotToTargetPos1Diff;
float negTotalAttachLen = -((this.m_info.m_generatedInfo.m_wheelBase + leadingVehInfo.m_generatedInfo.m_wheelBase) * 0.5f + attachOffset + leadingAttachOffset);
bool hasPath = false;
if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) {
float u1;
float u2;
if (Line3.Intersect(posAfterWheelRot, vehicleData.m_targetPos1, leadingPosBeforeWheelRot, negTotalAttachLen, out u1, out u2)) {
targetMotion = afterRotToTargetPos1Diff * Mathf.Clamp(Mathf.Min(u1, u2) / 0.6f, 0f, 2f);
} else {
Line3.DistanceSqr(posAfterWheelRot, vehicleData.m_targetPos1, leadingPosBeforeWheelRot, out u1);
targetMotion = afterRotToTargetPos1Diff * Mathf.Clamp(u1 / 0.6f, 0f, 2f);
}
hasPath = true;
}
if (hasPath) {
if (Vector3.Dot(leadingPosBeforeWheelRot - posAfterWheelRot, posAfterWheelRot - posBeforeWheelRot) < 0f) {
motionFactor = 0f;
}
} else {
float leadingPosBeforeToAfterWheelRotDist = Vector3.Distance(leadingPosBeforeWheelRot, posAfterWheelRot);
motionFactor = 0f;
targetMotion = curInvRot * ((leadingPosBeforeWheelRot - posAfterWheelRot) * (Mathf.Max(0f, leadingPosBeforeToAfterWheelRotDist - negTotalAttachLen) / Mathf.Max(1f, leadingPosBeforeToAfterWheelRotDist * 0.6f)));
}
} else {
float estimatedFrameDist = (curSpeed + acceleration) * (0.5f + 0.5f * (curSpeed + acceleration) / braking) + (this.m_info.m_generatedInfo.m_size.z - this.m_info.m_generatedInfo.m_wheelBase) * 0.5f;
float maxSpeedAdd = Mathf.Max(curSpeed + acceleration, 2f);
float meanSpeedAdd = Mathf.Max((estimatedFrameDist - maxSpeedAdd) / 2f, 2f);
float maxSpeedAddSqr = maxSpeedAdd * maxSpeedAdd;
float meanSpeedAddSqr = meanSpeedAdd * meanSpeedAdd;
if (Vector3.Dot(vehicleData.m_targetPos1 - vehicleData.m_targetPos0, (Vector3)vehicleData.m_targetPos0 - posBeforeWheelRot) < 0f && vehicleData.m_path != 0u && (leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0) {
int someIndex = -1;
InvokeUpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posBeforeWheelRot, leaderID, ref leaderData, ref someIndex, 0, 0, Vector3.SqrMagnitude(posBeforeWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f);
afterRotToTargetPos1DiffSqrMag = 0f;
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): dot < 0");
}
#endif
}
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Leading vehicle is 0. vehicleData.m_targetPos0={vehicleData.m_targetPos0} vehicleData.m_targetPos1={vehicleData.m_targetPos1} posBeforeWheelRot={posBeforeWheelRot} posBeforeWheelRot={posAfterWheelRot} estimatedFrameDist={estimatedFrameDist} maxSpeedAdd={maxSpeedAdd} meanSpeedAdd={meanSpeedAdd} maxSpeedAddSqr={maxSpeedAddSqr} meanSpeedAddSqr={meanSpeedAddSqr} afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag}");
}
#endif
int posIndex = 0;
bool hasValidPathTargetPos = false;
if ((afterRotToTargetPos1DiffSqrMag < maxSpeedAddSqr || vehicleData.m_targetPos3.w < 0.01f) && (leaderData.m_flags & (Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped)) == (Vehicle.Flags)0) {
if (vehicleData.m_path != 0u) {
InvokeUpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, posBeforeWheelRot, posAfterWheelRot, leaderID, ref leaderData, ref posIndex, 1, 4, maxSpeedAddSqr, meanSpeedAddSqr);
}
if (posIndex < 4) {
hasValidPathTargetPos = true;
while (posIndex < 4) {
vehicleData.SetTargetPos(posIndex, vehicleData.GetTargetPos(posIndex - 1));
posIndex++;
}
}
afterRotToTargetPos1Diff = (Vector3)vehicleData.m_targetPos1 - posAfterWheelRot;
afterRotToTargetPos1DiffSqrMag = afterRotToTargetPos1Diff.sqrMagnitude;
}
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): posIndex={posIndex} hasValidPathTargetPos={hasValidPathTargetPos}");
}
#endif
if (leaderData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) {
NetManager netMan = Singleton.instance;
byte leaderPathPosIndex = leaderData.m_pathPositionIndex;
byte leaderLastPathOffset = leaderData.m_lastPathOffset;
if (leaderPathPosIndex == 255) {
leaderPathPosIndex = 0;
}
int noise;
float leaderLen = 1f + leaderData.CalculateTotalLength(leaderID, out noise);
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): leaderPathPosIndex={leaderPathPosIndex} leaderLastPathOffset={leaderLastPathOffset} leaderPathPosIndex={leaderPathPosIndex} leaderLen={leaderLen}");
}
#endif
// reserve space / add traffic
PathManager pathMan = Singleton.instance;
PathUnit.Position pathPos;
if (pathMan.m_pathUnits.m_buffer[leaderData.m_path].GetPosition(leaderPathPosIndex >> 1, out pathPos)) {
netMan.m_segments.m_buffer[(int)pathPos.m_segment].AddTraffic(Mathf.RoundToInt(leaderLen * 2.5f), noise);
bool reservedSpaceOnCurrentLane = false;
if ((leaderPathPosIndex & 1) == 0 || leaderLastPathOffset == 0) {
uint laneId = PathManager.GetLaneID(pathPos);
if (laneId != 0u) {
Vector3 curPathOffsetPos = netMan.m_lanes.m_buffer[laneId].CalculatePosition((float)pathPos.m_offset * 0.003921569f);
float speedAdd = 0.5f * curSpeed * curSpeed / this.m_info.m_braking;
float afterWheelRotCurPathOffsetDist = Vector3.Distance(posAfterWheelRot, curPathOffsetPos);
float beforeWheelRotCurPathOffsetDist = Vector3.Distance(posBeforeWheelRot, curPathOffsetPos);
if (Mathf.Min(afterWheelRotCurPathOffsetDist, beforeWheelRotCurPathOffsetDist) >= speedAdd - 1f) {
netMan.m_lanes.m_buffer[laneId].ReserveSpace(leaderLen);
reservedSpaceOnCurrentLane = true;
}
}
}
if (!reservedSpaceOnCurrentLane && pathMan.m_pathUnits.m_buffer[leaderData.m_path].GetNextPosition(leaderPathPosIndex >> 1, out pathPos)) {
uint nextLaneId = PathManager.GetLaneID(pathPos);
if (nextLaneId != 0u) {
netMan.m_lanes.m_buffer[nextLaneId].ReserveSpace(leaderLen);
}
}
}
if ((ulong)(currentFrameIndex >> 4 & 15u) == (ulong)((long)(leaderID & 15))) {
// check if vehicle can proceed to next path position
bool canProceeed = false;
uint curLeaderPathId = leaderData.m_path;
int curLeaderPathPosIndex = leaderPathPosIndex >> 1;
int k = 0;
while (k < 5) {
bool invalidPos;
if (PathUnit.GetNextPosition(ref curLeaderPathId, ref curLeaderPathPosIndex, out pathPos, out invalidPos)) {
uint laneId = PathManager.GetLaneID(pathPos);
if (laneId != 0u && !netMan.m_lanes.m_buffer[laneId].CheckSpace(leaderLen)) {
k++;
continue;
}
}
if (invalidPos) {
this.InvalidPath(vehicleID, ref vehicleData, leaderID, ref leaderData);
}
canProceeed = true;
break;
}
if (!canProceeed) {
leaderData.m_flags |= Vehicle.Flags.Congestion;
}
}
}
float maxSpeed;
if ((leaderData.m_flags & Vehicle.Flags.Stopped) != (Vehicle.Flags)0) {
maxSpeed = 0f;
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Vehicle is stopped. maxSpeed={maxSpeed}");
}
#endif
} else {
maxSpeed = Mathf.Min(vehicleData.m_targetPos1.w, GetMaxSpeed(leaderID, ref leaderData));
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Vehicle is not stopped. maxSpeed={maxSpeed}");
}
#endif
}
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Start of second part. curSpeed={curSpeed} curInvRot={curInvRot}");
}
#endif
afterRotToTargetPos1Diff = curInvRot * afterRotToTargetPos1Diff;
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1Diff={afterRotToTargetPos1Diff} (old afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag})");
}
#endif
Vector3 zero = Vector3.zero;
bool blocked = false;
float forwardLen = 0f;
if (afterRotToTargetPos1DiffSqrMag > 1f) { // TODO why is this not recalculated?
forward = VectorUtils.NormalizeXZ(afterRotToTargetPos1Diff, out forwardLen);
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1DiffSqrMag > 1f. forward={forward} forwardLen={forwardLen}");
}
#endif
if (forwardLen > 1f) {
Vector3 fwd = afterRotToTargetPos1Diff;
maxSpeedAdd = Mathf.Max(curSpeed, 2f);
maxSpeedAddSqr = maxSpeedAdd * maxSpeedAdd;
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): forwardLen > 1f. fwd={fwd} maxSpeedAdd={maxSpeedAdd} maxSpeedAddSqr={maxSpeedAddSqr}");
}
#endif
if (afterRotToTargetPos1DiffSqrMag > maxSpeedAddSqr) {
float fwdLimiter = maxSpeedAdd / Mathf.Sqrt(afterRotToTargetPos1DiffSqrMag);
fwd.x *= fwdLimiter;
fwd.y *= fwdLimiter;
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1DiffSqrMag > maxSpeedAddSqr. afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag} maxSpeedAddSqr={maxSpeedAddSqr} fwdLimiter={fwdLimiter} fwd={fwd}");
}
#endif
}
if (fwd.z < -1f) { // !!!
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): fwd.z < -1f. fwd={fwd}");
}
#endif
if (vehicleData.m_path != 0u && (leaderData.m_flags & Vehicle.Flags.WaitingPath) == (Vehicle.Flags)0) {
Vector3 targetPos0TargetPos1Diff = vehicleData.m_targetPos1 - vehicleData.m_targetPos0;
if ((curInvRot * targetPos0TargetPos1Diff).z < -0.01f) { // !!!
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): (curInvRot * targetPos0TargetPos1Diff).z < -0.01f. curInvRot={curInvRot} targetPos0TargetPos1Diff={targetPos0TargetPos1Diff}");
}
#endif
if (afterRotToTargetPos1Diff.z < Mathf.Abs(afterRotToTargetPos1Diff.x) * -10f) { // !!!
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1Diff.z < Mathf.Abs(afterRotToTargetPos1Diff.x) * -10f. fwd={fwd} targetPos0TargetPos1Diff={targetPos0TargetPos1Diff} afterRotToTargetPos1Diff={afterRotToTargetPos1Diff}");
}
#endif
/*fwd.z = 0f;
afterRotToTargetPos1Diff = Vector3.zero;*/
maxSpeed = 0.5f; // NON-STOCK CODE
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): (1) set maxSpeed={maxSpeed}");
}
#endif
} else {
posAfterWheelRot = posBeforeWheelRot + Vector3.Normalize(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) * this.m_info.m_generatedInfo.m_wheelBase;
posIndex = -1;
InvokeUpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, vehicleData.m_targetPos1, leaderID, ref leaderData, ref posIndex, 0, 0, Vector3.SqrMagnitude(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) + 1f, 1f);
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1Diff.z >= Mathf.Abs(afterRotToTargetPos1Diff.x) * -10f. Invoked UpdatePathTargetPositions. posAfterWheelRot={posAfterWheelRot} posBeforeWheelRot={posBeforeWheelRot} this.m_info.m_generatedInfo.m_wheelBase={this.m_info.m_generatedInfo.m_wheelBase}");
}
#endif
}
} else {
posIndex = -1;
InvokeUpdatePathTargetPositions(tramBaseAI, vehicleID, ref vehicleData, vehicleData.m_targetPos0, posBeforeWheelRot, leaderID, ref leaderData, ref posIndex, 0, 0, Vector3.SqrMagnitude(posBeforeWheelRot - (Vector3)vehicleData.m_targetPos0) + 1f, 1f);
vehicleData.m_targetPos1 = posAfterWheelRot;
fwd.z = 0f;
afterRotToTargetPos1Diff = Vector3.zero;
maxSpeed = 0f;
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Vehicle is waiting for a path. posIndex={posIndex} vehicleData.m_targetPos1={vehicleData.m_targetPos1} fwd={fwd} afterRotToTargetPos1Diff={afterRotToTargetPos1Diff} maxSpeed={maxSpeed}");
}
#endif
}
}
motionFactor = 0f;
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Reset motion factor. motionFactor={motionFactor}");
}
#endif
}
forward = VectorUtils.NormalizeXZ(fwd, out forwardLen);
float curve = Mathf.PI/2f /* 1.57079637f*/ * (1f - forward.z); // <- constant: a bit inaccurate PI/2
if (forwardLen > 1f) {
curve /= forwardLen;
}
float targetDist = forwardLen;
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): targetDist={targetDist} fwd={fwd} curve={curve} maxSpeed={maxSpeed}");
}
#endif
if (vehicleData.m_targetPos1.w < 0.1f) {
maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1000f, curve);
maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos1.w, braking * 0.9f));
} else {
maxSpeed = Mathf.Min(maxSpeed, this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1000f, curve));
}
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [1] maxSpeed={maxSpeed}");
}
#endif
maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos2.w, braking * 0.9f));
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [2] maxSpeed={maxSpeed}");
}
#endif
targetDist += VectorUtils.LengthXZ(vehicleData.m_targetPos2 - vehicleData.m_targetPos1);
maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos3.w, braking * 0.9f));
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [3] maxSpeed={maxSpeed}");
}
#endif
targetDist += VectorUtils.LengthXZ(vehicleData.m_targetPos3 - vehicleData.m_targetPos2);
if (vehicleData.m_targetPos3.w < 0.01f) {
targetDist = Mathf.Max(0f, targetDist + (this.m_info.m_generatedInfo.m_wheelBase - this.m_info.m_generatedInfo.m_size.z) * 0.5f);
}
maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, 0f, braking * 0.9f));
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [4] maxSpeed={maxSpeed}");
}
#endif
CarAI.CheckOtherVehicles(vehicleID, ref vehicleData, ref frameData, ref maxSpeed, ref blocked, ref zero, estimatedFrameDist, braking * 0.9f, lodPhysics);
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): CheckOtherVehicles finished. blocked={blocked}");
}
#endif
if (maxSpeed < curSpeed) {
float brake = Mathf.Max(acceleration, Mathf.Min(braking, curSpeed));
targetSpeed = Mathf.Max(maxSpeed, curSpeed - brake);
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): maxSpeed < curSpeed. maxSpeed={maxSpeed} curSpeed={curSpeed} brake={brake} targetSpeed={targetSpeed}");
}
#endif
} else {
float accel = Mathf.Max(acceleration, Mathf.Min(braking, -curSpeed));
targetSpeed = Mathf.Min(maxSpeed, curSpeed + accel);
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): maxSpeed >= curSpeed. maxSpeed={maxSpeed} curSpeed={curSpeed} accel={accel} targetSpeed={targetSpeed}");
}
#endif
}
}
} else if (curSpeed < 0.1f && hasValidPathTargetPos && leaderInfo.m_vehicleAI.ArriveAtDestination(leaderID, ref leaderData)) {
leaderData.Unspawn(leaderID);
return;
}
if ((leaderData.m_flags & Vehicle.Flags.Stopped) == (Vehicle.Flags)0 && maxSpeed < 0.1f) {
#if DEBUG
if (debug) {
Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Vehicle is not stopped but maxSpeed < 0.1. maxSpeed={maxSpeed}");
}
#endif
blocked = true;
}
if (blocked) {
leaderData.m_blockCounter = (byte)Mathf.Min((int)(leaderData.m_blockCounter + 1), 255);
} else {
leaderData.m_blockCounter = 0;
}
if (forwardLen > 1f) {
turnAngle = Mathf.Asin(forward.x) * Mathf.Sign(targetSpeed);
targetMotion = forward * targetSpeed;
} else {
Vector3 vel = Vector3.ClampMagnitude(afterRotToTargetPos1Diff * 0.5f - curveTangent, braking);
targetMotion = curveTangent + vel;
}
}
bool mayBlink = (currentFrameIndex + (uint)leaderID & 16u) != 0u;
Vector3 springs = targetMotion - curveTangent;
Vector3 targetAfterWheelRotMotion = frameData.m_rotation * targetMotion;
Vector3 targetBeforeWheelRotMotion = Vector3.Normalize((Vector3)vehicleData.m_targetPos0 - posBeforeWheelRot) * (targetMotion.magnitude * motionFactor);
targetBeforeWheelRotMotion -= targetAfterWheelRotMotion * (Vector3.Dot(targetAfterWheelRotMotion, targetBeforeWheelRotMotion) / Mathf.Max(1f, targetAfterWheelRotMotion.sqrMagnitude));
posAfterWheelRot += targetAfterWheelRotMotion;
posBeforeWheelRot += targetBeforeWheelRotMotion;
frameData.m_rotation = Quaternion.LookRotation(posAfterWheelRot - posBeforeWheelRot);
Vector3 targetPos = posAfterWheelRot - frameData.m_rotation * new Vector3(0f, 0f, this.m_info.m_generatedInfo.m_wheelBase * 0.5f);
frameData.m_velocity = targetPos - frameData.m_position;
if (leadingVehicle != 0) {
frameData.m_position += frameData.m_velocity * 0.6f;
} else {
frameData.m_position += frameData.m_velocity * 0.5f;
}
frameData.m_swayVelocity = frameData.m_swayVelocity * (1f - this.m_info.m_dampers) - springs * (1f - this.m_info.m_springs) - frameData.m_swayPosition * this.m_info.m_springs;
frameData.m_swayPosition += frameData.m_swayVelocity * 0.5f;
frameData.m_steerAngle = 0f;
frameData.m_travelDistance += targetMotion.z;
if (leadingVehicle != 0) {
frameData.m_lightIntensity = Singleton.instance.m_vehicles.m_buffer[(int)leaderID].GetLastFrameData().m_lightIntensity;
} else {
frameData.m_lightIntensity.x = 5f;
frameData.m_lightIntensity.y = ((springs.z >= -0.1f) ? 0.5f : 5f);
frameData.m_lightIntensity.z = ((turnAngle >= -0.1f || !mayBlink) ? 0f : 5f);
frameData.m_lightIntensity.w = ((turnAngle <= 0.1f || !mayBlink) ? 0f : 5f);
}
frameData.m_underground = ((vehicleData.m_flags & Vehicle.Flags.Underground) != (Vehicle.Flags)0);
frameData.m_transition = ((vehicleData.m_flags & Vehicle.Flags.Transition) != (Vehicle.Flags)0);
//base.SimulationStep(vehicleID, ref vehicleData, ref frameData, leaderID, ref leaderData, lodPhysics);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void InvokeUpdatePathTargetPositions(TramBaseAI tramBaseAI, ushort vehicleID, ref Vehicle vehicleData, Vector3 refPos1, Vector3 refPos2, ushort leaderID, ref Vehicle leaderData, ref int index, int max1, int max2, float minSqrDistanceA, float minSqrDistanceB) {
Log.Error($"CustomTramBaseAI.InvokeUpdatePathTargetPositions called! tramBaseAI={tramBaseAI}");
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static float GetMaxSpeed(ushort leaderID, ref Vehicle leaderData) {
Log.Error("CustomTrainAI.GetMaxSpeed called");
return 0f;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static float CalculateMaxSpeed(float targetDist, float targetSpeed, float maxBraking) {
Log.Error("CustomTrainAI.CalculateMaxSpeed called");
return 0f;
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomTransportLineAI.cs
================================================
using ColossalFramework;
using CSUtil.Commons;
using CSUtil.Commons.Benchmark;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using TrafficManager.Custom.PathFinding;
using TrafficManager.Geometry;
using TrafficManager.State;
using TrafficManager.Traffic;
using TrafficManager.Traffic.Data;
using UnityEngine;
using static TrafficManager.Custom.PathFinding.CustomPathManager;
namespace TrafficManager.Custom.AI {
class CustomTransportLineAI : TransportLineAI { // TODO inherit from NetAI (in order to keep the correct references to `base`)
public static bool CustomStartPathFind(ushort segmentID, ref NetSegment data, ItemClass.Service netService, ItemClass.Service netService2, VehicleInfo.VehicleType vehicleType, bool skipQueue) {
if (data.m_path != 0u) {
Singleton.instance.ReleasePath(data.m_path);
data.m_path = 0u;
}
NetManager netManager = Singleton.instance;
if ((netManager.m_nodes.m_buffer[(int)data.m_startNode].m_flags & NetNode.Flags.Ambiguous) != NetNode.Flags.None) {
for (int i = 0; i < 8; i++) {
ushort segment = netManager.m_nodes.m_buffer[(int)data.m_startNode].GetSegment(i);
if (segment != 0 && segment != segmentID && netManager.m_segments.m_buffer[(int)segment].m_path != 0u) {
return true;
}
}
}
if ((netManager.m_nodes.m_buffer[(int)data.m_endNode].m_flags & NetNode.Flags.Ambiguous) != NetNode.Flags.None) {
for (int j = 0; j < 8; j++) {
ushort segment2 = netManager.m_nodes.m_buffer[(int)data.m_endNode].GetSegment(j);
if (segment2 != 0 && segment2 != segmentID && netManager.m_segments.m_buffer[(int)segment2].m_path != 0u) {
return true;
}
}
}
Vector3 position = netManager.m_nodes.m_buffer[(int)data.m_startNode].m_position;
Vector3 position2 = netManager.m_nodes.m_buffer[(int)data.m_endNode].m_position;
#if DEBUG
bool debug = GlobalConfig.Instance.Debug.Switches[18];
if (debug)
Log._Debug($"TransportLineAI.CustomStartPathFind({segmentID}, ..., {netService}, {netService2}, {vehicleType}, {skipQueue}): startNode={data.m_startNode} @ {position}, endNode={data.m_endNode} @ {position2} -- line: {netManager.m_nodes.m_buffer[(int)data.m_startNode].m_transportLine}/{netManager.m_nodes.m_buffer[(int)data.m_endNode].m_transportLine}");
#endif
PathUnit.Position startPosA;
PathUnit.Position startPosB;
float startSqrDistA;
float startSqrDistB;
if (!CustomPathManager.FindPathPosition(position, netService, netService2, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, vehicleType, true, false, 32f, out startPosA, out startPosB, out startSqrDistA, out startSqrDistB)) {
CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data);
return true;
}
PathUnit.Position endPosA;
PathUnit.Position endPosB;
float endSqrDistA;
float endSqrDistB;
if (!CustomPathManager.FindPathPosition(position2, netService, netService2, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, vehicleType, true, false, 32f, out endPosA, out endPosB, out endSqrDistA, out endSqrDistB)) {
CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data);
return true;
}
if ((netManager.m_nodes.m_buffer[(int)data.m_startNode].m_flags & NetNode.Flags.Fixed) != NetNode.Flags.None) {
startPosB = default(PathUnit.Position);
}
if ((netManager.m_nodes.m_buffer[(int)data.m_endNode].m_flags & NetNode.Flags.Fixed) != NetNode.Flags.None) {
endPosB = default(PathUnit.Position);
}
if (vehicleType != VehicleInfo.VehicleType.None) {
startPosA.m_offset = 128;
startPosB.m_offset = 128;
endPosA.m_offset = 128;
endPosB.m_offset = 128;
} else {
startPosA.m_offset = (byte)Mathf.Clamp(startPosA.m_offset, 1, 254);
startPosB.m_offset = (byte)Mathf.Clamp(startPosB.m_offset, 1, 254);
endPosA.m_offset = (byte)Mathf.Clamp(endPosA.m_offset, 1, 254);
endPosB.m_offset = (byte)Mathf.Clamp(endPosB.m_offset, 1, 254);
}
bool stopLane = CustomTransportLineAI.GetStopLane(ref startPosA, vehicleType);
bool stopLane2 = CustomTransportLineAI.GetStopLane(ref startPosB, vehicleType);
bool stopLane3 = CustomTransportLineAI.GetStopLane(ref endPosA, vehicleType);
bool stopLane4 = CustomTransportLineAI.GetStopLane(ref endPosB, vehicleType);
if ((!stopLane && !stopLane2) || (!stopLane3 && !stopLane4)) {
CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data);
return true;
}
ExtVehicleType extVehicleType = ExtVehicleType.None;
#if BENCHMARK
using (var bm = new Benchmark(null, "extVehicleType")) {
#endif
if ((vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None)
extVehicleType = ExtVehicleType.Bus;
if ((vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail)) != VehicleInfo.VehicleType.None)
extVehicleType = ExtVehicleType.PassengerTrain;
if ((vehicleType & VehicleInfo.VehicleType.Tram) != VehicleInfo.VehicleType.None)
extVehicleType = ExtVehicleType.Tram;
if ((vehicleType & VehicleInfo.VehicleType.Ship) != VehicleInfo.VehicleType.None)
extVehicleType = ExtVehicleType.PassengerShip;
if ((vehicleType & VehicleInfo.VehicleType.Plane) != VehicleInfo.VehicleType.None)
extVehicleType = ExtVehicleType.PassengerPlane;
if ((vehicleType & VehicleInfo.VehicleType.Ferry) != VehicleInfo.VehicleType.None)
extVehicleType = ExtVehicleType.Ferry;
if ((vehicleType & VehicleInfo.VehicleType.Blimp) != VehicleInfo.VehicleType.None)
extVehicleType = ExtVehicleType.Blimp;
if ((vehicleType & VehicleInfo.VehicleType.CableCar) != VehicleInfo.VehicleType.None)
extVehicleType = ExtVehicleType.CableCar;
#if BENCHMARK
}
#endif
//Log._Debug($"Transport line. extVehicleType={extVehicleType}");
uint path;
// NON-STOCK CODE START
PathCreationArgs args;
args.extPathType = ExtCitizenInstance.ExtPathType.None;
args.extVehicleType = extVehicleType;
args.vehicleId = 0;
args.spawned = true;
args.buildIndex = Singleton.instance.m_currentBuildIndex;
args.startPosA = startPosA;
args.startPosB = startPosB;
args.endPosA = endPosA;
args.endPosB = endPosB;
args.vehiclePosition = default(PathUnit.Position);
args.vehicleTypes = vehicleType;
args.isHeavyVehicle = false;
args.hasCombustionEngine = false;
args.ignoreBlocked = true;
args.ignoreFlooded = false;
args.ignoreCosts = false;
args.randomParking = false;
args.stablePath = true;
args.skipQueue = skipQueue;
if (vehicleType == VehicleInfo.VehicleType.None) {
args.laneTypes = NetInfo.LaneType.Pedestrian;
args.maxLength = 160000f;
} else {
args.laneTypes = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle);
args.maxLength = 20000f;
}
if (CustomPathManager._instance.CreatePath(out path, ref Singleton.instance.m_randomizer, args)) {
// NON-STOCK CODE END
if (startPosA.m_segment != 0 && startPosB.m_segment != 0) {
netManager.m_nodes.m_buffer[data.m_startNode].m_flags |= NetNode.Flags.Ambiguous;
} else {
netManager.m_nodes.m_buffer[data.m_startNode].m_flags &= ~NetNode.Flags.Ambiguous;
}
if (endPosA.m_segment != 0 && endPosB.m_segment != 0) {
netManager.m_nodes.m_buffer[data.m_endNode].m_flags |= NetNode.Flags.Ambiguous;
} else {
netManager.m_nodes.m_buffer[data.m_endNode].m_flags &= ~NetNode.Flags.Ambiguous;
}
data.m_path = path;
data.m_flags |= NetSegment.Flags.WaitingPath;
#if DEBUG
if (debug)
Log._Debug($"TransportLineAI.CustomStartPathFind({segmentID}, ..., {netService}, {netService2}, {vehicleType}, {skipQueue}): Started calculating path {path} for extVehicleType={extVehicleType}, startPosA=[seg={startPosA.m_segment}, lane={startPosA.m_lane}, off={startPosA.m_offset}], startPosB=[seg={startPosB.m_segment}, lane={startPosB.m_lane}, off={startPosB.m_offset}], endPosA=[seg={endPosA.m_segment}, lane={endPosA.m_lane}, off={endPosA.m_offset}], endPosB=[seg={endPosB.m_segment}, lane={endPosB.m_lane}, off={endPosB.m_offset}]");
#endif
return false;
}
CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data);
return true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool GetStopLane(ref PathUnit.Position pos, VehicleInfo.VehicleType vehicleType) {
Log.Error($"CustomTransportLineAI.GetStopLane called.");
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void CheckSegmentProblems(ushort segmentID, ref NetSegment data) {
Log.Error($"CustomTransportLineAI.CheckSegmentProblems called.");
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void CheckNodeProblems(ushort nodeID, ref NetNode data) {
Log.Error($"CustomTransportLineAI.CheckNodeProblems called.");
}
}
}
================================================
FILE: TLM/TLM/Custom/AI/CustomVehicleAI.cs
================================================
#define QUEUEDSTATSx
using ColossalFramework;
using ColossalFramework.Math;
using System;
using System.Collections.Generic;
using System.Text;
using TrafficManager.Custom.PathFinding;
using TrafficManager.State;
using TrafficManager.Geometry;
using TrafficManager.TrafficLight;
using UnityEngine;
using TrafficManager.Traffic;
using TrafficManager.Manager;
using TrafficManager.Manager.Impl;
using System.Runtime.CompilerServices;
using CSUtil.Commons;
using CSUtil.Commons.Benchmark;
namespace TrafficManager.Custom.AI {
class CustomVehicleAI : VehicleAI { // TODO inherit from PrefabAI (in order to keep the correct references to `base`)
//private static readonly int MIN_BLOCK_COUNTER_PATH_RECALC_VALUE = 3;
public void CustomCalculateSegmentPosition(ushort vehicleID, ref Vehicle vehicleData, PathUnit.Position nextPosition, PathUnit.Position position, uint laneID, byte offset, PathUnit.Position prevPos, uint prevLaneID, byte prevOffset, int index, out Vector3 pos, out Vector3 dir, out float maxSpeed) {
CalculateSegPos(vehicleID, ref vehicleData, position, laneID, offset, out pos, out dir, out maxSpeed);
}
public void CustomCalculateSegmentPositionPathFinder(ushort vehicleID, ref Vehicle vehicleData, PathUnit.Position position, uint laneID, byte offset, out Vector3 pos, out Vector3 dir, out float maxSpeed) {
CalculateSegPos(vehicleID, ref vehicleData, position, laneID, offset, out pos, out dir, out maxSpeed);
}
protected virtual void CalculateSegPos(ushort vehicleID, ref Vehicle vehicleData, PathUnit.Position position, uint laneID, byte offset, out Vector3 pos, out Vector3 dir, out float maxSpeed) {
NetManager instance = Singleton.instance;
instance.m_lanes.m_buffer[laneID].CalculatePositionAndDirection((float)offset * 0.003921569f, out pos, out dir);
NetInfo info = instance.m_segments.m_buffer[(int)position.m_segment].Info;
if (info.m_lanes != null && info.m_lanes.Length > (int)position.m_lane) {
float laneSpeedLimit;
#if BENCHMARK
using (var bm = new Benchmark(null, "GetLockFreeGameSpeedLimit")) {
#endif
laneSpeedLimit = Options.customSpeedLimitsEnabled ? SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(position.m_segment, position.m_lane, laneID, info.m_lanes[position.m_lane]) : info.m_lanes[position.m_lane].m_speedLimit;
#if BENCHMARK
}
#endif
maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, laneSpeedLimit, instance.m_lanes.m_buffer[laneID].m_curve);
} else {
maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1f, 0f);
}
}
protected void CustomUpdatePathTargetPositions(ushort vehicleID, ref Vehicle vehicleData, Vector3 refPos, ref int targetPosIndex, int maxTargetPosIndex, float minSqrDistanceA, float minSqrDistanceB) {
PathManager pathMan = Singleton.instance;
NetManager netManager = Singleton.instance;
Vector4 targetPos = vehicleData.m_targetPos0;
targetPos.w = 1000f;
float minSqrDistA = minSqrDistanceA;
uint pathId = vehicleData.m_path;
byte finePathPosIndex = vehicleData.m_pathPositionIndex;
byte lastPathOffset = vehicleData.m_lastPathOffset;
// initial position
if (finePathPosIndex == 255) {
finePathPosIndex = 0;
if (targetPosIndex <= 0) {
vehicleData.m_pathPositionIndex = 0;
}
if (!Singleton.instance.m_pathUnits.m_buffer[pathId].CalculatePathPositionOffset(finePathPosIndex >> 1, targetPos, out lastPathOffset)) {
this.InvalidPath(vehicleID, ref vehicleData, vehicleID, ref vehicleData);
return;
}
}
// get current path position, check for errors
PathUnit.Position currentPosition;
if (!pathMan.m_pathUnits.m_buffer[pathId].GetPosition(finePathPosIndex >> 1, out currentPosition)) {
this.InvalidPath(vehicleID, ref vehicleData, vehicleID, ref vehicleData);
return;
}
// get current segment info, check for errors
NetInfo curSegmentInfo = netManager.m_segments.m_buffer[(int)currentPosition.m_segment].Info;
if (curSegmentInfo.m_lanes.Length <= (int)currentPosition.m_lane) {
this.InvalidPath(vehicleID, ref vehicleData, vehicleID, ref vehicleData);
return;
}
// main loop
uint curLaneId = PathManager.GetLaneID(currentPosition);
NetInfo.Lane laneInfo = curSegmentInfo.m_lanes[(int)currentPosition.m_lane];
Bezier3 bezier;
bool firstIter = true; // NON-STOCK CODE
while (true) {
if ((finePathPosIndex & 1) == 0) {
// vehicle is not in transition
if (laneInfo.m_laneType != NetInfo.LaneType.CargoVehicle) {
bool first = true;
while (lastPathOffset != currentPosition.m_offset) {
// catch up and update target position until we get to the current segment offset
if (first) {
first = false;
} else {
float distDiff = Mathf.Sqrt(minSqrDistA) - Vector3.Distance(targetPos, refPos);
int pathOffsetDelta;
if (distDiff < 0f) {
pathOffsetDelta = 4;
} else {
pathOffsetDelta = 4 + Mathf.Max(0, Mathf.CeilToInt(distDiff * 256f / (netManager.m_lanes.m_buffer[curLaneId].m_length + 1f)));
}
if (lastPathOffset > currentPosition.m_offset) {
lastPathOffset = (byte)Mathf.Max((int)lastPathOffset - pathOffsetDelta, (int)currentPosition.m_offset);
} else if (lastPathOffset < currentPosition.m_offset) {
lastPathOffset = (byte)Mathf.Min((int)lastPathOffset + pathOffsetDelta, (int)currentPosition.m_offset);
}
}
Vector3 curSegPos;
Vector3 curSegDir;
float curSegOffset;
this.CalculateSegmentPosition(vehicleID, ref vehicleData, currentPosition, curLaneId, lastPathOffset, out curSegPos, out curSegDir, out curSegOffset);
targetPos.Set(curSegPos.x, curSegPos.y, curSegPos.z, Mathf.Min(targetPos.w, curSegOffset));
float refPosSqrDist = (curSegPos - refPos).sqrMagnitude;
if (refPosSqrDist >= minSqrDistA) {
if (targetPosIndex <= 0) {
vehicleData.m_lastPathOffset = lastPathOffset;
}
vehicleData.SetTargetPos(targetPosIndex++, targetPos);
minSqrDistA = minSqrDistanceB;
refPos = targetPos;
targetPos.w = 1000f;
if (targetPosIndex == maxTargetPosIndex) {
// maximum target position index reached
return;
}
}
}
}
// set vehicle in transition
finePathPosIndex += 1;
lastPathOffset = 0;
if (targetPosIndex <= 0) {
vehicleData.m_pathPositionIndex = finePathPosIndex;
vehicleData.m_lastPathOffset = lastPathOffset;
}
}
if ((vehicleData.m_flags2 & Vehicle.Flags2.EndStop) != 0) {
if (targetPosIndex <= 0) {
targetPos.w = 0f;
if (VectorUtils.LengthSqrXZ(vehicleData.GetLastFrameVelocity()) < 0.01f) {
vehicleData.m_flags2 &= ~Vehicle.Flags2.EndStop;
}
} else {
targetPos.w = 1f;
}
while (targetPosIndex < maxTargetPosIndex) {
vehicleData.SetTargetPos(targetPosIndex++, targetPos);
}
return;
}
// vehicle is in transition now
/*
* coarse path position format: 0..11 (always equals 'fine path position' / 2 == 'fine path position' >> 1)
* fine path position format: 0..23
*/
// find next path unit (or abort if at path end)
int nextCoarsePathPosIndex = (finePathPosIndex >> 1) + 1;
uint nextPathId = pathId;
if (nextCoarsePathPosIndex >= (int)pathMan.m_pathUnits.m_buffer[pathId].m_positionCount) {
nextCoarsePathPosIndex = 0;
nextPathId = pathMan.m_pathUnits.m_buffer[pathId].m_nextPathUnit;
if (nextPathId == 0u) {
if (targetPosIndex <= 0) {
Singleton