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* [![Discord](https://img.shields.io/discord/545065285862948894.svg)](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* [![Discord](https://img.shields.io/discord/545065285862948894.svg)](https://discord.gg/faKUnST) [![Build status](https://ci.appveyor.com/api/projects/status/dehkvuxk8b3h66e7/branch/master?svg=true)](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.instance.ReleasePath(vehicleData.m_path); vehicleData.m_path = 0u; } targetPos.w = 1f; vehicleData.SetTargetPos(targetPosIndex++, targetPos); return; } } // check for errors PathUnit.Position nextPathPos; if (!pathMan.m_pathUnits.m_buffer[nextPathId].GetPosition(nextCoarsePathPosIndex, out nextPathPos)) { this.InvalidPath(vehicleID, ref vehicleData, vehicleID, ref vehicleData); return; } // check for errors NetInfo nextSegmentInfo = netManager.m_segments.m_buffer[(int)nextPathPos.m_segment].Info; if (nextSegmentInfo.m_lanes.Length <= (int)nextPathPos.m_lane) { this.InvalidPath(vehicleID, ref vehicleData, vehicleID, ref vehicleData); return; } // find next lane (emergency vehicles / dynamic lane selection) int bestLaneIndex = nextPathPos.m_lane; if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != (Vehicle.Flags)0) { bestLaneIndex = FindBestLane(vehicleID, ref vehicleData, nextPathPos); } else { // NON-STOCK CODE START if (firstIter && this.m_info.m_vehicleType == VehicleInfo.VehicleType.Car && !this.m_info.m_isLargeVehicle ) { bool mayFindBestLane = false; #if BENCHMARK using (var bm = new Benchmark(null, "MayFindBestLane")) { #endif mayFindBestLane = VehicleBehaviorManager.Instance.MayFindBestLane(vehicleID, ref vehicleData, ref VehicleStateManager.Instance.VehicleStates[vehicleID]); #if BENCHMARK } #endif if (mayFindBestLane) { uint next2PathId = nextPathId; int next2PathPosIndex = nextCoarsePathPosIndex; bool next2Invalid; PathUnit.Position next2PathPos; NetInfo next2SegmentInfo = null; PathUnit.Position next3PathPos; NetInfo next3SegmentInfo = null; PathUnit.Position next4PathPos; if (PathUnit.GetNextPosition(ref next2PathId, ref next2PathPosIndex, out next2PathPos, out next2Invalid)) { next2SegmentInfo = netManager.m_segments.m_buffer[(int)next2PathPos.m_segment].Info; uint next3PathId = next2PathId; int next3PathPosIndex = next2PathPosIndex; bool next3Invalid; if (PathUnit.GetNextPosition(ref next3PathId, ref next3PathPosIndex, out next3PathPos, out next3Invalid)) { next3SegmentInfo = netManager.m_segments.m_buffer[(int)next3PathPos.m_segment].Info; uint next4PathId = next3PathId; int next4PathPosIndex = next3PathPosIndex; bool next4Invalid; if (!PathUnit.GetNextPosition(ref next4PathId, ref next4PathPosIndex, out next4PathPos, out next4Invalid)) { next4PathPos = default(PathUnit.Position); } } else { next3PathPos = default(PathUnit.Position); next4PathPos = default(PathUnit.Position); } } else { next2PathPos = default(PathUnit.Position); next3PathPos = default(PathUnit.Position); next4PathPos = default(PathUnit.Position); } #if BENCHMARK using (var bm = new Benchmark(null, "FindBestLane")) { #endif bestLaneIndex = VehicleBehaviorManager.Instance.FindBestLane(vehicleID, ref vehicleData, ref VehicleStateManager.Instance.VehicleStates[vehicleID], curLaneId, currentPosition, curSegmentInfo, nextPathPos, nextSegmentInfo, next2PathPos, next2SegmentInfo, next3PathPos, next3SegmentInfo, next4PathPos); #if BENCHMARK } #endif } // NON-STOCK CODE END } } // update lane index if (bestLaneIndex != (int)nextPathPos.m_lane) { nextPathPos.m_lane = (byte)bestLaneIndex; pathMan.m_pathUnits.m_buffer[nextPathId].SetPosition(nextCoarsePathPosIndex, nextPathPos); #if BENCHMARK using (var bm = new Benchmark(null, "AddTraffic")) { #endif // prevent multiple lane changes to the same lane from happening at the same time TrafficMeasurementManager.Instance.AddTraffic(nextPathPos.m_segment, nextPathPos.m_lane #if MEASUREDENSITY , VehicleStateManager.Instance.VehicleStates[vehicleID].totalLength #endif , 0); // NON-STOCK CODE #if BENCHMARK } #endif } // check for errors uint nextLaneId = PathManager.GetLaneID(nextPathPos); NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[(int)nextPathPos.m_lane]; ushort curSegStartNodeId = netManager.m_segments.m_buffer[(int)currentPosition.m_segment].m_startNode; ushort curSegEndNodeId = netManager.m_segments.m_buffer[(int)currentPosition.m_segment].m_endNode; ushort nextSegStartNodeId = netManager.m_segments.m_buffer[(int)nextPathPos.m_segment].m_startNode; ushort nextSegEndNodeId = netManager.m_segments.m_buffer[(int)nextPathPos.m_segment].m_endNode; if (nextSegStartNodeId != curSegStartNodeId && nextSegStartNodeId != curSegEndNodeId && nextSegEndNodeId != curSegStartNodeId && nextSegEndNodeId != curSegEndNodeId && ((netManager.m_nodes.m_buffer[(int)curSegStartNodeId].m_flags | netManager.m_nodes.m_buffer[(int)curSegEndNodeId].m_flags) & NetNode.Flags.Disabled) == NetNode.Flags.None && ((netManager.m_nodes.m_buffer[(int)nextSegStartNodeId].m_flags | netManager.m_nodes.m_buffer[(int)nextSegEndNodeId].m_flags) & NetNode.Flags.Disabled) != NetNode.Flags.None) { this.InvalidPath(vehicleID, ref vehicleData, vehicleID, ref vehicleData); return; } // park vehicle if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian) { if (vehicleID != 0 && (vehicleData.m_flags & Vehicle.Flags.Parking) == (Vehicle.Flags)0) { byte inOffset = currentPosition.m_offset; byte outOffset = currentPosition.m_offset; if (this.ParkVehicle(vehicleID, ref vehicleData, currentPosition, nextPathId, nextCoarsePathPosIndex << 1, out outOffset)) { if (outOffset != inOffset) { if (targetPosIndex <= 0) { vehicleData.m_pathPositionIndex = (byte)((int)vehicleData.m_pathPositionIndex & -2); vehicleData.m_lastPathOffset = inOffset; } currentPosition.m_offset = outOffset; pathMan.m_pathUnits.m_buffer[(int)((UIntPtr)pathId)].SetPosition(finePathPosIndex >> 1, currentPosition); } vehicleData.m_flags |= Vehicle.Flags.Parking; } else { this.InvalidPath(vehicleID, ref vehicleData, vehicleID, ref vehicleData); } } return; } // check for errors if ((byte)(nextLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.CargoVehicle | NetInfo.LaneType.TransportVehicle)) == 0) { this.InvalidPath(vehicleID, ref vehicleData, vehicleID, ref vehicleData); return; } // change vehicle if (nextLaneInfo.m_vehicleType != this.m_info.m_vehicleType && this.NeedChangeVehicleType(vehicleID, ref vehicleData, nextPathPos, nextLaneId, nextLaneInfo.m_vehicleType, ref targetPos) ) { float targetPos0ToRefPosSqrDist = ((Vector3)targetPos - refPos).sqrMagnitude; if (targetPos0ToRefPosSqrDist >= minSqrDistA) { vehicleData.SetTargetPos(targetPosIndex++, targetPos); } if (targetPosIndex <= 0) { while (targetPosIndex < maxTargetPosIndex) { vehicleData.SetTargetPos(targetPosIndex++, targetPos); } if (nextPathId != vehicleData.m_path) { Singleton.instance.ReleaseFirstUnit(ref vehicleData.m_path); } vehicleData.m_pathPositionIndex = (byte)(nextCoarsePathPosIndex << 1); PathUnit.CalculatePathPositionOffset(nextLaneId, targetPos, out vehicleData.m_lastPathOffset); if (vehicleID != 0 && !this.ChangeVehicleType(vehicleID, ref vehicleData, nextPathPos, nextLaneId)) { this.InvalidPath(vehicleID, ref vehicleData, vehicleID, ref vehicleData); } } else { while (targetPosIndex < maxTargetPosIndex) { vehicleData.SetTargetPos(targetPosIndex++, targetPos); } } return; } // unset leaving flag if (nextPathPos.m_segment != currentPosition.m_segment && vehicleID != 0) { vehicleData.m_flags &= ~Vehicle.Flags.Leaving; } // calculate next segment offset byte nextSegOffset = 0; if ((vehicleData.m_flags & Vehicle.Flags.Flying) != (Vehicle.Flags)0) { nextSegOffset = (byte)((nextPathPos.m_offset < 128) ? 255 : 0); } else if (curLaneId != nextLaneId && laneInfo.m_laneType != NetInfo.LaneType.CargoVehicle) { PathUnit.CalculatePathPositionOffset(nextLaneId, targetPos, out nextSegOffset); bezier = default(Bezier3); Vector3 curSegDir; float maxSpeed; this.CalculateSegmentPosition(vehicleID, ref vehicleData, currentPosition, curLaneId, currentPosition.m_offset, out bezier.a, out curSegDir, out maxSpeed); bool calculateNextNextPos = lastPathOffset == 0; if (calculateNextNextPos) { if ((vehicleData.m_flags & Vehicle.Flags.Reversed) != (Vehicle.Flags)0) { calculateNextNextPos = (vehicleData.m_trailingVehicle == 0); } else { calculateNextNextPos = (vehicleData.m_leadingVehicle == 0); } } Vector3 nextSegDir; float nextMaxSpeed; if (calculateNextNextPos) { PathUnit.Position nextNextPathPos; if (!pathMan.m_pathUnits.m_buffer[nextPathId].GetNextPosition(nextCoarsePathPosIndex, out nextNextPathPos)) { nextNextPathPos = default(PathUnit.Position); } this.CalculateSegmentPosition(vehicleID, ref vehicleData, nextNextPathPos, nextPathPos, nextLaneId, nextSegOffset, currentPosition, curLaneId, currentPosition.m_offset, targetPosIndex, out bezier.d, out nextSegDir, out nextMaxSpeed); } else { this.CalculateSegmentPosition(vehicleID, ref vehicleData, nextPathPos, nextLaneId, nextSegOffset, out bezier.d, out nextSegDir, out nextMaxSpeed); } if (nextMaxSpeed < 0.01f || (netManager.m_segments.m_buffer[(int)nextPathPos.m_segment].m_flags & (NetSegment.Flags.Collapsed | NetSegment.Flags.Flooded)) != NetSegment.Flags.None) { if (targetPosIndex <= 0) { vehicleData.m_lastPathOffset = lastPathOffset; } targetPos = bezier.a; targetPos.w = 0f; while (targetPosIndex < maxTargetPosIndex) { vehicleData.SetTargetPos(targetPosIndex++, targetPos); } return; } if (currentPosition.m_offset == 0) { curSegDir = -curSegDir; } if (nextSegOffset < nextPathPos.m_offset) { nextSegDir = -nextSegDir; } curSegDir.Normalize(); nextSegDir.Normalize(); float dist; NetSegment.CalculateMiddlePoints(bezier.a, curSegDir, bezier.d, nextSegDir, true, true, out bezier.b, out bezier.c, out dist); if (dist > 1f) { ushort nextNodeId; if (nextSegOffset == 0) { nextNodeId = netManager.m_segments.m_buffer[(int)nextPathPos.m_segment].m_startNode; } else if (nextSegOffset == 255) { nextNodeId = netManager.m_segments.m_buffer[(int)nextPathPos.m_segment].m_endNode; } else { nextNodeId = 0; } float curve = 1.57079637f * (1f + Vector3.Dot(curSegDir, nextSegDir)); if (dist > 1f) { curve /= dist; } nextMaxSpeed = Mathf.Min(nextMaxSpeed, this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1000f, curve)); while (lastPathOffset < 255) { float distDiff = Mathf.Sqrt(minSqrDistA) - Vector3.Distance(targetPos, refPos); int pathOffsetDelta; if (distDiff < 0f) { pathOffsetDelta = 8; } else { pathOffsetDelta = 8 + Mathf.Max(0, Mathf.CeilToInt(distDiff * 256f / (dist + 1f))); } lastPathOffset = (byte)Mathf.Min((int)lastPathOffset + pathOffsetDelta, 255); Vector3 bezierPos = bezier.Position((float)lastPathOffset * 0.003921569f); targetPos.Set(bezierPos.x, bezierPos.y, bezierPos.z, Mathf.Min(targetPos.w, nextMaxSpeed)); float sqrMagnitude2 = (bezierPos - refPos).sqrMagnitude; if (sqrMagnitude2 >= minSqrDistA) { if (targetPosIndex <= 0) { vehicleData.m_lastPathOffset = lastPathOffset; } if (nextNodeId != 0) { this.UpdateNodeTargetPos(vehicleID, ref vehicleData, nextNodeId, ref netManager.m_nodes.m_buffer[(int)nextNodeId], ref targetPos, targetPosIndex); } vehicleData.SetTargetPos(targetPosIndex++, targetPos); minSqrDistA = minSqrDistanceB; refPos = targetPos; targetPos.w = 1000f; if (targetPosIndex == maxTargetPosIndex) { return; } } } } } else { PathUnit.CalculatePathPositionOffset(nextLaneId, targetPos, out nextSegOffset); } // check for arrival if (targetPosIndex <= 0) { if ((netManager.m_segments.m_buffer[nextPathPos.m_segment].m_flags & NetSegment.Flags.Untouchable) != 0 && (netManager.m_segments.m_buffer[currentPosition.m_segment].m_flags & NetSegment.Flags.Untouchable) == NetSegment.Flags.None) { ushort ownerBuildingId = NetSegment.FindOwnerBuilding(nextPathPos.m_segment, 363f); if (ownerBuildingId != 0) { BuildingManager buildingMan = Singleton.instance; BuildingInfo ownerBuildingInfo = buildingMan.m_buildings.m_buffer[ownerBuildingId].Info; InstanceID itemID = default(InstanceID); itemID.Vehicle = vehicleID; ownerBuildingInfo.m_buildingAI.EnterBuildingSegment(ownerBuildingId, ref buildingMan.m_buildings.m_buffer[ownerBuildingId], nextPathPos.m_segment, nextPathPos.m_offset, itemID); } } if (nextCoarsePathPosIndex == 0) { Singleton.instance.ReleaseFirstUnit(ref vehicleData.m_path); } if (nextCoarsePathPosIndex >= (int)(pathMan.m_pathUnits.m_buffer[(int)((UIntPtr)nextPathId)].m_positionCount - 1) && pathMan.m_pathUnits.m_buffer[(int)((UIntPtr)nextPathId)].m_nextPathUnit == 0u && vehicleID != 0) { this.ArrivingToDestination(vehicleID, ref vehicleData); } } // prepare next loop iteration: go to next path position pathId = nextPathId; finePathPosIndex = (byte)(nextCoarsePathPosIndex << 1); lastPathOffset = nextSegOffset; if (targetPosIndex <= 0) { vehicleData.m_pathPositionIndex = finePathPosIndex; vehicleData.m_lastPathOffset = lastPathOffset; vehicleData.m_flags = ((vehicleData.m_flags & ~(Vehicle.Flags.OnGravel | Vehicle.Flags.Underground | Vehicle.Flags.Transition)) | nextSegmentInfo.m_setVehicleFlags); if (this.LeftHandDrive(nextLaneInfo)) { vehicleData.m_flags |= Vehicle.Flags.LeftHandDrive; } else { vehicleData.m_flags &= (Vehicle.Flags.Created | Vehicle.Flags.Deleted | Vehicle.Flags.Spawned | Vehicle.Flags.Inverted | Vehicle.Flags.TransferToTarget | Vehicle.Flags.TransferToSource | Vehicle.Flags.Emergency1 | Vehicle.Flags.Emergency2 | Vehicle.Flags.WaitingPath | Vehicle.Flags.Stopped | Vehicle.Flags.Leaving | Vehicle.Flags.Arriving | Vehicle.Flags.Reversed | Vehicle.Flags.TakingOff | Vehicle.Flags.Flying | Vehicle.Flags.Landing | Vehicle.Flags.WaitingSpace | Vehicle.Flags.WaitingCargo | Vehicle.Flags.GoingBack | Vehicle.Flags.WaitingTarget | Vehicle.Flags.Importing | Vehicle.Flags.Exporting | Vehicle.Flags.Parking | Vehicle.Flags.CustomName | Vehicle.Flags.OnGravel | Vehicle.Flags.WaitingLoading | Vehicle.Flags.Congestion | Vehicle.Flags.DummyTraffic | Vehicle.Flags.Underground | Vehicle.Flags.Transition | Vehicle.Flags.InsideBuilding); } } currentPosition = nextPathPos; curLaneId = nextLaneId; laneInfo = nextLaneInfo; firstIter = false; // NON-STOCK CODE } } [MethodImpl(MethodImplOptions.NoInlining)] private static int FindBestLane(ushort vehicleID, ref Vehicle vehicleData, PathUnit.Position position) { Log.Error("CustomVehicleAI.FindBestLane called"); return 0; } } } ================================================ FILE: TLM/TLM/Custom/AI/README.md ================================================ # TM:PE -- /Custom/AI Detoured *AI classes. ## Classes - **CustomAmbulanceAI**: Path-finding detours for ambulances. - **CustomBuildingAI**: Building detours. Used when the Parking AI is active. - **CustomBusAI**: Path-finding detours for busses. - **CustomCarAI**: Path-finding detours for cars. Updates current vehicle positions and prevents despawning (CustomSimulationStep), implements reckless driving, checks for custom speed limits (CalcMaxSpeed) and makes cars obey both priority rules and custom traffic lights (call to MayChangeSegment in CustomCalculateSegmentPosition). - **CustomCargoTruckAI**: Path-finding detours for all kinds of cargo trucks. - **CustomCitizenAI**: Path-finding detours for citizens (= both residents and tourists). Manages usage of public transport and spawning of pocket cars. - **CustomCommonBuildingAI**: Common building detours. Used when the Parking AI is active. - **CustomFireTruckAI**: Path-finding detours for fire trucks. - **CustomHumanAI**: Implements checks for custom traffic lights for humans. - **CustomPassengerCarAI**: Path-finding detours for passenger cars. - **CustomPoliceCarAI**: Path-finding detours for police cars. - **CustomResidentAI**: Parking AI detours for residents. - **CustomRoadAI**: Manages traffic density and current speed measurement values for every segment (CustomSegmentSimulationStep). Initiates the timed traffic light simulation (CustomNodeSimulationStep). Detours traffic light getters/setters in order to allow for custom traffic lights. - **CustomShipAI**: Path-finding detours for ships. - **CustomTaxiAI**: Path-finding detours for taxis. - **CustomTouristAI**: Parking AI detours for residents. - **CustomTrainAI**: Path-finding detours for trains. Updates current vehicle positions and prevents despawning (CustomSimulationStep), checks for custom speed limits (TmCalculateSegmentPositionPathFinder) and makes trains obey both priority rules and custom traffic lights (call to MayChangeSegment in CustomCheckNextLane). - **CustomTramBaseAI**: Path-finding detours for trams. Updates current vehicle positions and prevents despawning (CustomSimulationStep), checks for custom speed limits (CustomCalculateSegmentPosition and CustomCalculateSegmentPositionPathFinder) and makes trams obey both priority rules and custom traffic lights (call to MayChangeSegment in CustomCalculateSegmentPosition). - **CustomTransportLineAI**: Path-finding detours for public transport lines. - **CustomVehicleAI**: Implements checks whether a vehicle may move to the next segment (MayChangeSegment). This includes checking priority rules and custom traffic lights. Checks for custom speed limits (CalculateSegPos). ================================================ FILE: TLM/TLM/Custom/Data/CustomVehicle.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using CSUtil.Commons; using CSUtil.Commons.Benchmark; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Manager; using TrafficManager.Manager.Impl; using UnityEngine; namespace TrafficManager.Custom.Data { public static class CustomVehicle { public static void Spawn(ref Vehicle vehicleData, ushort vehicleId) { //Log._Debug($"CustomVehicle.Spawn({vehicleId}) called."); VehicleManager vehManager = Singleton.instance; VehicleInfo vehicleInfo = vehicleData.Info; if ((vehicleData.m_flags & Vehicle.Flags.Spawned) == (Vehicle.Flags)0) { vehicleData.m_flags |= Vehicle.Flags.Spawned; vehManager.AddToGrid(vehicleId, ref vehicleData, vehicleInfo.m_isLargeVehicle); } if (vehicleData.m_leadingVehicle == 0 && vehicleData.m_trailingVehicle != 0) { ushort trailingVehicle = vehicleData.m_trailingVehicle; int numIter = 0; while (trailingVehicle != 0) { vehManager.m_vehicles.m_buffer[trailingVehicle].Spawn(trailingVehicle); trailingVehicle = vehManager.m_vehicles.m_buffer[trailingVehicle].m_trailingVehicle; if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } } } if (vehicleData.m_leadingVehicle == 0 && vehicleData.m_trailingVehicle == 0 && vehicleInfo.m_trailers != null) { bool hasTrailers = vehicleInfo.m_vehicleAI.VerticalTrailers(); ushort curVehicleId = vehicleId; bool reversed = (vehManager.m_vehicles.m_buffer[curVehicleId].m_flags & Vehicle.Flags.Reversed) != (Vehicle.Flags)0; Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData(); float length = (!hasTrailers) ? (vehicleInfo.m_generatedInfo.m_size.z * 0.5f) : 0f; length -= (((vehicleData.m_flags & Vehicle.Flags.Inverted) == (Vehicle.Flags)0) ? vehicleInfo.m_attachOffsetBack : vehicleInfo.m_attachOffsetFront); Randomizer randomizer = new Randomizer((int)vehicleId); int trailerCount = 0; for (int i = 0; i < vehicleInfo.m_trailers.Length; i++) { if (randomizer.Int32(100u) < vehicleInfo.m_trailers[i].m_probability) { VehicleInfo trailerInfo = vehicleInfo.m_trailers[i].m_info; bool inverted = randomizer.Int32(100u) < vehicleInfo.m_trailers[i].m_invertProbability; length += ((!hasTrailers) ? (trailerInfo.m_generatedInfo.m_size.z * 0.5f) : trailerInfo.m_generatedInfo.m_size.y); length -= ((!inverted) ? trailerInfo.m_attachOffsetFront : trailerInfo.m_attachOffsetBack); Vector3 position = lastFrameData.m_position - lastFrameData.m_rotation * new Vector3(0f, (!hasTrailers) ? 0f : length, (!hasTrailers) ? length : 0f); ushort trailerVehicleId; if (vehManager.CreateVehicle(out trailerVehicleId, ref Singleton.instance.m_randomizer, trailerInfo, position, (TransferManager.TransferReason)vehicleData.m_transferType, false, false)) { vehManager.m_vehicles.m_buffer[(int)curVehicleId].m_trailingVehicle = trailerVehicleId; vehManager.m_vehicles.m_buffer[(int)trailerVehicleId].m_leadingVehicle = curVehicleId; vehManager.m_vehicles.m_buffer[(int)trailerVehicleId].m_gateIndex = vehicleData.m_gateIndex; if (inverted) { vehManager.m_vehicles.m_buffer[trailerVehicleId].m_flags |= Vehicle.Flags.Inverted; } if (reversed) { vehManager.m_vehicles.m_buffer[trailerVehicleId].m_flags |= Vehicle.Flags.Reversed; } vehManager.m_vehicles.m_buffer[(int)trailerVehicleId].m_frame0.m_rotation = lastFrameData.m_rotation; vehManager.m_vehicles.m_buffer[(int)trailerVehicleId].m_frame1.m_rotation = lastFrameData.m_rotation; vehManager.m_vehicles.m_buffer[(int)trailerVehicleId].m_frame2.m_rotation = lastFrameData.m_rotation; vehManager.m_vehicles.m_buffer[(int)trailerVehicleId].m_frame3.m_rotation = lastFrameData.m_rotation; trailerInfo.m_vehicleAI.FrameDataUpdated(trailerVehicleId, ref vehManager.m_vehicles.m_buffer[(int)trailerVehicleId], ref vehManager.m_vehicles.m_buffer[(int)trailerVehicleId].m_frame0); CustomVehicle.Spawn(ref vehManager.m_vehicles.m_buffer[(int)trailerVehicleId], trailerVehicleId); // NON-STOCK CODE curVehicleId = trailerVehicleId; } length += ((!hasTrailers) ? (trailerInfo.m_generatedInfo.m_size.z * 0.5f) : 0f); length -= ((!inverted) ? trailerInfo.m_attachOffsetBack : trailerInfo.m_attachOffsetFront); if (++trailerCount == vehicleInfo.m_maxTrailerCount) { break; } } } } // NON-STOCK CODE START #if BENCHMARK using (var bm = new Benchmark(null, "OnSpawnVehicle")) { #endif VehicleStateManager.Instance.OnSpawnVehicle(vehicleId, ref vehicleData); #if BENCHMARK } #endif // NON-STOCK CODE END } public static void Unspawn(ref Vehicle vehicleData, ushort vehicleId) { //Log._Debug($"CustomVehicle.Unspawn({vehicleId}) called."); // NON-STOCK CODE START #if BENCHMARK using (var bm = new Benchmark(null, "OnDespawnVehicle")) { #endif VehicleStateManager.Instance.OnDespawnVehicle(vehicleId, ref vehicleData); #if BENCHMARK } #endif // NON-STOCK CODE END VehicleManager vehManager = Singleton.instance; if (vehicleData.m_leadingVehicle == 0 && vehicleData.m_trailingVehicle != 0) { ushort curVehicleId = vehicleData.m_trailingVehicle; vehicleData.m_trailingVehicle = 0; int numIters = 0; while (curVehicleId != 0) { ushort trailingVehicleId = vehManager.m_vehicles.m_buffer[(int)curVehicleId].m_trailingVehicle; vehManager.m_vehicles.m_buffer[(int)curVehicleId].m_leadingVehicle = 0; vehManager.m_vehicles.m_buffer[(int)curVehicleId].m_trailingVehicle = 0; vehManager.ReleaseVehicle(curVehicleId); curVehicleId = trailingVehicleId; if (++numIters > 16384) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } } } if ((vehicleData.m_flags & Vehicle.Flags.Spawned) != (Vehicle.Flags)0) { VehicleInfo info = vehicleData.Info; if (info != null) { vehManager.RemoveFromGrid(vehicleId, ref vehicleData, info.m_isLargeVehicle); } vehicleData.m_flags &= ~Vehicle.Flags.Spawned; } } } } ================================================ FILE: TLM/TLM/Custom/Data/README.md ================================================ # TM:PE -- /Custom/Data Detoured data structs. ## Classes - **CustomVehicle**: Detoured for checking when vehicle are spawned/unspawned. ================================================ FILE: TLM/TLM/Custom/Manager/CustomCitizenManager.cs ================================================ using ColossalFramework.Math; using CSUtil.Commons; using CSUtil.Commons.Benchmark; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; using TrafficManager.Geometry; using TrafficManager.Manager; using TrafficManager.Manager.Impl; using UnityEngine; namespace TrafficManager.Custom.Manager { public class CustomCitizenManager : CitizenManager { public void CustomReleaseCitizenInstance(ushort instanceId) { #if BENCHMARK using (var bm = new Benchmark(null, "OnReleaseInstance")) { #endif ExtCitizenInstanceManager.Instance.OnReleaseInstance(instanceId); #if BENCHMARK } #endif this.ReleaseCitizenInstanceImplementation(instanceId, ref this.m_instances.m_buffer[(int)instanceId]); } public void CustomReleaseCitizen(uint citizenId) { #if BENCHMARK using (var bm = new Benchmark(null, "OnReleaseCitizen")) { #endif ExtCitizenManager.Instance.OnReleaseCitizen(citizenId); #if BENCHMARK } #endif this.ReleaseCitizenImplementation(citizenId, ref this.m_citizens.m_buffer[citizenId]); } [MethodImpl(MethodImplOptions.NoInlining)] private void ReleaseCitizenInstanceImplementation(ushort instanceId, ref CitizenInstance instanceData) { Log.Error("CustomCitizenManager.ReleaseCitizenInstanceImplementation called."); } [MethodImpl(MethodImplOptions.NoInlining)] private void ReleaseCitizenImplementation(uint citizenId, ref Citizen citizen) { Log.Error("CustomCitizenManager.ReleaseCitizenImplementation called."); } } } ================================================ FILE: TLM/TLM/Custom/Manager/CustomNetManager.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.Geometry; using TrafficManager.Geometry.Impl; using TrafficManager.State; using UnityEngine; namespace TrafficManager.Custom.Manager { public class CustomNetManager : NetManager { public void CustomFinalizeSegment(ushort segment, ref NetSegment data) { Vector3 vector = (this.m_nodes.m_buffer[(int)data.m_startNode].m_position + this.m_nodes.m_buffer[(int)data.m_endNode].m_position) * 0.5f; int num = Mathf.Clamp((int)(vector.x / 64f + 135f), 0, 269); int num2 = Mathf.Clamp((int)(vector.z / 64f + 135f), 0, 269); int num3 = num2 * 270 + num; ushort num4 = 0; ushort num5 = this.m_segmentGrid[num3]; int num6 = 0; while (num5 != 0) { if (num5 == segment) { if (num4 == 0) { this.m_segmentGrid[num3] = data.m_nextGridSegment; } else { this.m_segments.m_buffer[(int)num4].m_nextGridSegment = data.m_nextGridSegment; } break; } num4 = num5; num5 = this.m_segments.m_buffer[(int)num5].m_nextGridSegment; if (++num6 > 65536) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } } data.m_nextGridSegment = 0; // NON-STOCK CODE START #if BENCHMARK using (var bm = new Benchmark(null, "StartRecalculation")) { #endif try { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log.Warning($"CustomNetManager: CustomFinalizeSegment {segment}"); #endif SegmentGeometry.Get(segment, true).StartRecalculation(GeometryCalculationMode.Propagate); } catch (Exception e) { Log.Error($"Error occured in CustomNetManager.CustomFinalizeSegment @ seg. {segment}: " + e.ToString()); } #if BENCHMARK } #endif // NON-STOCK CODE END } public void CustomUpdateSegment(ushort segment, ushort fromNode, int level) { this.m_updatedSegments[segment >> 6] |= 1uL << (int)segment; this.m_segmentsUpdated = true; if (level <= 0) { ushort startNode = this.m_segments.m_buffer[(int)segment].m_startNode; ushort endNode = this.m_segments.m_buffer[(int)segment].m_endNode; if (startNode != 0 && startNode != fromNode) { this.UpdateNode(startNode, segment, level + 1); } if (endNode != 0 && endNode != fromNode) { this.UpdateNode(endNode, segment, level + 1); } } // NON-STOCK CODE START #if BENCHMARK using (var bm = new Benchmark(null, "StartRecalculation")) { #endif try { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[5]) Log.Warning($"CustomNetManager: CustomUpdateSegment {segment}"); #endif SegmentGeometry.Get(segment, true).StartRecalculation(GeometryCalculationMode.Propagate); } catch (Exception e) { Log.Error($"Error occured in CustomNetManager.CustomUpdateSegment @ seg. {segment}: " + e.ToString()); } #if BENCHMARK } #endif // NON-STOCK CODE END } // TODO remove #if DEBUG private void CustomMoveNode(ushort node, ref NetNode data, Vector3 position) { var connClass = data.Info.GetConnectionClass(); if (connClass == null || connClass.m_service == ItemClass.Service.PublicTransport) { Log.Warning($"CustomNetManager.CustomMoveNode({node}, ..., {position}): old position: {data.m_position} -- flags: {data.m_flags}, problems: {data.m_problems}, transport line: {data.m_transportLine}"); } for (int i = 0; i < 8; i++) { ushort segment = data.GetSegment(i); if (segment != 0) { CustomFinalizeSegment(segment, ref this.m_segments.m_buffer[(int)segment]); } } this.FinalizeNode(node, ref data); data.m_position = position; this.InitializeNode(node, ref data); for (int j = 0; j < 8; j++) { ushort segment2 = data.GetSegment(j); if (segment2 != 0) { InitializeSegment(segment2, ref this.m_segments.m_buffer[(int)segment2]); } } this.UpdateNode(node); } // TODO remove [MethodImpl(MethodImplOptions.NoInlining)] private void FinalizeNode(ushort node, ref NetNode data) { Log.Error($"CustomNetManager.FinalizeNode called"); } // TODO remove [MethodImpl(MethodImplOptions.NoInlining)] private void InitializeNode(ushort node, ref NetNode data) { Log.Error($"CustomNetManager.InitializeNode called"); } // TODO remove [MethodImpl(MethodImplOptions.NoInlining)] private void InitializeSegment(ushort segmentId, ref NetSegment data) { Log.Error($"CustomNetManager.InitializeNode called"); } #endif } } ================================================ FILE: TLM/TLM/Custom/Manager/CustomVehicleManager.cs ================================================ using ColossalFramework.Math; using CSUtil.Commons; using CSUtil.Commons.Benchmark; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; using TrafficManager.Geometry; using TrafficManager.Manager; using TrafficManager.Manager.Impl; using UnityEngine; namespace TrafficManager.Custom.Manager { public class CustomVehicleManager : VehicleManager { public void CustomReleaseVehicle(ushort vehicleId) { #if DEBUG //Log._Debug($"CustomVehicleManager.CustomReleaseVehicle({vehicleId})"); #endif #if BENCHMARK using (var bm = new Benchmark(null, "OnReleaseVehicle")) { #endif VehicleStateManager.Instance.OnReleaseVehicle(vehicleId, ref this.m_vehicles.m_buffer[vehicleId]); #if BENCHMARK } #endif ReleaseVehicleImplementation(vehicleId, ref this.m_vehicles.m_buffer[vehicleId]); } public bool CustomCreateVehicle(out ushort vehicleId, ref Randomizer r, VehicleInfo info, Vector3 position, TransferManager.TransferReason type, bool transferToSource, bool transferToTarget) { // NON-STOCK CODE START #if BENCHMARK using (var bm = new Benchmark(null, "keep-spare-vehicles")) { #endif if (this.m_vehicleCount > VehicleManager.MAX_VEHICLE_COUNT - 5) { // prioritize service vehicles and public transport when hitting the vehicle limit ItemClass.Service service = info.GetService(); if (service == ItemClass.Service.Residential || service == ItemClass.Service.Industrial || service == ItemClass.Service.Commercial || service == ItemClass.Service.Office) { vehicleId = 0; return false; } } #if BENCHMARK } #endif // NON-STOCK CODE END ushort vehId; if (this.m_vehicles.CreateItem(out vehId, ref r)) { vehicleId = vehId; Vehicle.Frame frame = new Vehicle.Frame(position, Quaternion.identity); this.m_vehicles.m_buffer[(int)vehicleId].m_flags = Vehicle.Flags.Created; this.m_vehicles.m_buffer[(int)vehicleId].m_flags2 = (Vehicle.Flags2)0; if (transferToSource) { this.m_vehicles.m_buffer[vehicleId].m_flags = (this.m_vehicles.m_buffer[vehicleId].m_flags | Vehicle.Flags.TransferToSource); } if (transferToTarget) { this.m_vehicles.m_buffer[vehicleId].m_flags = (this.m_vehicles.m_buffer[vehicleId].m_flags | Vehicle.Flags.TransferToTarget); } this.m_vehicles.m_buffer[(int)vehicleId].Info = info; this.m_vehicles.m_buffer[(int)vehicleId].m_frame0 = frame; this.m_vehicles.m_buffer[(int)vehicleId].m_frame1 = frame; this.m_vehicles.m_buffer[(int)vehicleId].m_frame2 = frame; this.m_vehicles.m_buffer[(int)vehicleId].m_frame3 = frame; this.m_vehicles.m_buffer[(int)vehicleId].m_targetPos0 = Vector4.zero; this.m_vehicles.m_buffer[(int)vehicleId].m_targetPos1 = Vector4.zero; this.m_vehicles.m_buffer[(int)vehicleId].m_targetPos2 = Vector4.zero; this.m_vehicles.m_buffer[(int)vehicleId].m_targetPos3 = Vector4.zero; this.m_vehicles.m_buffer[(int)vehicleId].m_sourceBuilding = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_targetBuilding = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_transferType = (byte)type; this.m_vehicles.m_buffer[(int)vehicleId].m_transferSize = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_waitCounter = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_blockCounter = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_nextGridVehicle = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_nextOwnVehicle = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_nextGuestVehicle = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_nextLineVehicle = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_transportLine = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_leadingVehicle = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_trailingVehicle = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_cargoParent = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_firstCargo = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_nextCargo = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_citizenUnits = 0u; this.m_vehicles.m_buffer[(int)vehicleId].m_path = 0u; this.m_vehicles.m_buffer[(int)vehicleId].m_lastFrame = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_pathPositionIndex = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_lastPathOffset = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_gateIndex = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_waterSource = 0; this.m_vehicles.m_buffer[(int)vehicleId].m_touristCount = 0; info.m_vehicleAI.CreateVehicle(vehicleId, ref this.m_vehicles.m_buffer[vehicleId]); info.m_vehicleAI.FrameDataUpdated(vehicleId, ref this.m_vehicles.m_buffer[vehicleId], ref this.m_vehicles.m_buffer[vehicleId].m_frame0); this.m_vehicleCount = (int)(this.m_vehicles.ItemCount() - 1u); // NON-STOCK CODE START #if BENCHMARK using (var bm = new Benchmark(null, "OnCreateVehicle")) { #endif VehicleStateManager.Instance.OnCreateVehicle(vehicleId, ref this.m_vehicles.m_buffer[vehicleId]); // NON-STOCK CODE #if BENCHMARK } #endif // NON-STOCK CODE END return true; } vehicleId = 0; return false; } [MethodImpl(MethodImplOptions.NoInlining)] private void ReleaseVehicleImplementation(ushort vehicleId, ref Vehicle vehicleData) { Log.Error("CustomVehicleManager.ReleaseVehicleImplementation called."); } } } ================================================ FILE: TLM/TLM/Custom/Manager/README.md ================================================ # TM:PE -- /Custom/Manager Detoured *Manager classes. ## Classes - **CustomCitizenManager**: Implements detours for checking if citizens instances are being released. Notifies the ExtCitizenInstanceManager. - **CustomNetManager**: Implements detours for checking if segments/nodes are being added/updated/removed. Recalculates segment/node geometries if necessary. - **CustomVehicleManager**: Implements detours for checking if vehicles are begin created/released. Determines the **ExtVehicleType** of a vehicle as soon as it is created. ================================================ FILE: TLM/TLM/Custom/PathFinding/CustomPathFind.cs ================================================ #define DEBUGLOCKSx #define COUNTSEGMENTSTONEXTJUNCTIONx using System; using System.Reflection; using System.Threading; using ColossalFramework; using ColossalFramework.Math; using ColossalFramework.UI; using TrafficManager.Geometry; using UnityEngine; using System.Collections.Generic; using TrafficManager.Custom.AI; using TrafficManager.TrafficLight; using TrafficManager.State; using TrafficManager.Manager; using TrafficManager.Traffic; using CSUtil.Commons; using TrafficManager.Manager.Impl; using static TrafficManager.Custom.PathFinding.CustomPathManager; using TrafficManager.Traffic.Data; namespace TrafficManager.Custom.PathFinding { public class CustomPathFind : PathFind { private struct BufferItem { public PathUnit.Position m_position; public float m_comparisonValue; public float m_methodDistance; public float m_duration; public uint m_laneID; public NetInfo.Direction m_direction; public NetInfo.LaneType m_lanesUsed; public VehicleInfo.VehicleType m_vehiclesUsed; public float m_trafficRand; #if COUNTSEGMENTSTONEXTJUNCTION public uint m_numSegmentsToNextJunction; #endif } private enum LaneChangingCostCalculationMode { None, ByLaneDistance, ByGivenDistance } private const float SEGMENT_MIN_AVERAGE_LENGTH = 30f; private const float LANE_DENSITY_DISCRETIZATION = 25f; private const float LANE_USAGE_DISCRETIZATION = 25f; private const float BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR = 0.003921569f; //Expose the private fields FieldInfo _fieldpathUnits; FieldInfo _fieldQueueFirst; FieldInfo _fieldQueueLast; FieldInfo _fieldQueueLock; FieldInfo _fieldCalculating; FieldInfo _fieldTerminated; FieldInfo _fieldPathFindThread; private Array32 PathUnits { get { return _fieldpathUnits.GetValue(this) as Array32; } set { _fieldpathUnits.SetValue(this, value); } } private uint QueueFirst { get { return (uint)_fieldQueueFirst.GetValue(this); } set { _fieldQueueFirst.SetValue(this, value); } } private uint QueueLast { get { return (uint)_fieldQueueLast.GetValue(this); } set { _fieldQueueLast.SetValue(this, value); } } private uint Calculating { get { return (uint)_fieldCalculating.GetValue(this); } set { _fieldCalculating.SetValue(this, value); } } private object QueueLock { get { return _fieldQueueLock.GetValue(this); } set { _fieldQueueLock.SetValue(this, value); } } private object _bufferLock; internal Thread CustomPathFindThread { get { return (Thread)_fieldPathFindThread.GetValue(this); } set { _fieldPathFindThread.SetValue(this, value); } } private bool Terminated { get { return (bool)_fieldTerminated.GetValue(this); } set { _fieldTerminated.SetValue(this, value); } } private int m_bufferMinPos; private int m_bufferMaxPos; private uint[] m_laneLocation; private PathUnit.Position[] m_laneTarget; private BufferItem[] m_buffer; private int[] m_bufferMin; private int[] m_bufferMax; private float m_maxLength; private uint m_startLaneA; private uint m_startLaneB; private ushort m_startSegmentA; private ushort m_startSegmentB; private uint m_endLaneA; private uint m_endLaneB; private uint m_vehicleLane; private byte m_startOffsetA; private byte m_startOffsetB; private byte m_vehicleOffset; private NetSegment.Flags m_carBanMask; private bool m_isHeavyVehicle; private bool m_ignoreBlocked; private bool m_stablePath; private bool m_randomParking; private bool m_transportVehicle; private bool m_ignoreCost; private PathUnitQueueItem queueItem; private NetSegment.Flags m_disableMask; /*private ExtVehicleType? _extVehicleType; private ushort? _vehicleId; private ExtCitizenInstance.ExtPathType? _extPathType;*/ private bool m_isRoadVehicle; private bool m_isLaneArrowObeyingEntity; private bool m_isLaneConnectionObeyingEntity; private bool m_leftHandDrive; //private float _speedRand; //private bool _extPublicTransport; //private static ushort laneChangeRandCounter = 0; #if DEBUG public uint m_failedPathFinds = 0; public uint m_succeededPathFinds = 0; private bool m_debug = false; private IDictionary> m_debugPositions = null; #endif public int pfId = 0; private Randomizer m_pathRandomizer; private uint m_pathFindIndex; private NetInfo.LaneType m_laneTypes; private VehicleInfo.VehicleType m_vehicleTypes; private GlobalConfig m_conf = null; private static readonly CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; private static readonly JunctionRestrictionsManager junctionManager = JunctionRestrictionsManager.Instance; private static readonly VehicleRestrictionsManager vehicleRestrictionsManager = VehicleRestrictionsManager.Instance; private static readonly SpeedLimitManager speedLimitManager = SpeedLimitManager.Instance; private static readonly TrafficMeasurementManager trafficMeasurementManager = TrafficMeasurementManager.Instance; private static readonly RoutingManager routingManager = RoutingManager.Instance; public bool IsMasterPathFind = false; protected virtual void Awake() { #if DEBUG Log._Debug($"CustomPathFind.Awake called."); #endif var stockPathFindType = typeof(PathFind); const BindingFlags fieldFlags = BindingFlags.NonPublic | BindingFlags.Instance; _fieldpathUnits = stockPathFindType.GetField("m_pathUnits", fieldFlags); _fieldQueueFirst = stockPathFindType.GetField("m_queueFirst", fieldFlags); _fieldQueueLast = stockPathFindType.GetField("m_queueLast", fieldFlags); _fieldQueueLock = stockPathFindType.GetField("m_queueLock", fieldFlags); _fieldTerminated = stockPathFindType.GetField("m_terminated", fieldFlags); _fieldCalculating = stockPathFindType.GetField("m_calculating", fieldFlags); _fieldPathFindThread = stockPathFindType.GetField("m_pathFindThread", fieldFlags); m_buffer = new BufferItem[65536]; // 2^16 _bufferLock = PathManager.instance.m_bufferLock; PathUnits = PathManager.instance.m_pathUnits; #if DEBUG if (QueueLock == null) { Log._Debug($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.Awake: QueueLock is null. Creating."); QueueLock = new object(); } else { Log._Debug($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.Awake: QueueLock is NOT null."); } #else QueueLock = new object(); #endif m_laneLocation = new uint[262144]; // 2^18 m_laneTarget = new PathUnit.Position[262144]; // 2^18 m_bufferMin = new int[1024]; // 2^10 m_bufferMax = new int[1024]; // 2^10 m_pathfindProfiler = new ThreadProfiler(); CustomPathFindThread = new Thread(PathFindThread) { Name = "Pathfind" }; CustomPathFindThread.Priority = SimulationManager.SIMULATION_PRIORITY; CustomPathFindThread.Start(); if (!CustomPathFindThread.IsAlive) { //CODebugBase.Error(LogChannel.Core, "Path find thread failed to start!"); Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) Path find thread failed to start!"); } } protected virtual void OnDestroy() { #if DEBUGLOCKS uint lockIter = 0; #endif try { Monitor.Enter(QueueLock); Terminated = true; Monitor.PulseAll(QueueLock); } catch (Exception e) { Log.Error("CustomPathFind.OnDestroy Error: " + e.ToString()); } finally { Monitor.Exit(QueueLock); } } public new bool CalculatePath(uint unit, bool skipQueue) { return ExtCalculatePath(unit, skipQueue); } public bool ExtCalculatePath(uint unit, bool skipQueue) { if (CustomPathManager._instance.AddPathReference(unit)) { try { Monitor.Enter(QueueLock); if (skipQueue) { if (this.QueueLast == 0u) { this.QueueLast = unit; } else { CustomPathManager._instance.queueItems[unit].nextPathUnitId = QueueFirst; //this.PathUnits.m_buffer[unit].m_nextPathUnit = this.QueueFirst; } this.QueueFirst = unit; } else { if (this.QueueLast == 0u) { this.QueueFirst = unit; } else { CustomPathManager._instance.queueItems[QueueLast].nextPathUnitId = unit; //this.PathUnits.m_buffer[this.QueueLast].m_nextPathUnit = unit; } this.QueueLast = unit; } this.PathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_CREATED; ++this.m_queuedPathFindCount; Monitor.Pulse(this.QueueLock); } catch (Exception e) { Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.CalculatePath({unit}, {skipQueue}): Error: {e.ToString()}"); } finally { Monitor.Exit(this.QueueLock); } return true; } return false; } // PathFind protected void PathFindImplementation(uint unit, ref PathUnit data) { m_conf = GlobalConfig.Instance; // NON-STOCK CODE NetManager netManager = Singleton.instance; this.m_laneTypes = (NetInfo.LaneType)this.PathUnits.m_buffer[unit].m_laneTypes; this.m_vehicleTypes = (VehicleInfo.VehicleType)this.PathUnits.m_buffer[unit].m_vehicleTypes; this.m_maxLength = this.PathUnits.m_buffer[unit].m_length; this.m_pathFindIndex = (this.m_pathFindIndex + 1u & 32767u); this.m_pathRandomizer = new Randomizer(unit); this.m_carBanMask = NetSegment.Flags.CarBan; this.m_isHeavyVehicle = ((this.PathUnits.m_buffer[unit].m_simulationFlags & 16) != 0); if (m_isHeavyVehicle) { this.m_carBanMask |= NetSegment.Flags.HeavyBan; } if ((this.PathUnits.m_buffer[unit].m_simulationFlags & 4) != 0) { this.m_carBanMask |= NetSegment.Flags.WaitingPath; } this.m_ignoreBlocked = ((this.PathUnits.m_buffer[unit].m_simulationFlags & 32) != 0); this.m_stablePath = ((this.PathUnits.m_buffer[unit].m_simulationFlags & 64) != 0); this.m_randomParking = ((this.PathUnits.m_buffer[unit].m_simulationFlags & 128) != 0); this.m_transportVehicle = ((byte)(this.m_laneTypes & NetInfo.LaneType.TransportVehicle) != 0); this.m_ignoreCost = (this.m_stablePath || (this.PathUnits.m_buffer[unit].m_simulationFlags & 8) != 0); this.m_disableMask = (NetSegment.Flags.Collapsed | NetSegment.Flags.PathFailed); if ((this.PathUnits.m_buffer[unit].m_simulationFlags & 2) == 0) { this.m_disableMask |= NetSegment.Flags.Flooded; } //this._speedRand = 0; this.m_leftHandDrive = Constants.ServiceFactory.SimulationService.LeftHandDrive; this.m_isRoadVehicle = (queueItem.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None; this.m_isLaneArrowObeyingEntity = (m_vehicleTypes & LaneArrowManager.VEHICLE_TYPES) != VehicleInfo.VehicleType.None && (queueItem.vehicleType & LaneArrowManager.EXT_VEHICLE_TYPES) != ExtVehicleType.None; this.m_isLaneConnectionObeyingEntity = (m_vehicleTypes & LaneConnectionManager.VEHICLE_TYPES) != VehicleInfo.VehicleType.None && (queueItem.vehicleType & LaneConnectionManager.EXT_VEHICLE_TYPES) != ExtVehicleType.None; #if DEBUGNEWPF && DEBUG bool debug = this.m_debug = m_conf.Debug.Switches[0] && ((m_conf.Debug.ExtVehicleType == ExtVehicleType.None && queueItem.vehicleType == ExtVehicleType.None) || (queueItem.vehicleType & m_conf.Debug.ExtVehicleType) != ExtVehicleType.None) && (m_conf.Debug.StartSegmentId == 0 || data.m_position00.m_segment == m_conf.Debug.StartSegmentId || data.m_position02.m_segment == m_conf.Debug.StartSegmentId) && (m_conf.Debug.EndSegmentId == 0 || data.m_position01.m_segment == m_conf.Debug.EndSegmentId || data.m_position03.m_segment == m_conf.Debug.EndSegmentId) && (m_conf.Debug.VehicleId == 0 || queueItem.vehicleId == m_conf.Debug.VehicleId) ; if (debug) { Log._Debug($"CustomPathFind.PathFindImplementation: START calculating path unit {unit}, type {queueItem.vehicleType}"); m_debugPositions = new Dictionary>(); } #endif if ((byte)(this.m_laneTypes & NetInfo.LaneType.Vehicle) != 0) { this.m_laneTypes |= NetInfo.LaneType.TransportVehicle; } int posCount = (int)(this.PathUnits.m_buffer[unit].m_positionCount & 15); int vehiclePosIndicator = this.PathUnits.m_buffer[unit].m_positionCount >> 4; BufferItem bufferItemStartA; if (data.m_position00.m_segment != 0 && posCount >= 1) { this.m_startLaneA = PathManager.GetLaneID(data.m_position00); this.m_startSegmentA = data.m_position00.m_segment; // NON-STOCK CODE this.m_startOffsetA = data.m_position00.m_offset; bufferItemStartA.m_laneID = this.m_startLaneA; bufferItemStartA.m_position = data.m_position00; this.GetLaneDirection(data.m_position00, out bufferItemStartA.m_direction, out bufferItemStartA.m_lanesUsed, out bufferItemStartA.m_vehiclesUsed); bufferItemStartA.m_comparisonValue = 0f; bufferItemStartA.m_duration = 0f; #if COUNTSEGMENTSTONEXTJUNCTION bufferItemStartA.m_numSegmentsToNextJunction = 0; #endif } else { this.m_startLaneA = 0u; this.m_startSegmentA = 0; // NON-STOCK CODE this.m_startOffsetA = 0; bufferItemStartA = default(BufferItem); } BufferItem bufferItemStartB; if (data.m_position02.m_segment != 0 && posCount >= 3) { this.m_startLaneB = PathManager.GetLaneID(data.m_position02); this.m_startSegmentB = data.m_position02.m_segment; // NON-STOCK CODE this.m_startOffsetB = data.m_position02.m_offset; bufferItemStartB.m_laneID = this.m_startLaneB; bufferItemStartB.m_position = data.m_position02; this.GetLaneDirection(data.m_position02, out bufferItemStartB.m_direction, out bufferItemStartB.m_lanesUsed, out bufferItemStartB.m_vehiclesUsed); bufferItemStartB.m_comparisonValue = 0f; bufferItemStartB.m_duration = 0f; #if COUNTSEGMENTSTONEXTJUNCTION bufferItemStartB.m_numSegmentsToNextJunction = 0; #endif } else { this.m_startLaneB = 0u; this.m_startSegmentB = 0; // NON-STOCK CODE this.m_startOffsetB = 0; bufferItemStartB = default(BufferItem); } BufferItem bufferItemEndA; if (data.m_position01.m_segment != 0 && posCount >= 2) { this.m_endLaneA = PathManager.GetLaneID(data.m_position01); bufferItemEndA.m_laneID = this.m_endLaneA; bufferItemEndA.m_position = data.m_position01; this.GetLaneDirection(data.m_position01, out bufferItemEndA.m_direction, out bufferItemEndA.m_lanesUsed, out bufferItemEndA.m_vehiclesUsed); bufferItemEndA.m_methodDistance = 0.01f; bufferItemEndA.m_comparisonValue = 0f; bufferItemEndA.m_duration = 0f; bufferItemEndA.m_trafficRand = 0; // NON-STOCK CODE #if COUNTSEGMENTSTONEXTJUNCTION bufferItemEndA.m_numSegmentsToNextJunction = 0; #endif } else { this.m_endLaneA = 0u; bufferItemEndA = default(BufferItem); } BufferItem bufferItemEndB; if (data.m_position03.m_segment != 0 && posCount >= 4) { this.m_endLaneB = PathManager.GetLaneID(data.m_position03); bufferItemEndB.m_laneID = this.m_endLaneB; bufferItemEndB.m_position = data.m_position03; this.GetLaneDirection(data.m_position03, out bufferItemEndB.m_direction, out bufferItemEndB.m_lanesUsed, out bufferItemEndB.m_vehiclesUsed); bufferItemEndB.m_methodDistance = 0.01f; bufferItemEndB.m_comparisonValue = 0f; bufferItemEndB.m_duration = 0f; bufferItemEndB.m_trafficRand = 0; // NON-STOCK CODE #if COUNTSEGMENTSTONEXTJUNCTION bufferItemEndB.m_numSegmentsToNextJunction = 0; #endif } else { this.m_endLaneB = 0u; bufferItemEndB = default(BufferItem); } if (data.m_position11.m_segment != 0 && vehiclePosIndicator >= 1) { this.m_vehicleLane = PathManager.GetLaneID(data.m_position11); this.m_vehicleOffset = data.m_position11.m_offset; } else { this.m_vehicleLane = 0u; this.m_vehicleOffset = 0; } #if DEBUGNEWPF && DEBUG if (debug) { Log._Debug($"CustomPathFind.PathFindImplementation: Preparing calculating path unit {unit}, type {queueItem.vehicleType}:\n" + $"\tbufferItemStartA: segment={bufferItemStartA.m_position.m_segment} lane={bufferItemStartA.m_position.m_lane} off={bufferItemStartA.m_position.m_offset} laneId={bufferItemStartA.m_laneID}\n" + $"\tbufferItemStartB: segment={bufferItemStartB.m_position.m_segment} lane={bufferItemStartB.m_position.m_lane} off={bufferItemStartB.m_position.m_offset} laneId={bufferItemStartB.m_laneID}\n" + $"\tbufferItemEndA: segment={bufferItemEndA.m_position.m_segment} lane={bufferItemEndA.m_position.m_lane} off={bufferItemEndA.m_position.m_offset} laneId={bufferItemEndA.m_laneID}\n" + $"\tbufferItemEndB: segment={bufferItemEndB.m_position.m_segment} lane={bufferItemEndB.m_position.m_lane} off={bufferItemEndB.m_position.m_offset} laneId={bufferItemEndB.m_laneID}\n" + $"\tvehicleItem: segment={data.m_position11.m_segment} lane={data.m_position11.m_lane} off={data.m_position11.m_offset} laneId={m_vehicleLane} vehiclePosIndicator={vehiclePosIndicator}\n" ); } #endif BufferItem finalBufferItem = default(BufferItem); byte startOffset = 0; this.m_bufferMinPos = 0; this.m_bufferMaxPos = -1; if (this.m_pathFindIndex == 0u) { uint maxUInt = 4294901760u; for (int i = 0; i < 262144; ++i) { this.m_laneLocation[i] = maxUInt; } } for (int j = 0; j < 1024; ++j) { this.m_bufferMin[j] = 0; this.m_bufferMax[j] = -1; } if (bufferItemEndA.m_position.m_segment != 0) { ++this.m_bufferMax[0]; this.m_buffer[++this.m_bufferMaxPos] = bufferItemEndA; } if (bufferItemEndB.m_position.m_segment != 0) { ++this.m_bufferMax[0]; this.m_buffer[++this.m_bufferMaxPos] = bufferItemEndB; } bool canFindPath = false; while (this.m_bufferMinPos <= this.m_bufferMaxPos) { int bufMin = this.m_bufferMin[this.m_bufferMinPos]; int bufMax = this.m_bufferMax[this.m_bufferMinPos]; if (bufMin > bufMax) { ++this.m_bufferMinPos; } else { this.m_bufferMin[this.m_bufferMinPos] = bufMin + 1; BufferItem candidateItem = this.m_buffer[(this.m_bufferMinPos << 6) + bufMin]; if (candidateItem.m_position.m_segment == bufferItemStartA.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartA.m_position.m_lane) { // we reached startA if ((byte)(candidateItem.m_direction & NetInfo.Direction.Forward) != 0 && candidateItem.m_position.m_offset >= this.m_startOffsetA) { finalBufferItem = candidateItem; startOffset = this.m_startOffsetA; canFindPath = true; break; } if ((byte)(candidateItem.m_direction & NetInfo.Direction.Backward) != 0 && candidateItem.m_position.m_offset <= this.m_startOffsetA) { finalBufferItem = candidateItem; startOffset = this.m_startOffsetA; canFindPath = true; break; } } if (candidateItem.m_position.m_segment == bufferItemStartB.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartB.m_position.m_lane) { // we reached startB if ((byte)(candidateItem.m_direction & NetInfo.Direction.Forward) != 0 && candidateItem.m_position.m_offset >= this.m_startOffsetB) { finalBufferItem = candidateItem; startOffset = this.m_startOffsetB; canFindPath = true; break; } if ((byte)(candidateItem.m_direction & NetInfo.Direction.Backward) != 0 && candidateItem.m_position.m_offset <= this.m_startOffsetB) { finalBufferItem = candidateItem; startOffset = this.m_startOffsetB; canFindPath = true; break; } } // explore the path if ((byte)(candidateItem.m_direction & NetInfo.Direction.Forward) != 0) { ushort startNode = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_startNode; uint laneRoutingIndex = routingManager.GetLaneEndRoutingIndex(candidateItem.m_laneID, true); this.ProcessItemMain(unit, candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], routingManager.segmentRoutings[candidateItem.m_position.m_segment], routingManager.laneEndBackwardRoutings[laneRoutingIndex], startNode, true, ref netManager.m_nodes.m_buffer[startNode], 0, false); } if ((byte)(candidateItem.m_direction & NetInfo.Direction.Backward) != 0) { ushort endNode = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_endNode; uint laneRoutingIndex = routingManager.GetLaneEndRoutingIndex(candidateItem.m_laneID, false); this.ProcessItemMain(unit, candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], routingManager.segmentRoutings[candidateItem.m_position.m_segment], routingManager.laneEndBackwardRoutings[laneRoutingIndex], endNode, false, ref netManager.m_nodes.m_buffer[endNode], 255, false); } // handle special nodes (e.g. bus stops) int num6 = 0; ushort specialNodeId = netManager.m_lanes.m_buffer[candidateItem.m_laneID].m_nodes; if (specialNodeId != 0) { ushort startNode2 = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_startNode; ushort endNode2 = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_endNode; bool nodesDisabled = ((netManager.m_nodes.m_buffer[startNode2].m_flags | netManager.m_nodes.m_buffer[endNode2].m_flags) & NetNode.Flags.Disabled) != NetNode.Flags.None; while (specialNodeId != 0) { NetInfo.Direction direction = NetInfo.Direction.None; byte laneOffset = netManager.m_nodes.m_buffer[specialNodeId].m_laneOffset; if (laneOffset <= candidateItem.m_position.m_offset) { direction |= NetInfo.Direction.Forward; } if (laneOffset >= candidateItem.m_position.m_offset) { direction |= NetInfo.Direction.Backward; } if ((byte)(candidateItem.m_direction & direction) != 0 && (!nodesDisabled || (netManager.m_nodes.m_buffer[specialNodeId].m_flags & NetNode.Flags.Disabled) != NetNode.Flags.None)) { #if DEBUGNEWPF && DEBUG if (debug && (m_conf.Debug.NodeId <= 0 || specialNodeId == m_conf.Debug.NodeId)) { Log._Debug($"CustomPathFind.PathFindImplementation: Handling special node for path unit {unit}, type {queueItem.vehicleType}:\n" + $"\tcandidateItem.m_position.m_segment={candidateItem.m_position.m_segment}\n" + $"\tcandidateItem.m_position.m_lane={candidateItem.m_position.m_lane}\n" + $"\tcandidateItem.m_laneID={candidateItem.m_laneID}\n" + $"\tspecialNodeId={specialNodeId}\n" + $"\tstartNode2={startNode2}\n" + $"\tendNode2={endNode2}\n" ); } #endif this.ProcessItemMain(unit, candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], routingManager.segmentRoutings[candidateItem.m_position.m_segment], routingManager.laneEndBackwardRoutings[0], specialNodeId, false, ref netManager.m_nodes.m_buffer[specialNodeId], laneOffset, true); } specialNodeId = netManager.m_nodes.m_buffer[specialNodeId].m_nextLaneNode; if (++num6 == 32768) { Log.Warning("Special loop: Too many iterations"); break; } } } } } if (!canFindPath) { // we could not find a path PathUnits.m_buffer[(int)unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; #if DEBUG ++m_failedPathFinds; #if DEBUGNEWPF if (debug) { Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Could not find path for unit {unit} -- path-finding failed during process"); string reachableBuf = ""; string unreachableBuf = ""; foreach (KeyValuePair> e in m_debugPositions) { string buf = $"{e.Key} -> {e.Value.CollectionToString()}\n"; if (e.Value.Count <= 0) { unreachableBuf += buf; } else { reachableBuf += buf; } } Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Reachability graph for unit {unit}:\n== REACHABLE ==\n" + reachableBuf + "\n== UNREACHABLE ==\n" + unreachableBuf); } #endif #endif //CustomPathManager._instance.ResetQueueItem(unit); return; } // we could calculate a valid path float duration = (this.m_laneTypes != NetInfo.LaneType.Pedestrian) ? finalBufferItem.m_duration : finalBufferItem.m_methodDistance; this.PathUnits.m_buffer[unit].m_length = duration; this.PathUnits.m_buffer[unit].m_laneTypes = (byte)finalBufferItem.m_lanesUsed; // NON-STOCK CODE this.PathUnits.m_buffer[unit].m_vehicleTypes = (ushort)finalBufferItem.m_vehiclesUsed; // NON-STOCK CODE #if DEBUG /*if (_conf.Debug.Switches[4]) Log._Debug($"Lane/Vehicle types of path unit {unit}: {finalBufferItem.m_lanesUsed} / {finalBufferItem.m_vehiclesUsed}");*/ #endif uint currentPathUnitId = unit; int currentItemPositionCount = 0; int sumOfPositionCounts = 0; PathUnit.Position currentPosition = finalBufferItem.m_position; if ((currentPosition.m_segment != bufferItemEndA.m_position.m_segment || currentPosition.m_lane != bufferItemEndA.m_position.m_lane || currentPosition.m_offset != bufferItemEndA.m_position.m_offset) && (currentPosition.m_segment != bufferItemEndB.m_position.m_segment || currentPosition.m_lane != bufferItemEndB.m_position.m_lane || currentPosition.m_offset != bufferItemEndB.m_position.m_offset)) { // the found starting position differs from the desired end position if (startOffset != currentPosition.m_offset) { // the offsets differ: copy the found starting position and modify the offset to fit the desired offset PathUnit.Position position2 = currentPosition; position2.m_offset = startOffset; this.PathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, position2); // now we have: [desired starting position] } // add the found starting position to the path unit this.PathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); currentPosition = this.m_laneTarget[finalBufferItem.m_laneID]; // go to the next path position // now we have either [desired starting position, found starting position] or [found starting position], depending on if the found starting position matched the desired } // beginning with the starting position, going to the target position: assemble the path units for (int k = 0; k < 262144; ++k) { //pfCurrentState = 6; this.PathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); // add the next path position to the current unit if ((currentPosition.m_segment == bufferItemEndA.m_position.m_segment && currentPosition.m_lane == bufferItemEndA.m_position.m_lane && currentPosition.m_offset == bufferItemEndA.m_position.m_offset) || (currentPosition.m_segment == bufferItemEndB.m_position.m_segment && currentPosition.m_lane == bufferItemEndB.m_position.m_lane && currentPosition.m_offset == bufferItemEndB.m_position.m_offset)) { // we have reached the end position this.PathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; sumOfPositionCounts += currentItemPositionCount; // add position count of last unit to sum if (sumOfPositionCounts != 0) { // for each path unit from start to target: calculate length (distance) to target currentPathUnitId = this.PathUnits.m_buffer[unit].m_nextPathUnit; // (we do not need to calculate the length for the starting unit since this is done before; it's the total path length) currentItemPositionCount = (int)this.PathUnits.m_buffer[unit].m_positionCount; int totalIter = 0; while (currentPathUnitId != 0u) { this.PathUnits.m_buffer[currentPathUnitId].m_length = duration * (float)(sumOfPositionCounts - currentItemPositionCount) / (float)sumOfPositionCounts; currentItemPositionCount += (int)this.PathUnits.m_buffer[currentPathUnitId].m_positionCount; currentPathUnitId = this.PathUnits.m_buffer[currentPathUnitId].m_nextPathUnit; if (++totalIter >= 262144) { #if DEBUG Log.Error("THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this._pathFindIndex}: PathFindImplementation: Invalid list detected."); #endif CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } } } #if DEBUG //Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this._pathFindIndex}: Path found (pfCurrentState={pfCurrentState}) for unit {unit}"); #endif PathUnits.m_buffer[(int)unit].m_pathFindFlags |= PathUnit.FLAG_READY; // Path found #if DEBUG ++m_succeededPathFinds; #if DEBUGNEWPF if (debug) Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Path-find succeeded for unit {unit}"); #endif #endif //CustomPathManager._instance.ResetQueueItem(unit); return; } // We have not reached the target position yet if (currentItemPositionCount == 12) { // the current path unit is full, we need a new one uint createdPathUnitId; try { Monitor.Enter(_bufferLock); if (!this.PathUnits.CreateItem(out createdPathUnitId, ref this.m_pathRandomizer)) { // we failed to create a new path unit, thus the path-finding also failed PathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; #if DEBUG ++m_failedPathFinds; #if DEBUGNEWPF if (debug) Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Could not find path for unit {unit} -- Could not create path unit"); #endif #endif //CustomPathManager._instance.ResetQueueItem(unit); return; } this.PathUnits.m_buffer[createdPathUnitId] = this.PathUnits.m_buffer[(int)currentPathUnitId]; this.PathUnits.m_buffer[createdPathUnitId].m_referenceCount = 1; this.PathUnits.m_buffer[createdPathUnitId].m_pathFindFlags = PathUnit.FLAG_READY; this.PathUnits.m_buffer[currentPathUnitId].m_nextPathUnit = createdPathUnitId; this.PathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; this.PathUnits.m_buffer[currentPathUnitId].m_laneTypes = (byte)finalBufferItem.m_lanesUsed; // NON-STOCK CODE (this is not accurate!) this.PathUnits.m_buffer[currentPathUnitId].m_vehicleTypes = (ushort)finalBufferItem.m_vehiclesUsed; // NON-STOCK CODE (this is not accurate!) sumOfPositionCounts += currentItemPositionCount; Singleton.instance.m_pathUnitCount = (int)(this.PathUnits.ItemCount() - 1u); } catch (Exception e) { Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.PathFindImplementation Error: {e.ToString()}"); break; } finally { Monitor.Exit(this._bufferLock); } currentPathUnitId = createdPathUnitId; currentItemPositionCount = 0; } uint laneID = PathManager.GetLaneID(currentPosition); #if PFTRAFFICSTATS // NON-STOCK CODE START #if MEASUREDENSITY if (!Options.isStockLaneChangerUsed()) { NetInfo.Lane laneInfo = Singleton.instance.m_segments.m_buffer[currentPosition.m_segment].Info.m_lanes[currentPosition.m_lane]; if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) trafficMeasurementManager.AddTraffic(currentPosition.m_segment, currentPosition.m_lane, (ushort)(this._isHeavyVehicle || _extVehicleType == ExtVehicleType.Bus ? 75 : 25), null); } #endif if (!Options.isStockLaneChangerUsed()) { NetInfo.Lane laneInfo = Singleton.instance.m_segments.m_buffer[currentPosition.m_segment].Info.m_lanes[currentPosition.m_lane]; if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) { trafficMeasurementManager.AddPathFindTraffic(currentPosition.m_segment, currentPosition.m_lane); } } // NON-STOCK CODE END #endif currentPosition = this.m_laneTarget[laneID]; } PathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; #if DEBUG ++m_failedPathFinds; #if DEBUGNEWPF if (debug) Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Could not find path for unit {unit} -- internal error: for loop break"); #endif #endif //CustomPathManager._instance.ResetQueueItem(unit); #if DEBUG //Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this._pathFindIndex}: Cannot find path (pfCurrentState={pfCurrentState}) for unit {unit}"); #endif } // be aware: // (1) path-finding works from target to start. the "next" segment is always the previous and the "previous" segment is always the next segment on the path! // (2) when I use the term "lane index from outer" this means outer right lane for right-hand traffic systems and outer-left lane for left-hand traffic systems. // 1 private void ProcessItemMain(uint unitId, BufferItem item, ref NetSegment prevSegment, SegmentRoutingData prevSegmentRouting, LaneEndRoutingData prevLaneEndRouting, ushort nextNodeId, bool nextIsStartNode, ref NetNode nextNode, byte connectOffset, bool isMiddle) { #if DEBUGNEWPF && DEBUG bool debug = this.m_debug && (m_conf.Debug.NodeId <= 0 || nextNodeId == m_conf.Debug.NodeId); bool debugPed = debug && m_conf.Debug.Switches[12]; if (debug) { if (! m_debugPositions.ContainsKey(item.m_position.m_segment)) { m_debugPositions[item.m_position.m_segment] = new List(); } } #else bool debug = false; bool debugPed = false; #endif //Log.Message($"THREAD #{Thread.CurrentThread.ManagedThreadId} Path finder: " + this._pathFindIndex + " vehicle types: " + this._vehicleTypes); #if DEBUGNEWPF && DEBUG //bool debug = isTransportVehicle && isMiddle && item.m_position.m_segment == 13550; List logBuf = null; if (debug) logBuf = new List(); #endif NetManager netManager = Singleton.instance; bool prevIsPedestrianLane = false; //bool prevIsBusLane = false; // non-stock bool prevIsBicycleLane = false; bool prevIsCenterPlatform = false; bool prevIsElevated = false; bool prevIsCarLane = false; int prevRelSimilarLaneIndex = 0; // inner/outer similar index //int prevInnerSimilarLaneIndex = 0; // similar index, starting with 0 at leftmost lane in right hand traffic int prevOuterSimilarLaneIndex = 0; // similar index, starting with 0 at rightmost lane in right hand traffic NetInfo prevSegmentInfo = prevSegment.Info; NetInfo.Lane prevLaneInfo = null; byte prevSimilarLaneCount = 0; if ((int)item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { prevLaneInfo = prevSegmentInfo.m_lanes[(int)item.m_position.m_lane]; prevIsPedestrianLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian); prevIsBicycleLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Vehicle && (prevLaneInfo.m_vehicleType & this.m_vehicleTypes) == VehicleInfo.VehicleType.Bicycle); prevIsCarLane = (prevLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (prevLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None; //prevIsBusLane = (prevLane.m_laneType == NetInfo.LaneType.TransportVehicle && (prevLane.m_vehicleType & this._vehicleTypes & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None); prevIsCenterPlatform = prevLaneInfo.m_centerPlatform; prevIsElevated = prevLaneInfo.m_elevated; prevSimilarLaneCount = (byte)prevLaneInfo.m_similarLaneCount; //prevInnerSimilarLaneIndex = RoutingManager.Instance.CalcInnerSimilarLaneIndex(prevLaneInfo); prevOuterSimilarLaneIndex = RoutingManager.Instance.CalcOuterSimilarLaneIndex(prevLaneInfo); if ((byte)(prevLaneInfo.m_finalDirection & NetInfo.Direction.Forward) != 0) { prevRelSimilarLaneIndex = prevLaneInfo.m_similarLaneIndex; } else { prevRelSimilarLaneIndex = prevLaneInfo.m_similarLaneCount - prevLaneInfo.m_similarLaneIndex - 1; } } int firstPrevSimilarLaneIndexFromInner = prevRelSimilarLaneIndex; ushort prevSegmentId = item.m_position.m_segment; if (isMiddle) { for (int i = 0; i < 8; ++i) { ushort nextSegmentId = nextNode.GetSegment(i); if (nextSegmentId <= 0) continue; #if DEBUGNEWPF if (debug) { FlushMainLog(logBuf, unitId); } #endif this.ProcessItemCosts(debug, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[(int)nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, !prevIsPedestrianLane, prevIsPedestrianLane, isMiddle); } } else if (prevIsPedestrianLane) { bool allowPedSwitch = (this.m_laneTypes & NetInfo.LaneType.Pedestrian) != 0; if (!prevIsElevated) { // explore pedestrian lanes int prevLaneIndex = (int)item.m_position.m_lane; if (nextNode.Info.m_class.m_service != ItemClass.Service.Beautification) { if (allowPedSwitch) { // NON-STOCK CODE bool canCrossStreet = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None; bool isOnCenterPlatform = prevIsCenterPlatform && (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Junction)) == NetNode.Flags.None; ushort nextLeftSegment = prevSegmentId; ushort nextRightSegment = prevSegmentId; int leftLaneIndex; int rightLaneIndex; uint leftLaneId; uint rightLaneId; prevSegment.GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, prevLaneIndex, isOnCenterPlatform, out leftLaneIndex, out rightLaneIndex, out leftLaneId, out rightLaneId); if (leftLaneId == 0u || rightLaneId == 0u) { ushort leftSegment; ushort rightSegment; prevSegment.GetLeftAndRightSegments(nextNodeId, out leftSegment, out rightSegment); int numIter = 0; while (leftSegment != 0 && leftSegment != prevSegmentId && leftLaneId == 0u) { int someLeftLaneIndex; int someRightLaneIndex; uint someLeftLaneId; uint someRightLaneId; netManager.m_segments.m_buffer[(int)leftSegment].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); if (someRightLaneId != 0u) { nextLeftSegment = leftSegment; leftLaneIndex = someRightLaneIndex; leftLaneId = someRightLaneId; } else { leftSegment = netManager.m_segments.m_buffer[(int)leftSegment].GetLeftSegment(nextNodeId); } if (++numIter == 8) { break; } } numIter = 0; while (rightSegment != 0 && rightSegment != prevSegmentId && rightLaneId == 0u) { int someLeftLaneIndex; int someRightLaneIndex; uint someLeftLaneId; uint someRightLaneId; netManager.m_segments.m_buffer[(int)rightSegment].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); if (someLeftLaneId != 0u) { nextRightSegment = rightSegment; rightLaneIndex = someLeftLaneIndex; rightLaneId = someLeftLaneId; } else { rightSegment = netManager.m_segments.m_buffer[(int)rightSegment].GetRightSegment(nextNodeId); } if (++numIter == 8) { break; } } } if (leftLaneId != 0u && (nextLeftSegment != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { #if DEBUGNEWPF if (debugPed) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring left segment\n" + "\t" + $"_extPathType={queueItem.pathType}\n" + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + "\t" + $"_stablePath={m_stablePath}\n" + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + "\t" + $"nextLeftSegment={nextLeftSegment}\n" + "\t" + $"leftLaneId={leftLaneId}\n" + "\t" + $"mayCrossStreet={canCrossStreet}\n" + "\t" + $"isOnCenterPlatform={isOnCenterPlatform}\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" ); FlushMainLog(logBuf, unitId); } #endif this.ProcessItemPedBicycle(debugPed, item, nextNodeId, nextLeftSegment, ref prevSegment, ref netManager.m_segments.m_buffer[(int)nextLeftSegment], connectOffset, connectOffset, leftLaneIndex, leftLaneId); // ped } if (rightLaneId != 0u && rightLaneId != leftLaneId && (nextRightSegment != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { #if DEBUGNEWPF if (debugPed) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring right segment\n" + "\t" + $"_extPathType={queueItem.pathType}\n" + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + "\t" + $"_stablePath={m_stablePath}\n" + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + "\t" + $"nextRightSegment={nextRightSegment}\n" + "\t" + $"rightLaneId={rightLaneId}\n" + "\t" + $"mayCrossStreet={canCrossStreet}\n" + "\t" + $"isOnCenterPlatform={isOnCenterPlatform}\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" ); FlushMainLog(logBuf, unitId); } #endif this.ProcessItemPedBicycle(debugPed, item, nextNodeId, nextRightSegment, ref prevSegment, ref netManager.m_segments.m_buffer[(int)nextRightSegment], connectOffset, connectOffset, rightLaneIndex, rightLaneId); // ped } } // switch from bicycle lane to pedestrian lane int nextLaneIndex; uint nextLaneId; if ((this.m_vehicleTypes & VehicleInfo.VehicleType.Bicycle) != VehicleInfo.VehicleType.None && prevSegment.GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Bicycle, out nextLaneIndex, out nextLaneId)) { #if DEBUGNEWPF if (debugPed) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring bicycle switch\n" + "\t" + $"_extPathType={queueItem.pathType}\n" + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + "\t" + $"_stablePath={m_stablePath}\n" + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + "\t" + $"nextLaneId={nextLaneId}\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" ); FlushMainLog(logBuf, unitId); } #endif this.ProcessItemPedBicycle(debugPed, item, nextNodeId, prevSegmentId, ref prevSegment, ref prevSegment, connectOffset, connectOffset, nextLaneIndex, nextLaneId); // bicycle } } else { // we are going from pedestrian lane to a beautification node for (int j = 0; j < 8; ++j) { ushort nextSegmentId = nextNode.GetSegment(j); if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { #if DEBUGNEWPF if (debug) { FlushMainLog(logBuf, unitId); } #endif this.ProcessItemCosts(debug, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[(int)nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, false, true, isMiddle); } } } // NON-STOCK CODE START // switch from vehicle to pedestrian lane (parking) bool parkingAllowed = true; if (Options.prohibitPocketCars) { if (queueItem.vehicleType == ExtVehicleType.PassengerCar) { if ((item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { // if pocket cars are prohibited, a citizen may only park their car once per path parkingAllowed = false; } else if ((item.m_lanesUsed & NetInfo.LaneType.PublicTransport) == NetInfo.LaneType.None) { // if the citizen is walking to their target (= no public transport used), the passenger car must be parked in the very last moment parkingAllowed = item.m_laneID == m_endLaneA || item.m_laneID == m_endLaneB; /*if (_conf.Debug.Switches[4]) { Log._Debug($"Path unit {unitId}: public transport has not been used. "); }*/ } } } if (parkingAllowed) { // NON-STOCK CODE END NetInfo.LaneType laneType = this.m_laneTypes & ~NetInfo.LaneType.Pedestrian; VehicleInfo.VehicleType vehicleType = this.m_vehicleTypes & ~VehicleInfo.VehicleType.Bicycle; if ((byte)(item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0) { laneType &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } int nextLaneIndex2; uint nextlaneId2; if (laneType != NetInfo.LaneType.None && vehicleType != VehicleInfo.VehicleType.None && prevSegment.GetClosestLane(prevLaneIndex, laneType, vehicleType, out nextLaneIndex2, out nextlaneId2)) { NetInfo.Lane lane5 = prevSegmentInfo.m_lanes[nextLaneIndex2]; byte connectOffset2; if ((prevSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None == ((byte)(lane5.m_finalDirection & NetInfo.Direction.Backward) != 0)) { connectOffset2 = 1; } else { connectOffset2 = 254; } CustomPathFind.BufferItem item2 = item; if (this.m_randomParking) { item2.m_comparisonValue += (float)this.m_pathRandomizer.Int32(300u) / this.m_maxLength; } #if DEBUGNEWPF if (debugPed) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring parking switch\n" + "\t" + $"_extPathType={queueItem.pathType}\n" + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + "\t" + $"_stablePath={m_stablePath}\n" + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + "\t" + $"nextLaneIndex2={nextLaneIndex2}\n" + "\t" + $"nextlaneId2={nextlaneId2}\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" ); FlushMainLog(logBuf, unitId); } #endif this.ProcessItemPedBicycle(debugPed, item2, nextNodeId, prevSegmentId, ref prevSegment, ref prevSegment, connectOffset2, 128, nextLaneIndex2, nextlaneId2); // ped } } } } else { // we are going to a non-pedestrian lane bool allowPedestrian = (byte)(this.m_laneTypes & NetInfo.LaneType.Pedestrian) != 0; // allow pedestrian switching to vehicle? bool nextIsBeautificationNode = nextNode.Info.m_class.m_service == ItemClass.Service.Beautification; bool allowBicycle = false; // is true if cim may switch from a pedestrian lane to a bike lane byte parkingConnectOffset = 0; if (allowPedestrian) { if (prevIsBicycleLane) { // we are going to a bicycle lane parkingConnectOffset = connectOffset; allowBicycle = nextIsBeautificationNode; } else if (this.m_vehicleLane != 0u) { // there is a parked vehicle position if (this.m_vehicleLane != item.m_laneID) { // we have not reached the parked vehicle yet allowPedestrian = false; } else { // pedestrian switches to parked vehicle parkingConnectOffset = this.m_vehicleOffset; } } else if (this.m_stablePath) { // enter a bus parkingConnectOffset = 128; } else { // pocket car spawning if (Options.prohibitPocketCars && queueItem.vehicleType == ExtVehicleType.PassengerCar && (queueItem.pathType == ExtCitizenInstance.ExtPathType.WalkingOnly || (queueItem.pathType == ExtCitizenInstance.ExtPathType.DrivingOnly && item.m_position.m_segment != m_startSegmentA && item.m_position.m_segment != m_startSegmentB))) { allowPedestrian = false; } else { parkingConnectOffset = (byte)this.m_pathRandomizer.UInt32(1u, 254u); } } } if ((this.m_vehicleTypes & (VehicleInfo.VehicleType.Ferry /* | VehicleInfo.VehicleType.Monorail*/)) != VehicleInfo.VehicleType.None) { // monorail / ferry for (int k = 0; k < 8; k++) { ushort nextSegmentId = nextNode.GetSegment(k); if (nextSegmentId == 0 || nextSegmentId == prevSegmentId) { continue; } this.ProcessItemCosts(debug, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, allowBicycle, isMiddle); } if ((nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None /*&& (this._vehicleTypes & VehicleInfo.VehicleType.Monorail) == VehicleInfo.VehicleType.None*/) { this.ProcessItemCosts(debug, item, nextNodeId, prevSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref prevSegment, ref prevRelSimilarLaneIndex, connectOffset, true, false, isMiddle); } } else { // road vehicles, trams, trains, metros, monorails, etc. // specifies if vehicles should follow lane arrows bool isStrictLaneChangePolicyEnabled = false; // specifies if the entity is allowed to u-turn (in general) bool isEntityAllowedToUturn = (this.m_vehicleTypes & (VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail)) == VehicleInfo.VehicleType.None; // specifies if thes next node allows for u-turns bool isUturnAllowedHere = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; /* * specifies if u-turns are handled by custom code. * If not (performCustomVehicleUturns == false) AND the vanilla u-turn condition (stockUturn) evaluates to true, then u-turns are handled by the vanilla code */ //bool performCustomVehicleUturns = false; bool prevIsRouted = prevLaneEndRouting.routed #if DEBUG && !m_conf.Debug.Switches[11] #endif ; if (prevIsRouted) { bool prevIsOutgoingOneWay = nextIsStartNode ? prevSegmentRouting.startNodeOutgoingOneWay : prevSegmentRouting.endNodeOutgoingOneWay; bool nextIsUntouchable = (nextNode.m_flags & (NetNode.Flags.Untouchable)) != NetNode.Flags.None; bool nextIsTransitionOrJunction = (nextNode.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) != NetNode.Flags.None; bool nextIsBend = (nextNode.m_flags & (NetNode.Flags.Bend)) != NetNode.Flags.None; // determine if the vehicle may u-turn at the target node according to customization isUturnAllowedHere = isUturnAllowedHere || // stock u-turn points (Options.junctionRestrictionsEnabled && m_isRoadVehicle && // only road vehicles may perform u-turns junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode) && // only do u-turns if allowed !nextIsBeautificationNode && // no u-turns at beautification nodes // TODO refactor to JunctionManager prevIsCarLane && // u-turns for road vehicles only !m_isHeavyVehicle && // only small vehicles may perform u-turns (nextIsTransitionOrJunction || nextIsBend) && // perform u-turns at transitions, junctions and bend nodes // TODO refactor to JunctionManager !prevIsOutgoingOneWay); // do not u-turn on one-ways // TODO refactor to JunctionManager isStrictLaneChangePolicyEnabled = !nextIsBeautificationNode && // do not obey lane arrows at beautification nodes !nextIsUntouchable && m_isLaneArrowObeyingEntity && //nextIsTransitionOrJunction && // follow lane arrows only at transitions and junctions !( #if DEBUG Options.allRelaxed || // debug option: all vehicle may ignore lane arrows #endif (Options.relaxedBusses && queueItem.vehicleType == ExtVehicleType.Bus)); // option: busses may ignore lane arrows /*if (! performCustomVehicleUturns) { isUturnAllowedHere = false; }*/ //isEntityAllowedToUturn = isEntityAllowedToUturn && !performCustomVehicleUturns; #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane} (id {item.m_laneID}), node {nextNodeId} ({nextIsStartNode}):\n" + "\t" + $"_extPathType={queueItem.pathType}\n" + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + "\t" + $"_vehicleLane={m_vehicleLane}\n" + "\t" + $"_stablePath={m_stablePath}\n" + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + "\t" + $"prevIsOutgoingOneWay={prevIsOutgoingOneWay}\n" + "\t" + $"prevIsRouted={prevIsRouted}\n\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + "\t" + $"isNextBeautificationNode={nextIsBeautificationNode}\n" + //"\t" + $"nextIsRealJunction={nextIsRealJunction}\n" + "\t" + $"nextIsTransitionOrJunction={nextIsTransitionOrJunction}\n" + "\t" + $"nextIsBend={nextIsBend}\n" + "\t" + $"nextIsUntouchable={nextIsUntouchable}\n" + "\t" + $"allowBicycle={allowBicycle}\n" + "\t" + $"isCustomUturnAllowed={junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode)}\n" + "\t" + $"isStrictLaneArrowPolicyEnabled={isStrictLaneChangePolicyEnabled}\n" + "\t" + $"isEntityAllowedToUturn={isEntityAllowedToUturn}\n" + "\t" + $"isUturnAllowedHere={isUturnAllowedHere}\n" //"\t" + $"performCustomVehicleUturns={performCustomVehicleUturns}\n" ); #endif } else { #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}):\n" + "\t" + $"_extPathType={queueItem.pathType}\n" + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + "\t" + $"_stablePath={m_stablePath}\n" + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + "\t" + $"prevIsRouted={prevIsRouted}\n\n" ); #endif } if (allowBicycle || !prevIsRouted) { /* * pedestrian to bicycle lane switch or no routing information available: * if pedestrian lanes should be explored (allowBicycle == true): do this here * if previous segment has custom routing (prevIsRouted == true): do NOT explore vehicle lanes here, else: vanilla exploration of vehicle lanes */ #if DEBUGNEWPF if (debug) { logBuf.Add( $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"-> using DEFAULT exploration mode\n" ); FlushMainLog(logBuf, unitId); } #endif /*if (performCustomVehicleUturns) { isUturnAllowedHere = true; isEntityAllowedToUturn = true; }*/ ushort nextSegmentId = prevSegment.GetRightSegment(nextNodeId); for (int k = 0; k < 8; ++k) { if (nextSegmentId == 0 || nextSegmentId == prevSegmentId) { break; } if (ProcessItemCosts(debug, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, !prevIsRouted, allowBicycle, isMiddle)) { // exceptional u-turns isUturnAllowedHere = true; } nextSegmentId = netManager.m_segments.m_buffer[nextSegmentId].GetRightSegment(nextNodeId); } } if (prevIsRouted) { /* routed vehicle paths */ #if DEBUGNEWPF if (debug) logBuf.Add( $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"-> using CUSTOM exploration mode\n" ); #endif bool canUseLane = CanUseLane(debug, item.m_position.m_segment, prevSegmentInfo, item.m_position.m_lane, prevLaneInfo); LaneTransitionData[] laneTransitions = prevLaneEndRouting.transitions; if (laneTransitions != null && (canUseLane || Options.vehicleRestrictionsAggression != VehicleRestrictionsAggression.Strict)) { #if DEBUGNEWPF if (debug) logBuf.Add( $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"CUSTOM exploration\n" ); #endif LaneChangingCostCalculationMode laneChangingCostCalculationMode = LaneChangingCostCalculationMode.None; // lane changing cost calculation mode to use float? segmentSelectionCost = null; // cost for using that particular segment float? laneSelectionCost = null; // cost for using that particular lane /* * ======================================================================================================= * (1) Apply vehicle restrictions * ======================================================================================================= */ if (! canUseLane) { laneSelectionCost = VehicleRestrictionsManager.PATHFIND_PENALTIES[(int)Options.vehicleRestrictionsAggression]; #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"applied vehicle restrictions for vehicle {queueItem.vehicleId}, type {queueItem.vehicleType}:\n" + "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" ); #endif } if (m_isRoadVehicle && prevLaneInfo != null && prevIsCarLane) { if (Options.advancedAI) { laneChangingCostCalculationMode = LaneChangingCostCalculationMode.ByGivenDistance; #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"AI is active, prev is car lane and we are a car\n" ); #endif } /* * ======================================================================================================= * (2) Apply car ban district policies * ======================================================================================================= */ // Apply costs for traffic ban policies if ((prevLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (prevLaneInfo.m_vehicleType & this.m_vehicleTypes) == VehicleInfo.VehicleType.Car && (netManager.m_segments.m_buffer[item.m_position.m_segment].m_flags & this.m_carBanMask) != NetSegment.Flags.None) { // heavy vehicle ban / car ban ("Old Town" policy) if (laneSelectionCost == null) { laneSelectionCost = 1f; } #if DEBUGNEWPF float? oldLaneSelectionCost = laneSelectionCost; #endif laneSelectionCost *= 7.5f; #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"applied heavy vehicle ban / car ban ('Old Town' policy):\n" + "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" ); #endif } /* * ======================================================================================================= * (3) Apply costs for using/not using transport lanes * ======================================================================================================= */ /* * (1) busses should prefer transport lanes * (2) regular traffic should prefer regular lanes * (3) taxis, service vehicles and emergency vehicles may choose freely between regular and transport lanes */ if ((prevLaneInfo.m_laneType & NetInfo.LaneType.TransportVehicle) != NetInfo.LaneType.None) { // previous lane is a public transport lane if ((queueItem.vehicleType & ExtVehicleType.Bus) != ExtVehicleType.None) { if (laneSelectionCost == null) { laneSelectionCost = 1f; } #if DEBUGNEWPF float? oldLaneSelectionCost = laneSelectionCost; #endif laneSelectionCost *= m_conf.PathFinding.PublicTransportLaneReward; // (1) #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"applied bus-on-transport lane reward:\n" + "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" ); #endif } else if ((queueItem.vehicleType & (ExtVehicleType.RoadPublicTransport | ExtVehicleType.Service | ExtVehicleType.Emergency)) == ExtVehicleType.None) { if (laneSelectionCost == null) { laneSelectionCost = 1f; } #if DEBUGNEWPF float? oldLaneSelectionCost = laneSelectionCost; #endif laneSelectionCost *= m_conf.PathFinding.PublicTransportLanePenalty; // (2) #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"applied car-on-transport lane penalty:\n" + "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" ); #endif } else { // (3), do nothing } } /* * ======================================================================================================= * (4) Apply costs for large vehicles using inner lanes on highways * ======================================================================================================= */ bool nextIsJunction = (netManager.m_nodes.m_buffer[nextNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction; bool nextIsRealJunction = nextIsJunction && (netManager.m_nodes.m_buffer[nextNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut); ushort prevNodeId = (nextNodeId == prevSegment.m_startNode) ? prevSegment.m_endNode : prevSegment.m_startNode; //bool prevIsRealJunction = (netManager.m_nodes.m_buffer[prevNodeId].m_flags & NetNode.Flags.Junction) != NetNode.Flags.None && (netManager.m_nodes.m_buffer[prevNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut); if (prevLaneInfo.m_similarLaneCount > 1) { if (m_isHeavyVehicle && Options.preferOuterLane && prevSegmentRouting.highway && m_pathRandomizer.Int32(m_conf.PathFinding.HeavyVehicleInnerLanePenaltySegmentSel) == 0 /* && (netManager.m_nodes.m_buffer[prevNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) != NetNode.Flags.None */ ) { // penalize large vehicles for using inner lanes if (laneSelectionCost == null) { laneSelectionCost = 1f; } #if DEBUGNEWPF float? oldLaneSelectionCost = laneSelectionCost; #endif float prevRelOuterLane = ((float)prevOuterSimilarLaneIndex / (float)(prevLaneInfo.m_similarLaneCount - 1)); laneSelectionCost *= 1f + m_conf.PathFinding.HeavyVehicleMaxInnerLanePenalty * prevRelOuterLane; #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"applied inner lane penalty:\n" + "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" ); #endif } /* * ======================================================================================================= * (5) Apply costs for randomized lane selection in front of junctions and highway transitions * ======================================================================================================= */ if (Options.advancedAI && !m_stablePath && !m_isHeavyVehicle && nextIsJunction && m_pathRandomizer.Int32(m_conf.AdvancedVehicleAI.LaneRandomizationJunctionSel) == 0) { // randomized lane selection at junctions if (laneSelectionCost == null) { laneSelectionCost = 1f; } #if DEBUGNEWPF float? oldLaneSelectionCost = laneSelectionCost; #endif laneSelectionCost *= 1f + m_pathRandomizer.Int32(2) * m_conf.AdvancedVehicleAI.LaneRandomizationCostFactor; #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"applied lane randomizations at junctions:\n" + "\t" + $"oldLaneSelectionCost={oldLaneSelectionCost}\n" + "\t" + $"=> laneSelectionCost={laneSelectionCost}\n" ); #endif } } /* * ======================================================================================================= * (6) Apply junction costs * ======================================================================================================= */ if (Options.advancedAI && nextIsJunction && prevSegmentRouting.highway) { if (segmentSelectionCost == null) { segmentSelectionCost = 1f; } segmentSelectionCost *= 1f + m_conf.AdvancedVehicleAI.JunctionBaseCost; } /* * ======================================================================================================= * (7) Apply traffic measurement costs for segment selection * ======================================================================================================= */ if (Options.advancedAI && (queueItem.vehicleType & (ExtVehicleType.RoadVehicle & ~ExtVehicleType.Bus)) != ExtVehicleType.None && !m_stablePath) { // segment selection based on segment traffic volume NetInfo.Direction prevFinalDir = nextIsStartNode ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; prevFinalDir = ((prevSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? prevFinalDir : NetInfo.InvertDirection(prevFinalDir); TrafficMeasurementManager.SegmentDirTrafficData prevDirTrafficData = trafficMeasurementManager.segmentDirTrafficData[trafficMeasurementManager.GetDirIndex(item.m_position.m_segment, prevFinalDir)]; float segmentTraffic = Mathf.Clamp(1f - (float)prevDirTrafficData.meanSpeed / (float)TrafficMeasurementManager.REF_REL_SPEED + item.m_trafficRand, 0, 1f); if (segmentSelectionCost == null) { segmentSelectionCost = 1f; } segmentSelectionCost *= 1f + m_conf.AdvancedVehicleAI.TrafficCostFactor * segmentTraffic; #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"applied traffic measurement costs for segment selection:\n" + "\t" + $"segmentTraffic={segmentTraffic}\n" + "\t" + $"=> segmentSelectionCost={segmentSelectionCost}\n" ); #endif if (m_conf.AdvancedVehicleAI.LaneDensityRandInterval > 0 && nextIsRealJunction) { item.m_trafficRand = 0.01f * ((float)m_pathRandomizer.Int32((uint)m_conf.AdvancedVehicleAI.LaneDensityRandInterval + 1u) - m_conf.AdvancedVehicleAI.LaneDensityRandInterval / 2f); #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"updated item.m_trafficRand:\n" + "\t" + $"=> item.m_trafficRand={item.m_trafficRand}\n" ); #endif } } #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"calculated traffic stats:\n" + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" ); #endif } for (int k = 0; k < laneTransitions.Length; ++k) { ushort nextSegmentId = laneTransitions[k].segmentId; if (nextSegmentId == 0) { #if DEBUGNEWPF if (debug) { logBuf.Add( $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"CUSTOM exploration\n" + "\t" + $"transition iteration {k}:\n" + "\t" + $"{laneTransitions[k].ToString()}\n" + "\t" + $"*** SKIPPING *** (nextSegmentId=0)\n" ); FlushMainLog(logBuf, unitId); } #endif continue; } bool uturn = nextSegmentId == prevSegmentId; if (uturn) { // prevent double/forbidden exploration of previous segment by vanilla code during this method execution if (! isEntityAllowedToUturn || ! isUturnAllowedHere) { #if DEBUGNEWPF if (debug) { logBuf.Add( $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"CUSTOM exploration\n" + "\t" + $"transition iteration {k}:\n" + "\t" + $"{laneTransitions[k].ToString()}\n" + "\t" + $"*** SKIPPING *** (u-turns prohibited)\n" ); FlushMainLog(logBuf, unitId); } #endif continue; } } if (laneTransitions[k].type == LaneEndTransitionType.Invalid) { #if DEBUGNEWPF if (debug) { logBuf.Add( $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"CUSTOM exploration\n" + "\t" + $"transition iteration {k}:\n" + "\t" + $"{laneTransitions[k].ToString()}\n" + "\t" + $"*** SKIPPING *** (invalid transition)\n" ); FlushMainLog(logBuf, unitId); } #endif continue; } // allow vehicles to ignore strict lane routing when moving off bool relaxedLaneChanging = m_isRoadVehicle && (queueItem.vehicleType & (ExtVehicleType.Service | ExtVehicleType.PublicTransport | ExtVehicleType.Emergency)) != ExtVehicleType.None && queueItem.vehicleId == 0 && (laneTransitions[k].laneId == m_startLaneA || laneTransitions[k].laneId == m_startLaneB); if (! relaxedLaneChanging && (isStrictLaneChangePolicyEnabled && laneTransitions[k].type == LaneEndTransitionType.Relaxed)) { #if DEBUGNEWPF if (debug) { logBuf.Add( $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"CUSTOM exploration\n" + "\t" + $"transition iteration {k}:\n" + "\t" + $"{laneTransitions[k].ToString()}\n" + "\t" + $"relaxedLaneChanging={relaxedLaneChanging}\n" + "\t" + $"isStrictLaneChangePolicyEnabled={relaxedLaneChanging}\n" + "\t" + $"*** SKIPPING *** (incompatible lane)\n" ); FlushMainLog(logBuf, unitId); } #endif continue; } #if DEBUGNEWPF if (debug) { logBuf.Add( $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"CUSTOM exploration\n" + "\t" + $"transition iteration {k}:\n" + "\t" + $"{laneTransitions[k].ToString()}\n" + "\t" + $"> PERFORMING EXPLORATION NOW <\n" ); FlushMainLog(logBuf, unitId); } #endif bool foundForced = false; int prevLaneIndexFromInner = prevRelSimilarLaneIndex; if (ProcessItemCosts(debug, false, laneChangingCostCalculationMode, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref netManager.m_segments.m_buffer[nextSegmentId], /*routingManager.segmentRoutings[nextSegmentId],*/ ref prevLaneIndexFromInner, connectOffset, true, false, laneTransitions[k].laneIndex, laneTransitions[k].laneId, laneTransitions[k].distance, segmentSelectionCost, laneSelectionCost, isMiddle, out foundForced)) { // process exceptional u-turning in vanilla code isUturnAllowedHere = true; } } } } if (!prevIsRouted && isEntityAllowedToUturn && isUturnAllowedHere) { #if DEBUGNEWPF if (debug) { logBuf.Add($"path unit {unitId}\n" + $"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"-> exploring DEFAULT u-turn\n" ); FlushMainLog(logBuf, unitId); } #endif this.ProcessItemCosts(debug, item, nextNodeId, prevSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref prevSegment, ref prevRelSimilarLaneIndex, connectOffset, true, false, isMiddle); } } if (allowPedestrian) { // switch from walking to driving a car, bus, etc. int nextLaneIndex; uint nextLaneId; if (prevSegment.GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Pedestrian, this.m_vehicleTypes, out nextLaneIndex, out nextLaneId)) { #if DEBUGNEWPF if (debugPed) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring vehicle switch\n" + "\t" + $"_extPathType={queueItem.pathType}\n" + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + "\t" + $"_stablePath={m_stablePath}\n" + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + "\t" + $"nextLaneId={nextLaneId}\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" ); FlushMainLog(logBuf, unitId); } #endif this.ProcessItemPedBicycle(debugPed, item, nextNodeId, prevSegmentId, ref prevSegment, ref prevSegment, parkingConnectOffset, parkingConnectOffset, nextLaneIndex, nextLaneId); // ped } } // allowPedSwitch } // !prevIsPedestrianLane // [18/05/06] conditions commented out because cims could not go to an outside connection with path "walk -> public transport -> walk -> car" if (nextNode.m_lane != 0u /*&& (!Options.prohibitPocketCars || queueItem.vehicleType != ExtVehicleType.PassengerCar || (item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) == NetInfo.LaneType.None)*/) { // transport lines, cargo lines, etc. bool targetDisabled = (nextNode.m_flags & (NetNode.Flags.Disabled | NetNode.Flags.DisableOnlyMiddle)) == NetNode.Flags.Disabled; ushort nextSegmentId = netManager.m_lanes.m_buffer[nextNode.m_lane].m_segment; if (nextSegmentId != 0 && nextSegmentId != item.m_position.m_segment) { #if DEBUGNEWPF if (debug) { logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId} ({nextIsStartNode}): Exploring transport segment\n" + "\t" + $"_extPathType={queueItem.pathType}\n" + "\t" + $"_vehicleTypes={m_vehicleTypes}, _laneTypes={m_laneTypes}\n" + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + "\t" + $"_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"_isHeavyVehicle={m_isHeavyVehicle}\n" + "\t" + $"_stablePath={m_stablePath}\n" + "\t" + $"_isLaneConnectionObeyingEntity={m_isLaneConnectionObeyingEntity}\n" + "\t" + $"_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n\n" + "\t" + $"nextNode.m_lane={nextNode.m_lane}\n" + "\t" + $"nextSegmentId={nextSegmentId}\n" + "\t" + $"nextIsStartNode={nextIsStartNode}\n" ); FlushMainLog(logBuf, unitId); } #endif this.ProcessItemPublicTransport(debug, item, nextNodeId, targetDisabled, nextSegmentId, ref prevSegment, ref netManager.m_segments.m_buffer[nextSegmentId], nextNode.m_lane, nextNode.m_laneOffset, connectOffset); } } #if DEBUGNEWPF if (debug) { FlushMainLog(logBuf, unitId); } #endif } // 2 private void ProcessItemPublicTransport(bool debug, BufferItem item, ushort nextNodeId, bool targetDisabled, ushort nextSegmentId, ref NetSegment prevSegment, ref NetSegment nextSegment, uint nextLaneId, byte offset, byte connectOffset) { if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { return; } NetManager netManager = Singleton.instance; if (targetDisabled && ((netManager.m_nodes.m_buffer[(int)nextSegment.m_startNode].m_flags | netManager.m_nodes.m_buffer[(int)nextSegment.m_endNode].m_flags) & NetNode.Flags.Disabled) == NetNode.Flags.None) { return; } #if COUNTSEGMENTSTONEXTJUNCTION bool nextIsRegularNode = nextNodeId == prevSegment.m_startNode || nextNodeId == prevSegment.m_endNode; bool prevIsRealJunction = false; if (nextIsRegularNode) { // no lane changing directly in front of a junction ushort prevNodeId = (nextNodeId == prevSegment.m_startNode) ? prevSegment.m_endNode : prevSegment.m_startNode; prevIsRealJunction = (netManager.m_nodes.m_buffer[prevNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction && (netManager.m_nodes.m_buffer[prevNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut); } #endif NetInfo nextSegmentInfo = nextSegment.Info; NetInfo prevSegmentInfo = prevSegment.Info; int nextNumLanes = nextSegmentInfo.m_lanes.Length; uint curLaneId = nextSegment.m_lanes; float prevMaxSpeed = 1f; float prevSpeed = 1f; NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; if ((int)item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[(int)item.m_position.m_lane]; prevMaxSpeed = GetLaneSpeedLimit(item.m_position.m_segment, item.m_position.m_lane, item.m_laneID, prevLaneInfo); // NON-STOCK CODE prevLaneType = prevLaneInfo.m_laneType; if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } prevSpeed = this.CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // NON-STOCK CODE } float segLength; if (prevLaneType == NetInfo.LaneType.PublicTransport) { segLength = netManager.m_lanes.m_buffer[item.m_laneID].m_length; } else { segLength = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, prevSegment.m_averageLength); } float offsetLength = (float)Mathf.Abs((int)(connectOffset - item.m_position.m_offset)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * segLength; float methodDistance = item.m_methodDistance + offsetLength; float comparisonValue = item.m_comparisonValue + offsetLength / (prevSpeed * this.m_maxLength); float duration = item.m_duration + offsetLength / prevMaxSpeed; Vector3 b = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); if (! this.m_ignoreCost) { int ticketCost = netManager.m_lanes.m_buffer[item.m_laneID].m_ticketCost; if (ticketCost != 0) { comparisonValue += (float)(ticketCost * this.m_pathRandomizer.Int32(2000u)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; } } uint laneIndex = 0; #if DEBUG int wIter = 0; #endif while (laneIndex < nextNumLanes && curLaneId != 0u) { #if DEBUG ++wIter; if (wIter >= 20) { Log.Error("Too many iterations in ProcessItem2!"); break; } #endif if (nextLaneId == curLaneId) { NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[laneIndex]; if (nextLaneInfo.CheckType(this.m_laneTypes, this.m_vehicleTypes)) { Vector3 a = netManager.m_lanes.m_buffer[nextLaneId].CalculatePosition((float)offset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); float distance = Vector3.Distance(a, b); BufferItem nextItem; #if COUNTSEGMENTSTONEXTJUNCTION // NON-STOCK CODE START // if (prevIsRealJunction) { nextItem.m_numSegmentsToNextJunction = 0; } else { nextItem.m_numSegmentsToNextJunction = item.m_numSegmentsToNextJunction + 1; } // NON-STOCK CODE END // #endif nextItem.m_position.m_segment = nextSegmentId; nextItem.m_position.m_lane = (byte)laneIndex; nextItem.m_position.m_offset = offset; if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { nextItem.m_methodDistance = 0f; } else { nextItem.m_methodDistance = methodDistance + distance; } float nextMaxSpeed = GetLaneSpeedLimit(nextSegmentId, (byte)laneIndex, curLaneId, nextLaneInfo); // NON-STOCK CODE if (nextLaneInfo.m_laneType != NetInfo.LaneType.Pedestrian || nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance || m_stablePath) { nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this.m_maxLength); nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); } else { nextItem.m_direction = nextLaneInfo.m_finalDirection; } if (nextLaneId == this.m_startLaneA) { if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < this.m_startOffsetA) && ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > this.m_startOffsetA)) { return; } float nextSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE float nextOffsetDistance = (float)Mathf.Abs((int)nextItem.m_position.m_offset - (int)this.m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffsetDistance * nextSegment.m_averageLength / (nextSpeed * this.m_maxLength); nextItem.m_duration += nextOffsetDistance * nextSegment.m_averageLength / nextSpeed; } if (nextLaneId == this.m_startLaneB) { if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < this.m_startOffsetB) && ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > this.m_startOffsetB)) { return; } float nextSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE float nextOffsetDistance = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetB)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffsetDistance * nextSegment.m_averageLength / (nextSpeed * this.m_maxLength); nextItem.m_duration += nextOffsetDistance * nextSegment.m_averageLength / nextSpeed; } nextItem.m_laneID = nextLaneId; nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); nextItem.m_trafficRand = 0; #if DEBUGNEWPF if (debug) { m_debugPositions[item.m_position.m_segment].Add(nextItem.m_position.m_segment); } #endif this.AddBufferItem(nextItem, item.m_position); } } return; } curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; ++laneIndex; } } private bool ProcessItemCosts(bool debug, BufferItem item, ushort nextNodeId, ushort nextSegmentId, ref NetSegment prevSegment, /*SegmentRoutingData prevSegmentRouting,*/ ref NetSegment nextSegment, ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian, bool isMiddle) { bool foundForced = false; return ProcessItemCosts(debug, true, LaneChangingCostCalculationMode.None, item, nextNodeId, nextSegmentId, ref prevSegment, /*prevSegmentRouting,*/ ref nextSegment, /*routingManager.segmentRoutings[nextSegmentId],*/ ref laneIndexFromInner, connectOffset, enableVehicle, enablePedestrian, null, null, null, null, null, isMiddle, out foundForced); } // 3 private bool ProcessItemCosts(bool debug, bool obeyStockLaneArrows, LaneChangingCostCalculationMode laneChangingCostCalculationMode, BufferItem item, ushort nextNodeId, ushort nextSegmentId, ref NetSegment prevSegment, /* SegmentRoutingData prevSegmentRouting,*/ ref NetSegment nextSegment, /*SegmentRoutingData nextSegmentRouting,*/ ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian, int? forcedLaneIndex, uint? forcedLaneId, byte? forcedLaneDist, float? segmentSelectionCost, float? laneSelectionCost, bool isMiddle, out bool foundForced) { #if DEBUGNEWPF && DEBUG debug = debug && m_conf.Debug.Switches[1]; #else debug = false; #endif #if DEBUGNEWPF && DEBUG List logBuf = null; if (debug) logBuf = new List(); #endif foundForced = false; bool blocked = false; if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { #if DEBUGNEWPF if (debug) { logBuf.Add($"Segment is PathFailed or flooded: {nextSegment.m_flags}"); logBuf.Add("-- method returns --"); FlushCostLog(logBuf); } #endif return blocked; } NetManager netManager = Singleton.instance; NetInfo nextSegmentInfo = nextSegment.Info; NetInfo prevSegmentInfo = prevSegment.Info; int nextNumLanes = nextSegmentInfo.m_lanes.Length; NetInfo.Direction nextDir = (nextNodeId != nextSegment.m_startNode) ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; NetInfo.Direction nextFinalDir = ((nextSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? nextDir : NetInfo.InvertDirection(nextDir); float turningAngle = 1f; #if DEBUGNEWPF if (debug) logBuf.Add($"isStockLaneChangerUsed={Options.isStockLaneChangerUsed()}, _extVehicleType={queueItem.vehicleType}, nonBus={(queueItem.vehicleType & (ExtVehicleType.RoadVehicle & ~ExtVehicleType.Bus)) != ExtVehicleType.None}, _stablePath={m_stablePath}, enablePedestrian={enablePedestrian}, enableVehicle={enableVehicle}"); #endif float prevMaxSpeed = 1f; float prevLaneSpeed = 1f; NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; VehicleInfo.VehicleType prevVehicleType = VehicleInfo.VehicleType.None; // NON-STOCK CODE START // bool nextIsStartNodeOfPrevSegment = prevSegment.m_startNode == nextNodeId; ushort sourceNodeId = nextIsStartNodeOfPrevSegment ? prevSegment.m_endNode : prevSegment.m_startNode; bool prevIsJunction = (netManager.m_nodes.m_buffer[sourceNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction; #if COUNTSEGMENTSTONEXTJUNCTION bool prevIsRealJunction = prevIsJunction && (netManager.m_nodes.m_buffer[sourceNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut); #endif /*bool nextIsRealJunction = (netManager.m_nodes.m_buffer[nextNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction && (netManager.m_nodes.m_buffer[nextNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut);*/ int prevOuterSimilarLaneIndex = -1; NetInfo.Lane prevLaneInfo = null; // NON-STOCK CODE END // if ((int)item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { prevLaneInfo = prevSegmentInfo.m_lanes[(int)item.m_position.m_lane]; prevLaneType = prevLaneInfo.m_laneType; prevVehicleType = prevLaneInfo.m_vehicleType; prevMaxSpeed = GetLaneSpeedLimit(item.m_position.m_segment, item.m_position.m_lane, item.m_laneID, prevLaneInfo); // NON-STOCK CODE prevLaneSpeed = this.CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // NON-STOCK CODE // NON-STOCK CODE START if ((byte)(prevLaneInfo.m_direction & NetInfo.Direction.Forward) != 0) { prevOuterSimilarLaneIndex = prevLaneInfo.m_similarLaneCount - prevLaneInfo.m_similarLaneIndex - 1; } else { prevOuterSimilarLaneIndex = prevLaneInfo.m_similarLaneIndex; } // NON-STOCK CODE END } if (prevLaneType == NetInfo.LaneType.Vehicle && (prevVehicleType & VehicleInfo.VehicleType.Car) == VehicleInfo.VehicleType.None) { // check turning angle turningAngle = 0.01f - Mathf.Min(nextSegmentInfo.m_maxTurnAngleCos, prevSegmentInfo.m_maxTurnAngleCos); if (turningAngle < 1f) { Vector3 prevDirection; if (nextNodeId == prevSegment.m_startNode) { prevDirection = prevSegment.m_startDirection; } else { prevDirection = prevSegment.m_endDirection; } Vector3 nextDirection; if ((nextDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) { nextDirection = nextSegment.m_endDirection; } else { nextDirection = nextSegment.m_startDirection; } float dirDotProd = prevDirection.x * nextDirection.x + prevDirection.z * nextDirection.z; if (dirDotProd >= turningAngle) { #if DEBUGNEWPF if (debug) { logBuf.Add($"turningAngle < 1f! dirDotProd={dirDotProd} >= turningAngle{turningAngle}!"); logBuf.Add("-- method returns --"); FlushCostLog(logBuf); } #endif return blocked; } } } float prevDist; if (prevLaneType == NetInfo.LaneType.PublicTransport) { prevDist = netManager.m_lanes.m_buffer[item.m_laneID].m_length; } else { prevDist = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, prevSegment.m_averageLength); } float prevCost = prevDist; #if DEBUGNEWPF float oldPrevCost = prevCost; #endif // NON-STOCK CODE START if (segmentSelectionCost != null) { prevCost *= (float)segmentSelectionCost; } if (laneSelectionCost != null) { prevCost *= (float)laneSelectionCost; } // NON-STOCK CODE END #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"applied traffic cost factors:\n" + "\t" + $"oldPrevCost={oldPrevCost}\n" + "\t" + $"=> prevCost={prevCost}\n" ); #endif // stock code check for vehicle ban policies removed // stock code for transport lane usage control removed // calculate ticket costs float ticketCosts = 0f; if (! this.m_ignoreCost) { int ticketCost = netManager.m_lanes.m_buffer[item.m_laneID].m_ticketCost; if (ticketCost != 0) { ticketCosts += (float)(ticketCost * this.m_pathRandomizer.Int32(2000u)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; } } if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } float prevOffsetCost = (float)Mathf.Abs((int)(connectOffset - item.m_position.m_offset)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevCost; float prevMethodDist = item.m_methodDistance + (float)Mathf.Abs((int)connectOffset - (int)item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevDist; float prevDuration = item.m_duration + prevOffsetCost / prevMaxSpeed; // NON-STOCK: vehicle restriction are applied to previous segment length in MainPathFind (not here, and not to prevOffsetCost) float prevComparisonPlusOffsetCostOverSpeed = item.m_comparisonValue + prevOffsetCost / (prevLaneSpeed * this.m_maxLength); if (!m_stablePath) { // CO randomization. Only randomizes over segments, not over lanes. if (segmentSelectionCost == null) { // NON-STOCK CODE Randomizer randomizer = new Randomizer(this.m_pathFindIndex << 16 | (uint)item.m_position.m_segment); prevOffsetCost *= (float)(randomizer.Int32(900, 1000 + (int)(prevSegment.m_trafficDensity * 10)) + this.m_pathRandomizer.Int32(20u)) * 0.001f; } } Vector3 prevLaneConnectPos = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); int newLaneIndexFromInner = laneIndexFromInner; bool transitionNode = (netManager.m_nodes.m_buffer[nextNodeId].m_flags & NetNode.Flags.Transition) != NetNode.Flags.None; NetInfo.LaneType allowedLaneTypes = this.m_laneTypes; VehicleInfo.VehicleType allowedVehicleTypes = this.m_vehicleTypes; if (!enableVehicle) { allowedVehicleTypes &= VehicleInfo.VehicleType.Bicycle; if (allowedVehicleTypes == VehicleInfo.VehicleType.None) { allowedLaneTypes &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } } if (!enablePedestrian) { allowedLaneTypes &= ~NetInfo.LaneType.Pedestrian; } #if DEBUGNEWPF if (debug) logBuf.Add($"allowedVehicleTypes={allowedVehicleTypes} allowedLaneTypes={allowedLaneTypes}"); #endif // NON-STOCK CODE START // float laneChangeBaseCosts = 1f; float junctionBaseCosts = 1f; if (laneChangingCostCalculationMode != LaneChangingCostCalculationMode.None) { float rand = (float)this.m_pathRandomizer.Int32(101u) / 100f; laneChangeBaseCosts = m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost + rand * (m_conf.AdvancedVehicleAI.LaneChangingBaseMaxCost - m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost); if (prevIsJunction) { junctionBaseCosts = m_conf.AdvancedVehicleAI.LaneChangingJunctionBaseCost; } } // NON-STOCK CODE END // uint laneIndex = forcedLaneIndex != null ? (uint)forcedLaneIndex : 0u; // NON-STOCK CODE, forcedLaneIndex is not null if the next node is a (real) junction uint curLaneId = (uint)(forcedLaneId != null ? forcedLaneId : nextSegment.m_lanes); // NON-STOCK CODE, forceLaneId is not null if the next node is a (real) junction while (laneIndex < nextNumLanes && curLaneId != 0u) { // NON-STOCK CODE START // if (forcedLaneIndex != null && laneIndex != forcedLaneIndex) { #if DEBUGNEWPF if (debug) logBuf.Add($"forceLaneIndex break! laneIndex={laneIndex}"); #endif break; } // NON-STOCK CODE END // NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[laneIndex]; if ((byte)(nextLaneInfo.m_finalDirection & nextFinalDir) != 0) { // lane direction is compatible #if DEBUGNEWPF if (debug) logBuf.Add($"Lane direction check passed: {nextLaneInfo.m_finalDirection}"); #endif if (nextLaneInfo.CheckType(allowedLaneTypes, allowedVehicleTypes) && (nextSegmentId != item.m_position.m_segment || laneIndex != (int)item.m_position.m_lane)) { // vehicle types match and no u-turn to the previous lane #if DEBUGNEWPF if (debug) logBuf.Add($"vehicle type check passed: {nextLaneInfo.CheckType(allowedLaneTypes, allowedVehicleTypes)} && {(nextSegmentId != item.m_position.m_segment || laneIndex != (int)item.m_position.m_lane)}"); #endif // NON-STOCK CODE START // float nextMaxSpeed = GetLaneSpeedLimit(nextSegmentId, (byte)laneIndex, curLaneId, nextLaneInfo); float customDeltaCost = 0f; // NON-STOCK CODE END // Vector3 nextLaneEndPointPos; if ((nextDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) { nextLaneEndPointPos = netManager.m_lanes.m_buffer[curLaneId].m_bezier.d; } else { nextLaneEndPointPos = netManager.m_lanes.m_buffer[curLaneId].m_bezier.a; } float transitionCost = Vector3.Distance(nextLaneEndPointPos, prevLaneConnectPos); // This gives the distance of the previous to next lane endpoints. #if DEBUGNEWPF if (debug) logBuf.Add($"costs from {nextSegmentId} (off {(byte)(((nextDir & NetInfo.Direction.Forward) == 0) ? 0 : 255)}) to {item.m_position.m_segment} (off {item.m_position.m_offset}), connectOffset={connectOffset}: transitionCost={transitionCost}"); #endif if (transitionNode) { transitionCost *= 2f; } BufferItem nextItem; #if COUNTSEGMENTSTONEXTJUNCTION // NON-STOCK CODE START // if (prevIsRealJunction) { nextItem.m_numSegmentsToNextJunction = 0; } else { nextItem.m_numSegmentsToNextJunction = item.m_numSegmentsToNextJunction + 1; } // NON-STOCK CODE END // #endif nextItem.m_comparisonValue = ticketCosts; nextItem.m_position.m_segment = nextSegmentId; nextItem.m_position.m_lane = (byte)laneIndex; nextItem.m_position.m_offset = (byte)(((nextDir & NetInfo.Direction.Forward) == 0) ? 0 : 255); if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { nextItem.m_methodDistance = 0f; // NON-STOCK CODE START if (Options.realisticPublicTransport && isMiddle && nextLaneInfo.m_laneType == NetInfo.LaneType.PublicTransport && (item.m_lanesUsed & NetInfo.LaneType.PublicTransport) != NetInfo.LaneType.None) { // apply penalty when switching public transport vehicles float transportTransitionPenalty = (m_conf.PathFinding.PublicTransportTransitionMinPenalty + ((float)netManager.m_nodes.m_buffer[nextNodeId].m_maxWaitTime * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR) * (m_conf.PathFinding.PublicTransportTransitionMaxPenalty - m_conf.PathFinding.PublicTransportTransitionMinPenalty)) / (0.25f * this.m_maxLength); #if DEBUGNEWPF if (debug) logBuf.Add($"applying public transport transition penalty: {transportTransitionPenalty}"); #endif nextItem.m_comparisonValue += transportTransitionPenalty; } // NON-STOCK CODE END } else { nextItem.m_methodDistance = prevMethodDist + transitionCost; } #if DEBUGNEWPF if (debug) logBuf.Add($"checking if methodDistance is in range: {nextLaneInfo.m_laneType != NetInfo.LaneType.Pedestrian} || {nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance} ({nextItem.m_methodDistance})"); #endif if (nextLaneInfo.m_laneType != NetInfo.LaneType.Pedestrian || nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance || m_stablePath) { // NON-STOCK CODE START // if (laneChangingCostCalculationMode == LaneChangingCostCalculationMode.None) { float transitionCostOverMeanMaxSpeed = transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this.m_maxLength); nextItem.m_comparisonValue += prevComparisonPlusOffsetCostOverSpeed + transitionCostOverMeanMaxSpeed; // stock code } else { nextItem.m_comparisonValue += item.m_comparisonValue; customDeltaCost = transitionCost + prevOffsetCost; // customDeltaCost now holds the costs for driving on the segment + costs for changing the segment #if DEBUGNEWPF if (debug) { logBuf.Add($"Path from {nextSegmentId} (idx {laneIndex}, id {curLaneId}) to {item.m_position.m_segment} (lane {prevOuterSimilarLaneIndex} from outer, idx {item.m_position.m_lane}): laneChangingCostCalculationMode={laneChangingCostCalculationMode}, transitionCost={transitionCost}"); } #endif } nextItem.m_duration = prevDuration + transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); // account for public tranport transition costs on non-PT paths if ( #if DEBUG !m_conf.Debug.Switches[20] && #endif Options.realisticPublicTransport && (curLaneId == this.m_startLaneA || curLaneId == this.m_startLaneB) && (item.m_lanesUsed & (NetInfo.LaneType.Pedestrian | NetInfo.LaneType.PublicTransport)) == NetInfo.LaneType.Pedestrian) { float transportTransitionPenalty = (2f * m_conf.PathFinding.PublicTransportTransitionMaxPenalty) / (0.25f * this.m_maxLength); #if DEBUGNEWPF if (debug) logBuf.Add($"applying public transport transition penalty on non-PT path: {transportTransitionPenalty}"); #endif nextItem.m_comparisonValue += transportTransitionPenalty; } // NON-STOCK CODE END // nextItem.m_direction = nextDir; if (curLaneId == this.m_startLaneA) { if (((byte)(nextItem.m_direction & NetInfo.Direction.Forward) == 0 || nextItem.m_position.m_offset < this.m_startOffsetA) && ((byte)(nextItem.m_direction & NetInfo.Direction.Backward) == 0 || nextItem.m_position.m_offset > this.m_startOffsetA)) { #if DEBUGNEWPF if (debug) logBuf.Add($"Current lane is start lane A. goto next lane"); #endif goto CONTINUE_LANE_LOOP; } float nextLaneSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE float nextOffset = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetA)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; float nextSegLength = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, nextSegment.m_averageLength); nextItem.m_comparisonValue += nextOffset * nextSegLength / (nextLaneSpeed * this.m_maxLength); nextItem.m_duration += nextOffset * nextSegLength / nextLaneSpeed; } if (curLaneId == this.m_startLaneB) { if (((byte)(nextItem.m_direction & NetInfo.Direction.Forward) == 0 || nextItem.m_position.m_offset < this.m_startOffsetB) && ((byte)(nextItem.m_direction & NetInfo.Direction.Backward) == 0 || nextItem.m_position.m_offset > this.m_startOffsetB)) { #if DEBUGNEWPF if (debug) logBuf.Add($"Current lane is start lane B. goto next lane"); #endif goto CONTINUE_LANE_LOOP; } float nextLaneSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE float nextOffset = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetB)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; float nextSegLength = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, nextSegment.m_averageLength); nextItem.m_comparisonValue += nextOffset * nextSegLength / (nextLaneSpeed * this.m_maxLength); nextItem.m_duration += nextOffset * nextSegLength / nextLaneSpeed; } if (!this.m_ignoreBlocked && (nextSegment.m_flags & NetSegment.Flags.Blocked) != NetSegment.Flags.None && (byte)(nextLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0) { // NON-STOCK CODE START // if (laneChangingCostCalculationMode != LaneChangingCostCalculationMode.None) { #if DEBUGNEWPF float oldCustomDeltaCost = customDeltaCost; #endif // apply vanilla game restriction policies customDeltaCost *= 10f; #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + "\t" + $"applied blocked road cost factor on activated AI:\n" + "\t" + $"oldCustomDeltaCost={oldCustomDeltaCost}\n" + "\t" + $"=> customDeltaCost={customDeltaCost}\n" ); #endif } else { // NON-STOCK CODE END // #if DEBUGNEWPF if (debug) logBuf.Add($"Applying blocked road cost factor on disabled advanced AI"); #endif nextItem.m_comparisonValue += 0.1f; } blocked = true; } if ((byte)(nextLaneInfo.m_laneType & prevLaneType) != 0 && nextLaneInfo.m_vehicleType == prevVehicleType) { #if DEBUGNEWPF if (debug) logBuf.Add($"Applying stock lane changing costs. obeyStockLaneArrows={obeyStockLaneArrows}"); #endif if (obeyStockLaneArrows) { // this is CO's way of matching lanes between segments int firstTarget = (int)netManager.m_lanes.m_buffer[curLaneId].m_firstTarget; int lastTarget = (int)netManager.m_lanes.m_buffer[curLaneId].m_lastTarget; if (laneIndexFromInner < firstTarget || laneIndexFromInner >= lastTarget) { nextItem.m_comparisonValue += Mathf.Max(1f, transitionCost * 3f - 3f) / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this.m_maxLength); } } // NON-STOCK CODE // stock code that prohibits cars to be on public transport lanes removed /*if (!this._transportVehicle && nextLaneInfo.m_laneType == NetInfo.LaneType.TransportVehicle) { nextItem.m_comparisonValue += 20f / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this._maxLength); }*/ } // NON-STOCK CODE START // bool addItem = true; // should we add the next item to the buffer? if (laneChangingCostCalculationMode != LaneChangingCostCalculationMode.None) { // Advanced AI cost calculation #if DEBUGNEWPF if (debug) logBuf.Add($"Calculating advanced AI lane changing costs"); #endif int laneDist; // absolute lane distance if (laneChangingCostCalculationMode == LaneChangingCostCalculationMode.ByGivenDistance && forcedLaneDist != null) { laneDist = (byte)forcedLaneDist; } else { int nextOuterSimilarLaneIndex; if ((byte)(nextLaneInfo.m_direction & NetInfo.Direction.Forward) != 0) { nextOuterSimilarLaneIndex = nextLaneInfo.m_similarLaneCount - nextLaneInfo.m_similarLaneIndex - 1; } else { nextOuterSimilarLaneIndex = nextLaneInfo.m_similarLaneIndex; } int relLaneDist = nextOuterSimilarLaneIndex - prevOuterSimilarLaneIndex; // relative lane distance (positive: change to more outer lane, negative: change to more inner lane) laneDist = Math.Abs(relLaneDist); } // apply lane changing costs float laneMetric = 1f; bool relaxedLaneChanging = queueItem.vehicleId == 0 && (curLaneId == m_startLaneA || curLaneId == m_startLaneB); if (laneDist > 0 && !relaxedLaneChanging) { laneMetric = 1f + laneDist * junctionBaseCosts * laneChangeBaseCosts * // road type based lane changing cost factor (laneDist > 1 ? m_conf.AdvancedVehicleAI.MoreThanOneLaneChangingCostFactor : 1f); // additional costs for changing multiple lanes at once } // on highways: avoid lane changing before junctions: multiply with inverted distance to next junction /*float junctionMetric = 1f; if (prevSegmentRouting.highway && nextSegmentRouting.highway && // we are on a highway road !nextIsRealJunction && // next is not a junction laneDist > 0) { uint dist = _pathRandomizer.UInt32(_conf.MinHighwayInterchangeSegments, (_conf.MaxHighwayInterchangeSegments >= _conf.MinHighwayInterchangeSegments ? _conf.MaxHighwayInterchangeSegments : _conf.MinHighwayInterchangeSegments) + 1u); if (nextItem.m_numSegmentsToNextJunction < dist) { junctionMetric = _conf.HighwayInterchangeLaneChangingBaseCost * (float)(dist - nextItem.m_numSegmentsToNextJunction); } }*/ // total metric value float metric = laneMetric/* * junctionMetric*/; //float oldTransitionDistanceOverMaxSpeed = transitionCostOverMeanMaxSpeed; float finalDeltaCost = (metric * customDeltaCost) / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * this.m_maxLength); // if (finalDeltaCost < 0f) { // // should never happen //#if DEBUG // Log.Error($"THREAD #{Thread.CurrentThread.ManagedThreadId}, PF {this._pathFindIndex}: distanceOverMeanMaxSpeed < 0! seg. {nextSegmentId}, lane {laneIndex}, off {nextItem.m_position.m_offset} -> {item.m_position.m_segment}, lane {item.m_position.m_lane}, off {item.m_position.m_offset}. distanceOverMeanMaxSpeed={finalDeltaCost}, prevSpeed={prevUsage}"/* + ", prevSpeed={prevSpeed}"*/); //#endif // finalDeltaCost = 0f; // } else if (Single.IsNaN(finalDeltaCost) || Single.IsInfinity(finalDeltaCost)) { // // Fallback if we mess something up. Should never happen. //#if DEBUG // //if (costDebug) // Log.Error($"Pathfinder ({this._pathFindIndex}): distanceOverMeanMaxSpeed is NaN or Infinity: seg. {nextSegmentId}, lane {laneIndex}, off {nextItem.m_position.m_offset} -> {item.m_position.m_segment}, lane {item.m_position.m_lane}, off {item.m_position.m_offset}. {finalDeltaCost} // nextMaxSpeed={nextMaxSpeed} prevMaxSpeed={prevMaxSpeed} nextMaxSpeed={nextMaxSpeed} laneDist={laneDist} laneMetric={laneMetric} metric={metric}"); //#endif //#if DEBUGNEWPF // Log.Error($"THREAD #{Thread.CurrentThread.ManagedThreadId}, PF {this._pathFindIndex}: deltaCostOverMeanMaxSpeed is NaN! deltaCostOverMeanMaxSpeed={finalDeltaCost}"); //#endif // finalDeltaCost = oldTransitionDistanceOverMaxSpeed; // } nextItem.m_comparisonValue += finalDeltaCost; if (nextItem.m_comparisonValue > 1f) { // comparison value got too big. Do not add the lane to the buffer addItem = false; } #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + $"-> TRANSIT to seg. {nextSegmentId}, lane {laneIndex}\n" + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" + "\t" + $"nextMaxSpeed={nextMaxSpeed}\n\n" + "\t" + $"laneChangingCostCalculationMode={laneChangingCostCalculationMode}\n\n" + "\t" + $"laneDist={laneDist}\n\n" + "\t" + $"_extVehicleType={queueItem.vehicleType}\n" + "\t" + $"laneChangeRoadBaseCost={laneChangeBaseCosts}\n" + "\t" + $"moreThanOneLaneCost={(laneDist > 1 ? m_conf.AdvancedVehicleAI.MoreThanOneLaneChangingCostFactor : 1f)}\n" + "\t" + $"=> laneMetric={laneMetric}\n" + //"\t" + $"=> junctionMetric={junctionMetric}\n\n" + "\t" + $"=> metric={metric}\n" + "\t" + $"deltaCostOverMeanMaxSpeed={finalDeltaCost}\n" + "\t" + $"nextItem.m_comparisonValue={nextItem.m_comparisonValue}\n\n" + "\t" + $"=> addItem={addItem}\n" ); #endif } if (forcedLaneIndex != null && laneIndex == forcedLaneIndex && addItem) { foundForced = true; } if (addItem) { // NON-STOCK CODE END // nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); nextItem.m_laneID = curLaneId; nextItem.m_trafficRand = item.m_trafficRand; #if DEBUGNEWPF if (debug) { logBuf.Add($"adding item: seg {nextItem.m_position.m_segment}, lane {nextItem.m_position.m_lane} (idx {nextItem.m_laneID}), off {nextItem.m_position.m_offset} -> seg {item.m_position.m_segment}, lane {item.m_position.m_lane} (idx {item.m_laneID}), off {item.m_position.m_offset}, cost {nextItem.m_comparisonValue}, previous cost {item.m_comparisonValue}, methodDist {nextItem.m_methodDistance}"); m_debugPositions[item.m_position.m_segment].Add(nextItem.m_position.m_segment); } #endif this.AddBufferItem(nextItem, item.m_position); // NON-STOCK CODE START // } else { #if DEBUGNEWPF if (debug) logBuf.Add($"item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}:\n" + $"-> item seg. {nextSegmentId}, lane {laneIndex} NOT ADDED\n" ); #endif } // NON-STOCK CODE END // } } goto CONTINUE_LANE_LOOP; } if ((byte)(nextLaneInfo.m_laneType & prevLaneType) != 0 && (nextLaneInfo.m_vehicleType & prevVehicleType) != VehicleInfo.VehicleType.None) { ++newLaneIndexFromInner; } CONTINUE_LANE_LOOP: curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; ++laneIndex; continue; } // foreach lane laneIndexFromInner = newLaneIndexFromInner; #if DEBUGNEWPF if (debug) { logBuf.Add("-- method returns --"); FlushCostLog(logBuf); } #endif return blocked; } #if DEBUGNEWPF private void FlushCostLog(List logBuf) { if (logBuf == null) return; foreach (String toLog in logBuf) { Log._Debug($"Pathfinder ({this.m_pathFindIndex}) for unit {Calculating} *COSTS*: " + toLog); } logBuf.Clear(); } private void FlushMainLog(List logBuf, uint unitId) { if (logBuf == null) return; foreach (String toLog in logBuf) { Log._Debug($"Pathfinder ({this.m_pathFindIndex}) for unit {Calculating} *MAIN*: " + toLog); } logBuf.Clear(); } #endif // 4 private void ProcessItemPedBicycle(bool debug, BufferItem item, ushort nextNodeId, ushort nextSegmentId, ref NetSegment prevSegment, ref NetSegment nextSegment, byte connectOffset, byte laneSwitchOffset, int nextLaneIndex, uint nextLaneId) { #if DEBUGNEWPF && DEBUG List logBuf = null; if (debug) { logBuf = new List(); logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: exploring\n" + "\t" + $"nextSegmentId={nextSegmentId}\n" + "\t" + $"connectOffset={connectOffset}\n" + "\t" + $"laneSwitchOffset={laneSwitchOffset}\n" + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + "\t" + $"nextLaneId={nextLaneId}\n"); } #endif if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { #if DEBUGNEWPF if (debug) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- next segment disabled mask is incompatible!\n" + "\t" + $"nextSegment.m_flags={nextSegment.m_flags}\n" + "\t" + $"_disableMask={m_disableMask}\n"); FlushCostLog(logBuf); } #endif return; } NetManager netManager = Singleton.instance; // NON-STOCK CODE START bool nextIsRegularNode = nextNodeId == nextSegment.m_startNode || nextNodeId == nextSegment.m_endNode; #if COUNTSEGMENTSTONEXTJUNCTION bool prevIsRealJunction = false; #endif if (nextIsRegularNode) { bool nextIsStartNode = nextNodeId == nextSegment.m_startNode; ushort prevNodeId = (nextNodeId == prevSegment.m_startNode) ? prevSegment.m_endNode : prevSegment.m_startNode; #if COUNTSEGMENTSTONEXTJUNCTION prevIsRealJunction = (netManager.m_nodes.m_buffer[prevNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) != NetNode.Flags.Junction && (netManager.m_nodes.m_buffer[prevNodeId].m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut); #endif // check if pedestrians are not allowed to cross here if (!junctionManager.IsPedestrianCrossingAllowed(nextSegmentId, nextIsStartNode)) { #if DEBUGNEWPF if (debug) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- pedestrian crossing not allowed!\n"); FlushCostLog(logBuf); } #endif return; } // check if pedestrian light won't change to green ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(nextSegmentId, nextIsStartNode, false); if (lights != null) { if (lights.InvalidPedestrianLight) { #if DEBUGNEWPF if (debug) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- invalid pedestrian lights!\n"); FlushCostLog(logBuf); } #endif return; } } } // NON-STOCK CODE END // NON-STOCK CODE START /*if (!_allowEscapeTransport) { ushort transportLineId = netManager.m_nodes.m_buffer[targetNodeId].m_transportLine; if (transportLineId != 0 && Singleton.instance.m_lines.m_buffer[transportLineId].Info.m_transportType == TransportInfo.TransportType.EvacuationBus) return; }*/ // NON-STOCK CODE END NetInfo nextSegmentInfo = nextSegment.Info; NetInfo prevSegmentInfo = prevSegment.Info; int numLanes = nextSegmentInfo.m_lanes.Length; float distance; byte offset; Vector3 b = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)laneSwitchOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); if (nextSegmentId == item.m_position.m_segment) { // next segment is previous segment Vector3 a = netManager.m_lanes.m_buffer[nextLaneId].CalculatePosition((float)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); distance = Vector3.Distance(a, b); offset = connectOffset; } else { // next segment differs from previous segment NetInfo.Direction direction = (nextNodeId != nextSegment.m_startNode) ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; Vector3 a; if ((byte)(direction & NetInfo.Direction.Forward) != 0) { a = netManager.m_lanes.m_buffer[nextLaneId].m_bezier.d; } else { a = netManager.m_lanes.m_buffer[nextLaneId].m_bezier.a; } distance = Vector3.Distance(a, b); offset = (byte)(((direction & NetInfo.Direction.Forward) == 0) ? 0 : 255); } float prevMaxSpeed = 1f; float prevSpeed = 1f; NetInfo.LaneType laneType = NetInfo.LaneType.None; VehicleInfo.VehicleType vehicleType = VehicleInfo.VehicleType.None; // NON-STOCK CODE if ((int)item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[(int)item.m_position.m_lane]; prevMaxSpeed = GetLaneSpeedLimit(item.m_position.m_segment, item.m_position.m_lane, item.m_laneID, prevLaneInfo); // SpeedLimitManager.GetLockFreeGameSpeedLimit(item.m_position.m_segment, item.m_position.m_lane, item.m_laneID, ref lane2); // NON-STOCK CODE laneType = prevLaneInfo.m_laneType; vehicleType = prevLaneInfo.m_vehicleType; // NON-STOCK CODE if ((byte)(laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0) { laneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } prevSpeed = this.CalculateLaneSpeed(prevMaxSpeed, laneSwitchOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // NON-STOCK CODE } float segLength; if (laneType == NetInfo.LaneType.PublicTransport) { segLength = netManager.m_lanes.m_buffer[item.m_laneID].m_length; } else { segLength = Mathf.Max(SEGMENT_MIN_AVERAGE_LENGTH, prevSegment.m_averageLength); } float offsetLength = (float)Mathf.Abs((int)(laneSwitchOffset - item.m_position.m_offset)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * segLength; float methodDistance = item.m_methodDistance + offsetLength; float comparisonValue = item.m_comparisonValue + offsetLength / (prevSpeed * this.m_maxLength); float duration = item.m_duration + offsetLength / prevMaxSpeed; if (! this.m_ignoreCost) { int ticketCost = netManager.m_lanes.m_buffer[item.m_laneID].m_ticketCost; if (ticketCost != 0) { comparisonValue += (float)(ticketCost * this.m_pathRandomizer.Int32(2000u)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; } } if (nextLaneIndex < numLanes) { NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; BufferItem nextItem; #if COUNTSEGMENTSTONEXTJUNCTION // NON-STOCK CODE START // if (prevIsRealJunction) { nextItem.m_numSegmentsToNextJunction = 0; } else { nextItem.m_numSegmentsToNextJunction = item.m_numSegmentsToNextJunction + 1; } // NON-STOCK CODE END // #endif nextItem.m_position.m_segment = nextSegmentId; nextItem.m_position.m_lane = (byte)nextLaneIndex; nextItem.m_position.m_offset = offset; if ((nextLaneInfo.m_laneType & laneType) == NetInfo.LaneType.None) { nextItem.m_methodDistance = 0f; } else { if (item.m_methodDistance == 0f) { comparisonValue += 100f / (0.25f * this.m_maxLength); } nextItem.m_methodDistance = methodDistance + distance; } float nextMaxSpeed = GetLaneSpeedLimit(nextSegmentId, (byte)nextLaneIndex, nextLaneId, nextLaneInfo); // NON-STOCK CODE if (nextLaneInfo.m_laneType != NetInfo.LaneType.Pedestrian || nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance || m_stablePath) { nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.25f * this.m_maxLength); nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); } else { nextItem.m_direction = nextLaneInfo.m_finalDirection; } if (nextLaneId == this.m_startLaneA) { if (((byte)(nextItem.m_direction & NetInfo.Direction.Forward) == 0 || nextItem.m_position.m_offset < this.m_startOffsetA) && ((byte)(nextItem.m_direction & NetInfo.Direction.Backward) == 0 || nextItem.m_position.m_offset > this.m_startOffsetA)) { #if DEBUGNEWPF if (debug) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- start lane A reached in wrong direction!\n"); FlushCostLog(logBuf); } #endif return; } float nextSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE float nextOffset = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetA)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * this.m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; } if (nextLaneId == this.m_startLaneB) { if (((byte)(nextItem.m_direction & NetInfo.Direction.Forward) == 0 || nextItem.m_position.m_offset < this.m_startOffsetB) && ((byte)(nextItem.m_direction & NetInfo.Direction.Backward) == 0 || nextItem.m_position.m_offset > this.m_startOffsetB)) { #if DEBUGNEWPF if (debug) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- start lane B reached in wrong direction!\n"); FlushCostLog(logBuf); } #endif return; } float nextSpeed = this.CalculateLaneSpeed(nextMaxSpeed, this.m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); // NON-STOCK CODE float nextOffset = (float)Mathf.Abs((int)(nextItem.m_position.m_offset - this.m_startOffsetB)) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * this.m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; } nextItem.m_laneID = nextLaneId; nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); nextItem.m_trafficRand = 0; #if DEBUGNEWPF if (debug) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: *ADDING*\n" + "\t" + $"nextItem.m_laneID={nextItem.m_laneID}\n" + "\t" + $"nextItem.m_lanesUsed={nextItem.m_lanesUsed}\n" + "\t" + $"nextItem.m_vehiclesUsed={nextItem.m_vehiclesUsed }\n" + "\t" + $"nextItem.m_comparisonValue={nextItem.m_comparisonValue}\n" + "\t" + $"nextItem.m_methodDistance={nextItem.m_methodDistance}\n" ); FlushCostLog(logBuf); m_debugPositions[item.m_position.m_segment].Add(nextItem.m_position.m_segment); } #endif this.AddBufferItem(nextItem, item.m_position); } else { #if DEBUGNEWPF if (debug) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- lane incompatible or method distance too large!\n" + "\t" + $"nextItem.m_methodDistance={nextItem.m_methodDistance}\n" + "\t" + $"nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}\n" ); FlushCostLog(logBuf); } #endif } } else { #if DEBUGNEWPF if (debug) { logBuf.Add($"*PED* item: seg. {item.m_position.m_segment}, lane {item.m_position.m_lane}, node {nextNodeId}: -NOT ADDING- nextLaneIndex >= numLanes ({nextLaneIndex} >= {numLanes})!\n"); FlushCostLog(logBuf); } #endif } } private float CalculateLaneSpeed(float speedLimit, byte startOffset, byte endOffset, ref NetSegment segment, NetInfo.Lane laneInfo) { /*if ((laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram)) != VehicleInfo.VehicleType.None) speedLimit = laneInfo.m_speedLimit; */ NetInfo.Direction direction = ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? laneInfo.m_finalDirection : NetInfo.InvertDirection(laneInfo.m_finalDirection); if ((byte)(direction & NetInfo.Direction.Avoid) == 0) { return speedLimit; } if (endOffset > startOffset && direction == NetInfo.Direction.AvoidForward) { return speedLimit * 0.1f; } if (endOffset < startOffset && direction == NetInfo.Direction.AvoidBackward) { return speedLimit * 0.1f; } return speedLimit * 0.2f; } private void AddBufferItem(BufferItem item, PathUnit.Position target) { uint laneLocation = m_laneLocation[item.m_laneID]; uint locPathFindIndex = laneLocation >> 16; // upper 16 bit, expected (?) path find index int bufferIndex = (int)(laneLocation & 65535u); // lower 16 bit int comparisonBufferPos; if (locPathFindIndex == m_pathFindIndex) { if (item.m_comparisonValue >= m_buffer[bufferIndex].m_comparisonValue) { return; } int bufferPosIndex = bufferIndex >> 6; // arithmetic shift (sign stays), upper 10 bit int bufferPos = bufferIndex & -64; // upper 10 bit (no shift) if (bufferPosIndex < m_bufferMinPos || (bufferPosIndex == m_bufferMinPos && bufferPos < m_bufferMin[bufferPosIndex])) { return; } comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); if (comparisonBufferPos == bufferPosIndex) { m_buffer[bufferIndex] = item; m_laneTarget[item.m_laneID] = target; return; } int newBufferIndex = bufferPosIndex << 6 | m_bufferMax[bufferPosIndex]--; BufferItem bufferItem = m_buffer[newBufferIndex]; m_laneLocation[bufferItem.m_laneID] = laneLocation; m_buffer[bufferIndex] = bufferItem; } else { comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); } if (comparisonBufferPos >= 1024) { return; } if (comparisonBufferPos < 0) { return; } while (m_bufferMax[comparisonBufferPos] == 63) { ++comparisonBufferPos; if (comparisonBufferPos == 1024) { return; } } if (comparisonBufferPos > m_bufferMaxPos) { m_bufferMaxPos = comparisonBufferPos; } bufferIndex = (comparisonBufferPos << 6 | ++m_bufferMax[comparisonBufferPos]); m_buffer[bufferIndex] = item; m_laneLocation[item.m_laneID] = (m_pathFindIndex << 16 | (uint)bufferIndex); m_laneTarget[item.m_laneID] = target; } private void GetLaneDirection(PathUnit.Position pathPos, out NetInfo.Direction direction, out NetInfo.LaneType laneType, out VehicleInfo.VehicleType vehicleType) { NetManager instance = Singleton.instance; NetInfo info = instance.m_segments.m_buffer[pathPos.m_segment].Info; if (info.m_lanes.Length > pathPos.m_lane) { direction = info.m_lanes[pathPos.m_lane].m_finalDirection; laneType = info.m_lanes[pathPos.m_lane].m_laneType; vehicleType = info.m_lanes[pathPos.m_lane].m_vehicleType; if ((instance.m_segments.m_buffer[pathPos.m_segment].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { direction = NetInfo.InvertDirection(direction); } } else { direction = NetInfo.Direction.None; laneType = NetInfo.LaneType.None; vehicleType = VehicleInfo.VehicleType.None; } } private void PathFindThread() { while (true) { //Log.Message($"Pathfind Thread #{Thread.CurrentThread.ManagedThreadId} iteration!"); try { Monitor.Enter(QueueLock); while (QueueFirst == 0u && !Terminated) { Monitor.Wait(QueueLock); } if (Terminated) { break; } Calculating = QueueFirst; QueueFirst = CustomPathManager._instance.queueItems[Calculating].nextPathUnitId; //QueueFirst = PathUnits.m_buffer[Calculating].m_nextPathUnit; if (QueueFirst == 0u) { QueueLast = 0u; m_queuedPathFindCount = 0; } else { --m_queuedPathFindCount; } CustomPathManager._instance.queueItems[Calculating].nextPathUnitId = 0u; //PathUnits.m_buffer[Calculating].m_nextPathUnit = 0u; // check if path unit is created /*if ((PathUnits.m_buffer[Calculating].m_pathFindFlags & PathUnit.FLAG_CREATED) == 0) { Log.Warning($"CustomPathFind: Refusing to calculate path unit {Calculating} which is not created!"); continue; }*/ PathUnits.m_buffer[Calculating].m_pathFindFlags = (byte)((PathUnits.m_buffer[Calculating].m_pathFindFlags & ~PathUnit.FLAG_CREATED) | PathUnit.FLAG_CALCULATING); this.queueItem = CustomPathManager._instance.queueItems[Calculating]; } catch (Exception e) { Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.PathFindThread Error for unit {Calculating}, flags={PathUnits.m_buffer[Calculating].m_pathFindFlags} (1): {e.ToString()}"); } finally { Monitor.Exit(QueueLock); } // calculate path unit try { PathFindImplementation(Calculating, ref PathUnits.m_buffer[Calculating]); } catch (Exception ex) { Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.PathFindThread Error for unit {Calculating}, flags={PathUnits.m_buffer[Calculating].m_pathFindFlags} (2): {ex.ToString()}"); //UIView.ForwardException(ex); #if DEBUG ++m_failedPathFinds; #if DEBUGNEWPF bool debug = this.m_debug; if (debug) Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Could not find path for unit {Calculating} -- exception occurred in PathFindImplementation"); #endif #endif //CustomPathManager._instance.ResetQueueItem(Calculating); PathUnits.m_buffer[Calculating].m_pathFindFlags |= PathUnit.FLAG_FAILED; } finally { } //tCurrentState = 10; #if DEBUGLOCKS lockIter = 0; #endif try { Monitor.Enter(QueueLock); PathUnits.m_buffer[Calculating].m_pathFindFlags = (byte)(PathUnits.m_buffer[Calculating].m_pathFindFlags & ~PathUnit.FLAG_CALCULATING); // NON-STOCK CODE START try { Monitor.Enter(_bufferLock); CustomPathManager._instance.queueItems[Calculating].queued = false; CustomPathManager._instance.ReleasePath(Calculating); } finally { Monitor.Exit(this._bufferLock); } // NON-STOCK CODE END Calculating = 0u; Monitor.Pulse(QueueLock); } catch (Exception e) { Log.Error($"(PF #{m_pathFindIndex}, T#{Thread.CurrentThread.ManagedThreadId}, Id #{pfId}) CustomPathFind.PathFindThread Error for unit {Calculating}, flags={PathUnits.m_buffer[Calculating].m_pathFindFlags} (3): {e.ToString()}"); } finally { Monitor.Exit(QueueLock); } } } /// /// Determines if a given lane may be used by the vehicle whose path is currently being calculated. /// /// /// /// /// /// /// protected virtual bool CanUseLane(bool debug, ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo) { if (!Options.vehicleRestrictionsEnabled) return true; if (queueItem.vehicleType == ExtVehicleType.None || queueItem.vehicleType == ExtVehicleType.Tram) return true; /*if (laneInfo == null) laneInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex];*/ if ((laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train)) == VehicleInfo.VehicleType.None) return true; ExtVehicleType allowedTypes = vehicleRestrictionsManager.GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); return ((allowedTypes & queueItem.vehicleType) != ExtVehicleType.None); } /// /// Determines the speed limit for the given lane. /// /// /// /// /// /// protected virtual float GetLaneSpeedLimit(ushort segmentId, byte laneIndex, uint laneId, NetInfo.Lane lane) { return Options.customSpeedLimitsEnabled ? speedLimitManager.GetLockFreeGameSpeedLimit(segmentId, laneIndex, laneId, lane) : lane.m_speedLimit; } } } ================================================ FILE: TLM/TLM/Custom/PathFinding/CustomPathFind2.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using ColossalFramework.UI; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using TrafficManager.Manager; using TrafficManager.Manager.Impl; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Traffic.Data; using TrafficManager.TrafficLight; using UnityEngine; using static TrafficManager.Custom.PathFinding.CustomPathManager; namespace TrafficManager.Custom.PathFinding { public class CustomPathFind2 : PathFind { private const float BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR = 0.003921569f; private const float TICKET_COST_CONVERSION_FACTOR = BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; #if ROUTING private readonly RoutingManager m_routingManager = RoutingManager.Instance; #endif #if JUNCTIONRESTRICTIONS private readonly JunctionRestrictionsManager m_junctionManager = JunctionRestrictionsManager.Instance; #endif #if VEHICLERESTRICTIONS private readonly VehicleRestrictionsManager m_vehicleRestrictionsManager = VehicleRestrictionsManager.Instance; #endif #if SPEEDLIMITS private readonly SpeedLimitManager m_speedLimitManager = SpeedLimitManager.Instance; #endif #if CUSTOMTRAFFICLIGHTS private readonly CustomSegmentLightsManager m_customTrafficLightsManager = CustomSegmentLightsManager.Instance; #endif #if ADVANCEDAI && ROUTING private readonly TrafficMeasurementManager m_trafficMeasurementManager = TrafficMeasurementManager.Instance; #endif private GlobalConfig m_conf = null; private struct BufferItem { public PathUnit.Position m_position; public float m_comparisonValue; public float m_methodDistance; public float m_duration; public uint m_laneID; public NetInfo.Direction m_direction; public NetInfo.LaneType m_lanesUsed; #if PARKINGAI public VehicleInfo.VehicleType m_vehiclesUsed; #endif #if ADVANCEDAI && ROUTING public float m_trafficRand; #endif public override string ToString() { return $"[BufferItem\n" + "\t" + $"m_position=(s#({m_position.m_segment}), l#({m_position.m_lane}), o#({m_position.m_offset}))\n" + "\t" + $"m_laneID={m_laneID}\n" + "\t" + $"m_comparisonValue={m_comparisonValue}\n" + "\t" + $"m_methodDistance={m_methodDistance}\n" + "\t" + $"m_duration={m_duration}\n" + "\t" + $"m_direction={m_direction}\n" + "\t" + $"m_lanesUsed={m_lanesUsed}\n" + #if PARKINGAI "\t" + $"m_vehiclesUsed={m_vehiclesUsed}\n" + #endif #if ADVANCEDAI && ROUTING "\t" + $"m_trafficRand={m_trafficRand}\n" + #endif "BufferItem]"; } } private enum LaneChangingCostCalculationMode { None, ByLaneDistance, ByGivenDistance } // private stock fields FieldInfo pathUnitsField; FieldInfo queueFirstField; FieldInfo queueLastField; FieldInfo queueLockField; FieldInfo calculatingField; FieldInfo terminatedField; FieldInfo pathFindThreadField; private Array32 m_pathUnits { get { return pathUnitsField.GetValue(this) as Array32; } set { pathUnitsField.SetValue(this, value); } } private uint m_queueFirst { get { return (uint)queueFirstField.GetValue(this); } set { queueFirstField.SetValue(this, value); } } private uint m_queueLast { get { return (uint)queueLastField.GetValue(this); } set { queueLastField.SetValue(this, value); } } private uint m_calculating { get { return (uint)calculatingField.GetValue(this); } set { calculatingField.SetValue(this, value); } } private object m_queueLock { get { return queueLockField.GetValue(this); } set { queueLockField.SetValue(this, value); } } private Thread m_customPathFindThread { get { return (Thread)pathFindThreadField.GetValue(this); } set { pathFindThreadField.SetValue(this, value); } } private bool m_terminated { get { return (bool)terminatedField.GetValue(this); } set { terminatedField.SetValue(this, value); } } // stock fields public ThreadProfiler m_pathfindProfiler; private object m_bufferLock; private int m_bufferMinPos; private int m_bufferMaxPos; private uint[] m_laneLocation; private PathUnit.Position[] m_laneTarget; private BufferItem[] m_buffer; private int[] m_bufferMin; private int[] m_bufferMax; private float m_maxLength; private uint m_startLaneA; private uint m_startLaneB; private uint m_endLaneA; private uint m_endLaneB; private uint m_vehicleLane; private byte m_startOffsetA; private byte m_startOffsetB; private byte m_vehicleOffset; private NetSegment.Flags m_carBanMask; private bool m_ignoreBlocked; private bool m_stablePath; private bool m_randomParking; private bool m_transportVehicle; private bool m_ignoreCost; private NetSegment.Flags m_disableMask; private Randomizer m_pathRandomizer; private uint m_pathFindIndex; private NetInfo.LaneType m_laneTypes; private VehicleInfo.VehicleType m_vehicleTypes; // custom fields private PathUnitQueueItem m_queueItem; private bool m_isHeavyVehicle; #if DEBUG public uint m_failedPathFinds = 0; public uint m_succeededPathFinds = 0; private bool m_debug = false; private IDictionary> m_debugPositions = null; #endif #if PARKINGAI || JUNCTIONRESTRICTIONS private ushort m_startSegmentA; private ushort m_startSegmentB; #endif #if ROUTING private bool m_isRoadVehicle; private bool m_isLaneArrowObeyingEntity; //private bool m_isLaneConnectionObeyingEntity; #endif private void Awake() { Type stockPathFindType = typeof(PathFind); const BindingFlags fieldFlags = BindingFlags.NonPublic | BindingFlags.Instance; pathUnitsField = stockPathFindType.GetField("m_pathUnits", fieldFlags); queueFirstField = stockPathFindType.GetField("m_queueFirst", fieldFlags); queueLastField = stockPathFindType.GetField("m_queueLast", fieldFlags); queueLockField = stockPathFindType.GetField("m_queueLock", fieldFlags); terminatedField = stockPathFindType.GetField("m_terminated", fieldFlags); calculatingField = stockPathFindType.GetField("m_calculating", fieldFlags); pathFindThreadField = stockPathFindType.GetField("m_pathFindThread", fieldFlags); m_pathfindProfiler = new ThreadProfiler(); m_laneLocation = new uint[262144]; m_laneTarget = new PathUnit.Position[262144]; m_buffer = new BufferItem[65536]; m_bufferMin = new int[1024]; m_bufferMax = new int[1024]; m_queueLock = new object(); m_bufferLock = Singleton.instance.m_bufferLock; m_pathUnits = Singleton.instance.m_pathUnits; m_customPathFindThread = new Thread(PathFindThread); m_customPathFindThread.Name = "Pathfind"; m_customPathFindThread.Priority = SimulationManager.SIMULATION_PRIORITY; m_customPathFindThread.Start(); if (!m_customPathFindThread.IsAlive) { CODebugBase.Error(LogChannel.Core, "Path find thread failed to start!"); } } private void OnDestroy() { try { Monitor.Enter(m_queueLock); m_terminated = true; Monitor.PulseAll(m_queueLock); } finally { Monitor.Exit(m_queueLock); } } public new bool CalculatePath(uint unit, bool skipQueue) { return ExtCalculatePath(unit, skipQueue); } public bool ExtCalculatePath(uint unit, bool skipQueue) { if (CustomPathManager._instance.AddPathReference(unit)) { try { Monitor.Enter(m_queueLock); if (skipQueue) { if (m_queueLast == 0) { m_queueLast = unit; } else { // NON-STOCK CODE START CustomPathManager._instance.queueItems[unit].nextPathUnitId = m_queueFirst; // NON-STOCK CODE END // PathUnits.m_buffer[unit].m_nextPathUnit = QueueFirst; // stock code commented } m_queueFirst = unit; } else { if (m_queueLast == 0) { m_queueFirst = unit; } else { // NON-STOCK CODE START CustomPathManager._instance.queueItems[m_queueLast].nextPathUnitId = unit; // NON-STOCK CODE END // PathUnits.m_buffer[QueueLast].m_nextPathUnit = unit; // stock code commented } m_queueLast = unit; } m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_CREATED; m_queuedPathFindCount++; Monitor.Pulse(m_queueLock); } finally { Monitor.Exit(m_queueLock); } return true; } return false; } private void PathFindImplementation(uint unit, ref PathUnit data) { m_conf = GlobalConfig.Instance; // NON-STOCK CODE NetManager netManager = Singleton.instance; m_laneTypes = (NetInfo.LaneType)m_pathUnits.m_buffer[unit].m_laneTypes; m_vehicleTypes = (VehicleInfo.VehicleType)m_pathUnits.m_buffer[unit].m_vehicleTypes; m_maxLength = m_pathUnits.m_buffer[unit].m_length; m_pathFindIndex = (m_pathFindIndex + 1 & 0x7FFF); m_pathRandomizer = new Randomizer(unit); m_carBanMask = NetSegment.Flags.CarBan; m_isHeavyVehicle = (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IS_HEAVY) != 0; // NON-STOCK CODE (refactored) if (m_isHeavyVehicle) { m_carBanMask |= NetSegment.Flags.HeavyBan; } if ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_READY) != 0) { m_carBanMask |= NetSegment.Flags.WaitingPath; } m_ignoreBlocked = ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_BLOCKED) != 0); m_stablePath = ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_STABLE_PATH) != 0); m_randomParking = ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_RANDOM_PARKING) != 0); m_transportVehicle = ((m_laneTypes & NetInfo.LaneType.TransportVehicle) != NetInfo.LaneType.None); m_ignoreCost = (m_stablePath || (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_COST) != 0); m_disableMask = (NetSegment.Flags.Collapsed | NetSegment.Flags.PathFailed); if ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_FLOODED) == 0) { m_disableMask |= NetSegment.Flags.Flooded; } if ((m_laneTypes & NetInfo.LaneType.Vehicle) != NetInfo.LaneType.None) { m_laneTypes |= NetInfo.LaneType.TransportVehicle; } #if ROUTING m_isRoadVehicle = (m_queueItem.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None; m_isLaneArrowObeyingEntity = #if DEBUG ! Options.allRelaxed && // debug option: all vehicle may ignore lane arrows #endif (! Options.relaxedBusses || m_queueItem.vehicleType != ExtVehicleType.Bus) && // option: busses may ignore lane arrows (m_vehicleTypes & LaneArrowManager.VEHICLE_TYPES) != VehicleInfo.VehicleType.None && (m_queueItem.vehicleType & LaneArrowManager.EXT_VEHICLE_TYPES) != ExtVehicleType.None ; #endif #if DEBUG m_debug = m_conf.Debug.Switches[0] && (m_conf.Debug.ExtVehicleType == ExtVehicleType.None || m_queueItem.vehicleType == m_conf.Debug.ExtVehicleType) && (m_conf.Debug.StartSegmentId == 0 || data.m_position00.m_segment == m_conf.Debug.StartSegmentId || data.m_position02.m_segment == m_conf.Debug.StartSegmentId) && (m_conf.Debug.EndSegmentId == 0 || data.m_position01.m_segment == m_conf.Debug.EndSegmentId || data.m_position03.m_segment == m_conf.Debug.EndSegmentId) && (m_conf.Debug.VehicleId == 0 || m_queueItem.vehicleId == m_conf.Debug.VehicleId) ; if (m_debug) { m_debugPositions = new Dictionary>(); } #endif int posCount = m_pathUnits.m_buffer[unit].m_positionCount & 0xF; int vehiclePosIndicator = m_pathUnits.m_buffer[unit].m_positionCount >> 4; BufferItem bufferItemStartA = default(BufferItem); if (data.m_position00.m_segment != 0 && posCount >= 1) { #if PARKINGAI || JUNCTIONRESTRICTIONS m_startSegmentA = data.m_position00.m_segment; // NON-STOCK CODE #endif m_startLaneA = PathManager.GetLaneID(data.m_position00); m_startOffsetA = data.m_position00.m_offset; bufferItemStartA.m_laneID = m_startLaneA; bufferItemStartA.m_position = data.m_position00; GetLaneDirection(data.m_position00, out bufferItemStartA.m_direction, out bufferItemStartA.m_lanesUsed #if PARKINGAI , out bufferItemStartA.m_vehiclesUsed #endif ); bufferItemStartA.m_comparisonValue = 0f; bufferItemStartA.m_duration = 0f; } else { #if PARKINGAI || JUNCTIONRESTRICTIONS m_startSegmentA = 0; // NON-STOCK CODE #endif m_startLaneA = 0u; m_startOffsetA = 0; } BufferItem bufferItemStartB = default(BufferItem); if (data.m_position02.m_segment != 0 && posCount >= 3) { #if PARKINGAI || JUNCTIONRESTRICTIONS m_startSegmentB = data.m_position02.m_segment; // NON-STOCK CODE #endif m_startLaneB = PathManager.GetLaneID(data.m_position02); m_startOffsetB = data.m_position02.m_offset; bufferItemStartB.m_laneID = m_startLaneB; bufferItemStartB.m_position = data.m_position02; GetLaneDirection(data.m_position02, out bufferItemStartB.m_direction, out bufferItemStartB.m_lanesUsed #if PARKINGAI , out bufferItemStartB.m_vehiclesUsed #endif ); bufferItemStartB.m_comparisonValue = 0f; bufferItemStartB.m_duration = 0f; } else { #if PARKINGAI || JUNCTIONRESTRICTIONS m_startSegmentB = 0; // NON-STOCK CODE #endif m_startLaneB = 0u; m_startOffsetB = 0; } BufferItem bufferItemEndA = default(BufferItem); if (data.m_position01.m_segment != 0 && posCount >= 2) { m_endLaneA = PathManager.GetLaneID(data.m_position01); bufferItemEndA.m_laneID = m_endLaneA; bufferItemEndA.m_position = data.m_position01; GetLaneDirection(data.m_position01, out bufferItemEndA.m_direction, out bufferItemEndA.m_lanesUsed #if PARKINGAI , out bufferItemEndA.m_vehiclesUsed #endif ); bufferItemEndA.m_methodDistance = 0.01f; bufferItemEndA.m_comparisonValue = 0f; bufferItemEndA.m_duration = 0f; } else { m_endLaneA = 0u; } BufferItem bufferItemEndB = default(BufferItem); if (data.m_position03.m_segment != 0 && posCount >= 4) { m_endLaneB = PathManager.GetLaneID(data.m_position03); bufferItemEndB.m_laneID = m_endLaneB; bufferItemEndB.m_position = data.m_position03; GetLaneDirection(data.m_position03, out bufferItemEndB.m_direction, out bufferItemEndB.m_lanesUsed #if PARKINGAI , out bufferItemEndB.m_vehiclesUsed #endif ); bufferItemEndB.m_methodDistance = 0.01f; bufferItemEndB.m_comparisonValue = 0f; bufferItemEndB.m_duration = 0f; } else { m_endLaneB = 0u; } if (data.m_position11.m_segment != 0 && vehiclePosIndicator >= 1) { m_vehicleLane = PathManager.GetLaneID(data.m_position11); m_vehicleOffset = data.m_position11.m_offset; } else { m_vehicleLane = 0u; m_vehicleOffset = 0; } #if DEBUG if (m_debug) { Debug(unit, $"PathFindImplementation: Preparing calculation:\n" + $"\tbufferItemStartA: segment={bufferItemStartA.m_position.m_segment} lane={bufferItemStartA.m_position.m_lane} off={bufferItemStartA.m_position.m_offset} laneId={bufferItemStartA.m_laneID}\n" + $"\tbufferItemStartB: segment={bufferItemStartB.m_position.m_segment} lane={bufferItemStartB.m_position.m_lane} off={bufferItemStartB.m_position.m_offset} laneId={bufferItemStartB.m_laneID}\n" + $"\tbufferItemEndA: segment={bufferItemEndA.m_position.m_segment} lane={bufferItemEndA.m_position.m_lane} off={bufferItemEndA.m_position.m_offset} laneId={bufferItemEndA.m_laneID}\n" + $"\tbufferItemEndB: segment={bufferItemEndB.m_position.m_segment} lane={bufferItemEndB.m_position.m_lane} off={bufferItemEndB.m_position.m_offset} laneId={bufferItemEndB.m_laneID}\n" + $"\tvehicleItem: segment={data.m_position11.m_segment} lane={data.m_position11.m_lane} off={data.m_position11.m_offset} laneId={m_vehicleLane} vehiclePosIndicator={vehiclePosIndicator}\n" + $"Properties:\n" + "\t" + $"m_maxLength={m_maxLength}\n" + "\t" + $"m_startLaneA={m_startLaneA}\n" + "\t" + $"m_startLaneB={m_startLaneB}\n" + "\t" + $"m_endLaneA={m_endLaneA}\n" + "\t" + $"m_endLaneB={m_endLaneB}\n" + "\t" + $"m_startOffsetA={m_startOffsetA}\n" + "\t" + $"m_startOffsetB={m_startOffsetB}\n" + "\t" + $"m_vehicleLane={m_vehicleLane}\n" + "\t" + $"m_vehicleOffset={m_vehicleOffset}\n" + "\t" + $"m_carBanMask={m_carBanMask}\n" + "\t" + $"m_disableMask={m_disableMask}\n" + "\t" + $"m_ignoreBlocked={m_ignoreBlocked}\n" + "\t" + $"m_stablePath={m_stablePath}\n" + "\t" + $"m_randomParking={m_randomParking}\n" + "\t" + $"m_transportVehicle={m_transportVehicle}\n" + "\t" + $"m_ignoreCost={m_ignoreCost}\n" + "\t" + $"m_pathFindIndex={m_pathFindIndex}\n" + "\t" + $"m_laneTypes={m_laneTypes}\n" + "\t" + $"m_vehicleTypes={m_vehicleTypes}\n" + "\t" + $"m_queueItem={m_queueItem}\n" + "\t" + $"m_isHeavyVehicle={m_isHeavyVehicle}\n" + "\t" + $"m_failedPathFinds={m_failedPathFinds}\n" + "\t" + $"m_succeededPathFinds={m_succeededPathFinds}\n" + #if PARKINGAI || JUNCTIONRESTRICTIONS "\t" + $"m_startSegmentA={m_startSegmentA}\n" + "\t" + $"m_startSegmentB={m_startSegmentB}\n" + #endif #if ROUTING "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"m_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}" #endif ); } #endif BufferItem finalBufferItem = default(BufferItem); byte startOffset = 0; m_bufferMinPos = 0; m_bufferMaxPos = -1; if (m_pathFindIndex == 0) { uint num3 = 4294901760u; for (int i = 0; i < 262144; i++) { m_laneLocation[i] = num3; } } for (int j = 0; j < 1024; j++) { m_bufferMin[j] = 0; m_bufferMax[j] = -1; } if (bufferItemEndA.m_position.m_segment != 0) { m_bufferMax[0]++; m_buffer[++m_bufferMaxPos] = bufferItemEndA; } if (bufferItemEndB.m_position.m_segment != 0) { m_bufferMax[0]++; m_buffer[++m_bufferMaxPos] = bufferItemEndB; } bool canFindPath = false; while (m_bufferMinPos <= m_bufferMaxPos) { int bufMin = m_bufferMin[m_bufferMinPos]; int bufMax = m_bufferMax[m_bufferMinPos]; if (bufMin > bufMax) { m_bufferMinPos++; } else { m_bufferMin[m_bufferMinPos] = bufMin + 1; BufferItem candidateItem = m_buffer[(m_bufferMinPos << 6) + bufMin]; if (candidateItem.m_position.m_segment == bufferItemStartA.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartA.m_position.m_lane) { if ((candidateItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None && candidateItem.m_position.m_offset >= m_startOffsetA) { finalBufferItem = candidateItem; startOffset = m_startOffsetA; canFindPath = true; break; } if ((candidateItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None && candidateItem.m_position.m_offset <= m_startOffsetA) { finalBufferItem = candidateItem; startOffset = m_startOffsetA; canFindPath = true; break; } } if (candidateItem.m_position.m_segment == bufferItemStartB.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartB.m_position.m_lane) { if ((candidateItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None && candidateItem.m_position.m_offset >= m_startOffsetB) { finalBufferItem = candidateItem; startOffset = m_startOffsetB; canFindPath = true; break; } if ((candidateItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None && candidateItem.m_position.m_offset <= m_startOffsetB) { finalBufferItem = candidateItem; startOffset = m_startOffsetB; canFindPath = true; break; } } ushort startNodeId = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_startNode; ushort endNodeId = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_endNode; if ((candidateItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None) { ProcessItemMain( #if DEBUG unit, #endif candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], ref netManager.m_lanes.m_buffer[candidateItem.m_laneID], startNodeId, ref netManager.m_nodes.m_buffer[startNodeId], 0, false); } if ((candidateItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None) { ProcessItemMain( #if DEBUG unit, #endif candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], ref netManager.m_lanes.m_buffer[candidateItem.m_laneID], endNodeId, ref netManager.m_nodes.m_buffer[endNodeId], 255, false); } int numIter = 0; ushort specialNodeId = netManager.m_lanes.m_buffer[candidateItem.m_laneID].m_nodes; if (specialNodeId != 0) { bool nodesDisabled = ((netManager.m_nodes.m_buffer[startNodeId].m_flags | netManager.m_nodes.m_buffer[endNodeId].m_flags) & NetNode.Flags.Disabled) != NetNode.Flags.None; while (specialNodeId != 0) { NetInfo.Direction direction = NetInfo.Direction.None; byte laneOffset = netManager.m_nodes.m_buffer[specialNodeId].m_laneOffset; if (laneOffset <= candidateItem.m_position.m_offset) { direction |= NetInfo.Direction.Forward; } if (laneOffset >= candidateItem.m_position.m_offset) { direction |= NetInfo.Direction.Backward; } if ((candidateItem.m_direction & direction) != NetInfo.Direction.None && (!nodesDisabled || (netManager.m_nodes.m_buffer[specialNodeId].m_flags & NetNode.Flags.Disabled) != NetNode.Flags.None)) { #if DEBUG if (m_debug && (m_conf.Debug.NodeId <= 0 || specialNodeId == m_conf.Debug.NodeId)) { Debug(unit, $"PathFindImplementation: Handling special node for path unit {unit}, type {m_queueItem.vehicleType}:\n" + $"\tcandidateItem.m_position.m_segment={candidateItem.m_position.m_segment}\n" + $"\tcandidateItem.m_position.m_lane={candidateItem.m_position.m_lane}\n" + $"\tcandidateItem.m_laneID={candidateItem.m_laneID}\n" + $"\tspecialNodeId={specialNodeId}\n" + $"\tstartNodeId={startNodeId}\n" + $"\tendNodeId={endNodeId}\n" ); } #endif ProcessItemMain( #if DEBUG unit, #endif candidateItem, ref netManager.m_segments.m_buffer[candidateItem.m_position.m_segment], ref netManager.m_lanes.m_buffer[candidateItem.m_laneID], specialNodeId, ref netManager.m_nodes.m_buffer[specialNodeId], laneOffset, true); } specialNodeId = netManager.m_nodes.m_buffer[specialNodeId].m_nextLaneNode; if (++numIter == 32768) { break; } } } } } if (!canFindPath) { m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; // NON-STOCK CODE START #if DEBUG ++m_failedPathFinds; if (m_debug) { Debug(unit, $"PathFindImplementation: Path-find failed: Could not find path"); string reachableBuf = ""; string unreachableBuf = ""; foreach (KeyValuePair> e in m_debugPositions) { string buf = $"{e.Key} -> {e.Value.CollectionToString()}\n"; if (e.Value.Count <= 0) { unreachableBuf += buf; } else { reachableBuf += buf; } } Debug(unit, $"PathFindImplementation: Reachability graph:\n== REACHABLE ==\n" + reachableBuf + "\n== UNREACHABLE ==\n" + unreachableBuf); } #endif // NON-STOCK CODE END } else { float duration = (m_laneTypes != NetInfo.LaneType.Pedestrian && (m_laneTypes & NetInfo.LaneType.Pedestrian) != NetInfo.LaneType.None) ? finalBufferItem.m_duration : finalBufferItem.m_methodDistance; m_pathUnits.m_buffer[unit].m_length = duration; m_pathUnits.m_buffer[unit].m_speed = (byte)Mathf.Clamp(finalBufferItem.m_methodDistance * 100f / Mathf.Max(0.01f, finalBufferItem.m_duration), 0f, 255f); #if PARKINGAI m_pathUnits.m_buffer[unit].m_laneTypes = (byte)finalBufferItem.m_lanesUsed; m_pathUnits.m_buffer[unit].m_vehicleTypes = (ushort)finalBufferItem.m_vehiclesUsed; #endif uint currentPathUnitId = unit; int currentItemPositionCount = 0; int sumOfPositionCounts = 0; PathUnit.Position currentPosition = finalBufferItem.m_position; if ((currentPosition.m_segment != bufferItemEndA.m_position.m_segment || currentPosition.m_lane != bufferItemEndA.m_position.m_lane || currentPosition.m_offset != bufferItemEndA.m_position.m_offset) && (currentPosition.m_segment != bufferItemEndB.m_position.m_segment || currentPosition.m_lane != bufferItemEndB.m_position.m_lane || currentPosition.m_offset != bufferItemEndB.m_position.m_offset)) { if (startOffset != currentPosition.m_offset) { PathUnit.Position position2 = currentPosition; position2.m_offset = startOffset; m_pathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, position2); } m_pathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); currentPosition = m_laneTarget[finalBufferItem.m_laneID]; } for (int k = 0; k < 262144; k++) { m_pathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); if ((currentPosition.m_segment == bufferItemEndA.m_position.m_segment && currentPosition.m_lane == bufferItemEndA.m_position.m_lane && currentPosition.m_offset == bufferItemEndA.m_position.m_offset) || (currentPosition.m_segment == bufferItemEndB.m_position.m_segment && currentPosition.m_lane == bufferItemEndB.m_position.m_lane && currentPosition.m_offset == bufferItemEndB.m_position.m_offset)) { m_pathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; sumOfPositionCounts += currentItemPositionCount; if (sumOfPositionCounts != 0) { currentPathUnitId = m_pathUnits.m_buffer[unit].m_nextPathUnit; currentItemPositionCount = m_pathUnits.m_buffer[unit].m_positionCount; int numIter = 0; while (currentPathUnitId != 0) { m_pathUnits.m_buffer[currentPathUnitId].m_length = duration * (float)(sumOfPositionCounts - currentItemPositionCount) / (float)sumOfPositionCounts; currentItemPositionCount += m_pathUnits.m_buffer[currentPathUnitId].m_positionCount; currentPathUnitId = m_pathUnits.m_buffer[currentPathUnitId].m_nextPathUnit; if (++numIter >= 262144) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } } } m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_READY; // NON-STOCK CODE START #if DEBUG ++m_succeededPathFinds; if (m_debug) { Debug(unit, $"PathFindImplementation: Path-find succeeded"); } #endif // NON-STOCK CODE END return; } if (currentItemPositionCount == 12) { uint createdPathUnitId; try { Monitor.Enter(m_bufferLock); if (!m_pathUnits.CreateItem(out createdPathUnitId, ref m_pathRandomizer)) { m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; // NON-STOCK CODE START #if DEBUG ++m_failedPathFinds; if (m_debug) { Debug(unit, $"Path-finding failed: Could not create path unit"); } #endif // NON-STOCK CODE END return; } m_pathUnits.m_buffer[createdPathUnitId] = m_pathUnits.m_buffer[currentPathUnitId]; m_pathUnits.m_buffer[createdPathUnitId].m_referenceCount = 1; m_pathUnits.m_buffer[createdPathUnitId].m_pathFindFlags = PathUnit.FLAG_READY; m_pathUnits.m_buffer[currentPathUnitId].m_nextPathUnit = createdPathUnitId; m_pathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; #if PARKINGAI m_pathUnits.m_buffer[currentPathUnitId].m_laneTypes = (byte)finalBufferItem.m_lanesUsed; // (this is not accurate!) m_pathUnits.m_buffer[currentPathUnitId].m_vehicleTypes = (ushort)finalBufferItem.m_vehiclesUsed; // (this is not accurate!) #endif sumOfPositionCounts += currentItemPositionCount; Singleton.instance.m_pathUnitCount = (int)(m_pathUnits.ItemCount() - 1); } finally { Monitor.Exit(m_bufferLock); } currentPathUnitId = createdPathUnitId; currentItemPositionCount = 0; } uint laneID = PathManager.GetLaneID(currentPosition); currentPosition = m_laneTarget[laneID]; } m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; // NON-STOCK CODE START #if DEBUG ++m_failedPathFinds; if (m_debug) { Debug(unit, $"Path-finding failed: Internal loop break error"); } #endif // NON-STOCK CODE END } } #if DEBUG private void Debug(uint unit, string message) { Log._Debug( $"PF T#({Thread.CurrentThread.ManagedThreadId}) IDX#({m_pathFindIndex}):\n" + $"UNIT({unit})\n" + message ); } private void Debug(uint unit, BufferItem item, string message) { Log._Debug( $"PF T#({Thread.CurrentThread.ManagedThreadId}) IDX#({m_pathFindIndex}):\n" + $"UNIT({unit}): s#({item.m_position.m_segment}), l#({item.m_position.m_lane})\n" + $"ITEM({item})\n" + message ); } private void Debug(uint unit, BufferItem item, ushort nextSegmentId, string message) { Log._Debug( $"PF T#({Thread.CurrentThread.ManagedThreadId}) IDX#({m_pathFindIndex}):\n" + $"UNIT({unit}): s#({item.m_position.m_segment}), l#({item.m_position.m_lane}) -> s#({nextSegmentId})\n" + $"ITEM({item})\n" + message ); } private void Debug(uint unit, BufferItem item, ushort nextSegmentId, int nextLaneIndex, uint nextLaneId, string message) { Log._Debug( $"PF T#({Thread.CurrentThread.ManagedThreadId}) IDX#({m_pathFindIndex}):\n" + $"UNIT({unit}): s#({item.m_position.m_segment}), l#({item.m_position.m_lane}) -> s#({nextSegmentId}), l#({nextLaneIndex}), lid#({nextLaneId})\n" + $"ITEM({item})\n" + message ); } #endif // 1 private void ProcessItemMain( #if DEBUG uint unitId, #endif BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, ushort nextNodeId, ref NetNode nextNode, byte connectOffset, bool isMiddle) { #if DEBUG bool debug = this.m_debug && (m_conf.Debug.NodeId <= 0 || nextNodeId == m_conf.Debug.NodeId); if (debug) { if (!m_debugPositions.ContainsKey(item.m_position.m_segment)) { m_debugPositions[item.m_position.m_segment] = new List(); } } if (debug) { Debug(unitId, item, $"ProcessItemMain called.\n" + "\t" + $"nextNodeId={nextNodeId}\n" + "\t" + $"connectOffset={connectOffset}\n" + "\t" + $"isMiddle={isMiddle}" ); } #endif NetManager netManager = Singleton.instance; ushort prevSegmentId = item.m_position.m_segment; byte prevLaneIndex = item.m_position.m_lane; bool prevIsPedestrianLane = false; bool prevIsBicycleLane = false; bool prevIsCenterPlatform = false; bool prevIsElevated = false; #if ADVANCEDAI && ROUTING // NON-STOCK CODE START bool prevIsCarLane = false; // NON-STOCK CODE END #endif int prevRelSimilarLaneIndex = 0; // NON-STOCK CODE START float prevMaxSpeed = 1f; float prevLaneSpeed = 1f; // NON-STOCK CODE END NetInfo prevSegmentInfo = prevSegment.Info; if (prevLaneIndex < prevSegmentInfo.m_lanes.Length) { NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; prevIsPedestrianLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian); prevIsBicycleLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Vehicle && (prevLaneInfo.m_vehicleType & m_vehicleTypes) == VehicleInfo.VehicleType.Bicycle); prevIsCenterPlatform = prevLaneInfo.m_centerPlatform; prevIsElevated = prevLaneInfo.m_elevated; #if (ADVANCEDAI || PARKINGAI) && ROUTING // NON-STOCK CODE START prevIsCarLane = (prevLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (prevLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None ; // NON-STOCK CODE END #endif // NON-STOCK CODE START #if SPEEDLIMITS prevMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(prevSegmentId, prevLaneIndex, item.m_laneID, prevLaneInfo); #else prevMaxSpeed = prevLaneInfo.m_speedLimit; #endif prevLaneSpeed = CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // NON-STOCK CODE END prevRelSimilarLaneIndex = (((prevLaneInfo.m_finalDirection & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? (prevLaneInfo.m_similarLaneCount - prevLaneInfo.m_similarLaneIndex - 1) : prevLaneInfo.m_similarLaneIndex); } if (isMiddle) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: middle: Exploring middle node\n" + "\t" + $"nextNodeId={nextNodeId}" ); } #endif for (int i = 0; i < 8; i++) { ushort nextSegmentId = nextNode.GetSegment(i); if (nextSegmentId != 0) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: middle: Exploring next segment behind middle node\n" + "\t" + $"nextSegmentId={nextSegmentId}"); } #endif ProcessItemCosts( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, true, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, !prevIsPedestrianLane, prevIsPedestrianLane ); } } } else if (prevIsPedestrianLane) { // we are going to a pedestrian lane if (!prevIsElevated) { if (nextNode.Info.m_class.m_service != ItemClass.Service.Beautification) { bool canCrossStreet = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None; bool isOnCenterPlatform = prevIsCenterPlatform && (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Junction)) == NetNode.Flags.None; ushort nextLeftSegmentId = prevSegmentId; ushort nextRightSegmentId = prevSegmentId; int leftLaneIndex; int rightLaneIndex; uint leftLaneId; uint rightLaneId; prevSegment.GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, prevLaneIndex, isOnCenterPlatform, out leftLaneIndex, out rightLaneIndex, out leftLaneId, out rightLaneId); if (leftLaneId == 0 || rightLaneId == 0) { ushort leftSegmentId; ushort rightSegmentId; prevSegment.GetLeftAndRightSegments(nextNodeId, out leftSegmentId, out rightSegmentId); int numIter = 0; while (leftSegmentId != 0 && leftSegmentId != prevSegmentId && leftLaneId == 0) { int someLeftLaneIndex; int someRightLaneIndex; uint someLeftLaneId; uint someRightLaneId; netManager.m_segments.m_buffer[leftSegmentId].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); if (someRightLaneId != 0) { nextLeftSegmentId = leftSegmentId; leftLaneIndex = someRightLaneIndex; leftLaneId = someRightLaneId; break; // NON-STOCK CODE } else { #if JUNCTIONRESTRICTIONS // next segment does not have pedestrian lanes but cims need to cross it to reach the next segment if (!m_junctionManager.IsPedestrianCrossingAllowed(leftSegmentId, netManager.m_segments.m_buffer[leftSegmentId].m_startNode == nextNodeId)) { break; } #endif leftSegmentId = netManager.m_segments.m_buffer[leftSegmentId].GetLeftSegment(nextNodeId); } if (++numIter == 8) { break; } } numIter = 0; while (rightSegmentId != 0 && rightSegmentId != prevSegmentId && rightLaneId == 0) { int someLeftLaneIndex; int someRightLaneIndex; uint someLeftLaneId; uint someRightLaneId; netManager.m_segments.m_buffer[rightSegmentId].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); if (someLeftLaneId != 0) { nextRightSegmentId = rightSegmentId; rightLaneIndex = someLeftLaneIndex; rightLaneId = someLeftLaneId; break; // NON-STOCK CODE } else { #if JUNCTIONRESTRICTIONS // next segment does not have pedestrian lanes but cims need to cross it to reach the next segment if (!m_junctionManager.IsPedestrianCrossingAllowed(rightSegmentId, netManager.m_segments.m_buffer[rightSegmentId].m_startNode == nextNodeId)) { break; } #endif rightSegmentId = netManager.m_segments.m_buffer[rightSegmentId].GetRightSegment(nextNodeId); } if (++numIter == 8) { break; } } } if (leftLaneId != 0 && (nextLeftSegmentId != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: ped -> ped: Exploring left pedestrian lane\n" + "\t" + $"leftLaneId={leftLaneId}\n" + "\t" + $"nextLeftSegmentId={nextLeftSegmentId}\n" + "\t" + $"canCrossStreet={canCrossStreet}\n" + "\t" + $"isOnCenterPlatform={isOnCenterPlatform}" ); } #endif ProcessItemPedBicycle( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextLeftSegmentId, ref netManager.m_segments.m_buffer[nextLeftSegmentId], nextNodeId, ref nextNode, leftLaneIndex, leftLaneId, ref netManager.m_lanes.m_buffer[leftLaneId], connectOffset, connectOffset); } if (rightLaneId != 0 && rightLaneId != leftLaneId && (nextRightSegmentId != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: ped -> ped: Exploring right pedestrian lane\n" + "\t" + $"leftLaneId={leftLaneId}\n" + "\t" + $"rightLaneId={rightLaneId}\n" + "\t" + $"nextRightSegmentId={nextRightSegmentId}\n" + "\t" + $"canCrossStreet={canCrossStreet}\n" + "\t" + $"isOnCenterPlatform={isOnCenterPlatform}" ); } #endif ProcessItemPedBicycle( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextRightSegmentId, ref netManager.m_segments.m_buffer[nextRightSegmentId], nextNodeId, ref nextNode, rightLaneIndex, rightLaneId, ref netManager.m_lanes.m_buffer[rightLaneId], connectOffset, connectOffset); } // switch from bicycle lane to pedestrian lane int nextLaneIndex; uint nextLaneId; if ((m_vehicleTypes & VehicleInfo.VehicleType.Bicycle) != VehicleInfo.VehicleType.None && prevSegment.GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Bicycle, out nextLaneIndex, out nextLaneId)) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: bicycle -> ped: Exploring bicycle switch\n" + "\t" + $"leftLaneId={leftLaneId}\n" + "\t" + $"rightLaneId={rightLaneId}\n" + "\t" + $"nextRightSegmentId={nextRightSegmentId}\n" + "\t" + $"canCrossStreet={canCrossStreet}\n" + "\t" + $"isOnCenterPlatform={isOnCenterPlatform}" ); } #endif ProcessItemPedBicycle( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, prevSegmentId, ref prevSegment, nextNodeId, ref nextNode, nextLaneIndex, nextLaneId, ref netManager.m_lanes.m_buffer[nextLaneId], connectOffset, connectOffset); } } else { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: beautification -> ped: Exploring pedestrian lane to beautficiation node\n" + "\t" + $"nextNodeId={nextNodeId}" ); } #endif // we are going from pedestrian lane to a beautification node for (int j = 0; j < 8; j++) { ushort nextSegmentId = nextNode.GetSegment(j); if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: beautification -> ped: Exploring next segment behind beautification node\n" + "\t" + $"nextSegmentId={nextSegmentId}" ); } #endif ProcessItemCosts( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, false, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, false, true); } } } // prepare switching from a vehicle to pedestrian lane NetInfo.LaneType nextLaneType = m_laneTypes & ~NetInfo.LaneType.Pedestrian; VehicleInfo.VehicleType nextVehicleType = m_vehicleTypes & ~VehicleInfo.VehicleType.Bicycle; if ((item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { nextLaneType &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> ped: Prepared parameters\n" + "\t" + $"m_queueItem.vehicleType={m_queueItem.vehicleType}\n" + "\t" + $"nextVehicleType={nextVehicleType}\n" + "\t" + $"nextLaneType={nextLaneType}" ); } #endif // NON-STOCK CODE START bool parkingAllowed = true; #if PARKINGAI // Parking AI: Determine if parking is allowed if (Options.prohibitPocketCars) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> ped: Parking AI: Determining if parking is allowed here\n" + "\t" + $"m_queueItem.vehicleType={m_queueItem.vehicleType}\n" + "\t" + $"nextVehicleType={nextVehicleType}\n" + "\t" + $"nextLaneType={nextLaneType}\n" + "\t" + $"item.m_lanesUsed={item.m_lanesUsed}\n" + "\t" + $"m_endLaneA={m_endLaneA}\n" + "\t" + $"m_endLaneB={m_endLaneB}" ); } #endif if (m_queueItem.vehicleType == ExtVehicleType.PassengerCar && (nextVehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None && ((nextLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None)) { if ((item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { /* if pocket cars are prohibited, a citizen may only park their car once per path */ parkingAllowed = false; } else if ((item.m_lanesUsed & NetInfo.LaneType.PublicTransport) == NetInfo.LaneType.None) { /* if the citizen is walking to their target (= no public transport used), the passenger car must be parked in the very last moment */ parkingAllowed = item.m_laneID == m_endLaneA || item.m_laneID == m_endLaneB; } } #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> ped: Parking AI: Parking allowed here? {parkingAllowed}"); } #endif } #endif // NON-STOCK CODE END int sameSegLaneIndex; uint sameSegLaneId; if (parkingAllowed && // NON-STOCK CODE nextLaneType != NetInfo.LaneType.None && nextVehicleType != VehicleInfo.VehicleType.None && prevSegment.GetClosestLane(prevLaneIndex, nextLaneType, nextVehicleType, out sameSegLaneIndex, out sameSegLaneId) ) { NetInfo.Lane sameSegLaneInfo = prevSegmentInfo.m_lanes[sameSegLaneIndex]; byte sameSegConnectOffset = (byte)(((prevSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None == ((sameSegLaneInfo.m_finalDirection & NetInfo.Direction.Backward) != NetInfo.Direction.None)) ? 1 : 254); BufferItem nextItem = item; if (m_randomParking) { nextItem.m_comparisonValue += (float)m_pathRandomizer.Int32(300u) / m_maxLength; } #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> ped: Exploring parking\n" + "\t" + $"nextLaneType={nextLaneType}\n" + "\t" + $"nextVehicleType={nextVehicleType}\n" + "\t" + $"nextLaneType={nextLaneType}\n" + "\t" + $"sameSegConnectOffset={sameSegConnectOffset}\n" + "\t" + $"m_randomParking={m_randomParking}" ); } #endif ProcessItemPedBicycle( #if DEBUG debug, unitId, #endif nextItem, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, prevSegmentId, ref prevSegment, nextNodeId, ref nextNode, sameSegLaneIndex, sameSegLaneId, ref netManager.m_lanes.m_buffer[sameSegLaneId], sameSegConnectOffset, 128); } } } else { // We are going to a non-pedestrian lane bool nextIsBeautificationNode = nextNode.Info.m_class.m_service == ItemClass.Service.Beautification; // NON-STOCK CODE (refactored) bool allowPedestrian = (m_laneTypes & NetInfo.LaneType.Pedestrian) != NetInfo.LaneType.None; // allow switching from pedestrian lane to a non-pedestrian lane? bool allowBicycle = false; // allow switching from a pedestrian lane to a bike lane? byte switchConnectOffset = 0; // lane switching offset if (allowPedestrian) { if (prevIsBicycleLane) { // we are going to a bicycle lane switchConnectOffset = connectOffset; allowBicycle = nextIsBeautificationNode; #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Switching to a bicycle may be allowed here\n" + "\t" + $"switchConnectOffset={switchConnectOffset}\n" + "\t" + $"allowBicycle={allowBicycle}" ); } #endif } else if (m_vehicleLane != 0) { // there is a parked vehicle position if (m_vehicleLane != item.m_laneID) { // we have not reached the parked vehicle yet allowPedestrian = false; #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Entering a parked vehicle is not allowed here"); } #endif } else { // pedestrian switches to parked vehicle switchConnectOffset = m_vehicleOffset; #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Entering a parked vehicle is allowed here\n" + "\t" + $"switchConnectOffset={switchConnectOffset}" ); } #endif } } else if (m_stablePath) { // enter a bus switchConnectOffset = 128; #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Entering a bus is allowed here\n" + "\t" + $"switchConnectOffset={switchConnectOffset}" ); } #endif } else { // pocket car spawning #if PARKINGAI if ( Options.prohibitPocketCars ) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Parking AI: Determining if spawning pocket cars is allowed\n" + "\t" + $"m_queueItem.pathType={m_queueItem.pathType}\n" + "\t" + $"prevIsCarLane={prevIsCarLane}\n" + "\t" + $"m_queueItem.vehicleType={m_queueItem.vehicleType}\n" + "\t" + $"m_startSegmentA={m_startSegmentA}\n" + "\t" + $"m_startSegmentB={m_startSegmentB}" ); } #endif if ( (m_queueItem.pathType == ExtCitizenInstance.ExtPathType.WalkingOnly && prevIsCarLane) || ( m_queueItem.pathType == ExtCitizenInstance.ExtPathType.DrivingOnly && m_queueItem.vehicleType == ExtVehicleType.PassengerCar && ((item.m_position.m_segment != m_startSegmentA && item.m_position.m_segment != m_startSegmentB) || !prevIsCarLane) ) ) { /* allow pocket cars only if an instant driving path is required and we are at the start segment */ /* disallow pocket cars on walking paths */ allowPedestrian = false; #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Parking AI: Spawning pocket cars is not allowed here"); } #endif } else { switchConnectOffset = (byte)m_pathRandomizer.UInt32(1u, 254u); #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Parking AI: Spawning pocket cars is allowed here\n" + "\t" + $"switchConnectOffset={switchConnectOffset}" ); } #endif } } else { #endif switchConnectOffset = (byte)m_pathRandomizer.UInt32(1u, 254u); #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Spawning pocket cars is allowed here\n" + "\t" + $"switchConnectOffset={switchConnectOffset}" ); } #endif #if PARKINGAI } #endif } } ushort nextSegmentId = 0; if ((m_vehicleTypes & (VehicleInfo.VehicleType.Ferry #if !ROUTING | VehicleInfo.VehicleType.Monorail #endif )) != VehicleInfo.VehicleType.None) { // ferry (/ monorail) #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring ferry routes"); } #endif bool isUturnAllowedHere = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None; for (int k = 0; k < 8; k++) { nextSegmentId = nextNode.GetSegment(k); if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring ferry route\n" + "\t" + $"nextSegmentId={nextSegmentId}" ); } #endif ProcessItemCosts( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, false, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, allowBicycle); } } if (isUturnAllowedHere #if !ROUTING && (m_vehicleTypes & VehicleInfo.VehicleType.Monorail) == VehicleInfo.VehicleType.None #endif ) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring ferry u-turn"); } #endif nextSegmentId = prevSegmentId; ProcessItemCosts( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, false, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, false); } } else { // road vehicles / trams / trains / metros (/ monorails) / etc. #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring vehicle routes"); } #endif #if ROUTING bool exploreUturn = false; #else bool exploreUturn = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; #endif #if ROUTING bool prevIsRouted = false; uint laneRoutingIndex = 0; bool nextIsStartNode = nextNodeId == prevSegment.m_startNode; if (nextIsStartNode || nextNodeId == prevSegment.m_endNode) { laneRoutingIndex = m_routingManager.GetLaneEndRoutingIndex(item.m_laneID, nextIsStartNode); prevIsRouted = m_routingManager.laneEndBackwardRoutings[laneRoutingIndex].routed; #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Is previous segment routed? {prevIsRouted}"); } #endif } if (allowBicycle || !prevIsRouted) { /* * pedestrian to bicycle lane switch or no routing information available: * if pedestrian lanes should be explored (allowBicycle == true): do this here * if previous segment has custom routing (prevIsRouted == true): do NOT explore vehicle lanes here, else: vanilla exploration of vehicle lanes */ #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: bicycle -> vehicle / stock vehicle routing\n" + "\t" + $"prevIsRouted={prevIsRouted}\n" + "\t" + $"allowBicycle={allowBicycle}" ); } #endif // NON-STOCK CODE END #endif nextSegmentId = prevSegment.GetRightSegment(nextNodeId); for (int l = 0; l < 8; l++) { if (nextSegmentId == 0) { break; } if (nextSegmentId == prevSegmentId) { break; } #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: bicycle -> vehicle / stock vehicle routing: exploring next segment\n" + "\t" + $"nextSegmentId={nextSegmentId}" ); } #endif if (ProcessItemCosts( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, ref nextNode, false, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, #if ROUTING !prevIsRouted // NON-STOCK CODE #else true #endif , allowBicycle) ) { exploreUturn = true; // allow exceptional u-turns #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: bicycle -> vehicle / stock vehicle routing: exceptional u-turn allowed\n" + "\t" + $"nextSegmentId={nextSegmentId}" ); } #endif } nextSegmentId = netManager.m_segments.m_buffer[nextSegmentId].GetRightSegment(nextNodeId); } #if ROUTING } // NON-STOCK CODE #endif #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing\n" + "\t" + $"Options.advancedAI={Options.advancedAI}\n" + "\t" + $"prevIsRouted={prevIsRouted}\n" + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"prevIsCarLane={prevIsCarLane}\n" + "\t" + $"m_stablePath={Options.advancedAI}" ); } #endif // NON-STOCK CODE START float segmentSelectionCost = 1f; float laneSelectionCost = 1f; float laneChangingCost = 1f; bool enableAdvancedAI = false; // NON-STOCK CODE END #if ADVANCEDAI && ROUTING /* * ======================================================================================================= * Calculate Advanced Vehicle AI cost factors * ======================================================================================================= */ if ( Options.advancedAI && prevIsRouted && m_isRoadVehicle && prevIsCarLane ) { enableAdvancedAI = true; if (!m_stablePath) { CalculateAdvancedAiCostFactors( #if DEBUG debug, unitId, #endif ref item, ref prevSegment, ref prevLane, nextNodeId, ref nextNode, ref segmentSelectionCost, ref laneSelectionCost, ref laneChangingCost ); #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing with activated Advanced Vehicle AI: Calculated cost factors\n" + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + "\t" + $"laneChangingCost={laneChangingCost}" ); } #endif } else { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing with activated Advanced Vehicle AI and stable path: Using default cost factors\n" + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + "\t" + $"laneChangingCost={laneChangingCost}" ); } #endif } } #endif #if ROUTING if (prevIsRouted) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing: Exploring custom routes"); } #endif exploreUturn = false; // custom routing processes regular u-turns if (ProcessItemRouted( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed #if ADVANCEDAI , enableAdvancedAI, laneChangingCost, #endif segmentSelectionCost, laneSelectionCost, nextNodeId, ref nextNode, false, m_routingManager.segmentRoutings[prevSegmentId], m_routingManager.laneEndBackwardRoutings[laneRoutingIndex], connectOffset, prevRelSimilarLaneIndex )) { exploreUturn = true; // allow exceptional u-turns } } else { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing: No custom routing present"); } #endif if (!exploreUturn) { // no exceptional u-turns allowed: allow regular u-turns exploreUturn = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing: Allowing regular u-turns:\n" + "\t" + $"exploreUturn={exploreUturn}\n" ); } #endif } } #endif if (exploreUturn && (m_vehicleTypes & VehicleInfo.VehicleType.Tram) == VehicleInfo.VehicleType.None) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring stock u-turn\n" + "\t" + $"exploreUturn={exploreUturn}\n" ); } #endif ProcessItemCosts( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, #if ADVANCEDAI && ROUTING false, 0f, #endif nextNodeId, ref nextNode, false, prevSegmentId, ref prevSegment, #if ROUTING segmentSelectionCost, laneSelectionCost, null, #endif ref prevRelSimilarLaneIndex, connectOffset, true, false ); } } if (allowPedestrian) { int nextLaneIndex; uint nextLaneId; if (prevSegment.GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Pedestrian, m_vehicleTypes, out nextLaneIndex, out nextLaneId)) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Exploring switch\n" + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + "\t" + $"nextLaneId={nextLaneId}" ); } #endif ProcessItemPedBicycle( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, prevSegmentId, ref prevSegment, nextNodeId, ref nextNode, nextLaneIndex, nextLaneId, ref netManager.m_lanes.m_buffer[nextLaneId], switchConnectOffset, switchConnectOffset); } } } if (nextNode.m_lane != 0) { bool targetDisabled = (nextNode.m_flags & (NetNode.Flags.Disabled | NetNode.Flags.DisableOnlyMiddle)) == NetNode.Flags.Disabled; ushort nextSegmentId = netManager.m_lanes.m_buffer[nextNode.m_lane].m_segment; if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemMain: transport -> *: Exploring special node\n" + "\t" + $"nextSegmentId={nextSegmentId}\n" + "\t" + $"nextNode.m_lane={nextNode.m_lane}\n" + "\t" + $"targetDisabled={targetDisabled}\n" + "\t" + $"nextNodeId={nextNodeId}" ); } #endif ProcessItemPublicTransport( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, nextNodeId, targetDisabled, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], nextNode.m_lane, nextNode.m_laneOffset, connectOffset); } } } // 2 private void ProcessItemPublicTransport( #if DEBUG bool debug, uint unitId, #endif BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, ushort nextNodeId, bool targetDisabled, ushort nextSegmentId, ref NetSegment nextSegment, uint nextLaneId, byte offset, byte connectOffset) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemPublicTransport called.\n" + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" + "\t" + $"prevLaneSpeed={prevLaneSpeed}\n" + "\t" + $"nextNodeId={nextNodeId}\n" + "\t" + $"targetDisabled={targetDisabled}\n" + "\t" + $"nextLaneId={nextLaneId}\n" + "\t" + $"offset={offset}\n" + "\t" + $"connectOffset={connectOffset}" ); } #endif if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemPublicTransport: Aborting: Disable mask\n" + "\t" + $"m_disableMask={m_disableMask}\n" + "\t" + $"nextSegment.m_flags={nextSegment.m_flags}\n"); } #endif return; } NetManager netManager = Singleton.instance; if (targetDisabled && ((netManager.m_nodes.m_buffer[nextSegment.m_startNode].m_flags | netManager.m_nodes.m_buffer[nextSegment.m_endNode].m_flags) & NetNode.Flags.Disabled) == NetNode.Flags.None) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemPublicTransport: Aborting: Target disabled"); } #endif return; } NetInfo nextSegmentInfo = nextSegment.Info; NetInfo prevSegmentInfo = prevSegment.Info; int nextNumLanes = nextSegmentInfo.m_lanes.Length; // float prevMaxSpeed = 1f; // stock code commented // float prevLaneSpeed = 1f; // stock code commented NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; // prevMaxSpeed = prevLaneInfo.m_speedLimit; // stock code commented // prevLaneSpeed = CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // stock code commented prevLaneType = prevLaneInfo.m_laneType; if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } } float prevLength = (prevLaneType != NetInfo.LaneType.PublicTransport) ? prevSegment.m_averageLength : prevLane.m_length; float offsetLength = (float)Mathf.Abs(connectOffset - item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevLength; float methodDistance = item.m_methodDistance + offsetLength; float comparisonValue = item.m_comparisonValue + offsetLength / (prevLaneSpeed * m_maxLength); float duration = item.m_duration + offsetLength / prevMaxSpeed; Vector3 b = prevLane.CalculatePosition((float)(int)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); if (!m_ignoreCost) { int ticketCost = prevLane.m_ticketCost; if (ticketCost != 0) { comparisonValue += (float)(ticketCost * m_pathRandomizer.Int32(2000u)) * TICKET_COST_CONVERSION_FACTOR; } } int nextLaneIndex = 0; uint curLaneId = nextSegment.m_lanes; while (true) { if (nextLaneIndex < nextNumLanes && curLaneId != 0) { if (nextLaneId != curLaneId) { curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; nextLaneIndex++; continue; } break; } #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemPublicTransport: Aborting: Next lane not found"); } #endif return; } #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Exploring next lane\n" + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + "\t" + $"nextLaneId={nextLaneId}" ); } #endif NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; if (nextLaneInfo.CheckType(m_laneTypes, m_vehicleTypes)) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Next lane compatible\n" + "\t" + $"nextLaneInfo.m_vehicleType={nextLaneInfo.m_vehicleType}\n" + "\t" + $"nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}" ); } #endif Vector3 a = netManager.m_lanes.m_buffer[nextLaneId].CalculatePosition((float)(int)offset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); float distance = Vector3.Distance(a, b); BufferItem nextItem = default(BufferItem); nextItem.m_position.m_segment = nextSegmentId; nextItem.m_position.m_lane = (byte)nextLaneIndex; nextItem.m_position.m_offset = offset; if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { nextItem.m_methodDistance = 0f; } else { nextItem.m_methodDistance = methodDistance + distance; } if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian && !(nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance) && !m_stablePath) { // NON-STOCK CODE (custom walking distance) #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Aborting: Max. walking distance exceeded\n" + "\t" + $"nextItem.m_methodDistance={nextItem.m_methodDistance}" ); } #endif return; } float nextMaxSpeed; #if SPEEDLIMITS // NON-STOCK CODE START nextMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(nextSegmentId, (byte)nextLaneIndex, nextLaneId, nextLaneInfo); // NON-STOCK CODE END #else nextMaxSpeed = nextLaneInfo.m_speedLimit; #endif nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); } else { nextItem.m_direction = nextLaneInfo.m_finalDirection; } if (nextLaneId == m_startLaneA) { if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetA) && ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetA)) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Aborting: Invalid offset/direction on start lane A\n" + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" + "\t" + $"m_startOffsetA={m_startOffsetA}" ); } #endif return; } float nextSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; } if (nextLaneId == m_startLaneB) { if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetB) && ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetB)) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Aborting: Invalid offset/direction on start lane B\n" + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" + "\t" + $"m_startOffsetB={m_startOffsetB}" ); } #endif return; } float nextSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetB) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; } nextItem.m_laneID = nextLaneId; nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); #if PARKINGAI nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); #endif #if ADVANCEDAI && ROUTING // NON-STOCK CODE START nextItem.m_trafficRand = item.m_trafficRand; // NON-STOCK CODE END #endif #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Adding next item\n" + "\t" + $"nextItem={nextItem}" ); } #endif AddBufferItem( #if DEBUG debug, #endif nextItem, item.m_position ); } } #if ADVANCEDAI && ROUTING // 3a (non-routed, no adv. AI) private bool ProcessItemCosts( #if DEBUG bool debug, uint unitId, #endif BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, ushort nextNodeId, ref NetNode nextNode, bool isMiddle, ushort nextSegmentId, ref NetSegment nextSegment, ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian) { return ProcessItemCosts( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, #if ADVANCEDAI && ROUTING false, 0f, #endif nextNodeId, ref nextNode, isMiddle, nextSegmentId, ref nextSegment, #if ROUTING 1f, 1f, null, #endif ref laneIndexFromInner, connectOffset, enableVehicle, enablePedestrian); } #endif // 3b private bool ProcessItemCosts( #if DEBUG bool debug, uint unitId, #endif BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, #if ADVANCEDAI && ROUTING bool enableAdvancedAI, float laneChangingCost, #endif ushort nextNodeId, ref NetNode nextNode, bool isMiddle, ushort nextSegmentId, ref NetSegment nextSegment, #if ROUTING float segmentSelectionCost, float laneSelectionCost, LaneTransitionData? transition, #endif ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian ) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemCosts called.\n" + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" + "\t" + $"prevLaneSpeed={prevLaneSpeed}\n" #if ADVANCEDAI && ROUTING + "\t" + $"enableAdvancedAI={enableAdvancedAI}\n" + "\t" + $"laneChangingCost={laneChangingCost}\n" #endif + "\t" + $"nextNodeId={nextNodeId}\n" + "\t" + $"isMiddle={isMiddle}\n" + "\t" + $"nextSegmentId={nextSegmentId}\n" #if ROUTING + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + "\t" + $"transition={transition}\n" #endif + "\t" + $"laneIndexFromInner={laneIndexFromInner}\n" + "\t" + $"connectOffset={connectOffset}\n" + "\t" + $"enableVehicle={enableVehicle}\n" + "\t" + $"enablePedestrian={enablePedestrian}" ); } #endif bool blocked = false; if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Aborting: Disable mask\n" + "\t" + $"m_disableMask={m_disableMask}\n" + "\t" + $"nextSegment.m_flags={nextSegment.m_flags}\n"); } #endif return blocked; } NetManager netManager = Singleton.instance; NetInfo nextSegmentInfo = nextSegment.Info; NetInfo prevSegmentInfo = prevSegment.Info; int nextNumLanes = nextSegmentInfo.m_lanes.Length; NetInfo.Direction nextDir = (nextNodeId != nextSegment.m_startNode) ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; NetInfo.Direction nextFinalDir = ((nextSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? nextDir : NetInfo.InvertDirection(nextDir); // float prevMaxSpeed = 1f; // stock code commented // float prevLaneSpeed = 1f; // stock code commented NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; VehicleInfo.VehicleType prevVehicleType = VehicleInfo.VehicleType.None; if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; prevLaneType = prevLaneInfo.m_laneType; prevVehicleType = prevLaneInfo.m_vehicleType; // prevMaxSpeed = prevLaneInfo.m_speedLimit; // stock code commented // prevLaneSpeed = CalculateLaneSpeed(prevMaxSpeed, connectOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // stock code commented } bool acuteTurningAngle = false; if (prevLaneType == NetInfo.LaneType.Vehicle && (prevVehicleType & VehicleInfo.VehicleType.Car) == VehicleInfo.VehicleType.None) { float turningAngle = 0.01f - Mathf.Min(nextSegmentInfo.m_maxTurnAngleCos, prevSegmentInfo.m_maxTurnAngleCos); if (turningAngle < 1f) { Vector3 vector = (nextNodeId != prevSegment.m_startNode) ? prevSegment.m_endDirection : prevSegment.m_startDirection; Vector3 vector2 = ((nextDir & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? nextSegment.m_startDirection : nextSegment.m_endDirection; float dirDotProd = vector.x * vector2.x + vector.z * vector2.z; if (dirDotProd >= turningAngle) { acuteTurningAngle = true; } } } float prevLength = (prevLaneType != NetInfo.LaneType.PublicTransport) ? prevSegment.m_averageLength : prevLane.m_length; float offsetLength = (float)Mathf.Abs(connectOffset - item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevLength; float methodDistance = item.m_methodDistance + offsetLength; float duration = item.m_duration + offsetLength / prevMaxSpeed; if (!m_stablePath) { #if ADVANCEDAI && ROUTING if (!enableAdvancedAI) { #endif offsetLength *= (float)(new Randomizer(m_pathFindIndex << 16 | item.m_position.m_segment).Int32(900, 1000 + prevSegment.m_trafficDensity * 10) + m_pathRandomizer.Int32(20u)) * 0.001f; #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied stock segment randomization cost factor\n" + "\t" + $"offsetLength={offsetLength}" ); } #endif #if ADVANCEDAI && ROUTING } #endif } if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (prevVehicleType & m_vehicleTypes) == VehicleInfo.VehicleType.Car && (prevSegment.m_flags & m_carBanMask) != NetSegment.Flags.None) { offsetLength *= 7.5f; #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied stock car ban cost factor\n" + "\t" + $"offsetLength={offsetLength}" ); } #endif } if (m_transportVehicle && prevLaneType == NetInfo.LaneType.TransportVehicle) { offsetLength *= 0.95f; #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied stock transport vehicle cost factor\n" + "\t" + $"offsetLength={offsetLength}" ); } #endif } #if ROUTING #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applying custom selection cost factors\n" + "\t" + $"offsetLength={offsetLength}\n" + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + "\t" + $"laneSelectionCost={laneSelectionCost}\n" ); } #endif offsetLength *= segmentSelectionCost; offsetLength *= laneSelectionCost; #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied custom selection cost factors\n" + "\t" + $"offsetLength={offsetLength}" ); } #endif #endif float baseLength = offsetLength / (prevLaneSpeed * m_maxLength); // NON-STOCK CODE float comparisonValue = item.m_comparisonValue; // NON-STOCK CODE #if ROUTING #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Calculated base length\n" + "\t" + $"baseLength={baseLength}" ); } #endif if ( #if ADVANCEDAI !enableAdvancedAI && #endif !m_stablePath) { comparisonValue += baseLength; } #endif int ticketCost = prevLane.m_ticketCost; if (!m_ignoreCost && ticketCost != 0) { comparisonValue += (float)(ticketCost * m_pathRandomizer.Int32(2000u)) * TICKET_COST_CONVERSION_FACTOR; } if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } Vector3 b = prevLane.CalculatePosition((float)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); int newLaneIndexFromInner = laneIndexFromInner; bool isTransition = (nextNode.m_flags & NetNode.Flags.Transition) != NetNode.Flags.None; NetInfo.LaneType allowedLaneTypes = m_laneTypes; VehicleInfo.VehicleType allowedVehicleTypes = m_vehicleTypes; if (!enableVehicle) { allowedVehicleTypes &= VehicleInfo.VehicleType.Bicycle; if (allowedVehicleTypes == VehicleInfo.VehicleType.None) { allowedLaneTypes &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } } if (!enablePedestrian) { allowedLaneTypes &= ~NetInfo.LaneType.Pedestrian; } // NON-STOCK CODE START bool applyTransportTransferPenalty = Options.realisticPublicTransport && !m_stablePath && (allowedLaneTypes & (NetInfo.LaneType.PublicTransport | NetInfo.LaneType.Pedestrian)) == (NetInfo.LaneType.PublicTransport | NetInfo.LaneType.Pedestrian) && m_conf.PathFinding.PublicTransportTransitionMinPenalty >= 0 && m_conf.PathFinding.PublicTransportTransitionMaxPenalty > m_conf.PathFinding.PublicTransportTransitionMinPenalty ; #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Shall apply transport transfer penalty?\n" + "\t" + $"applyTransportTransferPenalty={applyTransportTransferPenalty}\n" + "\t" + $"Options.realisticPublicTransport={Options.realisticPublicTransport}\n" + "\t" + $"allowedLaneTypes={allowedLaneTypes}\n" + "\t" + $"allowedVehicleTypes={allowedVehicleTypes}\n" + "\t" + $"m_conf.PathFinding.PublicTransportTransitionMinPenalty={m_conf.PathFinding.PublicTransportTransitionMinPenalty}\n" + "\t" + $"m_conf.PathFinding.PublicTransportTransitionMaxPenalty={m_conf.PathFinding.PublicTransportTransitionMaxPenalty}" ); } #endif int nextLaneIndex = 0; uint nextLaneId = nextSegment.m_lanes; int maxNextLaneIndex = nextNumLanes - 1; #if ADVANCEDAI && ROUTING byte laneDist = 0; #endif #if ROUTING if (transition != null) { LaneTransitionData trans = (LaneTransitionData)transition; if (trans.laneIndex >= 0 && trans.laneIndex <= maxNextLaneIndex) { nextLaneIndex = trans.laneIndex; nextLaneId = trans.laneId; maxNextLaneIndex = nextLaneIndex; } else { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Invalid transition detected. Skipping."); } #endif return blocked; } laneDist = trans.distance; #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Custom transition given\n" + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + "\t" + $"nextLaneId={nextLaneId}\n" + "\t" + $"maxNextLaneIndex={maxNextLaneIndex}\n" + "\t" + $"laneDist={laneDist}" ); } #endif } else { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: No custom transition given"); } #endif } #endif // NON-STOCK CODE END for (; nextLaneIndex <= maxNextLaneIndex && nextLaneId != 0; nextLaneIndex++) { NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; if ((nextLaneInfo.m_finalDirection & nextFinalDir) != NetInfo.Direction.None) { if (nextLaneInfo.CheckType(allowedLaneTypes, allowedVehicleTypes) && (nextSegmentId != item.m_position.m_segment || nextLaneIndex != item.m_position.m_lane)) { if (acuteTurningAngle && nextLaneInfo.m_laneType == NetInfo.LaneType.Vehicle && (nextLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) == VehicleInfo.VehicleType.None) { continue; } BufferItem nextItem = default(BufferItem); Vector3 a = ((nextDir & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? netManager.m_lanes.m_buffer[nextLaneId].m_bezier.a : netManager.m_lanes.m_buffer[nextLaneId].m_bezier.d; float transitionCost = Vector3.Distance(a, b); if (isTransition) { transitionCost *= 2f; } if (ticketCost != 0 && netManager.m_lanes.m_buffer[nextLaneId].m_ticketCost != 0) { transitionCost *= 10f; } float nextMaxSpeed; #if SPEEDLIMITS // NON-STOCK CODE START nextMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(nextSegmentId, (byte)nextLaneIndex, nextLaneId, nextLaneInfo); // NON-STOCK CODE END #else nextMaxSpeed = nextLaneInfo.m_speedLimit; #endif float transitionCostOverMeanMaxSpeed = transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); #if ADVANCEDAI && ROUTING if (!enableAdvancedAI) { #endif if (!this.m_stablePath && (netManager.m_lanes.m_buffer[nextLaneId].m_flags & (ushort)NetLane.Flags.Merge) != 0) { int firstTarget = netManager.m_lanes.m_buffer[nextLaneId].m_firstTarget; int lastTarget = netManager.m_lanes.m_buffer[nextLaneId].m_lastTarget; transitionCostOverMeanMaxSpeed *= (float)new Randomizer(this.m_pathFindIndex ^ nextLaneId).Int32(1000, (lastTarget - firstTarget + 2) * 1000) * 0.001f; } #if ADVANCEDAI && ROUTING } #endif nextItem.m_position.m_segment = nextSegmentId; nextItem.m_position.m_lane = (byte)nextLaneIndex; nextItem.m_position.m_offset = (byte)(((nextDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) ? 255 : 0); if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { nextItem.m_methodDistance = 0f; } else { nextItem.m_methodDistance = methodDistance + transitionCost; } if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian && !(nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance) && !m_stablePath) { // NON-STOCK CODE (custom walking distance) nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; continue; } // NON-STOCK CODE START if (applyTransportTransferPenalty) { if ( isMiddle && (nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None && (item.m_lanesUsed & NetInfo.LaneType.PublicTransport) != NetInfo.LaneType.None && nextLaneInfo.m_laneType == NetInfo.LaneType.PublicTransport ) { // apply penalty when switching between public transport lines float transportTransitionPenalty = (m_conf.PathFinding.PublicTransportTransitionMinPenalty + ((float)nextNode.m_maxWaitTime * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR) * (m_conf.PathFinding.PublicTransportTransitionMaxPenalty - m_conf.PathFinding.PublicTransportTransitionMinPenalty)) / (0.5f * this.m_maxLength); transitionCostOverMeanMaxSpeed += transportTransitionPenalty; #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Applied transport transfer penalty on PT change\n" + "\t" + $"transportTransitionPenalty={transportTransitionPenalty}\n" + "\t" + $"transitionCostOverMeanMaxSpeed={transitionCostOverMeanMaxSpeed}\n" + "\t" + $"isMiddle={isMiddle}\n" + "\t" + $"nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}\n" + "\t" + $"prevLaneType={prevLaneType}\n" + "\t" + $"item.m_lanesUsed={item.m_lanesUsed}\n" + "\t" + $"nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}" ); } #endif } else if ( (nextLaneId == m_startLaneA || nextLaneId == m_startLaneB) && (item.m_lanesUsed & (NetInfo.LaneType.Pedestrian | NetInfo.LaneType.PublicTransport)) == NetInfo.LaneType.Pedestrian ) { // account for public tranport transition costs on non-PT paths float transportTransitionPenalty = (2f * m_conf.PathFinding.PublicTransportTransitionMaxPenalty) / (0.5f * this.m_maxLength); transitionCostOverMeanMaxSpeed += transportTransitionPenalty; #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Applied transport transfer penalty on non-PT path\n" + "\t" + $"transportTransitionPenalty={transportTransitionPenalty}\n" + "\t" + $"transitionCostOverMeanMaxSpeed={transitionCostOverMeanMaxSpeed}" ); } #endif } } // NON-STOCK CODE END nextItem.m_comparisonValue = comparisonValue + transitionCostOverMeanMaxSpeed; nextItem.m_duration = duration + transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); nextItem.m_direction = nextDir; if (nextLaneId == m_startLaneA) { if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetA) && ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetA)) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Skipping: Invalid offset/direction on start lane A\n" + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" + "\t" + $"m_startOffsetA={m_startOffsetA}" ); } #endif nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; continue; } float nextLaneSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextLaneSpeed * m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextLaneSpeed; } if (nextLaneId == m_startLaneB) { if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetB) && ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetB)) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Skipping: Invalid offset/direction on start lane B\n" + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" + "\t" + $"m_startOffsetB={m_startOffsetB}" ); } #endif nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; continue; } float nextLaneSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetB) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextLaneSpeed * m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextLaneSpeed; } if ( !m_ignoreBlocked && (nextSegment.m_flags & NetSegment.Flags.Blocked) != NetSegment.Flags.None && (nextLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None ) { nextItem.m_comparisonValue += 0.1f; blocked = true; } nextItem.m_laneID = nextLaneId; nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); #if PARKINGAI nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); #endif #if ADVANCEDAI && ROUTING // NON-STOCK CODE START nextItem.m_trafficRand = item.m_trafficRand; // NON-STOCK CODE END #endif #if ROUTING #if ADVANCEDAI if (enableAdvancedAI) { float adjustedBaseLength = baseLength; if (m_queueItem.spawned || (nextLaneId != m_startLaneA && nextLaneId != m_startLaneB)) { if (laneDist != 0) { // apply lane changing costs adjustedBaseLength *= 1f + laneDist * laneChangingCost * (laneDist > 1 ? m_conf.AdvancedVehicleAI.MoreThanOneLaneChangingCostFactor : 1f); // additional costs for changing multiple lanes at once } } nextItem.m_comparisonValue += adjustedBaseLength; #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Applied Advanced Vehicle AI\n" + "\t" + $"baseLength={baseLength}\n" + "\t" + $"adjustedBaseLength={adjustedBaseLength}\n" + "\t" + $"laneDist={laneDist}\n" + "\t" + $"laneChangingCost={laneChangingCost}" ); } #endif } else #endif if (m_stablePath) { // all non-road vehicles with stable paths (trains, trams, etc.): apply lane distance factor float adjustedBaseLength = baseLength; adjustedBaseLength *= 1 + laneDist; nextItem.m_comparisonValue += adjustedBaseLength; #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Applied stable path lane distance costs\n" + "\t" + $"baseLength={baseLength}\n" + "\t" + $"adjustedBaseLength={adjustedBaseLength}\n" + "\t" + $"laneDist={laneDist}" ); } #endif } #endif if ( (nextLaneInfo.m_laneType & prevLaneType) != NetInfo.LaneType.None && (nextLaneInfo.m_vehicleType & m_vehicleTypes) != VehicleInfo.VehicleType.None ) { #if ADVANCEDAI && ROUTING if (! enableAdvancedAI) { #endif int firstTarget = netManager.m_lanes.m_buffer[nextLaneId].m_firstTarget; int lastTarget = netManager.m_lanes.m_buffer[nextLaneId].m_lastTarget; if (laneIndexFromInner < firstTarget || laneIndexFromInner >= lastTarget) { nextItem.m_comparisonValue += Mathf.Max(1f, transitionCost * 3f - 3f) / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); } #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: stock lane change costs\n" + "\t" + $"firstTarget={firstTarget}\n" + "\t" + $"lastTarget={lastTarget}\n" + "\t" + $"laneIndexFromInner={laneIndexFromInner}" ); } #endif #if ADVANCEDAI && ROUTING } #endif if ( !m_transportVehicle && nextLaneInfo.m_laneType == NetInfo.LaneType.TransportVehicle ) { nextItem.m_comparisonValue += 20f / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); } } #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Adding next item\n" + "\t" + $"nextItem={nextItem}" ); } #endif AddBufferItem( #if DEBUG debug, #endif nextItem, item.m_position ); } else { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Lane type and/or vehicle type mismatch or same segment/lane. Skipping." + "\t" + $"allowedLaneTypes={allowedLaneTypes}\n" + "\t" + $"allowedVehicleTypes={allowedVehicleTypes}" ); } #endif } } else { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Lane direction mismatch. Skipping." + "\t" + $"nextLaneInfo.m_finalDirection={nextLaneInfo.m_finalDirection}\n" + "\t" + $"nextFinalDir={nextFinalDir}" ); } #endif if ((nextLaneInfo.m_laneType & prevLaneType) != NetInfo.LaneType.None && (nextLaneInfo.m_vehicleType & prevVehicleType) != VehicleInfo.VehicleType.None) { newLaneIndexFromInner++; } } nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; continue; } laneIndexFromInner = newLaneIndexFromInner; return blocked; } // 4 private void ProcessItemPedBicycle( #if DEBUG bool debug, uint unitId, #endif BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, ushort nextSegmentId, ref NetSegment nextSegment, ushort nextNodeId, ref NetNode nextNode, int nextLaneIndex, uint nextLaneId, ref NetLane nextLane, byte connectOffset, byte laneSwitchOffset) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle called.\n" + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" + "\t" + $"prevLaneSpeed={prevLaneSpeed}\n" + "\t" + $"nextSegmentId={nextSegmentId}\n" + "\t" + $"nextNodeId={nextNodeId}\n" + "\t" + $"nextLaneIndex={nextLaneIndex}\n" + "\t" + $"nextLaneId={nextLaneId}\n" + "\t" + $"connectOffset={connectOffset}\n" + "\t" + $"laneSwitchOffset={laneSwitchOffset}" ); } #endif if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Disable mask\n" + "\t" + $"m_disableMask={m_disableMask}\n" + "\t" + $"nextSegment.m_flags={nextSegment.m_flags}\n"); } #endif return; } // NON-STOCK CODE START #if JUNCTIONRESTRICTIONS || CUSTOMTRAFFICLIGHTS if (Options.junctionRestrictionsEnabled || Options.timedLightsEnabled) { bool nextIsStartNode = nextNodeId == nextSegment.m_startNode; if (nextIsStartNode || nextNodeId == nextSegment.m_endNode) { #if JUNCTIONRESTRICTIONS if (Options.junctionRestrictionsEnabled && item.m_position.m_segment == nextSegmentId) { // check if pedestrians are not allowed to cross here if (!m_junctionManager.IsPedestrianCrossingAllowed(nextSegmentId, nextIsStartNode)) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Pedestrian crossing prohibited"); } #endif return; } } #endif #if CUSTOMTRAFFICLIGHTS if (Options.timedLightsEnabled) { // check if pedestrian light won't change to green ICustomSegmentLights lights = m_customTrafficLightsManager.GetSegmentLights(nextSegmentId, nextIsStartNode, false); if (lights != null && lights.InvalidPedestrianLight) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Invalid pedestrian light"); } #endif return; } } #endif } } #endif // NON-STOCK CODE END NetInfo nextSegmentInfo = nextSegment.Info; NetInfo prevSegmentInfo = prevSegment.Info; int nextNumLanes = nextSegmentInfo.m_lanes.Length; float distance; byte offset; if (nextSegmentId == item.m_position.m_segment) { Vector3 b = prevLane.CalculatePosition((float)(int)laneSwitchOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); Vector3 a = nextLane.CalculatePosition((float)(int)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); distance = Vector3.Distance(a, b); offset = connectOffset; } else { NetInfo.Direction direction = (NetInfo.Direction)((nextNodeId != nextSegment.m_startNode) ? 1 : 2); Vector3 b = prevLane.CalculatePosition((float)(int)laneSwitchOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); Vector3 a = ((direction & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? nextLane.m_bezier.a : nextLane.m_bezier.d; distance = Vector3.Distance(a, b); offset = (byte)(((direction & NetInfo.Direction.Forward) != NetInfo.Direction.None) ? 255 : 0); } // float prevMaxSpeed = 1f; // stock code commented // float prevLaneSpeed = 1f; // stock code commented NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; // prevMaxSpeed = prevLaneInfo.m_speedLimit; // stock code commented // prevLaneSpeed = CalculateLaneSpeed(prevMaxSpeed, laneSwitchOffset, item.m_position.m_offset, ref prevSegment, prevLaneInfo); // stock code commented prevLaneType = prevLaneInfo.m_laneType; if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } } float prevLength = (prevLaneType != NetInfo.LaneType.PublicTransport) ? prevSegment.m_averageLength : prevLane.m_length; float offsetLength = (float)Mathf.Abs(laneSwitchOffset - item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevLength; float methodDistance = item.m_methodDistance + offsetLength; float comparisonValue = item.m_comparisonValue + offsetLength / (prevLaneSpeed * m_maxLength); float duration = item.m_duration + offsetLength / prevMaxSpeed; if (!m_ignoreCost) { int ticketCost = prevLane.m_ticketCost; if (ticketCost != 0) { comparisonValue += (float)(ticketCost * m_pathRandomizer.Int32(2000u)) * TICKET_COST_CONVERSION_FACTOR; } } if (nextLaneIndex < nextNumLanes) { NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; BufferItem nextItem = default(BufferItem); nextItem.m_position.m_segment = nextSegmentId; nextItem.m_position.m_lane = (byte)nextLaneIndex; nextItem.m_position.m_offset = offset; if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { nextItem.m_methodDistance = 0f; } else { if (item.m_methodDistance == 0f) { comparisonValue += 100f / (0.25f * m_maxLength); } nextItem.m_methodDistance = methodDistance + distance; } if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian && !(nextItem.m_methodDistance < m_conf.PathFinding.MaxWalkingDistance) && !m_stablePath) { // NON-STOCK CODE (custom walking distance) #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Max. walking distance exceeded\n" + "\t" + $"nextItem.m_methodDistance={nextItem.m_methodDistance}" ); } #endif return; } float nextMaxSpeed; #if SPEEDLIMITS // NON-STOCK CODE START nextMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(nextSegmentId, (byte)nextLaneIndex, nextLaneId, nextLaneInfo); // NON-STOCK CODE END #else nextMaxSpeed = nextLaneInfo.m_speedLimit; #endif nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.25f * m_maxLength); nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); } else { nextItem.m_direction = nextLaneInfo.m_finalDirection; } if (nextLaneId == m_startLaneA) { if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetA) && ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetA)) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Invalid offset/direction on start lane A\n" + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" + "\t" + $"m_startOffsetA={m_startOffsetA}" ); } #endif return; } float nextSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; } if (nextLaneId == m_startLaneB) { if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetB) && ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetB)) { #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Invalid offset/direction on start lane B\n" + "\t" + $"nextItem.m_direction={nextItem.m_direction}\n" + "\t" + $"nextItem.m_position.m_offset={nextItem.m_position.m_offset}\n" + "\t" + $"m_startOffsetB={m_startOffsetB}" ); } #endif return; } float nextSpeed = CalculateLaneSpeed(nextMaxSpeed, m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetB) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; } nextItem.m_laneID = nextLaneId; nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); #if PARKINGAI nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); #endif #if ADVANCEDAI && ROUTING // NON-STOCK CODE START nextItem.m_trafficRand = item.m_trafficRand; // NON-STOCK CODE END #endif #if DEBUG if (debug) { Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Adding next item\n" + "\t" + $"nextItem={nextItem}" ); } #endif AddBufferItem( #if DEBUG debug, #endif nextItem, item.m_position ); } } #if ROUTING // 5 (custom: process routed vehicle paths) private bool ProcessItemRouted( #if DEBUG bool debug, uint unitId, #endif BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, #if ADVANCEDAI bool enableAdvancedAI, float laneChangingCost, #endif float segmentSelectionCost, float laneSelectionCost, ushort nextNodeId, ref NetNode nextNode, bool isMiddle, SegmentRoutingData prevSegmentRouting, LaneEndRoutingData prevLaneEndRouting, byte connectOffset, int prevInnerSimilarLaneIndex ) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemRouted called.\n" + "\t" + $"prevMaxSpeed={prevMaxSpeed}\n" + "\t" + $"prevLaneSpeed={prevLaneSpeed}\n" #if ADVANCEDAI + "\t" + $"enableAdvancedAI={enableAdvancedAI}\n" + "\t" + $"laneChangingCost={laneChangingCost}\n" #endif + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + "\t" + $"nextNodeId={nextNodeId}\n" + "\t" + $"isMiddle={isMiddle}\n" + "\t" + $"prevSegmentRouting={prevSegmentRouting}\n" + "\t" + $"prevLaneEndRouting={prevLaneEndRouting}\n" + "\t" + $"connectOffset={connectOffset}\n" + "\t" + $"prevInnerSimilarLaneIndex={prevInnerSimilarLaneIndex}\n" ); } #endif /* * ======================================================================================================= * Fetch lane end transitions, check if there are any present * ======================================================================================================= */ LaneTransitionData[] laneTransitions = prevLaneEndRouting.transitions; if (laneTransitions == null) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemRouted: Aborting: No lane transitions"); } #endif return false; } ushort prevSegmentId = item.m_position.m_segment; int prevLaneIndex = item.m_position.m_lane; NetInfo prevSegmentInfo = prevSegment.Info; if (prevLaneIndex >= prevSegmentInfo.m_lanes.Length) { #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemRouted: Aborting: Invalid lane index"); } #endif return false; } NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; #if VEHICLERESTRICTIONS /* * ======================================================================================================= * Check vehicle restrictions, especially bans * ======================================================================================================= */ bool canUseLane = CanUseLane(prevSegmentId, prevSegmentInfo, prevLaneIndex, prevLaneInfo); if (! canUseLane && Options.vehicleRestrictionsAggression == VehicleRestrictionsAggression.Strict) { // vehicle is strictly prohibited to use this lane #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemRouted: Vehicle restrictions: Aborting: Strict vehicle restrictions active"); } #endif return false; } #endif bool strictLaneRouting = m_isLaneArrowObeyingEntity && nextNode.Info.m_class.m_service != ItemClass.Service.Beautification && (nextNode.m_flags & NetNode.Flags.Untouchable) == NetNode.Flags.None ; bool prevIsCarLane = (prevLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (prevLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None ; #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemRouted: Strict lane routing? {strictLaneRouting}\n" + "\t" + $"m_isLaneArrowObeyingEntity={m_isLaneArrowObeyingEntity}\n" + "\t" + $"nextNode.Info.m_class.m_service={nextNode.Info.m_class.m_service}\n" + "\t" + $"nextNode.m_flags={nextNode.m_flags}\n" + "\t" + $"prevIsCarLane={prevIsCarLane}" ); } #endif /* * ======================================================================================================= * Check if u-turns may be performed * ======================================================================================================= */ bool isUturnAllowedHere = false; // is u-turn allowed at this place? if ((this.m_vehicleTypes & (VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail)) == VehicleInfo.VehicleType.None) { // is vehicle able to perform a u-turn? bool isStockUturnPoint = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; #if JUNCTIONRESTRICTIONS if (Options.junctionRestrictionsEnabled) { bool nextIsStartNode = nextNodeId == prevSegment.m_startNode; bool prevIsOutgoingOneWay = nextIsStartNode ? prevSegmentRouting.startNodeOutgoingOneWay : prevSegmentRouting.endNodeOutgoingOneWay; // determine if the vehicle may u-turn at the target node, according to customization isUturnAllowedHere = m_isRoadVehicle && // only road vehicles may perform u-turns prevIsCarLane && // u-turns for road vehicles only (!m_isHeavyVehicle || isStockUturnPoint) && // only small vehicles may perform u-turns OR everyone at stock u-turn points !prevIsOutgoingOneWay && // do not u-turn on one-ways ( m_junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode) /*|| // only do u-turns if allowed (!m_queueItem.spawned && // or a yet unspawned vehicle ... (prevSegmentId == m_startSegmentA || prevSegmentId == m_startSegmentB)) // ... starts at the current segment*/ ) ; #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemRouted: Junction restrictions: Is u-turn allowed here? {isUturnAllowedHere}\n" + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"prevIsCarLane={prevIsCarLane}\n" + "\t" + $"m_isHeavyVehicle={m_isHeavyVehicle}\n" + "\t" + $"isStockUturnPoint={isStockUturnPoint}\n" + "\t" + $"prevIsOutgoingOneWay={prevIsOutgoingOneWay}\n" + "\t" + $"m_junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode)={m_junctionManager.IsUturnAllowed(prevSegmentId, nextIsStartNode)}\n" + "\t" + $"m_queueItem.vehicleId={m_queueItem.vehicleId}\n" + "\t" + $"m_queueItem.spawned={m_queueItem.spawned}\n" + "\t" + $"prevSegmentId={prevSegmentId}\n" + "\t" + $"m_startSegmentA={m_startSegmentA}\n" + "\t" + $"m_startSegmentB={m_startSegmentB}" ); } #endif } else { #endif isUturnAllowedHere = isStockUturnPoint; #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemRouted: Junction restrictions disabled: Is u-turn allowed here? {isUturnAllowedHere}"); } #endif #if JUNCTIONRESTRICTIONS } #endif } #if VEHICLERESTRICTIONS /* * ======================================================================================================= * Apply vehicle restriction costs * ======================================================================================================= */ if (!canUseLane) { laneSelectionCost *= VehicleRestrictionsManager.PATHFIND_PENALTIES[(int)Options.vehicleRestrictionsAggression]; #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemRouted: Vehicle restrictions: Applied lane costs\n" + "\t" + $"laneSelectionCost={laneSelectionCost}" ); } #endif } #endif /* * ======================================================================================================= * Apply costs for large vehicles using inner lanes on highways * ======================================================================================================= */ if (Options.preferOuterLane && m_isHeavyVehicle && m_isRoadVehicle && prevIsCarLane && prevSegmentRouting.highway && prevLaneInfo.m_similarLaneCount > 1 && m_pathRandomizer.Int32(m_conf.PathFinding.HeavyVehicleInnerLanePenaltySegmentSel) == 0) { int prevOuterSimilarLaneIndex = m_routingManager.CalcOuterSimilarLaneIndex(prevLaneInfo); float prevRelOuterLane = ((float)prevOuterSimilarLaneIndex / (float)(prevLaneInfo.m_similarLaneCount - 1)); laneSelectionCost *= 1f + m_conf.PathFinding.HeavyVehicleMaxInnerLanePenalty * prevRelOuterLane; #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemRouted: Heavy trucks prefer outer lanes on highways: Applied lane costs\n" + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + "\t" + $"Options.preferOuterLane={Options.preferOuterLane}\n" + "\t" + $"m_isHeavyVehicle={m_isHeavyVehicle}\n" + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"prevIsCarLane={prevIsCarLane}\n" + "\t" + $"prevSegmentRouting.highway={prevSegmentRouting.highway}\n" + "\t" + $"prevLaneInfo.m_similarLaneCount={prevLaneInfo.m_similarLaneCount}\n" + "\t" + $"prevOuterSimilarLaneIndex={prevOuterSimilarLaneIndex}\n" + "\t" + $"prevRelOuterLane={prevRelOuterLane}" ); } #endif } #if DEBUG if (debug) { Debug(unitId, item, $"ProcessItemRouted: Final cost factors:\n" + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + "\t" + $"laneChangingCost={laneChangingCost}" ); } #endif /* * ======================================================================================================= * Explore available lane end routings * ======================================================================================================= */ NetManager netManager = Singleton.instance; bool blocked = false; bool uturnExplored = false; for (int k = 0; k < laneTransitions.Length; ++k) { #if DEBUG if (debug) { Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Exploring lane transition #{k}: {laneTransitions[k]}"); } #endif ushort nextSegmentId = laneTransitions[k].segmentId; if (nextSegmentId == 0) { continue; } if (laneTransitions[k].type == LaneEndTransitionType.Invalid) { #if DEBUG if (debug) { Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Skipping transition: Transition is invalid"); } #endif continue; } if (nextSegmentId == prevSegmentId) { if (!isUturnAllowedHere) { #if DEBUG if (debug) { Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Skipping transition: U-turn is not allowed here"); } #endif // prevent double/forbidden exploration of previous segment by vanilla code during this method execution continue; } #if DEBUG if (debug) { Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Processing transition: Exploring u-turn"); } #endif // we are going to explore a regular u-turn uturnExplored = true; } // allow vehicles to ignore strict lane routing when moving off bool relaxedLaneRouting = m_isRoadVehicle && ((!m_queueItem.spawned || (m_queueItem.vehicleType & (ExtVehicleType.PublicTransport | ExtVehicleType.Emergency)) != ExtVehicleType.None) && (laneTransitions[k].laneId == m_startLaneA || laneTransitions[k].laneId == m_startLaneB)); #if DEBUG if (debug) { Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Relaxed lane routing? {relaxedLaneRouting}\n" + "\t" + $"relaxedLaneRouting={relaxedLaneRouting}\n" + "\t" + $"m_isRoadVehicle={m_isRoadVehicle}\n" + "\t" + $"m_queueItem.spawned={m_queueItem.spawned}\n" + "\t" + $"m_queueItem.vehicleType={m_queueItem.vehicleType}\n" + "\t" + $"m_queueItem.vehicleId={m_queueItem.vehicleId}\n" + "\t" + $"m_startLaneA={m_startLaneA}\n" + "\t" + $"m_startLaneB={m_startLaneB}" ); } #endif if ( !relaxedLaneRouting && (strictLaneRouting && laneTransitions[k].type == LaneEndTransitionType.Relaxed) ) { #if DEBUG if (debug) { Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Aborting: Cannot explore relaxed lane\n" + "\t" + $"relaxedLaneRouting={relaxedLaneRouting}\n" + "\t" + $"strictLaneRouting={strictLaneRouting}\n" + "\t" + $"laneTransitions[k].type={laneTransitions[k].type}" ); } #endif continue; } #if DEBUG if (debug) { Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Exploring lane transition now\n" #if ADVANCEDAI + "\t" + $"enableAdvancedAI={enableAdvancedAI}\n" + "\t" + $"laneChangingCost={laneChangingCost}\n" #endif + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + "\t" + $"laneSelectionCost={laneSelectionCost}" ); } #endif if ( ProcessItemCosts( #if DEBUG debug, unitId, #endif item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, #if ADVANCEDAI enableAdvancedAI, laneChangingCost, #endif nextNodeId, ref nextNode, isMiddle, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], segmentSelectionCost, laneSelectionCost, laneTransitions[k], ref prevInnerSimilarLaneIndex, connectOffset, true, false ) ) { blocked = true; } } return blocked && !uturnExplored; } #endif private void AddBufferItem( #if DEBUG bool debug, #endif BufferItem item, PathUnit.Position target ) { #if DEBUG if (debug) { m_debugPositions[target.m_segment].Add(item.m_position.m_segment); } #endif uint laneLocation = m_laneLocation[item.m_laneID]; uint locPathFindIndex = laneLocation >> 16; // upper 16 bit, expected (?) path find index int bufferIndex = (int)(laneLocation & 65535u); // lower 16 bit int comparisonBufferPos; if (locPathFindIndex == m_pathFindIndex) { if (item.m_comparisonValue >= m_buffer[bufferIndex].m_comparisonValue) { return; } int bufferPosIndex = bufferIndex >> 6; // arithmetic shift (sign stays), upper 10 bit int bufferPos = bufferIndex & -64; // upper 10 bit (no shift) if (bufferPosIndex < m_bufferMinPos || (bufferPosIndex == m_bufferMinPos && bufferPos < m_bufferMin[bufferPosIndex])) { return; } comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); if (comparisonBufferPos == bufferPosIndex) { m_buffer[bufferIndex] = item; m_laneTarget[item.m_laneID] = target; return; } int newBufferIndex = bufferPosIndex << 6 | m_bufferMax[bufferPosIndex]--; BufferItem bufferItem = m_buffer[newBufferIndex]; m_laneLocation[bufferItem.m_laneID] = laneLocation; m_buffer[bufferIndex] = bufferItem; } else { comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); } if (comparisonBufferPos >= 1024 || comparisonBufferPos < 0) { return; } while (m_bufferMax[comparisonBufferPos] == 63) { ++comparisonBufferPos; if (comparisonBufferPos == 1024) { return; } } if (comparisonBufferPos > m_bufferMaxPos) { m_bufferMaxPos = comparisonBufferPos; } bufferIndex = (comparisonBufferPos << 6 | ++m_bufferMax[comparisonBufferPos]); m_buffer[bufferIndex] = item; m_laneLocation[item.m_laneID] = (m_pathFindIndex << 16 | (uint)bufferIndex); m_laneTarget[item.m_laneID] = target; } private float CalculateLaneSpeed(float maxSpeed, byte startOffset, byte endOffset, ref NetSegment segment, NetInfo.Lane laneInfo) { NetInfo.Direction direction = ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? laneInfo.m_finalDirection : NetInfo.InvertDirection(laneInfo.m_finalDirection); if ((direction & NetInfo.Direction.Avoid) != NetInfo.Direction.None) { if (endOffset > startOffset && direction == NetInfo.Direction.AvoidForward) { return maxSpeed * 0.1f; } if (endOffset < startOffset && direction == NetInfo.Direction.AvoidBackward) { return maxSpeed * 0.1f; } return maxSpeed * 0.2f; } return maxSpeed; } private void GetLaneDirection(PathUnit.Position pathPos, out NetInfo.Direction direction, out NetInfo.LaneType laneType #if PARKINGAI , out VehicleInfo.VehicleType vehicleType #endif ) { NetManager netManager = Singleton.instance; NetInfo info = netManager.m_segments.m_buffer[pathPos.m_segment].Info; if (info.m_lanes.Length > pathPos.m_lane) { direction = info.m_lanes[pathPos.m_lane].m_finalDirection; laneType = info.m_lanes[pathPos.m_lane].m_laneType; #if PARKINGAI vehicleType = info.m_lanes[pathPos.m_lane].m_vehicleType; #endif if ((netManager.m_segments.m_buffer[pathPos.m_segment].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { direction = NetInfo.InvertDirection(direction); } } else { direction = NetInfo.Direction.None; laneType = NetInfo.LaneType.None; #if PARKINGAI vehicleType = VehicleInfo.VehicleType.None; #endif } } #if VEHICLERESTRICTIONS private bool CanUseLane(ushort segmentId, NetInfo segmentInfo, int laneIndex, NetInfo.Lane laneInfo) { if (!Options.vehicleRestrictionsEnabled || m_queueItem.vehicleType == ExtVehicleType.None || m_queueItem.vehicleType == ExtVehicleType.Tram || (laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train)) == VehicleInfo.VehicleType.None) { return true; } ExtVehicleType allowedTypes = m_vehicleRestrictionsManager.GetAllowedVehicleTypes(segmentId, segmentInfo, (uint)laneIndex, laneInfo, VehicleRestrictionsMode.Configured); return ((allowedTypes & m_queueItem.vehicleType) != ExtVehicleType.None); } #endif #if ADVANCEDAI && ROUTING private void CalculateAdvancedAiCostFactors( #if DEBUG bool debug, uint unit, #endif ref BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, ushort nextNodeId, ref NetNode nextNode, ref float segmentSelectionCost, ref float laneSelectionCost, ref float laneChangingCost ) { #if DEBUG if (debug) { Debug(unit, item, $"CalculateAdvancedAiCostFactors called.\n" + "\t" + $"nextNodeId={nextNodeId}\n" + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + "\t" + $"laneChangingCost={laneChangingCost}" ); } #endif NetInfo prevSegmentInfo = prevSegment.Info; bool nextIsJunction = (nextNode.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction; if (nextIsJunction) { /* * ======================================================================================================= * Calculate costs for randomized lane selection behind junctions and highway transitions * ======================================================================================================= */ // TODO check if highway transitions are actually covered by this code if ( !m_isHeavyVehicle && m_conf.AdvancedVehicleAI.LaneRandomizationJunctionSel > 0 && m_pathRandomizer.Int32(m_conf.AdvancedVehicleAI.LaneRandomizationJunctionSel) == 0 && m_pathRandomizer.Int32((uint)prevSegmentInfo.m_lanes.Length) == 0 ) { // randomized lane selection at junctions laneSelectionCost *= 1f + m_conf.AdvancedVehicleAI.LaneRandomizationCostFactor; #if DEBUG if (debug) { Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated randomized lane selection costs\n" + "\t" + $"laneSelectionCost={laneSelectionCost}" ); } #endif } /* * ======================================================================================================= * Calculate junction costs * ======================================================================================================= */ // TODO if (prevSegmentRouting.highway) ? segmentSelectionCost *= 1f + m_conf.AdvancedVehicleAI.JunctionBaseCost; #if DEBUG if (debug) { Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated junction costs\n" + "\t" + $"segmentSelectionCost={segmentSelectionCost}" ); } #endif } bool nextIsStartNode = prevSegment.m_startNode == nextNodeId; bool nextIsEndNode = nextNodeId == prevSegment.m_endNode; if (nextIsStartNode || nextIsEndNode) { // next node is a regular node /* * ======================================================================================================= * Calculate traffic measurement costs for segment selection * ======================================================================================================= */ NetInfo.Direction prevFinalDir = nextIsStartNode ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; prevFinalDir = ((prevSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? prevFinalDir : NetInfo.InvertDirection(prevFinalDir); TrafficMeasurementManager.SegmentDirTrafficData prevDirTrafficData = m_trafficMeasurementManager.segmentDirTrafficData[m_trafficMeasurementManager.GetDirIndex(item.m_position.m_segment, prevFinalDir)]; float segmentTraffic = Mathf.Clamp(1f - (float)prevDirTrafficData.meanSpeed / (float)TrafficMeasurementManager.REF_REL_SPEED + item.m_trafficRand, 0, 1f); segmentSelectionCost *= 1f + m_conf.AdvancedVehicleAI.TrafficCostFactor * segmentTraffic; if ( m_conf.AdvancedVehicleAI.LaneDensityRandInterval > 0 && nextIsJunction && (nextNode.m_flags & (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut)) != (NetNode.Flags.OneWayIn | NetNode.Flags.OneWayOut) ) { item.m_trafficRand = 0.01f * ((float)m_pathRandomizer.Int32((uint)m_conf.AdvancedVehicleAI.LaneDensityRandInterval + 1u) - m_conf.AdvancedVehicleAI.LaneDensityRandInterval / 2f); } if ( m_conf.AdvancedVehicleAI.LaneChangingJunctionBaseCost > 0 && (Singleton.instance.m_nodes.m_buffer[nextIsStartNode ? prevSegment.m_endNode : prevSegment.m_startNode].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) == NetNode.Flags.Junction // check previous node ) { /* * ======================================================================================================= * Calculate lane changing base cost factor when in front of junctions * ======================================================================================================= */ laneChangingCost *= m_conf.AdvancedVehicleAI.LaneChangingJunctionBaseCost; #if DEBUG if (debug) { Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated in-front-of-junction lane changing costs\n" + "\t" + $"laneChangingCost={laneChangingCost}" ); } #endif } /* * ======================================================================================================= * Calculate general lane changing base cost factor * ======================================================================================================= */ if ( m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost > 0 && m_conf.AdvancedVehicleAI.LaneChangingBaseMaxCost > m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost ) { float rand = (float)m_pathRandomizer.Int32(101u) / 100f; laneChangingCost *= m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost + rand * (m_conf.AdvancedVehicleAI.LaneChangingBaseMaxCost - m_conf.AdvancedVehicleAI.LaneChangingBaseMinCost); #if DEBUG if (debug) { Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated base lane changing costs\n" + "\t" + $"laneChangingCost={laneChangingCost}" ); } #endif } } #if DEBUG if (debug) { Debug(unit, item, $"CalculateAdvancedAiCostFactors: Calculated cost factors\n" + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + "\t" + $"laneSelectionCost={laneSelectionCost}\n" + "\t" + $"laneChangingCost={laneChangingCost}" ); } #endif } #endif private void PathFindThread() { while (true) { try { Monitor.Enter(m_queueLock); while (m_queueFirst == 0 && !m_terminated) { Monitor.Wait(m_queueLock); } if (m_terminated) { break; } m_calculating = m_queueFirst; // NON-STOCK CODE START m_queueFirst = CustomPathManager._instance.queueItems[m_calculating].nextPathUnitId; // NON-STOCK CODE END // QueueFirst = PathUnits.m_buffer[Calculating].m_nextPathUnit; // stock code commented if (m_queueFirst == 0) { m_queueLast = 0u; m_queuedPathFindCount = 0; } else { m_queuedPathFindCount--; } // NON-STOCK CODE START CustomPathManager._instance.queueItems[m_calculating].nextPathUnitId = 0u; // NON-STOCK CODE END // PathUnits.m_buffer[Calculating].m_nextPathUnit = 0u; // stock code commented m_pathUnits.m_buffer[m_calculating].m_pathFindFlags = (byte)((m_pathUnits.m_buffer[m_calculating].m_pathFindFlags & ~PathUnit.FLAG_CREATED) | PathUnit.FLAG_CALCULATING); // NON-STOCK CODE START m_queueItem = CustomPathManager._instance.queueItems[m_calculating]; // NON-STOCK CODE END } finally { Monitor.Exit(m_queueLock); } try { m_pathfindProfiler.BeginStep(); try { PathFindImplementation(m_calculating, ref m_pathUnits.m_buffer[m_calculating]); } finally { m_pathfindProfiler.EndStep(); } } catch (Exception ex) { UIView.ForwardException(ex); CODebugBase.Error(LogChannel.Core, "Path find error: " + ex.Message + "\n" + ex.StackTrace); m_pathUnits.m_buffer[m_calculating].m_pathFindFlags |= PathUnit.FLAG_FAILED; // NON-STOCK CODE START #if DEBUG ++m_failedPathFinds; #endif // NON-STOCK CODE END } try { Monitor.Enter(m_queueLock); m_pathUnits.m_buffer[m_calculating].m_pathFindFlags = (byte)(m_pathUnits.m_buffer[m_calculating].m_pathFindFlags & ~PathUnit.FLAG_CALCULATING); // NON-STOCK CODE START try { Monitor.Enter(m_bufferLock); CustomPathManager._instance.queueItems[m_calculating].queued = false; CustomPathManager._instance.ReleasePath(m_calculating); } finally { Monitor.Exit(m_bufferLock); } // NON-STOCK CODE END // Singleton.instance.ReleasePath(Calculating); // stock code commented m_calculating = 0u; Monitor.Pulse(m_queueLock); } finally { Monitor.Exit(m_queueLock); } } } } } ================================================ FILE: TLM/TLM/Custom/PathFinding/CustomPathManager.cs ================================================ #define QUEUEDSTATSx #define DEBUGPF3x using System; using System.Reflection; using System.Threading; using ColossalFramework; using ColossalFramework.Math; using JetBrains.Annotations; using UnityEngine; using TrafficManager.Geometry; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Util; using CSUtil.Commons; using static TrafficManager.Traffic.Data.ExtCitizenInstance; using TrafficManager.Traffic.Data; // ReSharper disable InconsistentNaming namespace TrafficManager.Custom.PathFinding { public class CustomPathManager : PathManager { public struct PathCreationArgs { /// /// Extended path type /// public ExtCitizenInstance.ExtPathType extPathType; /// /// Extended vehicle type /// public ExtVehicleType extVehicleType; /// /// (optional) vehicle id /// public ushort vehicleId; /// /// is entity alredy spawned? /// public bool spawned; /// /// Current build index /// public uint buildIndex; /// /// Start position (first alternative) /// public PathUnit.Position startPosA; /// /// Start position (second alternative, opposite road side) /// public PathUnit.Position startPosB; /// /// End position (first alternative) /// public PathUnit.Position endPosA; /// /// End position (second alternative, opposite road side) /// public PathUnit.Position endPosB; /// /// (optional) position of the parked vehicle /// public PathUnit.Position vehiclePosition; /// /// Allowed set of lane types /// public NetInfo.LaneType laneTypes; /// /// Allowed set of vehicle types /// public VehicleInfo.VehicleType vehicleTypes; /// /// Maximum allowed path length /// public float maxLength; /// /// Is the path calculated for a heavy vehicle? /// public bool isHeavyVehicle; /// /// Is the path calculated for a vehicle with a combustion engine? /// public bool hasCombustionEngine; /// /// Should blocked segments be ignored? /// public bool ignoreBlocked; /// /// Should flooded segments be ignored? /// public bool ignoreFlooded; /// /// Should path costs be ignored? /// public bool ignoreCosts; /// /// Should random parking apply? /// public bool randomParking; /// /// Should the path be stable (and not randomized)? /// public bool stablePath; /// /// Is this a high priority path? /// public bool skipQueue; } public struct PathUnitQueueItem { public uint nextPathUnitId; // access requires acquisition of CustomPathFind.QueueLock public ExtVehicleType vehicleType; // access requires acquisition of m_bufferLock public ExtPathType pathType; // access requires acquisition of m_bufferLock public ushort vehicleId; // access requires acquisition of m_bufferLock public bool queued; // access requires acquisition of m_bufferLock public bool spawned; // access requires acquisition of m_bufferLock //public void Reset() { // vehicleType = ExtVehicleType.None; // pathType = ExtPathType.None; // vehicleId = 0; //} public override string ToString() { return $"[PathUnitQueueItem\n" + "\t" + $"nextPathUnitId={nextPathUnitId}\n" + "\t" + $"vehicleType={vehicleType}\n" + "\t" + $"pathType={pathType}\n" + "\t" + $"vehicleId={vehicleId}\n" + "\t" + $"queued={queued}\n" + "\t" + $"spawned={spawned}\n" + "PathUnitQueueItem]"; } } /// /// Holds a linked list of path units waiting to be calculated /// internal PathUnitQueueItem[] queueItems; #if PF2 private CustomPathFind2[] _replacementPathFinds; #else private CustomPathFind[] _replacementPathFinds; #endif public static CustomPathManager _instance; #if QUEUEDSTATS public static uint TotalQueuedPathFinds { get; private set; } = 0; #endif public static bool InitDone { get; private set; } = false; //On waking up, replace the stock pathfinders with the custom one [UsedImplicitly] public new virtual void Awake() { _instance = this; } public void UpdateWithPathManagerValues(PathManager stockPathManager) { // Needed fields come from joaofarias' csl-traffic // https://github.com/joaofarias/csl-traffic m_simulationProfiler = stockPathManager.m_simulationProfiler; m_drawCallData = stockPathManager.m_drawCallData; m_properties = stockPathManager.m_properties; m_pathUnitCount = stockPathManager.m_pathUnitCount; m_renderPathGizmo = stockPathManager.m_renderPathGizmo; m_pathUnits = stockPathManager.m_pathUnits; m_bufferLock = stockPathManager.m_bufferLock; Log._Debug("Waking up CustomPathManager."); queueItems = new PathUnitQueueItem[PathManager.MAX_PATHUNIT_COUNT]; var stockPathFinds = GetComponents(); var numOfStockPathFinds = stockPathFinds.Length; int numCustomPathFinds = numOfStockPathFinds; Log._Debug("Creating " + numCustomPathFinds + " custom PathFind objects."); #if PF2 _replacementPathFinds = new CustomPathFind2[numCustomPathFinds]; #else _replacementPathFinds = new CustomPathFind[numCustomPathFinds]; #endif try { Monitor.Enter(this.m_bufferLock); for (var i = 0; i < numCustomPathFinds; i++) { #if PF2 _replacementPathFinds[i] = gameObject.AddComponent(); #else _replacementPathFinds[i] = gameObject.AddComponent(); _replacementPathFinds[i].pfId = i; if (i == 0) { _replacementPathFinds[i].IsMasterPathFind = true; } #endif } Log._Debug("Setting _replacementPathFinds"); var fieldInfo = typeof(PathManager).GetField("m_pathfinds", BindingFlags.NonPublic | BindingFlags.Instance); Log._Debug("Setting m_pathfinds to custom collection"); fieldInfo?.SetValue(this, _replacementPathFinds); for (var i = 0; i < numOfStockPathFinds; i++) { #if DEBUG Log._Debug($"PF {i}: {stockPathFinds[i].m_queuedPathFindCount} queued path-finds"); #endif //stockPathFinds[i].WaitForAllPaths(); // would cause deadlock since we have a lock on m_bufferLock Destroy(stockPathFinds[i]); } } finally { Monitor.Exit(this.m_bufferLock); } InitDone = true; } public new void ReleasePath(uint unit) { #if DEBUGPF3 Log.Warning($"CustomPathManager.ReleasePath({unit}) called."); #endif if (this.m_pathUnits.m_buffer[unit].m_simulationFlags == 0) { return; } try { Monitor.Enter(m_bufferLock); int numIters = 0; while (unit != 0u) { if (this.m_pathUnits.m_buffer[unit].m_referenceCount > 1) { --this.m_pathUnits.m_buffer[unit].m_referenceCount; break; } /*if (this.m_pathUnits.m_buffer[unit].m_pathFindFlags == PathUnit.FLAG_CREATED) { Log.Error($"Will release path unit {unit} which is CREATED!"); }*/ uint nextPathUnit = this.m_pathUnits.m_buffer[unit].m_nextPathUnit; this.m_pathUnits.m_buffer[unit].m_simulationFlags = 0; this.m_pathUnits.m_buffer[unit].m_pathFindFlags = 0; this.m_pathUnits.m_buffer[unit].m_nextPathUnit = 0u; this.m_pathUnits.m_buffer[unit].m_referenceCount = 0; this.m_pathUnits.ReleaseItem(unit); //queueItems[unit].Reset(); // NON-STOCK CODE unit = nextPathUnit; if (++numIters >= 262144) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } } this.m_pathUnitCount = (int)(this.m_pathUnits.ItemCount() - 1u); } finally { Monitor.Exit(this.m_bufferLock); } } public bool CreatePath(out uint unit, ref Randomizer randomizer, PathCreationArgs args) { uint pathUnitId; try { Monitor.Enter(this.m_bufferLock); int numIters = 0; while (true) { // NON-STOCK CODE ++numIters; if (!this.m_pathUnits.CreateItem(out pathUnitId, ref randomizer)) { unit = 0u; return false; } this.m_pathUnits.m_buffer[pathUnitId].m_simulationFlags = 1; this.m_pathUnits.m_buffer[pathUnitId].m_referenceCount = 1; this.m_pathUnits.m_buffer[pathUnitId].m_nextPathUnit = 0u; // NON-STOCK CODE START if (queueItems[pathUnitId].queued) { ReleasePath(pathUnitId); if (numIters > 10) { unit = 0u; return false; } continue; } break; } queueItems[pathUnitId].vehicleType = args.extVehicleType; queueItems[pathUnitId].vehicleId = args.vehicleId; queueItems[pathUnitId].pathType = args.extPathType; queueItems[pathUnitId].spawned = args.spawned; queueItems[pathUnitId].queued = true; // NON-STOCK CODE END this.m_pathUnitCount = (int)(this.m_pathUnits.ItemCount() - 1u); } finally { Monitor.Exit(this.m_bufferLock); } unit = pathUnitId; if (args.isHeavyVehicle) { this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 16; } if (args.ignoreBlocked || args.ignoreFlooded) { this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 32; } if (args.stablePath) { this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 64; } if (args.randomParking) { this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 2; } if (args.hasCombustionEngine) { this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 4; } if (args.ignoreCosts) { this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 8; } this.m_pathUnits.m_buffer[unit].m_pathFindFlags = 0; this.m_pathUnits.m_buffer[unit].m_buildIndex = args.buildIndex; this.m_pathUnits.m_buffer[unit].m_position00 = args.startPosA; this.m_pathUnits.m_buffer[unit].m_position01 = args.endPosA; this.m_pathUnits.m_buffer[unit].m_position02 = args.startPosB; this.m_pathUnits.m_buffer[unit].m_position03 = args.endPosB; this.m_pathUnits.m_buffer[unit].m_position11 = args.vehiclePosition; this.m_pathUnits.m_buffer[unit].m_laneTypes = (byte)args.laneTypes; this.m_pathUnits.m_buffer[unit].m_vehicleTypes = (ushort)args.vehicleTypes; this.m_pathUnits.m_buffer[unit].m_length = args.maxLength; this.m_pathUnits.m_buffer[unit].m_positionCount = 20; int minQueued = 10000000; #if PF2 CustomPathFind2 pathFind = null; #else CustomPathFind pathFind = null; #endif #if QUEUEDSTATS TotalQueuedPathFinds = 0; #endif for (int i = 0; i < _replacementPathFinds.Length; ++i) { #if PF2 CustomPathFind2 pathFindCandidate = _replacementPathFinds[i]; #else CustomPathFind pathFindCandidate = _replacementPathFinds[i]; #endif #if QUEUEDSTATS TotalQueuedPathFinds += (uint)pathFindCandidate.m_queuedPathFindCount; #endif if (pathFindCandidate.IsAvailable && pathFindCandidate.m_queuedPathFindCount < minQueued) { minQueued = pathFindCandidate.m_queuedPathFindCount; pathFind = pathFindCandidate; } } #if PF2 if (pathFind != null && pathFind.CalculatePath(unit, args.skipQueue)) { return true; } #else if (pathFind != null && pathFind.ExtCalculatePath(unit, args.skipQueue)) { return true; } #endif // NON-STOCK CODE START try { Monitor.Enter(this.m_bufferLock); queueItems[pathUnitId].queued = false; // NON-STOCK CODE END this.ReleasePath(unit); // NON-STOCK CODE START this.m_pathUnitCount = (int)(this.m_pathUnits.ItemCount() - 1u); } finally { Monitor.Exit(this.m_bufferLock); } // NON-STOCK CODE END return false; } public static bool FindPathPositionWithSpiralLoop(Vector3 position, ItemClass.Service service, NetInfo.LaneType laneType, VehicleInfo.VehicleType vehicleType, NetInfo.LaneType otherLaneType, VehicleInfo.VehicleType otherVehicleType, bool allowUnderground, bool requireConnect, float maxDistance, out PathUnit.Position pathPos) { return FindPathPositionWithSpiralLoop(position, null, service, laneType, vehicleType, otherLaneType, otherVehicleType, allowUnderground, requireConnect, maxDistance, out pathPos); } public static bool FindPathPositionWithSpiralLoop(Vector3 position, Vector3? secondaryPosition, ItemClass.Service service, NetInfo.LaneType laneType, VehicleInfo.VehicleType vehicleType, NetInfo.LaneType otherLaneType, VehicleInfo.VehicleType otherVehicleType, bool allowUnderground, bool requireConnect, float maxDistance, out PathUnit.Position pathPos) { PathUnit.Position position2; float distanceSqrA; float distanceSqrB; return FindPathPositionWithSpiralLoop(position, secondaryPosition, service, laneType, vehicleType, otherLaneType, otherVehicleType, VehicleInfo.VehicleType.None, allowUnderground, requireConnect, maxDistance, out pathPos, out position2, out distanceSqrA, out distanceSqrB); } public static bool FindPathPositionWithSpiralLoop(Vector3 position, ItemClass.Service service, NetInfo.LaneType laneType, VehicleInfo.VehicleType vehicleType, NetInfo.LaneType otherLaneType, VehicleInfo.VehicleType otherVehicleType, bool allowUnderground, bool requireConnect, float maxDistance, out PathUnit.Position pathPosA, out PathUnit.Position pathPosB, out float distanceSqrA, out float distanceSqrB) { return FindPathPositionWithSpiralLoop(position, null, service, laneType, vehicleType, otherLaneType, otherVehicleType, allowUnderground, requireConnect, maxDistance, out pathPosA, out pathPosB, out distanceSqrA, out distanceSqrB); } public static bool FindPathPositionWithSpiralLoop(Vector3 position, Vector3? secondaryPosition, ItemClass.Service service, NetInfo.LaneType laneType, VehicleInfo.VehicleType vehicleType, NetInfo.LaneType otherLaneType, VehicleInfo.VehicleType otherVehicleType, bool allowUnderground, bool requireConnect, float maxDistance, out PathUnit.Position pathPosA, out PathUnit.Position pathPosB, out float distanceSqrA, out float distanceSqrB) { return FindPathPositionWithSpiralLoop(position, secondaryPosition, service, laneType, vehicleType, otherLaneType, otherVehicleType, VehicleInfo.VehicleType.None, allowUnderground, requireConnect, maxDistance, out pathPosA, out pathPosB, out distanceSqrA, out distanceSqrB); } public static bool FindPathPositionWithSpiralLoop(Vector3 position, ItemClass.Service service, NetInfo.LaneType laneType, VehicleInfo.VehicleType vehicleType, NetInfo.LaneType otherLaneType, VehicleInfo.VehicleType otherVehicleType, VehicleInfo.VehicleType stopType, bool allowUnderground, bool requireConnect, float maxDistance, out PathUnit.Position pathPosA, out PathUnit.Position pathPosB, out float distanceSqrA, out float distanceSqrB) { return FindPathPositionWithSpiralLoop(position, null, service, laneType, vehicleType, otherLaneType, otherVehicleType, stopType, allowUnderground, requireConnect, maxDistance, out pathPosA, out pathPosB, out distanceSqrA, out distanceSqrB); } public static bool FindPathPositionWithSpiralLoop(Vector3 position, Vector3? secondaryPosition, ItemClass.Service service, NetInfo.LaneType laneType, VehicleInfo.VehicleType vehicleType, NetInfo.LaneType otherLaneType, VehicleInfo.VehicleType otherVehicleType, VehicleInfo.VehicleType stopType, bool allowUnderground, bool requireConnect, float maxDistance, out PathUnit.Position pathPosA, out PathUnit.Position pathPosB, out float distanceSqrA, out float distanceSqrB) { int iMin = Mathf.Max((int)((position.z - (float)NetManager.NODEGRID_CELL_SIZE) / (float)NetManager.NODEGRID_CELL_SIZE + (float)NetManager.NODEGRID_RESOLUTION / 2f), 0); int iMax = Mathf.Min((int)((position.z + (float)NetManager.NODEGRID_CELL_SIZE) / (float)NetManager.NODEGRID_CELL_SIZE + (float)NetManager.NODEGRID_RESOLUTION / 2f), NetManager.NODEGRID_RESOLUTION-1); int jMin = Mathf.Max((int)((position.x - (float)NetManager.NODEGRID_CELL_SIZE) / (float)NetManager.NODEGRID_CELL_SIZE + (float)NetManager.NODEGRID_RESOLUTION / 2f), 0); int jMax = Mathf.Min((int)((position.x + (float)NetManager.NODEGRID_CELL_SIZE) / (float)NetManager.NODEGRID_CELL_SIZE + (float)NetManager.NODEGRID_RESOLUTION / 2f), NetManager.NODEGRID_RESOLUTION - 1); int width = iMax-iMin+1; int height = jMax-jMin+1; int centerI = (int)(position.z / (float)NetManager.NODEGRID_CELL_SIZE + (float)NetManager.NODEGRID_RESOLUTION / 2f); int centerJ = (int)(position.x / (float)NetManager.NODEGRID_CELL_SIZE + (float)NetManager.NODEGRID_RESOLUTION / 2f); NetManager netManager = Singleton.instance; /*pathPosA.m_segment = 0; pathPosA.m_lane = 0; pathPosA.m_offset = 0;*/ distanceSqrA = 1E+10f; /*pathPosB.m_segment = 0; pathPosB.m_lane = 0; pathPosB.m_offset = 0;*/ distanceSqrB = 1E+10f; float minDist = float.MaxValue; PathUnit.Position myPathPosA = default(PathUnit.Position); float myDistanceSqrA = float.MaxValue; PathUnit.Position myPathPosB = default(PathUnit.Position); float myDistanceSqrB = float.MaxValue; int lastSpiralDist = 0; bool found = false; LoopUtil.SpiralLoop(centerI, centerJ, width, height, delegate (int i, int j) { if (i < 0 || i >= NetManager.NODEGRID_RESOLUTION || j < 0 || j >= NetManager.NODEGRID_RESOLUTION) return true; int spiralDist = Math.Max(Math.Abs(i - centerI), Math.Abs(j - centerJ)); // maximum norm if (found && spiralDist > lastSpiralDist) { // last iteration return false; } ushort segmentId = netManager.m_segmentGrid[i * NetManager.NODEGRID_RESOLUTION + j]; int iterations = 0; while (segmentId != 0) { NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; if (segmentInfo != null && segmentInfo.m_class.m_service == service && (netManager.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Collapsed | NetSegment.Flags.Flooded)) == NetSegment.Flags.None && (allowUnderground || !segmentInfo.m_netAI.IsUnderground())) { bool otherPassed = true; if (otherLaneType != NetInfo.LaneType.None || otherVehicleType != VehicleInfo.VehicleType.None) { // check if any lane is present that matches the given conditions otherPassed = false; Constants.ServiceFactory.NetService.IterateSegmentLanes(segmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segtId, ref NetSegment segment, byte laneIndex) { if ( (otherLaneType == NetInfo.LaneType.None || (laneInfo.m_laneType & otherLaneType) != NetInfo.LaneType.None) && (otherVehicleType == VehicleInfo.VehicleType.None || (laneInfo.m_vehicleType & otherVehicleType) != VehicleInfo.VehicleType.None)) { otherPassed = true; return false; } else { return true; } }); } if (otherPassed) { ushort startNodeId = netManager.m_segments.m_buffer[segmentId].m_startNode; ushort endNodeId = netManager.m_segments.m_buffer[segmentId].m_endNode; Vector3 startNodePos = netManager.m_nodes.m_buffer[startNodeId].m_position; Vector3 endNodePos = netManager.m_nodes.m_buffer[endNodeId].m_position; Vector3 posA; int laneIndexA; float laneOffsetA; Vector3 posB; int laneIndexB; float laneOffsetB; if (netManager.m_segments.m_buffer[segmentId].GetClosestLanePosition(position, laneType, vehicleType, stopType, requireConnect, out posA, out laneIndexA, out laneOffsetA, out posB, out laneIndexB, out laneOffsetB)) { float dist = Vector3.SqrMagnitude(position - posA); if (secondaryPosition != null) dist += Vector3.SqrMagnitude((Vector3)secondaryPosition - posA); if (dist < minDist) { found = true; minDist = dist; myPathPosA.m_segment = segmentId; myPathPosA.m_lane = (byte)laneIndexA; myPathPosA.m_offset = (byte)Mathf.Clamp(Mathf.RoundToInt(laneOffsetA * 255f), 0, 255); myDistanceSqrA = dist; dist = Vector3.SqrMagnitude(position - posB); if (secondaryPosition != null) dist += Vector3.SqrMagnitude((Vector3)secondaryPosition - posB); if (laneIndexB < 0) { myPathPosB.m_segment = 0; myPathPosB.m_lane = 0; myPathPosB.m_offset = 0; myDistanceSqrB = float.MaxValue; } else { myPathPosB.m_segment = segmentId; myPathPosB.m_lane = (byte)laneIndexB; myPathPosB.m_offset = (byte)Mathf.Clamp(Mathf.RoundToInt(laneOffsetB * 255f), 0, 255); myDistanceSqrB = dist; } } } } } segmentId = netManager.m_segments.m_buffer[segmentId].m_nextGridSegment; if (++iterations >= NetManager.MAX_SEGMENT_COUNT) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } } lastSpiralDist = spiralDist; return true; }); pathPosA = myPathPosA; distanceSqrA = myDistanceSqrA; pathPosB = myPathPosB; distanceSqrB = myDistanceSqrB; return pathPosA.m_segment != 0; } /// /// Finds a suitable path position for a walking citizen with the given world position. /// /// world position /// allowed lane types /// allowed vehicle types /// public transport allowed? /// underground position allowed? /// resulting path position /// true if a position could be found, false otherwise public static bool FindCitizenPathPosition(Vector3 pos, NetInfo.LaneType laneTypes, VehicleInfo.VehicleType vehicleTypes, bool allowTransport, bool allowUnderground, out PathUnit.Position position) { // TODO move to ExtPathManager after harmony upgrade position = default(PathUnit.Position); float minDist = 1E+10f; PathUnit.Position posA; PathUnit.Position posB; float distA; float distB; if (PathManager.FindPathPosition(pos, ItemClass.Service.Road, laneTypes, vehicleTypes, allowUnderground, false, Options.prohibitPocketCars ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { minDist = distA; position = posA; } if (PathManager.FindPathPosition(pos, ItemClass.Service.Beautification, laneTypes, vehicleTypes, allowUnderground, false, Options.prohibitPocketCars ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { minDist = distA; position = posA; } if (allowTransport && PathManager.FindPathPosition(pos, ItemClass.Service.PublicTransport, laneTypes, vehicleTypes, allowUnderground, false, Options.prohibitPocketCars ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { minDist = distA; position = posA; } return position.m_segment != 0; } /*internal void ResetQueueItem(uint unit) { queueItems[unit].Reset(); }*/ private void StopPathFinds() { #if PF2 foreach (CustomPathFind2 pathFind in _replacementPathFinds) { UnityEngine.Object.Destroy(pathFind); } #else foreach (CustomPathFind pathFind in _replacementPathFinds) { UnityEngine.Object.Destroy(pathFind); } #endif } protected virtual void OnDestroy() { Log._Debug("CustomPathManager: OnDestroy"); StopPathFinds(); } } } ================================================ FILE: TLM/TLM/Custom/PathFinding/README.md ================================================ # TM:PE -- /Custom/PathFinding Detoured path-finding classes. ## Classes - **CustomPathFind**: Implements modifications made with the lane changer and lane connector tool (ProcessItemMain), implements improved algorithms for lane selection at city roads and highways (ProcessItemMain) and implements the Advanced Vehicle AI (ProcessItemCosts) by reading current densities and speeds and calculating appropriate costs hereof. - **CustomPathManager**: Initiates custom path-finding where the **ExtVehicleType** of a vehicle is additionally passed to the **CustomPathFind** instance. ================================================ FILE: TLM/TLM/Custom/PathFinding/StockPathFind.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using ColossalFramework.UI; using System; using System.Threading; using UnityEngine; namespace TrafficManager.Custom.PathFinding { public class StockPathFind : MonoBehaviour { private const float BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR = 0.003921569f; private const float TICKET_COST_CONVERSION_FACTOR = BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * 0.0001f; private struct BufferItem { public PathUnit.Position m_position; public float m_comparisonValue; public float m_methodDistance; public float m_duration; public uint m_laneID; public NetInfo.Direction m_direction; public NetInfo.LaneType m_lanesUsed; } private Array32 m_pathUnits; private uint m_queueFirst; private uint m_queueLast; private uint m_calculating; private object m_queueLock; private Thread m_pathFindThread; private bool m_terminated; public ThreadProfiler m_pathfindProfiler; public volatile int m_queuedPathFindCount; private object m_bufferLock; private int m_bufferMinPos; private int m_bufferMaxPos; private uint[] m_laneLocation; private PathUnit.Position[] m_laneTarget; private BufferItem[] m_buffer; private int[] m_bufferMin; private int[] m_bufferMax; private float m_maxLength; private uint m_startLaneA; private uint m_startLaneB; private uint m_endLaneA; private uint m_endLaneB; private uint m_vehicleLane; private byte m_startOffsetA; private byte m_startOffsetB; private byte m_vehicleOffset; private NetSegment.Flags m_carBanMask; private bool m_ignoreBlocked; private bool m_stablePath; private bool m_randomParking; private bool m_transportVehicle; private bool m_ignoreCost; private NetSegment.Flags m_disableMask; private Randomizer m_pathRandomizer; private uint m_pathFindIndex; private NetInfo.LaneType m_laneTypes; private VehicleInfo.VehicleType m_vehicleTypes; public bool IsAvailable { get { return m_pathFindThread.IsAlive; } } private void Awake() { m_pathfindProfiler = new ThreadProfiler(); m_laneLocation = new uint[262144]; m_laneTarget = new PathUnit.Position[262144]; m_buffer = new BufferItem[65536]; m_bufferMin = new int[1024]; m_bufferMax = new int[1024]; m_queueLock = new object(); m_bufferLock = Singleton.instance.m_bufferLock; m_pathUnits = Singleton.instance.m_pathUnits; m_pathFindThread = new Thread(PathFindThread); m_pathFindThread.Name = "Pathfind"; m_pathFindThread.Priority = SimulationManager.SIMULATION_PRIORITY; m_pathFindThread.Start(); if (!m_pathFindThread.IsAlive) { CODebugBase.Error(LogChannel.Core, "Path find thread failed to start!"); } } private void OnDestroy() { while (!Monitor.TryEnter(m_queueLock, SimulationManager.SYNCHRONIZE_TIMEOUT)) { } try { m_terminated = true; Monitor.PulseAll(m_queueLock); } finally { Monitor.Exit(m_queueLock); } } public bool CalculatePath(uint unit, bool skipQueue) { if (Singleton.instance.AddPathReference(unit)) { while (!Monitor.TryEnter(m_queueLock, SimulationManager.SYNCHRONIZE_TIMEOUT)) { } try { if (skipQueue) { if (m_queueLast == 0) { m_queueLast = unit; } else { m_pathUnits.m_buffer[unit].m_nextPathUnit = m_queueFirst; } m_queueFirst = unit; } else { if (m_queueLast == 0) { m_queueFirst = unit; } else { m_pathUnits.m_buffer[m_queueLast].m_nextPathUnit = unit; } m_queueLast = unit; } m_pathUnits.m_buffer[unit].m_pathFindFlags |= 1; m_queuedPathFindCount++; Monitor.Pulse(m_queueLock); } finally { Monitor.Exit(m_queueLock); } return true; } return false; } public void WaitForAllPaths() { while (!Monitor.TryEnter(m_queueLock, SimulationManager.SYNCHRONIZE_TIMEOUT)) { } try { while (true) { if (m_queueFirst == 0 && m_calculating == 0) { break; } if (!m_terminated) { Monitor.Wait(m_queueLock); continue; } break; } } finally { Monitor.Exit(m_queueLock); } } private void PathFindImplementation(uint unit, ref PathUnit data) { NetManager netManager = Singleton.instance; m_laneTypes = (NetInfo.LaneType)m_pathUnits.m_buffer[unit].m_laneTypes; m_vehicleTypes = (VehicleInfo.VehicleType)m_pathUnits.m_buffer[unit].m_vehicleTypes; m_maxLength = m_pathUnits.m_buffer[unit].m_length; m_pathFindIndex = (m_pathFindIndex + 1 & 0x7FFF); m_pathRandomizer = new Randomizer(unit); m_carBanMask = NetSegment.Flags.CarBan; if ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IS_HEAVY) != 0) { m_carBanMask |= NetSegment.Flags.HeavyBan; } if ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_READY) != 0) { m_carBanMask |= NetSegment.Flags.WaitingPath; } m_ignoreBlocked = ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_BLOCKED) != 0); m_stablePath = ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_STABLE_PATH) != 0); m_randomParking = ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_RANDOM_PARKING) != 0); m_transportVehicle = ((m_laneTypes & NetInfo.LaneType.TransportVehicle) != NetInfo.LaneType.None); m_ignoreCost = (m_stablePath || (m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_COST) != 0); m_disableMask = (NetSegment.Flags.Collapsed | NetSegment.Flags.PathFailed); if ((m_pathUnits.m_buffer[unit].m_simulationFlags & PathUnit.FLAG_IGNORE_FLOODED) == 0) { m_disableMask |= NetSegment.Flags.Flooded; } if ((m_laneTypes & NetInfo.LaneType.Vehicle) != NetInfo.LaneType.None) { m_laneTypes |= NetInfo.LaneType.TransportVehicle; } int posCount = m_pathUnits.m_buffer[unit].m_positionCount & 0xF; int vehiclePosIndicator = m_pathUnits.m_buffer[unit].m_positionCount >> 4; BufferItem bufferItemStartA = default(BufferItem); if (data.m_position00.m_segment != 0 && posCount >= 1) { m_startLaneA = PathManager.GetLaneID(data.m_position00); m_startOffsetA = data.m_position00.m_offset; bufferItemStartA.m_laneID = m_startLaneA; bufferItemStartA.m_position = data.m_position00; GetLaneDirection(data.m_position00, out bufferItemStartA.m_direction, out bufferItemStartA.m_lanesUsed); bufferItemStartA.m_comparisonValue = 0f; bufferItemStartA.m_duration = 0f; } else { m_startLaneA = 0u; m_startOffsetA = 0; } BufferItem bufferItemStartB = default(BufferItem); if (data.m_position02.m_segment != 0 && posCount >= 3) { m_startLaneB = PathManager.GetLaneID(data.m_position02); m_startOffsetB = data.m_position02.m_offset; bufferItemStartB.m_laneID = m_startLaneB; bufferItemStartB.m_position = data.m_position02; GetLaneDirection(data.m_position02, out bufferItemStartB.m_direction, out bufferItemStartB.m_lanesUsed); bufferItemStartB.m_comparisonValue = 0f; bufferItemStartB.m_duration = 0f; } else { m_startLaneB = 0u; m_startOffsetB = 0; } BufferItem bufferItemEndA = default(BufferItem); if (data.m_position01.m_segment != 0 && posCount >= 2) { m_endLaneA = PathManager.GetLaneID(data.m_position01); bufferItemEndA.m_laneID = m_endLaneA; bufferItemEndA.m_position = data.m_position01; GetLaneDirection(data.m_position01, out bufferItemEndA.m_direction, out bufferItemEndA.m_lanesUsed); bufferItemEndA.m_methodDistance = 0.01f; bufferItemEndA.m_comparisonValue = 0f; bufferItemEndA.m_duration = 0f; } else { m_endLaneA = 0u; } BufferItem bufferItemEndB = default(BufferItem); if (data.m_position03.m_segment != 0 && posCount >= 4) { m_endLaneB = PathManager.GetLaneID(data.m_position03); bufferItemEndB.m_laneID = m_endLaneB; bufferItemEndB.m_position = data.m_position03; GetLaneDirection(data.m_position03, out bufferItemEndB.m_direction, out bufferItemEndB.m_lanesUsed); bufferItemEndB.m_methodDistance = 0.01f; bufferItemEndB.m_comparisonValue = 0f; bufferItemEndB.m_duration = 0f; } else { m_endLaneB = 0u; } if (data.m_position11.m_segment != 0 && vehiclePosIndicator >= 1) { m_vehicleLane = PathManager.GetLaneID(data.m_position11); m_vehicleOffset = data.m_position11.m_offset; } else { m_vehicleLane = 0u; m_vehicleOffset = 0; } BufferItem finalBufferItem = default(BufferItem); byte startOffset = 0; m_bufferMinPos = 0; m_bufferMaxPos = -1; if (m_pathFindIndex == 0) { uint num3 = 4294901760u; for (int i = 0; i < 262144; i++) { m_laneLocation[i] = num3; } } for (int j = 0; j < 1024; j++) { m_bufferMin[j] = 0; m_bufferMax[j] = -1; } if (bufferItemEndA.m_position.m_segment != 0) { m_bufferMax[0]++; m_buffer[++m_bufferMaxPos] = bufferItemEndA; } if (bufferItemEndB.m_position.m_segment != 0) { m_bufferMax[0]++; m_buffer[++m_bufferMaxPos] = bufferItemEndB; } bool canFindPath = false; while (m_bufferMinPos <= m_bufferMaxPos) { int bufMin = m_bufferMin[m_bufferMinPos]; int bufMax = m_bufferMax[m_bufferMinPos]; if (bufMin > bufMax) { m_bufferMinPos++; } else { m_bufferMin[m_bufferMinPos] = bufMin + 1; BufferItem candidateItem = m_buffer[(m_bufferMinPos << 6) + bufMin]; if (candidateItem.m_position.m_segment == bufferItemStartA.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartA.m_position.m_lane) { if ((candidateItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None && candidateItem.m_position.m_offset >= m_startOffsetA) { finalBufferItem = candidateItem; startOffset = m_startOffsetA; canFindPath = true; break; } if ((candidateItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None && candidateItem.m_position.m_offset <= m_startOffsetA) { finalBufferItem = candidateItem; startOffset = m_startOffsetA; canFindPath = true; break; } } if (candidateItem.m_position.m_segment == bufferItemStartB.m_position.m_segment && candidateItem.m_position.m_lane == bufferItemStartB.m_position.m_lane) { if ((candidateItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None && candidateItem.m_position.m_offset >= m_startOffsetB) { finalBufferItem = candidateItem; startOffset = m_startOffsetB; canFindPath = true; break; } if ((candidateItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None && candidateItem.m_position.m_offset <= m_startOffsetB) { finalBufferItem = candidateItem; startOffset = m_startOffsetB; canFindPath = true; break; } } if ((candidateItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None) { ushort startNodeId = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_startNode; ProcessItemMain(candidateItem, startNodeId, ref netManager.m_nodes.m_buffer[startNodeId], (byte)0, false); } if ((candidateItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None) { ushort endNodeId = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_endNode; ProcessItemMain(candidateItem, endNodeId, ref netManager.m_nodes.m_buffer[endNodeId], (byte)255, false); } int numIter = 0; ushort specialNodeId = netManager.m_lanes.m_buffer[candidateItem.m_laneID].m_nodes; if (specialNodeId != 0) { ushort startNode2 = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_startNode; ushort endNode2 = netManager.m_segments.m_buffer[candidateItem.m_position.m_segment].m_endNode; bool nodesDisabled = ((netManager.m_nodes.m_buffer[startNode2].m_flags | netManager.m_nodes.m_buffer[endNode2].m_flags) & NetNode.Flags.Disabled) != NetNode.Flags.None; while (specialNodeId != 0) { NetInfo.Direction direction = NetInfo.Direction.None; byte laneOffset = netManager.m_nodes.m_buffer[specialNodeId].m_laneOffset; if (laneOffset <= candidateItem.m_position.m_offset) { direction |= NetInfo.Direction.Forward; } if (laneOffset >= candidateItem.m_position.m_offset) { direction |= NetInfo.Direction.Backward; } if ((candidateItem.m_direction & direction) != NetInfo.Direction.None && (!nodesDisabled || (netManager.m_nodes.m_buffer[specialNodeId].m_flags & NetNode.Flags.Disabled) != NetNode.Flags.None)) { ProcessItemMain(candidateItem, specialNodeId, ref netManager.m_nodes.m_buffer[specialNodeId], laneOffset, true); } specialNodeId = netManager.m_nodes.m_buffer[specialNodeId].m_nextLaneNode; if (++numIter == 32768) { break; } } } } } if (!canFindPath) { m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; } else { float duration = (m_laneTypes != NetInfo.LaneType.Pedestrian && (m_laneTypes & NetInfo.LaneType.Pedestrian) != NetInfo.LaneType.None) ? finalBufferItem.m_duration : finalBufferItem.m_methodDistance; m_pathUnits.m_buffer[unit].m_length = duration; m_pathUnits.m_buffer[unit].m_speed = (byte)Mathf.Clamp(finalBufferItem.m_methodDistance * 100f / Mathf.Max(0.01f, finalBufferItem.m_duration), 0f, 255f); uint currentPathUnitId = unit; int currentItemPositionCount = 0; int sumOfPositionCounts = 0; PathUnit.Position currentPosition = finalBufferItem.m_position; if ((currentPosition.m_segment != bufferItemEndA.m_position.m_segment || currentPosition.m_lane != bufferItemEndA.m_position.m_lane || currentPosition.m_offset != bufferItemEndA.m_position.m_offset) && (currentPosition.m_segment != bufferItemEndB.m_position.m_segment || currentPosition.m_lane != bufferItemEndB.m_position.m_lane || currentPosition.m_offset != bufferItemEndB.m_position.m_offset)) { if (startOffset != currentPosition.m_offset) { PathUnit.Position position2 = currentPosition; position2.m_offset = startOffset; m_pathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, position2); } m_pathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); currentPosition = m_laneTarget[finalBufferItem.m_laneID]; } for (int k = 0; k < 262144; k++) { m_pathUnits.m_buffer[currentPathUnitId].SetPosition(currentItemPositionCount++, currentPosition); if (currentPosition.m_segment == bufferItemEndA.m_position.m_segment && currentPosition.m_lane == bufferItemEndA.m_position.m_lane && currentPosition.m_offset == bufferItemEndA.m_position.m_offset) { goto IL_0c87; } if (currentPosition.m_segment == bufferItemEndB.m_position.m_segment && currentPosition.m_lane == bufferItemEndB.m_position.m_lane && currentPosition.m_offset == bufferItemEndB.m_position.m_offset) { goto IL_0c87; } if (currentItemPositionCount == 12) { while (!Monitor.TryEnter(m_bufferLock, SimulationManager.SYNCHRONIZE_TIMEOUT)) { } uint createdPathUnitId = default(uint); try { if (m_pathUnits.CreateItem(out createdPathUnitId, ref m_pathRandomizer)) { m_pathUnits.m_buffer[createdPathUnitId] = m_pathUnits.m_buffer[currentPathUnitId]; m_pathUnits.m_buffer[createdPathUnitId].m_referenceCount = 1; m_pathUnits.m_buffer[createdPathUnitId].m_pathFindFlags = 4; m_pathUnits.m_buffer[currentPathUnitId].m_nextPathUnit = createdPathUnitId; m_pathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; sumOfPositionCounts += currentItemPositionCount; Singleton.instance.m_pathUnitCount = (int)(m_pathUnits.ItemCount() - 1); goto end_IL_0dbc; } m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; return; end_IL_0dbc:; } finally { Monitor.Exit(m_bufferLock); } currentPathUnitId = createdPathUnitId; currentItemPositionCount = 0; } uint laneID = PathManager.GetLaneID(currentPosition); currentPosition = m_laneTarget[laneID]; continue; IL_0c87: m_pathUnits.m_buffer[currentPathUnitId].m_positionCount = (byte)currentItemPositionCount; sumOfPositionCounts += currentItemPositionCount; if (sumOfPositionCounts != 0) { currentPathUnitId = m_pathUnits.m_buffer[unit].m_nextPathUnit; currentItemPositionCount = m_pathUnits.m_buffer[unit].m_positionCount; int numIter = 0; while (currentPathUnitId != 0) { m_pathUnits.m_buffer[currentPathUnitId].m_length = duration * (float)(sumOfPositionCounts - currentItemPositionCount) / (float)sumOfPositionCounts; currentItemPositionCount += m_pathUnits.m_buffer[currentPathUnitId].m_positionCount; currentPathUnitId = m_pathUnits.m_buffer[currentPathUnitId].m_nextPathUnit; if (++numIter >= 262144) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } } } m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_READY; return; } m_pathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; } } // 1 private void ProcessItemMain(BufferItem item, ushort nextNodeId, ref NetNode nextNode, byte connectOffset, bool isMiddle) { NetManager netManager = Singleton.instance; bool prevIsPedestrianLane = false; bool prevIsBicycleLane = false; bool prevIsCenterPlatform = false; bool prevIsElevated = false; int prevRelSimilarLaneIndex = 0; NetInfo prevSegmentInfo = netManager.m_segments.m_buffer[item.m_position.m_segment].Info; if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; prevIsPedestrianLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian); prevIsBicycleLane = (prevLaneInfo.m_laneType == NetInfo.LaneType.Vehicle && (prevLaneInfo.m_vehicleType & m_vehicleTypes) == VehicleInfo.VehicleType.Bicycle); prevIsCenterPlatform = prevLaneInfo.m_centerPlatform; prevIsElevated = prevLaneInfo.m_elevated; prevRelSimilarLaneIndex = (((prevLaneInfo.m_finalDirection & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? (prevLaneInfo.m_similarLaneCount - prevLaneInfo.m_similarLaneIndex - 1) : prevLaneInfo.m_similarLaneIndex); } if (isMiddle) { for (int i = 0; i < 8; i++) { ushort nextSegmentId = nextNode.GetSegment(i); if (nextSegmentId != 0) { ProcessItemCosts(item, nextNodeId, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, !prevIsPedestrianLane, prevIsPedestrianLane); } } } else if (prevIsPedestrianLane) { if (!prevIsElevated) { ushort prevSegmentId = item.m_position.m_segment; int prevLaneIndex = item.m_position.m_lane; if (nextNode.Info.m_class.m_service != ItemClass.Service.Beautification) { bool canCrossStreet = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None; bool isOnCenterPlatform = prevIsCenterPlatform && (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Junction)) == NetNode.Flags.None; ushort nextLeftSegmentId = prevSegmentId; ushort nextRightSegmentId = prevSegmentId; int leftLaneIndex = default(int); int rightLaneIndex = default(int); uint leftLaneId = default(uint); uint rightLaneId = default(uint); netManager.m_segments.m_buffer[prevSegmentId].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, prevLaneIndex, isOnCenterPlatform, out leftLaneIndex, out rightLaneIndex, out leftLaneId, out rightLaneId); if (leftLaneId == 0 || rightLaneId == 0) { ushort leftSegmentId = default(ushort); ushort rightSegmentId = default(ushort); netManager.m_segments.m_buffer[prevSegmentId].GetLeftAndRightSegments(nextNodeId, out leftSegmentId, out rightSegmentId); int numIter = 0; while (leftSegmentId != 0 && leftSegmentId != prevSegmentId && leftLaneId == 0) { int someLeftLaneIndex = default(int); int someRightLaneIndex = default(int); uint someLeftLaneId = default(uint); uint someRightLaneId = default(uint); netManager.m_segments.m_buffer[leftSegmentId].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); if (someRightLaneId != 0) { nextLeftSegmentId = leftSegmentId; leftLaneIndex = someRightLaneIndex; leftLaneId = someRightLaneId; } else { leftSegmentId = netManager.m_segments.m_buffer[leftSegmentId].GetLeftSegment(nextNodeId); } if (++numIter == 8) { break; } } numIter = 0; while (rightSegmentId != 0 && rightSegmentId != prevSegmentId && rightLaneId == 0) { int someLeftLaneIndex = default(int); int someRightLaneIndex = default(int); uint someLeftLaneId = default(uint); uint someRightLaneId = default(uint); netManager.m_segments.m_buffer[rightSegmentId].GetLeftAndRightLanes(nextNodeId, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, -1, isOnCenterPlatform, out someLeftLaneIndex, out someRightLaneIndex, out someLeftLaneId, out someRightLaneId); if (someLeftLaneId != 0) { nextRightSegmentId = rightSegmentId; rightLaneIndex = someLeftLaneIndex; rightLaneId = someLeftLaneId; } else { rightSegmentId = netManager.m_segments.m_buffer[rightSegmentId].GetRightSegment(nextNodeId); } if (++numIter == 8) { break; } } } if (leftLaneId != 0 && (nextLeftSegmentId != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { ProcessItemPedBicycle(item, nextNodeId, nextLeftSegmentId, ref netManager.m_segments.m_buffer[nextLeftSegmentId], connectOffset, connectOffset, leftLaneIndex, leftLaneId); } if (rightLaneId != 0 && rightLaneId != leftLaneId && (nextRightSegmentId != prevSegmentId || canCrossStreet || isOnCenterPlatform)) { ProcessItemPedBicycle(item, nextNodeId, nextRightSegmentId, ref netManager.m_segments.m_buffer[nextRightSegmentId], connectOffset, connectOffset, rightLaneIndex, rightLaneId); } int nextLaneIndex = default(int); uint nextLaneId = default(uint); if ((m_vehicleTypes & VehicleInfo.VehicleType.Bicycle) != VehicleInfo.VehicleType.None && netManager.m_segments.m_buffer[prevSegmentId].GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Bicycle, out nextLaneIndex, out nextLaneId)) { ProcessItemPedBicycle(item, nextNodeId, prevSegmentId, ref netManager.m_segments.m_buffer[prevSegmentId], connectOffset, connectOffset, nextLaneIndex, nextLaneId); } } else { for (int j = 0; j < 8; j++) { ushort nextSegmentId = nextNode.GetSegment(j); if (nextSegmentId != 0 && nextSegmentId != prevSegmentId) { ProcessItemCosts(item, nextNodeId, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, false, true); } } } NetInfo.LaneType nextLaneType = m_laneTypes & ~NetInfo.LaneType.Pedestrian; VehicleInfo.VehicleType nextVehicleType = m_vehicleTypes & ~VehicleInfo.VehicleType.Bicycle; if ((item.m_lanesUsed & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { nextLaneType &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } int sameSegLaneIndex = default(int); uint sameSegLaneId = default(uint); if (nextLaneType != NetInfo.LaneType.None && nextVehicleType != VehicleInfo.VehicleType.None && netManager.m_segments.m_buffer[prevSegmentId].GetClosestLane(prevLaneIndex, nextLaneType, nextVehicleType, out sameSegLaneIndex, out sameSegLaneId)) { NetInfo.Lane sameSegLaneInfo = prevSegmentInfo.m_lanes[sameSegLaneIndex]; byte sameSegConnectOffset = (byte)(((netManager.m_segments.m_buffer[prevSegmentId].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None == ((sameSegLaneInfo.m_finalDirection & NetInfo.Direction.Backward) != NetInfo.Direction.None)) ? 1 : 254); BufferItem nextItem = item; if (m_randomParking) { nextItem.m_comparisonValue += (float)m_pathRandomizer.Int32(300u) / m_maxLength; } ProcessItemPedBicycle(nextItem, nextNodeId, prevSegmentId, ref netManager.m_segments.m_buffer[prevSegmentId], sameSegConnectOffset, (byte)128, sameSegLaneIndex, sameSegLaneId); } } } else { bool allowPedestrian = (m_laneTypes & NetInfo.LaneType.Pedestrian) != NetInfo.LaneType.None; bool allowBicycle = false; byte switchConnectOffset = 0; if (allowPedestrian) { if (prevIsBicycleLane) { switchConnectOffset = connectOffset; allowBicycle = (nextNode.Info.m_class.m_service == ItemClass.Service.Beautification); } else if (m_vehicleLane != 0) { if (m_vehicleLane != item.m_laneID) { allowPedestrian = false; } else { switchConnectOffset = m_vehicleOffset; } } else { switchConnectOffset = (byte)((!m_stablePath) ? ((byte)m_pathRandomizer.UInt32(1u, 254u)) : 128); } } ushort nextSegmentId = 0; if ((m_vehicleTypes & (VehicleInfo.VehicleType.Ferry | VehicleInfo.VehicleType.Monorail)) != VehicleInfo.VehicleType.None) { bool isUturnAllowedHere = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.Junction)) != NetNode.Flags.None; for (int k = 0; k < 8; k++) { nextSegmentId = nextNode.GetSegment(k); if (nextSegmentId != 0 && nextSegmentId != item.m_position.m_segment) { ProcessItemCosts(item, nextNodeId, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, allowBicycle); } } if (isUturnAllowedHere && (m_vehicleTypes & VehicleInfo.VehicleType.Monorail) == VehicleInfo.VehicleType.None) { nextSegmentId = item.m_position.m_segment; ProcessItemCosts(item, nextNodeId, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, false); } } else { bool isUturnAllowedHere = (nextNode.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; nextSegmentId = netManager.m_segments.m_buffer[item.m_position.m_segment].GetRightSegment(nextNodeId); for (int l = 0; l < 8; l++) { if (nextSegmentId == 0) { break; } if (nextSegmentId == item.m_position.m_segment) { break; } if (ProcessItemCosts(item, nextNodeId, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, allowBicycle)) { isUturnAllowedHere = true; } nextSegmentId = netManager.m_segments.m_buffer[nextSegmentId].GetRightSegment(nextNodeId); } if (isUturnAllowedHere && (m_vehicleTypes & VehicleInfo.VehicleType.Tram) == VehicleInfo.VehicleType.None) { nextSegmentId = item.m_position.m_segment; ProcessItemCosts(item, nextNodeId, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], ref prevRelSimilarLaneIndex, connectOffset, true, false); } } if (allowPedestrian) { nextSegmentId = item.m_position.m_segment; int nextLaneIndex = default(int); uint nextLaneId = default(uint); if (netManager.m_segments.m_buffer[nextSegmentId].GetClosestLane((int)item.m_position.m_lane, NetInfo.LaneType.Pedestrian, m_vehicleTypes, out nextLaneIndex, out nextLaneId)) { ProcessItemPedBicycle(item, nextNodeId, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], switchConnectOffset, switchConnectOffset, nextLaneIndex, nextLaneId); } } } if (nextNode.m_lane != 0) { bool targetDisabled = (nextNode.m_flags & (NetNode.Flags.Disabled | NetNode.Flags.DisableOnlyMiddle)) == NetNode.Flags.Disabled; ushort nextSegmentId = netManager.m_lanes.m_buffer[nextNode.m_lane].m_segment; if (nextSegmentId != 0 && nextSegmentId != item.m_position.m_segment) { ProcessItemPublicTransport(item, nextNodeId, targetDisabled, nextSegmentId, ref netManager.m_segments.m_buffer[nextSegmentId], nextNode.m_lane, nextNode.m_laneOffset, connectOffset); } } } // 2 private void ProcessItemPublicTransport(BufferItem item, ushort nextNodeId, bool targetDisabled, ushort nextSegmentId, ref NetSegment nextSegment, uint nextLaneId, byte offset, byte connectOffset) { if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { return; } NetManager netManager = Singleton.instance; if (targetDisabled && ((netManager.m_nodes.m_buffer[nextSegment.m_startNode].m_flags | netManager.m_nodes.m_buffer[nextSegment.m_endNode].m_flags) & NetNode.Flags.Disabled) == NetNode.Flags.None) { return; } NetInfo nextSegmentInfo = nextSegment.Info; NetInfo prevSegmentInfo = netManager.m_segments.m_buffer[item.m_position.m_segment].Info; int nextNumLanes = nextSegmentInfo.m_lanes.Length; uint curLaneId = nextSegment.m_lanes; float prevMaxSpeed = 1f; float prevSpeed = 1f; NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; prevMaxSpeed = prevLaneInfo.m_speedLimit; prevLaneType = prevLaneInfo.m_laneType; if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } prevSpeed = CalculateLaneSpeed(connectOffset, item.m_position.m_offset, ref netManager.m_segments.m_buffer[item.m_position.m_segment], prevLaneInfo); } float prevLength = (prevLaneType != NetInfo.LaneType.PublicTransport) ? netManager.m_segments.m_buffer[item.m_position.m_segment].m_averageLength : netManager.m_lanes.m_buffer[item.m_laneID].m_length; float offsetLength = (float)Mathf.Abs(connectOffset - item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevLength; float methodDistance = item.m_methodDistance + offsetLength; float comparisonValue = item.m_comparisonValue + offsetLength / (prevSpeed * m_maxLength); float duration = item.m_duration + offsetLength / prevMaxSpeed; Vector3 b = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)(int)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); if (!m_ignoreCost) { int ticketCost = netManager.m_lanes.m_buffer[item.m_laneID].m_ticketCost; if (ticketCost != 0) { comparisonValue += (float)(ticketCost * m_pathRandomizer.Int32(2000u)) * TICKET_COST_CONVERSION_FACTOR; } } int nextLaneIndex = 0; while (true) { if (nextLaneIndex < nextNumLanes && curLaneId != 0) { if (nextLaneId != curLaneId) { curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; nextLaneIndex++; continue; } break; } return; } NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; if (nextLaneInfo.CheckType(m_laneTypes, m_vehicleTypes)) { Vector3 a = netManager.m_lanes.m_buffer[nextLaneId].CalculatePosition((float)(int)offset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); float distance = Vector3.Distance(a, b); BufferItem nextItem = default(BufferItem); nextItem.m_position.m_segment = nextSegmentId; nextItem.m_position.m_lane = (byte)nextLaneIndex; nextItem.m_position.m_offset = offset; if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { nextItem.m_methodDistance = 0f; } else { nextItem.m_methodDistance = methodDistance + distance; } if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian && !(nextItem.m_methodDistance < 1000f) && !m_stablePath) { return; } nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextLaneInfo.m_speedLimit) * 0.5f * m_maxLength); nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextLaneInfo.m_speedLimit) * 0.5f); if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); } else { nextItem.m_direction = nextLaneInfo.m_finalDirection; } if (nextLaneId == m_startLaneA) { if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetA) && ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetA)) { return; } float nextSpeed = CalculateLaneSpeed(m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; } if (nextLaneId == m_startLaneB) { if (((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetB) && ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None || nextItem.m_position.m_offset > m_startOffsetB)) { return; } float nextSpeed = CalculateLaneSpeed(m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetB) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; } nextItem.m_laneID = nextLaneId; nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); AddBufferItem(nextItem, item.m_position); } } // 3 private bool ProcessItemCosts(BufferItem item, ushort nextNodeId, ushort nextSegmentId, ref NetSegment nextSegment, ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian) { bool blocked = false; if ((nextSegment.m_flags & m_disableMask) != 0) { return blocked; } NetManager netManager = Singleton.instance; NetInfo nextSegmentInfo = nextSegment.Info; NetInfo prevSegmentInfo = netManager.m_segments.m_buffer[item.m_position.m_segment].Info; int nextNumLanes = nextSegmentInfo.m_lanes.Length; uint curLaneId = nextSegment.m_lanes; NetInfo.Direction nextDir = (nextNodeId != nextSegment.m_startNode) ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; NetInfo.Direction nextFinalDir = ((nextSegment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? nextDir : NetInfo.InvertDirection(nextDir); float prevMaxSpeed = 1f; float prevLaneSpeed = 1f; NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; VehicleInfo.VehicleType prevVehicleType = VehicleInfo.VehicleType.None; if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; prevLaneType = prevLaneInfo.m_laneType; prevVehicleType = prevLaneInfo.m_vehicleType; prevMaxSpeed = prevLaneInfo.m_speedLimit; prevLaneSpeed = CalculateLaneSpeed(connectOffset, item.m_position.m_offset, ref netManager.m_segments.m_buffer[item.m_position.m_segment], prevLaneInfo); } bool acuteTurningAngle = false; if (prevLaneType == NetInfo.LaneType.Vehicle && (prevVehicleType & VehicleInfo.VehicleType.Car) == VehicleInfo.VehicleType.None) { float turningAngle = 0.01f - Mathf.Min(nextSegmentInfo.m_maxTurnAngleCos, prevSegmentInfo.m_maxTurnAngleCos); if (turningAngle < 1f) { Vector3 vector = (nextNodeId != netManager.m_segments.m_buffer[item.m_position.m_segment].m_startNode) ? netManager.m_segments.m_buffer[item.m_position.m_segment].m_endDirection : netManager.m_segments.m_buffer[item.m_position.m_segment].m_startDirection; Vector3 vector2 = ((nextDir & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? nextSegment.m_startDirection : nextSegment.m_endDirection; float dirDotProd = vector.x * vector2.x + vector.z * vector2.z; if (dirDotProd >= turningAngle) { acuteTurningAngle = true; } } } float prevLength = (prevLaneType != NetInfo.LaneType.PublicTransport) ? netManager.m_segments.m_buffer[item.m_position.m_segment].m_averageLength : netManager.m_lanes.m_buffer[item.m_laneID].m_length; float offsetLength = (float)Mathf.Abs(connectOffset - item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevLength; float methodDistance = item.m_methodDistance + offsetLength; float duration = item.m_duration + offsetLength / prevMaxSpeed; if (!m_stablePath) { offsetLength *= (float)(new Randomizer(m_pathFindIndex << 16 | item.m_position.m_segment).Int32(900, 1000 + netManager.m_segments.m_buffer[item.m_position.m_segment].m_trafficDensity * 10) + m_pathRandomizer.Int32(20u)) * 0.001f; } if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (prevVehicleType & m_vehicleTypes) == VehicleInfo.VehicleType.Car && (netManager.m_segments.m_buffer[item.m_position.m_segment].m_flags & m_carBanMask) != NetSegment.Flags.None) { offsetLength *= 7.5f; } if (m_transportVehicle && prevLaneType == NetInfo.LaneType.TransportVehicle) { offsetLength *= 0.95f; } float comparisonValue = item.m_comparisonValue + offsetLength / (prevLaneSpeed * m_maxLength); int ticketCost = netManager.m_lanes.m_buffer[item.m_laneID].m_ticketCost; if (!this.m_ignoreCost && ticketCost != 0) { comparisonValue += (float)(ticketCost * this.m_pathRandomizer.Int32(2000u)) * TICKET_COST_CONVERSION_FACTOR; } if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0) { prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } Vector3 b = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)(int)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); int newLaneIndexFromInner = laneIndexFromInner; bool transitionNode = (netManager.m_nodes.m_buffer[nextNodeId].m_flags & NetNode.Flags.Transition) != NetNode.Flags.None; NetInfo.LaneType allowedLaneTypes = m_laneTypes; VehicleInfo.VehicleType allowedVehicleTypes = m_vehicleTypes; if (!enableVehicle) { allowedVehicleTypes &= VehicleInfo.VehicleType.Bicycle; if (allowedVehicleTypes == VehicleInfo.VehicleType.None) { allowedLaneTypes &= ~(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } } if (!enablePedestrian) { allowedLaneTypes &= ~NetInfo.LaneType.Pedestrian; } for (int i = 0; i < nextNumLanes && curLaneId != 0; i++) { NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[i]; float transitionCost; BufferItem nextItem = default(BufferItem); if ((nextLaneInfo.m_finalDirection & nextFinalDir) != 0) { if (nextLaneInfo.CheckType(allowedLaneTypes, allowedVehicleTypes) && (nextSegmentId != item.m_position.m_segment || i != item.m_position.m_lane) && (nextLaneInfo.m_finalDirection & nextFinalDir) != 0) { if (acuteTurningAngle && nextLaneInfo.m_laneType == NetInfo.LaneType.Vehicle && (nextLaneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) == VehicleInfo.VehicleType.None) { continue; } Vector3 a = ((nextDir & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? netManager.m_lanes.m_buffer[curLaneId].m_bezier.a : netManager.m_lanes.m_buffer[curLaneId].m_bezier.d; transitionCost = Vector3.Distance(a, b); if (transitionNode) { transitionCost *= 2f; } if (ticketCost != 0 && netManager.m_lanes.m_buffer[curLaneId].m_ticketCost != 0) { transitionCost *= 10f; } float transitionCostOverMeanMaxSpeed = transitionCost / ((prevMaxSpeed + nextLaneInfo.m_speedLimit) * 0.5f * m_maxLength); if (!this.m_stablePath && (netManager.m_lanes.m_buffer[curLaneId].m_flags & 0x80) != 0) { int firstTarget = netManager.m_lanes.m_buffer[curLaneId].m_firstTarget; int lastTarget = netManager.m_lanes.m_buffer[curLaneId].m_lastTarget; transitionCostOverMeanMaxSpeed *= (float)new Randomizer(this.m_pathFindIndex ^ curLaneId).Int32(1000, (lastTarget - firstTarget + 2) * 1000) * 0.001f; } nextItem.m_position.m_segment = nextSegmentId; nextItem.m_position.m_lane = (byte)i; nextItem.m_position.m_offset = (byte)(((nextDir & NetInfo.Direction.Forward) != 0) ? 255 : 0); if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { nextItem.m_methodDistance = 0f; } else { nextItem.m_methodDistance = methodDistance + transitionCost; } if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian && !(nextItem.m_methodDistance < 1000f) && !m_stablePath) { goto IL_09e6; } nextItem.m_comparisonValue = comparisonValue + transitionCostOverMeanMaxSpeed; nextItem.m_duration = duration + transitionCost / ((prevMaxSpeed + nextLaneInfo.m_speedLimit) * 0.5f); nextItem.m_direction = nextDir; if (curLaneId == m_startLaneA) { if ((nextItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None && nextItem.m_position.m_offset >= m_startOffsetA) { goto IL_06c5; } if ((nextItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None && nextItem.m_position.m_offset <= m_startOffsetA) { goto IL_06c5; } curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; continue; } goto IL_0765; } } else if ((nextLaneInfo.m_laneType & prevLaneType) != NetInfo.LaneType.None && (nextLaneInfo.m_vehicleType & prevVehicleType) != VehicleInfo.VehicleType.None) { newLaneIndexFromInner++; } goto IL_09e6; IL_06c5: float nextLaneSpeed = CalculateLaneSpeed(m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextLaneSpeed * m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextLaneSpeed; goto IL_0765; IL_09e6: curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; continue; IL_085e: if (!m_ignoreBlocked && (nextSegment.m_flags & NetSegment.Flags.Blocked) != NetSegment.Flags.None && (nextLaneInfo.m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { nextItem.m_comparisonValue += 0.1f; blocked = true; } nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); nextItem.m_laneID = curLaneId; if ((nextLaneInfo.m_laneType & prevLaneType) != NetInfo.LaneType.None && (nextLaneInfo.m_vehicleType & m_vehicleTypes) != VehicleInfo.VehicleType.None) { int firstTarget = netManager.m_lanes.m_buffer[curLaneId].m_firstTarget; int lastTarget = netManager.m_lanes.m_buffer[curLaneId].m_lastTarget; if (laneIndexFromInner < firstTarget || laneIndexFromInner >= lastTarget) { nextItem.m_comparisonValue += Mathf.Max(1f, transitionCost * 3f - 3f) / ((prevMaxSpeed + nextLaneInfo.m_speedLimit) * 0.5f * m_maxLength); } if (!m_transportVehicle && nextLaneInfo.m_laneType == NetInfo.LaneType.TransportVehicle) { nextItem.m_comparisonValue += 20f / ((prevMaxSpeed + nextLaneInfo.m_speedLimit) * 0.5f * m_maxLength); } } AddBufferItem(nextItem, item.m_position); goto IL_09e6; IL_0765: if (curLaneId == m_startLaneB) { if ((nextItem.m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None && nextItem.m_position.m_offset >= m_startOffsetB) { goto IL_07be; } if ((nextItem.m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None && nextItem.m_position.m_offset <= m_startOffsetB) { goto IL_07be; } curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; continue; } goto IL_085e; IL_07be: float nextLaneSpeed2 = CalculateLaneSpeed(m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); float nextOffset2 = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetB) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset2 * nextSegment.m_averageLength / (nextLaneSpeed2 * m_maxLength); nextItem.m_duration += nextOffset2 * nextSegment.m_averageLength / nextLaneSpeed2; goto IL_085e; } laneIndexFromInner = newLaneIndexFromInner; return blocked; } // 4 private void ProcessItemPedBicycle(BufferItem item, ushort nextNodeId, ushort nextSegmentId, ref NetSegment nextSegment, byte connectOffset, byte laneSwitchOffset, int nextLaneIndex, uint nextLaneId) { if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { return; } NetManager netManager = Singleton.instance; NetInfo nextSegmentInfo = nextSegment.Info; NetInfo prevSegmentInfo = netManager.m_segments.m_buffer[item.m_position.m_segment].Info; int nextNumLanes = nextSegmentInfo.m_lanes.Length; float distance; byte offset; if (nextSegmentId == item.m_position.m_segment) { Vector3 b = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)(int)laneSwitchOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); Vector3 a = netManager.m_lanes.m_buffer[nextLaneId].CalculatePosition((float)(int)connectOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); distance = Vector3.Distance(a, b); offset = connectOffset; } else { NetInfo.Direction direction = (NetInfo.Direction)((nextNodeId != nextSegment.m_startNode) ? 1 : 2); Vector3 b = netManager.m_lanes.m_buffer[item.m_laneID].CalculatePosition((float)(int)laneSwitchOffset * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); Vector3 a = ((direction & NetInfo.Direction.Forward) == NetInfo.Direction.None) ? netManager.m_lanes.m_buffer[nextLaneId].m_bezier.a : netManager.m_lanes.m_buffer[nextLaneId].m_bezier.d; distance = Vector3.Distance(a, b); offset = (byte)(((direction & NetInfo.Direction.Forward) != 0) ? 255 : 0); } float prevMaxSpeed = 1f; float prevSpeed = 1f; NetInfo.LaneType prevLaneType = NetInfo.LaneType.None; if (item.m_position.m_lane < prevSegmentInfo.m_lanes.Length) { NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[item.m_position.m_lane]; prevMaxSpeed = prevLaneInfo.m_speedLimit; prevLaneType = prevLaneInfo.m_laneType; if ((prevLaneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None) { prevLaneType = (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle); } prevSpeed = CalculateLaneSpeed(laneSwitchOffset, item.m_position.m_offset, ref netManager.m_segments.m_buffer[item.m_position.m_segment], prevLaneInfo); } float prevLength = (prevLaneType != NetInfo.LaneType.PublicTransport) ? netManager.m_segments.m_buffer[item.m_position.m_segment].m_averageLength : netManager.m_lanes.m_buffer[item.m_laneID].m_length; float offsetLength = (float)Mathf.Abs(laneSwitchOffset - item.m_position.m_offset) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR * prevLength; float methodDistance = item.m_methodDistance + offsetLength; float comparisonValue = item.m_comparisonValue + offsetLength / (prevSpeed * m_maxLength); float duration = item.m_duration + offsetLength / prevMaxSpeed; if (!m_ignoreCost) { int ticketCost = netManager.m_lanes.m_buffer[item.m_laneID].m_ticketCost; if (ticketCost != 0) { comparisonValue += (float)(ticketCost * m_pathRandomizer.Int32(2000u)) * TICKET_COST_CONVERSION_FACTOR; } } if (nextLaneIndex < nextNumLanes) { NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; BufferItem nextItem = default(BufferItem); nextItem.m_position.m_segment = nextSegmentId; nextItem.m_position.m_lane = (byte)nextLaneIndex; nextItem.m_position.m_offset = offset; if ((nextLaneInfo.m_laneType & prevLaneType) == NetInfo.LaneType.None) { nextItem.m_methodDistance = 0f; } else { if (item.m_methodDistance == 0f) { comparisonValue += 100f / (0.25f * m_maxLength); } nextItem.m_methodDistance = methodDistance + distance; } if (nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian && !(nextItem.m_methodDistance < 1000f) && !m_stablePath) { return; } nextItem.m_comparisonValue = comparisonValue + distance / ((prevMaxSpeed + nextLaneInfo.m_speedLimit) * 0.25f * m_maxLength); nextItem.m_duration = duration + distance / ((prevMaxSpeed + nextLaneInfo.m_speedLimit) * 0.5f); if ((nextSegment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { nextItem.m_direction = NetInfo.InvertDirection(nextLaneInfo.m_finalDirection); } else { nextItem.m_direction = nextLaneInfo.m_finalDirection; } if (nextLaneId == m_startLaneA) { if ((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetA) { if ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None) { return; } if (nextItem.m_position.m_offset > m_startOffsetA) { return; } } float nextSpeed = CalculateLaneSpeed(m_startOffsetA, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetA) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; } if (nextLaneId == m_startLaneB) { if ((nextItem.m_direction & NetInfo.Direction.Forward) == NetInfo.Direction.None || nextItem.m_position.m_offset < m_startOffsetB) { if ((nextItem.m_direction & NetInfo.Direction.Backward) == NetInfo.Direction.None) { return; } if (nextItem.m_position.m_offset > m_startOffsetB) { return; } } float nextSpeed = CalculateLaneSpeed(m_startOffsetB, nextItem.m_position.m_offset, ref nextSegment, nextLaneInfo); float nextOffset = (float)Mathf.Abs(nextItem.m_position.m_offset - m_startOffsetB) * BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; nextItem.m_comparisonValue += nextOffset * nextSegment.m_averageLength / (nextSpeed * m_maxLength); nextItem.m_duration += nextOffset * nextSegment.m_averageLength / nextSpeed; } nextItem.m_laneID = nextLaneId; nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); AddBufferItem(nextItem, item.m_position); } } private void AddBufferItem(BufferItem item, PathUnit.Position target) { uint laneLocation = m_laneLocation[item.m_laneID]; uint locPathFindIndex = laneLocation >> 16; // upper 16 bit, expected (?) path find index int bufferIndex = (int)(laneLocation & 65535u); // lower 16 bit int comparisonBufferPos; if (locPathFindIndex == m_pathFindIndex) { if (item.m_comparisonValue >= m_buffer[bufferIndex].m_comparisonValue) { return; } int bufferPosIndex = bufferIndex >> 6; // arithmetic shift (sign stays), upper 10 bit int bufferPos = bufferIndex & -64; // upper 10 bit (no shift) if (bufferPosIndex < m_bufferMinPos || (bufferPosIndex == m_bufferMinPos && bufferPos < m_bufferMin[bufferPosIndex])) { return; } comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); if (comparisonBufferPos == bufferPosIndex) { m_buffer[bufferIndex] = item; m_laneTarget[item.m_laneID] = target; return; } int newBufferIndex = bufferPosIndex << 6 | m_bufferMax[bufferPosIndex]--; BufferItem bufferItem = m_buffer[newBufferIndex]; m_laneLocation[bufferItem.m_laneID] = laneLocation; m_buffer[bufferIndex] = bufferItem; } else { comparisonBufferPos = Mathf.Max(Mathf.RoundToInt(item.m_comparisonValue * 1024f), m_bufferMinPos); } if (comparisonBufferPos >= 1024 || comparisonBufferPos < 0) { return; } while (m_bufferMax[comparisonBufferPos] == 63) { comparisonBufferPos++; if (comparisonBufferPos == 1024) { return; } } if (comparisonBufferPos > m_bufferMaxPos) { m_bufferMaxPos = comparisonBufferPos; } bufferIndex = (comparisonBufferPos << 6 | ++m_bufferMax[comparisonBufferPos]); m_buffer[bufferIndex] = item; m_laneLocation[item.m_laneID] = (uint)((int)(m_pathFindIndex << 16) | bufferIndex); m_laneTarget[item.m_laneID] = target; } private float CalculateLaneSpeed(byte startOffset, byte endOffset, ref NetSegment segment, NetInfo.Lane laneInfo) { NetInfo.Direction direction = ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? laneInfo.m_finalDirection : NetInfo.InvertDirection(laneInfo.m_finalDirection); if ((direction & NetInfo.Direction.Avoid) != 0) { if (endOffset > startOffset && direction == NetInfo.Direction.AvoidForward) { return laneInfo.m_speedLimit * 0.1f; } if (endOffset < startOffset && direction == NetInfo.Direction.AvoidBackward) { return laneInfo.m_speedLimit * 0.1f; } return laneInfo.m_speedLimit * 0.2f; } return laneInfo.m_speedLimit; } private void GetLaneDirection(PathUnit.Position pathPos, out NetInfo.Direction direction, out NetInfo.LaneType type) { NetManager netManager = Singleton.instance; NetInfo info = netManager.m_segments.m_buffer[pathPos.m_segment].Info; if (info.m_lanes.Length > pathPos.m_lane) { direction = info.m_lanes[pathPos.m_lane].m_finalDirection; type = info.m_lanes[pathPos.m_lane].m_laneType; if ((netManager.m_segments.m_buffer[pathPos.m_segment].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { direction = NetInfo.InvertDirection(direction); } } else { direction = NetInfo.Direction.None; type = NetInfo.LaneType.None; } } private void PathFindThread() { while (true) { if (Monitor.TryEnter(m_queueLock, SimulationManager.SYNCHRONIZE_TIMEOUT)) { try { while (m_queueFirst == 0 && !m_terminated) { Monitor.Wait(m_queueLock); } if (!m_terminated) { m_calculating = m_queueFirst; m_queueFirst = m_pathUnits.m_buffer[m_calculating].m_nextPathUnit; if (m_queueFirst == 0) { m_queueLast = 0u; m_queuedPathFindCount = 0; } else { m_queuedPathFindCount--; } m_pathUnits.m_buffer[m_calculating].m_nextPathUnit = 0u; m_pathUnits.m_buffer[m_calculating].m_pathFindFlags = (byte)((m_pathUnits.m_buffer[m_calculating].m_pathFindFlags & -2) | 2); goto end_IL_001a; } return; end_IL_001a:; } finally { Monitor.Exit(m_queueLock); } try { m_pathfindProfiler.BeginStep(); try { PathFindImplementation(m_calculating, ref m_pathUnits.m_buffer[m_calculating]); } finally { m_pathfindProfiler.EndStep(); } } catch (Exception ex) { UIView.ForwardException(ex); CODebugBase.Error(LogChannel.Core, "Path find error: " + ex.Message + "\n" + ex.StackTrace); m_pathUnits.m_buffer[m_calculating].m_pathFindFlags |= 8; } while (!Monitor.TryEnter(m_queueLock, SimulationManager.SYNCHRONIZE_TIMEOUT)) { } try { m_pathUnits.m_buffer[m_calculating].m_pathFindFlags = (byte)(m_pathUnits.m_buffer[m_calculating].m_pathFindFlags & -3); Singleton.instance.ReleasePath(m_calculating); m_calculating = 0u; Monitor.Pulse(m_queueLock); } finally { Monitor.Exit(m_queueLock); } } } } } } ================================================ FILE: TLM/TLM/Custom/README.md ================================================ # TM:PE -- /Custom Everything that is being detoured lands here. ## Classes *none* ================================================ FILE: TLM/TLM/Geometry/GeometryCalculationMode.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Geometry { public enum GeometryCalculationMode { Init, Propagate, NoPropagate } } ================================================ FILE: TLM/TLM/Geometry/ISegmentEndId.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Geometry { public interface ISegmentEndId : IEquatable { // TODO documentation ushort SegmentId { get; } bool StartNode { get; } bool Relocate(ushort segmentId, bool startNode); } } ================================================ FILE: TLM/TLM/Geometry/Impl/NodeGeometry.cs ================================================ using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Text; using System.Threading; using TrafficManager.Manager; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.TrafficLight; using TrafficManager.Util; namespace TrafficManager.Geometry.Impl { public class NodeGeometry : IEquatable { public struct SegmentEndReplacement { public ISegmentEndId oldSegmentEndId; public ISegmentEndId newSegmentEndId; public override string ToString() { return $"[SegmentEndReplacement\n" + "\t" + $"oldSegmentEndId = {oldSegmentEndId}\n" + "\t" + $"newSegmentEndId = {newSegmentEndId}\n" + "SegmentEndReplacement]"; } public bool IsDefined() { return oldSegmentEndId != null && newSegmentEndId != null; } } private const byte MAX_NUM_SEGMENTS = 8; private static NodeGeometry[] nodeGeometries; public static void PrintDebugInfo() { string buf = "-----------------------\n" + "--- NODE GEOMETRIES ---\n" + "-----------------------"; buf += $"Total: {nodeGeometries.Length}\n"; foreach (NodeGeometry nodeGeo in nodeGeometries) { if (nodeGeo.IsValid()) { buf += nodeGeo.ToString() + "\n" + "-------------------------\n"; } } Log.Info(buf); } public ushort NodeId { get; private set; } = 0; public bool IsSimpleJunction { get { return IncomingSegments == 1 || OutgoingSegments == 1; } } private ISegmentEndId lastRemovedSegmentEndId = null; public int IncomingSegments { get; private set; } = 0; public int OutgoingSegments { get; private set; } = 0; public SegmentEndReplacement CurrentSegmentReplacement = default(SegmentEndReplacement); /// /// Connected segment end geometries. /// WARNING: Individual entries may be null /// public SegmentEndGeometry[] SegmentEndGeometries { get; private set; } = new SegmentEndGeometry[MAX_NUM_SEGMENTS]; public byte NumSegmentEnds { get; private set; } = 0; /// /// Holds a list of observers which are being notified as soon as the managed node's geometry is updated (but not neccessarily modified) /// //private List> observers = new List>(); /// /// Lock object. Acquire this before accessing the HashSets. /// //public readonly object Lock = new object(); public override string ToString() { return $"[NodeGeometry ({NodeId})\n" + "\t" + $"IsValid() = {IsValid()}\n" + "\t" + $"IsSimpleJunction = {IsSimpleJunction}\n" + "\t" + $"IncomingSegments = {IncomingSegments}\n" + "\t" + $"OutgoingSegments = {OutgoingSegments}\n" + "\t" + $"SegmentEndGeometries = {SegmentEndGeometries.ArrayToString()}\n" + "\t" + $"NumSegmentEnds = {NumSegmentEnds}\n" + "NodeGeometry]"; } /// /// Constructor /// /// id of the managed node public NodeGeometry(ushort nodeId) { this.NodeId = nodeId; } public bool IsValid() { return Constants.ServiceFactory.NetService.IsNodeValid(NodeId); } /// /// Registers an observer. /// /// /// An unsubscriber /*public IDisposable Subscribe(IObserver observer) { try { Monitor.Enter(Lock); observers.Add(observer); } finally { Monitor.Exit(Lock); } return new GenericUnsubscriber(observers, observer, Lock); }*/ internal void AddSegmentEnd(SegmentEndGeometry segEndGeo, GeometryCalculationMode calcMode) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($">>> NodeGeometry: Add segment end {segEndGeo.SegmentId}, start? {segEndGeo.StartNode} @ node {NodeId}"); #endif if (!IsValid()) { //Log.Error($"NodeGeometry: Trying to add segment {segmentId} @ invalid node {NodeId}"); Invalidate(); return; } bool found = false; int freeIndex = -1; for (int i = 0; i < MAX_NUM_SEGMENTS; ++i) { SegmentEndGeometry storedEndGeo = SegmentEndGeometries[i]; if (segEndGeo.Equals(storedEndGeo)) { SegmentEndGeometries[i] = segEndGeo; found = true; break; } else if (storedEndGeo == null && freeIndex < 0) { freeIndex = i; } } if (!found) { if (freeIndex >= 0) { SegmentEndGeometries[freeIndex] = segEndGeo; } else { Log.Error($"NodeGeometry.AddSegmentEnd: Detected inconsistency. Unable to add segment end {segEndGeo} to node {NodeId}. Maximum segment end capacity reached."); } } if (calcMode == GeometryCalculationMode.Propagate) { RecalculateSegments(segEndGeo.SegmentId); } if (!found && lastRemovedSegmentEndId != null) { CurrentSegmentReplacement.oldSegmentEndId = lastRemovedSegmentEndId; CurrentSegmentReplacement.newSegmentEndId = segEndGeo; lastRemovedSegmentEndId = null; } Recalculate(); } internal void RemoveSegmentEnd(SegmentEndGeometry segmentEndGeo, GeometryCalculationMode calcMode) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($">>> NodeGeometry: Remove segment end {segmentEndGeo.SegmentId} @ {NodeId}, calcMode? {calcMode}"); #endif if (calcMode == GeometryCalculationMode.Init) { return; } if (!IsValid()) { //Log.Warning($"NodeGeometry: Trying to remove segment {segmentId} @ invalid node {NodeId}"); Invalidate(); return; } for (int i = 0; i < MAX_NUM_SEGMENTS; ++i) { if (segmentEndGeo.Equals(SegmentEndGeometries[i])) { SegmentEndGeometries[i] = null; lastRemovedSegmentEndId = segmentEndGeo; } } if (calcMode == GeometryCalculationMode.Propagate) { RecalculateSegments(segmentEndGeo.SegmentId); } Recalculate(); } private void Cleanup() { IncomingSegments = 0; OutgoingSegments = 0; NumSegmentEnds = 0; } internal void RecalculateSegments(ushort? ignoreSegmentId= null) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"NodeGeometry: Propagate @ {NodeId}. ignoreSegmentId={ignoreSegmentId}"); #endif // recalculate (other) segments for (int i = 0; i < MAX_NUM_SEGMENTS; ++i) { if (SegmentEndGeometries[i] == null) continue; if (ignoreSegmentId != null && SegmentEndGeometries[i].SegmentId == ignoreSegmentId) continue; #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"NodeGeometry: Recalculating segment {SegmentEndGeometries[i].SegmentId} @ {NodeId}"); #endif SegmentEndGeometries[i].GetSegmentGeometry(true).StartRecalculation(GeometryCalculationMode.NoPropagate); } } internal void Recalculate() { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($">>> NodeGeometry: Recalculate @ {NodeId}"); #endif Cleanup(); // check if node is valid if (!IsValid()) { Invalidate(); return; } else { // calculate node properties byte incomingSegments = 0; byte outgoingSegments = 0; for (int i = 0; i < MAX_NUM_SEGMENTS; ++i) { if (SegmentEndGeometries[i] == null) continue; ++NumSegmentEnds; #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"NodeGeometry.Recalculate: Iterating over segment end {SegmentEndGeometries[i].SegmentId} @ node {NodeId}"); #endif bool startNode = SegmentEndGeometries[i].StartNode; if (SegmentEndGeometries[i].GetSegmentGeometry(true).IsIncoming(startNode)) ++incomingSegments; if (SegmentEndGeometries[i].GetSegmentGeometry(true).IsOutgoing(startNode)) ++outgoingSegments; } IncomingSegments = incomingSegments; OutgoingSegments = outgoingSegments; #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"NodeGeometry.Recalculate: Node {NodeId} has {incomingSegments} incoming and {outgoingSegments} outgoing segments."); #endif NotifyGeomentryManager(); } } protected void Invalidate() { for (int i = 0; i < MAX_NUM_SEGMENTS; ++i) { SegmentEndGeometries[i] = null; } lastRemovedSegmentEndId = null; CurrentSegmentReplacement = default(SegmentEndReplacement); NotifyGeomentryManager(); } public bool Equals(NodeGeometry otherNodeGeo) { if (otherNodeGeo == null) { return false; } return NodeId == otherNodeGeo.NodeId; } public override bool Equals(object other) { if (other == null) { return false; } if (!(other is NodeGeometry)) { return false; } return Equals((NodeGeometry)other); } public override int GetHashCode() { int prime = 31; int result = 1; result = prime * result + NodeId.GetHashCode(); return result; } private void NotifyGeomentryManager() { if (CurrentSegmentReplacement.IsDefined()) { Constants.ManagerFactory.GeometryManager.OnSegmentEndReplacement(CurrentSegmentReplacement); } CurrentSegmentReplacement.oldSegmentEndId = null; CurrentSegmentReplacement.newSegmentEndId = null; } // static methods internal static void OnBeforeLoadData() { nodeGeometries = new NodeGeometry[NetManager.MAX_NODE_COUNT]; #if DEBUGGEO Log._Debug($"Building {nodeGeometries.Length} node geometries..."); #endif for (int i = 0; i < nodeGeometries.Length; ++i) { nodeGeometries[i] = new NodeGeometry((ushort)i); } #if DEBUGGEO Log._Debug($"Built node geometries."); #endif } public static NodeGeometry Get(ushort nodeId) { if (nodeGeometries == null) { return null; } return nodeGeometries[nodeId]; } } } ================================================ FILE: TLM/TLM/Geometry/Impl/SegmentEndGeometry.cs ================================================ using ColossalFramework; using CSUtil.Commons; using GenericGameBridge.Service; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Util; using UnityEngine; namespace TrafficManager.Geometry.Impl { public class SegmentEndGeometry : SegmentEndId { public ushort NodeId() { return Constants.ServiceFactory.NetService.GetSegmentNodeId(SegmentId, StartNode); } /// /// last known connected node /// public ushort LastKnownNodeId { get; private set; } = 0; public ushort[] ConnectedSegments { get; private set; } = new ushort[7]; public byte NumConnectedSegments { get; private set; } = 0; public byte NumIncomingSegments { get; private set; } = 0; public byte NumOutgoingSegments { get; private set; } = 0; public ushort[] LeftSegments { get; private set; } = new ushort[7]; public byte NumLeftSegments { get; private set; } = 0; public ushort[] IncomingLeftSegments { get; private set; } = new ushort[7]; public byte NumIncomingLeftSegments { get; private set; } = 0; public ushort[] OutgoingLeftSegments { get; private set; } = new ushort[7]; public byte NumOutgoingLeftSegments { get; private set; } = 0; public ushort[] RightSegments { get; private set; } = new ushort[7]; public byte NumRightSegments { get; private set; } = 0; public ushort[] IncomingRightSegments { get; private set; } = new ushort[7]; public byte NumIncomingRightSegments { get; private set; } = 0; public ushort[] OutgoingRightSegments { get; private set; } = new ushort[7]; public byte NumOutgoingRightSegments { get; private set; } = 0; public ushort[] StraightSegments { get; private set; } = new ushort[7]; public byte NumStraightSegments { get; private set; } = 0; public ushort[] IncomingStraightSegments { get; private set; } = new ushort[7]; public byte NumIncomingStraightSegments { get; private set; } = 0; public ushort[] OutgoingStraightSegments { get; private set; } = new ushort[7]; public byte NumOutgoingStraightSegments { get; private set; } = 0; /// /// Indicates that the managed segment is only connected to highway segments at the start node /// public bool OnlyHighways { get; private set; } = false; /// /// Indicates that the managed segment is an outgoing one-way segment at start node. That means vehicles may come from the node. /// public bool OutgoingOneWay { get; private set; } = false; /// /// Indicates that the managed segment is an incoming one-way segment at start node. That means vehicles may go to the node. /// public bool IncomingOneWay { get; private set; } = false; public override string ToString() { return $"[SegmentEndGeometry {base.ToString()}\n" + "\t" + $"NodeId() = {NodeId()}\n" + "\t" + $"IsValid() = {IsValid()}\n" + "\t" + $"LastKnownNodeId = {LastKnownNodeId}\n" + "\t" + $"ConnectedSegments = {ConnectedSegments.ArrayToString()}\n" + "\t" + $"NumConnectedSegments = {NumConnectedSegments}\n" + "\t" + $"NumIncomingSegments = {NumIncomingSegments}\n" + "\t" + $"NumOutgoingSegments = {NumOutgoingSegments}\n" + "\t" + $"LeftSegments = {LeftSegments.ArrayToString()}\n" + "\t" + $"NumLeftSegments = {NumLeftSegments}\n" + "\t" + $"IncomingLeftSegments = {IncomingLeftSegments.ArrayToString()}\n" + "\t" + $"NumIncomingLeftSegments = {NumIncomingLeftSegments}\n" + "\t" + $"OutgoingLeftSegments = {OutgoingLeftSegments.ArrayToString()}\n" + "\t" + $"NumOutgoingLeftSegments = {NumOutgoingLeftSegments}\n" + "\t" + $"RightSegments = {RightSegments.ArrayToString()}\n" + "\t" + $"NumRightSegments = {NumRightSegments}\n" + "\t" + $"IncomingRightSegments = {IncomingRightSegments.ArrayToString()}\n" + "\t" + $"NumIncomingRightSegments = {NumIncomingRightSegments}\n" + "\t" + $"OutgoingRightSegments = {OutgoingRightSegments.ArrayToString()}\n" + "\t" + $"NumOutgoingRightSegments = {NumOutgoingRightSegments}\n" + "\t" + $"StraightSegments = {StraightSegments.ArrayToString()}\n" + "\t" + $"NumStraightSegments = {NumStraightSegments}\n" + "\t" + $"IncomingStraightSegments = {IncomingStraightSegments.ArrayToString()}\n" + "\t" + $"NumIncomingStraightSegments = {NumIncomingStraightSegments}\n" + "\t" + $"OutgoingStraightSegments = {OutgoingStraightSegments.ArrayToString()}\n" + "\t" + $"NumOutgoingStraightSegments = {NumOutgoingStraightSegments}\n" + "\t" + $"OnlyHighways = {OnlyHighways}\n" + "\t" + $"OutgoingOneWay = {OutgoingOneWay}\n" + "\t" + $"IncomingOneWay = {IncomingOneWay}\n" + "\t" + $"GetClockwiseIndex() = {GetClockwiseIndex()}\n" + "SegmentEndGeometry]"; } public SegmentEndGeometry(ushort segmentId, bool startNode) : base(segmentId, startNode) { } public static SegmentEndGeometry Get(ISegmentEndId endId) { return Get(endId.SegmentId, endId.StartNode); } public static SegmentEndGeometry Get(ushort segmentId, bool startNode) { return SegmentGeometry.Get(segmentId)?.GetEnd(startNode); } internal void Cleanup() { for (int i = 0; i < 7; ++i) { ConnectedSegments[i] = 0; LeftSegments[i] = 0; IncomingLeftSegments[i] = 0; OutgoingLeftSegments[i] = 0; RightSegments[i] = 0; IncomingRightSegments[i] = 0; OutgoingRightSegments[i] = 0; StraightSegments[i] = 0; IncomingStraightSegments[i] = 0; OutgoingStraightSegments[i] = 0; } NumConnectedSegments = 0; NumLeftSegments = 0; NumIncomingLeftSegments = 0; NumOutgoingLeftSegments = 0; NumRightSegments = 0; NumIncomingRightSegments = 0; NumOutgoingRightSegments = 0; NumStraightSegments = 0; NumIncomingStraightSegments = 0; NumOutgoingStraightSegments = 0; NumIncomingSegments = 0; NumOutgoingSegments = 0; OnlyHighways = false; OutgoingOneWay = false; IncomingOneWay = false; LastKnownNodeId = 0; } public bool IsValid() { SegmentGeometry segGeo = GetSegmentGeometry(); bool valid = segGeo != null && segGeo.IsValid(); return valid && NodeId() != 0; } public bool IsConnectedTo(ushort otherSegmentId) { if (! IsValid()) return false; foreach (ushort segId in ConnectedSegments) if (segId == otherSegmentId) return true; return false; } public ushort[] GetIncomingSegments() { ushort[] ret = new ushort[NumIncomingLeftSegments + NumIncomingRightSegments + NumIncomingStraightSegments]; int i = 0; for (int k = 0; k < 7; ++k) { if (IncomingLeftSegments[k] == 0) break; ret[i++] = IncomingLeftSegments[k]; } for (int k = 0; k < 7; ++k) { if (IncomingStraightSegments[k] == 0) break; ret[i++] = IncomingStraightSegments[k]; } for (int k = 0; k < 7; ++k) { if (IncomingRightSegments[k] == 0) break; ret[i++] = IncomingRightSegments[k]; } return ret; } public ushort[] GetOutgoingSegments() { ushort[] ret = new ushort[NumOutgoingLeftSegments + NumOutgoingRightSegments + NumOutgoingStraightSegments]; int i = 0; for (int k = 0; k < 7; ++k) { if (OutgoingLeftSegments[k] == 0) break; ret[i++] = OutgoingLeftSegments[k]; } for (int k = 0; k < 7; ++k) { if (OutgoingRightSegments[k] == 0) break; ret[i++] = OutgoingRightSegments[k]; } for (int k = 0; k < 7; ++k) { if (OutgoingStraightSegments[k] == 0) break; ret[i++] = OutgoingStraightSegments[k]; } return ret; } public SegmentGeometry GetSegmentGeometry(bool ignoreInvalid=false) { return SegmentGeometry.Get(SegmentId, ignoreInvalid); } public short GetClockwiseIndex() { // calculate clockwise index short clockwiseIndex = -1; Constants.ServiceFactory.NetService.IterateNodeSegments(NodeId(), ClockDirection.Clockwise, delegate (ushort sId, ref NetSegment segment) { ++clockwiseIndex; //Log._Debug($"SegmentEndGeometry.Recalculate: Setting clockwise index of seg. {sId} to {clockwiseIndex} (we are @ seg. {SegmentId})"); if (sId == SegmentId) { return false; } return true; }); return clockwiseIndex; } internal void Recalculate(GeometryCalculationMode calcMode) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($">>> SegmentEndGeometry.Recalculate({calcMode}): seg. {SegmentId} @ node {NodeId()}"); #endif ushort nodeIdBeforeRecalc = LastKnownNodeId; Cleanup(); if (!IsValid()) { if (calcMode == GeometryCalculationMode.Propagate && nodeIdBeforeRecalc != 0) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"SegmentEndGeometry.Recalculate({calcMode}): seg. {SegmentId} is not valid. nodeIdBeforeRecalc={nodeIdBeforeRecalc}. Removing segment from node."); #endif NodeGeometry.Get(nodeIdBeforeRecalc).RemoveSegmentEnd(this, GeometryCalculationMode.Propagate); } return; } //NetManager netManager = Singleton.instance; ushort nodeId = NodeId(); LastKnownNodeId = nodeId; bool oneway; bool outgoingOneWay; SegmentGeometry.calculateOneWayAtNode(SegmentId, nodeId, out oneway, out outgoingOneWay); OutgoingOneWay = outgoingOneWay; if (oneway && ! OutgoingOneWay) { IncomingOneWay = true; } OnlyHighways = true; #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"Checking if segment {SegmentId} is connected to highways only at node {NodeId()}. OnlyHighways={OnlyHighways}"); #endif //ItemClass connectionClass = netManager.m_segments.m_buffer[SegmentId].Info.GetConnectionClass(); ushort firstClockwiseSegmentId = 0; bool hasOtherSegments = false; for (var s = 0; s < 8; s++) { ushort otherSegmentId = 0; Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { otherSegmentId = node.GetSegment(s); if (s == 0) { firstClockwiseSegmentId = otherSegmentId; } return true; }); if (otherSegmentId == 0 || otherSegmentId == SegmentId || ! SegmentGeometry.IsValid(otherSegmentId)) continue; /*ItemClass otherConnectionClass = Singleton.instance.m_segments.m_buffer[otherSegmentId].Info.GetConnectionClass(); if (otherConnectionClass.m_service != connectionClass.m_service) continue;*/ hasOtherSegments = true; // determine geometry bool otherIsOneWay; bool otherIsOutgoingOneWay; SegmentGeometry.calculateOneWayAtNode(otherSegmentId, nodeId, out otherIsOneWay, out otherIsOutgoingOneWay); bool otherIsHighway = SegmentGeometry.calculateIsHighway(otherSegmentId); #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"Segment {SegmentId} is connected to segment {otherSegmentId} at node {NodeId()}. otherIsOneWay={otherIsOneWay} otherIsOutgoingOneWay={otherIsOutgoingOneWay} otherIsHighway={otherIsHighway}"); #endif if (! otherIsHighway || ! otherIsOneWay) OnlyHighways = false; ArrowDirection dir = GetSegmentDir(SegmentId, otherSegmentId, nodeId); if (dir == ArrowDirection.Right) { RightSegments[NumRightSegments++] = otherSegmentId; if (!otherIsOutgoingOneWay) { IncomingRightSegments[NumIncomingRightSegments++] = otherSegmentId; if (!otherIsOneWay) OutgoingRightSegments[NumOutgoingRightSegments++] = otherSegmentId; } else { OutgoingRightSegments[NumOutgoingRightSegments++] = otherSegmentId; } } else if (dir == ArrowDirection.Left) { LeftSegments[NumLeftSegments++] = otherSegmentId; if (!otherIsOutgoingOneWay) { IncomingLeftSegments[NumIncomingLeftSegments++] = otherSegmentId; if (!otherIsOneWay) OutgoingLeftSegments[NumOutgoingLeftSegments++] = otherSegmentId; } else { OutgoingLeftSegments[NumOutgoingLeftSegments++] = otherSegmentId; } } else { #if DEBUG if (dir != ArrowDirection.Forward) { Log.Warning($"Invalid segment direction detected: {dir} for segments {SegmentId} and {otherSegmentId}"); } #endif StraightSegments[NumStraightSegments++] = otherSegmentId; if (!otherIsOutgoingOneWay) { IncomingStraightSegments[NumIncomingStraightSegments++] = otherSegmentId; if (!otherIsOneWay) OutgoingStraightSegments[NumOutgoingStraightSegments++] = otherSegmentId; } else { OutgoingStraightSegments[NumOutgoingStraightSegments++] = otherSegmentId; } } // reset highway lane arrows //Flags.removeHighwayLaneArrowFlagsAtSegment(otherSegmentId); // TODO refactor ConnectedSegments[NumConnectedSegments++] = otherSegmentId; } NumIncomingSegments = (byte)(NumIncomingLeftSegments + NumIncomingStraightSegments + NumIncomingRightSegments); NumOutgoingSegments = (byte)(NumOutgoingLeftSegments + NumOutgoingStraightSegments + NumOutgoingRightSegments); if (!hasOtherSegments) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"Segment {SegmentId} is not connected to any other segments at node {NodeId()}."); #endif OnlyHighways = false; } // propagate information to other segments if (calcMode == GeometryCalculationMode.Init || (calcMode == GeometryCalculationMode.Propagate && nodeIdBeforeRecalc != nodeId)) { if (calcMode == GeometryCalculationMode.Propagate && nodeIdBeforeRecalc != 0) { NodeGeometry.Get(nodeIdBeforeRecalc).RemoveSegmentEnd(this, GeometryCalculationMode.Propagate); } NodeGeometry.Get(nodeId).AddSegmentEnd(this, calcMode); } } public bool IsRightSegment(ushort toSegmentId) { bool contains = false; foreach (ushort segId in RightSegments) if (segId == toSegmentId) { contains = true; break; } return contains; } public bool IsLeftSegment(ushort toSegmentId) { bool contains = false; foreach (ushort segId in LeftSegments) if (segId == toSegmentId) { contains = true; break; } return contains; } public bool IsStraightSegment(ushort toSegmentId) { bool contains = false; foreach (ushort segId in StraightSegments) if (segId == toSegmentId) { contains = true; break; } return contains; } public ArrowDirection GetDirection(ushort otherSegmentId) { if (otherSegmentId == SegmentId) { return ArrowDirection.Turn; } if (! IsConnectedTo(otherSegmentId)) { return ArrowDirection.None; } if (otherSegmentId == SegmentId) return ArrowDirection.Turn; else if (IsRightSegment(otherSegmentId)) return ArrowDirection.Right; else if (IsLeftSegment(otherSegmentId)) return ArrowDirection.Left; else if (IsStraightSegment(otherSegmentId)) return ArrowDirection.Forward; return ArrowDirection.Turn; } // static methods [Obsolete] private static bool IsRightSegment(ushort fromSegment, ushort toSegment, ushort nodeid) { if (fromSegment <= 0 || toSegment <= 0) return false; return IsLeftSegment(toSegment, fromSegment, nodeid); } [Obsolete] private static bool IsLeftSegment(ushort fromSegment, ushort toSegment, ushort nodeid) { if (fromSegment <= 0 || toSegment <= 0) return false; Vector3 fromDir = GetSegmentDir(fromSegment, nodeid); fromDir.y = 0; fromDir.Normalize(); Vector3 toDir = GetSegmentDir(toSegment, nodeid); toDir.y = 0; toDir.Normalize(); return Vector3.Cross(fromDir, toDir).y >= 0.5; } private static ArrowDirection GetSegmentDir(ushort fromSegment, ushort toSegment, ushort nodeId) { if (fromSegment == 0 || toSegment == 0) { return ArrowDirection.None; } Vector3 fromDir = GetSegmentDir(fromSegment, nodeId); fromDir.y = 0; fromDir.Normalize(); Vector3 toDir = GetSegmentDir(toSegment, nodeId); toDir.y = 0; toDir.Normalize(); float c = Vector3.Cross(fromDir, toDir).y; if (c >= 0.5f) { // [+30°, +150°] return ArrowDirection.Left; } else if (c <= -0.5f) { // [-30°, -150°] return ArrowDirection.Right; } else { // (-30°, +30°) / (-150°, -180°] / (+150°, +180°] float d = Vector3.Dot(fromDir, toDir); if (d > 0) { // (-30°, +30°) if (c > 0) { // (0°, 30°] return ArrowDirection.Left; } else if (c < 0) { // (0°, -30°] return ArrowDirection.Right; } else { // [0°] return ArrowDirection.Turn; } } else { // (-150°, -180°] / (+150°, +180°] return ArrowDirection.Forward; } } } private static Vector3 GetSegmentDir(int segment, ushort nodeid) { var instance = Singleton.instance; Vector3 dir; dir = instance.m_segments.m_buffer[segment].m_startNode == nodeid ? instance.m_segments.m_buffer[segment].m_startDirection : instance.m_segments.m_buffer[segment].m_endDirection; return dir; } } } ================================================ FILE: TLM/TLM/Geometry/Impl/SegmentEndId.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Geometry.Impl { public class SegmentEndId : ISegmentEndId { public virtual ushort SegmentId { get; protected set; } = 0; public virtual bool StartNode { get; protected set; } = false; public SegmentEndId(ushort segmentId, bool startNode) { SegmentId = segmentId; StartNode = startNode; } private SegmentEndId() { } public virtual bool Relocate(ushort segmentId, bool startNode) { SegmentId = segmentId; StartNode = startNode; return true; } public override bool Equals(object other) { if (other == null) { return false; } if (!(other is ISegmentEndId)) { return false; } return Equals((ISegmentEndId)other); } public bool Equals(ISegmentEndId otherSegEndId) { if (otherSegEndId == null) { return false; } return SegmentId == otherSegEndId.SegmentId && StartNode == otherSegEndId.StartNode; } public override int GetHashCode() { int prime = 31; int result = 1; result = prime * result + SegmentId.GetHashCode(); result = prime * result + StartNode.GetHashCode(); return result; } public override string ToString() { return $"[SegmentEndId {SegmentId} @ {StartNode}]"; } } } ================================================ FILE: TLM/TLM/Geometry/Impl/SegmentGeometry.cs ================================================ using ColossalFramework; using System; using System.Collections.Generic; using System.Text; using System.Threading; using TrafficManager.Custom.AI; using TrafficManager.State; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using TrafficManager.Util; using UnityEngine; using TrafficManager.Traffic; using TrafficManager.Manager; using System.Linq; using CSUtil.Commons; using TrafficManager.Manager.Impl; namespace TrafficManager.Geometry.Impl { /// /// Manages segment geometry data (e.g. if a segment is one-way or not, which incoming/outgoing segments are connected at the start or end node) of one specific segment. /// Directional data (left, right, straight) is always given relatively to the managed segment. /// The terms "incoming"/"outgoing" refer to vehicles being able to move to/from the managed segment: Vehicles may to go to the managed segment if the other segment /// is "incoming". Vehicles may go to the other segment if it is "outgoing". /// /// Segment geometry data is primarily updated by the path-finding master thread (see method CustomPathFind.ProcessItemMain and field CustomPathFind.IsMasterPathFind). /// However, other methods may manually update geometry data by calling the "Recalculate" method. This is especially necessary for segments that are not visited by the /// path-finding algorithm (apparently if a segment is not used by any vehicle) /// public class SegmentGeometry : IEquatable { private static SegmentGeometry[] segmentGeometries; public static void PrintDebugInfo() { string buf = "--------------------------\n" + "--- SEGMENT GEOMETRIES ---\n" + "--------------------------\n"; foreach (SegmentGeometry segGeo in segmentGeometries) { if (segGeo.IsValid()) { buf += segGeo.ToString() + "\n" + "-------------------------\n"; } } Log.Info(buf); } /*public LaneGeometry[] LaneGeometries { get; private set; } = null;*/ /// /// The id of the managed segment /// public ushort SegmentId { get; private set; } private SegmentEndGeometry startNodeGeometry; private SegmentEndGeometry endNodeGeometry; public SegmentEndGeometry StartNodeGeometry { get { if (startNodeGeometry.IsValid()) return startNodeGeometry; else return null; } private set { startNodeGeometry = value; } } public SegmentEndGeometry EndNodeGeometry { get { if (endNodeGeometry.IsValid()) return endNodeGeometry; else return null; } private set { endNodeGeometry = value; } } /// /// Indicates that the managed segment is a one-way segment /// private bool oneWay = false; /// /// Indicates that the managed segment is a highway /// private bool highway = false; /// /// Indicates that the managed segment has a buslane /// private bool buslane = false; /// /// Constructor /// /// id of the managed segment public SegmentGeometry(ushort segmentId) { this.SegmentId = segmentId; startNodeGeometry = new SegmentEndGeometry(segmentId, true); endNodeGeometry = new SegmentEndGeometry(segmentId, false); } /// /// Determines the start node of the managed segment /// /// public ushort StartNodeId() { return Singleton.instance.m_segments.m_buffer[SegmentId].m_startNode; } /// /// Determines the end node of the managed segment /// /// public ushort EndNodeId() { return Singleton.instance.m_segments.m_buffer[SegmentId].m_endNode; } /// /// Lock object. Acquire this before accessing the HashSets. /// //public readonly object Lock = new object(); /// /// Holds a list of observers which are being notified as soon as the managed segment's geometry is updated (but not neccessarily modified) /// //private List> observers = new List>(); private bool valid = false; public override string ToString() { return $"[SegmentGeometry ({SegmentId})\n" + "\t" + $"IsValid() = {IsValid()}\n" + "\t" + $"oneWay = {oneWay}\n" + "\t" + $"highway = {highway}\n" + "\t" + $"buslane = {buslane}\n" + "\t" + $"StartNodeGeometry = {StartNodeGeometry}\n" + "\t" + $"EndNodeGeometry = {EndNodeGeometry}\n" + //"\t" + $"LaneGeometries = {(LaneGeometries == null ? "" : LaneGeometries.ArrayToString())}\n" + "SegmentGeometry]"; } /// /// Registers an observer. /// /// /// An unsubscriber /*public IDisposable Subscribe(IObserver observer) { try { Monitor.Enter(Lock); observers.Add(observer); } finally { Monitor.Exit(Lock); } return new GenericUnsubscriber(observers, observer, Lock); }*/ [Obsolete] public static bool IsValid(ushort segmentId) { return Constants.ServiceFactory.NetService.IsSegmentValid(segmentId); } public bool IsValid() { return IsValid(SegmentId); } public void StartRecalculation(GeometryCalculationMode calcMode) { Recalculate(calcMode); //RecalculateLaneGeometries(calcMode); } /// /// Requests recalculation of the managed segment's geometry data. If recalculation is not enforced (argument "force"), /// recalculation is only done if recalculation has not been recently executed. /// /// Specifies if logging should be performed /// Specifies if recalculation should be enforced. public void Recalculate(GeometryCalculationMode calcMode) { #if DEBUGGEO bool output = GlobalConfig.Instance.Debug.Switches[5]; if (output) Log._Debug($">>> SegmentGeometry.Recalculate({calcMode}): called for segment {SegmentId}. IsValid()={IsValid()}, wasValid={valid}"); #endif if (!IsValid()) { if (valid) { valid = false; if (calcMode == GeometryCalculationMode.Propagate) { startNodeGeometry.Recalculate(GeometryCalculationMode.Propagate); endNodeGeometry.Recalculate(GeometryCalculationMode.Propagate); } Cleanup(); Constants.ManagerFactory.GeometryManager.OnUpdateSegment(this); //NotifyObservers(); } return; } valid = true; try { #if DEBUGGEO if (output) Log._Debug($"Trying to get a lock for Recalculating geometries of segment {SegmentId}..."); #endif //Monitor.Enter(Lock); #if DEBUGGEO if (output) Log.Info($"Recalculating geometries of segment {SegmentId} STARTED"); #endif Cleanup(); oneWay = calculateIsOneWay(SegmentId); highway = calculateIsHighway(SegmentId); buslane = calculateHasBusLane(SegmentId); startNodeGeometry.Recalculate(calcMode); endNodeGeometry.Recalculate(calcMode); #if DEBUGGEO if (output) { Log.Info($"Recalculating geometries of segment {SegmentId} FINISHED (flags={Singleton.instance.m_segments.m_buffer[SegmentId].m_flags})"); SegmentEndGeometry[] endGeometries = new SegmentEndGeometry[] { startNodeGeometry, endNodeGeometry }; Log._Debug($"seg. {SegmentId}. oneWay={oneWay}"); Log._Debug($"seg. {SegmentId}. highway={highway}"); int i = 0; foreach (SegmentEndGeometry endGeometry in endGeometries) { if (i == 0) Log._Debug("--- end @ start node ---"); else Log._Debug("--- end @ end node ---"); Log._Debug($"Node {endGeometry.NodeId()} (flags={Singleton.instance.m_nodes.m_buffer[endGeometry.NodeId()].m_flags})"); Log._Debug($"seg. {SegmentId}. connectedSegments={ string.Join(", ", endGeometry.ConnectedSegments.Select(x => x.ToString()).ToArray())}"); Log._Debug($"seg. {SegmentId}. leftSegments={ string.Join(", ", endGeometry.LeftSegments.Select(x => x.ToString()).ToArray())}"); Log._Debug($"seg. {SegmentId}. incomingLeftSegments={ string.Join(", ", endGeometry.IncomingLeftSegments.Select(x => x.ToString()).ToArray())}"); Log._Debug($"seg. {SegmentId}. outgoingLeftSegments={ string.Join(", ", endGeometry.OutgoingLeftSegments.Select(x => x.ToString()).ToArray())}"); Log._Debug($"seg. {SegmentId}. rightSegments={ string.Join(", ", endGeometry.RightSegments.Select(x => x.ToString()).ToArray())}"); Log._Debug($"seg. {SegmentId}. incomingRightSegments={ string.Join(", ", endGeometry.IncomingRightSegments.Select(x => x.ToString()).ToArray())}"); Log._Debug($"seg. {SegmentId}. outgoingRightSegments={ string.Join(", ", endGeometry.OutgoingRightSegments.Select(x => x.ToString()).ToArray())}"); Log._Debug($"seg. {SegmentId}. straightSegments={ string.Join(", ", endGeometry.StraightSegments.Select(x => x.ToString()).ToArray())}"); Log._Debug($"seg. {SegmentId}. incomingStraightSegments={ string.Join(", ", endGeometry.IncomingStraightSegments.Select(x => x.ToString()).ToArray())}"); Log._Debug($"seg. {SegmentId}. outgoingStraightSegments={ string.Join(", ", endGeometry.OutgoingStraightSegments.Select(x => x.ToString()).ToArray())}"); Log._Debug($"seg. {SegmentId}. onlyHighways={endGeometry.OnlyHighways}"); Log._Debug($"seg. {SegmentId}. outgoingOneWay={endGeometry.OutgoingOneWay}"); ++i; } } #endif #if DEBUGGEO //Log._Debug($"Recalculation of segment {SegmentId} completed. Valid? {IsValid()}"); #endif Constants.ManagerFactory.GeometryManager.OnUpdateSegment(this); //NotifyObservers(); } finally { #if DEBUGGEO if (output) Log._Debug($"Lock released after recalculating geometries of segment {SegmentId}"); #endif //Monitor.Exit(Lock); } } public SegmentEndGeometry GetEnd(bool startNode) { if (! IsValid()) { return null; } if (startNode) { if (StartNodeGeometry != null && StartNodeGeometry.IsValid()) { return StartNodeGeometry; } } else if (EndNodeGeometry != null && EndNodeGeometry.IsValid()) { return EndNodeGeometry; } return null; } public SegmentEndGeometry GetEnd(ushort nodeId) { if (!IsValid()) { return null; } ushort startNodeId = StartNodeId(); if (nodeId == startNodeId) { return StartNodeGeometry; } else { ushort endNodeId = EndNodeId(); if (nodeId == endNodeId) return EndNodeGeometry; } return null; } /// /// Verifies the information that another is/is not connected to the managed segment. If the verification fails, a recalculation of geometry data is performed. /// The method does not necessarily guarantee that the segment geometry data regarding the queried segment with id "otherSegmentId" is correct. /// /// This method should only be called if there is a good case to believe that the other segment may be connected to the managed segment. /// Else, a possibly unnecessary geometry recalculation is performed. /// /// The other segment that is could be connected to the managed segment. /// /*internal bool VerifyConnectedSegment(ushort otherSegmentId) { if ((Singleton.instance.m_segments.m_buffer[otherSegmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) { return false; } if (otherSegmentId == SegmentId) return false; bool segmentIsConnectedToStartNode = false; bool segmentIsConnectedToEndNode = false; try { Monitor.Enter(Lock); segmentIsConnectedToStartNode = startNodeGeometry.IsConnectedTo(otherSegmentId); segmentIsConnectedToEndNode = endNodeGeometry.IsConnectedTo(otherSegmentId); } finally { Monitor.Exit(Lock); } if (!segmentIsConnectedToStartNode && !segmentIsConnectedToEndNode) { Log.Warning($"Neither the segments of start node {startNodeGeometry.NodeId()} nor of end node {endNodeGeometry.NodeId()} of segment {SegmentId} contain the segment {otherSegmentId}"); Recalculate(true); return true; } return false; }*/ /// /// Runs a simple segment geometry verification that only checks if the stored number of connected segments at start and end node. /// /// If the numbers of connected segments at the given node mismatches, a geometry recalculation is performed. /// /// Node at which segment counts should be checked /// true if a recalculation has been performed, false otherwise /*internal bool VerifySegmentsByCount(bool startNode) { ushort nodeId = startNode ? startNodeGeometry.NodeId() : endNodeGeometry.NodeId(); if (nodeId == 0 || (Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Created) == NetNode.Flags.None) return false; int expectedCount = Singleton.instance.m_nodes.m_buffer[nodeId].CountSegments(NetSegment.Flags.Created, SegmentId); var storedCount = CountOtherSegments(startNode); if (storedCount != expectedCount) { Log._Debug($"The number of other segments (expected {expectedCount}) at node {nodeId} does not equals the stored count ({storedCount})"); Recalculate(); return true; } return false; } internal void VerifySegmentsByCount() { if (VerifySegmentsByCount(true)) return; VerifySegmentsByCount(false); }*/ /*internal void VerifyCreated() { if (!IsValid() && wasValid) { Log._Debug($"SegmentGeometry: Segment {SegmentId} has become invalid. Recalculating."); Recalculate(true); } } internal void VerifyByNodes() { if (startNodeGeometry.NodeId() != startNodeGeometry.LastKnownNodeId) startNodeGeometry.Recalculate(true); if (endNodeGeometry.NodeId() != endNodeGeometry.LastKnownNodeId) endNodeGeometry.Recalculate(true); }*/ /// /// Determines the node id at the given segment end. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort GetNodeId(bool startNode) { if (!IsValid()) return 0; SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NodeId(); } /// /// Determines all connected segments at the given node. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort[] GetConnectedSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.ConnectedSegments; } /// /// Determines all connected right segments at the given node. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort[] GetRightSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.RightSegments; } /// /// Determines all connected left segments at the given node. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort[] GetLeftSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.LeftSegments; } /// /// Determines all connected straight segments at the given node. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort[] GetStraightSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.StraightSegments; } /// /// Determines all incoming segments at the given node. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort[] GetIncomingSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.GetIncomingSegments(); } /// /// Determines all incoming straight segments at the given node. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort[] GetIncomingStraightSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.IncomingStraightSegments; } /// /// Determines all incoming left segments at the given node. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort[] GetIncomingLeftSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.IncomingLeftSegments; } /// /// Determines all incoming right segments at the given node. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort[] GetIncomingRightSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.IncomingRightSegments; } /// /// Determines all outgoing segments at the given node. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort[] GetOutgoingSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.GetOutgoingSegments(); } /// /// Determines all outgoing straight segments at the given node. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort[] GetOutgoingStraightSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.OutgoingStraightSegments; } /// /// Determines all outgoing left segments at the given node. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort[] GetOutgoingLeftSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.OutgoingLeftSegments; } /// /// Determines all outgoing right segments at the given node. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// public ushort[] GetOutgoingRightSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.OutgoingRightSegments; } /// /// Determines the number of segments connected to the managed segment at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// number of connected segments at the given node public int CountOtherSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NumConnectedSegments; } /// /// Determines the number of left segments connected to the managed segment at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// number of connected left segments at the given node public int CountLeftSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NumLeftSegments; } /// /// Determines the number of right segments connected to the managed segment at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// number of connected right segments at the given node public int CountRightSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NumRightSegments; } /// /// Determines the number of straight segments connected to the managed segment at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// number of connected straight segments at the given node public int CountStraightSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NumStraightSegments; } /// /// Determines the number of incoming segments connected to the managed segment at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// number of connected incoming left segments at the given node public int CountIncomingSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NumIncomingSegments; } /// /// Determines the number of incoming left segments connected to the managed segment at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// number of connected incoming left segments at the given node public int CountIncomingLeftSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NumIncomingLeftSegments; } /// /// Determines the number of incoming right segments connected to the managed segment at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// number of connected incoming right segments at the given node public int CountIncomingRightSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NumIncomingRightSegments; } /// /// Determines the number of incoming straight segments connected to the managed segment at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// number of connected incoming straight segments at the given node public int CountIncomingStraightSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NumIncomingStraightSegments; } /// /// Determines the number of outgoing segments connected to the managed segment at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// number of connected incoming left segments at the given node public int CountOutgoingSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NumOutgoingSegments; } /// /// Determines the number of outgoing left segments connected to the managed segment at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// number of connected outgoing left segments at the given node public int CountOutgoingLeftSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NumOutgoingLeftSegments; } /// /// Determines the number of outgoing right segments connected to the managed segment at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// number of connected outgoing right segments at the given node public int CountOutgoingRightSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NumOutgoingRightSegments; } /// /// Determines the number of outgoing straight segments connected to the managed segment at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// number of connected outgoing straight segments at the given node public int CountOutgoingStraightSegments(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.NumOutgoingStraightSegments; } /// /// Determines if the managed segment is connected to left segments at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is connected to left segments at the given node, else false. public bool HasLeftSegment(bool startNode) { return CountLeftSegments(startNode) > 0; } /// /// Determines if the managed segment is connected to right segments at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is connected to right segments at the given node, else false. public bool HasRightSegment(bool startNode) { return CountRightSegments(startNode) > 0; } /// /// Determines if the managed segment is connected to straight segments at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is connected to straight segments at the given node, else false. public bool HasStraightSegment(bool startNode) { return CountStraightSegments(startNode) > 0; } /// /// Determines if the managed segment is connected to incoming left segments at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is connected to incoming left segments at the given node, else false. public bool HasIncomingLeftSegment(bool startNode) { return CountIncomingLeftSegments(startNode) > 0; } /// /// Determines if the managed segment is connected to incoming right segments at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is connected to incoming right segments at the given node, else false. public bool HasIncomingRightSegment(bool startNode) { return CountIncomingRightSegments(startNode) > 0; } /// /// Determines if the managed segment is connected to incoming straight segments at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is connected to incoming straight segments at the given node, else false. public bool HasIncomingStraightSegment(bool startNode) { return CountIncomingStraightSegments(startNode) > 0; } /// /// Determines if the managed segment is connected to outgoing left segments at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is connected to outgoing left segments at the given node, else false. public bool HasOutgoingLeftSegment(bool startNode) { return CountOutgoingLeftSegments(startNode) > 0; } /// /// Determines if the managed segment is connected to outgoing right segments at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is connected to outgoing right segments at the given node, else false. public bool HasOutgoingRightSegment(bool startNode) { return CountOutgoingRightSegments(startNode) > 0; } /// /// Determines if the managed segment is connected to outgoing straight segments at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is connected to outgoing straight segments at the given node, else false. public bool HasOutgoingStraightSegment(bool startNode) { return CountOutgoingStraightSegments(startNode) > 0; } /// /// Determines if, according to the stored geometry data, the given segment is connected to the managed segment and is a left segment at the given node relatively to the managed segment. /// /// A segment geometry verification is not performed. /// /// other segment that ought to be left, relatively to the managed segment /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if the other segment is, according to the stored geometry data, connected on the left-hand side of the managed segment at the given node public bool IsLeftSegment(ushort toSegmentId, bool startNode) { if (!IsValid(toSegmentId)) return false; if (toSegmentId == SegmentId) return false; SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; bool contains = false; foreach (ushort segId in endGeometry.LeftSegments) if (segId == toSegmentId) { contains = true; break; } return contains; } /// /// Determines if, according to the stored geometry data, the given segment is connected to the managed segment and is a right segment at the given node relatively to the managed segment. /// /// A segment geometry verification is not performed. /// /// other segment that ought to be right, relatively to the managed segment /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if the other segment is, according to the stored geometry data, connected on the right-hand side of the managed segment at the given node public bool IsRightSegment(ushort toSegmentId, bool startNode) { if (!IsValid(toSegmentId)) return false; if (toSegmentId == SegmentId) return false; SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.IsRightSegment(toSegmentId); } /// /// Determines if, according to the stored geometry data, the given segment is connected to the managed segment and is a straight segment at the given node relatively to the managed segment. /// /// A segment geometry verification is not performed. /// /// other segment that ought to be straight, relatively to the managed segment /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if the other segment is, according to the stored geometry data, connected straight-wise to the managed segment at the given node public bool IsStraightSegment(ushort toSegmentId, bool startNode) { if (!IsValid(toSegmentId)) return false; if (toSegmentId == SegmentId) return false; SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; bool contains = false; foreach (ushort segId in endGeometry.StraightSegments) if (segId == toSegmentId) { contains = true; break; } return contains; } /// /// Determines if, according to the stored geometry data, the given segment is connected to the managed segment and is a left segment at the given node relatively to the managed segment. /// /// A segment geometry verification is not performed. /// /// other segment that ought to be left, relatively to the managed segment /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if the other segment is, according to the stored geometry data, connected on the left-hand side of the managed segment at the given node public bool IsIncomingLeftSegment(ushort toSegmentId, bool startNode) { if (!IsValid(toSegmentId)) return false; if (toSegmentId == SegmentId) return false; SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; bool contains = false; foreach (ushort segId in endGeometry.IncomingLeftSegments) if (segId == toSegmentId) { contains = true; break; } return contains; } /// /// Determines if, according to the stored geometry data, the given segment is connected to the managed segment and is a right segment at the given node relatively to the managed segment. /// /// A segment geometry verification is not performed. /// /// other segment that ought to be right, relatively to the managed segment /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if the other segment is, according to the stored geometry data, connected on the right-hand side of the managed segment at the given node public bool IsIncomingRightSegment(ushort toSegmentId, bool startNode) { if (!IsValid(toSegmentId)) return false; if (toSegmentId == SegmentId) return false; SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; bool contains = false; foreach (ushort segId in endGeometry.IncomingRightSegments) if (segId == toSegmentId) { contains = true; break; } return contains; } /// /// Determines if, according to the stored geometry data, the given segment is connected to the managed segment and is a straight segment at the given node relatively to the managed segment. /// /// A segment geometry verification is not performed. /// /// other segment that ought to be straight, relatively to the managed segment /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if the other segment is, according to the stored geometry data, connected straight-wise to the managed segment at the given node public bool IsIncomingStraightSegment(ushort toSegmentId, bool startNode) { if (!IsValid(toSegmentId)) return false; if (toSegmentId == SegmentId) return false; SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; bool contains = false; foreach (ushort segId in endGeometry.IncomingStraightSegments) if (segId == toSegmentId) { contains = true; break; } return contains; } /// /// Determines if, according to the stored geometry data, the managed segment is only connected to highways at the given node. /// /// A segment geometry verification is not performed. /// /// true, if, according to the stored geometry data, the managed segment is only connected to highways at the given node, false otherwise public bool HasOnlyHighways(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.OnlyHighways; } /// /// Determines if, according to the stored geometry data, the managed segment is a one-way road. /// /// A segment geometry verification is not performed. /// /// true, if, according to the stored geometry data, the managed segment is a one-way road, false otherwise public bool IsOneWay() { return oneWay; } /// /// Determines if, according to the stored geometry data, the managed segment is a highway. /// /// A segment geometry verification is not performed. /// /// true, if, according to the stored geometry data, the managed segment is a highway, false otherwise public bool IsHighway() { return highway; } /// /// Determines if, according to the stored data, the managed segment has a buslane. /// /// public bool HasBusLane() { return buslane; } /// /// Determines if, according to the stored geometry data, the managed segment is an outgoing one-way road at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is an outgoing one-way road at the given node, false otherwise public bool IsOutgoingOneWay(bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.OutgoingOneWay; } /// /// Determines if, according to the stored geometry data, the managed segment is an incoming one-way road at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is an incoming one-way road at the given node, false otherwise public bool IsIncomingOneWay(bool startNode) { return (IsOneWay() && !IsOutgoingOneWay(startNode)); } /// /// Determines if, according to the stored geometry data, the managed segment is an incoming road at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is an incoming road at the given node, false otherwise public bool IsIncoming(bool startNode) { return !IsOutgoingOneWay(startNode); } /// /// Determines if, according to the stored geometry data, the managed segment is an outgoing road at the given node. /// /// A segment geometry verification is not performed. /// /// defines if the segment should be checked at the start node (true) or end node (false) /// true, if, according to the stored geometry data, the managed segment is an outgoing road at the given node, false otherwise public bool IsOutgoing(bool startNode) { return !IsIncomingOneWay(startNode); } /// /// Determines the relative direction of the other segment relatively to the managed segment at the given node, according to the stored geometry information. /// /// A segment geometry verification is not performed. /// /// other segment /// defines if the segment should be checked at the start node (true) or end node (false) /// relative direction of the other segment relatively to the managed segment at the given node public ArrowDirection GetDirection(ushort otherSegmentId, bool startNode) { SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; return endGeometry.GetDirection(otherSegmentId); } /// /// Determines if highway merging/splitting rules are activated at the managed segment for the given node. /// /// A segment geometry verification is not performed. /// /// /// [Obsolete] public bool AreHighwayRulesEnabled(bool startNode) { if (!Options.highwayRules) return false; if (!IsIncomingOneWay(startNode)) return false; if (!(Singleton.instance.m_segments.m_buffer[SegmentId].Info.m_netAI is RoadBaseAI)) return false; if (!((RoadBaseAI)Singleton.instance.m_segments.m_buffer[SegmentId].Info.m_netAI).m_highwayRules) return false; SegmentEndGeometry endGeometry = startNode ? startNodeGeometry : endNodeGeometry; if (endGeometry.NumConnectedSegments <= 1) return false; bool nextAreOnlyOneWayHighways = true; foreach (ushort otherSegmentId in endGeometry.ConnectedSegments) { if (Singleton.instance.m_segments.m_buffer[otherSegmentId].Info.m_netAI is RoadBaseAI) { if (! SegmentGeometry.Get(otherSegmentId).IsOneWay() || !((RoadBaseAI)Singleton.instance.m_segments.m_buffer[otherSegmentId].Info.m_netAI).m_highwayRules) { nextAreOnlyOneWayHighways = false; break; } } else { nextAreOnlyOneWayHighways = false; break; } } return nextAreOnlyOneWayHighways; } /// /// Calculates if the given segment is an outgoing one-way road at the given node. /// /// segment to check /// node the given segment shall be checked at /// true, if the given segment is an outgoing one-way road at the given node, false otherwise internal static bool calculateIsOutgoingOneWay(ushort segmentId, ushort nodeId) { // TODO move to SegmentEnd if (!IsValid(segmentId)) return false; var instance = Singleton.instance; var info = instance.m_segments.m_buffer[segmentId].Info; NetInfo.Direction dir = Constants.ServiceFactory.NetService.GetFinalSegmentEndDirection(segmentId, ref instance.m_segments.m_buffer[segmentId], instance.m_segments.m_buffer[segmentId].m_startNode == nodeId); var laneId = instance.m_segments.m_buffer[segmentId].m_lanes; var laneIndex = 0; while (laneIndex < info.m_lanes.Length && laneId != 0u) { bool validLane = (info.m_lanes[laneIndex].m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (info.m_lanes[laneIndex].m_vehicleType & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail)) != VehicleInfo.VehicleType.None; // TODO the lane types and vehicle types should be specified to make it clear which lanes we need to check if (validLane) { if ((info.m_lanes[laneIndex].m_finalDirection & dir) != NetInfo.Direction.None) { return false; } } laneId = instance.m_lanes.m_buffer[laneId].m_nextLane; laneIndex++; } return true; } /// /// Calculates if the given segment is a one-way road. /// /// true, if the managed segment is a one-way road, false otherwise private static bool calculateIsOneWay(ushort segmentId) { if (!IsValid(segmentId)) return false; var instance = Singleton.instance; var info = instance.m_segments.m_buffer[segmentId].Info; var hasForward = false; var hasBackward = false; var laneId = instance.m_segments.m_buffer[segmentId].m_lanes; var laneIndex = 0; while (laneIndex < info.m_lanes.Length && laneId != 0u) { bool validLane = (info.m_lanes[laneIndex].m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (info.m_lanes[laneIndex].m_vehicleType & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail)) != VehicleInfo.VehicleType.None; // TODO the lane types and vehicle types should be specified to make it clear which lanes we need to check if (validLane) { if ((info.m_lanes[laneIndex].m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None) { hasForward = true; } if ((info.m_lanes[laneIndex].m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None) { hasBackward = true; } if (hasForward && hasBackward) { return false; } } laneId = instance.m_lanes.m_buffer[laneId].m_nextLane; laneIndex++; } return true; } /// /// Calculates if the given segment has a buslane. /// /// segment to check /// true, if the given segment has a buslane, false otherwise internal static bool calculateHasBusLane(ushort segmentId) { if (!IsValid(segmentId)) return false; bool ret = false; Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate (ushort segId, ref NetSegment segment) { ret = calculateHasBusLane(segment.Info); return true; }); return ret; } /// /// Calculates if the given segment info describes a segment having a bus lane /// /// /// internal static bool calculateHasBusLane(NetInfo segmentInfo) { for (int laneIndex = 0; laneIndex < segmentInfo.m_lanes.Length; ++laneIndex) { if (segmentInfo.m_lanes[laneIndex].m_laneType == NetInfo.LaneType.TransportVehicle && (segmentInfo.m_lanes[laneIndex].m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) { return true; } } return false; } internal static void calculateOneWayAtNode(ushort segmentId, ushort nodeId, out bool isOneway, out bool isOutgoingOneWay) { if (!IsValid(segmentId)) { isOneway = false; isOutgoingOneWay = false; return; } var instance = Singleton.instance; var info = instance.m_segments.m_buffer[segmentId].Info; var dir = NetInfo.Direction.Forward; if (instance.m_segments.m_buffer[segmentId].m_startNode == nodeId) dir = NetInfo.Direction.Backward; var dir2 = ((instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection(dir); var hasForward = false; var hasBackward = false; isOutgoingOneWay = true; var laneId = instance.m_segments.m_buffer[segmentId].m_lanes; var laneIndex = 0; while (laneIndex < info.m_lanes.Length && laneId != 0u) { bool validLane = (info.m_lanes[laneIndex].m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (info.m_lanes[laneIndex].m_vehicleType & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail)) != VehicleInfo.VehicleType.None; // TODO the lane types and vehicle types should be specified to make it clear which lanes we need to check if (validLane) { if ((info.m_lanes[laneIndex].m_finalDirection & dir2) != NetInfo.Direction.None) { isOutgoingOneWay = false; } if ((info.m_lanes[laneIndex].m_direction & NetInfo.Direction.Forward) != NetInfo.Direction.None) { hasForward = true; } if ((info.m_lanes[laneIndex].m_direction & NetInfo.Direction.Backward) != NetInfo.Direction.None) { hasBackward = true; } } laneId = instance.m_lanes.m_buffer[laneId].m_nextLane; laneIndex++; } isOneway = !(hasForward && hasBackward); if (!isOneway) isOutgoingOneWay = false; } /// /// Calculates if the given segment is a highway /// /// /// internal static bool calculateIsHighway(ushort segmentId) { if (!IsValid(segmentId)) return false; bool ret = false; Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate (ushort segId, ref NetSegment segment) { ret = calculateIsHighway(segment.Info); return true; }); return ret; } /// /// Calculates if the given segment info describes a highway segment /// /// /// internal static bool calculateIsHighway(NetInfo segmentInfo) { if (segmentInfo.m_netAI is RoadBaseAI) return ((RoadBaseAI)segmentInfo.m_netAI).m_highwayRules; return false; } /// /// Clears the segment geometry data. /// private void Cleanup() { highway = false; oneWay = false; buslane = false; try { //Monitor.Enter(Lock); startNodeGeometry.Cleanup(); endNodeGeometry.Cleanup(); // reset highway lane arrows //Flags.removeHighwayLaneArrowFlagsAtSegment(SegmentId); // TODO refactor // clear default vehicle type cache VehicleRestrictionsManager.Instance.ClearCache(SegmentId); } finally { //Monitor.Exit(Lock); } } public bool Equals(SegmentGeometry otherSegGeo) { if (otherSegGeo == null) { return false; } return SegmentId == otherSegGeo.SegmentId; } public override bool Equals(object other) { if (other == null) { return false; } if (!(other is SegmentGeometry)) { return false; } return Equals((SegmentGeometry)other); } public override int GetHashCode() { int prime = 31; int result = 1; result = prime * result + SegmentId.GetHashCode(); return result; } // static methods static SegmentGeometry() { segmentGeometries = new SegmentGeometry[NetManager.MAX_SEGMENT_COUNT]; } internal static void OnBeforeLoadData() { Log._Debug($"Building {segmentGeometries.Length} segment geometries..."); for (int i = 0; i < segmentGeometries.Length; ++i) { segmentGeometries[i] = new SegmentGeometry((ushort)i); } for (int i = 0; i < segmentGeometries.Length; ++i) { segmentGeometries[i].Recalculate(GeometryCalculationMode.Init); } /*for (int i = 0; i < segmentGeometries.Length; ++i) { segmentGeometries[i].RecalculateLaneGeometries(GeometryCalculationMode.Init); }*/ Log._Debug($"Calculated segment geometries."); } internal static void OnBeforeSaveData() { /*Log._Debug($"Recalculating all segment geometries..."); for (int i = 0; i < segmentGeometries.Length; ++i) { segmentGeometries[i].Recalculate(false); } Log._Debug($"Calculated segment geometries.");*/ } public static SegmentGeometry Get(ushort segmentId, bool ignoreInvalid=false) { if (segmentGeometries == null) { return null; } SegmentGeometry segGeo = segmentGeometries[segmentId]; if (segGeo == null || (! ignoreInvalid && ! segGeo.valid)) { return null; } return segGeo; } /*private void RecalculateLaneGeometries() { RebuildLaneGeometries(); foreach (LaneGeometry laneGeo in LaneGeometries) { laneGeo.Recalculate(); } }*/ /*private void RebuildLaneGeometries() { LaneGeometry[] newLaneGeos = null; if (IsValid()) { Constants.ServiceFactory.NetService.ProcessSegment(SegmentId, delegate (ushort segmentId, ref NetSegment segment) { newLaneGeos = new LaneGeometry[segment.Info.m_lanes.Length]; return true; }); int minNum = 0; if (LaneGeometries != null) { minNum = Math.Min(newLaneGeos.Length, LaneGeometries.Length); for (int i = 0; i < minNum; ++i) { newLaneGeos[i] = LaneGeometries[i]; } } for (int i = minNum; i < newLaneGeos.Length; ++i) { newLaneGeos[i] = new LaneGeometry(SegmentId, i); } } LaneGeometries = newLaneGeos; }*/ /*private void NotifyObservers() { List> myObservers = new List>(observers); // in case somebody unsubscribes while iterating over subscribers foreach (IObserver observer in myObservers) { try { observer.OnUpdate(this); } catch (Exception e) { Log.Error($"SegmentGeometry.NotifyObservers: An exception occured while notifying an observer of segment {SegmentId}: {e}"); } } }*/ } } ================================================ FILE: TLM/TLM/Geometry/README.md ================================================ # TM:PE -- /Custom/Geometry Classes for pre-calculated properties of segments, segment ends and nodes. ## Terminology Although the term *geometry* is being used, we are not storing angles and lengths of segments. We are instead storing relational information of two or more segments that are connected at a node (e.g. segment X is left of segment Y). The term *segment end* represents the directional component of traffic at one segment. For example, a segment end at segment X and node Y represent the set of all lanes that allow traffic to flow from X to Y. Thus, for each segment that is not a one-way street and is connected to two nodes, two separate segment ends exist: One describing the segment's part connected to its start node and the other segment end describes the situation at the segment's end node. *Incoming* traffic at segment ends always flows to the node where *outgoing* traffic flows away from the node. ## Classes - **GeometryCalculationMode**: Controls propagation when performing geometry calculations. - **NodeGeometry**: Holds all connected segment end geometries. - **SegmentEndGeometry**: Stores information about a segment at one connected node. - **SegmentEndId**: Abstract class to represent segment ends - **SegmentGeometry**: Stores general information about a segment and holds references to both segment ends. ================================================ FILE: TLM/TLM/LoadingExtension.cs ================================================ using System; using System.Reflection; using ColossalFramework; using ICities; using TrafficManager.Custom.AI; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using TrafficManager.UI; using UnityEngine; using Object = UnityEngine.Object; using System.Collections.Generic; using TrafficManager.State; using ColossalFramework.UI; using ColossalFramework.Math; using TrafficManager.Custom.PathFinding; using TrafficManager.Util; using TrafficManager.Custom.Manager; using TrafficManager.Manager; using CSUtil.Commons; using TrafficManager.Custom.Data; using TrafficManager.Manager.Impl; namespace TrafficManager { public class LoadingExtension : LoadingExtensionBase { public class Detour { public MethodInfo OriginalMethod; public MethodInfo CustomMethod; public RedirectCallsState Redirect; public Detour(MethodInfo originalMethod, MethodInfo customMethod) { this.OriginalMethod = originalMethod; this.CustomMethod = customMethod; this.Redirect = RedirectionHelper.RedirectCalls(originalMethod, customMethod); } } //public static LoadingExtension Instance; public static bool IsPathManagerReplaced { get; private set; } = false; public static bool IsRainfallLoaded { get; private set; } = false; public static bool IsRushHourLoaded { get; private set; } = false; public static CustomPathManager CustomPathManager { get; set; } public static bool DetourInited { get; set; } public static List Detours { get; set; } //public static TrafficManagerMode ToolMode { get; set; } //public static TrafficManagerTool TrafficManagerTool { get; set; } #if !TAM public static UIBase BaseUI { get; private set; } #endif public static UITransportDemand TransportDemandUI { get; private set; } public static List RegisteredManagers { get; private set; } public static bool IsGameLoaded { get; private set; } = false; public LoadingExtension() { } public void revertDetours() { if (DetourInited) { Log.Info("Revert detours"); Detours.Reverse(); foreach (Detour d in Detours) { RedirectionHelper.RevertRedirect(d.OriginalMethod, d.Redirect); } DetourInited = false; Detours.Clear(); Log.Info("Reverting detours finished."); } } public void initDetours() { // TODO realize detouring with annotations if (!DetourInited) { Log.Info("Init detours"); bool detourFailed = false; // REVERSE REDIRECTION Log.Info("Reverse-Redirection CustomVehicleManager::ReleaseVehicleImplementation calls"); try { Detours.Add(new Detour(typeof(CustomVehicleManager).GetMethod("ReleaseVehicleImplementation", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), }, null), typeof(VehicleManager).GetMethod("ReleaseVehicleImplementation", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomVehicleManager::ReleaseVehicleImplementation"); detourFailed = true; } #if DEBUGBUSBUG // TODO remove Log.Info("Reverse-Redirection CustomNetManager::FinalizeNode calls"); try { Detours.Add(new Detour(typeof(CustomNetManager).GetMethod("FinalizeNode", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (NetNode).MakeByRefType(), }, null), typeof(NetManager).GetMethod("FinalizeNode", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (NetNode).MakeByRefType(), }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomNetManager::FinalizeNode"); detourFailed = true; } // TODO remove Log.Info("Reverse-Redirection CustomNetManager::InitializeNode calls"); try { Detours.Add(new Detour(typeof(CustomNetManager).GetMethod("InitializeNode", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (NetNode).MakeByRefType(), }, null), typeof(NetManager).GetMethod("InitializeNode", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (NetNode).MakeByRefType(), }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomNetManager::InitializeNode"); detourFailed = true; } // TODO remove Log.Info("Reverse-Redirection CustomNetManager::InitializeSegment calls"); try { Detours.Add(new Detour(typeof(CustomNetManager).GetMethod("InitializeSegment", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (NetSegment).MakeByRefType(), }, null), typeof(NetManager).GetMethod("InitializeSegment", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (NetSegment).MakeByRefType(), }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomNetManager::InitializeSegment"); detourFailed = true; } #endif Log.Info("Reverse-Redirection CustomCitizenManager::ReleaseCitizenInstanceImplementation calls"); try { Detours.Add(new Detour(typeof(CustomCitizenManager).GetMethod("ReleaseCitizenInstanceImplementation", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), }, null), typeof(CitizenManager).GetMethod("ReleaseCitizenInstanceImplementation", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomCitizenManager::ReleaseCitizenInstanceImplementation"); detourFailed = true; } Log.Info("Reverse-Redirection CustomCitizenManager::ReleaseCitizenImplementation calls"); try { Detours.Add(new Detour(typeof(CustomCitizenManager).GetMethod("ReleaseCitizenImplementation", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (uint), typeof (Citizen).MakeByRefType(), }, null), typeof(CitizenManager).GetMethod("ReleaseCitizenImplementation", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (uint), typeof (Citizen).MakeByRefType(), }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomCitizenManager::ReleaseCitizenImplementation"); detourFailed = true; } Log.Info("Reverse-Redirection ResidentAI::GetTaxiProbability calls"); try { Detours.Add(new Detour(typeof(CustomResidentAI).GetMethod("GetTaxiProbability", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (Citizen.AgeGroup) }, null), typeof(ResidentAI).GetMethod("GetTaxiProbability", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (Citizen.AgeGroup) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect ResidentAI::GetTaxiProbability"); detourFailed = true; } Log.Info("Reverse-Redirection ResidentAI::GetBikeProbability calls"); try { Detours.Add(new Detour(typeof(CustomResidentAI).GetMethod("GetBikeProbability", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (Citizen.AgeGroup) }, null), typeof(ResidentAI).GetMethod("GetBikeProbability", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (Citizen.AgeGroup) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect ResidentAI::GetBikeProbability"); detourFailed = true; } Log.Info("Reverse-Redirection ResidentAI::GetCarProbability calls"); try { Detours.Add(new Detour(typeof(CustomResidentAI).GetMethod("GetCarProbability", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (Citizen.AgeGroup) }, null), typeof(ResidentAI).GetMethod("GetCarProbability", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (Citizen.AgeGroup) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect ResidentAI::GetCarProbability"); detourFailed = true; } Log.Info("Reverse-Redirection ResidentAI::GetElectricCarProbability calls"); try { Detours.Add(new Detour(typeof(CustomResidentAI).GetMethod("GetElectricCarProbability", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (Citizen.AgePhase) }, null), typeof(ResidentAI).GetMethod("GetElectricCarProbability", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (Citizen.AgePhase) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect ResidentAI::GetElectricCarProbability"); detourFailed = true; } Log.Info("Reverse-Redirection TouristAI::GetTaxiProbability calls"); try { Detours.Add(new Detour( typeof(CustomTouristAI).GetMethod("GetTaxiProbability", BindingFlags.NonPublic | BindingFlags.Instance), typeof(TouristAI).GetMethod("GetTaxiProbability", BindingFlags.NonPublic | BindingFlags.Instance))); } catch (Exception) { Log.Error("Could not reverse-redirect TouristAI::GetTaxiProbability"); detourFailed = true; } Log.Info("Reverse-Redirection TouristAI::GetBikeProbability calls"); try { Detours.Add(new Detour( typeof(CustomTouristAI).GetMethod("GetBikeProbability", BindingFlags.NonPublic | BindingFlags.Instance), typeof(TouristAI).GetMethod("GetBikeProbability", BindingFlags.NonPublic | BindingFlags.Instance))); } catch (Exception) { Log.Error("Could not reverse-redirect TouristAI::GetBikeProbability"); detourFailed = true; } Log.Info("Reverse-Redirection TouristAI::GetCarProbability calls"); try { Detours.Add(new Detour( typeof(CustomTouristAI).GetMethod("GetCarProbability", BindingFlags.NonPublic | BindingFlags.Instance), typeof(TouristAI).GetMethod("GetCarProbability", BindingFlags.NonPublic | BindingFlags.Instance))); } catch (Exception) { Log.Error("Could not reverse-redirect TouristAI::GetCarProbability"); detourFailed = true; } Log.Info("Reverse-Redirection TouristAI::GetElectricCarProbability calls"); try { Detours.Add(new Detour( typeof(CustomTouristAI).GetMethod("GetElectricCarProbability", BindingFlags.NonPublic | BindingFlags.Instance), typeof(TouristAI).GetMethod("GetElectricCarProbability", BindingFlags.NonPublic | BindingFlags.Instance))); } catch (Exception) { Log.Error("Could not reverse-redirect TouristAI::GetElectricCarProbability"); detourFailed = true; } Log.Info("Reverse-Redirection TouristAI::GetCamperProbability calls"); try { Detours.Add(new Detour(typeof(CustomTouristAI).GetMethod("GetCamperProbability", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (Citizen.Wealth) }, null), typeof(TouristAI).GetMethod("GetCamperProbability", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (Citizen.Wealth) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect TouristAI::GetCamperProbability"); detourFailed = true; } Log.Info("Reverse-Redirection HumanAI::GetBuildingTargetPosition calls"); try { Detours.Add(new Detour(typeof(CustomHumanAI).GetMethod("GetBuildingTargetPosition", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (float) }, null), typeof(HumanAI).GetMethod("GetBuildingTargetPosition", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (float) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect HumanAI::GetBuildingTargetPosition"); detourFailed = true; } Log.Info("Reverse-Redirection HumanAI::PathfindFailure calls"); try { Detours.Add(new Detour(typeof(CustomHumanAI).GetMethod("PathfindFailure", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType() }, null), typeof(HumanAI).GetMethod("PathfindFailure", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect HumanAI::PathfindFailure"); detourFailed = true; } Log.Info("Reverse-Redirection HumanAI::PathfindSuccess calls"); try { Detours.Add(new Detour(typeof(CustomHumanAI).GetMethod("PathfindSuccess", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType() }, null), typeof(HumanAI).GetMethod("PathfindSuccess", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect HumanAI::PathfindSuccess"); detourFailed = true; } Log.Info("Reverse-Redirection HumanAI::Spawn calls"); try { Detours.Add(new Detour(typeof(CustomHumanAI).GetMethod("Spawn", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType() }, null), typeof(HumanAI).GetMethod("Spawn", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect HumanAI::Spawn"); detourFailed = true; } Log.Info("Reverse-Redirection HumanAI::WaitTouristVehicle calls"); try { Detours.Add(new Detour(typeof(CustomHumanAI).GetMethod("WaitTouristVehicle", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (ushort) }, null), typeof(HumanAI).GetMethod("WaitTouristVehicle", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (ushort) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect HumanAI::WaitTouristVehicle"); detourFailed = true; } Log.Info("Reverse-Redirection PassengerCarAI::FindParkingSpaceRoadSide calls"); try { Detours.Add(new Detour(typeof(CustomPassengerCarAI).GetMethod("FindParkingSpaceRoadSide", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (ushort), typeof (Vector3), typeof (float), typeof (float), typeof (Vector3).MakeByRefType(), typeof (Quaternion).MakeByRefType(), typeof (float).MakeByRefType() }, null), typeof(PassengerCarAI).GetMethod("FindParkingSpaceRoadSide", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (ushort), typeof (Vector3), typeof (float), typeof (float), typeof (Vector3).MakeByRefType(), typeof (Quaternion).MakeByRefType(), typeof (float).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect PassengerCarAI::FindParkingSpaceRoadSide"); detourFailed = true; } /*Log.Info("Reverse-Redirection PassengerCarAI::FindParkingSpaceBuilding calls"); try { Detours.Add(new Detour(typeof(CustomPassengerCarAI).GetMethod("FindParkingSpaceBuilding", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (ushort), typeof (Vector3), typeof (float), typeof (float), typeof (float), typeof (Vector3).MakeByRefType(), typeof (Quaternion).MakeByRefType() }, null), typeof(PassengerCarAI).GetMethod("FindParkingSpaceBuilding", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (ushort), typeof (Vector3), typeof (float), typeof (float), typeof (float), typeof (Vector3).MakeByRefType(), typeof (Quaternion).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect PassengerCarAI::FindParkingSpaceBuilding"); detourFailed = true; }*/ Log.Info("Reverse-Redirection PassengerCarAI::FindParkingSpace calls"); try { Detours.Add(new Detour(typeof(CustomPassengerCarAI).GetMethod("FindParkingSpace", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (bool), typeof (ushort), typeof (Vector3), typeof (Vector3), typeof (ushort), typeof (float), typeof (float), typeof (Vector3).MakeByRefType(), typeof (Quaternion).MakeByRefType(), typeof (float).MakeByRefType() }, null), typeof(PassengerCarAI).GetMethod("FindParkingSpace", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (bool), typeof (ushort), typeof (Vector3), typeof (Vector3), typeof (ushort), typeof (float), typeof (float), typeof (Vector3).MakeByRefType(), typeof (Quaternion).MakeByRefType(), typeof (float).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect PassengerCarAI::FindParkingSpace"); detourFailed = true; } Log.Info("Reverse-Redirection PassengerCarAI::FindParkingSpaceProp calls"); try { Detours.Add(new Detour(typeof(CustomPassengerCarAI).GetMethod("FindParkingSpaceProp", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (bool), typeof (ushort), typeof (PropInfo), typeof (Vector3), typeof (float), typeof (bool), typeof (Vector3), typeof (float), typeof (float), typeof (float).MakeByRefType(), typeof (Vector3).MakeByRefType(), typeof (Quaternion).MakeByRefType() }, null), typeof(PassengerCarAI).GetMethod("FindParkingSpaceProp", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (bool), typeof (ushort), typeof (PropInfo), typeof (Vector3), typeof (float), typeof (bool), typeof (Vector3), typeof (float), typeof (float), typeof (float).MakeByRefType(), typeof (Vector3).MakeByRefType(), typeof (Quaternion).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect PassengerCarAI::FindParkingSpaceProp"); detourFailed = true; } Log.Info("Reverse-Redirection CarAI::CheckOverlap calls"); try { Detours.Add(new Detour(typeof(CustomCarAI).GetMethod("CheckOverlap", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (Segment3), typeof (ushort), typeof (float), }, null), typeof(CarAI).GetMethod("CheckOverlap", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (Segment3), typeof (ushort), typeof (float), }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CarAI::CheckOverlap"); detourFailed = true; } Log.Info("Reverse-Redirection CarAI::CheckOtherVehicle calls"); try { Detours.Add(new Detour(typeof(CustomCarAI).GetMethod("CheckOtherVehicle", BindingFlags.NonPublic | BindingFlags.Static), typeof(CarAI).GetMethod("CheckOtherVehicle", BindingFlags.NonPublic | BindingFlags.Static))); } catch (Exception) { Log.Error("Could not reverse-redirect CarAI::CheckOtherVehicle"); detourFailed = true; } Log.Info("Reverse-Redirection CarAI::CheckCitizen calls"); try { Detours.Add(new Detour(typeof(CustomCarAI).GetMethod("CheckCitizen", BindingFlags.NonPublic | BindingFlags.Static), typeof(CarAI).GetMethod("CheckCitizen", BindingFlags.NonPublic | BindingFlags.Static))); } catch (Exception) { Log.Error("Could not reverse-redirect CarAI::CheckCitizen"); detourFailed = true; } Log.Info("Reverse-Redirection TrainAI::InitializePath calls"); try { Detours.Add(new Detour(typeof(CustomTrainAI).GetMethod("InitializePath", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }, null), typeof(TrainAI).GetMethod("InitializePath", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect TrainAI::InitializePath"); detourFailed = true; } Log.Info("Reverse-Redirection TramBaseAI::InitializePath calls"); try { Detours.Add(new Detour(typeof(CustomTramBaseAI).GetMethod("InitializePath", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }, null), typeof(TramBaseAI).GetMethod("InitializePath", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect TramBaseAI::InitializePath"); detourFailed = true; } Log.Info("Reverse-Redirection TramBaseAI::UpdatePathTargetPositions calls"); MethodInfo sourceMethod = null, targetMethod = null; try { sourceMethod = typeof(CustomTramBaseAI).GetMethod("InvokeUpdatePathTargetPositions", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof (TramBaseAI), typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (int).MakeByRefType(), typeof (int), typeof (int), typeof (float), typeof (float) }, null); targetMethod = typeof(TramBaseAI).GetMethod("UpdatePathTargetPositions", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (int).MakeByRefType(), typeof (int), typeof (int), typeof (float), typeof (float) }, null); Detours.Add(new Detour(sourceMethod, targetMethod)); } catch (Exception e) { Log.Error($"Could not reverse-redirect TramBaseAI::UpdatePathTargetPositions: {e} sourceMethod={sourceMethod} targetMethod={targetMethod}"); detourFailed = true; } Log.Info("Reverse-Redirection CustomTramBaseAI::GetMaxSpeed calls"); try { Detours.Add(new Detour(typeof(CustomTramBaseAI).GetMethod("GetMaxSpeed", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }, null), typeof(TramBaseAI).GetMethod("GetMaxSpeed", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomTramBaseAI::GetMaxSpeed"); detourFailed = true; } Log.Info("Reverse-Redirection CustomTramBaseAI::CalculateMaxSpeed calls"); try { Detours.Add(new Detour(typeof(CustomTramBaseAI).GetMethod("CalculateMaxSpeed", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (float), typeof (float), typeof (float) }, null), typeof(TramBaseAI).GetMethod("CalculateMaxSpeed", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (float), typeof (float), typeof (float) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomTramBaseAI::CalculateMaxSpeed"); detourFailed = true; } Log.Info("Reverse-Redirection CustomTrainAI::CheckOverlap calls (1)"); try { Detours.Add(new Detour(typeof(CustomTrainAI).GetMethod("CheckOverlap", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Segment3), typeof (ushort) }, null), typeof(TrainAI).GetMethod("CheckOverlap", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Segment3), typeof (ushort) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomRoadBaseAI::CheckOverlap (1)"); detourFailed = true; } Log.Info("Reverse-Redirection CustomTrainAI::CheckOverlap calls (2)"); try { Detours.Add(new Detour(typeof(CustomTrainAI).GetMethod("CheckOverlap", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Segment3), typeof (ushort), typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (bool).MakeByRefType(), typeof (Vector3), typeof (Vector3) }, null), typeof(TrainAI).GetMethod("CheckOverlap", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Segment3), typeof (ushort), typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (bool).MakeByRefType(), typeof (Vector3), typeof (Vector3) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomTrainAI::CheckOverlap (2)"); detourFailed = true; } Log.Info("Reverse-Redirection TrainAI::UpdatePathTargetPositions calls"); try { Detours.Add(new Detour(typeof(CustomTrainAI).GetMethod("InvokeUpdatePathTargetPositions", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof (TrainAI), typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (int).MakeByRefType(), typeof (int), typeof (int), typeof (float), typeof (float) }, null), typeof(TrainAI).GetMethod("UpdatePathTargetPositions", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (int).MakeByRefType(), typeof (int), typeof (int), typeof (float), typeof (float) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect TrainAI::UpdatePathTargetPositions"); detourFailed = true; } Log.Info("Reverse-Redirection CustomTrainAI::Reverse calls"); try { Detours.Add(new Detour(typeof(CustomTrainAI).GetMethod("Reverse", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }, null), typeof(TrainAI).GetMethod("Reverse", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomTrainAI::Reverse"); detourFailed = true; } Log.Info("Reverse-Redirection CustomTrainAI::GetMaxSpeed calls"); try { Detours.Add(new Detour(typeof(CustomTrainAI).GetMethod("GetMaxSpeed", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }, null), typeof(TrainAI).GetMethod("GetMaxSpeed", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomTrainAI::GetMaxSpeed"); detourFailed = true; } Log.Info("Reverse-Redirection CustomTrainAI::CalculateMaxSpeed calls"); try { Detours.Add(new Detour(typeof(CustomTrainAI).GetMethod("CalculateMaxSpeed", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (float), typeof (float), typeof (float) }, null), typeof(TrainAI).GetMethod("CalculateMaxSpeed", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (float), typeof (float), typeof (float) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomTrainAI::CalculateMaxSpeed"); detourFailed = true; } Log.Info("Reverse-Redirection CustomTransportLineAI::GetStopLane calls"); try { Detours.Add(new Detour(typeof(CustomTransportLineAI).GetMethod("GetStopLane", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (PathUnit.Position).MakeByRefType(), typeof (VehicleInfo.VehicleType) }, null), typeof(TransportLineAI).GetMethod("GetStopLane", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (PathUnit.Position).MakeByRefType(), typeof (VehicleInfo.VehicleType) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomTransportLineAI::GetStopLane"); detourFailed = true; } Log.Info("Reverse-Redirection CustomTransportLineAI::CheckSegmentProblems calls"); try { Detours.Add(new Detour(typeof(CustomTransportLineAI).GetMethod("CheckSegmentProblems", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (NetSegment).MakeByRefType() }, null), typeof(TransportLineAI).GetMethod("CheckSegmentProblems", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (NetSegment).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomTransportLineAI::CheckSegmentProblems"); detourFailed = true; } Log.Info("Reverse-Redirection CustomTransportLineAI::CheckNodeProblems calls"); try { Detours.Add(new Detour(typeof(CustomTransportLineAI).GetMethod("CheckNodeProblems", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (NetNode).MakeByRefType() }, null), typeof(TransportLineAI).GetMethod("CheckNodeProblems", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (NetNode).MakeByRefType() }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomTransportLineAI::CheckNodeProblems"); detourFailed = true; } Log.Info("Reverse-Redirection CustomVehicleAI::FindBestLane calls"); try { Detours.Add(new Detour(typeof(CustomVehicleAI).GetMethod("FindBestLane", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (PathUnit.Position) }, null), typeof(VehicleAI).GetMethod("FindBestLane", BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (PathUnit.Position) }, null))); } catch (Exception) { Log.Error("Could not reverse-redirect CustomVehicleAI::FindBestLane"); detourFailed = true; } // FORWARD REDIRECTION /*Log.Info("Redirecting NetAI::AfterSplitOrMove"); try { Detours.Add(new Detour( typeof(NetAI).GetMethod("AfterSplitOrMove"), typeof(CustomNetAI).GetMethod("CustomAfterSplitOrMove"))); } catch (Exception) { Log.Error("Could not redirect NetAI::AfterSplitOrMove."); detourFailed = true; }*/ Log.Info("Redirecting Vehicle AI Calculate Segment Calls (1)"); try { Detours.Add(new Detour(typeof(VehicleAI).GetMethod("CalculateSegmentPosition", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (PathUnit.Position), typeof (PathUnit.Position), typeof (uint), typeof (byte), typeof (PathUnit.Position), typeof (uint), typeof (byte), typeof (int), typeof (Vector3).MakeByRefType(), typeof (Vector3).MakeByRefType(), typeof (float).MakeByRefType() }, null), typeof(CustomVehicleAI).GetMethod("CustomCalculateSegmentPosition"))); } catch (Exception) { Log.Error("Could not redirect VehicleAI::CalculateSegmentPosition (1)."); detourFailed = true; } Log.Info("Redirecting Vehicle AI Calculate Segment Calls (2)"); try { Detours.Add(new Detour(typeof(VehicleAI).GetMethod("CalculateSegmentPosition", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (PathUnit.Position), typeof (uint), typeof (byte), typeof (Vector3).MakeByRefType(), typeof (Vector3).MakeByRefType(), typeof (float).MakeByRefType() }, null), typeof(CustomVehicleAI).GetMethod("CustomCalculateSegmentPositionPathFinder"))); } catch (Exception) { Log.Error("Could not redirect VehicleAI::CalculateSegmentPosition (2)."); detourFailed = true; } Log.Info("Redirecting VehicleAI::UpdatePathTargetPositions calls"); try { Detours.Add(new Detour(typeof(VehicleAI).GetMethod("UpdatePathTargetPositions", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (int).MakeByRefType(), typeof (int), typeof (float), typeof (float) }, null), typeof(CustomVehicleAI).GetMethod("CustomUpdatePathTargetPositions", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (int).MakeByRefType(), typeof (int), typeof (float), typeof (float) }, null))); } catch (Exception) { Log.Error("Could not redirect VehicleAI::UpdatePathTargetPositions."); detourFailed = true; } Log.Info("Redirection CitizenManager::ReleaseCitizenInstance calls"); try { Detours.Add(new Detour(typeof(CitizenManager).GetMethod("ReleaseCitizenInstance", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (ushort) }, null), typeof(CustomCitizenManager).GetMethod("CustomReleaseCitizenInstance"))); } catch (Exception) { Log.Error("Could not redirect CitizenManager::ReleaseCitizenInstance"); detourFailed = true; } Log.Info("Redirection CitizenManager::ReleaseCitizen calls"); try { Detours.Add(new Detour(typeof(CitizenManager).GetMethod("ReleaseCitizen", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (uint) }, null), typeof(CustomCitizenManager).GetMethod("CustomReleaseCitizen"))); } catch (Exception) { Log.Error("Could not redirect CitizenManager::ReleaseCitizen"); detourFailed = true; } Log.Info("Redirection VehicleManager::ReleaseVehicle calls"); try { Detours.Add(new Detour(typeof(VehicleManager).GetMethod("ReleaseVehicle", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (ushort) }, null), typeof(CustomVehicleManager).GetMethod("CustomReleaseVehicle"))); } catch (Exception) { Log.Error("Could not redirect VehicleManager::ReleaseVehicle"); detourFailed = true; } Log.Info("Redirection VehicleManager::CreateVehicle calls"); try { Detours.Add(new Detour(typeof(VehicleManager).GetMethod("CreateVehicle", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (ushort).MakeByRefType(), typeof (Randomizer).MakeByRefType(), typeof (VehicleInfo), typeof (Vector3), typeof (TransferManager.TransferReason), typeof (bool), typeof (bool) }, null), typeof(CustomVehicleManager).GetMethod("CustomCreateVehicle"))); } catch (Exception) { Log.Error("Could not redirect VehicleManager::CreateVehicle calls"); detourFailed = true; } Log.Info("Redirecting TramBaseAI Calculate Segment Calls (2)"); try { Detours.Add(new Detour(typeof(TramBaseAI).GetMethod("CalculateSegmentPosition", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (PathUnit.Position), typeof (uint), typeof (byte), typeof (Vector3).MakeByRefType(), typeof (Vector3).MakeByRefType(), typeof (float).MakeByRefType() }, null), typeof(CustomTramBaseAI).GetMethod("CustomCalculateSegmentPositionPathFinder"))); } catch (Exception) { Log.Error("Could not redirect TramBaseAI::CalculateSegmentPosition (2)."); detourFailed = true; } Log.Info("Redirecting RoadBaseAI::ClickNodeButton calls"); try { Detours.Add(new Detour( typeof(RoadBaseAI).GetMethod("ClickNodeButton"), typeof(CustomRoadAI).GetMethod("CustomClickNodeButton"))); } catch (Exception) { Log.Error("Could not redirect RoadBaseAI::ClickNodeButton"); detourFailed = true; } Log.Info("Redirecting RoadBaseAI::GetTrafficLightNodeState calls"); try { Detours.Add(new Detour(typeof(RoadBaseAI).GetMethod("GetTrafficLightNodeState", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof (ushort), typeof (NetNode).MakeByRefType(), typeof (ushort), typeof (NetSegment).MakeByRefType(), typeof (NetNode.Flags).MakeByRefType(), typeof (Color).MakeByRefType() }, null), typeof(CustomRoadAI).GetMethod("CustomGetTrafficLightNodeState"))); } catch (Exception) { Log.Error("Could not redirect RoadBaseAI::GetTrafficLightNodeState"); detourFailed = true; } //public static void CustomGetTrafficLightNodeState(ushort nodeID, ref NetNode nodeData, ushort segmentID, ref NetSegment segmentData, ref NetNode.Flags flags, ref Color color) { Log.Info("Redirecting RoadBaseAI.SimulationStep for nodes"); try { Detours.Add(new Detour(typeof(RoadBaseAI).GetMethod("SimulationStep", new[] { typeof(ushort), typeof(NetNode).MakeByRefType() }), typeof(CustomRoadAI).GetMethod("CustomNodeSimulationStep"))); } catch (Exception) { Log.Error("Could not redirect RoadBaseAI::SimulationStep."); detourFailed = true; } Log.Info("Redirecting RoadBaseAI.SimulationStep for segments"); try { Detours.Add(new Detour(typeof(RoadBaseAI).GetMethod("SimulationStep", new[] { typeof(ushort), typeof(NetSegment).MakeByRefType() }), typeof(CustomRoadAI).GetMethod("CustomSegmentSimulationStep"))); } catch (Exception) { Log.Error("Could not redirect RoadBaseAI::SimulationStep."); } Log.Info("Redirection BuildingAI::GetColor calls"); try { Detours.Add(new Detour(typeof(BuildingAI).GetMethod("GetColor", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Building).MakeByRefType(), typeof (InfoManager.InfoMode) }, null), typeof(CustomBuildingAI).GetMethod("CustomGetColor"))); } catch (Exception) { Log.Error("Could not redirect BuildingAI::GetColor"); detourFailed = true; } Log.Info("Redirecting CarAI::TrySpawn Calls"); try { Detours.Add(new Detour(typeof(CarAI).GetMethod("TrySpawn", new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }), typeof(CustomCarAI).GetMethod("TrySpawn"))); } catch (Exception) { Log.Error("Could not redirect CarAI::TrySpawn."); detourFailed = true; } Log.Info("Redirecting CarAI Simulation Step Calls"); try { Detours.Add(new Detour(typeof(CarAI).GetMethod("SimulationStep", new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3) }), typeof(CustomCarAI).GetMethod("CustomSimulationStep"))); } catch (Exception) { Log.Error("Could not redirect CarAI::SimulationStep."); detourFailed = true; } /*Log.Info("Redirecting CarAI::CheckOtherVehicles Calls"); try { Detours.Add(new Detour(typeof(CarAI).GetMethod("CheckOtherVehicles", BindingFlags.Public | BindingFlags.Static), typeof(CustomCarAI).GetMethod("CustomCheckOtherVehicles", BindingFlags.Public | BindingFlags.Static ))); } catch (Exception) { Log.Error("Could not redirect CarAI::CheckOtherVehicles."); detourFailed = true; }*/ Log.Info("Redirecting CommonBuildingAI::SimulationStep Calls"); try { Detours.Add(new Detour(typeof(CommonBuildingAI).GetMethod("SimulationStep", new[] { typeof (ushort), typeof (Building).MakeByRefType() }), typeof(CustomCommonBuildingAI).GetMethod("CustomSimulationStep"))); } catch (Exception) { Log.Error("Could not redirect CommonBuildingAI::SimulationStep."); detourFailed = true; } Log.Info("Redirecting HumanAI Simulation Step Calls"); try { Detours.Add(new Detour(typeof(HumanAI).GetMethod("SimulationStep", new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (Vector3) }), typeof(CustomHumanAI).GetMethod("CustomSimulationStep"))); } catch (Exception) { Log.Error("Could not redirect HumanAI::SimulationStep."); detourFailed = true; } Log.Info("Redirecting HumanAI::CheckTrafficLights Calls"); try { Detours.Add(new Detour(typeof(HumanAI).GetMethod("CheckTrafficLights", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(ushort), typeof(ushort) }, null), typeof(CustomHumanAI).GetMethod("CustomCheckTrafficLights"))); } catch (Exception) { Log.Error("Could not redirect HumanAI::CheckTrafficLights."); detourFailed = true; } Log.Info("Redirecting HumanAI::ArriveAtDestination Calls"); try { Detours.Add(new Detour(typeof(HumanAI).GetMethod("ArriveAtDestination", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (bool) }, null), typeof(CustomHumanAI).GetMethod("CustomArriveAtDestination", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (bool) }, null))); } catch (Exception) { Log.Error("Could not redirect HumanAI::ArriveAtDestination."); detourFailed = true; } Log.Info("Redirection ResidentAI::GetVehicleInfo calls"); try { Detours.Add(new Detour(typeof(ResidentAI).GetMethod("GetVehicleInfo", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (bool), typeof (VehicleInfo).MakeByRefType() }, null), typeof(CustomResidentAI).GetMethod("CustomGetVehicleInfo"))); } catch (Exception) { Log.Error("Could not redirect ResidentAI::GetVehicleInfo"); detourFailed = true; } Log.Info("Redirection TouristAI::GetVehicleInfo calls"); try { Detours.Add(new Detour(typeof(TouristAI).GetMethod("GetVehicleInfo", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (bool), typeof (VehicleInfo).MakeByRefType() }, null), typeof(CustomTouristAI).GetMethod("CustomGetVehicleInfo"))); } catch (Exception) { Log.Error("Could not redirect TouristAI::GetVehicleInfo"); detourFailed = true; } Log.Info("Redirecting PassengerCarAI Simulation Step Calls"); try { Detours.Add(new Detour(typeof(PassengerCarAI).GetMethod("SimulationStep", new[] { typeof(ushort), typeof(Vehicle).MakeByRefType(), typeof(Vector3) }), typeof(CustomPassengerCarAI).GetMethod("CustomSimulationStep"))); } catch (Exception) { Log.Error("Could not redirect PassengerCarAI::SimulationStep."); detourFailed = true; } Log.Info("Redirecting PassengerCarAI UpdateParkedVehicle Calls"); try { Detours.Add(new Detour(typeof(PassengerCarAI).GetMethod("UpdateParkedVehicle", new[] { typeof (ushort), typeof (VehicleParked).MakeByRefType() }), typeof(CustomPassengerCarAI).GetMethod("CustomUpdateParkedVehicle"))); } catch (Exception) { Log.Error("Could not redirect PassengerCarAI::UpdateParkedVehicle."); detourFailed = true; } Log.Info("Redirection PassengerCarAI::ParkVehicle calls"); try { Detours.Add(new Detour(typeof(PassengerCarAI).GetMethod("ParkVehicle", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (PathUnit.Position), typeof (uint), typeof (int), typeof (byte).MakeByRefType() }, null), typeof(CustomPassengerCarAI).GetMethod("CustomParkVehicle"))); } catch (Exception) { Log.Error("Could not redirect PassengerCarAI::ParkVehicle"); detourFailed = true; } Log.Info("Redirection PassengerCarAI::GetLocalizedStatus calls"); try { Detours.Add(new Detour(typeof(PassengerCarAI).GetMethod("GetLocalizedStatus", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (InstanceID).MakeByRefType() }, null), typeof(CustomPassengerCarAI).GetMethod("CustomGetLocalizedStatus"))); } catch (Exception) { Log.Error("Could not redirect PassengerCarAI::GetLocalizedStatus"); detourFailed = true; } Log.Info("Redirection ResidentAI::GetLocalizedStatus calls"); try { Detours.Add(new Detour(typeof(ResidentAI).GetMethod("GetLocalizedStatus", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (InstanceID).MakeByRefType() }, null), typeof(CustomResidentAI).GetMethod("CustomGetLocalizedStatus"))); } catch (Exception) { Log.Error("Could not redirect ResidentAI::GetLocalizedStatus"); detourFailed = true; } Log.Info("Redirection TouristAI::GetLocalizedStatus calls"); try { Detours.Add(new Detour(typeof(TouristAI).GetMethod("GetLocalizedStatus", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (InstanceID).MakeByRefType() }, null), typeof(CustomTouristAI).GetMethod("CustomGetLocalizedStatus"))); } catch (Exception) { Log.Error("Could not redirect TouristAI::GetLocalizedStatus"); detourFailed = true; } Log.Info("Redirecting CargoTruckAI::SimulationStep calls"); try { Detours.Add(new Detour(typeof(CargoTruckAI).GetMethod("SimulationStep", new[] { typeof(ushort), typeof(Vehicle).MakeByRefType(), typeof(Vector3) }), typeof(CustomCargoTruckAI).GetMethod("CustomSimulationStep"))); } catch (Exception) { Log.Error("Could not redirect CargoTruckAI::SimulationStep."); detourFailed = true; } Log.Info("Redirecting TrainAI::SimulationStep calls"); try { Detours.Add(new Detour(typeof(TrainAI).GetMethod("SimulationStep", new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3) }), typeof(CustomTrainAI).GetMethod("CustomSimulationStep", new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3) }))); } catch (Exception) { Log.Error("Could not redirect TrainAI::SimulationStep."); detourFailed = true; } Log.Info("Redirecting TrainAI::SimulationStep (2) calls"); try { Detours.Add(new Detour(typeof(TrainAI).GetMethod("SimulationStep", new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vehicle.Frame).MakeByRefType(), typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (int) }), typeof(CustomTrainAI).GetMethod("CustomSimulationStep", new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vehicle.Frame).MakeByRefType(), typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (int) }))); } catch (Exception) { Log.Error("Could not redirect TrainAI::SimulationStep (2)."); detourFailed = true; } /*Log.Info("Redirecting TrainAI::TrySpawn Calls"); try { Detours.Add(new Detour(typeof(TrainAI).GetMethod("TrySpawn", new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }), typeof(CustomTrainAI).GetMethod("TrySpawn"))); } catch (Exception) { Log.Error("Could not redirect TrainAI::TrySpawn."); detourFailed = true; }*/ Log.Info("Redirection TramBaseAI::SimulationStep calls"); try { Detours.Add(new Detour(typeof(TramBaseAI).GetMethod("SimulationStep", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), }, null), typeof(CustomTramBaseAI).GetMethod("CustomSimulationStep", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), }, null))); } catch (Exception) { Log.Error("Could not redirect TramBaseAI::SimulationStep"); detourFailed = true; } Log.Info("Redirection TramBaseAI::SimulationStep (2) calls"); try { Detours.Add(new Detour(typeof(TramBaseAI).GetMethod("SimulationStep", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vehicle.Frame).MakeByRefType(), typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (int) }, null), typeof(CustomTramBaseAI).GetMethod("CustomSimulationStep", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vehicle.Frame).MakeByRefType(), typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (int) }, null))); } catch (Exception) { Log.Error("Could not redirect TramBaseAI::SimulationStep (2)"); detourFailed = true; } /*Log.Info("Redirection TramBaseAI::ResetTargets calls"); try { Detours.Add(new Detour(typeof(TramBaseAI).GetMethod("ResetTargets", BindingFlags.NonPublic | BindingFlags.Static), typeof(CustomTramBaseAI).GetMethod("CustomResetTargets", BindingFlags.NonPublic | BindingFlags.Static))); } catch (Exception) { Log.Error("Could not redirect TramBaseAI::ResetTargets"); detourFailed = true; }*/ /*Log.Info("Redirecting TramBaseAI::TrySpawn Calls"); try { Detours.Add(new Detour(typeof(TramBaseAI).GetMethod("TrySpawn", new[] { typeof (ushort), typeof (Vehicle).MakeByRefType() }), typeof(CustomTramBaseAI).GetMethod("TrySpawn"))); } catch (Exception) { Log.Error("Could not redirect TramBaseAI::TrySpawn."); detourFailed = true; }*/ Log.Info("Redirecting Car AI Calculate Segment Calls"); try { Detours.Add(new Detour(typeof(CarAI).GetMethod("CalculateSegmentPosition", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (PathUnit.Position), typeof (PathUnit.Position), typeof (uint), typeof (byte), typeof (PathUnit.Position), typeof (uint), typeof (byte), typeof (int), typeof (Vector3).MakeByRefType(), typeof (Vector3).MakeByRefType(), typeof (float).MakeByRefType() }, null), typeof(CustomCarAI).GetMethod("CustomCalculateSegmentPosition"))); } catch (Exception) { Log.Error("Could not redirect CarAI::CalculateSegmentPosition."); detourFailed = true; } Log.Info("Redirection TramBaseAI Calculate Segment Position calls"); try { Detours.Add(new Detour(typeof(TramBaseAI).GetMethod("CalculateSegmentPosition", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (PathUnit.Position), typeof (PathUnit.Position), typeof (uint), typeof (byte), typeof (PathUnit.Position), typeof (uint), typeof (byte), typeof (int), typeof (Vector3).MakeByRefType(), typeof (Vector3).MakeByRefType(), typeof (float).MakeByRefType() }, null), typeof(CustomTramBaseAI).GetMethod("CustomCalculateSegmentPosition"))); } catch (Exception) { Log.Error("Could not redirect TramBaseAI::CalculateSegmentPosition"); detourFailed = true; } Log.Info("Redirection PathFind::CalculatePath calls for non-Traffic++"); try { Detours.Add(new Detour(typeof(PathFind).GetMethod("CalculatePath", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (uint), typeof (bool) }, null), #if PF2 typeof(CustomPathFind2).GetMethod("CalculatePath"))); #else typeof(CustomPathFind).GetMethod("CalculatePath"))); #endif } catch (Exception) { Log.Error("Could not redirect PathFind::CalculatePath"); detourFailed = true; } Log.Info("Redirection PathManager::ReleasePath calls for non-Traffic++"); try { Detours.Add(new Detour(typeof(PathManager).GetMethod("ReleasePath", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (uint) }, null), typeof(CustomPathManager).GetMethod("ReleasePath"))); } catch (Exception) { Log.Error("Could not redirect PathManager::ReleasePath"); detourFailed = true; } Log.Info("Redirection CarAI Calculate Segment Position calls for non-Traffic++"); try { Detours.Add(new Detour(typeof(CarAI).GetMethod("CalculateSegmentPosition", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (PathUnit.Position), typeof (uint), typeof (byte), typeof (Vector3).MakeByRefType(), typeof (Vector3).MakeByRefType(), typeof (float).MakeByRefType() }, null), typeof(CustomCarAI).GetMethod("CustomCalculateSegmentPositionPathFinder"))); } catch (Exception) { Log.Error("Could not redirect CarAI::CalculateSegmentPosition"); detourFailed = true; } Log.Info("Redirection TrainAI Calculate Segment Position calls for non-Traffic++"); try { Detours.Add(new Detour(typeof(TrainAI).GetMethod("CalculateSegmentPosition", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (PathUnit.Position), typeof (uint), typeof (byte), typeof (Vector3).MakeByRefType(), typeof (Vector3).MakeByRefType(), typeof (float).MakeByRefType() }, null), typeof(CustomTrainAI).GetMethod("TmCalculateSegmentPositionPathFinder"))); } catch (Exception) { Log.Error("Could not redirect TrainAI::CalculateSegmentPosition (2)"); detourFailed = true; } Log.Info("Redirection AmbulanceAI::StartPathFind calls"); try { Detours.Add(new Detour(typeof(AmbulanceAI).GetMethod("StartPathFind", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (bool), typeof (bool), typeof (bool) }, null), typeof(CustomAmbulanceAI).GetMethod("CustomStartPathFind"))); } catch (Exception) { Log.Error("Could not redirect AmbulanceAI::StartPathFind"); detourFailed = true; } Log.Info("Redirection BusAI::StartPathFind calls"); try { Detours.Add(new Detour(typeof(BusAI).GetMethod("StartPathFind", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (bool), typeof (bool), typeof (bool) }, null), typeof(CustomBusAI).GetMethod("CustomStartPathFind"))); } catch (Exception) { Log.Error("Could not redirect BusAI::StartPathFind"); detourFailed = true; } Log.Info("Redirection CarAI::StartPathFind calls"); try { Detours.Add(new Detour(typeof(CarAI).GetMethod("StartPathFind", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (bool), typeof (bool), typeof (bool) }, null), typeof(CustomCarAI).GetMethod("CustomStartPathFind"))); } catch (Exception) { Log.Error("Could not redirect CarAI::StartPathFind"); detourFailed = true; } Log.Info("Redirection CargoTruckAI::StartPathFind calls"); try { Detours.Add(new Detour(typeof(CargoTruckAI).GetMethod("StartPathFind", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (bool), typeof (bool), typeof (bool) }, null), typeof(CustomCargoTruckAI).GetMethod("CustomStartPathFind"))); } catch (Exception) { Log.Error("Could not redirect CargoTruckAI::StartPathFind"); detourFailed = true; } Log.Info("Redirection FireTruckAI::StartPathFind calls"); try { Detours.Add(new Detour(typeof(FireTruckAI).GetMethod("StartPathFind", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (bool), typeof (bool), typeof (bool) }, null), typeof(CustomFireTruckAI).GetMethod("CustomStartPathFind"))); } catch (Exception) { Log.Error("Could not redirect FireTruckAI::StartPathFind"); detourFailed = true; } Log.Info("Redirection PassengerCarAI::StartPathFind(1) calls"); try { Detours.Add(new Detour(typeof(PassengerCarAI).GetMethod("StartPathFind", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (bool), typeof (bool), typeof (bool) }, null), typeof(CustomPassengerCarAI).GetMethod("CustomStartPathFind", new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (bool), typeof (bool), typeof (bool) }))); } catch (Exception) { Log.Error("Could not redirect PassengerCarAI::StartPathFind(1)"); detourFailed = true; } Log.Info("Redirection PoliceCarAI::StartPathFind calls"); try { Detours.Add(new Detour(typeof(PoliceCarAI).GetMethod("StartPathFind", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (bool), typeof (bool), typeof (bool) }, null), typeof(CustomPoliceCarAI).GetMethod("CustomStartPathFind"))); } catch (Exception) { Log.Error("Could not redirect PoliceCarAI::StartPathFind"); detourFailed = true; } Log.Info("Redirection TaxiAI::StartPathFind calls"); try { Detours.Add(new Detour(typeof(TaxiAI).GetMethod("StartPathFind", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (bool), typeof (bool), typeof (bool) }, null), typeof(CustomTaxiAI).GetMethod("CustomStartPathFind"))); } catch (Exception) { Log.Error("Could not redirect TaxiAI::StartPathFind"); detourFailed = true; } Log.Info("Redirection TrainAI::StartPathFind calls"); try { Detours.Add(new Detour(typeof(TrainAI).GetMethod("StartPathFind", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (bool), typeof (bool) }, null), typeof(CustomTrainAI).GetMethod("CustomStartPathFind"))); } catch (Exception) { Log.Error("Could not redirect TrainAI::StartPathFind"); detourFailed = true; } Log.Info("Redirection ShipAI::StartPathFind calls"); try { Detours.Add(new Detour(typeof(ShipAI).GetMethod("StartPathFind", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (bool), typeof (bool) }, null), typeof(CustomShipAI).GetMethod("CustomStartPathFind"))); } catch (Exception) { Log.Error("Could not redirect ShipAI::StartPathFind"); detourFailed = true; } Log.Info("Redirection CitizenAI::StartPathFind calls"); try { Detours.Add(new Detour(typeof(CitizenAI).GetMethod("StartPathFind", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (CitizenInstance).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (VehicleInfo), typeof (bool), typeof (bool) }, null), typeof(CustomCitizenAI).GetMethod("CustomStartPathFind"))); } catch (Exception) { Log.Error("Could not redirect CitizenAI::StartPathFind"); detourFailed = true; } Log.Info("Redirection CitizenAI::FindPathPosition calls"); try { Detours.Add(new Detour(typeof(CitizenAI).GetMethod("FindPathPosition", BindingFlags.Public | BindingFlags.Instance), typeof(CustomCitizenAI).GetMethod("CustomFindPathPosition"))); } catch (Exception) { Log.Error("Could not redirect CitizenAI::FindPathPosition"); detourFailed = true; } Log.Info("Redirection TransportLineAI::StartPathFind calls"); try { Detours.Add(new Detour(typeof(TransportLineAI).GetMethod("StartPathFind", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof (ushort), typeof (NetSegment).MakeByRefType(), typeof (ItemClass.Service), typeof (ItemClass.Service), typeof (VehicleInfo.VehicleType), typeof (bool) }, null), typeof(CustomTransportLineAI).GetMethod("CustomStartPathFind"))); } catch (Exception) { Log.Error("Could not redirect TransportLineAI::StartPathFind"); detourFailed = true; } Log.Info("Redirection TramBaseAI::StartPathFind calls"); try { Detours.Add(new Detour(typeof(TramBaseAI).GetMethod("StartPathFind", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (Vector3), typeof (Vector3), typeof (bool), typeof (bool) }, null), typeof(CustomTramBaseAI).GetMethod("CustomStartPathFind"))); } catch (Exception) { Log.Error("Could not redirect TramBaseAI::StartPathFind"); detourFailed = true; } Log.Info("Redirection RoadBaseAI::SetTrafficLightState calls"); try { Detours.Add(new Detour(typeof(RoadBaseAI).GetMethod("SetTrafficLightState", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof (ushort), typeof (NetSegment).MakeByRefType(), typeof (uint), typeof (RoadBaseAI.TrafficLightState), typeof (RoadBaseAI.TrafficLightState), typeof (bool), typeof (bool) }, null), typeof(CustomRoadAI).GetMethod("CustomSetTrafficLightState"))); } catch (Exception) { Log.Error("Could not redirect RoadBaseAI::SetTrafficLightState"); detourFailed = true; } Log.Info("Redirection RoadBaseAI::UpdateLanes calls"); try { Detours.Add(new Detour(typeof(RoadBaseAI).GetMethod("UpdateLanes", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (NetSegment).MakeByRefType(), typeof (bool) }, null), typeof(CustomRoadAI).GetMethod("CustomUpdateLanes"))); } catch (Exception) { Log.Error("Could not redirect RoadBaseAI::UpdateLanes"); detourFailed = true; } Log.Info("Redirection TrainAI::CheckNextLane calls"); try { Detours.Add(new Detour(typeof(TrainAI).GetMethod("CheckNextLane", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (float).MakeByRefType(), typeof (PathUnit.Position), typeof (uint), typeof (byte), typeof (PathUnit.Position), typeof (uint), typeof (byte), typeof (Bezier3) }, null), typeof(CustomTrainAI).GetMethod("CustomCheckNextLane"))); } catch (Exception) { Log.Error("Could not redirect TrainAI::CheckNextLane"); detourFailed = true; } Log.Info("Redirection TrainAI::ForceTrafficLights calls"); try { Detours.Add(new Detour(typeof(TrainAI).GetMethod("ForceTrafficLights", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (bool) }, null), typeof(CustomTrainAI).GetMethod("CustomForceTrafficLights", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (Vehicle).MakeByRefType(), typeof (bool) }, null))); } catch (Exception) { Log.Error("Could not redirect TrainAI::CheckNextLane"); detourFailed = true; } // TODO remove /*Log.Info("Redirection NetManager::FinalizeNode calls"); try { Detours.Add(new Detour(typeof(NetManager).GetMethod("FinalizeNode", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (NetNode).MakeByRefType() }, null), typeof(CustomNetManager).GetMethod("CustomFinalizeNode"))); } catch (Exception) { Log.Error("Could not redirect NetManager::FinalizeNode"); detourFailed = true; }*/ Log.Info("Redirection NetManager::FinalizeSegment calls"); try { Detours.Add(new Detour(typeof(NetManager).GetMethod("FinalizeSegment", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (NetSegment).MakeByRefType() }, null), typeof(CustomNetManager).GetMethod("CustomFinalizeSegment"))); } catch (Exception) { Log.Error("Could not redirect NetManager::FinalizeSegment"); detourFailed = true; } #if DEBUGBUSBUG // TODO remove Log.Info("Redirection NetManager::MoveNode calls"); try { Detours.Add(new Detour(typeof(NetManager).GetMethod("MoveNode", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (NetNode).MakeByRefType(), typeof (Vector3) }, null), typeof(CustomNetManager).GetMethod("CustomMoveNode", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (NetNode).MakeByRefType(), typeof (Vector3) }, null))); } catch (Exception) { Log.Error("Could not redirect NetManager::MoveNode"); detourFailed = true; } #endif Log.Info("Redirection NetManager::UpdateSegment calls"); try { Detours.Add(new Detour(typeof(NetManager).GetMethod("UpdateSegment", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof (ushort), typeof (ushort), typeof (int), }, null), typeof(CustomNetManager).GetMethod("CustomUpdateSegment"))); } catch (Exception) { Log.Error("Could not redirect NetManager::UpdateSegment"); detourFailed = true; } Log.Info("Redirection Vehicle::Spawn calls"); try { Detours.Add(new Detour(typeof(Vehicle).GetMethod("Spawn", BindingFlags.Public | BindingFlags.Instance), typeof(CustomVehicle).GetMethod("Spawn", BindingFlags.Public | BindingFlags.Static))); } catch (Exception) { Log.Error("Could not redirect Vehicle::Spawn"); detourFailed = true; } Log.Info("Redirection Vehicle::Unspawn calls"); try { Detours.Add(new Detour(typeof(Vehicle).GetMethod("Unspawn", BindingFlags.Public | BindingFlags.Instance), typeof(CustomVehicle).GetMethod("Unspawn", BindingFlags.Public | BindingFlags.Static))); } catch (Exception) { Log.Error("Could not redirect Vehicle::Unspawn"); detourFailed = true; } if (detourFailed) { Log.Info("Detours failed"); Singleton.instance.m_ThreadingWrapper.QueueMainThread(() => { UIView.library.ShowModal("ExceptionPanel").SetMessage("TM:PE failed to load", "Traffic Manager: President Edition failed to load. You can continue playing but it's NOT recommended. Traffic Manager will not work as expected.", true); }); } else { Log.Info("Detours successful"); } DetourInited = true; } } public override void OnCreated(ILoading loading) { //SelfDestruct.DestructOldInstances(this); base.OnCreated(loading); Detours = new List(); RegisteredManagers = new List(); DetourInited = false; CustomPathManager = new CustomPathManager(); RegisterCustomManagers(); } private void RegisterCustomManagers() { // TODO represent data dependencies differently RegisteredManagers.Add(GeometryManager.Instance); RegisteredManagers.Add(AdvancedParkingManager.Instance); RegisteredManagers.Add(CustomSegmentLightsManager.Instance); RegisteredManagers.Add(ExtBuildingManager.Instance); RegisteredManagers.Add(ExtCitizenInstanceManager.Instance); RegisteredManagers.Add(ExtCitizenManager.Instance); RegisteredManagers.Add(TurnOnRedManager.Instance); RegisteredManagers.Add(LaneArrowManager.Instance); RegisteredManagers.Add(LaneConnectionManager.Instance); RegisteredManagers.Add(OptionsManager.Instance); RegisteredManagers.Add(ParkingRestrictionsManager.Instance); RegisteredManagers.Add(RoutingManager.Instance); RegisteredManagers.Add(SegmentEndManager.Instance); RegisteredManagers.Add(SpeedLimitManager.Instance); RegisteredManagers.Add(TrafficLightManager.Instance); RegisteredManagers.Add(TrafficLightSimulationManager.Instance); RegisteredManagers.Add(TrafficMeasurementManager.Instance); RegisteredManagers.Add(TrafficPriorityManager.Instance); RegisteredManagers.Add(UtilityManager.Instance); RegisteredManagers.Add(VehicleRestrictionsManager.Instance); RegisteredManagers.Add(VehicleStateManager.Instance); RegisteredManagers.Add(JunctionRestrictionsManager.Instance); // depends on TurnOnRedManager, TrafficLightManager, TrafficLightSimulationManager } public override void OnReleased() { base.OnReleased(); UIBase.ReleaseTool(); } public override void OnLevelUnloading() { Log.Info("OnLevelUnloading"); base.OnLevelUnloading(); if (IsPathManagerReplaced) { CustomPathManager._instance.WaitForAllPaths(); } /*Object.Destroy(BaseUI); BaseUI = null; Object.Destroy(TransportDemandUI); TransportDemandUI = null;*/ try { foreach (ICustomManager manager in RegisteredManagers) { Log.Info($"OnLevelUnloading: {manager.GetType().Name}"); manager.OnLevelUnloading(); } Flags.OnLevelUnloading(); Translation.OnLevelUnloading(); GlobalConfig.OnLevelUnloading(); // remove vehicle button var removeVehicleButtonExtender = UIView.GetAView().gameObject.GetComponent(); if (removeVehicleButtonExtender != null) { Object.Destroy(removeVehicleButtonExtender, 10f); } // remove citizen instance button var removeCitizenInstanceButtonExtender = UIView.GetAView().gameObject.GetComponent(); if (removeCitizenInstanceButtonExtender != null) { Object.Destroy(removeCitizenInstanceButtonExtender, 10f); } #if TRACE Singleton.instance.OnLevelUnloading(); #endif } catch (Exception e) { Log.Error("Exception unloading mod. " + e.Message); // ignored - prevents collision with other mods } revertDetours(); IsGameLoaded = false; } public override void OnLevelLoaded(LoadMode mode) { SimulationManager.UpdateMode updateMode = SimulationManager.instance.m_metaData.m_updateMode; Log.Info($"OnLevelLoaded({mode}) called. updateMode={updateMode}"); base.OnLevelLoaded(mode); Log._Debug("OnLevelLoaded Returned from base, calling custom code."); IsGameLoaded = false; switch (updateMode) { case SimulationManager.UpdateMode.NewGameFromMap: case SimulationManager.UpdateMode.NewGameFromScenario: case SimulationManager.UpdateMode.LoadGame: if (BuildConfig.applicationVersion != BuildConfig.VersionToString(TrafficManagerMod.GameVersion, false)) { string[] majorVersionElms = BuildConfig.applicationVersion.Split('-'); string[] versionElms = majorVersionElms[0].Split('.'); uint versionA = Convert.ToUInt32(versionElms[0]); uint versionB = Convert.ToUInt32(versionElms[1]); uint versionC = Convert.ToUInt32(versionElms[2]); Log.Info($"Detected game version v{BuildConfig.applicationVersion}"); bool isModTooOld = TrafficManagerMod.GameVersionA < versionA || (TrafficManagerMod.GameVersionA == versionA && TrafficManagerMod.GameVersionB < versionB)/* || (TrafficManagerMod.GameVersionA == versionA && TrafficManagerMod.GameVersionB == versionB && TrafficManagerMod.GameVersionC < versionC)*/; bool isModNewer = TrafficManagerMod.GameVersionA < versionA || (TrafficManagerMod.GameVersionA == versionA && TrafficManagerMod.GameVersionB > versionB)/* || (TrafficManagerMod.GameVersionA == versionA && TrafficManagerMod.GameVersionB == versionB && TrafficManagerMod.GameVersionC > versionC)*/; if (isModTooOld) { string msg = $"Traffic Manager: President Edition detected that you are running a newer game version ({BuildConfig.applicationVersion}) than TM:PE has been built for ({BuildConfig.VersionToString(TrafficManagerMod.GameVersion, false)}). Please be aware that TM:PE has not been updated for the newest game version yet and thus it is very likely it will not work as expected."; Log.Error(msg); Singleton.instance.m_ThreadingWrapper.QueueMainThread(() => { UIView.library.ShowModal("ExceptionPanel").SetMessage("TM:PE has not been updated yet", msg, false); }); } else if (isModNewer) { string msg = $"Traffic Manager: President Edition has been built for game version {BuildConfig.VersionToString(TrafficManagerMod.GameVersion, false)}. You are running game version {BuildConfig.applicationVersion}. Some features of TM:PE will not work with older game versions. Please let Steam update your game."; Log.Error(msg); Singleton.instance.m_ThreadingWrapper.QueueMainThread(() => { UIView.library.ShowModal("ExceptionPanel").SetMessage("Your game should be updated", msg, false); }); } } IsGameLoaded = true; break; default: Log.Info($"OnLevelLoaded: Unsupported game mode {mode}"); return; } IsRainfallLoaded = CheckRainfallIsLoaded(); IsRushHourLoaded = CheckRushHourIsLoaded(); if (!IsPathManagerReplaced) { try { Log.Info("Pathfinder Compatible. Setting up CustomPathManager and SimManager."); var pathManagerInstance = typeof(Singleton).GetField("sInstance", BindingFlags.Static | BindingFlags.NonPublic); var stockPathManager = PathManager.instance; Log._Debug($"Got stock PathManager instance {stockPathManager.GetName()}"); CustomPathManager = stockPathManager.gameObject.AddComponent(); Log._Debug("Added CustomPathManager to gameObject List"); if (CustomPathManager == null) { Log.Error("CustomPathManager null. Error creating it."); return; } CustomPathManager.UpdateWithPathManagerValues(stockPathManager); Log._Debug("UpdateWithPathManagerValues success"); pathManagerInstance?.SetValue(null, CustomPathManager); Log._Debug("Getting Current SimulationManager"); var simManager = typeof(SimulationManager).GetField("m_managers", BindingFlags.Static | BindingFlags.NonPublic)? .GetValue(null) as FastList; Log._Debug("Removing Stock PathManager"); simManager?.Remove(stockPathManager); Log._Debug("Adding Custom PathManager"); simManager?.Add(CustomPathManager); Object.Destroy(stockPathManager, 10f); Log._Debug("Should be custom: " + Singleton.instance.GetType().ToString()); IsPathManagerReplaced = true; } catch (Exception ex) { string error = "Traffic Manager: President Edition failed to load. You can continue playing but it's NOT recommended. Traffic Manager will not work as expected."; Log.Error(error); Log.Error($"Path manager replacement error: {ex.ToString()}"); Singleton.instance.m_ThreadingWrapper.QueueMainThread(() => { UIView.library.ShowModal("ExceptionPanel").SetMessage("TM:PE failed to load", error, true); }); } } Log.Info("Adding Controls to UI."); if (BaseUI == null) { Log._Debug("Adding UIBase instance."); BaseUI = ToolsModifierControl.toolController.gameObject.AddComponent(); } // Init transport demand UI if (TransportDemandUI == null) { var uiView = UIView.GetAView(); TransportDemandUI = (UITransportDemand)uiView.AddUIComponent(typeof(UITransportDemand)); } // add "remove vehicle" button UIView.GetAView().gameObject.AddComponent(); // add "remove citizen instance" button UIView.GetAView().gameObject.AddComponent(); initDetours(); //Log.Info("Fixing non-created nodes with problems..."); //FixNonCreatedNodeProblems(); Log.Info("Notifying managers..."); foreach (ICustomManager manager in RegisteredManagers) { Log.Info($"OnLevelLoading: {manager.GetType().Name}"); manager.OnLevelLoading(); } //InitTool(); //Log._Debug($"Current tool: {ToolManager.instance.m_properties.CurrentTool}"); Log.Info("OnLevelLoaded complete."); } /*private void FixNonCreatedNodeProblems() { for (int nodeId = 0; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { if ((NetManager.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Created) == NetNode.Flags.None) { NetManager.instance.m_nodes.m_buffer[nodeId].m_problems = Notification.Problem.None; NetManager.instance.m_nodes.m_buffer[nodeId].m_flags = NetNode.Flags.None; } } }*/ private bool CheckRainfallIsLoaded() { return Check3rdPartyModLoaded("Rainfall"); } private bool CheckRushHourIsLoaded() { return Check3rdPartyModLoaded("RushHour", true); } private bool Check3rdPartyModLoaded(string namespaceStr, bool printAll=false) { bool thirdPartyModLoaded = false; var loadingWrapperLoadingExtensionsField = typeof(LoadingWrapper).GetField("m_LoadingExtensions", BindingFlags.NonPublic | BindingFlags.Instance); List loadingExtensions = null; if (loadingWrapperLoadingExtensionsField != null) { loadingExtensions = (List)loadingWrapperLoadingExtensionsField.GetValue(Singleton.instance.m_LoadingWrapper); } else { Log.Warning("Could not get loading extensions field"); } if (loadingExtensions != null) { foreach (ILoadingExtension extension in loadingExtensions) { if (printAll) Log.Info($"Detected extension: {extension.GetType().Name} in namespace {extension.GetType().Namespace}"); if (extension.GetType().Namespace == null) continue; var nsStr = extension.GetType().Namespace.ToString(); if (namespaceStr.Equals(nsStr)) { Log.Info($"The mod '{namespaceStr}' has been detected."); thirdPartyModLoaded = true; break; } } } else { Log._Debug("Could not get loading extensions"); } return thirdPartyModLoaded; } } } ================================================ FILE: TLM/TLM/Manager/AbstractCustomManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using GenericGameBridge.Factory; using CSUtil.Commons; namespace TrafficManager.Manager { /// /// Abstract manager class, supports events before/after loading/saving. /// /// Event sequences: /// /// Startup / Level loading: /// 1. OnInit (TODO) -> /// 2. {Flags|NodeGeometry|SegmentGeometry}.OnBeforeLoadData -> /// 3. OnBeforeLoadData -> /// 4. (SerializableDataExtension loads custom game data) -> /// 5. OnAfterLoadData -> /// 6. (LoadingManager sets up detours) -> /// 7. OnLevelLoading /// Saving: /// 1. OnBeforeSaveData -> /// 2. (SerializableDataExtension saves custom game data) -> /// 3. OnAfterSaveData /// Level unloading: /// 1. (LoadingManager releases detours) -> /// 2. OnLevelUnloading /// public abstract class AbstractCustomManager : ICustomManager { public IServiceFactory Services { get { return Constants.ServiceFactory; } } /// /// Performs actions after game data has been loaded /// public virtual void OnAfterLoadData() { } /// /// Performs actions after game data has been saved /// public virtual void OnAfterSaveData() { } /// /// Performs actions before game data is going to be loaded /// public virtual void OnBeforeLoadData() { } /// /// Performs actions before game data is going to be saved /// public virtual void OnBeforeSaveData() { } /// /// Performs actions after a game has been loaded /// public virtual void OnLevelLoading() { } /// /// Performs actions after a game has been unloaded /// public virtual void OnLevelUnloading() { } /// /// Prints information for debugging purposes /// protected virtual void InternalPrintDebugInfo() { } public void PrintDebugInfo() { Log._Debug($"=== {this.GetType().Name}.PrintDebugInfo() *START* ==="); InternalPrintDebugInfo(); Log._Debug($"=== {this.GetType().Name}.PrintDebugInfo() *END* ==="); } } } ================================================ FILE: TLM/TLM/Manager/AbstractFeatureManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { /// /// Helper class to ensure that events are always handled in the simulation thread /// public abstract class AbstractFeatureManager : AbstractCustomManager, IFeatureManager { public void OnDisableFeature() { Services.SimulationService.AddAction(() => { OnDisableFeatureInternal(); }); } public void OnEnableFeature() { Services.SimulationService.AddAction(() => { OnEnableFeatureInternal(); }); } /// /// Executes whenever the associated feature is disabled. Guaranteed to run in the simulation thread. /// protected abstract void OnDisableFeatureInternal(); /// /// Executes whenever the associated feature is enabled. Guaranteed to run in the simulation thread. /// protected abstract void OnEnableFeatureInternal(); } } ================================================ FILE: TLM/TLM/Manager/AbstractGeometryObservingManager.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using TrafficManager.Geometry; using TrafficManager.Geometry.Impl; using TrafficManager.State; using TrafficManager.Util; using static TrafficManager.Geometry.Impl.NodeGeometry; namespace TrafficManager.Manager { public abstract class AbstractGeometryObservingManager : AbstractCustomManager, IObserver { private IDisposable geoUpdateUnsubscriber = null; private object geoLock = new object(); /// /// Handles an invalid segment /// /// segment geometry protected virtual void HandleInvalidSegment(SegmentGeometry geometry) { } /// /// Handles a valid segment /// /// segment geometry protected virtual void HandleValidSegment(SegmentGeometry geometry) { } /// /// Handles an invalid node /// /// node geometry protected virtual void HandleInvalidNode(NodeGeometry geometry) { } /// /// Handles a valid node /// /// node geometry protected virtual void HandleValidNode(NodeGeometry geometry) { } /// /// Handles a segment replacement /// /// segment replacement /// new segment end geometry protected virtual void HandleSegmentEndReplacement(SegmentEndReplacement replacement, SegmentEndGeometry newEndGeo) { } protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); } public override void OnLevelLoading() { base.OnLevelLoading(); geoUpdateUnsubscriber = Constants.ManagerFactory.GeometryManager.Subscribe(this); } public override void OnLevelUnloading() { base.OnLevelUnloading(); if (geoUpdateUnsubscriber != null) { geoUpdateUnsubscriber.Dispose(); } } public void OnUpdate(GeometryUpdate update) { if (update.segmentGeometry != null) { // Handle a segment update SegmentGeometry geometry = update.segmentGeometry; if (!geometry.IsValid()) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"{this.GetType().Name}.HandleInvalidSegment({geometry.SegmentId})"); #endif HandleInvalidSegment(geometry); } else { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"{this.GetType().Name}.HandleValidSegment({geometry.SegmentId})"); #endif HandleValidSegment(geometry); } } else if (update.nodeGeometry != null) { // Handle a node update NodeGeometry geometry = update.nodeGeometry; if (!geometry.IsValid()) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"{this.GetType().Name}.HandleInvalidNode({geometry.NodeId})"); #endif HandleInvalidNode(geometry); } else { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"{this.GetType().Name}.HandleValidNode({geometry.NodeId})"); #endif HandleValidNode(geometry); } } else { // Handle a segment end replacement SegmentEndGeometry endGeo = SegmentGeometry.Get(update.replacement.newSegmentEndId.SegmentId)?.GetEnd(update.replacement.newSegmentEndId.StartNode); if (endGeo != null) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"{this.GetType().Name}.HandleSegmentReplacement({update.replacement.oldSegmentEndId} -> {update.replacement.newSegmentEndId})"); #endif HandleSegmentEndReplacement(update.replacement, endGeo); } } } ~AbstractGeometryObservingManager() { if (geoUpdateUnsubscriber != null) { geoUpdateUnsubscriber.Dispose(); } } } } ================================================ FILE: TLM/TLM/Manager/IAdvancedParkingManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Traffic; using TrafficManager.Traffic.Data; using UnityEngine; using static TrafficManager.Traffic.Data.ExtCitizenInstance; namespace TrafficManager.Manager { /// /// Indicates if a private car [may]/[shall]/[must not] be used /// public enum CarUsagePolicy { /// /// Citizens may use their own car /// Allowed, /// /// Citizens are forced to use their parked car /// ForcedParked, /// /// Citizens are forced to use a pocket car /// ForcedPocket, /// /// Citizens are forbidden to use their car /// Forbidden } /// /// Indicates the current state while approaching a private car /// public enum ParkedCarApproachState { /// /// Citizen is not approaching their parked car /// None, /// /// Citizen is currently approaching their parked car /// Approaching, /// /// Citizen has approaching their parked car /// Approached, /// /// Citizen failed to approach their parked car /// Failure } /// /// Represents the reason why a parked car could not be spawned /// public enum ParkingUnableReason { /// /// Parked car could be spawned /// None, /// /// No free parking space was found /// NoSpaceFound, /// /// The maximum allowed number of parked vehicles has been reached /// LimitHit } public interface IAdvancedParkingManager : IFeatureManager { /// /// Determines the color the given building should be colorized with given the current info view mode. /// While the traffic view is active buildings with high parking space demand are colored red and /// buildings with low demand are colored green. /// /// building id /// building data /// current info view mode /// output color /// true if a custom color should be displayed, false otherwise bool GetBuildingInfoViewColor(ushort buildingId, ref Building buildingData, ref ExtBuilding extBuilding, InfoManager.InfoMode infoMode, out Color? color); /// /// Adds Parking AI related information to the given citizen status text. /// /// status text to enrich /// extended citizen instance data /// extended citizen data /// string EnrichLocalizedCitizenStatus(string ret, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen); /// /// Adds Parking AI related information to the given passenger car status text. /// /// status text to enrich /// extended citizen instance data /// string EnrichLocalizedCarStatus(string ret, ref ExtCitizenInstance driverExtInstance); /// /// Merges the current calculation states of the citizen's main path and return path (while walking). /// If a definite calculation state can be determined path-find failure/success is handled appropriately. /// The returned (soft) path state indicates if further handling must be undertaken by the game. /// /// citizen instance that shall be processed /// citizen instance data /// extended citizen instance data /// extended citizen data /// citizen data /// current state of the citizen instance's main path /// /// Indication of how (external) game logic should treat this situation: /// Calculating: Paths are still being calculated. Game must await completion. /// Ready: All paths are ready and path-find success must be handled. /// FailedHard: At least one path calculation failed and the failure must be handled. /// FailedSoft: Path-finding must be repeated. /// Ignore: Default citizen behavior must be skipped. /// ExtSoftPathState UpdateCitizenPathState(ushort citizenInstanceId, ref CitizenInstance citizenInstance, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, ref Citizen citizen, ExtPathState mainPathState); /// /// Merges the current calculation states of the citizen's main path and return path (while driving a passenger car). /// If a definite calculation state can be determined path-find failure/success is handled appropriately. /// The returned (soft) path state indicates if further handling must be undertaken by the game. /// /// vehicle that shall be processed /// vehicle data /// driver citizen instance /// extended citizen instance data of the driving citizen /// current state of the citizen instance's main path /// /// Indication of how (external) game logic should treat this situation: /// Calculating: Paths are still being calculated. Game must await completion. /// Ready: All paths are ready and path-find success must be handled. /// FailedHard: At least one path calculation failed and the failure must be handled. /// FailedSoft: Path-finding must be repeated. /// Ignore: Default citizen behavior must be skipped. /// ExtSoftPathState UpdateCarPathState(ushort vehicleId, ref Vehicle vehicleData, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, ExtPathState mainPathState); /// /// Processes a citizen that is approaching their private car. /// Internal state information is updated appropriately. The returned approach /// state indicates if the approach is finished. /// /// citizen instance that shall be processed /// citizen instance data /// extended citizen instance data /// simulation accuracy /// parked car data /// /// Approach state indication: /// Approaching: The citizen is currently approaching the parked car. /// Approached: The citizen has approached the car and is ready to enter it. /// Failure: The approach procedure failed (currently not returned). /// ParkedCarApproachState CitizenApproachingParkedCarSimulationStep(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, Vector3 physicsLodRefPos, ref VehicleParked parkedCar); /// /// Processes a citizen that is approaching their target building. /// Internal state information is updated appropriately. The returned flag /// indicates if the approach is finished. /// /// citizen instance that shall be processed /// citizen instance data /// extended citizen instance /// true if the citizen arrived at the target, false otherwise bool CitizenApproachingTargetSimulationStep(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance); /// /// Finds a free parking space in the vicinity of the given target position /// for the given citizen instance . /// /// target position /// vehicle type that is being used /// cititzen instance that is driving the car /// Home building of the citizen (may be 0 for tourists/homeless cims) /// Specifies if the citizen is going home /// Vehicle that is being used (used for logging) /// If true, method fails if given citizen is a tourist (TODO remove this parameter) /// parking position (output) /// sidewalk path position near parking space (output). only valid if yields false. /// if false, a parking space path position could be calculated (TODO negate & rename parameter) /// true if a parking space could be found, false otherwise bool FindParkingSpaceForCitizen(Vector3 endPos, VehicleInfo vehicleInfo, ref ExtCitizenInstance extDriverInstance, ushort homeId, bool goingHome, ushort vehicleId, bool allowTourists, out Vector3 parkPos, ref PathUnit.Position endPathPos, out bool calculateEndPos); /// /// Tries to relocate the given parked car (, ) /// within the vicinity of the given reference position . /// /// parked vehicle id /// parked vehicle data /// reference position /// maximum allowed distance between reference position and parking space location /// Home building id of the citizen (For residential buildings, parked cars may only spawn at the home building) /// true if the parked vehicle was relocated, false otherwise bool TryMoveParkedVehicle(ushort parkedVehicleId, ref VehicleParked parkedVehicle, Vector3 refPos, float maxDistance, ushort homeId); /// /// Tries to spawn a parked passenger car for the given citizen /// in the vicinity of the given position . /// /// Citizen that requires a parked car /// Home building id of the citizen (For residential buildings, parked cars may only spawn at the home building) /// Reference position /// Vehicle type to spawn /// Parked vehicle position (output) /// Indicates the reason why no car could be spawned when the method returns false /// true if a passenger car could be spawned, false otherwise bool TrySpawnParkedPassengerCar(uint citizenId, ushort homeId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason); /// /// Tries to spawn a parked passenger car for the given citizen /// at a road segment in the vicinity of the given position . /// /// Citizen that requires a parked car /// Reference position /// Vehicle type to spawn /// Parked vehicle position (output) /// Indicates the reason why no car could be spawned when the method returns false /// true if a passenger car could be spawned, false otherwise bool TrySpawnParkedPassengerCarRoadSide(uint citizenId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason); /// /// Tries to spawn a parked passenger car for the given citizen /// at a building in the vicinity of the given position . /// /// Citizen that requires a parked car /// Home building id of the citizen (For residential buildings, parked cars may only spawn at the home building) /// Reference position /// Vehicle type to spawn /// Parked vehicle position (output) /// Indicates the reason why no car could be spawned when the method returns false /// true if a passenger car could be spawned, false otherwise bool TrySpawnParkedPassengerCarBuilding(uint citizenId, ushort homeId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason); /// /// Tries to find a parking space in the broaded vicinity of the given position . /// /// Target position that is used as a center point for the search procedure /// Vehicle that shall be parked (used for gathering vehicle geometry information) /// Home building id of the citizen (citizens are not allowed to park their car on foreign residential premises) /// Vehicle that shall be parked /// maximum allowed distance between target position and parking space location /// identified parking space location type (only valid if method returns true) /// identified parking space location identifier (only valid if method returns true) /// identified parking space position (only valid if method returns true) /// identified parking space rotation (only valid if method returns true) /// identified parking space offset (only valid if method returns true) /// true if a parking space could be found, false otherwise bool FindParkingSpaceInVicinity(Vector3 targetPos, VehicleInfo vehicleInfo, ushort homeId, ushort vehicleId, float maxDist, out ExtParkingSpaceLocation parkingSpaceLocation, out ushort parkingSpaceLocationId, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset); /// /// Tries to find a parking space for a moving vehicle at a given segment. The search /// is restricted to the given segment. /// /// vehicle that shall be parked (used for gathering vehicle geometry information) /// if true, already parked vehicles are ignored /// segment to search on /// current vehicle position /// identified parking space position (only valid if method returns true) /// identified parking space rotation (only valid if method returns true) /// identified parking space offset (only valid if method returns true) /// identified parking space lane id (only valid if method returns true) /// identified parking space lane index (only valid if method returns true) /// true if a parking space could be found, false otherwise bool FindParkingSpaceRoadSideForVehiclePos(VehicleInfo vehicleInfo, ushort ignoreParked, ushort segmentId, Vector3 refPos, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset, out uint laneId, out int laneIndex); /// /// Tries to find a road-side parking space in the vicinity of the given position . /// /// if true, already parked vehicles are ignored /// Target position that is used as a center point for the search procedure /// vehicle width /// vehicle length /// Maximum allowed distance between the target position and the parking space /// identified parking space position (only valid if method returns true) /// identified parking space rotation (only valid if method returns true) /// identified parking space offset (only valid if method returns true) /// true if a parking space could be found, false otherwise bool FindParkingSpaceRoadSide(ushort ignoreParked, Vector3 refPos, float width, float length, float maxDistance, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset); /// /// Tries to find a parking space at a building in the vicinity of the given position . /// /// vehicle that shall be parked (used for gathering vehicle geometry information) /// Home building id of the citizen (citizens are not allowed to park their car on foreign residential premises) /// if true, already parked vehicles are ignored /// if != 0, the building is forced to be "accessible" from this segment (where accessible means "close enough") /// Target position that is used as a center point for the search procedure /// Maximum allowed distance between the target position and the parking building /// Maximum allowed distance between the target position and the parking space /// identified parking space position (only valid if method returns true) /// identified parking space rotation (only valid if method returns true) /// identified parking space offset (only valid if method returns true and a segment id was given) /// true if a parking space could be found, false otherwise bool FindParkingSpaceBuilding(VehicleInfo vehicleInfo, ushort homeID, ushort ignoreParked, ushort segmentId, Vector3 refPos, float maxBuildingDistance, float maxParkingSpaceDistance, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset); /// /// Tries to find a parking space prop that belongs to the given building . /// /// vehicle that shall be parked (used for gathering vehicle geometry information) /// Home building id of the citizen (citizens are not allowed to park their car on foreign residential premises) /// if true, already parked vehicles are ignored /// Building that is queried /// Building data /// if != 0, the building is forced to be "accessible" from this segment (where accessible means "close enough") /// Target position that is used as a center point for the search procedure /// Maximum allowed distance between the target position and the parking space /// If true, search is randomized such that not always only the closest parking space is selected. /// identified parking space position (only valid if method returns true) /// identified parking space rotation (only valid if method returns true) /// identified parking space offset (only valid if method returns true and a segment id was given) /// true if a parking space could be found, false otherwise bool FindParkingSpacePropAtBuilding(VehicleInfo vehicleInfo, ushort homeID, ushort ignoreParked, ushort buildingID, ref Building building, ushort segmentId, Vector3 refPos, ref float maxDistance, bool randomize, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset); } } ================================================ FILE: TLM/TLM/Manager/ICustomDataManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { public interface ICustomDataManager { // TODO documentation bool LoadData(T data); T SaveData(ref bool success); } } ================================================ FILE: TLM/TLM/Manager/ICustomManager.cs ================================================ using GenericGameBridge.Factory; using System; using System.Collections.Generic; using System.Text; namespace TrafficManager.Manager { public interface ICustomManager { // TODO documentation IServiceFactory Services { get; } void OnBeforeLoadData(); void OnAfterLoadData(); void OnBeforeSaveData(); void OnAfterSaveData(); void OnLevelLoading(); void OnLevelUnloading(); void PrintDebugInfo(); } } ================================================ FILE: TLM/TLM/Manager/ICustomSegmentLightsManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Geometry; using TrafficManager.Traffic; using TrafficManager.TrafficLight; namespace TrafficManager.Manager { public interface ICustomSegmentLightsManager { // TODO documentation ICustomSegmentLights GetSegmentLights(ushort nodeId, ushort segmentId); ICustomSegmentLights GetSegmentLights(ushort segmentId, bool startNode, bool add = true, RoadBaseAI.TrafficLightState lightState = RoadBaseAI.TrafficLightState.Red); ICustomSegmentLights GetOrLiveSegmentLights(ushort segmentId, bool startNode); void AddNodeLights(ushort nodeId); void RemoveNodeLights(ushort nodeId); void RemoveSegmentLights(ushort segmentId); void RemoveSegmentLight(ushort segmentId, bool startNode); bool IsSegmentLight(ushort segmentId, bool startNode); void SetLightMode(ushort segmentId, bool startNode, ExtVehicleType vehicleType, LightMode mode); bool ApplyLightModes(ushort segmentId, bool startNode, ICustomSegmentLights otherLights); bool SetSegmentLights(ushort nodeId, ushort segmentId, ICustomSegmentLights lights); short ClockwiseIndexOfSegmentEnd(ISegmentEndId endId); } } ================================================ FILE: TLM/TLM/Manager/IExtBuildingManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { public interface IExtBuildingManager { // TODO define me! } } ================================================ FILE: TLM/TLM/Manager/IExtCitizenInstanceManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Traffic.Data; using UnityEngine; namespace TrafficManager.Manager { public interface IExtCitizenInstanceManager { // TODO define me! void ResetInstance(ushort instanceId); /// /// Determines whether the given citizen instance is located at an outside connection based on the given start position. /// /// citizen instance id /// citizen instance data /// extended citizen instance data /// start position /// true if the citizen instance is located at an outside connection, false otherwise bool IsAtOutsideConnection(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, Vector3 startPos); } } ================================================ FILE: TLM/TLM/Manager/IExtCitizenManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { public interface IExtCitizenManager { // TODO define me! void ResetCitizen(uint citizenId); /// /// Called whenever a citizen reaches their destination building. /// /// citizen id /// citizen data void OnArriveAtDestination(uint citizenId, ref Citizen citizen); } } ================================================ FILE: TLM/TLM/Manager/IFeatureManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { /// /// Represents a manager that handles logic bound to a certain feature /// public interface IFeatureManager { /// /// Handles disabling the managed feature /// void OnDisableFeature(); /// /// Handles enabling the managed feature /// void OnEnableFeature(); } } ================================================ FILE: TLM/TLM/Manager/IGeometryManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Geometry.Impl; using TrafficManager.Util; using static TrafficManager.Geometry.Impl.NodeGeometry; namespace TrafficManager.Manager { public struct GeometryUpdate { public SegmentGeometry segmentGeometry { get; private set; } public NodeGeometry nodeGeometry { get; private set; } public SegmentEndReplacement replacement { get; private set; } public GeometryUpdate(SegmentGeometry segmentGeometry) { this.segmentGeometry = segmentGeometry; nodeGeometry = null; replacement = default(SegmentEndReplacement); } public GeometryUpdate(NodeGeometry nodeGeometry) { this.nodeGeometry = nodeGeometry; segmentGeometry = null; replacement = default(SegmentEndReplacement); } public GeometryUpdate(SegmentEndReplacement replacement) { this.replacement = replacement; segmentGeometry = null; nodeGeometry = null; } } public interface IGeometryManager { // TODO define me! void SimulationStep(bool onylFirstPass=false); void OnUpdateSegment(SegmentGeometry geo); void OnSegmentEndReplacement(SegmentEndReplacement replacement); IDisposable Subscribe(IObserver observer); void MarkAsUpdated(SegmentGeometry geometry, bool updateNodes = true); void MarkAsUpdated(NodeGeometry geometry, bool updateSegments = false); void MarkAllAsUpdated(); } } ================================================ FILE: TLM/TLM/Manager/IJunctionRestrictionsManager.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { public interface IJunctionRestrictionsManager { /// /// Determines if u-turn behavior may be controlled at the given segment end. /// /// segment id /// at start node? /// node data /// true if u-turns may be customized, false otherwise bool IsUturnAllowedConfigurable(ushort segmentId, bool startNode, ref NetNode node); /// /// Determines if turn-on-red behavior is enabled for near turns and may be controlled at the given segment end. /// /// segment id /// at start node? /// node data /// true if turn-on-red may be customized for near turns, false otherwise bool IsNearTurnOnRedAllowedConfigurable(ushort segmentId, bool startNode, ref NetNode node); /// /// Determines if turn-on-red behavior is enabled for far turns and may be controlled at the given segment end. /// /// segment id /// at start node? /// node data /// true if turn-on-red may be customized for far turns, false otherwise bool IsFarTurnOnRedAllowedConfigurable(ushort segmentId, bool startNode, ref NetNode node); /// /// Determines if turn-on-red behavior is enabled for the given turn type and that it may be controlled at the given segment end. /// /// for near turns? /// segment id /// at start node? /// node data /// true if turn-on-red may be customized, false otherwise bool IsTurnOnRedAllowedConfigurable(bool near, ushort segmentId, bool startNode, ref NetNode node); /// /// Determines if lane changing behavior may be controlled at the given segment end. /// /// segment id /// at start node? /// node data /// true if lane changing may be customized, false otherwise bool IsLaneChangingAllowedWhenGoingStraightConfigurable(ushort segmentId, bool startNode, ref NetNode node); /// /// Determines if entering blocked junctions may be controlled at the given segment end. /// /// segment id /// at start node? /// node data /// true if entering blocked junctions may be customized, false otherwise bool IsEnteringBlockedJunctionAllowedConfigurable(ushort segmentId, bool startNode, ref NetNode node); /// /// Determines if pedestrian crossings may be controlled at the given segment end. /// /// segment id /// at start node? /// node data /// true if pedestrian crossings may be customized, false otherwise bool IsPedestrianCrossingAllowedConfigurable(ushort segmentId, bool startNode, ref NetNode node); /// /// Determines the default setting for u-turns at the given segment end. /// /// segment id /// at start node? /// node data /// true if u-turns are allowed by default, false otherwise bool GetDefaultUturnAllowed(ushort segmentId, bool startNode, ref NetNode node); /// /// Determines the default setting for near turn-on-red at the given segment end. /// /// segment id /// at start node? /// node data /// true if turn-on-red is allowed for near turns by default, false otherwise bool GetDefaultNearTurnOnRedAllowed(ushort segmentId, bool startNode, ref NetNode node); /// /// Determines the default setting for far turn-on-red at the given segment end. /// /// segment id /// at start node? /// node data /// true if turn-on-red is allowed for far turns by default, false otherwise bool GetDefaultFarTurnOnRedAllowed(ushort segmentId, bool startNode, ref NetNode node); /// /// Determines the default turn-on-red setting for the given turn type at the given segment end. /// /// for near turns? /// segment id /// at start node? /// node data /// true if turn-on-red is allowed by default, false otherwise bool GetDefaultTurnOnRedAllowed(bool near, ushort segmentId, bool startNode, ref NetNode node); /// /// Determines the default setting for straight lane changes at the given segment end. /// /// segment id /// at start node? /// node data /// true if straight lane changes are allowed by default, false otherwise bool GetDefaultLaneChangingAllowedWhenGoingStraight(ushort segmentId, bool startNode, ref NetNode node); /// /// Determines the default setting for entering a blocked junction at the given segment end. /// /// segment id /// at start node? /// node data /// true if entering a blocked junction is allowed by default, false otherwise bool GetDefaultEnteringBlockedJunctionAllowed(ushort segmentId, bool startNode, ref NetNode node); /// /// Determines the default setting for pedestrian crossings at the given segment end. /// /// segment id /// at start node? /// node data /// true if crossing the road is allowed by default, false otherwise bool GetDefaultPedestrianCrossingAllowed(ushort segmentId, bool startNode, ref NetNode node); /// /// Determines whether u-turns are allowed at the given segment end. /// /// segment id /// at start node? /// true if u-turns are allowed, false otherwise bool IsUturnAllowed(ushort segmentId, bool startNode); /// /// Determines whether turn-on-red is allowed for near turns at the given segment end. /// /// segment id /// at start node? /// true if turn-on-red is allowed for near turns, false otherwise bool IsNearTurnOnRedAllowed(ushort segmentId, bool startNode); /// /// Determines whether turn-on-red is allowed for far turns at the given segment end. /// /// segment id /// at start node? /// true if turn-on-red is allowed for far turns, false otherwise bool IsFarTurnOnRedAllowed(ushort segmentId, bool startNode); /// /// Determines whether turn-on-red is allowed for the given turn type at the given segment end. /// /// for near turns? /// segment id /// at start node? /// true if turn-on-red is allowed, false otherwise bool IsTurnOnRedAllowed(bool near, ushort segmentId, bool startNode); /// /// Determines whether lane changing when going straight is allowed at the given segment end. /// /// segment id /// at start node? /// true if lane changing when going straight is allowed, false otherwise bool IsLaneChangingAllowedWhenGoingStraight(ushort segmentId, bool startNode); /// /// Determines whether entering a blocked junction is allowed at the given segment end. /// /// segment id /// at start node? /// true if entering a blocked junction is allowed, false otherwise bool IsEnteringBlockedJunctionAllowed(ushort segmentId, bool startNode); /// /// Determines whether crossing the road is allowed at the given segment end. /// /// segment id /// at start node? /// true if crossing the road is allowed, false otherwise bool IsPedestrianCrossingAllowed(ushort segmentId, bool startNode); /// /// Retrieves the u-turn setting for the given segment end. /// /// segment id /// at start node? /// ternary u-turn flag TernaryBool GetUturnAllowed(ushort segmentId, bool startNode); /// /// Retrieves the turn-on-red setting for near turns and the given segment end. /// /// segment id /// at start node? /// ternary turn-on-red flag for near turns TernaryBool GetNearTurnOnRedAllowed(ushort segmentId, bool startNode); /// /// Retrieves the turn-on-red setting for far turns and the given segment end. /// /// segment id /// at start node? /// ternary turn-on-red flag for far turns TernaryBool GetFarTurnOnRedAllowed(ushort segmentId, bool startNode); /// /// Retrieves the turn-on-red setting for the given turn type and segment end. /// /// for near turns? /// segment id /// at start node? /// ternary turn-on-red flag TernaryBool GetTurnOnRedAllowed(bool near, ushort segmentId, bool startNode); /// /// Retrieves the lane changing setting for the given segment end. /// /// segment id /// at start node? /// ternary lane changing flag TernaryBool GetLaneChangingAllowedWhenGoingStraight(ushort segmentId, bool startNode); /// /// Retrieves the "enter blocked junction" setting for the given segment end. /// /// segment id /// at start node? /// ternary "enter blocked junction" flag TernaryBool GetEnteringBlockedJunctionAllowed(ushort segmentId, bool startNode); /// /// Retrieves the pedestrian crossing setting for the given segment end. /// /// segment id /// at start node? /// ternary pedestrian crossing flag TernaryBool GetPedestrianCrossingAllowed(ushort segmentId, bool startNode); /// /// Switches the u-turn flag for the given segment end. /// /// segment id /// at start node? /// true on success, false otherwise bool ToggleUturnAllowed(ushort segmentId, bool startNode); /// /// Switches the turn-on-red flag for near turns and given segment end. /// /// segment id /// at start node? /// true on success, false otherwise bool ToggleNearTurnOnRedAllowed(ushort segmentId, bool startNode); /// /// Switches the turn-on-red flag for far turns and given segment end. /// /// segment id /// at start node? /// true on success, false otherwise bool ToggleFarTurnOnRedAllowed(ushort segmentId, bool startNode); /// /// Switches the turn-on-red flag for the given turn type and segment end. /// /// for near turns? /// segment id /// at start node? /// true on success, false otherwise bool ToggleTurnOnRedAllowed(bool near, ushort segmentId, bool startNode); /// /// Switches the lane changing flag for the given segment end. /// /// segment id /// at start node? /// true on success, false otherwise bool ToggleLaneChangingAllowedWhenGoingStraight(ushort segmentId, bool startNode); /// /// Switches the "enter blocked junction" flag for the given segment end. /// /// segment id /// at start node? /// true on success, false otherwise bool ToggleEnteringBlockedJunctionAllowed(ushort segmentId, bool startNode); /// /// Switches the pedestrian crossing flag for the given segment end. /// /// segment id /// at start node? /// true on success, false otherwise bool TogglePedestrianCrossingAllowed(ushort segmentId, bool startNode); /// /// Sets the u-turn flag for the given segment end to the given value. /// /// segment id /// at start node? /// new value /// true on success, false otherwise bool SetUturnAllowed(ushort segmentId, bool startNode, bool value); /// /// Sets the turn-on-red flag for near turns at the given segment end to the given value. /// /// segment id /// at start node? /// new value /// true on success, false otherwise bool SetNearTurnOnRedAllowed(ushort segmentId, bool startNode, bool value); /// /// Sets the turn-on-red flag for far turns at the given segment end to the given value. /// /// segment id /// at start node? /// new value /// true on success, false otherwise bool SetFarTurnOnRedAllowed(ushort segmentId, bool startNode, bool value); /// /// Sets the turn-on-red flag for the given turn type and segment end to the given value. /// /// for near turns? /// segment id /// at start node? /// new value /// true on success, false otherwise bool SetTurnOnRedAllowed(bool near, ushort segmentId, bool startNode, bool value); /// /// Sets the lane changing flag for the given segment end to the given value. /// /// segment id /// at start node? /// new value /// true on success, false otherwise bool SetLaneChangingAllowedWhenGoingStraight(ushort segmentId, bool startNode, bool value); /// /// Sets the "enter blocked junction" flag for the given segment end to the given value. /// /// segment id /// at start node? /// new value /// true on success, false otherwise bool SetEnteringBlockedJunctionAllowed(ushort segmentId, bool startNode, bool value); /// /// Sets the pedestrian crossing flag for the given segment end to the given value. /// /// segment id /// at start node? /// new value /// true on success, false otherwise bool SetPedestrianCrossingAllowed(ushort segmentId, bool startNode, bool value); /// /// Updates the default values for all junction restrictions and segments. /// void UpdateAllDefaults(); } } ================================================ FILE: TLM/TLM/Manager/ILaneArrowManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { public interface ILaneArrowManager { // TODO define me! } } ================================================ FILE: TLM/TLM/Manager/ILaneConnectionManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { public interface ILaneConnectionManager { // TODO define me! /// /// Determines whether u-turn connections exist for the given segment end. /// /// segment id /// at start node? /// true if u-turn connections exist, false otherwise bool HasUturnConnections(ushort segmentId, bool startNode); } } ================================================ FILE: TLM/TLM/Manager/IManagerFactory.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Manager; namespace TrafficManager.Manager { public interface IManagerFactory { IAdvancedParkingManager AdvancedParkingManager { get; } ICustomSegmentLightsManager CustomSegmentLightsManager { get; } IExtBuildingManager ExtBuildingManager { get; } IExtCitizenInstanceManager ExtCitizenInstanceManager { get; } IExtCitizenManager ExtCitizenManager { get; } IJunctionRestrictionsManager JunctionRestrictionsManager { get; } ILaneArrowManager LaneArrowManager { get; } ILaneConnectionManager LaneConnectionManager { get; } IGeometryManager GeometryManager { get; } IOptionsManager OptionsManager { get; } IParkingRestrictionsManager ParkingRestrictionsManager { get; } IRoutingManager RoutingManager { get; } ISegmentEndManager SegmentEndManager { get; } ISpeedLimitManager SpeedLimitManager { get; } ITrafficLightManager TrafficLightManager { get; } ITrafficLightSimulationManager TrafficLightSimulationManager { get; } ITrafficMeasurementManager TrafficMeasurementManager { get; } ITrafficPriorityManager TrafficPriorityManager { get; } ITurnOnRedManager TurnOnRedManager { get; } IUtilityManager UtilityManager { get; } IVehicleBehaviorManager VehicleBehaviorManager { get; } IVehicleRestrictionsManager VehicleRestrictionsManager { get; } IVehicleStateManager VehicleStateManager { get; } } } ================================================ FILE: TLM/TLM/Manager/IOptionsManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { /// /// Manages mod options /// public interface IOptionsManager : ICustomDataManager { /// /// Determines if modifications to segments may be published in the current state. /// /// true if changes may be published, false otherwise bool MayPublishSegmentChanges(); } } ================================================ FILE: TLM/TLM/Manager/IParkingRestrictionsManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { public interface IParkingRestrictionsManager { // TODO define me! } } ================================================ FILE: TLM/TLM/Manager/IRoutingManager.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { public enum LaneEndTransitionType { /// /// No connection /// Invalid, /// /// Lane arrow or regular lane connection /// Default, /// /// Custom lane connection /// LaneConnection, /// /// Relaxed connection for road vehicles [!] that do not have to follow lane arrows /// Relaxed } public struct SegmentRoutingData { public bool startNodeOutgoingOneWay; public bool endNodeOutgoingOneWay; public bool highway; public override string ToString() { return $"[SegmentRoutingData\n" + "\t" + $"startNodeOutgoingOneWay = {startNodeOutgoingOneWay}\n" + "\t" + $"endNodeOutgoingOneWay = {endNodeOutgoingOneWay}\n" + "\t" + $"highway = {highway}\n" + "SegmentRoutingData]"; } public void Reset() { startNodeOutgoingOneWay = false; endNodeOutgoingOneWay = false; highway = false; } } public struct LaneEndRoutingData { public bool routed; public LaneTransitionData[] transitions; public override string ToString() { return $"[LaneEndRoutingData\n" + "\t" + $"routed = {routed}\n" + "\t" + $"transitions = {(transitions == null ? "" : transitions.ArrayToString())}\n" + "LaneEndRoutingData]"; } public void Reset() { routed = false; transitions = null; } public void RemoveTransition(uint laneId) { if (transitions == null) { return; } int index = -1; for (int i = 0; i < transitions.Length; ++i) { if (transitions[i].laneId == laneId) { index = i; break; } } if (index < 0) { return; } if (transitions.Length == 1) { Reset(); return; } LaneTransitionData[] newTransitions = new LaneTransitionData[transitions.Length - 1]; if (index > 0) { Array.Copy(transitions, 0, newTransitions, 0, index); } if (index < transitions.Length - 1) { Array.Copy(transitions, index + 1, newTransitions, index, transitions.Length - index - 1); } transitions = newTransitions; } public void AddTransitions(LaneTransitionData[] transitionsToAdd) { if (transitions == null) { transitions = transitionsToAdd; routed = true; return; } LaneTransitionData[] newTransitions = new LaneTransitionData[transitions.Length + transitionsToAdd.Length]; Array.Copy(transitions, newTransitions, transitions.Length); Array.Copy(transitionsToAdd, 0, newTransitions, transitions.Length, transitionsToAdd.Length); transitions = newTransitions; routed = true; } public void AddTransition(LaneTransitionData transition) { AddTransitions(new LaneTransitionData[1] { transition }); } } public struct LaneTransitionData { public uint laneId; public byte laneIndex; public LaneEndTransitionType type; public byte distance; public ushort segmentId; public bool startNode; public override string ToString() { return $"[LaneTransitionData\n" + "\t" + $"laneId = {laneId}\n" + "\t" + $"laneIndex = {laneIndex}\n" + "\t" + $"segmentId = {segmentId}\n" + "\t" + $"startNode = {startNode}\n" + "\t" + $"type = {type}\n" + "\t" + $"distance = {distance}\n" + "LaneTransitionData]"; } public void Set(uint laneId, byte laneIndex, LaneEndTransitionType type, ushort segmentId, bool startNode, byte distance) { this.laneId = laneId; this.laneIndex = laneIndex; this.type = type; this.distance = distance; this.segmentId = segmentId; this.startNode = startNode; } public void Set(uint laneId, byte laneIndex, LaneEndTransitionType type, ushort segmentId, bool startNode) { Set(laneId, laneIndex, type, segmentId, startNode, 0); } } public interface IRoutingManager { // TODO documentation void SimulationStep(); void RequestFullRecalculation(); void RequestRecalculation(ushort segmentId, bool propagate = true); } } ================================================ FILE: TLM/TLM/Manager/ISegmentEndManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Geometry; using TrafficManager.Traffic; using TrafficManager.TrafficLight; namespace TrafficManager.Manager { public interface ISegmentEndManager { // TODO documentation ISegmentEnd GetOrAddSegmentEnd(ISegmentEndId endId); ISegmentEnd GetOrAddSegmentEnd(ushort segmentId, bool startNode); ISegmentEnd GetSegmentEnd(ISegmentEndId endId); ISegmentEnd GetSegmentEnd(ushort segmentId, bool startNode); void RemoveSegmentEnd(ISegmentEndId endId); void RemoveSegmentEnd(ushort segmentId, bool startNode); void RemoveSegmentEnds(ushort segmentId); bool UpdateSegmentEnd(ISegmentEndId endId); bool UpdateSegmentEnd(ushort segmentId, bool startNode); } } ================================================ FILE: TLM/TLM/Manager/ISpeedLimitManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { public interface ISpeedLimitManager { // TODO define me! /// /// Retrieves the speed limit for the given lane without locking. /// /// segment id /// lane index /// lane id /// lane info /// speed limit in game units float GetLockFreeGameSpeedLimit(ushort segmentId, byte laneIndex, uint laneId, NetInfo.Lane laneInfo); } } ================================================ FILE: TLM/TLM/Manager/ITrafficLightManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { public enum UnableReason { None, NoJunction, HasTimedLight, IsLevelCrossing, InsufficientSegments } public interface ITrafficLightManager { // TODO documentation bool AddTrafficLight(ushort nodeId, ref NetNode node); bool AddTrafficLight(ushort nodeId, ref NetNode node, out UnableReason reason); bool HasTrafficLight(ushort nodeId, ref NetNode node); bool IsTrafficLightEnablable(ushort nodeId, ref NetNode node, out UnableReason reason); bool IsTrafficLightToggleable(ushort nodeId, bool flag, ref NetNode node, out UnableReason reason); bool RemoveTrafficLight(ushort nodeId, ref NetNode node); bool RemoveTrafficLight(ushort nodeId, ref NetNode node, out UnableReason reason); bool SetTrafficLight(ushort nodeId, bool flag, ref NetNode node); bool SetTrafficLight(ushort nodeId, bool flag, ref NetNode node, out UnableReason reason); bool ToggleTrafficLight(ushort nodeId, ref NetNode node); bool ToggleTrafficLight(ushort nodeId, ref NetNode node, out UnableReason reason); } } ================================================ FILE: TLM/TLM/Manager/ITrafficLightSimulationManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.TrafficLight; namespace TrafficManager.Manager { public interface ITrafficLightSimulationManager { // TODO documentation bool SetUpManualTrafficLight(ushort nodeId); bool SetUpTimedTrafficLight(ushort nodeId, IList nodeGroup); bool HasActiveSimulation(ushort nodeId); bool HasActiveTimedSimulation(ushort nodeId); bool HasSimulation(ushort nodeId); bool HasManualSimulation(ushort nodeId); bool HasTimedSimulation(ushort nodeId); void RemoveNodeFromSimulation(ushort nodeId, bool destroyGroup, bool removeTrafficLight); void SimulationStep(); } } ================================================ FILE: TLM/TLM/Manager/ITrafficMeasurementManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { public interface ITrafficMeasurementManager { // TODO define me! } } ================================================ FILE: TLM/TLM/Manager/ITrafficPriorityManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using static TrafficManager.Traffic.Data.PrioritySegment; namespace TrafficManager.Manager { public interface ITrafficPriorityManager { // TODO define me! /// /// Checks if a vehicle (the target vehicle) has to wait for other incoming vehicles at a junction with priority signs. /// /// target vehicle /// target vehicle data /// current path position /// transit node /// true if the transit node is the start node of the current segment /// next path position /// transit node data /// false if the target vehicle must wait for other vehicles, true otherwise bool HasPriority(ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Position curPos, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, ref NetNode transitNode); PriorityType GetPrioritySign(ushort segmentId, bool startNode); } } ================================================ FILE: TLM/TLM/Manager/ITurnOnRedManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Traffic.Data; namespace TrafficManager.Manager { public interface ITurnOnRedManager { TurnOnRedSegments[] TurnOnRedSegments { get; } /// /// Retrieves the array index for the given segment end id. /// /// segment id /// start node /// array index for the segment end id int GetIndex(ushort segmentId, bool startNode); } } ================================================ FILE: TLM/TLM/Manager/IUtilityManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Manager { public interface IUtilityManager { // TODO define me! } } ================================================ FILE: TLM/TLM/Manager/IVehicleBehaviorManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Traffic.Data; using UnityEngine; namespace TrafficManager.Manager { public interface IVehicleBehaviorManager { // TODO define me! // TODO documentation /// /// Checks if space reservation at is allowed. When a custom traffic light is active at the transit node /// space reservation is only allowed if the light is not red. /// /// transition node id /// source path position /// target path position /// bool IsSpaceReservationAllowed(ushort transitNodeId, PathUnit.Position sourcePos, PathUnit.Position targetPos); /// /// Determines if the given vehicle is driven by a reckless driver. /// Note that the result is cached in VehicleState for individual vehicles. /// /// /// /// bool IsRecklessDriver(ushort vehicleId, ref Vehicle vehicleData); /// /// Identifies the best lane on the next segment. /// /// queried vehicle /// vehicle data /// vehicle state /// current lane id /// current path position /// current segment info /// 1st next path position /// 1st next segment info /// 2nd next path position /// 3rd next path position /// 4th next path position /// target position lane index int FindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref VehicleState vehicleState, uint currentLaneId, PathUnit.Position currentPathPos, NetInfo currentSegInfo, PathUnit.Position next1PathPos, NetInfo next1SegInfo, PathUnit.Position next2PathPos, NetInfo next2SegInfo, PathUnit.Position next3PathPos, NetInfo next3SegInfo, PathUnit.Position next4PathPos); /// /// Determines if the given vehicle is allowed to find an alternative lane. /// /// queried vehicle /// vehicle data /// vehicle state /// bool MayFindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref VehicleState vehicleState); /// /// Calculates the current randomization value for a vehicle. /// The value changes over time. /// /// vehicle id /// a number between 0 and 99 uint GetTimedVehicleRand(ushort vehicleId); /// /// Calculates the randomization value for a vehicle. /// The value is static throughout the vehicle's lifetime. /// /// vehicle id /// a number between 0 and 99 uint GetStaticVehicleRand(ushort vehicleId); /// /// Applies realistic speed multipliers to the given velocity. /// /// vehicle target velocity /// vehicle id /// vehicle state /// vehicle info /// modified target velocity float ApplyRealisticSpeeds(float speed, ushort vehicleId, ref VehicleState vehicleState, VehicleInfo vehicleInfo); /// /// Calculates the target velocity for the given vehicle. /// /// vehicle id /// vehicle state /// vehicle info /// current path position /// segment data /// current world position /// vehicle target velocity /// modified target velocity float CalcMaxSpeed(ushort vehicleId, ref VehicleState state, VehicleInfo vehicleInfo, PathUnit.Position position, ref NetSegment segment, Vector3 pos, float maxSpeed); } } ================================================ FILE: TLM/TLM/Manager/IVehicleRestrictionsManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Traffic; namespace TrafficManager.Manager { public enum VehicleRestrictionsMode { /// /// Interpret bus lanes as "free for all" /// Unrestricted, /// /// Interpret bus lanes according to the configuration /// Configured, /// /// Interpret bus lanes as restricted /// Restricted } /// /// Represents vehicle restrictions effect strength /// public enum VehicleRestrictionsAggression { /// /// Low aggression /// Low = 0, /// /// Medium aggression /// Medium = 1, /// /// High aggression /// High = 2, /// /// Strict aggression /// Strict = 3 } public interface IVehicleRestrictionsManager { // TODO documentation void AddAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType); ExtVehicleType GetAllowedVehicleTypes(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode); ExtVehicleType GetAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode); IDictionary GetAllowedVehicleTypesAsDict(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode); HashSet GetAllowedVehicleTypesAsSet(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode); ExtVehicleType GetBaseMask(uint laneId, VehicleRestrictionsMode includeBusLanes); ExtVehicleType GetBaseMask(NetInfo.Lane laneInfo, VehicleRestrictionsMode includeBusLanes); ExtVehicleType GetDefaultAllowedVehicleTypes(NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode); ExtVehicleType GetDefaultAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode); bool IsAllowed(ExtVehicleType? allowedTypes, ExtVehicleType vehicleType); bool IsBicycleAllowed(ExtVehicleType? allowedTypes); bool IsBlimpAllowed(ExtVehicleType? allowedTypes); bool IsBusAllowed(ExtVehicleType? allowedTypes); bool IsCableCarAllowed(ExtVehicleType? allowedTypes); bool IsCargoTrainAllowed(ExtVehicleType? allowedTypes); bool IsCargoTruckAllowed(ExtVehicleType? allowedTypes); bool IsEmergencyAllowed(ExtVehicleType? allowedTypes); bool IsFerryAllowed(ExtVehicleType? allowedTypes); bool IsMonorailSegment(NetInfo segmentInfo); bool IsPassengerCarAllowed(ExtVehicleType? allowedTypes); bool IsPassengerTrainAllowed(ExtVehicleType? allowedTypes); bool IsRailLane(NetInfo.Lane laneInfo); bool IsRailSegment(NetInfo segmentInfo); bool IsRailVehicleAllowed(ExtVehicleType? allowedTypes); bool IsRoadLane(NetInfo.Lane laneInfo); bool IsRoadSegment(NetInfo segmentInfo); bool IsRoadVehicleAllowed(ExtVehicleType? allowedTypes); bool IsServiceAllowed(ExtVehicleType? allowedTypes); bool IsTaxiAllowed(ExtVehicleType? allowedTypes); bool IsTramAllowed(ExtVehicleType? allowedTypes); bool IsTramLane(NetInfo.Lane laneInfo); bool LoadData(List data); void NotifyStartEndNode(ushort segmentId); void OnLevelUnloading(); void RemoveAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType); List SaveData(ref bool success); void ToggleAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType, bool add); } } ================================================ FILE: TLM/TLM/Manager/IVehicleStateManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Traffic.Data; namespace TrafficManager.Manager { public interface IVehicleStateManager { // TODO define me! // TODO documentation VehicleState[] VehicleStates { get; } void SetNextVehicleIdOnSegment(ushort vehicleId, ushort nextVehicleId); void SetPreviousVehicleIdOnSegment(ushort vehicleId, ushort previousVehicleId); } } ================================================ FILE: TLM/TLM/Manager/Impl/AdvancedParkingManager.cs ================================================ using ColossalFramework; using ColossalFramework.Globalization; using ColossalFramework.Math; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Custom.AI; using TrafficManager.Custom.PathFinding; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Traffic.Data; using TrafficManager.UI; using TrafficManager.Util; using UnityEngine; using static TrafficManager.Traffic.Data.ExtCitizenInstance; namespace TrafficManager.Manager.Impl { public class AdvancedParkingManager : AbstractFeatureManager, IAdvancedParkingManager { public static AdvancedParkingManager Instance { get; private set; } = null; static AdvancedParkingManager() { Instance = new AdvancedParkingManager(); } protected override void OnDisableFeatureInternal() { for (int citizenInstanceId = 0; citizenInstanceId < ExtCitizenInstanceManager.Instance.ExtInstances.Length; ++citizenInstanceId) { ExtPathMode pathMode = ExtCitizenInstanceManager.Instance.ExtInstances[citizenInstanceId].pathMode; switch (pathMode) { case ExtPathMode.RequiresWalkingPathToParkedCar: case ExtPathMode.CalculatingWalkingPathToParkedCar: case ExtPathMode.WalkingToParkedCar: case ExtPathMode.ApproachingParkedCar: // citizen requires a path to their parked car: release instance to prevent it from floating Services.CitizenService.ReleaseCitizenInstance((ushort)citizenInstanceId); break; case ExtPathMode.RequiresCarPath: case ExtPathMode.RequiresMixedCarPathToTarget: case ExtPathMode.CalculatingCarPathToKnownParkPos: case ExtPathMode.CalculatingCarPathToTarget: case ExtPathMode.DrivingToKnownParkPos: case ExtPathMode.DrivingToTarget: if (Services.CitizenService.CheckCitizenInstanceFlags((ushort)citizenInstanceId, CitizenInstance.Flags.Character)) { // citizen instance requires a car but is walking: release instance to prevent it from floating Services.CitizenService.ReleaseCitizenInstance((ushort)citizenInstanceId); } break; } } ExtCitizenManager.Instance.Reset(); ExtCitizenInstanceManager.Instance.Reset(); } protected override void OnEnableFeatureInternal() { } public ExtSoftPathState UpdateCitizenPathState(ushort citizenInstanceId, ref CitizenInstance citizenInstance, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, ref Citizen citizen, ExtPathState mainPathState) { #if DEBUG bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == citizenInstanceId) && (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenInstance.m_citizen) && (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == citizenInstance.m_sourceBuilding) && (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == citizenInstance.m_targetBuilding) ; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; if (fineDebug) Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}) called."); #endif if (mainPathState == ExtPathState.Calculating) { // main path is still calculating, do not check return path #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): still calculating main path. returning CALCULATING."); #endif return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); } //ExtCitizenInstance extInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(citizenInstanceId); if (!extInstance.IsValid()) { // no citizen #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): no citizen found!"); #endif return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); } if (mainPathState == ExtPathState.None || mainPathState == ExtPathState.Failed) { // main path failed or non-existing #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): mainPathSate is {mainPathState}."); #endif if (mainPathState == ExtPathState.Failed) { #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Checking if path-finding may be repeated."); #endif return OnCitizenPathFindFailure(citizenInstanceId, ref citizenInstance, ref extInstance, ref extCitizen); } else { #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Resetting instance and returning FAILED."); #endif extInstance.Reset(); return ExtSoftPathState.FailedHard; } } // main path state is READY // main path calculation succeeded: update return path state and check its state if necessary extInstance.UpdateReturnPathState(); bool success = true; switch (extInstance.returnPathState) { case ExtPathState.None: default: // no return path calculated: ignore #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): return path state is None. Ignoring and returning main path state."); #endif break; case ExtPathState.Calculating: // OK // return path not read yet: wait for it #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): return path state is still calculating."); #endif return ExtSoftPathState.Calculating; case ExtPathState.Failed: // OK // no walking path from parking position to target found. flag main path as 'failed'. #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Return path FAILED."); #endif success = false; break; case ExtPathState.Ready: // handle valid return path #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Path is READY."); #endif break; } extInstance.ReleaseReturnPath(); if (success) { // handle path find success return OnCitizenPathFindSuccess(citizenInstanceId, ref citizenInstance, ref extInstance, ref extCitizen, ref citizen); } else { // handle path find failure return OnCitizenPathFindFailure(citizenInstanceId, ref citizenInstance, ref extInstance, ref extCitizen); } } public ExtSoftPathState UpdateCarPathState(ushort vehicleId, ref Vehicle vehicleData, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, ExtPathState mainPathState) { #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 (fineDebug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}) called."); #endif if (mainPathState == ExtPathState.Calculating) { // main path is still calculating, do not check return path #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): still calculating main path. returning CALCULATING."); #endif return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); } //ExtCitizenInstance driverExtInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(CustomPassengerCarAI.GetDriverInstance(vehicleId, ref vehicleData)); if (!driverExtInstance.IsValid()) { // no driver #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): no driver found!"); #endif return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); } if (VehicleStateManager.Instance.VehicleStates[vehicleId].vehicleType != ExtVehicleType.PassengerCar) { // non-passenger cars are not handled #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): not a passenger car!"); #endif driverExtInstance.Reset(); return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); } if (mainPathState == ExtPathState.None || mainPathState == ExtPathState.Failed) { // main path failed or non-existing: reset return path #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): mainPathSate is {mainPathState}."); #endif if (mainPathState == ExtPathState.Failed) { #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Checking if path-finding may be repeated."); #endif driverExtInstance.ReleaseReturnPath(); return OnCarPathFindFailure(vehicleId, ref vehicleData, ref driverInstance, ref driverExtInstance); } else { #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Resetting instance and returning FAILED."); #endif driverExtInstance.Reset(); return ExtSoftPathState.FailedHard; } } // main path state is READY // main path calculation succeeded: update return path state and check its state driverExtInstance.UpdateReturnPathState(); switch (driverExtInstance.returnPathState) { case ExtPathState.None: default: // no return path calculated: ignore #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): return path state is None. Setting pathMode=DrivingToTarget and returning main path state."); #endif driverExtInstance.pathMode = ExtPathMode.DrivingToTarget; return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); case ExtPathState.Calculating: // return path not read yet: wait for it #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): return path state is still calculating."); #endif return ExtSoftPathState.Calculating; case ExtPathState.Failed: // no walking path from parking position to target found. flag main path as 'failed'. #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Return path {driverExtInstance.returnPathId} FAILED. Forcing path-finding to fail."); #endif driverExtInstance.Reset(); return ExtSoftPathState.FailedHard; case ExtPathState.Ready: // handle valid return path driverExtInstance.ReleaseReturnPath(); #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Path is ready for vehicle {vehicleId}, citizen instance {driverExtInstance.instanceId}! CurrentPathMode={driverExtInstance.pathMode}"); #endif byte laneTypes = CustomPathManager._instance.m_pathUnits.m_buffer[vehicleData.m_path].m_laneTypes; bool usesPublicTransport = (laneTypes & (byte)(NetInfo.LaneType.PublicTransport)) != 0; if (usesPublicTransport && (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos || driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos)) { driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; driverExtInstance.parkingSpaceLocation = ExtParkingSpaceLocation.None; driverExtInstance.parkingSpaceLocationId = 0; } if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos) { driverExtInstance.pathMode = ExtPathMode.DrivingToAltParkPos; driverExtInstance.parkingPathStartPosition = null; #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Path to an alternative parking position is READY for vehicle {vehicleId}! CurrentPathMode={driverExtInstance.pathMode}"); #endif } else if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget) { driverExtInstance.pathMode = ExtPathMode.DrivingToTarget; #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Car path is READY for vehicle {vehicleId}! CurrentPathMode={driverExtInstance.pathMode}"); #endif } else if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos) { driverExtInstance.pathMode = ExtPathMode.DrivingToKnownParkPos; #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Car path to known parking position is READY for vehicle {vehicleId}! CurrentPathMode={driverExtInstance.pathMode}"); #endif } return ExtSoftPathState.Ready; } } public ParkedCarApproachState CitizenApproachingParkedCarSimulationStep(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, Vector3 physicsLodRefPos, ref VehicleParked parkedCar) { #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 if ((instanceData.m_flags & CitizenInstance.Flags.WaitingPath) != CitizenInstance.Flags.None) { #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.CheckCitizenReachedParkedCar({instanceId}): citizen instance {instanceId} is waiting for path-finding to complete."); #endif return ParkedCarApproachState.None; } //ExtCitizenInstance extInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(instanceId); if (extInstance.pathMode != ExtPathMode.ApproachingParkedCar && extInstance.pathMode != ExtPathMode.WalkingToParkedCar) { #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): citizen instance {instanceId} is not reaching a parked car ({extInstance.pathMode})"); #endif return ParkedCarApproachState.None; } if ((instanceData.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) { #if DEBUG /*if (fineDebug) Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): citizen instance {instanceId} is not spawned!");*/ #endif return ParkedCarApproachState.None; } Vector3 lastFramePos = instanceData.GetLastFramePosition(); Vector3 doorPosition = parkedCar.GetClosestDoorPosition(parkedCar.m_position, VehicleInfo.DoorType.Enter); if (extInstance.pathMode == ExtPathMode.WalkingToParkedCar) { // check if path is complete PathUnit.Position pos; if (instanceData.m_pathPositionIndex != 255 && (instanceData.m_path == 0 || !CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].GetPosition(instanceData.m_pathPositionIndex >> 1, out pos))) { extInstance.pathMode = ExtPathMode.ApproachingParkedCar; extInstance.lastDistanceToParkedCar = (instanceData.GetLastFramePosition() - doorPosition).sqrMagnitude; #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): citizen instance {instanceId} was walking to parked car and reached final path position. Switched PathMode to {extInstance.pathMode}."); #endif } } if (extInstance.pathMode == ExtPathMode.ApproachingParkedCar) { Vector3 doorTargetDir = doorPosition - lastFramePos; Vector3 doorWalkVector = doorPosition; float doorTargetDirMagnitude = doorTargetDir.magnitude; if (doorTargetDirMagnitude > 1f) { float speed = Mathf.Max(doorTargetDirMagnitude - 5f, doorTargetDirMagnitude * 0.5f); doorWalkVector = lastFramePos + (doorTargetDir * (speed / doorTargetDirMagnitude)); } instanceData.m_targetPos = new Vector4(doorWalkVector.x, doorWalkVector.y, doorWalkVector.z, 0.5f); instanceData.m_targetDir = VectorUtils.XZ(doorTargetDir); CitizenApproachingParkedCarSimulationStep(instanceId, ref instanceData, physicsLodRefPos); float doorSqrDist = (instanceData.GetLastFramePosition() - doorPosition).sqrMagnitude; if (doorSqrDist > GlobalConfig.Instance.ParkingAI.MaxParkedCarInstanceSwitchSqrDistance) { // citizen is still too far away from the parked car ExtPathMode oldPathMode = extInstance.pathMode; if (doorSqrDist > extInstance.lastDistanceToParkedCar + 1024f) { // distance has increased dramatically since the last time #if DEBUG if (debug) Log.Warning($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): Citizen instance {instanceId} is currently reaching their parked car but distance increased! dist={doorSqrDist}, LastDistanceToParkedCar={extInstance.lastDistanceToParkedCar}."); if (GlobalConfig.Instance.Debug.Switches[6]) { Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): FORCED PAUSE. Distance increased! Citizen instance {instanceId}. dist={doorSqrDist}"); Singleton.instance.SimulationPaused = true; } #endif CitizenInstance.Frame frameData = instanceData.GetLastFrameData(); frameData.m_position = doorPosition; instanceData.SetLastFrameData(frameData); extInstance.pathMode = ExtCitizenInstance.ExtPathMode.RequiresCarPath; return ParkedCarApproachState.Approached; } else if (doorSqrDist < extInstance.lastDistanceToParkedCar) { extInstance.lastDistanceToParkedCar = doorSqrDist; } #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): Citizen instance {instanceId} is currently reaching their parked car (dist={doorSqrDist}, LastDistanceToParkedCar={extInstance.lastDistanceToParkedCar}). CurrentDepartureMode={extInstance.pathMode}"); #endif return ParkedCarApproachState.Approaching; } else { extInstance.pathMode = ExtCitizenInstance.ExtPathMode.RequiresCarPath; #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): Citizen instance {instanceId} reached parking position (dist={doorSqrDist}). Calculating remaining path now. CurrentDepartureMode={extInstance.pathMode}"); #endif return ParkedCarApproachState.Approached; } } return ParkedCarApproachState.None; } protected void CitizenApproachingParkedCarSimulationStep(ushort instanceID, ref CitizenInstance instanceData, Vector3 physicsLodRefPos) { if ((instanceData.m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None) { CitizenInstance.Frame lastFrameData = instanceData.GetLastFrameData(); int oldGridX = Mathf.Clamp((int)(lastFrameData.m_position.x / (float)CitizenManager.CITIZENGRID_CELL_SIZE + (float)CitizenManager.CITIZENGRID_RESOLUTION / 2f), 0, CitizenManager.CITIZENGRID_RESOLUTION - 1); int oldGridY = Mathf.Clamp((int)(lastFrameData.m_position.z / (float)CitizenManager.CITIZENGRID_CELL_SIZE + (float)CitizenManager.CITIZENGRID_RESOLUTION / 2f), 0, CitizenManager.CITIZENGRID_RESOLUTION - 1); bool lodPhysics = Vector3.SqrMagnitude(physicsLodRefPos - lastFrameData.m_position) >= 62500f; CitizenApproachingParkedCarSimulationStep(instanceID, ref instanceData, ref lastFrameData, lodPhysics); int newGridX = Mathf.Clamp((int)(lastFrameData.m_position.x / (float)CitizenManager.CITIZENGRID_CELL_SIZE + (float)CitizenManager.CITIZENGRID_RESOLUTION / 2f), 0, CitizenManager.CITIZENGRID_RESOLUTION - 1); int newGridY = Mathf.Clamp((int)(lastFrameData.m_position.z / (float)CitizenManager.CITIZENGRID_CELL_SIZE + (float)CitizenManager.CITIZENGRID_RESOLUTION / 2f), 0, CitizenManager.CITIZENGRID_RESOLUTION - 1); if ((newGridX != oldGridX || newGridY != oldGridY) && (instanceData.m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None) { Singleton.instance.RemoveFromGrid(instanceID, ref instanceData, oldGridX, oldGridY); Singleton.instance.AddToGrid(instanceID, ref instanceData, newGridX, newGridY); } if (instanceData.m_flags != CitizenInstance.Flags.None) { instanceData.SetFrameData(Singleton.instance.m_currentFrameIndex, lastFrameData); } } } protected void CitizenApproachingParkedCarSimulationStep(ushort instanceID, ref CitizenInstance instanceData, ref CitizenInstance.Frame frameData, bool lodPhysics) { frameData.m_position = frameData.m_position + (frameData.m_velocity * 0.5f); Vector3 targetDiff = (Vector3)instanceData.m_targetPos - frameData.m_position; Vector3 targetVelDiff = targetDiff - frameData.m_velocity; float targetVelDiffMag = targetVelDiff.magnitude; targetVelDiff = targetVelDiff * (2f / Mathf.Max(targetVelDiffMag, 2f)); frameData.m_velocity = frameData.m_velocity + targetVelDiff; frameData.m_velocity = frameData.m_velocity - (Mathf.Max(0f, Vector3.Dot((frameData.m_position + frameData.m_velocity) - (Vector3)instanceData.m_targetPos, frameData.m_velocity)) / Mathf.Max(0.01f, frameData.m_velocity.sqrMagnitude) * frameData.m_velocity); if (frameData.m_velocity.sqrMagnitude > 0.01f) { frameData.m_rotation = Quaternion.LookRotation(frameData.m_velocity); } } public bool CitizenApproachingTargetSimulationStep(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance) { #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 if ((instanceData.m_flags & CitizenInstance.Flags.WaitingPath) != CitizenInstance.Flags.None) { #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is waiting for path-finding to complete."); #endif return false; } //ExtCitizenInstance extInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(instanceId); if (extInstance.pathMode != ExtCitizenInstance.ExtPathMode.WalkingToTarget && extInstance.pathMode != ExtCitizenInstance.ExtPathMode.TaxiToTarget) { #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is not reaching target ({extInstance.pathMode})"); #endif return false; } if ((instanceData.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) { #if DEBUG /*if (fineDebug) Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is not spawned!");*/ #endif return false; } // check if path is complete PathUnit.Position pos; if (instanceData.m_pathPositionIndex != 255 && (instanceData.m_path == 0 || !CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].GetPosition(instanceData.m_pathPositionIndex >> 1, out pos))) { extInstance.Reset(); #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): Citizen instance {instanceId} reached target. CurrentDepartureMode={extInstance.pathMode}"); #endif return true; } return false; } /// /// Handles a path-finding success for activated Parking AI. /// /// Citizen instance id /// Citizen instance data /// Extended citizen instance data /// Extended citizen data /// Citizen data /// soft path state protected ExtSoftPathState OnCitizenPathFindSuccess(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, ref Citizen citizenData) { #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($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Path-finding succeeded for citizen instance {instanceId}. Path: {instanceData.m_path} vehicle={citizenData.m_vehicle}"); #endif //ExtCitizenInstance extInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(instanceId); if (!extInstance.IsValid()) { #if DEBUG if (fineDebug) Log.Warning($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Ext. citizen instance not found."); #endif return ExtSoftPathState.FailedHard; } if (citizenData.m_vehicle == 0) { // citizen does not already have a vehicle assigned if (extInstance.pathMode == ExtPathMode.TaxiToTarget) { #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen uses a taxi. Decreasing public transport demand and returning READY."); #endif // cim uses taxi if (instanceData.m_sourceBuilding != 0) { ExtBuildingManager.Instance.ExtBuildings[instanceData.m_sourceBuilding].RemovePublicTransportDemand((uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); } if (instanceData.m_targetBuilding != 0) { ExtBuildingManager.Instance.ExtBuildings[instanceData.m_targetBuilding].RemovePublicTransportDemand((uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); } extCitizen.transportMode |= ExtCitizen.ExtTransportMode.PublicTransport; return ExtSoftPathState.Ready; } ushort parkedVehicleId = citizenData.m_parkedVehicle; float sqrDistToParkedVehicle = 0f; if (parkedVehicleId != 0) { // calculate distance to parked vehicle VehicleManager vehicleManager = Singleton.instance; Vector3 doorPosition = vehicleManager.m_parkedVehicles.m_buffer[parkedVehicleId].GetClosestDoorPosition(vehicleManager.m_parkedVehicles.m_buffer[parkedVehicleId].m_position, VehicleInfo.DoorType.Enter); sqrDistToParkedVehicle = (instanceData.GetLastFramePosition() - doorPosition).sqrMagnitude; } byte laneTypes = CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].m_laneTypes; ushort vehicleTypes = CustomPathManager._instance.m_pathUnits.m_buffer[instanceData.m_path].m_vehicleTypes; bool usesPublicTransport = (laneTypes & (byte)(NetInfo.LaneType.PublicTransport)) != 0; bool usesCar = (laneTypes & (byte)(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != 0 && (vehicleTypes & (ushort)(VehicleInfo.VehicleType.Car)) != 0; if (usesPublicTransport && usesCar && (extInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos || extInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos)) { /* * when using public transport together with a car (assuming a "source -> walk -> drive -> walk -> use public transport -> walk -> target" path) * discard parking space information since the cim has to park near the public transport stop * (instead of parking in the vicinity of the target building). * * TODO we could check if the path looks like "source -> walk -> use public transport -> walk -> drive -> [walk ->] target" (in this case parking space information would still be valid) */ #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen uses their car together with public transport. Discarding parking space information and setting path mode to CalculatingCarPathToTarget."); #endif extInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; extInstance.parkingSpaceLocation = ExtParkingSpaceLocation.None; extInstance.parkingSpaceLocationId = 0; } switch (extInstance.pathMode) { case ExtPathMode.None: // citizen starts at source building default: if (extInstance.pathMode != ExtPathMode.None) { #if DEBUG if (debug) Log.Warning($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Unexpected path mode {extInstance.pathMode}! {extInstance}"); #endif } if (usesCar) { #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Path for citizen instance {instanceId} contains passenger car section. Ensuring that citizen is allowed to use their car."); #endif ushort sourceBuildingId = instanceData.m_sourceBuilding; ushort homeId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].m_homeBuilding; if (parkedVehicleId == 0) { #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen {instanceData.m_citizen} (citizen instance {instanceId}), source building {sourceBuildingId} does not have a parked vehicle! CurrentPathMode={extInstance.pathMode}"); #endif // try to spawn parked vehicle in the vicinity of the starting point. VehicleInfo vehicleInfo = null; if (instanceData.Info.m_agePhase > Citizen.AgePhase.Child) { // get a random car info (due to the fact we are using a different randomizer, car assignment differs from the selection in ResidentAI.GetVehicleInfo/TouristAI.GetVehicleInfo method, but this should not matter since we are reusing parked vehicle infos there) vehicleInfo = Singleton.instance.GetRandomVehicleInfo(ref Singleton.instance.m_randomizer, ItemClass.Service.Residential, ItemClass.SubService.ResidentialLow, ItemClass.Level.Level1); } if (vehicleInfo != null) { #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen {instanceData.m_citizen} (citizen instance {instanceId}), source building {sourceBuildingId} is using their own passenger car. CurrentPathMode={extInstance.pathMode}"); #endif // determine current position vector Vector3 currentPos; ushort currentBuildingId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].GetBuildingByLocation(); if (currentBuildingId != 0) { currentPos = Singleton.instance.m_buildings.m_buffer[currentBuildingId].m_position; #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Taking current position from current building {currentBuildingId} for citizen {instanceData.m_citizen} (citizen instance {instanceId}): {currentPos} CurrentPathMode={extInstance.pathMode}"); #endif } else { currentBuildingId = sourceBuildingId; currentPos = instanceData.GetLastFramePosition(); #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Taking current position from last frame position for citizen {instanceData.m_citizen} (citizen instance {instanceId}): {currentPos}. Home {homeId} pos: {Singleton.instance.m_buildings.m_buffer[homeId].m_position} CurrentPathMode={extInstance.pathMode}"); #endif } // spawn a passenger car near the current position Vector3 parkPos; ParkingUnableReason parkReason; if (AdvancedParkingManager.Instance.TrySpawnParkedPassengerCar(instanceData.m_citizen, homeId, currentPos, vehicleInfo, out parkPos, out parkReason)) { parkedVehicleId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].m_parkedVehicle; #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Parked vehicle for citizen {instanceData.m_citizen} (instance {instanceId}) is {parkedVehicleId} now (parkPos={parkPos})."); #endif if (currentBuildingId != 0) { ExtBuildingManager.Instance.ExtBuildings[currentBuildingId].ModifyParkingSpaceDemand(parkPos, GlobalConfig.Instance.ParkingAI.MinSpawnedCarParkingSpaceDemandDelta, GlobalConfig.Instance.ParkingAI.MaxSpawnedCarParkingSpaceDemandDelta); } } else { #if DEBUG if (debug) { Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): >> Failed to spawn parked vehicle for citizen {instanceData.m_citizen} (citizen instance {instanceId}). reason={parkReason}. homePos: {Singleton.instance.m_buildings.m_buffer[homeId].m_position}"); } #endif if (parkReason == ParkingUnableReason.NoSpaceFound && currentBuildingId != 0) { ExtBuildingManager.Instance.ExtBuildings[currentBuildingId].AddParkingSpaceDemand(GlobalConfig.Instance.ParkingAI.FailedSpawnParkingSpaceDemandIncrement); } } } else { #if DEBUG if (debug) { Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen {instanceData.m_citizen} (citizen instance {instanceId}), source building {sourceBuildingId}, home {homeId} does not own a vehicle."); } #endif } } if (parkedVehicleId != 0) { // citizen has to reach their parked vehicle first #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Calculating path to reach parked vehicle {parkedVehicleId} for citizen instance {instanceId}. targetPos={instanceData.m_targetPos} lastFramePos={instanceData.GetLastFramePosition()}"); #endif extInstance.pathMode = ExtPathMode.RequiresWalkingPathToParkedCar; return ExtSoftPathState.FailedSoft; } else { // error! could not find/spawn parked car #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} still does not have a parked vehicle! Retrying: Cim should walk to target"); #endif extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; return ExtSoftPathState.FailedSoft; } } else { // path does not contain a car section: path can be reused for walking #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): A direct car path OR initial path was queried that does not contain a car section. Switching path mode to walking."); #endif if (usesPublicTransport) { // decrease public tranport demand if (instanceData.m_sourceBuilding != 0) { ExtBuildingManager.Instance.ExtBuildings[instanceData.m_sourceBuilding].RemovePublicTransportDemand((uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); } if (instanceData.m_targetBuilding != 0) { ExtBuildingManager.Instance.ExtBuildings[instanceData.m_targetBuilding].RemovePublicTransportDemand((uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); } extCitizen.transportMode |= ExtCitizen.ExtTransportMode.PublicTransport; } extInstance.pathMode = ExtPathMode.WalkingToTarget; return ExtSoftPathState.Ready; } case ExtPathMode.CalculatingCarPathToTarget: // citizen has not yet entered their car (but is close to do so) and tries to reach the target directly case ExtPathMode.CalculatingCarPathToKnownParkPos: // citizen has not yet entered their (but is close to do so) car and tries to reach a parking space in the vicinity of the target case ExtPathMode.CalculatingCarPathToAltParkPos: // citizen has not yet entered their car (but is close to do so) and tries to reach an alternative parking space in the vicinity of the target if (usesCar) { // parked car should be reached now #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Path for citizen instance {instanceId} contains passenger car section and citizen should stand in front of their car."); #endif if (extInstance.atOutsideConnection) { // car path calculated starting at road outside connection: success if (extInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos) { extInstance.pathMode = ExtPathMode.DrivingToAltParkPos; extInstance.parkingPathStartPosition = null; #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Path to an alternative parking position is READY! CurrentPathMode={extInstance.pathMode}"); #endif } else if (extInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget) { extInstance.pathMode = ExtPathMode.DrivingToTarget; #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Car path is READY! CurrentPathMode={extInstance.pathMode}"); #endif } else if (extInstance.pathMode == ExtPathMode.CalculatingCarPathToKnownParkPos) { extInstance.pathMode = ExtPathMode.DrivingToKnownParkPos; #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Car path to known parking position is READY! CurrentPathMode={extInstance.pathMode}"); #endif } extInstance.atOutsideConnection = false; // citizen leaves outside connection return ExtSoftPathState.Ready; } else if (parkedVehicleId == 0) { // error! could not find/spawn parked car #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} still does not have a parked vehicle! Retrying: Cim should walk to target"); #endif extInstance.Reset(); extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; return ExtSoftPathState.FailedSoft; } else if (sqrDistToParkedVehicle > 4f * GlobalConfig.Instance.ParkingAI.MaxParkedCarInstanceSwitchSqrDistance) { // error! parked car is too far away #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} cannot enter parked vehicle because it is too far away (sqrDistToParkedVehicle={sqrDistToParkedVehicle})! Retrying: Cim should walk to parked car"); #endif extInstance.pathMode = ExtPathMode.RequiresWalkingPathToParkedCar; return ExtSoftPathState.FailedSoft; } else { // path using passenger car has been calculated ushort vehicleId; if (CustomHumanAI.EnterParkedCar(instanceId, ref instanceData, parkedVehicleId, out vehicleId)) { // TODO move method body here extInstance.pathMode = extInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget ? ExtPathMode.DrivingToTarget : ExtPathMode.DrivingToKnownParkPos; extCitizen.transportMode |= ExtCitizen.ExtTransportMode.Car; #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} has entered their car and is now travelling by car (vehicleId={vehicleId}). CurrentDepartureMode={extInstance.pathMode}, targetPos={instanceData.m_targetPos} lastFramePos={instanceData.GetLastFramePosition()}"); #endif return ExtSoftPathState.Ignore; } else { // error! parked car could not be entered (reached vehicle limit?): try to walk to target #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Entering parked vehicle {parkedVehicleId} failed for citizen instance {instanceId}. Trying to walk to target. CurrentDepartureMode={extInstance.pathMode}"); #endif extInstance.Reset(); extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; return ExtSoftPathState.FailedSoft; } } } else { // citizen does not need a car for the calculated path... switch (extInstance.pathMode) { case ExtPathMode.CalculatingCarPathToTarget: // ... and the path can be reused for walking #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): A direct car path was queried that does not contain a car section. Switching path mode to walking."); #endif extInstance.Reset(); if (usesPublicTransport) { // decrease public tranport demand if (instanceData.m_sourceBuilding != 0) { ExtBuildingManager.Instance.ExtBuildings[instanceData.m_sourceBuilding].RemovePublicTransportDemand((uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); } if (instanceData.m_targetBuilding != 0) { ExtBuildingManager.Instance.ExtBuildings[instanceData.m_targetBuilding].RemovePublicTransportDemand((uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); } extCitizen.transportMode |= ExtCitizen.ExtTransportMode.PublicTransport; } extInstance.pathMode = ExtPathMode.WalkingToTarget; return ExtSoftPathState.Ready; case ExtPathMode.CalculatingCarPathToKnownParkPos: case ExtPathMode.CalculatingCarPathToAltParkPos: default: // ... and a path to a parking spot was calculated: dismiss path and restart path-finding for walking #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): A parking space car path was queried but it turned out that no car is needed. Retrying path-finding for walking."); #endif extInstance.Reset(); extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; return ExtSoftPathState.FailedSoft; } } case ExtPathMode.CalculatingWalkingPathToParkedCar: // path to parked vehicle has been calculated... if (parkedVehicleId == 0) { // ... but the parked vehicle has vanished #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} shall walk to their parked vehicle but it disappeared. Retrying path-find for walking."); #endif extInstance.Reset(); extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; return ExtSoftPathState.FailedSoft; } else { extInstance.pathMode = ExtPathMode.WalkingToParkedCar; #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} is now on their way to its parked vehicle. CurrentDepartureMode={extInstance.pathMode}, targetPos={instanceData.m_targetPos} lastFramePos={instanceData.GetLastFramePosition()}"); #endif return ExtSoftPathState.Ready; } case ExtPathMode.CalculatingWalkingPathToTarget: // final walking path to target has been calculated extInstance.pathMode = ExtPathMode.WalkingToTarget; #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen instance {instanceId} is now travelling by foot to their final target. CurrentDepartureMode={extInstance.pathMode}, targetPos={instanceData.m_targetPos} lastFramePos={instanceData.GetLastFramePosition()}"); #endif return ExtSoftPathState.Ready; } } else { // citizen has a vehicle assigned #if DEBUG if (debug) Log.Warning($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Citizen has a vehicle assigned but this method does not handle this situation. Forcing path-find to fail."); #endif extInstance.Reset(); return ExtSoftPathState.FailedHard; } } /// /// Handles a path-finding failure for citizen instances and activated Parking AI. /// /// Citizen instance id /// Citizen instance data /// extended citizen instance information /// extended citizen information /// if true path-finding may be repeated (path mode has been updated), false otherwise protected ExtSoftPathState OnCitizenPathFindFailure(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen) { #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($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Path-finding failed for citizen instance {extInstance.instanceId}. CurrentPathMode={extInstance.pathMode}"); #endif // update public transport demands switch (extInstance.pathMode) { case ExtPathMode.None: case ExtPathMode.CalculatingWalkingPathToTarget: case ExtPathMode.CalculatingWalkingPathToParkedCar: case ExtPathMode.TaxiToTarget: // could not reach target building by walking/driving/public transport: increase public transport demand if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) { #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Increasing public transport demand of target building {instanceData.m_targetBuilding} and source building {instanceData.m_sourceBuilding}"); #endif if (instanceData.m_targetBuilding != 0) { ExtBuildingManager.Instance.ExtBuildings[instanceData.m_targetBuilding].AddPublicTransportDemand((uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandIncrement, false); } if (instanceData.m_sourceBuilding != 0) { ExtBuildingManager.Instance.ExtBuildings[instanceData.m_sourceBuilding].AddPublicTransportDemand((uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandIncrement, true); } } break; } /* * relocate parked car if abandoned */ if (extInstance.pathMode == ExtPathMode.CalculatingWalkingPathToParkedCar) { /* * parked car is unreachable */ ushort parkedVehicleId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].m_parkedVehicle; if (parkedVehicleId != 0) { /* * parked car is present */ ushort homeId = 0; Services.CitizenService.ProcessCitizen(extCitizen.citizenId, delegate (uint citId, ref Citizen cit) { homeId = cit.m_homeBuilding; return true; }); // calculate distance between citizen and parked car bool movedCar = false; Vector3 citizenPos = instanceData.GetLastFramePosition(); float parkedToCitizen = 0f; Vector3 oldParkedVehiclePos = default(Vector3); Services.VehicleService.ProcessParkedVehicle(parkedVehicleId, delegate (ushort parkedVehId, ref VehicleParked parkedVehicle) { oldParkedVehiclePos = parkedVehicle.m_position; parkedToCitizen = (parkedVehicle.m_position - citizenPos).magnitude; if (parkedToCitizen > GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome) { /* * parked car is far away from current location * -> relocate parked car and try again */ movedCar = TryMoveParkedVehicle(parkedVehicleId, ref parkedVehicle, citizenPos, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome, homeId); } return true; }); if (movedCar) { /* * successfully moved the parked car to a closer location * -> retry path-finding */ extInstance.pathMode = ExtPathMode.RequiresWalkingPathToParkedCar; #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Relocated parked car {parkedVehicleId} to a closer location (old pos/distance: {oldParkedVehiclePos}/{parkedToCitizen}, new pos/distance: {Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position}/{(Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position - citizenPos).magnitude}) for citizen @ {citizenPos}. Retrying path-finding. CurrentPathMode={extInstance.pathMode}"); #endif return ExtSoftPathState.FailedSoft; } else { /* * could not move car * -> despawn parked car, walk to target or use public transport */ #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Releasing unreachable parked vehicle {parkedVehicleId} for citizen instance {extInstance.instanceId}. CurrentPathMode={extInstance.pathMode}"); #endif Singleton.instance.ReleaseParkedVehicle(parkedVehicleId); } } } // check if path-finding may be repeated ExtSoftPathState ret = ExtSoftPathState.FailedHard; switch (extInstance.pathMode) { case ExtPathMode.CalculatingCarPathToTarget: case ExtPathMode.CalculatingCarPathToKnownParkPos: case ExtPathMode.CalculatingWalkingPathToParkedCar: // try to walk to target #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Path failed but it may be retried to walk to the target."); #endif extInstance.pathMode = ExtPathMode.RequiresWalkingPathToTarget; ret = ExtSoftPathState.FailedSoft; break; default: #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Path failed and walking to target is not an option. Resetting ext. instance."); #endif extInstance.Reset(); break; } #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCitizenPathFindFailure({instanceId}): Setting CurrentPathMode for citizen instance {extInstance.instanceId} to {extInstance.pathMode}, ret={ret}"); #endif // reset current transport mode for hard failures if (ret == ExtSoftPathState.FailedHard) { extCitizen.transportMode = ExtCitizen.ExtTransportMode.None; } return ret; } /// /// Handles a path-finding failure for citizen instances and activated Parking AI. /// /// Vehicle id /// Vehicle data /// Driver citizen instance data /// extended citizen instance information of driver /// if true path-finding may be repeated (path mode has been updated), false otherwise protected ExtSoftPathState OnCarPathFindFailure(ushort vehicleId, ref Vehicle vehicleData, ref CitizenInstance driverInstanceData, ref ExtCitizenInstance driverExtInstance) { #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 == driverInstanceData.m_citizen) && (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == driverInstanceData.m_sourceBuilding) && (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == driverInstanceData.m_targetBuilding) ; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; if (debug) Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Path-finding failed for driver citizen instance {driverExtInstance.instanceId}. CurrentPathMode={driverExtInstance.pathMode}"); #endif // update parking demands switch (driverExtInstance.pathMode) { case ExtPathMode.None: case ExtPathMode.CalculatingCarPathToAltParkPos: case ExtPathMode.CalculatingCarPathToKnownParkPos: // could not reach target building by driving: increase parking space demand #if DEBUG if (fineDebug) Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Increasing parking space demand of target building {driverInstanceData.m_targetBuilding}"); #endif if (driverInstanceData.m_targetBuilding != 0) { ExtBuildingManager.Instance.ExtBuildings[driverInstanceData.m_targetBuilding].AddParkingSpaceDemand((uint)GlobalConfig.Instance.ParkingAI.FailedParkingSpaceDemandIncrement); } break; } // check if path-finding may be repeated ExtSoftPathState ret = ExtSoftPathState.FailedHard; switch (driverExtInstance.pathMode) { case ExtPathMode.CalculatingCarPathToAltParkPos: case ExtPathMode.CalculatingCarPathToKnownParkPos: // try to drive directly to the target if public transport is allowed if ((driverInstanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None) { #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Path failed but it may be retried to drive directly to the target / using public transport."); #endif driverExtInstance.pathMode = ExtPathMode.RequiresMixedCarPathToTarget; ret = ExtSoftPathState.FailedSoft; } break; default: #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Path failed and a direct target is not an option. Resetting driver ext. instance."); #endif driverExtInstance.Reset(); break; } #if DEBUG if (debug) Log._Debug($"AdvancedParkingManager.OnCarPathFindFailure({vehicleId}): Setting CurrentPathMode for driver citizen instance {driverExtInstance.instanceId} to {driverExtInstance.pathMode}, ret={ret}"); #endif return ret; } public bool TryMoveParkedVehicle(ushort parkedVehicleId, ref VehicleParked parkedVehicle, Vector3 refPos, float maxDistance, ushort homeId) { ExtParkingSpaceLocation parkingSpaceLocation; ushort parkingSpaceLocationId; Vector3 parkPos; Quaternion parkRot; float parkOffset; bool found = false; #if BENCHMARK using (var bm = new Benchmark(null, "FindParkingSpaceInVicinity")) { #endif found = AdvancedParkingManager.Instance.FindParkingSpaceInVicinity(refPos, parkedVehicle.Info, homeId, 0, maxDistance, out parkingSpaceLocation, out parkingSpaceLocationId, out parkPos, out parkRot, out parkOffset); #if BENCHMARK } #endif if (found) { Singleton.instance.RemoveFromGrid(parkedVehicleId, ref parkedVehicle); parkedVehicle.m_position = parkPos; parkedVehicle.m_rotation = parkRot; Singleton.instance.AddToGrid(parkedVehicleId, ref parkedVehicle); } return found; } public bool FindParkingSpaceForCitizen(Vector3 endPos, VehicleInfo vehicleInfo, ref ExtCitizenInstance extDriverInstance, ushort homeId, bool goingHome, ushort vehicleId, bool allowTourists, out Vector3 parkPos, ref PathUnit.Position endPathPos, out bool calculateEndPos) { #if DEBUG bool citDebug = (GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId) && (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == extDriverInstance.instanceId) && (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == extDriverInstance.GetCitizenId()) && (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == Singleton.instance.m_instances.m_buffer[extDriverInstance.instanceId].m_sourceBuilding) && (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == Singleton.instance.m_instances.m_buffer[extDriverInstance.instanceId].m_targetBuilding) ; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; #endif calculateEndPos = true; parkPos = default(Vector3); if (!allowTourists) { // TODO remove this from this method uint citizenId = extDriverInstance.GetCitizenId(); if (citizenId == 0 || (Singleton.instance.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Tourist) != Citizen.Flags.None) return false; } #if DEBUG if (fineDebug) Log._Debug($"Citizen instance {extDriverInstance.instanceId} (CurrentPathMode={extDriverInstance.pathMode}) can still use their passenger car and is either not a tourist or wants to find an alternative parking spot. Finding a parking space before starting path-finding."); #endif ExtParkingSpaceLocation knownParkingSpaceLocation; ushort knownParkingSpaceLocationId; Quaternion parkRot; float parkOffset; // find a free parking space bool success = FindParkingSpaceInVicinity(endPos, vehicleInfo, homeId, vehicleId, goingHome ? GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome : GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, out knownParkingSpaceLocation, out knownParkingSpaceLocationId, out parkPos, out parkRot, out parkOffset); extDriverInstance.parkingSpaceLocation = knownParkingSpaceLocation; extDriverInstance.parkingSpaceLocationId = knownParkingSpaceLocationId; if (success) { #if DEBUG if (fineDebug) Log._Debug($"Found a parking spot for citizen instance {extDriverInstance.instanceId} (CurrentPathMode={extDriverInstance.pathMode}) before starting car path: {knownParkingSpaceLocation} @ {knownParkingSpaceLocationId}"); #endif if (knownParkingSpaceLocation == ExtParkingSpaceLocation.RoadSide) { // found segment with parking space Vector3 pedPos; uint laneId; int laneIndex; float laneOffset; #if DEBUG if (debug) Log._Debug($"Found segment {knownParkingSpaceLocationId} for road-side parking position for citizen instance {extDriverInstance.instanceId}!"); #endif // determine nearest sidewalk position for parking position at segment if (Singleton.instance.m_segments.m_buffer[knownParkingSpaceLocationId].GetClosestLanePosition(parkPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, out pedPos, out laneId, out laneIndex, out laneOffset)) { endPathPos.m_segment = knownParkingSpaceLocationId; endPathPos.m_lane = (byte)laneIndex; endPathPos.m_offset = (byte)(parkOffset * 255f); calculateEndPos = false; //extDriverInstance.CurrentPathMode = successMode;// ExtCitizenInstance.PathMode.CalculatingKnownCarPath; #if DEBUG if (debug) Log._Debug($"Found an parking spot sidewalk position for citizen instance {extDriverInstance.instanceId} @ segment {knownParkingSpaceLocationId}, laneId {laneId}, laneIndex {laneIndex}, offset={endPathPos.m_offset}! CurrentPathMode={extDriverInstance.pathMode}"); #endif return true; } else { #if DEBUG if (debug) Log._Debug($"Could not find an alternative parking spot sidewalk position for citizen instance {extDriverInstance.instanceId}! CurrentPathMode={extDriverInstance.pathMode}"); #endif return false; } } else if (knownParkingSpaceLocation == ExtParkingSpaceLocation.Building) { // found a building with parking space if (CustomPathManager.FindPathPositionWithSpiralLoop(parkPos, endPos, ItemClass.Service.Road, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.None, VehicleInfo.VehicleType.None, false, false, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out endPathPos)) { calculateEndPos = false; } //endPos = parkPos; //extDriverInstance.CurrentPathMode = successMode;// ExtCitizenInstance.PathMode.CalculatingKnownCarPath; #if DEBUG if (debug) Log._Debug($"Navigating citizen instance {extDriverInstance.instanceId} to parking building {knownParkingSpaceLocationId}! segment={endPathPos.m_segment}, laneIndex={endPathPos.m_lane}, offset={endPathPos.m_offset}. CurrentPathMode={extDriverInstance.pathMode} calculateEndPos={calculateEndPos}"); #endif return true; } } return false; } public bool TrySpawnParkedPassengerCar(uint citizenId, ushort homeId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason) { #if DEBUG bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenId; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; if (fineDebug && homeId != 0) Log._Debug($"Trying to spawn parked passenger car for citizen {citizenId}, home {homeId} @ {refPos}"); #endif Vector3 roadParkPos; ParkingUnableReason roadParkReason; bool roadParkSuccess = TrySpawnParkedPassengerCarRoadSide(citizenId, refPos, vehicleInfo, out roadParkPos, out roadParkReason); Vector3 buildingParkPos; ParkingUnableReason buildingParkReason; bool buildingParkSuccess = TrySpawnParkedPassengerCarBuilding(citizenId, homeId, refPos, vehicleInfo, out buildingParkPos, out buildingParkReason); if ((!roadParkSuccess && !buildingParkSuccess) || (roadParkSuccess && !buildingParkSuccess)) { parkPos = roadParkPos; reason = roadParkReason; return roadParkSuccess; } else if (buildingParkSuccess && !roadParkSuccess) { parkPos = buildingParkPos; reason = buildingParkReason; return buildingParkSuccess; } else if ((roadParkPos - refPos).sqrMagnitude < (buildingParkPos - refPos).sqrMagnitude) { parkPos = roadParkPos; reason = roadParkReason; return roadParkSuccess; } else { parkPos = buildingParkPos; reason = buildingParkReason; return buildingParkSuccess; } } public bool TrySpawnParkedPassengerCarRoadSide(uint citizenId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason) { #if DEBUG bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenId; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; if (debug) Log._Debug($"Trying to spawn parked passenger car at road side for citizen {citizenId} @ {refPos}"); #endif parkPos = Vector3.zero; Quaternion parkRot = Quaternion.identity; float parkOffset = 0f; if (FindParkingSpaceRoadSide(0, refPos, vehicleInfo.m_generatedInfo.m_size.x, vehicleInfo.m_generatedInfo.m_size.z, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, out parkPos, out parkRot, out parkOffset)) { // position found, spawn a parked vehicle ushort parkedVehicleId; if (Singleton.instance.CreateParkedVehicle(out parkedVehicleId, ref Singleton.instance.m_randomizer, vehicleInfo, parkPos, parkRot, citizenId)) { Singleton.instance.m_citizens.m_buffer[citizenId].SetParkedVehicle(citizenId, parkedVehicleId); Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_flags &= (ushort)(VehicleParked.Flags.All & ~VehicleParked.Flags.Parking); #if DEBUG if (debug) Log._Debug($"[SUCCESS] Spawned parked passenger car at road side for citizen {citizenId}: {parkedVehicleId} @ {parkPos}"); #endif reason = ParkingUnableReason.None; return true; } else { reason = ParkingUnableReason.LimitHit; } } else { reason = ParkingUnableReason.NoSpaceFound; } #if DEBUG if (debug) Log._Debug($"[FAIL] Failed to spawn parked passenger car at road side for citizen {citizenId}"); #endif return false; } public bool TrySpawnParkedPassengerCarBuilding(uint citizenId, ushort homeId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason) { #if DEBUG bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenId; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; if (fineDebug && homeId != 0) Log._Debug($"Trying to spawn parked passenger car next to building for citizen {citizenId} @ {refPos}"); #endif parkPos = Vector3.zero; Quaternion parkRot = Quaternion.identity; float parkOffset; if (FindParkingSpaceBuilding(vehicleInfo, homeId, 0, 0, refPos, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, out parkPos, out parkRot, out parkOffset)) { // position found, spawn a parked vehicle ushort parkedVehicleId; if (Singleton.instance.CreateParkedVehicle(out parkedVehicleId, ref Singleton.instance.m_randomizer, vehicleInfo, parkPos, parkRot, citizenId)) { Singleton.instance.m_citizens.m_buffer[citizenId].SetParkedVehicle(citizenId, parkedVehicleId); Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_flags &= (ushort)(VehicleParked.Flags.All & ~VehicleParked.Flags.Parking); #if DEBUG if (fineDebug && homeId != 0) Log._Debug($"[SUCCESS] Spawned parked passenger car next to building for citizen {citizenId}: {parkedVehicleId} @ {parkPos}"); #endif reason = ParkingUnableReason.None; return true; } else { reason = ParkingUnableReason.LimitHit; } } else { reason = ParkingUnableReason.NoSpaceFound; } #if DEBUG if (debug && homeId != 0) Log._Debug($"[FAIL] Failed to spawn parked passenger car next to building for citizen {citizenId}"); #endif return false; } public bool FindParkingSpaceInVicinity(Vector3 targetPos, VehicleInfo vehicleInfo, ushort homeId, ushort vehicleId, float maxDist, out ExtParkingSpaceLocation parkingSpaceLocation, out ushort parkingSpaceLocationId, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { #if DEBUG bool vehDebug = GlobalConfig.Instance.Debug.VehicleId == 0 || GlobalConfig.Instance.Debug.VehicleId == vehicleId; bool debug = GlobalConfig.Instance.Debug.Switches[22] && vehDebug; #endif // TODO check isElectric Vector3 roadParkPos; Quaternion roadParkRot; float roadParkOffset; Vector3 buildingParkPos; Quaternion buildingParkRot; float buildingParkOffset; ushort parkingSpaceSegmentId = FindParkingSpaceAtRoadSide(0, targetPos, vehicleInfo.m_generatedInfo.m_size.x, vehicleInfo.m_generatedInfo.m_size.z, maxDist, true, out roadParkPos, out roadParkRot, out roadParkOffset); ushort parkingBuildingId = FindParkingSpaceBuilding(vehicleInfo, homeId, 0, 0, targetPos, maxDist, maxDist, true, out buildingParkPos, out buildingParkRot, out buildingParkOffset); if (parkingSpaceSegmentId != 0) { if (parkingBuildingId != 0) { Randomizer rng = Singleton.instance.m_randomizer; // choose nearest parking position, after a bit of randomization if ((roadParkPos - targetPos).magnitude < (buildingParkPos - targetPos).magnitude && rng.Int32(GlobalConfig.Instance.ParkingAI.VicinityParkingSpaceSelectionRand) != 0) { // road parking space is closer #if DEBUG if (debug) Log._Debug($"Found an (alternative) road-side parking position for vehicle {vehicleId} @ segment {parkingSpaceSegmentId} after comparing distance with a bulding parking position @ {parkingBuildingId}!"); #endif parkPos = roadParkPos; parkRot = roadParkRot; parkOffset = roadParkOffset; parkingSpaceLocation = ExtCitizenInstance.ExtParkingSpaceLocation.RoadSide; parkingSpaceLocationId = parkingSpaceSegmentId; return true; } else { // building parking space is closer #if DEBUG if (debug) Log._Debug($"Found an alternative building parking position for vehicle {vehicleId} at building {parkingBuildingId} after comparing distance with a road-side parking position @ {parkingSpaceSegmentId}!"); #endif parkPos = buildingParkPos; parkRot = buildingParkRot; parkOffset = buildingParkOffset; parkingSpaceLocation = ExtCitizenInstance.ExtParkingSpaceLocation.Building; parkingSpaceLocationId = parkingBuildingId; return true; } } else { // road-side but no building parking space found #if DEBUG if (debug) Log._Debug($"Found an alternative road-side parking position for vehicle {vehicleId} @ segment {parkingSpaceSegmentId}!"); #endif parkPos = roadParkPos; parkRot = roadParkRot; parkOffset = roadParkOffset; parkingSpaceLocation = ExtCitizenInstance.ExtParkingSpaceLocation.RoadSide; parkingSpaceLocationId = parkingSpaceSegmentId; return true; } } else if (parkingBuildingId != 0) { // building but no road-side parking space found #if DEBUG if (debug) Log._Debug($"Found an alternative building parking position for vehicle {vehicleId} at building {parkingBuildingId}!"); #endif parkPos = buildingParkPos; parkRot = buildingParkRot; parkOffset = buildingParkOffset; parkingSpaceLocation = ExtCitizenInstance.ExtParkingSpaceLocation.Building; parkingSpaceLocationId = parkingBuildingId; return true; } else { //driverExtInstance.CurrentPathMode = ExtCitizenInstance.PathMode.AltParkFailed; parkingSpaceLocation = ExtCitizenInstance.ExtParkingSpaceLocation.None; parkingSpaceLocationId = 0; parkPos = default(Vector3); parkRot = default(Quaternion); parkOffset = -1f; #if DEBUG if (debug) Log._Debug($"Could not find a road-side or building parking position for vehicle {vehicleId}!"); #endif return false; } } protected ushort FindParkingSpaceAtRoadSide(ushort ignoreParked, Vector3 refPos, float width, float length, float maxDistance, bool randomize, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[22]; #endif parkPos = Vector3.zero; parkRot = Quaternion.identity; parkOffset = 0f; int centerI = (int)(refPos.z / (float)BuildingManager.BUILDINGGRID_CELL_SIZE + (float)BuildingManager.BUILDINGGRID_RESOLUTION / 2f); int centerJ = (int)(refPos.x / (float)BuildingManager.BUILDINGGRID_CELL_SIZE + (float)BuildingManager.BUILDINGGRID_RESOLUTION / 2f); int radius = Math.Max(1, (int)(maxDistance / ((float)BuildingManager.BUILDINGGRID_CELL_SIZE / 2f)) + 1); NetManager netManager = Singleton.instance; Randomizer rng = Singleton.instance.m_randomizer; ushort foundSegmentId = 0; Vector3 myParkPos = parkPos; Quaternion myParkRot = parkRot; float myParkOffset = parkOffset; LoopUtil.SpiralLoop(centerI, centerJ, radius, radius, delegate (int i, int j) { if (i < 0 || i >= BuildingManager.BUILDINGGRID_RESOLUTION || j < 0 || j >= BuildingManager.BUILDINGGRID_RESOLUTION) return true; ushort segmentId = netManager.m_segmentGrid[i * BuildingManager.BUILDINGGRID_RESOLUTION + j]; int iterations = 0; while (segmentId != 0) { uint laneId; int laneIndex; float laneOffset; Vector3 innerParkPos; Quaternion innerParkRot; float innerParkOffset; NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; Vector3 segCenter = netManager.m_segments.m_buffer[segmentId].m_bounds.center; // randomize target position to allow for opposite road-side parking segCenter.x += Singleton.instance.m_randomizer.Int32(GlobalConfig.Instance.ParkingAI.ParkingSpacePositionRand) - GlobalConfig.Instance.ParkingAI.ParkingSpacePositionRand / 2u; segCenter.z += Singleton.instance.m_randomizer.Int32(GlobalConfig.Instance.ParkingAI.ParkingSpacePositionRand) - GlobalConfig.Instance.ParkingAI.ParkingSpacePositionRand / 2u; if (netManager.m_segments.m_buffer[segmentId].GetClosestLanePosition(segCenter, NetInfo.LaneType.Parking, VehicleInfo.VehicleType.Car, out innerParkPos, out laneId, out laneIndex, out laneOffset)) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; if (!Options.parkingRestrictionsEnabled || ParkingRestrictionsManager.Instance.IsParkingAllowed(segmentId, laneInfo.m_finalDirection)) { if (!Options.vehicleRestrictionsEnabled || (VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(segmentId, segmentInfo, (uint)laneIndex, laneInfo, VehicleRestrictionsMode.Configured) & ExtVehicleType.PassengerCar) != ExtVehicleType.None) { if (CustomPassengerCarAI.FindParkingSpaceRoadSide(ignoreParked, segmentId, innerParkPos, width, length, out innerParkPos, out innerParkRot, out innerParkOffset)) { #if DEBUG if (debug) Log._Debug($"FindParkingSpaceRoadSide: Found a parking space for refPos {refPos}, segment center {segCenter} @ {innerParkPos}, laneId {laneId}, laneIndex {laneIndex}!"); #endif foundSegmentId = segmentId; myParkPos = innerParkPos; myParkRot = innerParkRot; myParkOffset = innerParkOffset; if (!randomize || rng.Int32(GlobalConfig.Instance.ParkingAI.VicinityParkingSpaceSelectionRand) != 0) return false; } } } } else { /*if (debug) Log._Debug($"FindParkingSpaceRoadSide: Could not find closest lane position for parking @ {segmentId}!");*/ } segmentId = netManager.m_segments.m_buffer[segmentId].m_nextGridSegment; if (++iterations >= NetManager.MAX_SEGMENT_COUNT) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } } return true; }); if (foundSegmentId == 0) { #if DEBUG if (debug) Log._Debug($"FindParkingSpaceRoadSide: Could not find a parking space for refPos {refPos}!"); #endif return 0; } parkPos = myParkPos; parkRot = myParkRot; parkOffset = myParkOffset; return foundSegmentId; } protected ushort FindParkingSpaceBuilding(VehicleInfo vehicleInfo, ushort homeID, ushort ignoreParked, ushort segmentId, Vector3 refPos, float maxBuildingDistance, float maxParkingSpaceDistance, bool randomize, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[22]; #endif parkPos = Vector3.zero; parkRot = Quaternion.identity; parkOffset = -1f; int centerI = (int)(refPos.z / (float)BuildingManager.BUILDINGGRID_CELL_SIZE + (float)BuildingManager.BUILDINGGRID_RESOLUTION / 2f); int centerJ = (int)(refPos.x / (float)BuildingManager.BUILDINGGRID_CELL_SIZE + (float)BuildingManager.BUILDINGGRID_RESOLUTION / 2f); int radius = Math.Max(1, (int)(maxBuildingDistance / ((float)BuildingManager.BUILDINGGRID_CELL_SIZE / 2f)) + 1); BuildingManager buildingMan = Singleton.instance; Randomizer rng = Singleton.instance.m_randomizer; ushort foundBuildingId = 0; Vector3 myParkPos = parkPos; Quaternion myParkRot = parkRot; float myParkOffset = parkOffset; LoopUtil.SpiralLoop(centerI, centerJ, radius, radius, delegate (int i, int j) { if (i < 0 || i >= BuildingManager.BUILDINGGRID_RESOLUTION || j < 0 || j >= BuildingManager.BUILDINGGRID_RESOLUTION) return true; #if DEBUG if (debug) { //Log._Debug($"FindParkingSpaceBuilding: Checking building grid @ i={i}, j={j}, index={i * BuildingManager.BUILDINGGRID_RESOLUTION + j} for {refPos}, homeID {homeID}, segment {segmentId}, maxDistance {maxDistance}"); } #endif ushort buildingId = buildingMan.m_buildingGrid[i * BuildingManager.BUILDINGGRID_RESOLUTION + j]; int numIterations = 0; while (buildingId != 0) { Vector3 innerParkPos; Quaternion innerParkRot; float innerParkOffset; #if DEBUG if (debug) { //Log._Debug($"FindParkingSpaceBuilding: Checking building {buildingId} @ i={i}, j={j}, index={i * BuildingManager.BUILDINGGRID_RESOLUTION + j}, for {refPos}, homeID {homeID}, segment {segmentId}, maxDistance {maxDistance}."); } #endif if (FindParkingSpacePropAtBuilding(vehicleInfo, homeID, ignoreParked, buildingId, ref buildingMan.m_buildings.m_buffer[(int)buildingId], segmentId, refPos, ref maxParkingSpaceDistance, randomize, out innerParkPos, out innerParkRot, out innerParkOffset)) { #if DEBUG /*/if (fineDebug && homeID != 0) Log._Debug($"FindParkingSpaceBuilding: Found a parking space for {refPos}, homeID {homeID} @ building {buildingId}, {myParkPos}, offset {myParkOffset}!"); */ #endif foundBuildingId = buildingId; myParkPos = innerParkPos; myParkRot = innerParkRot; myParkOffset = innerParkOffset; if (!randomize || rng.Int32(GlobalConfig.Instance.ParkingAI.VicinityParkingSpaceSelectionRand) != 0) return false; } buildingId = buildingMan.m_buildings.m_buffer[(int)buildingId].m_nextGridBuilding; if (++numIterations >= 49152) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } } return true; }); if (foundBuildingId == 0) { #if DEBUG if (debug && homeID != 0) Log._Debug($"FindParkingSpaceBuilding: Could not find a parking space for homeID {homeID}!"); #endif return 0; } parkPos = myParkPos; parkRot = myParkRot; parkOffset = myParkOffset; return foundBuildingId; } public bool FindParkingSpacePropAtBuilding(VehicleInfo vehicleInfo, ushort homeID, ushort ignoreParked, ushort buildingID, ref Building building, ushort segmentId, Vector3 refPos, ref float maxDistance, bool randomize, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[22]; #endif int buildingWidth = building.Width; int buildingLength = building.Length; // NON-STOCK CODE START parkOffset = -1f; // only set if segmentId != 0 parkPos = default(Vector3); parkRot = default(Quaternion); if ((building.m_flags & Building.Flags.Created) == Building.Flags.None) { #if DEBUG if (debug) Log._Debug($"Refusing to find parking space at building {buildingID}! Building is not created."); #endif return false; } if ((building.m_problems & Notification.Problem.TurnedOff) != Notification.Problem.None) { #if DEBUG if (debug) Log._Debug($"Refusing to find parking space at building {buildingID}! Building is not active."); #endif return false; } if ((building.m_flags & Building.Flags.Collapsed) != Building.Flags.None) { #if DEBUG if (debug) Log._Debug($"Refusing to find parking space at building {buildingID}! Building is collapsed."); #endif return false; } /*else { // NON-STOCK CODE END float diagWidth = Mathf.Sqrt((float)(buildingWidth * buildingWidth + buildingLength * buildingLength)) * 8f; if (VectorUtils.LengthXZ(building.m_position - refPos) >= maxDistance + diagWidth) {*/ #if DEBUG /*if (fineDebug) Log._Debug($"Refusing to find parking space at building {buildingID}! {VectorUtils.LengthXZ(building.m_position - refPos)} >= {maxDistance + diagWidth} (maxDistance={maxDistance})");*/ #endif /*return false; } }*/ // NON-STOCK CODE Randomizer rng = Singleton.instance.m_randomizer; // NON-STOCK CODE bool isElectric = vehicleInfo.m_class.m_subService != ItemClass.SubService.ResidentialLow; BuildingInfo buildingInfo = building.Info; Matrix4x4 transformMatrix = default(Matrix4x4); bool transformMatrixCalculated = false; bool result = false; if (buildingInfo.m_class.m_service == ItemClass.Service.Residential && buildingID != homeID && rng.Int32((uint)Options.getRecklessDriverModulo()) != 0) { // NON-STOCK CODE #if DEBUG /*if (fineDebug) Log._Debug($"Refusing to find parking space at building {buildingID}! Building is a residential building which does not match home id {homeID}.");*/ #endif return false; } float propMinDistance = 9999f; // NON-STOCK CODE if (buildingInfo.m_props != null && (buildingInfo.m_hasParkingSpaces & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) { for (int i = 0; i < buildingInfo.m_props.Length; i++) { BuildingInfo.Prop prop = buildingInfo.m_props[i]; Randomizer randomizer = new Randomizer((int)buildingID << 6 | prop.m_index); if (randomizer.Int32(100u) < prop.m_probability && buildingLength >= prop.m_requiredLength) { PropInfo propInfo = prop.m_finalProp; if (propInfo != null) { propInfo = propInfo.GetVariation(ref randomizer); if (propInfo.m_parkingSpaces != null && propInfo.m_parkingSpaces.Length != 0) { if (!transformMatrixCalculated) { transformMatrixCalculated = true; Vector3 pos = Building.CalculateMeshPosition(buildingInfo, building.m_position, building.m_angle, building.Length); Quaternion q = Quaternion.AngleAxis(building.m_angle * 57.29578f, Vector3.down); transformMatrix.SetTRS(pos, q, Vector3.one); } Vector3 position = transformMatrix.MultiplyPoint(prop.m_position); if (CustomPassengerCarAI.FindParkingSpaceProp(isElectric, ignoreParked, propInfo, position, building.m_angle + prop.m_radAngle, prop.m_fixedHeight, refPos, vehicleInfo.m_generatedInfo.m_size.x, vehicleInfo.m_generatedInfo.m_size.z, ref propMinDistance, ref parkPos, ref parkRot)) { // NON-STOCK CODE result = true; if (randomize && propMinDistance <= maxDistance && rng.Int32(GlobalConfig.Instance.ParkingAI.VicinityParkingSpaceSelectionRand) == 0) break; } } } } } } if (result && propMinDistance <= maxDistance) { maxDistance = propMinDistance; // NON-STOCK CODE #if DEBUG if (debug) Log._Debug($"Found parking space prop in range ({maxDistance}) at building {buildingID}."); #endif if (segmentId != 0) { // check if building is accessible from the given segment #if DEBUG if (debug) Log._Debug($"Calculating unspawn position of building {buildingID} for segment {segmentId}."); #endif Vector3 unspawnPos; Vector3 unspawnTargetPos; building.Info.m_buildingAI.CalculateUnspawnPosition(buildingID, ref building, ref Singleton.instance.m_randomizer, vehicleInfo, out unspawnPos, out unspawnTargetPos); Vector3 lanePos; uint laneId; int laneIndex; float laneOffset; // calculate segment offset if (Singleton.instance.m_segments.m_buffer[segmentId].GetClosestLanePosition(unspawnPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, out lanePos, out laneId, out laneIndex, out laneOffset)) { #if DEBUG if (debug) Log._Debug($"Succeeded in finding unspawn position lane offset for building {buildingID}, segment {segmentId}, unspawnPos={unspawnPos}! lanePos={lanePos}, dist={(lanePos - unspawnPos).magnitude}, laneId={laneId}, laneIndex={laneIndex}, laneOffset={laneOffset}"); #endif /*if (dist > 16f) { if (debug) Log._Debug($"Distance between unspawn position and lane position is too big! {dist} unspawnPos={unspawnPos} lanePos={lanePos}"); return false; }*/ parkOffset = laneOffset; } else { #if DEBUG if (debug) Log.Warning($"Could not find unspawn position lane offset for building {buildingID}, segment {segmentId}, unspawnPos={unspawnPos}!"); #endif } } return true; } else { #if DEBUG if (result && debug) Log._Debug($"Could not find parking space prop in range ({maxDistance}) at building {buildingID}."); #endif return false; } } public bool FindParkingSpaceRoadSideForVehiclePos(VehicleInfo vehicleInfo, ushort ignoreParked, ushort segmentId, Vector3 refPos, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset, out uint laneId, out int laneIndex) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[22]; #endif float width = vehicleInfo.m_generatedInfo.m_size.x; float length = vehicleInfo.m_generatedInfo.m_size.z; NetManager netManager = Singleton.instance; if ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) != NetSegment.Flags.None) { if (netManager.m_segments.m_buffer[segmentId].GetClosestLanePosition(refPos, NetInfo.LaneType.Parking, VehicleInfo.VehicleType.Car, out parkPos, out laneId, out laneIndex, out parkOffset)) { if (!Options.parkingRestrictionsEnabled || ParkingRestrictionsManager.Instance.IsParkingAllowed(segmentId, netManager.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex].m_finalDirection)) { if (CustomPassengerCarAI.FindParkingSpaceRoadSide(ignoreParked, segmentId, parkPos, width, length, out parkPos, out parkRot, out parkOffset)) { #if DEBUG if (debug) Log._Debug($"FindParkingSpaceRoadSideForVehiclePos: Found a parking space for refPos {refPos} @ {parkPos}, laneId {laneId}, laneIndex {laneIndex}!"); #endif return true; } } } } // parkPos = default(Vector3); parkRot = default(Quaternion); laneId = 0; laneIndex = -1; parkOffset = -1f; return false; } public bool FindParkingSpaceRoadSide(ushort ignoreParked, Vector3 refPos, float width, float length, float maxDistance, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { return FindParkingSpaceAtRoadSide(ignoreParked, refPos, width, length, maxDistance, false, out parkPos, out parkRot, out parkOffset) != 0; } public bool FindParkingSpaceBuilding(VehicleInfo vehicleInfo, ushort homeID, ushort ignoreParked, ushort segmentId, Vector3 refPos, float maxBuildingDistance, float maxParkingSpaceDistance, out Vector3 parkPos, out Quaternion parkRot, out float parkOffset) { return FindParkingSpaceBuilding(vehicleInfo, homeID, ignoreParked, segmentId, refPos, maxBuildingDistance, maxParkingSpaceDistance, false, out parkPos, out parkRot, out parkOffset) != 0; } public bool GetBuildingInfoViewColor(ushort buildingId, ref Building buildingData, ref ExtBuilding extBuilding, InfoManager.InfoMode infoMode, out Color? color) { color = null; if (infoMode == InfoManager.InfoMode.Traffic) { // parking space demand info view color = Color.Lerp(Singleton.instance.m_properties.m_modeProperties[(int)infoMode].m_targetColor, Singleton.instance.m_properties.m_modeProperties[(int)infoMode].m_negativeColor, Mathf.Clamp01((float)extBuilding.parkingSpaceDemand * 0.01f)); return true; } else if (infoMode == InfoManager.InfoMode.Transport && !(buildingData.Info.m_buildingAI is DepotAI)) { // public transport demand info view // TODO should not depend on UI class "TrafficManagerTool" color = Color.Lerp(Singleton.instance.m_properties.m_modeProperties[(int)InfoManager.InfoMode.Traffic].m_targetColor, Singleton.instance.m_properties.m_modeProperties[(int)InfoManager.InfoMode.Traffic].m_negativeColor, Mathf.Clamp01((float)(TrafficManagerTool.CurrentTransportDemandViewMode == TransportDemandViewMode.Outgoing ? extBuilding.outgoingPublicTransportDemand : extBuilding.incomingPublicTransportDemand) * 0.01f)); return true; } return false; } public string EnrichLocalizedCitizenStatus(string ret, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen) { if (extInstance.IsValid()) { switch (extInstance.pathMode) { case ExtPathMode.ApproachingParkedCar: case ExtPathMode.RequiresCarPath: case ExtPathMode.RequiresMixedCarPathToTarget: ret = Translation.GetString("Entering_vehicle") + ", " + ret; break; case ExtPathMode.RequiresWalkingPathToParkedCar: case ExtPathMode.CalculatingWalkingPathToParkedCar: case ExtPathMode.WalkingToParkedCar: ret = Translation.GetString("Walking_to_car") + ", " + ret; break; case ExtPathMode.CalculatingWalkingPathToTarget: case ExtPathMode.TaxiToTarget: case ExtPathMode.WalkingToTarget: if ((extCitizen.transportMode & ExtCitizen.ExtTransportMode.PublicTransport) != ExtCitizen.ExtTransportMode.None) { ret = Translation.GetString("Using_public_transport") + ", " + ret; } else { ret = Translation.GetString("Walking") + ", " + ret; } break; case ExtPathMode.CalculatingCarPathToTarget: case ExtPathMode.CalculatingCarPathToKnownParkPos: ret = Translation.GetString("Thinking_of_a_good_parking_spot") + ", " + ret; break; } } return ret; } public string EnrichLocalizedCarStatus(string ret, ref ExtCitizenInstance driverExtInstance) { if (driverExtInstance.IsValid()) { switch (driverExtInstance.pathMode) { case ExtPathMode.DrivingToAltParkPos: if (driverExtInstance.failedParkingAttempts <= 1) { ret = Translation.GetString("Driving_to_a_parking_spot") + ", " + ret; } else { ret = Translation.GetString("Driving_to_another_parking_spot") + " (#" + driverExtInstance.failedParkingAttempts + "), " + ret; } break; case ExtPathMode.CalculatingCarPathToKnownParkPos: case ExtPathMode.DrivingToKnownParkPos: ret = Translation.GetString("Driving_to_a_parking_spot") + ", " + ret; break; case ExtPathMode.ParkingFailed: case ExtPathMode.CalculatingCarPathToAltParkPos: ret = Translation.GetString("Looking_for_a_parking_spot") + ", " + ret; break; case ExtPathMode.RequiresWalkingPathToTarget: ret = Locale.Get("VEHICLE_STATUS_PARKING") + ", " + ret; break; } } return ret; } } } ================================================ FILE: TLM/TLM/Manager/Impl/CustomSegmentLightsManager.cs ================================================ using System; using System.Collections.Generic; using ColossalFramework; using TrafficManager.Geometry; using TrafficManager.Util; using TrafficManager.TrafficLight; using TrafficManager.State; using System.Linq; using TrafficManager.Traffic; using CSUtil.Commons; using TrafficManager.TrafficLight.Impl; using TrafficManager.Geometry.Impl; namespace TrafficManager.Manager.Impl { /// /// Manages the states of all custom traffic lights on the map /// public class CustomSegmentLightsManager : AbstractGeometryObservingManager, ICustomSegmentLightsManager { public static CustomSegmentLightsManager Instance { get; private set; } = null; static CustomSegmentLightsManager() { Instance = new CustomSegmentLightsManager(); } /// /// custom traffic lights by segment id /// private CustomSegment[] CustomSegments = new CustomSegment[NetManager.MAX_SEGMENT_COUNT]; protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"Custom segments:"); for (int i = 0; i < CustomSegments.Length; ++i) { if (CustomSegments[i] == null) { continue; } Log._Debug($"Segment {i}: {CustomSegments[i]}"); } } /// /// Adds custom traffic lights at the specified node and segment. /// Light states (red, yellow, green) are taken from the "live" state, that is the traffic light's light state right before the custom light takes control. /// /// /// public ICustomSegmentLights AddLiveSegmentLights(ushort segmentId, bool startNode) { SegmentGeometry segGeometry = SegmentGeometry.Get(segmentId); if (segGeometry == null) { Log.Error($"CustomTrafficLightsManager.AddLiveSegmentLights: Segment {segmentId} is invalid."); return null; } SegmentEndGeometry endGeometry = segGeometry.GetEnd(startNode); if (endGeometry == null) { Log.Error($"CustomTrafficLightsManager.AddLiveSegmentLights: Segment {segmentId} is not connected to a node @ start {startNode}"); return null; } var currentFrameIndex = Singleton.instance.m_currentFrameIndex; RoadBaseAI.TrafficLightState vehicleLightState; RoadBaseAI.TrafficLightState pedestrianLightState; bool vehicles; bool pedestrians; RoadBaseAI.GetTrafficLightState(endGeometry.NodeId(), ref Singleton.instance.m_segments.m_buffer[segmentId], currentFrameIndex - 256u, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians); return AddSegmentLights(segmentId, startNode, vehicleLightState == RoadBaseAI.TrafficLightState.Green ? RoadBaseAI.TrafficLightState.Green : RoadBaseAI.TrafficLightState.Red); } /// /// Adds custom traffic lights at the specified node and segment. /// Light stats are set to the given light state, or to "Red" if no light state is given. /// /// /// /// (optional) light state to set public ICustomSegmentLights AddSegmentLights(ushort segmentId, bool startNode, RoadBaseAI.TrafficLightState lightState=RoadBaseAI.TrafficLightState.Red) { #if DEBUG Log._Debug($"CustomTrafficLights.AddSegmentLights: Adding segment light: {segmentId} @ startNode={startNode}"); #endif SegmentGeometry segGeometry = SegmentGeometry.Get(segmentId); if (segGeometry == null) { Log.Error($"CustomTrafficLightsManager.AddSegmentLights: Segment {segmentId} is invalid."); return null; } SegmentEndGeometry endGeometry = segGeometry.GetEnd(startNode); if (endGeometry == null) { Log.Error($"CustomTrafficLightsManager.AddSegmentLights: Segment {segmentId} is not connected to a node @ start {startNode}"); return null; } CustomSegment customSegment = CustomSegments[segmentId]; if (customSegment == null) { customSegment = new CustomSegment(); CustomSegments[segmentId] = customSegment; } else { ICustomSegmentLights existingLights = startNode ? customSegment.StartNodeLights : customSegment.EndNodeLights; if (existingLights != null) { existingLights.SetLights(lightState); return existingLights; } } if (startNode) { customSegment.StartNodeLights = new CustomSegmentLights(this, segmentId, startNode, false); customSegment.StartNodeLights.SetLights(lightState); return customSegment.StartNodeLights; } else { customSegment.EndNodeLights = new CustomSegmentLights(this, segmentId, startNode, false); customSegment.EndNodeLights.SetLights(lightState); return customSegment.EndNodeLights; } } public bool SetSegmentLights(ushort nodeId, ushort segmentId, ICustomSegmentLights lights) { SegmentEndGeometry endGeo = SegmentGeometry.Get(segmentId)?.GetEnd(nodeId); if (endGeo == null) { return false; } CustomSegment customSegment = CustomSegments[segmentId]; if (customSegment == null) { customSegment = new CustomSegment(); CustomSegments[segmentId] = customSegment; } if (endGeo.StartNode) { customSegment.StartNodeLights = lights; } else { customSegment.EndNodeLights = lights; } return true; } /// /// Add custom traffic lights at the given node /// /// public void AddNodeLights(ushort nodeId) { NodeGeometry nodeGeo = NodeGeometry.Get(nodeId); if (!nodeGeo.IsValid()) return; foreach (SegmentEndGeometry endGeo in nodeGeo.SegmentEndGeometries) { if (endGeo == null) continue; AddSegmentLights(endGeo.SegmentId, endGeo.StartNode); } } /// /// Removes custom traffic lights at the given node /// /// public void RemoveNodeLights(ushort nodeId) { NodeGeometry nodeGeo = NodeGeometry.Get(nodeId); /*if (!nodeGeo.IsValid()) return;*/ foreach (SegmentEndGeometry endGeo in nodeGeo.SegmentEndGeometries) { if (endGeo == null) continue; RemoveSegmentLight(endGeo.SegmentId, endGeo.StartNode); } } /// /// Removes all custom traffic lights at both ends of the given segment. /// /// public void RemoveSegmentLights(ushort segmentId) { CustomSegments[segmentId] = null; } /// /// Removes the custom traffic light at the given segment end. /// /// /// public void RemoveSegmentLight(ushort segmentId, bool startNode) { #if DEBUG Log.Warning($"Removing segment light: {segmentId} @ startNode={startNode}"); #endif CustomSegment customSegment = CustomSegments[segmentId]; if (customSegment == null) { return; } if (startNode) { customSegment.StartNodeLights = null; } else { customSegment.EndNodeLights = null; } if (customSegment.StartNodeLights == null && customSegment.EndNodeLights == null) { CustomSegments[segmentId] = null; } } /// /// Checks if a custom traffic light is present at the given segment end. /// /// /// /// public bool IsSegmentLight(ushort segmentId, bool startNode) { CustomSegment customSegment = CustomSegments[segmentId]; if (customSegment == null) { return false; } return (startNode && customSegment.StartNodeLights != null) || (!startNode && customSegment.EndNodeLights != null); } /// /// Retrieves the custom traffic light at the given segment end. If none exists, a new custom traffic light is created and returned. /// /// /// /// existing or new custom traffic light at segment end public ICustomSegmentLights GetOrLiveSegmentLights(ushort segmentId, bool startNode) { if (! IsSegmentLight(segmentId, startNode)) return AddLiveSegmentLights(segmentId, startNode); return GetSegmentLights(segmentId, startNode); } /// /// Retrieves the custom traffic light at the given segment end. /// /// /// /// existing custom traffic light at segment end, null if none exists public ICustomSegmentLights GetSegmentLights(ushort segmentId, bool startNode, bool add=true, RoadBaseAI.TrafficLightState lightState = RoadBaseAI.TrafficLightState.Red) { if (!IsSegmentLight(segmentId, startNode)) { if (add) return AddSegmentLights(segmentId, startNode, lightState); else return null; } CustomSegment customSegment = CustomSegments[segmentId]; if (startNode) { return customSegment.StartNodeLights; } else { return customSegment.EndNodeLights; } } public void SetLightMode(ushort segmentId, bool startNode, ExtVehicleType vehicleType, LightMode mode) { ICustomSegmentLights liveLights = GetSegmentLights(segmentId, startNode); if (liveLights == null) { Log.Warning($"CustomSegmentLightsManager.SetLightMode({segmentId}, {startNode}, {vehicleType}, {mode}): Could not retrieve segment lights."); return; } ICustomSegmentLight liveLight = liveLights.GetCustomLight(vehicleType); if (liveLight == null) { Log.Error($"CustomSegmentLightsManager.SetLightMode: Cannot change light mode on seg. {segmentId} @ {startNode} for vehicle type {vehicleType} to {mode}: Vehicle light not found"); return; } liveLight.CurrentMode = mode; } public bool ApplyLightModes(ushort segmentId, bool startNode, ICustomSegmentLights otherLights) { ICustomSegmentLights sourceLights = GetSegmentLights(segmentId, startNode); if (sourceLights == null) { Log.Warning($"CustomSegmentLightsManager.ApplyLightModes({segmentId}, {startNode}, {otherLights}): Could not retrieve segment lights."); return false; } foreach (KeyValuePair e in sourceLights.CustomLights) { ExtVehicleType vehicleType = e.Key; ICustomSegmentLight targetLight = e.Value; ICustomSegmentLight sourceLight; if (otherLights.CustomLights.TryGetValue(vehicleType, out sourceLight)) { targetLight.CurrentMode = sourceLight.CurrentMode; } } return true; } public ICustomSegmentLights GetSegmentLights(ushort nodeId, ushort segmentId) { SegmentEndGeometry endGeometry = SegmentGeometry.Get(segmentId)?.GetEnd(nodeId); if (endGeometry == null) { return null; } return GetSegmentLights(segmentId, endGeometry.StartNode, false); } protected override void HandleInvalidSegment(SegmentGeometry geometry) { RemoveSegmentLights(geometry.SegmentId); } public override void OnLevelUnloading() { base.OnLevelUnloading(); CustomSegments = new CustomSegment[NetManager.MAX_SEGMENT_COUNT]; } public short ClockwiseIndexOfSegmentEnd(ISegmentEndId endId) { SegmentEndGeometry endGeo = SegmentGeometry.Get(endId.SegmentId)?.GetEnd(endId.StartNode); if (endGeo == null) { return 0; } return endGeo.GetClockwiseIndex(); } } } ================================================ FILE: TLM/TLM/Manager/Impl/ExtBuildingManager.cs ================================================ using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Text; using System.Threading; using TrafficManager.Custom.AI; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Traffic.Data; using TrafficManager.Util; using UnityEngine; namespace TrafficManager.Manager.Impl { public class ExtBuildingManager : AbstractCustomManager, IExtBuildingManager { public static ExtBuildingManager Instance { get; private set; } = null; static ExtBuildingManager() { Instance = new ExtBuildingManager(); } /// /// All additional data for buildings /// public ExtBuilding[] ExtBuildings = null; private ExtBuildingManager() { ExtBuildings = new ExtBuilding[BuildingManager.MAX_BUILDING_COUNT]; for (uint i = 0; i < BuildingManager.MAX_BUILDING_COUNT; ++i) { ExtBuildings[i] = new ExtBuilding((ushort)i); } } protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"Extended building data:"); for (int i = 0; i < ExtBuildings.Length; ++i) { if (! ExtBuildings[i].IsValid()) { continue; } Log._Debug($"Building {i}: {ExtBuildings[i]}"); } } public override void OnLevelUnloading() { base.OnLevelUnloading(); for (int i = 0; i < ExtBuildings.Length; ++i) ExtBuildings[i].Reset(); } } } ================================================ FILE: TLM/TLM/Manager/Impl/ExtCitizenInstanceManager.cs ================================================ using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Text; using System.Threading; using TrafficManager.Custom.AI; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Traffic.Data; using TrafficManager.Util; using UnityEngine; using static TrafficManager.Traffic.Data.ExtCitizenInstance; namespace TrafficManager.Manager.Impl { public class ExtCitizenInstanceManager : AbstractCustomManager, ICustomDataManager>, IExtCitizenInstanceManager { public static ExtCitizenInstanceManager Instance = new ExtCitizenInstanceManager(); /// /// All additional data for citizen instance. Index: citizen instance id /// public ExtCitizenInstance[] ExtInstances = null; protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"Extended citizen instance data:"); for (int i = 0; i < ExtInstances.Length; ++i) { if (!ExtInstances[i].IsValid()) { continue; } Log._Debug($"Citizen instance {i}: {ExtInstances[i]}"); } } internal void OnReleaseInstance(ushort instanceId) { ExtInstances[instanceId].Reset(); } public void ResetInstance(ushort instanceId) { ExtInstances[instanceId].Reset(); } private ExtCitizenInstanceManager() { ExtInstances = new ExtCitizenInstance[CitizenManager.MAX_INSTANCE_COUNT]; for (uint i = 0; i < CitizenManager.MAX_INSTANCE_COUNT; ++i) { ExtInstances[i] = new ExtCitizenInstance((ushort)i); } } public override void OnLevelUnloading() { base.OnLevelUnloading(); Reset(); } internal void Reset() { for (int i = 0; i < ExtInstances.Length; ++i) { ExtInstances[i].Reset(); } } public bool LoadData(List data) { bool success = true; Log.Info($"Loading {data.Count} extended citizen instances"); foreach (Configuration.ExtCitizenInstanceData item in data) { try { uint instanceId = item.instanceId; ExtInstances[instanceId].pathMode = (ExtPathMode)item.pathMode; ExtInstances[instanceId].failedParkingAttempts = item.failedParkingAttempts; ExtInstances[instanceId].parkingSpaceLocationId = item.parkingSpaceLocationId; ExtInstances[instanceId].parkingSpaceLocation = (ExtParkingSpaceLocation)item.parkingSpaceLocation; if (item.parkingPathStartPositionSegment != 0) { PathUnit.Position pos = new PathUnit.Position(); pos.m_segment = item.parkingPathStartPositionSegment; pos.m_lane = item.parkingPathStartPositionLane; pos.m_offset = item.parkingPathStartPositionOffset; ExtInstances[instanceId].parkingPathStartPosition = pos; } else { ExtInstances[instanceId].parkingPathStartPosition = null; } ExtInstances[instanceId].returnPathId = item.returnPathId; ExtInstances[instanceId].returnPathState = (ExtPathState)item.returnPathState; ExtInstances[instanceId].lastDistanceToParkedCar = item.lastDistanceToParkedCar; } catch (Exception e) { // ignore, as it's probably corrupt save data. it'll be culled on next save Log.Warning("Error loading ext. citizen instance: " + e.ToString()); success = false; } } return success; } public List SaveData(ref bool success) { List ret = new List(); for (uint instanceId = 0; instanceId < CitizenManager.MAX_INSTANCE_COUNT; ++instanceId) { try { if ((Singleton.instance.m_instances.m_buffer[instanceId].m_flags & CitizenInstance.Flags.Created) == CitizenInstance.Flags.None) { continue; } if (ExtInstances[instanceId].pathMode == ExtPathMode.None && ExtInstances[instanceId].returnPathId == 0) { continue; } Configuration.ExtCitizenInstanceData item = new Configuration.ExtCitizenInstanceData(instanceId); item.pathMode = (int)ExtInstances[instanceId].pathMode; item.failedParkingAttempts = ExtInstances[instanceId].failedParkingAttempts; item.parkingSpaceLocationId = ExtInstances[instanceId].parkingSpaceLocationId; item.parkingSpaceLocation = (int)ExtInstances[instanceId].parkingSpaceLocation; if (ExtInstances[instanceId].parkingPathStartPosition != null) { PathUnit.Position pos = (PathUnit.Position)ExtInstances[instanceId].parkingPathStartPosition; item.parkingPathStartPositionSegment = pos.m_segment; item.parkingPathStartPositionLane = pos.m_lane; item.parkingPathStartPositionOffset = pos.m_offset; } else { item.parkingPathStartPositionSegment = 0; item.parkingPathStartPositionLane = 0; item.parkingPathStartPositionOffset = 0; } item.returnPathId = ExtInstances[instanceId].returnPathId; item.returnPathState = (int)ExtInstances[instanceId].returnPathState; item.lastDistanceToParkedCar = ExtInstances[instanceId].lastDistanceToParkedCar; ret.Add(item); } catch (Exception ex) { Log.Error($"Exception occurred while saving ext. citizen instances @ {instanceId}: {ex.ToString()}"); success = false; } } return ret; } public bool IsAtOutsideConnection(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, Vector3 startPos) { #if DEBUG bool citDebug = (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == extInstance.GetCitizenId()) && (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == Singleton.instance.m_instances.m_buffer[extInstance.instanceId].m_sourceBuilding) && (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == Singleton.instance.m_instances.m_buffer[extInstance.instanceId].m_targetBuilding) ; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; if (debug) Log._Debug($"ExtCitizenInstanceManager.IsAtOutsideConnection({extInstance.instanceId}): called. Path: {instanceData.m_path} sourceBuilding={instanceData.m_sourceBuilding}"); #endif bool ret = (Singleton.instance.m_buildings.m_buffer[instanceData.m_sourceBuilding].m_flags & Building.Flags.IncomingOutgoing) != Building.Flags.None && (startPos - Singleton.instance.m_buildings.m_buffer[instanceData.m_sourceBuilding].m_position).magnitude <= GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance; #if DEBUG if (debug) Log._Debug($"ExtCitizenInstanceManager.IsAtOutsideConnection({instanceId}): ret={ret}"); #endif return ret; } } } ================================================ FILE: TLM/TLM/Manager/Impl/ExtCitizenManager.cs ================================================ using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Text; using System.Threading; using TrafficManager.Custom.AI; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Traffic.Data; using TrafficManager.Util; using UnityEngine; using static TrafficManager.Traffic.Data.ExtCitizen; using static TrafficManager.Traffic.Data.ExtCitizenInstance; namespace TrafficManager.Manager.Impl { public class ExtCitizenManager : AbstractCustomManager, ICustomDataManager>, IExtCitizenManager { public static ExtCitizenManager Instance = new ExtCitizenManager(); /// /// All additional data for citizens. Index: citizen id /// public ExtCitizen[] ExtCitizens = null; protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"Extended citizen data:"); for (int i = 0; i < ExtCitizens.Length; ++i) { if (!ExtCitizens[i].IsValid()) { continue; } Log._Debug($"Citizen {i}: {ExtCitizens[i]}"); } } internal void OnReleaseCitizen(uint citizenId) { ExtCitizens[citizenId].Reset(); } public void OnArriveAtDestination(uint citizenId, ref Citizen citizen) { #if DEBUG bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenId; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; if (fineDebug) Log._Debug($"ExtCitizenManager.OnArriveAtDestination({citizenId}) called"); #endif if (ExtCitizens[citizenId].lastLocation == Citizen.Location.Home) { #if DEBUG if (fineDebug) Log._Debug($"ExtCitizenManager.OnArriveAtDestination({citizenId}): setting lastTransportMode=transportMode={ExtCitizens[citizenId].transportMode}"); #endif ExtCitizens[citizenId].lastTransportMode = ExtCitizens[citizenId].transportMode; } ExtCitizens[citizenId].transportMode = ExtTransportMode.None; if (citizen.CurrentLocation != Citizen.Location.Moving) { ExtCitizens[citizenId].lastLocation = citizen.CurrentLocation; #if DEBUG if (fineDebug) Log._Debug($"ExtCitizenManager.OnArriveAtDestination({citizenId}): setting lastLocation={ExtCitizens[citizenId].lastLocation}"); #endif } } public void ResetCitizen(uint citizenId) { ExtCitizens[citizenId].Reset(); } private ExtCitizenManager() { ExtCitizens = new ExtCitizen[CitizenManager.MAX_CITIZEN_COUNT]; for (uint i = 0; i < CitizenManager.MAX_CITIZEN_COUNT; ++i) { ExtCitizens[i] = new ExtCitizen(i); } } public override void OnLevelUnloading() { base.OnLevelUnloading(); Reset(); } internal void Reset() { for (int i = 0; i < ExtCitizens.Length; ++i) { ExtCitizens[i].Reset(); } } public bool LoadData(List data) { bool success = true; Log.Info($"Loading {data.Count} extended citizens"); foreach (Configuration.ExtCitizenData item in data) { try { uint citizenId = item.citizenId; ExtCitizens[citizenId].transportMode = (ExtTransportMode)item.lastTransportMode; } catch (Exception e) { // ignore, as it's probably corrupt save data. it'll be culled on next save Log.Warning("Error loading ext. citizen: " + e.ToString()); success = false; } } return success; } public List SaveData(ref bool success) { List ret = new List(); for (uint citizenId = 0; citizenId < CitizenManager.MAX_CITIZEN_COUNT; ++citizenId) { try { if ((Singleton.instance.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Created) == Citizen.Flags.None) { continue; } if (ExtCitizens[citizenId].transportMode == ExtTransportMode.None) { continue; } Configuration.ExtCitizenData item = new Configuration.ExtCitizenData(citizenId); item.lastTransportMode = (int)ExtCitizens[citizenId].transportMode; ret.Add(item); } catch (Exception ex) { Log.Error($"Exception occurred while saving ext. citizen @ {citizenId}: {ex.ToString()}"); success = false; } } return ret; } } } ================================================ FILE: TLM/TLM/Manager/Impl/GeometryManager.cs ================================================ using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using TrafficManager.Geometry.Impl; using TrafficManager.State; using TrafficManager.Util; using static TrafficManager.Geometry.Impl.NodeGeometry; namespace TrafficManager.Manager.Impl { public class GeometryManager : AbstractCustomManager, IGeometryManager { public static GeometryManager Instance { get; private set; } = new GeometryManager(); public class GeometryUpdateObservable : GenericObservable { } private bool stateUpdated; private ulong[] updatedSegmentBuckets; private ulong[] updatedNodeBuckets; private object updateLock; private Queue segmentReplacements; private GeometryUpdateObservable geometryUpdateObservable; private GeometryManager() { stateUpdated = false; updatedSegmentBuckets = new ulong[576]; updatedNodeBuckets = new ulong[512]; updateLock = new object(); segmentReplacements = new Queue(); geometryUpdateObservable = new GeometryUpdateObservable(); } public override void OnBeforeLoadData() { base.OnBeforeLoadData(); segmentReplacements.Clear(); SimulationStep(); } public void OnUpdateSegment(SegmentGeometry geo) { MarkAsUpdated(geo); } public void SimulationStep(bool onlyFirstPass=false) { #if DEBUGGEO bool debug = GlobalConfig.Instance.Debug.Switches[5]; #endif if (!stateUpdated) { return; } NetManager netManager = Singleton.instance; if (!onlyFirstPass && (netManager.m_segmentsUpdated || netManager.m_nodesUpdated)) { // TODO maybe refactor NetManager use (however this could influence performance) #if DEBUGGEO if (debug) Log._Debug($"GeometryManager.SimulationStep(): Skipping! stateUpdated={stateUpdated}, m_segmentsUpdated={netManager.m_segmentsUpdated}, m_nodesUpdated={netManager.m_nodesUpdated}"); #endif return; } try { Monitor.Enter(updateLock); bool updatesMissing = onlyFirstPass; for (int pass = 0; pass < (onlyFirstPass ? 1 : 2); ++pass) { bool firstPass = pass == 0; int len = updatedSegmentBuckets.Length; for (int i = 0; i < len; i++) { ulong segMask = updatedSegmentBuckets[i]; if (segMask != 0uL) { for (int m = 0; m < 64; m++) { if ((segMask & 1uL << m) != 0uL) { ushort segmentId = (ushort)(i << 6 | m); SegmentGeometry segmentGeometry = SegmentGeometry.Get(segmentId, true); if (firstPass ^ !segmentGeometry.IsValid()) { if (! firstPass) { updatesMissing = true; #if DEBUGGEO if (debug) Log.Warning($"GeometryManager.SimulationStep(): Detected invalid segment {segmentGeometry.SegmentId} in second pass"); #endif } continue; } #if DEBUGGEO if (debug) Log._Debug($"GeometryManager.SimulationStep(): Notifying observers about segment {segmentGeometry.SegmentId}. Valid? {segmentGeometry.IsValid()} First pass? {firstPass}"); #endif NotifyObservers(new GeometryUpdate(segmentGeometry)); updatedSegmentBuckets[i] &= ~(1uL << m); } } } } len = updatedNodeBuckets.Length; for (int i = 0; i < len; i++) { ulong nodeMask = updatedNodeBuckets[i]; if (nodeMask != 0uL) { for (int m = 0; m < 64; m++) { if ((nodeMask & 1uL << m) != 0uL) { ushort nodeId = (ushort)(i << 6 | m); NodeGeometry nodeGeometry = NodeGeometry.Get(nodeId); if (firstPass ^ !nodeGeometry.IsValid()) { if (!firstPass) { updatesMissing = true; #if DEBUGGEO if (debug) Log.Warning($"GeometryManager.SimulationStep(): Detected invalid node {nodeGeometry.NodeId} in second pass"); #endif } continue; } #if DEBUGGEO if (debug) Log._Debug($"GeometryManager.SimulationStep(): Notifying observers about node {nodeGeometry.NodeId}. Valid? {nodeGeometry.IsValid()} First pass? {firstPass}"); #endif NotifyObservers(new GeometryUpdate(nodeGeometry)); updatedNodeBuckets[i] &= ~(1uL << m); } } } } } if (! updatesMissing) { while (segmentReplacements.Count > 0) { SegmentEndReplacement replacement = segmentReplacements.Dequeue(); #if DEBUGGEO if (debug) Log._Debug($"GeometryManager.SimulationStep(): Notifying observers about segment end replacement {replacement}"); #endif NotifyObservers(new GeometryUpdate(replacement)); } stateUpdated = false; } } finally { Monitor.Exit(updateLock); } } public void MarkAllAsUpdated() { for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { SegmentGeometry segGeo = SegmentGeometry.Get((ushort)segmentId); if (segGeo != null) { MarkAsUpdated(segGeo, true); } } } public void MarkAsUpdated(SegmentGeometry geometry, bool updateNodes = true) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"GeometryManager.MarkAsUpdated(segment {geometry.SegmentId}): Marking segment as updated"); #endif try { Monitor.Enter(updateLock); ushort segmentId = geometry.SegmentId; updatedSegmentBuckets[segmentId >> 6] |= 1uL << (int)(segmentId & 63); stateUpdated = true; if (updateNodes) { MarkAsUpdated(NodeGeometry.Get(geometry.StartNodeId())); MarkAsUpdated(NodeGeometry.Get(geometry.EndNodeId())); } if (! geometry.IsValid()) { SimulationStep(true); } } finally { Monitor.Exit(updateLock); } } public void MarkAsUpdated(NodeGeometry geometry, bool updateSegments = false) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"GeometryManager.MarkAsUpdated(node {geometry.NodeId}): Marking node as updated"); #endif try { Monitor.Enter(updateLock); ushort nodeId = geometry.NodeId; if (nodeId != 0) { updatedNodeBuckets[nodeId >> 6] |= 1uL << (int)(nodeId & 63); stateUpdated = true; if (updateSegments) { foreach (SegmentEndGeometry segEndGeo in geometry.SegmentEndGeometries) { if (segEndGeo != null) { MarkAsUpdated(segEndGeo.GetSegmentGeometry(), false); } } } if (!geometry.IsValid()) { SimulationStep(true); } } } finally { Monitor.Exit(updateLock); } } public void OnSegmentEndReplacement(NodeGeometry.SegmentEndReplacement replacement) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"GeometryManager.OnSegmentEndReplacement(): Detected segment replacement: {replacement.oldSegmentEndId.SegmentId} -> {replacement.newSegmentEndId.SegmentId}"); #endif try { Monitor.Enter(updateLock); segmentReplacements.Enqueue(replacement); stateUpdated = true; } finally { Monitor.Exit(updateLock); } } public IDisposable Subscribe(IObserver observer) { #if DEBUGGEO if (GlobalConfig.Instance.Debug.Switches[5]) Log._Debug($"GeometryManager.Subscribe(): Subscribing observer {observer.GetType().Name}"); #endif return geometryUpdateObservable.Subscribe(observer); } protected void NotifyObservers(GeometryUpdate geometryUpdate) { geometryUpdateObservable.NotifyObservers(geometryUpdate); } } } ================================================ FILE: TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs ================================================ using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Text; using TrafficManager.Geometry; using TrafficManager.Geometry.Impl; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Traffic.Data; using TrafficManager.Traffic.Impl; using TrafficManager.Util; using static TrafficManager.Geometry.Impl.NodeGeometry; namespace TrafficManager.Manager.Impl { public class JunctionRestrictionsManager : AbstractGeometryObservingManager, ICustomDataManager>, IJunctionRestrictionsManager { public static JunctionRestrictionsManager Instance { get; private set; } = new JunctionRestrictionsManager(); private SegmentFlags[] invalidSegmentFlags = null; /// /// Holds junction restrictions for each segment end /// private SegmentFlags[] SegmentFlags = null; private JunctionRestrictionsManager() { SegmentFlags = new Traffic.Data.SegmentFlags[NetManager.MAX_SEGMENT_COUNT]; invalidSegmentFlags = new Traffic.Data.SegmentFlags[NetManager.MAX_SEGMENT_COUNT]; } protected void AddInvalidSegmentEndFlags(ushort segmentId, bool startNode, ref SegmentEndFlags endFlags) { if (startNode) { invalidSegmentFlags[segmentId].startNodeFlags = endFlags; } else { invalidSegmentFlags[segmentId].endNodeFlags = endFlags; } } protected override void HandleSegmentEndReplacement(SegmentEndReplacement replacement, SegmentEndGeometry endGeo) { ISegmentEndId oldSegmentEndId = replacement.oldSegmentEndId; ISegmentEndId newSegmentEndId = replacement.newSegmentEndId; SegmentEndFlags flags; if (oldSegmentEndId.StartNode) { flags = invalidSegmentFlags[oldSegmentEndId.SegmentId].startNodeFlags; invalidSegmentFlags[oldSegmentEndId.SegmentId].startNodeFlags.Reset(); } else { flags = invalidSegmentFlags[oldSegmentEndId.SegmentId].endNodeFlags; invalidSegmentFlags[oldSegmentEndId.SegmentId].endNodeFlags.Reset(); } Services.NetService.ProcessNode(endGeo.NodeId(), delegate (ushort nId, ref NetNode node) { flags.UpdateDefaults(newSegmentEndId.SegmentId, newSegmentEndId.StartNode, ref node); return true; }); Log._Debug($"JunctionRestrictionsManager.HandleSegmentEndReplacement({replacement}): Segment replacement detected: {oldSegmentEndId.SegmentId} -> {newSegmentEndId.SegmentId} @ {newSegmentEndId.StartNode}"); SetSegmentEndFlags(newSegmentEndId.SegmentId, newSegmentEndId.StartNode, flags); } public override void OnLevelLoading() { base.OnLevelLoading(); for (uint i = 0; i < NetManager.MAX_SEGMENT_COUNT; ++i) { SegmentGeometry geo = SegmentGeometry.Get((ushort)i); if (geo != null && geo.IsValid()) { //Log._Debug($"JunctionRestrictionsManager.OnLevelLoading: Handling valid segment {geo.SegmentId}"); HandleValidSegment(geo); } } } protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"Junction restrictions:"); for (int i = 0; i < SegmentFlags.Length; ++i) { if (SegmentFlags[i].IsDefault()) { continue; } Log._Debug($"Segment {i}: {SegmentFlags[i]}"); } } public bool MayHaveJunctionRestrictions(ushort nodeId) { NetNode.Flags flags = NetNode.Flags.None; Services.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { flags = node.m_flags; return true; }); Log._Debug($"JunctionRestrictionsManager.MayHaveJunctionRestrictions({nodeId}): flags={(NetNode.Flags)flags}"); if (!LogicUtil.CheckFlags((uint)flags, (uint)(NetNode.Flags.Created | NetNode.Flags.Deleted), (uint)NetNode.Flags.Created)) { return false; } return LogicUtil.CheckFlags((uint)flags, (uint)(NetNode.Flags.Junction | NetNode.Flags.Bend)); } public bool HasJunctionRestrictions(ushort nodeId) { if (!Services.NetService.IsNodeValid(nodeId)) { return false; } bool ret = false; Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { if (segmentId == 0) { return true; } bool startNode = segment.m_startNode == nodeId; bool isDefault = startNode ? SegmentFlags[segmentId].startNodeFlags.IsDefault() : SegmentFlags[segmentId].endNodeFlags.IsDefault(); if (!isDefault) { ret = true; return false; } return true; }); return ret; } public void RemoveJunctionRestrictions(ushort nodeId) { Log._Debug($"JunctionRestrictionsManager.RemoveJunctionRestrictions({nodeId}) called."); Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { if (segmentId == 0) { return true; } if (segment.m_startNode == nodeId) { SegmentFlags[segmentId].startNodeFlags.Reset(false); } else { SegmentFlags[segmentId].endNodeFlags.Reset(false); } return true; }); } public void RemoveJunctionRestrictionsIfNecessary() { for (uint nodeId = 0; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { RemoveJunctionRestrictionsIfNecessary((ushort)nodeId); } } public void RemoveJunctionRestrictionsIfNecessary(ushort nodeId) { if (!MayHaveJunctionRestrictions(nodeId)) { RemoveJunctionRestrictions(nodeId); } } protected override void HandleInvalidSegment(SegmentGeometry geometry) { foreach (bool startNode in Constants.ALL_BOOL) { SegmentEndFlags flags = startNode ? SegmentFlags[geometry.SegmentId].startNodeFlags : SegmentFlags[geometry.SegmentId].endNodeFlags; if (!flags.IsDefault()) { AddInvalidSegmentEndFlags(geometry.SegmentId, startNode, ref flags); } SegmentFlags[geometry.SegmentId].Reset(startNode, true); } } protected override void HandleValidSegment(SegmentGeometry geometry) { UpdateDefaults(geometry); } public void UpdateAllDefaults() { for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { SegmentGeometry segGeo = SegmentGeometry.Get((ushort)segmentId); if (segGeo != null) { UpdateDefaults(segGeo); } } } protected void UpdateDefaults(SegmentGeometry geometry) { //Log.Warning($"JunctionRestrictionsManager.HandleValidSegment({geometry.SegmentId}) called."); ushort startNodeId = geometry.StartNodeId(); Services.NetService.ProcessNode(startNodeId, delegate (ushort nId, ref NetNode node) { SegmentFlags[geometry.SegmentId].startNodeFlags.UpdateDefaults(geometry.SegmentId, true, ref node); return true; }); ushort endNodeId = geometry.EndNodeId(); Services.NetService.ProcessNode(endNodeId, delegate (ushort nId, ref NetNode node) { SegmentFlags[geometry.SegmentId].endNodeFlags.UpdateDefaults(geometry.SegmentId, false, ref node); return true; }); } public bool IsUturnAllowedConfigurable(ushort segmentId, bool startNode, ref NetNode node) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[11]; #endif SegmentEndGeometry endGeo = SegmentGeometry.Get(segmentId)?.GetEnd(startNode); if (endGeo == null) { Log.Warning($"JunctionRestrictionsManager.IsUturnAllowedConfigurable({segmentId}, {startNode}): Could not get segment end geometry"); return false; } bool ret = (node.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition | NetNode.Flags.End | NetNode.Flags.Bend | NetNode.Flags.OneWayOut)) != NetNode.Flags.None && node.Info?.m_class?.m_service != ItemClass.Service.Beautification && !endGeo.IncomingOneWay && !endGeo.OutgoingOneWay ; #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.IsUturnAllowedConfigurable({segmentId}, {startNode}): ret={ret}, flags={node.m_flags}, service={node.Info?.m_class?.m_service}, incomingOneWay={endGeo.IncomingOneWay}, outgoingOneWay={endGeo.OutgoingOneWay}"); #endif return ret; } public bool GetDefaultUturnAllowed(ushort segmentId, bool startNode, ref NetNode node) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[11]; #endif if (!Constants.ManagerFactory.JunctionRestrictionsManager.IsUturnAllowedConfigurable(segmentId, startNode, ref node)) { bool res = (node.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.GetDefaultUturnAllowed({segmentId}, {startNode}): Setting is not configurable. res={res}, flags={node.m_flags}"); #endif return res; } bool ret = (node.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; if (!ret && Options.allowUTurns) { ret = (node.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) != NetNode.Flags.None; } #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.GetDefaultUturnAllowed({segmentId}, {startNode}): Setting is configurable. ret={ret}, flags={node.m_flags}"); #endif return ret; } public bool IsUturnAllowed(ushort segmentId, bool startNode) { return SegmentFlags[segmentId].IsUturnAllowed(startNode); } public bool IsNearTurnOnRedAllowedConfigurable(ushort segmentId, bool startNode, ref NetNode node) { return IsTurnOnRedAllowedConfigurable(true, segmentId, startNode, ref node); } public bool IsFarTurnOnRedAllowedConfigurable(ushort segmentId, bool startNode, ref NetNode node) { return IsTurnOnRedAllowedConfigurable(false, segmentId, startNode, ref node); } public bool IsTurnOnRedAllowedConfigurable(bool near, ushort segmentId, bool startNode, ref NetNode node) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[11]; #endif ITurnOnRedManager turnOnRedMan = Constants.ManagerFactory.TurnOnRedManager; int index = turnOnRedMan.GetIndex(segmentId, startNode); bool lhd = Services.SimulationService.LeftHandDrive; bool ret = (node.m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None && (((lhd == near) && turnOnRedMan.TurnOnRedSegments[index].leftSegmentId != 0) || ((!lhd == near) && turnOnRedMan.TurnOnRedSegments[index].rightSegmentId != 0)); #if DEBUG if (debug) Log.Warning($"JunctionRestrictionsManager.IsTurnOnRedAllowedConfigurable({near}, {segmentId}, {startNode}): ret={ret}"); #endif return ret; } public bool GetDefaultNearTurnOnRedAllowed(ushort segmentId, bool startNode, ref NetNode node) { return GetDefaultTurnOnRedAllowed(true, segmentId, startNode, ref node); } public bool GetDefaultFarTurnOnRedAllowed(ushort segmentId, bool startNode, ref NetNode node) { return GetDefaultTurnOnRedAllowed(false, segmentId, startNode, ref node); } public bool GetDefaultTurnOnRedAllowed(bool near, ushort segmentId, bool startNode, ref NetNode node) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[11]; #endif if (!IsTurnOnRedAllowedConfigurable(near, segmentId, startNode, ref node)) { #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.IsTurnOnRedAllowedConfigurable({near}, {segmentId}, {startNode}): Setting is not configurable. res=false"); #endif return false; } bool ret = near ? Options.allowNearTurnOnRed : Options.allowFarTurnOnRed; #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.GetTurnOnRedAllowed({near}, {segmentId}, {startNode}): Setting is configurable. ret={ret}, flags={node.m_flags}"); #endif return ret; } public bool IsTurnOnRedAllowed(bool near, ushort segmentId, bool startNode) { return near ? IsNearTurnOnRedAllowed(segmentId, startNode) : IsFarTurnOnRedAllowed(segmentId, startNode); } public bool IsNearTurnOnRedAllowed(ushort segmentId, bool startNode) { return SegmentFlags[segmentId].IsNearTurnOnRedAllowed(startNode); } public bool IsFarTurnOnRedAllowed(ushort segmentId, bool startNode) { return SegmentFlags[segmentId].IsFarTurnOnRedAllowed(startNode); } public bool IsLaneChangingAllowedWhenGoingStraightConfigurable(ushort segmentId, bool startNode, ref NetNode node) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[11]; #endif SegmentEndGeometry endGeo = SegmentGeometry.Get(segmentId)?.GetEnd(startNode); if (endGeo == null) { Log.Warning($"JunctionRestrictionsManager.IsLaneChangingAllowedWhenGoingStraightConfigurable({segmentId}, {startNode}): Could not get segment end geometry"); return false; } bool ret = (node.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) != NetNode.Flags.None && node.Info?.m_class?.m_service != ItemClass.Service.Beautification && !endGeo.OutgoingOneWay && node.CountSegments() > 2 ; #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.IsLaneChangingAllowedWhenGoingStraightConfigurable({segmentId}, {startNode}): ret={ret}, flags={node.m_flags}, service={node.Info?.m_class?.m_service}, incomingOneWay={endGeo.IncomingOneWay}, outgoingOneWay={endGeo.OutgoingOneWay}, node.CountSegments()={node.CountSegments()}"); #endif return ret; } public bool GetDefaultLaneChangingAllowedWhenGoingStraight(ushort segmentId, bool startNode, ref NetNode node) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[11]; #endif if (!Constants.ManagerFactory.JunctionRestrictionsManager.IsLaneChangingAllowedWhenGoingStraightConfigurable(segmentId, startNode, ref node)) { #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.GetDefaultLaneChangingAllowedWhenGoingStraight({segmentId}, {startNode}): Setting is not configurable. res=false"); #endif return false; } bool ret = Options.allowLaneChangesWhileGoingStraight; #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.GetDefaultLaneChangingAllowedWhenGoingStraight({segmentId}, {startNode}): Setting is configurable. ret={ret}"); #endif return ret; } public bool IsLaneChangingAllowedWhenGoingStraight(ushort segmentId, bool startNode) { return SegmentFlags[segmentId].IsLaneChangingAllowedWhenGoingStraight(startNode); } public bool IsEnteringBlockedJunctionAllowedConfigurable(ushort segmentId, bool startNode, ref NetNode node) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[11]; #endif SegmentEndGeometry endGeo = SegmentGeometry.Get(segmentId)?.GetEnd(startNode); if (endGeo == null) { Log.Warning($"JunctionRestrictionsManager.IsEnteringBlockedJunctionAllowedConfigurable({segmentId}, {startNode}): Could not get segment end geometry"); return false; } bool ret = (node.m_flags & NetNode.Flags.Junction) != NetNode.Flags.None && node.Info?.m_class?.m_service != ItemClass.Service.Beautification && !endGeo.OutgoingOneWay; ; #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.IsEnteringBlockedJunctionAllowedConfigurable({segmentId}, {startNode}): ret={ret}, flags={node.m_flags}, service={node.Info?.m_class?.m_service}, outgoingOneWay={endGeo.OutgoingOneWay}"); #endif return ret; } public bool GetDefaultEnteringBlockedJunctionAllowed(ushort segmentId, bool startNode, ref NetNode node) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[11]; #endif SegmentEndGeometry endGeo = SegmentGeometry.Get(segmentId)?.GetEnd(startNode); if (endGeo == null) { Log.Warning($"JunctionRestrictionsManager.GetDefaultEnteringBlockedJunctionAllowed({segmentId}, {startNode}): Could not get segment end geometry"); return false; } if (!IsEnteringBlockedJunctionAllowedConfigurable(segmentId, startNode, ref node)) { bool res = (node.m_flags & (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) != NetNode.Flags.Junction || node.CountSegments() == 2; #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.GetDefaultEnteringBlockedJunctionAllowed({segmentId}, {startNode}): Setting is not configurable. res={res}, flags={node.m_flags}, node.CountSegments()={node.CountSegments()}"); #endif return res; } bool ret; if (Options.allowEnterBlockedJunctions) { ret = true; } else { int numOutgoing = 0; int numIncoming = 0; node.CountLanes(endGeo.NodeId(), 0, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, true, ref numOutgoing, ref numIncoming); ret = numOutgoing == 1 || numIncoming == 1; } #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.GetDefaultEnteringBlockedJunctionAllowed({segmentId}, {startNode}): Setting is configurable. ret={ret}"); #endif return ret; } public bool IsEnteringBlockedJunctionAllowed(ushort segmentId, bool startNode) { //Log.Warning($"JunctionRestrictionsManager.IsEnteringBlockedJunctionAllowed({segmentId}, {startNode}) called."); return SegmentFlags[segmentId].IsEnteringBlockedJunctionAllowed(startNode); } public bool IsPedestrianCrossingAllowedConfigurable(ushort segmentId, bool startNode, ref NetNode node) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[11]; #endif bool ret = (node.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Bend)) != NetNode.Flags.None && node.Info?.m_class?.m_service != ItemClass.Service.Beautification; #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.IsPedestrianCrossingAllowedConfigurable({segmentId}, {startNode}): ret={ret}, flags={node.m_flags}, service={node.Info?.m_class?.m_service}"); #endif return ret; } public bool GetDefaultPedestrianCrossingAllowed(ushort segmentId, bool startNode, ref NetNode node) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[11]; #endif if (!IsPedestrianCrossingAllowedConfigurable(segmentId, startNode, ref node)) { #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.GetDefaultPedestrianCrossingAllowed({segmentId}, {startNode}): Setting is not configurable. res=true"); #endif return true; } bool ret = (node.m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.GetDefaultPedestrianCrossingAllowed({segmentId}, {startNode}): Setting is configurable. ret={ret}, flags={node.m_flags}"); #endif return ret; } public bool IsPedestrianCrossingAllowed(ushort segmentId, bool startNode) { return SegmentFlags[segmentId].IsPedestrianCrossingAllowed(startNode); } public TernaryBool GetUturnAllowed(ushort segmentId, bool startNode) { return SegmentFlags[segmentId].GetUturnAllowed(startNode); } public TernaryBool GetNearTurnOnRedAllowed(ushort segmentId, bool startNode) { return SegmentFlags[segmentId].GetNearTurnOnRedAllowed(startNode); } public TernaryBool GetFarTurnOnRedAllowed(ushort segmentId, bool startNode) { return SegmentFlags[segmentId].GetFarTurnOnRedAllowed(startNode); } public TernaryBool GetTurnOnRedAllowed(bool near, ushort segmentId, bool startNode) { return near ? GetNearTurnOnRedAllowed(segmentId, startNode) : GetFarTurnOnRedAllowed(segmentId, startNode); } public TernaryBool GetLaneChangingAllowedWhenGoingStraight(ushort segmentId, bool startNode) { return SegmentFlags[segmentId].GetLaneChangingAllowedWhenGoingStraight(startNode); } public TernaryBool GetEnteringBlockedJunctionAllowed(ushort segmentId, bool startNode) { return SegmentFlags[segmentId].GetEnteringBlockedJunctionAllowed(startNode); } public TernaryBool GetPedestrianCrossingAllowed(ushort segmentId, bool startNode) { return SegmentFlags[segmentId].GetPedestrianCrossingAllowed(startNode); } public bool ToggleUturnAllowed(ushort segmentId, bool startNode) { return SetUturnAllowed(segmentId, startNode, !IsUturnAllowed(segmentId, startNode)); } public bool ToggleNearTurnOnRedAllowed(ushort segmentId, bool startNode) { return ToggleTurnOnRedAllowed(true, segmentId, startNode); } public bool ToggleFarTurnOnRedAllowed(ushort segmentId, bool startNode) { return ToggleTurnOnRedAllowed(false, segmentId, startNode); } public bool ToggleTurnOnRedAllowed(bool near, ushort segmentId, bool startNode) { return SetTurnOnRedAllowed(near, segmentId, startNode, !IsTurnOnRedAllowed(near, segmentId, startNode)); } public bool ToggleLaneChangingAllowedWhenGoingStraight(ushort segmentId, bool startNode) { return SetLaneChangingAllowedWhenGoingStraight(segmentId, startNode, !IsLaneChangingAllowedWhenGoingStraight(segmentId, startNode)); } public bool ToggleEnteringBlockedJunctionAllowed(ushort segmentId, bool startNode) { return SetEnteringBlockedJunctionAllowed(segmentId, startNode, !IsEnteringBlockedJunctionAllowed(segmentId, startNode)); } public bool TogglePedestrianCrossingAllowed(ushort segmentId, bool startNode) { return SetPedestrianCrossingAllowed(segmentId, startNode, !IsPedestrianCrossingAllowed(segmentId, startNode)); } private void SetSegmentEndFlags(ushort segmentId, bool startNode, SegmentEndFlags flags) { if (flags.uturnAllowed != TernaryBool.Undefined) { SetUturnAllowed(segmentId, startNode, flags.IsUturnAllowed()); } if (flags.nearTurnOnRedAllowed != TernaryBool.Undefined) { SetNearTurnOnRedAllowed(segmentId, startNode, flags.IsNearTurnOnRedAllowed()); } if (flags.nearTurnOnRedAllowed != TernaryBool.Undefined) { SetFarTurnOnRedAllowed(segmentId, startNode, flags.IsFarTurnOnRedAllowed()); } if (flags.straightLaneChangingAllowed != TernaryBool.Undefined) { SetLaneChangingAllowedWhenGoingStraight(segmentId, startNode, flags.IsLaneChangingAllowedWhenGoingStraight()); } if (flags.enterWhenBlockedAllowed != TernaryBool.Undefined) { //Log.Warning($"JunctionRestrictionsManager.SetSegmentEndFlags({segmentId}, {startNode}, {flags}): flags.enterWhenBlockedAllowed is defined"); SetEnteringBlockedJunctionAllowed(segmentId, startNode, flags.IsEnteringBlockedJunctionAllowed()); } if (flags.pedestrianCrossingAllowed != TernaryBool.Undefined) { SetPedestrianCrossingAllowed(segmentId, startNode, flags.IsPedestrianCrossingAllowed()); } } public bool SetUturnAllowed(ushort segmentId, bool startNode, bool value) { if (!Services.NetService.IsSegmentValid(segmentId)) { return false; } if (!value && Constants.ManagerFactory.LaneConnectionManager.HasUturnConnections(segmentId, startNode)) { return false; } SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { Log.Error($"JunctionRestrictionsManager.SetUturnAllowed: No geometry information available for segment {segmentId}"); return false; } SegmentFlags[segmentId].SetUturnAllowed(startNode, value); OnSegmentChange(segmentId, startNode, segGeo, true); return true; } public bool SetNearTurnOnRedAllowed(ushort segmentId, bool startNode, bool value) { return SetTurnOnRedAllowed(true, segmentId, startNode, value); } public bool SetFarTurnOnRedAllowed(ushort segmentId, bool startNode, bool value) { return SetTurnOnRedAllowed(false, segmentId, startNode, value); } public bool SetTurnOnRedAllowed(bool near, ushort segmentId, bool startNode, bool value) { if (!Services.NetService.IsSegmentValid(segmentId)) { return false; } SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { Log.Error($"JunctionRestrictionsManager.SetTurnOnRedAllowed: No geometry information available for segment {segmentId}"); return false; } if (near) { SegmentFlags[segmentId].SetNearTurnOnRedAllowed(startNode, value); } else { SegmentFlags[segmentId].SetFarTurnOnRedAllowed(startNode, value); } OnSegmentChange(segmentId, startNode, segGeo, true); return true; } public bool SetLaneChangingAllowedWhenGoingStraight(ushort segmentId, bool startNode, bool value) { if (!Services.NetService.IsSegmentValid(segmentId)) { return false; } SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { Log.Error($"JunctionRestrictionsManager.SetUturnAllowed: No geometry information available for segment {segmentId}"); return false; } SegmentFlags[segmentId].SetLaneChangingAllowedWhenGoingStraight(startNode, value); OnSegmentChange(segmentId, startNode, segGeo, true); return true; } public bool SetEnteringBlockedJunctionAllowed(ushort segmentId, bool startNode, bool value) { if (!Services.NetService.IsSegmentValid(segmentId)) { return false; } SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { Log.Error($"JunctionRestrictionsManager.SetUturnAllowed: No geometry information available for segment {segmentId}"); return false; } SegmentFlags[segmentId].SetEnteringBlockedJunctionAllowed(startNode, value); // recalculation not needed here because this is a simulation-time feature OnSegmentChange(segmentId, startNode, segGeo, false); return true; } public bool SetPedestrianCrossingAllowed(ushort segmentId, bool startNode, bool value) { if (!Services.NetService.IsSegmentValid(segmentId)) { return false; } SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { Log.Error($"JunctionRestrictionsManager.SetPedestrianCrossingAllowed: No geometry information available for segment {segmentId}"); return false; } SegmentFlags[segmentId].SetPedestrianCrossingAllowed(startNode, value); OnSegmentChange(segmentId, startNode, segGeo, true); return true; } protected void OnSegmentChange(ushort segmentId, bool startNode, SegmentGeometry segGeo, bool requireRecalc) { ushort nodeId = segGeo.GetNodeId(startNode); HandleValidSegment(segGeo); if (requireRecalc) { Constants.ManagerFactory.RoutingManager.RequestRecalculation(segmentId); if (Constants.ManagerFactory.OptionsManager.MayPublishSegmentChanges()) { Services.NetService.PublishSegmentChanges(segmentId); } } } public override void OnLevelUnloading() { base.OnLevelUnloading(); for (int i = 0; i < SegmentFlags.Length; ++i) { SegmentFlags[i].Reset(true); } for (int i = 0; i < invalidSegmentFlags.Length; ++i) { invalidSegmentFlags[i].Reset(true); } } public bool LoadData(List data) { bool success = true; Log.Info($"Loading junction restrictions. {data.Count} elements"); foreach (Configuration.SegmentNodeConf segNodeConf in data) { try { if (!Services.NetService.IsSegmentValid(segNodeConf.segmentId)) { continue; } Log._Debug($"JunctionRestrictionsManager.LoadData: Loading junction restrictions for segment {segNodeConf.segmentId}: startNodeFlags={segNodeConf.startNodeFlags} endNodeFlags={segNodeConf.endNodeFlags}"); if (segNodeConf.startNodeFlags != null) { SegmentEndGeometry startNodeSegGeo = SegmentGeometry.Get(segNodeConf.segmentId)?.GetEnd(true); if (startNodeSegGeo != null) { Configuration.SegmentNodeFlags flags = segNodeConf.startNodeFlags; Services.NetService.ProcessNode(startNodeSegGeo.NodeId(), delegate (ushort nId, ref NetNode node) { if (flags.uturnAllowed != null && IsUturnAllowedConfigurable(segNodeConf.segmentId, true, ref node)) { SetUturnAllowed(segNodeConf.segmentId, true, (bool)flags.uturnAllowed); } if (flags.turnOnRedAllowed != null && IsNearTurnOnRedAllowedConfigurable(segNodeConf.segmentId, true, ref node)) { SetNearTurnOnRedAllowed(segNodeConf.segmentId, true, (bool)flags.turnOnRedAllowed); } if (flags.farTurnOnRedAllowed != null && IsNearTurnOnRedAllowedConfigurable(segNodeConf.segmentId, true, ref node)) { SetFarTurnOnRedAllowed(segNodeConf.segmentId, true, (bool)flags.farTurnOnRedAllowed); } if (flags.straightLaneChangingAllowed != null && IsLaneChangingAllowedWhenGoingStraightConfigurable(segNodeConf.segmentId, true, ref node)) { SetLaneChangingAllowedWhenGoingStraight(segNodeConf.segmentId, true, (bool)flags.straightLaneChangingAllowed); } if (flags.enterWhenBlockedAllowed != null && IsEnteringBlockedJunctionAllowedConfigurable(segNodeConf.segmentId, true, ref node)) { SetEnteringBlockedJunctionAllowed(segNodeConf.segmentId, true, (bool)flags.enterWhenBlockedAllowed); } if (flags.pedestrianCrossingAllowed != null && IsPedestrianCrossingAllowedConfigurable(segNodeConf.segmentId, true, ref node)) { SetPedestrianCrossingAllowed(segNodeConf.segmentId, true, (bool)flags.pedestrianCrossingAllowed); } return true; }); } else { Log.Warning($"JunctionRestrictionsManager.LoadData(): Could not get segment end geometry for segment {segNodeConf.segmentId} @ start node"); } } if (segNodeConf.endNodeFlags != null) { SegmentEndGeometry endNodeSegGeo = SegmentGeometry.Get(segNodeConf.segmentId)?.GetEnd(false); if (endNodeSegGeo != null) { Configuration.SegmentNodeFlags flags = segNodeConf.endNodeFlags; Services.NetService.ProcessNode(endNodeSegGeo.NodeId(), delegate (ushort nId, ref NetNode node) { if (flags.uturnAllowed != null && IsUturnAllowedConfigurable(segNodeConf.segmentId, false, ref node)) { SetUturnAllowed(segNodeConf.segmentId, false, (bool)flags.uturnAllowed); } if (flags.straightLaneChangingAllowed != null && IsLaneChangingAllowedWhenGoingStraightConfigurable(segNodeConf.segmentId, false, ref node)) { SetLaneChangingAllowedWhenGoingStraight(segNodeConf.segmentId, false, (bool)flags.straightLaneChangingAllowed); } if (flags.enterWhenBlockedAllowed != null && IsEnteringBlockedJunctionAllowedConfigurable(segNodeConf.segmentId, false, ref node)) { SetEnteringBlockedJunctionAllowed(segNodeConf.segmentId, false, (bool)flags.enterWhenBlockedAllowed); } if (flags.pedestrianCrossingAllowed != null && IsPedestrianCrossingAllowedConfigurable(segNodeConf.segmentId, false, ref node)) { SetPedestrianCrossingAllowed(segNodeConf.segmentId, false, (bool)flags.pedestrianCrossingAllowed); } if (flags.turnOnRedAllowed != null) { SetNearTurnOnRedAllowed(segNodeConf.segmentId, false, (bool)flags.turnOnRedAllowed); } if (flags.farTurnOnRedAllowed != null) { SetFarTurnOnRedAllowed(segNodeConf.segmentId, false, (bool)flags.farTurnOnRedAllowed); } return true; }); } else { Log.Warning($"JunctionRestrictionsManager.LoadData(): Could not get segment end geometry for segment {segNodeConf.segmentId} @ end node"); } } } catch (Exception e) { // ignore, as it's probably corrupt save data. it'll be culled on next save Log.Warning($"Error loading junction restrictions @ segment {segNodeConf.segmentId}: " + e.ToString()); success = false; } } return success; } public List SaveData(ref bool success) { List ret = new List(); NetManager netManager = Singleton.instance; for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; segmentId++) { try { if (!Services.NetService.IsSegmentValid((ushort)segmentId)) { continue; } Configuration.SegmentNodeFlags startNodeFlags = null; Configuration.SegmentNodeFlags endNodeFlags = null; ushort startNodeId = netManager.m_segments.m_buffer[segmentId].m_startNode; if (Services.NetService.IsNodeValid(startNodeId)) { SegmentEndFlags endFlags = SegmentFlags[segmentId].startNodeFlags; if (!endFlags.IsDefault()) { startNodeFlags = new Configuration.SegmentNodeFlags(); startNodeFlags.uturnAllowed = TernaryBoolUtil.ToOptBool(GetUturnAllowed((ushort)segmentId, true)); startNodeFlags.turnOnRedAllowed = TernaryBoolUtil.ToOptBool(GetNearTurnOnRedAllowed((ushort)segmentId, true)); startNodeFlags.farTurnOnRedAllowed = TernaryBoolUtil.ToOptBool(GetFarTurnOnRedAllowed((ushort)segmentId, true)); startNodeFlags.straightLaneChangingAllowed = TernaryBoolUtil.ToOptBool(GetLaneChangingAllowedWhenGoingStraight((ushort)segmentId, true)); startNodeFlags.enterWhenBlockedAllowed = TernaryBoolUtil.ToOptBool(GetEnteringBlockedJunctionAllowed((ushort)segmentId, true)); startNodeFlags.pedestrianCrossingAllowed = TernaryBoolUtil.ToOptBool(GetPedestrianCrossingAllowed((ushort)segmentId, true)); Log._Debug($"JunctionRestrictionsManager.SaveData: Saving start node junction restrictions for segment {segmentId}: {startNodeFlags}"); } } ushort endNodeId = netManager.m_segments.m_buffer[segmentId].m_endNode; if (Services.NetService.IsNodeValid(endNodeId)) { SegmentEndFlags endFlags = SegmentFlags[segmentId].endNodeFlags; if (!endFlags.IsDefault()) { endNodeFlags = new Configuration.SegmentNodeFlags(); endNodeFlags.uturnAllowed = TernaryBoolUtil.ToOptBool(GetUturnAllowed((ushort)segmentId, false)); endNodeFlags.turnOnRedAllowed = TernaryBoolUtil.ToOptBool(GetNearTurnOnRedAllowed((ushort)segmentId, false)); endNodeFlags.farTurnOnRedAllowed = TernaryBoolUtil.ToOptBool(GetFarTurnOnRedAllowed((ushort)segmentId, false)); endNodeFlags.straightLaneChangingAllowed = TernaryBoolUtil.ToOptBool(GetLaneChangingAllowedWhenGoingStraight((ushort)segmentId, false)); endNodeFlags.enterWhenBlockedAllowed = TernaryBoolUtil.ToOptBool(GetEnteringBlockedJunctionAllowed((ushort)segmentId, false)); endNodeFlags.pedestrianCrossingAllowed = TernaryBoolUtil.ToOptBool(GetPedestrianCrossingAllowed((ushort)segmentId, false)); Log._Debug($"JunctionRestrictionsManager.SaveData: Saving end node junction restrictions for segment {segmentId}: {endNodeFlags}"); } } if (startNodeFlags == null && endNodeFlags == null) { continue; } Configuration.SegmentNodeConf conf = new Configuration.SegmentNodeConf((ushort)segmentId); conf.startNodeFlags = startNodeFlags; conf.endNodeFlags = endNodeFlags; Log._Debug($"Saving segment-at-node flags for seg. {segmentId}"); ret.Add(conf); } catch (Exception e) { Log.Error($"Exception occurred while saving segment node flags @ {segmentId}: {e.ToString()}"); success = false; } } return ret; } } } ================================================ FILE: TLM/TLM/Manager/Impl/LaneArrowManager.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Util; using TrafficManager.State; using ColossalFramework; using TrafficManager.Geometry; using static TrafficManager.State.Flags; using TrafficManager.Traffic; using CSUtil.Commons; using TrafficManager.Geometry.Impl; namespace TrafficManager.Manager.Impl { public class LaneArrowManager : AbstractGeometryObservingManager, ICustomDataManager>, ICustomDataManager, ILaneArrowManager { public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car; public const ExtVehicleType EXT_VEHICLE_TYPES = ExtVehicleType.RoadVehicle &~ ExtVehicleType.Emergency; public static readonly LaneArrowManager Instance = new LaneArrowManager(); protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"- Not implemented -"); // TODO implement } public LaneArrows GetFinalLaneArrows(uint laneId) { return Flags.getFinalLaneArrowFlags(laneId, true); } public bool SetLaneArrows(uint laneId, LaneArrows flags, bool overrideHighwayArrows = false) { if (Flags.setLaneArrowFlags(laneId, flags, overrideHighwayArrows)) { OnLaneChange(laneId); return true; } return false; } public bool ToggleLaneArrows(uint laneId, bool startNode, LaneArrows flags, out LaneArrowChangeResult res) { if (Flags.toggleLaneArrowFlags(laneId, startNode, flags, out res)) { OnLaneChange(laneId); return true; } return false; } protected void OnLaneChange(uint laneId) { Services.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { RoutingManager.Instance.RequestRecalculation(lane.m_segment); if (OptionsManager.Instance.MayPublishSegmentChanges()) { Services.NetService.PublishSegmentChanges(lane.m_segment); } return true; }); } protected override void HandleInvalidSegment(SegmentGeometry geometry) { Flags.resetSegmentArrowFlags(geometry.SegmentId); } protected override void HandleValidSegment(SegmentGeometry geometry) { } public void ApplyFlags() { for (uint laneId = 0; laneId < NetManager.MAX_LANE_COUNT; ++laneId) { Flags.applyLaneArrowFlags(laneId); } } public override void OnBeforeSaveData() { base.OnBeforeSaveData(); ApplyFlags(); } public override void OnAfterLoadData() { base.OnAfterLoadData(); Flags.clearHighwayLaneArrows(); ApplyFlags(); } [Obsolete] public bool LoadData(string data) { bool success = true; Log.Info($"Loading lane arrow data (old method)"); #if DEBUG Log._Debug($"LaneFlags: {data}"); #endif var lanes = data.Split(','); if (lanes.Length > 1) { foreach (var split in lanes.Select(lane => lane.Split(':')).Where(split => split.Length > 1)) { try { Log._Debug($"Split Data: {split[0]} , {split[1]}"); var laneId = Convert.ToUInt32(split[0]); uint flags = Convert.ToUInt32(split[1]); if (!Services.NetService.IsLaneValid(laneId)) continue; if (flags > ushort.MaxValue) continue; uint laneArrowFlags = flags & Flags.lfr; uint origFlags = (Singleton.instance.m_lanes.m_buffer[laneId].m_flags & Flags.lfr); #if DEBUG Log._Debug("Setting flags for lane " + laneId + " to " + flags + " (" + ((Flags.LaneArrows)(laneArrowFlags)).ToString() + ")"); if ((origFlags | laneArrowFlags) == origFlags) { // only load if setting differs from default Log._Debug("Flags for lane " + laneId + " are original (" + ((NetLane.Flags)(origFlags)).ToString() + ")"); } #endif SetLaneArrows(laneId, (Flags.LaneArrows)laneArrowFlags); } catch (Exception e) { Log.Error($"Error loading Lane Split data. Length: {split.Length} value: {split}\nError: {e.ToString()}"); success = false; } } } return success; } [Obsolete] string ICustomDataManager.SaveData(ref bool success) { return null; } public bool LoadData(List data) { bool success = true; Log.Info($"Loading lane arrow data (new method)"); foreach (var laneArrowData in data) { try { if (!Services.NetService.IsLaneValid(laneArrowData.laneId)) continue; uint laneArrowFlags = laneArrowData.arrows & Flags.lfr; SetLaneArrows(laneArrowData.laneId, (Flags.LaneArrows)laneArrowFlags); } catch (Exception e) { Log.Error($"Error loading lane arrow data for lane {laneArrowData.laneId}, arrows={laneArrowData.arrows}: {e.ToString()}"); success = false; } } return success; } public List SaveData(ref bool success) { List ret = new List(); for (uint i = 0; i < Singleton.instance.m_lanes.m_buffer.Length; i++) { try { Flags.LaneArrows? laneArrows = Flags.getLaneArrowFlags(i); if (laneArrows == null) continue; uint laneArrowInt = (uint)laneArrows; Log._Debug($"Saving lane arrows for lane {i}, setting to {laneArrows.ToString()} ({laneArrowInt})"); ret.Add(new Configuration.LaneArrowData(i, laneArrowInt)); } catch (Exception e) { Log.Error($"Exception occurred while saving lane arrows @ {i}: {e.ToString()}"); success = false; } } return ret; } } } ================================================ FILE: TLM/TLM/Manager/Impl/LaneConnectionManager.cs ================================================ using ColossalFramework; using System; using System.Collections.Generic; using System.Text; using System.Threading; using TrafficManager.Geometry; using TrafficManager.Traffic; using TrafficManager.State; using TrafficManager.Util; using UnityEngine; using CSUtil.Commons; using TrafficManager.Geometry.Impl; namespace TrafficManager.Manager.Impl { public class LaneConnectionManager : AbstractGeometryObservingManager, ICustomDataManager>, ILaneConnectionManager { public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail; public const ExtVehicleType EXT_VEHICLE_TYPES = ExtVehicleType.RoadVehicle | ExtVehicleType.Tram | ExtVehicleType.RailVehicle; public static LaneConnectionManager Instance { get; private set; } = null; static LaneConnectionManager() { Instance = new LaneConnectionManager(); } protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"- Not implemented -"); // TODO implement } /// /// Checks if traffic may flow from source lane to target lane according to setup lane connections /// /// /// /// (optional) check at start node of source lane? /// public bool AreLanesConnected(uint sourceLaneId, uint targetLaneId, bool sourceStartNode) { if (! Options.laneConnectorEnabled) { return true; } if (targetLaneId == 0 || Flags.laneConnections[sourceLaneId] == null) { return false; } int nodeArrayIndex = sourceStartNode ? 0 : 1; uint[] connectedLanes = Flags.laneConnections[sourceLaneId][nodeArrayIndex]; if (connectedLanes == null) { return false; } bool ret = false; foreach (uint laneId in connectedLanes) if (laneId == targetLaneId) { ret = true; break; } return ret; } /// /// Determines if the given lane has outgoing connections /// /// /// public bool HasConnections(uint sourceLaneId, bool startNode) { if (!Options.laneConnectorEnabled) { return false; } int nodeArrayIndex = startNode ? 0 : 1; bool ret = Flags.laneConnections[sourceLaneId] != null && Flags.laneConnections[sourceLaneId][nodeArrayIndex] != null; return ret; } /// /// Determines if there exist custom lane connections at the specified node /// /// public bool HasNodeConnections(ushort nodeId) { if (!Options.laneConnectorEnabled) { return false; } bool ret = false; Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { Services.NetService.IterateSegmentLanes(segmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segId, ref NetSegment seg, byte laneIndex) { if (HasConnections(laneId, seg.m_startNode == nodeId)) { ret = true; return false; } return true; }); return !ret; }); return ret; } public bool HasUturnConnections(ushort segmentId, bool startNode) { if (!Options.laneConnectorEnabled) { return false; } NetManager netManager = Singleton.instance; int nodeArrayIndex = startNode ? 0 : 1; uint sourceLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; while (sourceLaneId != 0) { uint[] targetLaneIds = GetLaneConnections(sourceLaneId, startNode); if (targetLaneIds != null) { foreach (uint targetLaneId in targetLaneIds) { if (netManager.m_lanes.m_buffer[targetLaneId].m_segment == segmentId) { return true; } } } sourceLaneId = netManager.m_lanes.m_buffer[sourceLaneId].m_nextLane; } return false; } internal int CountConnections(uint sourceLaneId, bool startNode) { if (!Options.laneConnectorEnabled) { return 0; } if (Flags.laneConnections[sourceLaneId] == null) return 0; int nodeArrayIndex = startNode ? 0 : 1; if (Flags.laneConnections[sourceLaneId][nodeArrayIndex] == null) return 0; return Flags.laneConnections[sourceLaneId][nodeArrayIndex].Length; } /// /// Gets all lane connections for the given lane /// /// /// internal uint[] GetLaneConnections(uint laneId, bool startNode) { if (!Options.laneConnectorEnabled) { return null; } if (Flags.laneConnections[laneId] == null) return null; int nodeArrayIndex = startNode ? 0 : 1; return Flags.laneConnections[laneId][nodeArrayIndex]; } /// /// Removes a lane connection between two lanes /// /// /// /// /// internal bool RemoveLaneConnection(uint laneId1, uint laneId2, bool startNode1) { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug($"LaneConnectionManager.RemoveLaneConnection({laneId1}, {laneId2}, {startNode1}) called."); #endif bool ret = Flags.RemoveLaneConnection(laneId1, laneId2, startNode1); #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RemoveLaneConnection({laneId1}, {laneId2}, {startNode1}): ret={ret}"); #endif if (ret) { NetManager netManager = Singleton.instance; ushort segmentId1 = netManager.m_lanes.m_buffer[laneId1].m_segment; ushort segmentId2 = netManager.m_lanes.m_buffer[laneId2].m_segment; ushort commonNodeId; bool startNode2; GetCommonNodeId(laneId1, laneId2, startNode1, out commonNodeId, out startNode2); RecalculateLaneArrows(laneId1, commonNodeId, startNode1); RecalculateLaneArrows(laneId2, commonNodeId, startNode2); RoutingManager.Instance.RequestRecalculation(segmentId1, false); RoutingManager.Instance.RequestRecalculation(segmentId2, false); if (OptionsManager.Instance.MayPublishSegmentChanges()) { Services.NetService.PublishSegmentChanges(segmentId1); Services.NetService.PublishSegmentChanges(segmentId2); } } return ret; } /// /// Removes all lane connections at the specified node /// /// internal void RemoveLaneConnectionsFromNode(ushort nodeId) { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug($"LaneConnectionManager.RemoveLaneConnectionsFromNode({nodeId}) called."); #endif Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { RemoveLaneConnectionsFromSegment(segmentId, segment.m_startNode == nodeId); return true; }); } /// /// Removes all lane connections at the specified segment end /// /// /// internal void RemoveLaneConnectionsFromSegment(ushort segmentId, bool startNode, bool recalcAndPublish=true) { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug($"LaneConnectionManager.RemoveLaneConnectionsFromSegment({segmentId}, {startNode}) called."); #endif Services.NetService.IterateSegmentLanes(segmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segId, ref NetSegment segment, byte laneIndex) { #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RemoveLaneConnectionsFromSegment: Removing lane connections from segment {segmentId}, lane {laneId}."); #endif RemoveLaneConnections(laneId, startNode, false); return true; }); if (recalcAndPublish) { RoutingManager.Instance.RequestRecalculation(segmentId); if (OptionsManager.Instance.MayPublishSegmentChanges()) { Services.NetService.PublishSegmentChanges(segmentId); } } } /// /// Removes all lane connections from the specified lane /// /// /// internal void RemoveLaneConnections(uint laneId, bool startNode, bool recalcAndPublish=true) { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug($"LaneConnectionManager.RemoveLaneConnections({laneId}, {startNode}) called."); #endif if (Flags.laneConnections[laneId] == null) return; int nodeArrayIndex = startNode ? 0 : 1; if (Flags.laneConnections[laneId][nodeArrayIndex] == null) return; NetManager netManager = Singleton.instance; /*for (int i = 0; i < Flags.laneConnections[laneId][nodeArrayIndex].Length; ++i) { uint otherLaneId = Flags.laneConnections[laneId][nodeArrayIndex][i]; if (Flags.laneConnections[otherLaneId] != null) { if ((Flags.laneConnections[otherLaneId][0] != null && Flags.laneConnections[otherLaneId][0].Length == 1 && Flags.laneConnections[otherLaneId][0][0] == laneId && Flags.laneConnections[otherLaneId][1] == null) || Flags.laneConnections[otherLaneId][1] != null && Flags.laneConnections[otherLaneId][1].Length == 1 && Flags.laneConnections[otherLaneId][1][0] == laneId && Flags.laneConnections[otherLaneId][0] == null) { ushort otherSegmentId = netManager.m_lanes.m_buffer[otherLaneId].m_segment; UnsubscribeFromSegmentGeometry(otherSegmentId); } } }*/ Flags.RemoveLaneConnections(laneId, startNode); Services.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { if (recalcAndPublish) { RoutingManager.Instance.RequestRecalculation(lane.m_segment); if (OptionsManager.Instance.MayPublishSegmentChanges()) { Services.NetService.PublishSegmentChanges(lane.m_segment); } } return true; }); } /// /// Adds a lane connection between two lanes /// /// /// /// /// internal bool AddLaneConnection(uint sourceLaneId, uint targetLaneId, bool sourceStartNode) { if (sourceLaneId == targetLaneId) { return false; } bool ret = Flags.AddLaneConnection(sourceLaneId, targetLaneId, sourceStartNode); #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug($"LaneConnectionManager.AddLaneConnection({sourceLaneId}, {targetLaneId}, {sourceStartNode}): ret={ret}"); #endif if (ret) { ushort commonNodeId; bool targetStartNode; GetCommonNodeId(sourceLaneId, targetLaneId, sourceStartNode, out commonNodeId, out targetStartNode); RecalculateLaneArrows(sourceLaneId, commonNodeId, sourceStartNode); RecalculateLaneArrows(targetLaneId, commonNodeId, targetStartNode); NetManager netManager = Singleton.instance; ushort sourceSegmentId = netManager.m_lanes.m_buffer[sourceLaneId].m_segment; ushort targetSegmentId = netManager.m_lanes.m_buffer[targetLaneId].m_segment; if (sourceSegmentId == targetSegmentId) { JunctionRestrictionsManager.Instance.SetUturnAllowed(sourceSegmentId, sourceStartNode, true); } RoutingManager.Instance.RequestRecalculation(sourceSegmentId, false); RoutingManager.Instance.RequestRecalculation(targetSegmentId, false); if (OptionsManager.Instance.MayPublishSegmentChanges()) { Services.NetService.PublishSegmentChanges(sourceSegmentId); Services.NetService.PublishSegmentChanges(targetSegmentId); } } return ret; } protected override void HandleInvalidSegment(SegmentGeometry geometry) { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug($"LaneConnectionManager.HandleInvalidSegment({geometry.SegmentId}): Segment has become invalid. Removing lane connections."); #endif RemoveLaneConnectionsFromSegment(geometry.SegmentId, false, false); RemoveLaneConnectionsFromSegment(geometry.SegmentId, true); } protected override void HandleValidSegment(SegmentGeometry geometry) { } /// /// Given two lane ids and node of the first lane, determines the node id to which both lanes are connected to /// /// /// /// internal void GetCommonNodeId(uint laneId1, uint laneId2, bool startNode1, out ushort commonNodeId, out bool startNode2) { NetManager netManager = Singleton.instance; ushort segmentId1 = netManager.m_lanes.m_buffer[laneId1].m_segment; ushort segmentId2 = netManager.m_lanes.m_buffer[laneId2].m_segment; ushort nodeId2Start = netManager.m_segments.m_buffer[segmentId2].m_startNode; ushort nodeId2End = netManager.m_segments.m_buffer[segmentId2].m_endNode; ushort nodeId1 = startNode1 ? netManager.m_segments.m_buffer[segmentId1].m_startNode : netManager.m_segments.m_buffer[segmentId1].m_endNode; startNode2 = (nodeId1 == nodeId2Start); if (!startNode2 && nodeId1 != nodeId2End) commonNodeId = 0; else commonNodeId = nodeId1; } internal bool GetLaneEndPoint(ushort segmentId, bool startNode, byte laneIndex, uint? laneId, NetInfo.Lane laneInfo, out bool outgoing, out bool incoming, out Vector3? pos) { NetManager netManager = Singleton.instance; pos = null; outgoing = false; incoming = false; if ((netManager.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) return false; if (laneId == null) { laneId = FindLaneId(segmentId, laneIndex); if (laneId == null) return false; } if ((netManager.m_lanes.m_buffer[(uint)laneId].m_flags & ((ushort)NetLane.Flags.Created | (ushort)NetLane.Flags.Deleted)) != (ushort)NetLane.Flags.Created) return false; if (laneInfo == null) { if (laneIndex < netManager.m_segments.m_buffer[segmentId].Info.m_lanes.Length) laneInfo = netManager.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex]; else return false; } NetInfo.Direction laneDir = ((NetManager.instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? laneInfo.m_finalDirection : NetInfo.InvertDirection(laneInfo.m_finalDirection); if (startNode) { if ((laneDir & NetInfo.Direction.Backward) != NetInfo.Direction.None) outgoing = true; if ((laneDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) incoming = true; pos = NetManager.instance.m_lanes.m_buffer[(uint)laneId].m_bezier.a; } else { if ((laneDir & NetInfo.Direction.Forward) != NetInfo.Direction.None) outgoing = true; if ((laneDir & NetInfo.Direction.Backward) != NetInfo.Direction.None) incoming = true; pos = NetManager.instance.m_lanes.m_buffer[(uint)laneId].m_bezier.d; } return true; } private uint? FindLaneId(ushort segmentId, byte laneIndex) { NetInfo.Lane[] lanes = NetManager.instance.m_segments.m_buffer[segmentId].Info.m_lanes; uint laneId = NetManager.instance.m_segments.m_buffer[segmentId].m_lanes; for (byte i = 0; i < lanes.Length && laneId != 0; i++) { if (i == laneIndex) return laneId; laneId = NetManager.instance.m_lanes.m_buffer[laneId].m_nextLane; } return null; } /// /// Recalculates lane arrows based on present lane connections. /// /// /// private void RecalculateLaneArrows(uint laneId, ushort nodeId, bool startNode) { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}) called"); #endif if (!Options.laneConnectorEnabled) { return; } if (!Flags.mayHaveLaneArrows(laneId, startNode)) { #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): lane {laneId}, startNode? {startNode} must not have lane arrows"); #endif return; } if (!HasConnections(laneId, startNode)) { #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): lane {laneId} does not have outgoing connections"); #endif return; } if (nodeId == 0) { #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid node"); #endif return; } Flags.LaneArrows arrows = Flags.LaneArrows.None; NetManager netManager = Singleton.instance; ushort segmentId = netManager.m_lanes.m_buffer[laneId].m_segment; if (segmentId == 0) { #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid segment"); #endif return; } #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): startNode? {startNode}"); #endif NodeGeometry nodeGeo = NodeGeometry.Get(nodeId); if (!nodeGeo.IsValid()) { #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid node geometry"); #endif return; } SegmentGeometry segmentGeo = SegmentGeometry.Get(segmentId); if (segmentGeo == null) { #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid segment geometry"); #endif return; } ushort[] connectedSegmentIds = segmentGeo.GetConnectedSegments(startNode); if (connectedSegmentIds == null) { #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): connectedSegmentIds is null"); #endif return; } ushort[] allSegmentIds = new ushort[connectedSegmentIds.Length + 1]; allSegmentIds[0] = segmentId; Array.Copy(connectedSegmentIds, 0, allSegmentIds, 1, connectedSegmentIds.Length); foreach (ushort otherSegmentId in allSegmentIds) { if (otherSegmentId == 0) continue; ArrowDirection dir = segmentGeo.GetDirection(otherSegmentId, startNode); #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}. dir={dir}"); #endif // check if arrow has already been set for this direction switch (dir) { case ArrowDirection.Turn: if (Constants.ServiceFactory.SimulationService.LeftHandDrive) { if ((arrows & Flags.LaneArrows.Right) != Flags.LaneArrows.None) continue; } else { if ((arrows & Flags.LaneArrows.Left) != Flags.LaneArrows.None) continue; } break; case ArrowDirection.Forward: if ((arrows & Flags.LaneArrows.Forward) != Flags.LaneArrows.None) continue; break; case ArrowDirection.Left: if ((arrows & Flags.LaneArrows.Left) != Flags.LaneArrows.None) continue; break; case ArrowDirection.Right: if ((arrows & Flags.LaneArrows.Right) != Flags.LaneArrows.None) continue; break; default: continue; } #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: need to determine arrows"); #endif bool addArrow = false; uint curLaneId = netManager.m_segments.m_buffer[otherSegmentId].m_lanes; while (curLaneId != 0) { #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: checking lane {curLaneId}"); #endif if (AreLanesConnected(laneId, curLaneId, startNode)) { #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: checking lane {curLaneId}: lanes are connected"); #endif addArrow = true; break; } curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; } #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: finished processing lanes. addArrow={addArrow} arrows (before)={arrows}"); #endif if (addArrow) { switch (dir) { case ArrowDirection.Turn: if (Constants.ServiceFactory.SimulationService.LeftHandDrive) { arrows |= Flags.LaneArrows.Right; } else { arrows |= Flags.LaneArrows.Left; } break; case ArrowDirection.Forward: arrows |= Flags.LaneArrows.Forward; break; case ArrowDirection.Left: arrows |= Flags.LaneArrows.Left; break; case ArrowDirection.Right: arrows |= Flags.LaneArrows.Right; break; default: continue; } #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: arrows={arrows}"); #endif } } #if DEBUGCONN if (debug) Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): setting lane arrows to {arrows}"); #endif LaneArrowManager.Instance.SetLaneArrows(laneId, arrows, true); } public bool LoadData(List data) { bool success = true; Log.Info($"Loading {data.Count} lane connections"); foreach (Configuration.LaneConnection conn in data) { try { if (!Services.NetService.IsLaneValid(conn.lowerLaneId)) continue; if (!Services.NetService.IsLaneValid(conn.higherLaneId)) continue; if (conn.lowerLaneId == conn.higherLaneId) { continue; } Log._Debug($"Loading lane connection: lane {conn.lowerLaneId} -> {conn.higherLaneId}"); AddLaneConnection(conn.lowerLaneId, conn.higherLaneId, conn.lowerStartNode); } catch (Exception e) { // ignore, as it's probably corrupt save data. it'll be culled on next save Log.Error("Error loading data from lane connection: " + e.ToString()); success = false; } } return success; } public List SaveData(ref bool success) { List ret = new List(); for (uint i = 0; i < Singleton.instance.m_lanes.m_buffer.Length; i++) { try { if (Flags.laneConnections[i] == null) continue; for (int nodeArrayIndex = 0; nodeArrayIndex <= 1; ++nodeArrayIndex) { uint[] connectedLaneIds = Flags.laneConnections[i][nodeArrayIndex]; bool startNode = nodeArrayIndex == 0; if (connectedLaneIds != null) { foreach (uint otherHigherLaneId in connectedLaneIds) { if (otherHigherLaneId <= i) continue; if (!Services.NetService.IsLaneValid(otherHigherLaneId)) continue; Log._Debug($"Saving lane connection: lane {i} -> {otherHigherLaneId}"); ret.Add(new Configuration.LaneConnection(i, (uint)otherHigherLaneId, startNode)); } } } } catch (Exception e) { Log.Error($"Exception occurred while saving lane data @ {i}: {e.ToString()}"); success = false; } } return ret; } } } ================================================ FILE: TLM/TLM/Manager/Impl/ManagerFactory.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Manager; namespace TrafficManager.Manager.Impl { public class ManagerFactory : IManagerFactory { public static IManagerFactory Instance = new ManagerFactory(); public IAdvancedParkingManager AdvancedParkingManager { get { return Impl.AdvancedParkingManager.Instance; } } public ICustomSegmentLightsManager CustomSegmentLightsManager { get { return Impl.CustomSegmentLightsManager.Instance; } } public IExtBuildingManager ExtBuildingManager { get { return Impl.ExtBuildingManager.Instance; } } public IExtCitizenInstanceManager ExtCitizenInstanceManager { get { return Impl.ExtCitizenInstanceManager.Instance; } } public IExtCitizenManager ExtCitizenManager { get { return Impl.ExtCitizenManager.Instance; } } public IJunctionRestrictionsManager JunctionRestrictionsManager { get { return Impl.JunctionRestrictionsManager.Instance; } } public ILaneArrowManager LaneArrowManager { get { return Impl.LaneArrowManager.Instance; } } public ILaneConnectionManager LaneConnectionManager { get { return Impl.LaneConnectionManager.Instance; } } public IGeometryManager GeometryManager { get { return Impl.GeometryManager.Instance; } } public IOptionsManager OptionsManager { get { return Impl.OptionsManager.Instance; } } public IParkingRestrictionsManager ParkingRestrictionsManager { get { return Impl.ParkingRestrictionsManager.Instance; } } public IRoutingManager RoutingManager { get { return Impl.RoutingManager.Instance; } } public ISegmentEndManager SegmentEndManager { get { return Impl.SegmentEndManager.Instance; } } public ISpeedLimitManager SpeedLimitManager { get { return Impl.SpeedLimitManager.Instance; } } public ITrafficLightManager TrafficLightManager { get { return Impl.TrafficLightManager.Instance; } } public ITrafficLightSimulationManager TrafficLightSimulationManager { get { return Impl.TrafficLightSimulationManager.Instance; } } public ITrafficMeasurementManager TrafficMeasurementManager { get { return Impl.TrafficMeasurementManager.Instance; } } public ITrafficPriorityManager TrafficPriorityManager { get { return Impl.TrafficPriorityManager.Instance; } } public ITurnOnRedManager TurnOnRedManager { get { return Impl.TurnOnRedManager.Instance; } } public IUtilityManager UtilityManager { get { return Impl.UtilityManager.Instance; } } public IVehicleBehaviorManager VehicleBehaviorManager { get { return Impl.VehicleBehaviorManager.Instance; } } public IVehicleRestrictionsManager VehicleRestrictionsManager { get { return Impl.VehicleRestrictionsManager.Instance; } } public IVehicleStateManager VehicleStateManager { get { return Impl.VehicleStateManager.Instance; } } } } ================================================ FILE: TLM/TLM/Manager/Impl/OptionsManager.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.State; namespace TrafficManager.Manager.Impl { public class OptionsManager : AbstractCustomManager, IOptionsManager { // TODO I contain ugly code public static OptionsManager Instance = new OptionsManager(); protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"- Not implemented -"); // TODO implement } public bool MayPublishSegmentChanges() { return Options.instantEffects && !SerializableDataExtension.StateLoading; } public bool LoadData(byte[] data) { if (data.Length >= 1) { Options.setSimAccuracy(data[0]); } if (data.Length >= 2) { //Options.setLaneChangingRandomization(options[1]); } if (data.Length >= 3) { Options.setRecklessDrivers(data[2]); } if (data.Length >= 4) { Options.setRelaxedBusses(data[3] == (byte)1); } if (data.Length >= 5) { Options.setNodesOverlay(data[4] == (byte)1); } if (data.Length >= 6) { Options.setMayEnterBlockedJunctions(data[5] == (byte)1); } if (data.Length >= 7) { Options.setAdvancedAI(data[6] == (byte)1); } if (data.Length >= 8) { Options.setHighwayRules(data[7] == (byte)1); } if (data.Length >= 9) { Options.setPrioritySignsOverlay(data[8] == (byte)1); } if (data.Length >= 10) { Options.setTimedLightsOverlay(data[9] == (byte)1); } if (data.Length >= 11) { Options.setSpeedLimitsOverlay(data[10] == (byte)1); } if (data.Length >= 12) { Options.setVehicleRestrictionsOverlay(data[11] == (byte)1); } if (data.Length >= 13) { Options.setStrongerRoadConditionEffects(data[12] == (byte)1); } if (data.Length >= 14) { Options.setAllowUTurns(data[13] == (byte)1); } if (data.Length >= 15) { Options.setAllowLaneChangesWhileGoingStraight(data[14] == (byte)1); } if (data.Length >= 16) { Options.setDisableDespawning(data[15] != (byte)1); } if (data.Length >= 17) { //Options.setDynamicPathRecalculation(data[16] == (byte)1); } if (data.Length >= 18) { Options.setConnectedLanesOverlay(data[17] == (byte)1); } if (data.Length >= 19) { Options.setPrioritySignsEnabled(data[18] == (byte)1); } if (data.Length >= 20) { Options.setTimedLightsEnabled(data[19] == (byte)1); } if (data.Length >= 21) { Options.setCustomSpeedLimitsEnabled(data[20] == (byte)1); } if (data.Length >= 22) { Options.setVehicleRestrictionsEnabled(data[21] == (byte)1); } if (data.Length >= 23) { Options.setLaneConnectorEnabled(data[22] == (byte)1); } if (data.Length >= 24) { Options.setJunctionRestrictionsOverlay(data[23] == (byte)1); } if (data.Length >= 25) { Options.setJunctionRestrictionsEnabled(data[24] == (byte)1); } if (data.Length >= 26) { Options.setProhibitPocketCars(data[25] == (byte)1); } if (data.Length >= 27) { Options.setPreferOuterLane(data[26] == (byte)1); } if (data.Length >= 28) { Options.setRealisticSpeeds(data[27] == (byte)1); } if (data.Length >= 29) { Options.setEvacBussesMayIgnoreRules(data[28] == (byte)1); } if (data.Length >= 30) { Options.setInstantEffects(data[29] == (byte)1); } if (data.Length >= 31) { Options.setParkingRestrictionsEnabled(data[30] == (byte)1); } if (data.Length >= 32) { Options.setParkingRestrictionsOverlay(data[31] == (byte)1); } if (data.Length >= 33) { Options.setBanRegularTrafficOnBusLanes(data[32] == (byte)1); } if (data.Length >= 34) { Options.setShowPathFindStats(data[33] == (byte)1); } if (data.Length >= 35) { Options.setAltLaneSelectionRatio(data[34]); } if (data.Length >= 36) { try { Options.setVehicleRestrictionsAggression((VehicleRestrictionsAggression)data[35]); } catch (Exception e) { Log.Warning($"Skipping invalid value {data[35]} for vehicle restrictions aggression"); } } if (data.Length >= 37) { Options.setTrafficLightPriorityRules(data[36] == (byte)1); } if (data.Length >= 38) { Options.setRealisticPublicTransport(data[37] == (byte)1); } if (data.Length >= 39) { Options.setTurnOnRedEnabled(data[38] == (byte)1); } if (data.Length >= 40) { Options.setAllowNearTurnOnRed(data[39] == (byte)1); } if (data.Length >= 41) { Options.setAllowFarTurnOnRed(data[40] == (byte)1); } return true; } public byte[] SaveData(ref bool success) { return new byte[] { (byte)Options.simAccuracy, (byte)0,//Options.laneChangingRandomization, (byte)Options.recklessDrivers, (byte)(Options.relaxedBusses ? 1 : 0), (byte) (Options.nodesOverlay ? 1 : 0), (byte)(Options.allowEnterBlockedJunctions ? 1 : 0), (byte)(Options.advancedAI ? 1 : 0), (byte)(Options.highwayRules ? 1 : 0), (byte)(Options.prioritySignsOverlay ? 1 : 0), (byte)(Options.timedLightsOverlay ? 1 : 0), (byte)(Options.speedLimitsOverlay ? 1 : 0), (byte)(Options.vehicleRestrictionsOverlay ? 1 : 0), (byte)(Options.strongerRoadConditionEffects ? 1 : 0), (byte)(Options.allowUTurns ? 1 : 0), (byte)(Options.allowLaneChangesWhileGoingStraight ? 1 : 0), (byte)(Options.disableDespawning ? 0 : 1), (byte)0,//Options.IsDynamicPathRecalculationActive() (byte)(Options.connectedLanesOverlay ? 1 : 0), (byte)(Options.prioritySignsEnabled ? 1 : 0), (byte)(Options.timedLightsEnabled ? 1 : 0), (byte)(Options.customSpeedLimitsEnabled ? 1 : 0), (byte)(Options.vehicleRestrictionsEnabled ? 1 : 0), (byte)(Options.laneConnectorEnabled ? 1 : 0), (byte)(Options.junctionRestrictionsOverlay ? 1 : 0), (byte)(Options.junctionRestrictionsEnabled ? 1 : 0), (byte)(Options.prohibitPocketCars ? 1 : 0), (byte)(Options.preferOuterLane ? 1 : 0), (byte)(Options.realisticSpeeds ? 1 : 0), (byte)(Options.evacBussesMayIgnoreRules ? 1 : 0), (byte)(Options.instantEffects ? 1 : 0), (byte)(Options.parkingRestrictionsEnabled ? 1 : 0), (byte)(Options.parkingRestrictionsOverlay ? 1 : 0), (byte)(Options.banRegularTrafficOnBusLanes ? 1 : 0), (byte)(Options.showPathFindStats ? 1 : 0), (byte)Options.altLaneSelectionRatio, (byte)Options.vehicleRestrictionsAggression, (byte)(Options.trafficLightPriorityRules ? 1 : 0), (byte)(Options.realisticPublicTransport ? 1 : 0), (byte)(Options.turnOnRedEnabled ? 1 : 0), (byte)(Options.allowNearTurnOnRed ? 1 : 0), (byte)(Options.allowFarTurnOnRed ? 1 : 0) }; } } } ================================================ FILE: TLM/TLM/Manager/Impl/ParkingRestrictionsManager.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Geometry; using TrafficManager.Geometry.Impl; namespace TrafficManager.Manager.Impl { public class ParkingRestrictionsManager : AbstractGeometryObservingManager, ICustomDataManager>, IParkingRestrictionsManager { public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Parking; public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car; public static readonly ParkingRestrictionsManager Instance = new ParkingRestrictionsManager(); private bool[][] parkingAllowed; private ParkingRestrictionsManager() { } public bool MayHaveParkingRestriction(ushort segmentId) { bool ret = false; Services.NetService.ProcessSegment(segmentId, delegate (ushort segId, ref NetSegment segment) { if ((segment.m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) { return true; } ItemClass connectionClass = segment.Info.GetConnectionClass(); ret = connectionClass.m_service == ItemClass.Service.Road && segment.Info.m_hasParkingSpaces; return true; }); return ret; } public bool IsParkingAllowed(ushort segmentId, NetInfo.Direction finalDir) { return parkingAllowed[segmentId][GetDirIndex(finalDir)]; } public bool ToggleParkingAllowed(ushort segmentId, NetInfo.Direction finalDir) { return SetParkingAllowed(segmentId, finalDir, !IsParkingAllowed(segmentId, finalDir)); } public bool SetParkingAllowed(ushort segmentId, NetInfo.Direction finalDir, bool flag) { if (!MayHaveParkingRestriction(segmentId)) { return false; } int dirIndex = GetDirIndex(finalDir); parkingAllowed[segmentId][dirIndex] = flag; if (!flag || !parkingAllowed[segmentId][1 - dirIndex]) { Services.SimulationService.AddAction(() => { // force relocation of illegaly parked vehicles Services.NetService.ProcessSegment(segmentId, delegate (ushort segId, ref NetSegment segment) { segment.UpdateSegment(segmentId); return true; }); }); } return true; } protected override void HandleInvalidSegment(SegmentGeometry geometry) { parkingAllowed[geometry.SegmentId][0] = true; parkingAllowed[geometry.SegmentId][1] = true; } protected override void HandleValidSegment(SegmentGeometry geometry) { if (! MayHaveParkingRestriction(geometry.SegmentId)) { parkingAllowed[geometry.SegmentId][0] = true; parkingAllowed[geometry.SegmentId][1] = true; } } protected int GetDirIndex(NetInfo.Direction dir) { return dir == NetInfo.Direction.Backward ? 1 : 0; } public override void OnBeforeLoadData() { base.OnBeforeLoadData(); parkingAllowed = new bool[NetManager.MAX_SEGMENT_COUNT][]; for (uint segmentId = 0; segmentId < parkingAllowed.Length; ++segmentId) { parkingAllowed[segmentId] = new bool[2]; for (int i = 0; i < 2; ++i) { parkingAllowed[segmentId][i] = true; } } } public bool LoadData(List data) { bool success = true; Log.Info($"Loading parking restrictions data. {data.Count} elements"); foreach (Configuration.ParkingRestriction restr in data) { try { SetParkingAllowed(restr.segmentId, NetInfo.Direction.Forward, restr.forwardParkingAllowed); SetParkingAllowed(restr.segmentId, NetInfo.Direction.Backward, restr.backwardParkingAllowed); } catch (Exception e) { // ignore, as it's probably corrupt save data. it'll be culled on next save Log.Warning("Error loading data from parking restrictions: " + e.ToString()); success = false; } } return success; } public List SaveData(ref bool success) { List ret = new List(); for (uint segmentId = 0; segmentId < parkingAllowed.Length; ++segmentId) { try { if (parkingAllowed[segmentId][0] && parkingAllowed[segmentId][1]) { continue; } Configuration.ParkingRestriction restr = new Configuration.ParkingRestriction((ushort)segmentId); restr.forwardParkingAllowed = parkingAllowed[segmentId][0]; restr.backwardParkingAllowed = parkingAllowed[segmentId][1]; ret.Add(restr); } catch (Exception ex) { Log.Error($"Exception occurred while saving parking restrictions @ {segmentId}: {ex.ToString()}"); success = false; } } return ret; } } } ================================================ FILE: TLM/TLM/Manager/Impl/RoutingManager.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using ColossalFramework.UI; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using TrafficManager.Geometry; using TrafficManager.Geometry.Impl; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.UI; using TrafficManager.Util; using static TrafficManager.State.Flags; namespace TrafficManager.Manager.Impl { public class RoutingManager : AbstractGeometryObservingManager, IRoutingManager { public static readonly RoutingManager Instance = new RoutingManager(); public const NetInfo.LaneType ROUTED_LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; public const VehicleInfo.VehicleType ROUTED_VEHICLE_TYPES = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail; public const VehicleInfo.VehicleType ARROW_VEHICLE_TYPES = VehicleInfo.VehicleType.Car; private const byte MAX_NUM_TRANSITIONS = 64; /// /// Structs for path-finding that contain required segment-related routing data /// public SegmentRoutingData[] segmentRoutings = new SegmentRoutingData[NetManager.MAX_SEGMENT_COUNT]; /// /// Structs for path-finding that contain required lane-end-related backward routing data. /// Index: /// [0 .. NetManager.MAX_LANE_COUNT-1]: lane ends at start node /// [NetManager.MAX_LANE_COUNT .. 2*NetManger.MAX_LANE_COUNT-1]: lane ends at end node /// public LaneEndRoutingData[] laneEndBackwardRoutings = new LaneEndRoutingData[(uint)NetManager.MAX_LANE_COUNT * 2u]; /// /// Structs for path-finding that contain required lane-end-related forward routing data. /// Index: /// [0 .. NetManager.MAX_LANE_COUNT-1]: lane ends at start node /// [NetManager.MAX_LANE_COUNT .. 2*NetManger.MAX_LANE_COUNT-1]: lane ends at end node /// public LaneEndRoutingData[] laneEndForwardRoutings = new LaneEndRoutingData[(uint)NetManager.MAX_LANE_COUNT * 2u]; protected bool segmentsUpdated = false; protected ulong[] updatedSegmentBuckets = new ulong[576]; protected object updateLock = new object(); protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); String buf = $"Segment routings:\n"; for (int i = 0; i < segmentRoutings.Length; ++i) { if (!Services.NetService.IsSegmentValid((ushort)i)) { continue; } buf += $"Segment {i}: {segmentRoutings[i]}\n"; } buf += $"\nLane end backward routings:\n"; for (uint laneId = 0; laneId < NetManager.MAX_LANE_COUNT; ++laneId) { if (!Services.NetService.IsLaneValid(laneId)) { continue; } buf += $"Lane {laneId} @ start: {laneEndBackwardRoutings[GetLaneEndRoutingIndex(laneId, true)]}\n"; buf += $"Lane {laneId} @ end: {laneEndBackwardRoutings[GetLaneEndRoutingIndex(laneId, false)]}\n"; } buf += $"\nLane end forward routings:\n"; for (uint laneId = 0; laneId < NetManager.MAX_LANE_COUNT; ++laneId) { if (!Services.NetService.IsLaneValid(laneId)) { continue; } buf += $"Lane {laneId} @ start: {laneEndForwardRoutings[GetLaneEndRoutingIndex(laneId, true)]}\n"; buf += $"Lane {laneId} @ end: {laneEndForwardRoutings[GetLaneEndRoutingIndex(laneId, false)]}\n"; } Log._Debug(buf); } private RoutingManager() { } public void SimulationStep() { if (!segmentsUpdated || Singleton.instance.m_segmentsUpdated || Singleton.instance.m_nodesUpdated) { // TODO maybe refactor NetManager use (however this could influence performance) return; } try { Monitor.Enter(updateLock); segmentsUpdated = false; int len = updatedSegmentBuckets.Length; for (int i = 0; i < len; i++) { ulong segMask = updatedSegmentBuckets[i]; if (segMask != 0uL) { for (int m = 0; m < 64; m++) { if ((segMask & 1uL << m) != 0uL) { ushort segmentId = (ushort)(i << 6 | m); RecalculateSegment(segmentId); } } updatedSegmentBuckets[i] = 0; } } } finally { Monitor.Exit(updateLock); } } public void RequestFullRecalculation() { try { Monitor.Enter(updateLock); for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { updatedSegmentBuckets[segmentId >> 6] |= 1uL << (int)(segmentId & 63); } Flags.clearHighwayLaneArrows(); segmentsUpdated = true; if (Services.SimulationService.SimulationPaused || Services.SimulationService.ForcedSimulationPaused) { SimulationStep(); } } finally { Monitor.Exit(updateLock); } } public void RequestRecalculation(ushort segmentId, bool propagate = true) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[1] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || GlobalConfig.Instance.Debug.SegmentId == segmentId); if (debug) { Log._Debug($"RoutingManager.RequestRecalculation({segmentId}, {propagate}) called."); } #endif try { Monitor.Enter(updateLock); updatedSegmentBuckets[segmentId >> 6] |= 1uL << (int)(segmentId & 63); ResetIncomingHighwayLaneArrows(segmentId); segmentsUpdated = true; } finally { Monitor.Exit(updateLock); } if (propagate) { SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { return; } foreach (ushort otherSegmentId in segGeo.GetConnectedSegments(true)) { if (otherSegmentId == 0) { continue; } RequestRecalculation(otherSegmentId, false); } foreach (ushort otherSegmentId in segGeo.GetConnectedSegments(false)) { if (otherSegmentId == 0) { continue; } RequestRecalculation(otherSegmentId, false); } } } protected void RecalculateAll() { #if DEBUGROUTING bool debug = GlobalConfig.Instance.Debug.Switches[1]; Log._Debug($"RoutingManager.RecalculateAll: called"); #endif Flags.clearHighwayLaneArrows(); for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { try { RecalculateSegment((ushort)segmentId); } catch (Exception e) { Log.Error($"An error occurred while calculating routes for segment {segmentId}: {e}"); } } } protected void RecalculateSegment(ushort segmentId) { #if DEBUGROUTING bool debug = GlobalConfig.Instance.Debug.Switches[1] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || GlobalConfig.Instance.Debug.SegmentId == segmentId); if (debug) Log._Debug($"RoutingManager.RecalculateSegment({segmentId}) called."); #endif if (!Services.NetService.IsSegmentValid(segmentId)) { #if DEBUGROUTING if (debug) Log._Debug($"RoutingManager.RecalculateSegment({segmentId}): Segment is invalid. Skipping recalculation"); #endif return; } RecalculateSegmentRoutingData(segmentId); Services.NetService.IterateSegmentLanes(segmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segId, ref NetSegment segment, byte laneIndex) { RecalculateLaneEndRoutingData(segmentId, laneIndex, laneId, true); RecalculateLaneEndRoutingData(segmentId, laneIndex, laneId, false); return true; }); } protected void ResetIncomingHighwayLaneArrows(ushort segmentId) { ushort[] nodeIds = new ushort[2]; Services.NetService.ProcessSegment(segmentId, delegate (ushort segId, ref NetSegment segment) { nodeIds[0] = segment.m_startNode; nodeIds[1] = segment.m_endNode; return true; }); #if DEBUGROUTING bool debug = GlobalConfig.Instance.Debug.Switches[1] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || GlobalConfig.Instance.Debug.SegmentId == segmentId); if (debug) Log._Debug($"RoutingManager.ResetRoutingData: Identify nodes connected to {segmentId}: nodeIds={nodeIds.ArrayToString()}"); #endif // reset highway lane arrows on all incoming lanes foreach (ushort nodeId in nodeIds) { if (nodeId == 0) { continue; } Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segId, ref NetSegment segment) { if (segId == segmentId) { return true; } Services.NetService.IterateSegmentLanes(segId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort sId, ref NetSegment seg, byte laneIndex) { if (IsIncomingLane(segId, seg.m_startNode == nodeId, laneIndex)) { Flags.removeHighwayLaneArrowFlags(laneId); } return true; }); return true; }); } } protected void ResetRoutingData(ushort segmentId) { #if DEBUGROUTING bool debugBasic = GlobalConfig.Instance.Debug.Switches[1] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || GlobalConfig.Instance.Debug.SegmentId == segmentId); bool debugFine = GlobalConfig.Instance.Debug.Switches[8] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || GlobalConfig.Instance.Debug.SegmentId == segmentId); if (debugBasic) Log._Debug($"RoutingManager.ResetRoutingData: called for segment {segmentId}"); #endif segmentRoutings[segmentId].Reset(); ResetIncomingHighwayLaneArrows(segmentId); Services.NetService.IterateSegmentLanes(segmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segId, ref NetSegment segment, byte laneIndex) { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.ResetRoutingData: Resetting lane {laneId}, idx {laneIndex} @ seg. {segmentId}"); #endif ResetLaneRoutings(laneId, true); ResetLaneRoutings(laneId, false); return true; }); } protected void RecalculateSegmentRoutingData(ushort segmentId) { #if DEBUGROUTING bool debugBasic = GlobalConfig.Instance.Debug.Switches[1] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || GlobalConfig.Instance.Debug.SegmentId == segmentId); bool debugFine = GlobalConfig.Instance.Debug.Switches[8] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || GlobalConfig.Instance.Debug.SegmentId == segmentId); if (debugBasic) Log._Debug($"RoutingManager.RecalculateSegmentRoutingData: called for seg. {segmentId}"); #endif segmentRoutings[segmentId].Reset(); SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { return; } segmentRoutings[segmentId].highway = segGeo.IsHighway(); segmentRoutings[segmentId].startNodeOutgoingOneWay = segGeo.IsOutgoingOneWay(true); segmentRoutings[segmentId].endNodeOutgoingOneWay = segGeo.IsOutgoingOneWay(false); #if DEBUGROUTING if (debugBasic) Log._Debug($"RoutingManager.RecalculateSegmentRoutingData: Calculated routing data for segment {segmentId}: {segmentRoutings[segmentId]}"); #endif } protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, uint laneId, bool startNode) { #if DEBUGROUTING bool debugBasic = GlobalConfig.Instance.Debug.Switches[1] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || GlobalConfig.Instance.Debug.SegmentId == segmentId); bool debugFine = GlobalConfig.Instance.Debug.Switches[8] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || GlobalConfig.Instance.Debug.SegmentId == segmentId); if (debugBasic) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}) called"); #endif ResetLaneRoutings(laneId, startNode); if (!IsOutgoingLane(segmentId, startNode, laneIndex)) { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): Lane is not an outgoing lane"); #endif return; } bool prevIsMergeLane = Constants.ServiceFactory.NetService.CheckLaneFlags(laneId, NetLane.Flags.Merge); NetInfo prevSegmentInfo = null; bool prevSegIsInverted = false; Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate (ushort prevSegId, ref NetSegment segment) { prevSegmentInfo = segment.Info; prevSegIsInverted = (segment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None; return true; }); bool leftHandDrive = Constants.ServiceFactory.SimulationService.LeftHandDrive; SegmentGeometry prevSegGeo = SegmentGeometry.Get(segmentId); if (prevSegGeo == null) { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): No segment geometry found"); #endif //Log.Warning($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevSegGeo for segment {segmentId} is null"); return; } SegmentEndGeometry prevEndGeo = prevSegGeo.GetEnd(startNode); if (prevEndGeo == null) { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): No segment end geometry found"); #endif return; } ushort prevSegmentId = segmentId; int prevLaneIndex = laneIndex; uint prevLaneId = laneId; ushort nextNodeId = prevEndGeo.NodeId(); NetInfo.Lane prevLaneInfo = prevSegmentInfo.m_lanes[prevLaneIndex]; if (!prevLaneInfo.CheckType(ROUTED_LANE_TYPES, ROUTED_VEHICLE_TYPES)) { return; } LaneEndRoutingData backwardRouting = new LaneEndRoutingData(); backwardRouting.routed = true; int prevSimilarLaneCount = prevLaneInfo.m_similarLaneCount; int prevInnerSimilarLaneIndex = CalcInnerSimilarLaneIndex(prevSegmentId, prevLaneIndex); int prevOuterSimilarLaneIndex = CalcOuterSimilarLaneIndex(prevSegmentId, prevLaneIndex); bool prevHasBusLane = prevSegGeo.HasBusLane(); bool nextIsJunction = false; bool nextIsTransition = false; bool nextIsEndOrOneWayOut = false; bool nextHasTrafficLights = false; Constants.ServiceFactory.NetService.ProcessNode(nextNodeId, delegate (ushort nodeId, ref NetNode node) { nextIsJunction = (node.m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; nextIsTransition = (node.m_flags & NetNode.Flags.Transition) != NetNode.Flags.None; nextHasTrafficLights = (node.m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None; nextIsEndOrOneWayOut = (node.m_flags & (NetNode.Flags.End | NetNode.Flags.OneWayOut)) != NetNode.Flags.None; return true; }); bool nextIsSimpleJunction = false; bool nextIsSplitJunction = false; if (Options.highwayRules && !nextHasTrafficLights) { // determine if junction is a simple junction (highway rules only apply to simple junctions) NodeGeometry nodeGeo = NodeGeometry.Get(nextNodeId); nextIsSimpleJunction = nodeGeo.IsSimpleJunction; nextIsSplitJunction = nodeGeo.OutgoingSegments > 1; } bool isNextRealJunction = prevSegGeo.CountOtherSegments(startNode) > 1; bool nextAreOnlyOneWayHighways = prevEndGeo.OnlyHighways; // determine if highway rules should be applied bool onHighway = Options.highwayRules && nextAreOnlyOneWayHighways && prevEndGeo.OutgoingOneWay && prevSegGeo.IsHighway(); bool applyHighwayRules = onHighway && nextIsSimpleJunction; bool applyHighwayRulesAtJunction = applyHighwayRules && isNextRealJunction; bool iterateViaGeometry = applyHighwayRulesAtJunction && prevLaneInfo.CheckType(ROUTED_LANE_TYPES, ARROW_VEHICLE_TYPES); ushort nextSegmentId = iterateViaGeometry ? segmentId : (ushort)0; // start with u-turns at highway junctions #if DEBUGROUTING if (debugFine) { Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevSegment={segmentId}. Starting exploration with nextSegment={nextSegmentId} @ nextNodeId={nextNodeId} -- onHighway={onHighway} applyHighwayRules={applyHighwayRules} applyHighwayRulesAtJunction={applyHighwayRulesAtJunction} Options.highwayRules={Options.highwayRules} nextIsSimpleJunction={nextIsSimpleJunction} nextAreOnlyOneWayHighways={nextAreOnlyOneWayHighways} prevEndGeo.OutgoingOneWay={prevEndGeo.OutgoingOneWay} prevSegGeo.IsHighway()={prevSegGeo.IsHighway()} iterateViaGeometry={iterateViaGeometry}"); Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevSegIsInverted={prevSegIsInverted} leftHandDrive={leftHandDrive}"); Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevSimilarLaneCount={prevSimilarLaneCount} prevInnerSimilarLaneIndex={prevInnerSimilarLaneIndex} prevOuterSimilarLaneIndex={prevOuterSimilarLaneIndex} prevHasBusLane={prevHasBusLane}"); Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextIsJunction={nextIsJunction} nextIsEndOrOneWayOut={nextIsEndOrOneWayOut} nextHasTrafficLights={nextHasTrafficLights} nextIsSimpleJunction={nextIsSimpleJunction} nextIsSplitJunction={nextIsSplitJunction} isNextRealJunction={isNextRealJunction}"); } #endif int totalIncomingLanes = 0; // running number of next incoming lanes (number is updated at each segment iteration) int totalOutgoingLanes = 0; // running number of next outgoing lanes (number is updated at each segment iteration) for (int k = 0; k < 8; ++k) { if (!iterateViaGeometry) { Constants.ServiceFactory.NetService.ProcessNode(nextNodeId, delegate (ushort nId, ref NetNode node) { nextSegmentId = node.GetSegment(k); return true; }); if (nextSegmentId == 0) { continue; } } int outgoingVehicleLanes = 0; int incomingVehicleLanes = 0; bool isNextStartNodeOfNextSegment = false; bool nextSegIsInverted = false; NetInfo nextSegmentInfo = null; uint nextFirstLaneId = 0; Constants.ServiceFactory.NetService.ProcessSegment(nextSegmentId, delegate (ushort nextSegId, ref NetSegment segment) { isNextStartNodeOfNextSegment = segment.m_startNode == nextNodeId; /*segment.UpdateLanes(nextSegmentId, true); if (isNextStartNodeOfNextSegment) { segment.UpdateStartSegments(nextSegmentId); } else { segment.UpdateEndSegments(nextSegmentId); }*/ nextSegmentInfo = segment.Info; nextSegIsInverted = (segment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None; nextFirstLaneId = segment.m_lanes; return true; }); bool nextIsHighway = SegmentGeometry.calculateIsHighway(nextSegmentInfo); bool nextHasBusLane = SegmentGeometry.calculateHasBusLane(nextSegmentInfo); #if DEBUGROUTING if (debugFine) { Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): Exploring nextSegmentId={nextSegmentId}"); Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): isNextStartNodeOfNextSegment={isNextStartNodeOfNextSegment} nextSegIsInverted={nextSegIsInverted} nextFirstLaneId={nextFirstLaneId} nextIsHighway={nextIsHighway} nextHasBusLane={nextHasBusLane} totalOutgoingLanes={totalOutgoingLanes} totalIncomingLanes={totalIncomingLanes}"); } #endif // determine next segment direction by evaluating the geometry information ArrowDirection nextIncomingDir = prevEndGeo.GetDirection(nextSegmentId); bool isNextSegmentValid = nextIncomingDir != ArrowDirection.None; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevSegment={segmentId}. Exploring nextSegment={nextSegmentId} -- nextFirstLaneId={nextFirstLaneId} -- nextIncomingDir={nextIncomingDir} valid={isNextSegmentValid}"); #endif NetInfo.Direction nextDir = isNextStartNodeOfNextSegment ? NetInfo.Direction.Backward : NetInfo.Direction.Forward; NetInfo.Direction nextDir2 = !nextSegIsInverted ? nextDir : NetInfo.InvertDirection(nextDir); LaneTransitionData[] nextRelaxedTransitionDatas = null; byte numNextRelaxedTransitionDatas = 0; LaneTransitionData[] nextCompatibleTransitionDatas = null; int[] nextCompatibleOuterSimilarIndices = null; byte numNextCompatibleTransitionDatas = 0; LaneTransitionData[] nextLaneConnectionTransitionDatas = null; byte numNextLaneConnectionTransitionDatas = 0; LaneTransitionData[] nextForcedTransitionDatas = null; byte numNextForcedTransitionDatas = 0; int[] nextCompatibleTransitionDataIndices = null; byte numNextCompatibleTransitionDataIndices = 0; int[] compatibleLaneIndexToLaneConnectionIndex = null; if (isNextSegmentValid) { nextRelaxedTransitionDatas = new LaneTransitionData[MAX_NUM_TRANSITIONS]; nextCompatibleTransitionDatas = new LaneTransitionData[MAX_NUM_TRANSITIONS]; nextLaneConnectionTransitionDatas = new LaneTransitionData[MAX_NUM_TRANSITIONS]; nextForcedTransitionDatas = new LaneTransitionData[MAX_NUM_TRANSITIONS]; nextCompatibleOuterSimilarIndices = new int[MAX_NUM_TRANSITIONS]; nextCompatibleTransitionDataIndices = new int[MAX_NUM_TRANSITIONS]; compatibleLaneIndexToLaneConnectionIndex = new int[MAX_NUM_TRANSITIONS]; } uint nextLaneId = nextFirstLaneId; byte nextLaneIndex = 0; //ushort compatibleLaneIndicesMask = 0; while (nextLaneIndex < nextSegmentInfo.m_lanes.Length && nextLaneId != 0u) { // determine valid lanes based on lane arrows NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[nextLaneIndex]; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevSegment={segmentId}. Exploring nextSegment={nextSegmentId}, lane {nextLaneId}, idx {nextLaneIndex}"); #endif if (nextLaneInfo.CheckType(ROUTED_LANE_TYPES, ROUTED_VEHICLE_TYPES) && (prevLaneInfo.m_vehicleType & nextLaneInfo.m_vehicleType) != VehicleInfo.VehicleType.None /*(nextLaneInfo.m_vehicleType & prevLaneInfo.m_vehicleType) != VehicleInfo.VehicleType.None && (nextLaneInfo.m_laneType & prevLaneInfo.m_laneType) != NetInfo.LaneType.None*/) { // next is compatible lane #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): vehicle type check passed for nextLaneId={nextLaneId}, idx={nextLaneIndex}"); #endif if ((nextLaneInfo.m_finalDirection & nextDir2) != NetInfo.Direction.None) { // next is incoming lane #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): lane direction check passed for nextLaneId={nextLaneId}, idx={nextLaneIndex}"); #endif ++incomingVehicleLanes; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): increasing number of incoming lanes at nextLaneId={nextLaneId}, idx={nextLaneIndex}: isNextValid={isNextSegmentValid}, nextLaneInfo.m_finalDirection={nextLaneInfo.m_finalDirection}, nextDir2={nextDir2}: incomingVehicleLanes={incomingVehicleLanes}, outgoingVehicleLanes={outgoingVehicleLanes} "); #endif if (isNextSegmentValid) { // calculate current similar lane index starting from outer lane int nextOuterSimilarLaneIndex = CalcOuterSimilarLaneIndex(nextSegmentId, nextLaneIndex); //int nextInnerSimilarLaneIndex = CalcInnerSimilarLaneIndex(nextSegmentId, nextLaneIndex); bool isCompatibleLane = false; LaneEndTransitionType transitionType = LaneEndTransitionType.Invalid; // check for lane connections bool nextHasOutgoingConnections = LaneConnectionManager.Instance.HasConnections(nextLaneId, isNextStartNodeOfNextSegment); bool nextIsConnectedWithPrev = true; if (nextHasOutgoingConnections) { nextIsConnectedWithPrev = LaneConnectionManager.Instance.AreLanesConnected(prevLaneId, nextLaneId, startNode); } #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): checking lane connections of nextLaneId={nextLaneId}, idx={nextLaneIndex}: isNextStartNodeOfNextSegment={isNextStartNodeOfNextSegment}, nextSegmentId={nextSegmentId}, nextHasOutgoingConnections={nextHasOutgoingConnections}, nextIsConnectedWithPrev={nextIsConnectedWithPrev}"); #endif #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): connection information for nextLaneId={nextLaneId}, idx={nextLaneIndex}: nextOuterSimilarLaneIndex={nextOuterSimilarLaneIndex}, nextHasOutgoingConnections={nextHasOutgoingConnections}, nextIsConnectedWithPrev={nextIsConnectedWithPrev}"); #endif int currentLaneConnectionTransIndex = -1; if (nextHasOutgoingConnections) { // check for lane connections if (nextIsConnectedWithPrev) { // lane is connected with previous lane if (numNextLaneConnectionTransitionDatas < MAX_NUM_TRANSITIONS) { currentLaneConnectionTransIndex = numNextLaneConnectionTransitionDatas; nextLaneConnectionTransitionDatas[numNextLaneConnectionTransitionDatas++].Set(nextLaneId, nextLaneIndex, LaneEndTransitionType.LaneConnection, nextSegmentId, isNextStartNodeOfNextSegment); } else { Log.Warning($"nextTransitionDatas overflow @ source lane {prevLaneId}, idx {prevLaneIndex} @ seg. {prevSegmentId}"); } #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextLaneId={nextLaneId}, idx={nextLaneIndex} has outgoing connections and is connected with previous lane. adding as lane connection lane."); #endif } else { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextLaneId={nextLaneId}, idx={nextLaneIndex} has outgoing connections but is NOT connected with previous lane"); #endif } } if (prevIsMergeLane && Constants.ServiceFactory.NetService.CheckLaneFlags(nextLaneId, NetLane.Flags.Merge)) { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextLaneId={nextLaneId}, idx={nextLaneIndex} is a merge lane, as the previous lane. adding as Default."); #endif if (nextOuterSimilarLaneIndex == prevOuterSimilarLaneIndex) { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextLaneId={nextLaneId}, idx={nextLaneIndex} is a continuous merge lane. adding as Default."); #endif isCompatibleLane = true; transitionType = LaneEndTransitionType.Default; } } else if (!nextIsJunction) { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextLaneId={nextLaneId}, idx={nextLaneIndex} is not a junction. adding as Default."); #endif isCompatibleLane = true; transitionType = LaneEndTransitionType.Default; } else if (nextLaneInfo.CheckType(ROUTED_LANE_TYPES, ARROW_VEHICLE_TYPES)) { // check for lane arrows LaneArrows nextLaneArrows = LaneArrowManager.Instance.GetFinalLaneArrows(nextLaneId); bool hasLeftArrow = (nextLaneArrows & LaneArrows.Left) != LaneArrows.None; bool hasRightArrow = (nextLaneArrows & LaneArrows.Right) != LaneArrows.None; bool hasForwardArrow = (nextLaneArrows & LaneArrows.Forward) != LaneArrows.None || (nextLaneArrows & LaneArrows.LeftForwardRight) == LaneArrows.None; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): start lane arrow check for nextLaneId={nextLaneId}, idx={nextLaneIndex}: hasLeftArrow={hasLeftArrow}, hasForwardArrow={hasForwardArrow}, hasRightArrow={hasRightArrow}"); #endif if (applyHighwayRules || // highway rules enabled (nextIncomingDir == ArrowDirection.Right && hasLeftArrow) || // valid incoming right (nextIncomingDir == ArrowDirection.Left && hasRightArrow) || // valid incoming left (nextIncomingDir == ArrowDirection.Forward && hasForwardArrow) || // valid incoming straight (nextIncomingDir == ArrowDirection.Turn && (nextIsEndOrOneWayOut || ((leftHandDrive && hasRightArrow) || (!leftHandDrive && hasLeftArrow))))) { // valid turning lane #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): lane arrow check passed for nextLaneId={nextLaneId}, idx={nextLaneIndex}. adding as default lane."); #endif isCompatibleLane = true; transitionType = LaneEndTransitionType.Default; } else if (nextIsConnectedWithPrev) { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): lane arrow check FAILED for nextLaneId={nextLaneId}, idx={nextLaneIndex}. adding as relaxed lane."); #endif // lane can be used by all vehicles that may disregard lane arrows transitionType = LaneEndTransitionType.Relaxed; if (numNextRelaxedTransitionDatas < MAX_NUM_TRANSITIONS) { nextRelaxedTransitionDatas[numNextRelaxedTransitionDatas++].Set(nextLaneId, nextLaneIndex, transitionType, nextSegmentId, isNextStartNodeOfNextSegment, GlobalConfig.Instance.PathFinding.IncompatibleLaneDistance); } else { Log.Warning($"nextTransitionDatas overflow @ source lane {prevLaneId}, idx {prevLaneIndex} @ seg. {prevSegmentId}"); } } } else if (!nextHasOutgoingConnections) { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextLaneId={nextLaneId}, idx={nextLaneIndex} is used by vehicles that do not follow lane arrows. adding as default."); #endif // routed vehicle that does not follow lane arrows (trains, trams, metros, monorails) transitionType = LaneEndTransitionType.Default; if (numNextForcedTransitionDatas < MAX_NUM_TRANSITIONS) { nextForcedTransitionDatas[numNextForcedTransitionDatas].Set(nextLaneId, nextLaneIndex, transitionType, nextSegmentId, isNextStartNodeOfNextSegment); if (! isNextRealJunction) { // simple forced lane transition: set lane distance nextForcedTransitionDatas[numNextForcedTransitionDatas].distance = (byte)Math.Abs(prevOuterSimilarLaneIndex - nextOuterSimilarLaneIndex); } ++numNextForcedTransitionDatas; } else { Log.Warning($"nextForcedTransitionDatas overflow @ source lane {prevLaneId}, idx {prevLaneIndex} @ seg. {prevSegmentId}"); } } if (isCompatibleLane) { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): adding nextLaneId={nextLaneId}, idx={nextLaneIndex} as compatible lane now."); #endif if (numNextCompatibleTransitionDatas < MAX_NUM_TRANSITIONS) { nextCompatibleOuterSimilarIndices[numNextCompatibleTransitionDatas] = nextOuterSimilarLaneIndex; compatibleLaneIndexToLaneConnectionIndex[numNextCompatibleTransitionDatas] = currentLaneConnectionTransIndex; //compatibleLaneIndicesMask |= POW2MASKS[numNextCompatibleTransitionDatas]; nextCompatibleTransitionDatas[numNextCompatibleTransitionDatas++].Set(nextLaneId, nextLaneIndex, transitionType, nextSegmentId, isNextStartNodeOfNextSegment); } else { Log.Warning($"nextCompatibleTransitionDatas overflow @ source lane {prevLaneId}, idx {prevLaneIndex} @ seg. {prevSegmentId}"); } } else { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextLaneId={nextLaneId}, idx={nextLaneIndex} is NOT compatible."); #endif } } } else { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): lane direction check NOT passed for nextLaneId={nextLaneId}, idx={nextLaneIndex}: isNextValid={isNextSegmentValid}, nextLaneInfo.m_finalDirection={nextLaneInfo.m_finalDirection}, nextDir2={nextDir2}"); #endif if ((nextLaneInfo.m_finalDirection & NetInfo.InvertDirection(nextDir2)) != NetInfo.Direction.None) { ++outgoingVehicleLanes; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): increasing number of outgoing lanes at nextLaneId={nextLaneId}, idx={nextLaneIndex}: isNextValid={isNextSegmentValid}, nextLaneInfo.m_finalDirection={nextLaneInfo.m_finalDirection}, nextDir2={nextDir2}: incomingVehicleLanes={incomingVehicleLanes}, outgoingVehicleLanes={outgoingVehicleLanes}"); #endif } } } else { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): vehicle type check NOT passed for nextLaneId={nextLaneId}, idx={nextLaneIndex}: prevLaneInfo.m_vehicleType={prevLaneInfo.m_vehicleType}, nextLaneInfo.m_vehicleType={nextLaneInfo.m_vehicleType}, prevLaneInfo.m_laneType={prevLaneInfo.m_laneType}, nextLaneInfo.m_laneType={nextLaneInfo.m_laneType}"); #endif } Constants.ServiceFactory.NetService.ProcessLane(nextLaneId, delegate (uint lId, ref NetLane lane) { nextLaneId = lane.m_nextLane; return true; }); ++nextLaneIndex; } // foreach lane #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): isNextValid={isNextSegmentValid} Compatible lanes: " + nextCompatibleTransitionDatas?.ArrayToString()); #endif if (isNextSegmentValid) { bool laneChangesAllowed = Options.junctionRestrictionsEnabled && JunctionRestrictionsManager.Instance.IsLaneChangingAllowedWhenGoingStraight(nextSegmentId, isNextStartNodeOfNextSegment); int nextCompatibleLaneCount = numNextCompatibleTransitionDatas; if (nextCompatibleLaneCount > 0) { // we found compatible lanes int[] tmp = new int[nextCompatibleLaneCount]; Array.Copy(nextCompatibleOuterSimilarIndices, tmp, nextCompatibleLaneCount); nextCompatibleOuterSimilarIndices = tmp; int[] compatibleLaneIndicesSortedByOuterSimilarIndex = nextCompatibleOuterSimilarIndices.Select((x, i) => new KeyValuePair(x, i)).OrderBy(p => p.Key).Select(p => p.Value).ToArray(); // enable highway rules only at junctions or at simple lane merging/splitting points int laneDiff = nextCompatibleLaneCount - prevSimilarLaneCount; bool applyHighwayRulesAtSegment = applyHighwayRules && (applyHighwayRulesAtJunction || Math.Abs(laneDiff) == 1); #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): found compatible lanes! compatibleLaneIndicesSortedByOuterSimilarIndex={compatibleLaneIndicesSortedByOuterSimilarIndex.ArrayToString()}, laneDiff={laneDiff}, applyHighwayRulesAtSegment={applyHighwayRulesAtSegment}"); #endif if (applyHighwayRulesAtJunction) { // we reached a highway junction where more than two segments are connected to each other #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): applying highway rules at junction"); #endif // number of lanes that were processed in earlier segment iterations (either all incoming or all outgoing) int numLanesSeen = Math.Max(totalIncomingLanes, totalOutgoingLanes); int minNextInnerSimilarIndex = -1; int maxNextInnerSimilarIndex = -1; int refNextInnerSimilarIndex = -1; // this lane will be referred as the "stay" lane with zero distance #if DEBUGHWJUNCTIONROUTING if (debugFine) { Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): applying highway rules at junction"); Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): totalIncomingLanes={totalIncomingLanes}, totalOutgoingLanes={totalOutgoingLanes}, numLanesSeen={numLanesSeen} laneChangesAllowed={laneChangesAllowed}"); Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevInnerSimilarLaneIndex={prevInnerSimilarLaneIndex}, prevSimilarLaneCount={prevSimilarLaneCount}, nextCompatibleLaneCount={nextCompatibleLaneCount}"); } #endif if (nextIsSplitJunction) { // lane splitting at junction minNextInnerSimilarIndex = prevInnerSimilarLaneIndex + numLanesSeen; if (minNextInnerSimilarIndex >= nextCompatibleLaneCount) { // there have already been explored more outgoing lanes than incoming lanes on the previous segment. Also allow vehicles to go to the current segment. minNextInnerSimilarIndex = maxNextInnerSimilarIndex = refNextInnerSimilarIndex = nextCompatibleLaneCount - 1; } else { maxNextInnerSimilarIndex = refNextInnerSimilarIndex = minNextInnerSimilarIndex; if (laneChangesAllowed) { // allow lane changes at highway junctions if (minNextInnerSimilarIndex > 0 && prevInnerSimilarLaneIndex > 0) { --minNextInnerSimilarIndex; } } } #if DEBUGHWJUNCTIONROUTING if (debugFine) { Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): highway rules at junction: lane splitting junction. minNextInnerSimilarIndex={minNextInnerSimilarIndex}, maxNextInnerSimilarIndex={maxNextInnerSimilarIndex}"); } #endif } else { // lane merging at junction minNextInnerSimilarIndex = prevInnerSimilarLaneIndex - numLanesSeen; if (minNextInnerSimilarIndex < 0) { if (prevInnerSimilarLaneIndex == prevSimilarLaneCount - 1) { // there have already been explored more incoming lanes than outgoing lanes on the previous segment. Allow the current segment to also join the big merging party. What a fun! minNextInnerSimilarIndex = 0; maxNextInnerSimilarIndex = nextCompatibleLaneCount - 1; } else { // lanes do not connect (min/max = -1) } } else { // allow lane changes at highway junctions refNextInnerSimilarIndex = minNextInnerSimilarIndex; if (laneChangesAllowed) { maxNextInnerSimilarIndex = Math.Min(nextCompatibleLaneCount - 1, minNextInnerSimilarIndex + 1); if (minNextInnerSimilarIndex > 0) { --minNextInnerSimilarIndex; } } else { maxNextInnerSimilarIndex = minNextInnerSimilarIndex; } if (totalIncomingLanes > 0 && prevInnerSimilarLaneIndex == prevSimilarLaneCount - 1 && maxNextInnerSimilarIndex < nextCompatibleLaneCount - 1) { // we reached the outermost lane on the previous segment but there are still lanes to go on the next segment: allow merging maxNextInnerSimilarIndex = nextCompatibleLaneCount - 1; } } #if DEBUGHWJUNCTIONROUTING if (debugFine) { Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): highway rules at junction: lane merging/unknown junction. minNextInnerSimilarIndex={minNextInnerSimilarIndex}, maxNextInnerSimilarIndex={maxNextInnerSimilarIndex}"); } #endif } if (minNextInnerSimilarIndex >= 0) { #if DEBUGHWJUNCTIONROUTING if (debugFine) { Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): minNextInnerSimilarIndex >= 0. nextCompatibleTransitionDatas={nextCompatibleTransitionDatas.ArrayToString()}"); } #endif // explore lanes for (int nextInnerSimilarIndex = minNextInnerSimilarIndex; nextInnerSimilarIndex <= maxNextInnerSimilarIndex; ++nextInnerSimilarIndex) { int nextTransitionIndex = FindLaneByInnerIndex(nextCompatibleTransitionDatas, numNextCompatibleTransitionDatas, nextSegmentId, nextInnerSimilarIndex); #if DEBUGHWJUNCTIONROUTING if (debugFine) { Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): highway junction iteration: nextInnerSimilarIndex={nextInnerSimilarIndex}, nextTransitionIndex={nextTransitionIndex}"); } #endif if (nextTransitionIndex < 0) { continue; } // calculate lane distance byte compatibleLaneDist = 0; if (refNextInnerSimilarIndex >= 0) { compatibleLaneDist = (byte)Math.Abs(refNextInnerSimilarIndex - nextInnerSimilarIndex); } // skip lanes having lane connections if (LaneConnectionManager.Instance.HasConnections(nextCompatibleTransitionDatas[nextTransitionIndex].laneId, isNextStartNodeOfNextSegment)) { int laneConnectionTransIndex = compatibleLaneIndexToLaneConnectionIndex[nextTransitionIndex]; if (laneConnectionTransIndex >= 0) { nextLaneConnectionTransitionDatas[laneConnectionTransIndex].distance = compatibleLaneDist; } #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): Next lane ({nextCompatibleTransitionDatas[nextTransitionIndex].laneId}) has outgoing lane connections. Skip for now but set compatibleLaneDist={compatibleLaneDist} if laneConnectionTransIndex={laneConnectionTransIndex} >= 0."); #endif continue; // disregard lane since it has outgoing connections } nextCompatibleTransitionDatas[nextTransitionIndex].distance = compatibleLaneDist; #if DEBUGHWJUNCTIONROUTING if (debugFine) { Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): highway junction iteration: compatibleLaneDist={compatibleLaneDist}"); } #endif UpdateHighwayLaneArrows(nextCompatibleTransitionDatas[nextTransitionIndex].laneId, isNextStartNodeOfNextSegment, nextIncomingDir); if (numNextCompatibleTransitionDataIndices < MAX_NUM_TRANSITIONS) { nextCompatibleTransitionDataIndices[numNextCompatibleTransitionDataIndices++] = nextTransitionIndex; } else { Log.Warning($"nextCompatibleTransitionDataIndices overflow @ source lane {prevLaneId}, idx {prevLaneIndex} @ seg. {prevSegmentId}"); } } #if DEBUGHWJUNCTIONROUTING if (debugFine) { Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): highway junction iterations finished: nextCompatibleTransitionDataIndices={nextCompatibleTransitionDataIndices.ArrayToString()}"); } #endif } } else { /* * This is * 1. a highway lane splitting/merging point, * 2. a city or highway lane continuation point (simple transition with equal number of lanes or flagged city transition), or * 3. a city junction * with multiple or a single target lane: Perform lane matching */ #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): regular node"); #endif // min/max compatible outer similar lane indices int minNextCompatibleOuterSimilarIndex = -1; int maxNextCompatibleOuterSimilarIndex = -1; if (nextIncomingDir == ArrowDirection.Turn) { minNextCompatibleOuterSimilarIndex = 0; maxNextCompatibleOuterSimilarIndex = nextCompatibleLaneCount - 1; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): u-turn: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else if (isNextRealJunction) { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): next is real junction"); #endif // at junctions: try to match distinct lanes if (nextCompatibleLaneCount > prevSimilarLaneCount && prevOuterSimilarLaneIndex == prevSimilarLaneCount - 1) { // merge inner lanes minNextCompatibleOuterSimilarIndex = prevOuterSimilarLaneIndex; maxNextCompatibleOuterSimilarIndex = nextCompatibleLaneCount - 1; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): merge inner lanes: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else if (nextCompatibleLaneCount < prevSimilarLaneCount && prevSimilarLaneCount % nextCompatibleLaneCount == 0) { // symmetric split int splitFactor = prevSimilarLaneCount / nextCompatibleLaneCount; minNextCompatibleOuterSimilarIndex = maxNextCompatibleOuterSimilarIndex = prevOuterSimilarLaneIndex / splitFactor; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): symmetric split: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else { // 1-to-n (split inner lane) or 1-to-1 (direct lane matching) minNextCompatibleOuterSimilarIndex = prevOuterSimilarLaneIndex; maxNextCompatibleOuterSimilarIndex = prevOuterSimilarLaneIndex; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): 1-to-n (split inner lane) or 1-to-1 (direct lane matching): minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } bool straightLaneChangesAllowed = nextIncomingDir == ArrowDirection.Forward && laneChangesAllowed; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): laneChangesAllowed={laneChangesAllowed} straightLaneChangesAllowed={straightLaneChangesAllowed}"); #endif if (!straightLaneChangesAllowed) { if (nextHasBusLane && !prevHasBusLane) { // allow vehicles on the bus lane AND on the next lane to merge on this lane maxNextCompatibleOuterSimilarIndex = Math.Min(nextCompatibleLaneCount - 1, maxNextCompatibleOuterSimilarIndex + 1); #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): allow vehicles on the bus lane AND on the next lane to merge on this lane: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else if (!nextHasBusLane && prevHasBusLane) { // allow vehicles to enter the bus lane minNextCompatibleOuterSimilarIndex = Math.Max(0, minNextCompatibleOuterSimilarIndex - 1); #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): allow vehicles to enter the bus lane: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } } else { // vehicles may change lanes when going straight minNextCompatibleOuterSimilarIndex = minNextCompatibleOuterSimilarIndex - 1; maxNextCompatibleOuterSimilarIndex = maxNextCompatibleOuterSimilarIndex + 1; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): vehicles may change lanes when going straight: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } } else if (prevSimilarLaneCount == nextCompatibleLaneCount) { // equal lane count: consider all available lanes minNextCompatibleOuterSimilarIndex = 0; maxNextCompatibleOuterSimilarIndex = nextCompatibleLaneCount - 1; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): equal lane count: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else { // lane continuation point: lane merging/splitting #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): lane continuation point: lane merging/splitting"); #endif bool sym1 = (prevSimilarLaneCount & 1) == 0; // mod 2 == 0 bool sym2 = (nextCompatibleLaneCount & 1) == 0; // mod 2 == 0 #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): sym1={sym1}, sym2={sym2}"); #endif if (prevSimilarLaneCount < nextCompatibleLaneCount) { #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): lane merging (prevSimilarLaneCount={prevSimilarLaneCount} < nextCompatibleLaneCount={nextCompatibleLaneCount})"); #endif // lane merging if (sym1 == sym2) { // merge outer lanes int a = (nextCompatibleLaneCount - prevSimilarLaneCount) >> 1; // nextCompatibleLaneCount - prevSimilarLaneCount is always > 0 #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): merge outer lanes. a={a}"); #endif if (prevSimilarLaneCount == 1) { minNextCompatibleOuterSimilarIndex = 0; maxNextCompatibleOuterSimilarIndex = nextCompatibleLaneCount - 1; // always >=0 #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevSimilarLaneCount == 1: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else if (prevOuterSimilarLaneIndex == 0) { minNextCompatibleOuterSimilarIndex = 0; maxNextCompatibleOuterSimilarIndex = a; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevOuterSimilarLaneIndex == 0: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else if (prevOuterSimilarLaneIndex == prevSimilarLaneCount - 1) { minNextCompatibleOuterSimilarIndex = prevOuterSimilarLaneIndex + a; maxNextCompatibleOuterSimilarIndex = nextCompatibleLaneCount - 1; // always >=0 #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevOuterSimilarLaneIndex == prevSimilarLaneCount - 1: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else { minNextCompatibleOuterSimilarIndex = maxNextCompatibleOuterSimilarIndex = prevOuterSimilarLaneIndex + a; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): default case: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } } else { // criss-cross merge int a = (nextCompatibleLaneCount - prevSimilarLaneCount - 1) >> 1; // nextCompatibleLaneCount - prevSimilarLaneCount - 1 is always >= 0 int b = (nextCompatibleLaneCount - prevSimilarLaneCount + 1) >> 1; // nextCompatibleLaneCount - prevSimilarLaneCount + 1 is always >= 2 #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): criss-cross merge: a={a}, b={b}"); #endif if (prevSimilarLaneCount == 1) { minNextCompatibleOuterSimilarIndex = 0; maxNextCompatibleOuterSimilarIndex = nextCompatibleLaneCount - 1; // always >=0 #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevSimilarLaneCount == 1: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else if (prevOuterSimilarLaneIndex == 0) { minNextCompatibleOuterSimilarIndex = 0; maxNextCompatibleOuterSimilarIndex = b; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevOuterSimilarLaneIndex == 0: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else if (prevOuterSimilarLaneIndex == prevSimilarLaneCount - 1) { minNextCompatibleOuterSimilarIndex = prevOuterSimilarLaneIndex + a; maxNextCompatibleOuterSimilarIndex = nextCompatibleLaneCount - 1; // always >=0 #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): prevOuterSimilarLaneIndex == prevSimilarLaneCount - 1: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else { minNextCompatibleOuterSimilarIndex = prevOuterSimilarLaneIndex + a; maxNextCompatibleOuterSimilarIndex = prevOuterSimilarLaneIndex + b; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): default criss-cross case: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } } } else { // at lane splits: distribute traffic evenly (1-to-n, n-to-n) // prevOuterSimilarIndex is always > nextCompatibleLaneCount #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): at lane splits: distribute traffic evenly (1-to-n, n-to-n)"); #endif if (sym1 == sym2) { // split outer lanes int a = (prevSimilarLaneCount - nextCompatibleLaneCount) >> 1; // prevSimilarLaneCount - nextCompatibleLaneCount is always > 0 minNextCompatibleOuterSimilarIndex = maxNextCompatibleOuterSimilarIndex = prevOuterSimilarLaneIndex - a; // a is always <= prevSimilarLaneCount #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): split outer lanes: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else { // split outer lanes, criss-cross inner lanes int a = (prevSimilarLaneCount - nextCompatibleLaneCount - 1) >> 1; // prevSimilarLaneCount - nextCompatibleLaneCount - 1 is always >= 0 minNextCompatibleOuterSimilarIndex = (a - 1 >= prevOuterSimilarLaneIndex) ? 0 : prevOuterSimilarLaneIndex - a - 1; maxNextCompatibleOuterSimilarIndex = (a >= prevOuterSimilarLaneIndex) ? 0 : prevOuterSimilarLaneIndex - a; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): split outer lanes, criss-cross inner lanes: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } } } #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): pre-final bounds: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif minNextCompatibleOuterSimilarIndex = Math.Max(0, Math.Min(minNextCompatibleOuterSimilarIndex, nextCompatibleLaneCount - 1)); maxNextCompatibleOuterSimilarIndex = Math.Max(0, Math.Min(maxNextCompatibleOuterSimilarIndex, nextCompatibleLaneCount - 1)); if (minNextCompatibleOuterSimilarIndex > maxNextCompatibleOuterSimilarIndex) { minNextCompatibleOuterSimilarIndex = maxNextCompatibleOuterSimilarIndex; } #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): final bounds: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif // find best matching lane(s) for (int nextCompatibleOuterSimilarIndex = minNextCompatibleOuterSimilarIndex; nextCompatibleOuterSimilarIndex <= maxNextCompatibleOuterSimilarIndex; ++nextCompatibleOuterSimilarIndex) { int nextTransitionIndex = FindLaneWithMaxOuterIndex(compatibleLaneIndicesSortedByOuterSimilarIndex, nextCompatibleOuterSimilarIndex); #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): best matching lane iteration -- nextCompatibleOuterSimilarIndex={nextCompatibleOuterSimilarIndex} => nextTransitionIndex={nextTransitionIndex}"); #endif if (nextTransitionIndex < 0) { continue; } // calculate lane distance byte compatibleLaneDist = 0; if (nextIncomingDir == ArrowDirection.Turn) { compatibleLaneDist = (byte)GlobalConfig.Instance.PathFinding.UturnLaneDistance; } else if (!isNextRealJunction && ((!nextIsJunction && !nextIsTransition) || nextCompatibleLaneCount == prevSimilarLaneCount)) { int relLaneDist = nextCompatibleOuterSimilarIndices[nextTransitionIndex] - prevOuterSimilarLaneIndex; // relative lane distance (positive: change to more outer lane, negative: change to more inner lane) compatibleLaneDist = (byte)Math.Abs(relLaneDist); } // skip lanes having lane connections if (LaneConnectionManager.Instance.HasConnections(nextCompatibleTransitionDatas[nextTransitionIndex].laneId, isNextStartNodeOfNextSegment)) { int laneConnectionTransIndex = compatibleLaneIndexToLaneConnectionIndex[nextTransitionIndex]; if (laneConnectionTransIndex >= 0) { nextLaneConnectionTransitionDatas[laneConnectionTransIndex].distance = compatibleLaneDist; } #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): Next lane ({nextCompatibleTransitionDatas[nextTransitionIndex].laneId}) has outgoing lane connections. Skip for now but set compatibleLaneDist={compatibleLaneDist} if laneConnectionTransIndex={laneConnectionTransIndex} >= 0."); #endif continue; // disregard lane since it has outgoing connections } if ( nextIncomingDir == ArrowDirection.Turn && // u-turn !nextIsEndOrOneWayOut && // not a dead end nextCompatibleOuterSimilarIndex != maxNextCompatibleOuterSimilarIndex // incoming lane is not innermost lane ) { // force u-turns to happen on the innermost lane ++compatibleLaneDist; nextCompatibleTransitionDatas[nextTransitionIndex].type = LaneEndTransitionType.Relaxed; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): Next lane ({nextCompatibleTransitionDatas[nextTransitionIndex].laneId}) is avoided u-turn. Incrementing compatible lane distance to {compatibleLaneDist}"); #endif } #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): -> compatibleLaneDist={compatibleLaneDist}"); #endif nextCompatibleTransitionDatas[nextTransitionIndex].distance = compatibleLaneDist; if (onHighway && !isNextRealJunction && compatibleLaneDist > 1) { // under normal circumstances vehicles should not change more than one lane on highways at one time nextCompatibleTransitionDatas[nextTransitionIndex].type = LaneEndTransitionType.Relaxed; #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): -> under normal circumstances vehicles should not change more than one lane on highways at one time: setting type to Relaxed"); #endif } else if (applyHighwayRulesAtSegment) { UpdateHighwayLaneArrows(nextCompatibleTransitionDatas[nextTransitionIndex].laneId, isNextStartNodeOfNextSegment, nextIncomingDir); } if (numNextCompatibleTransitionDataIndices < MAX_NUM_TRANSITIONS) { nextCompatibleTransitionDataIndices[numNextCompatibleTransitionDataIndices++] = nextTransitionIndex; } else { Log.Warning($"nextCompatibleTransitionDataIndices overflow @ source lane {prevLaneId}, idx {prevLaneIndex} @ seg. {prevSegmentId}"); } } // foreach lane } // highway/city rules if/else } // compatible lanes found // build final array LaneTransitionData[] nextTransitionDatas = new LaneTransitionData[numNextRelaxedTransitionDatas + numNextCompatibleTransitionDataIndices + numNextLaneConnectionTransitionDatas + numNextForcedTransitionDatas]; int j = 0; for (int i = 0; i < numNextCompatibleTransitionDataIndices; ++i) { nextTransitionDatas[j++] = nextCompatibleTransitionDatas[nextCompatibleTransitionDataIndices[i]]; } for (int i = 0; i < numNextLaneConnectionTransitionDatas; ++i) { nextTransitionDatas[j++] = nextLaneConnectionTransitionDatas[i]; } for (int i = 0; i < numNextRelaxedTransitionDatas; ++i) { nextTransitionDatas[j++] = nextRelaxedTransitionDatas[i]; } for (int i = 0; i < numNextForcedTransitionDatas; ++i) { nextTransitionDatas[j++] = nextForcedTransitionDatas[i]; } #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): build array for nextSegment={nextSegmentId}: nextTransitionDatas={nextTransitionDatas.ArrayToString()}"); #endif backwardRouting.AddTransitions(nextTransitionDatas); #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): updated incoming/outgoing lanes for next segment iteration: totalIncomingLanes={totalIncomingLanes}, totalOutgoingLanes={totalOutgoingLanes}"); #endif } // valid segment if (nextSegmentId != prevSegmentId) { totalIncomingLanes += incomingVehicleLanes; totalOutgoingLanes += outgoingVehicleLanes; } if (iterateViaGeometry) { Constants.ServiceFactory.NetService.ProcessSegment(nextSegmentId, delegate (ushort nextSegId, ref NetSegment segment) { if (Constants.ServiceFactory.SimulationService.LeftHandDrive) { nextSegmentId = segment.GetLeftSegment(nextNodeId); } else { nextSegmentId = segment.GetRightSegment(nextNodeId); } return true; }); if (nextSegmentId == prevSegmentId || nextSegmentId == 0) { // we reached the first segment again break; } } } // foreach segment // update backward routing laneEndBackwardRoutings[GetLaneEndRoutingIndex(laneId, startNode)] = backwardRouting; // update forward routing LaneTransitionData[] newTransitions = backwardRouting.transitions; if (newTransitions != null) { for (int i = 0; i < newTransitions.Length; ++i) { uint sourceIndex = GetLaneEndRoutingIndex(newTransitions[i].laneId, newTransitions[i].startNode); LaneTransitionData forwardTransition = new LaneTransitionData(); forwardTransition.laneId = laneId; forwardTransition.laneIndex = (byte)laneIndex; forwardTransition.type = newTransitions[i].type; forwardTransition.distance = newTransitions[i].distance; forwardTransition.segmentId = segmentId; forwardTransition.startNode = startNode; laneEndForwardRoutings[sourceIndex].AddTransition(forwardTransition); #if DEBUGROUTING if (debugFine) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): adding transition to forward routing of laneId={laneId}, idx={laneIndex} @ seg. {newTransitions[i].segmentId} @ node {newTransitions[i].startNode} (sourceIndex={sourceIndex}): {forwardTransition.ToString()}\n\nNew forward routing:\n{laneEndForwardRoutings[sourceIndex].ToString()}"); #endif } } #if DEBUGROUTING if (debugBasic) Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): FINISHED calculating routing data for array index {GetLaneEndRoutingIndex(laneId, startNode)}: {backwardRouting}"); #endif } /// /// remove all backward routings from this lane and forward routings pointing to this lane /// /// /// protected void ResetLaneRoutings(uint laneId, bool startNode) { uint index = GetLaneEndRoutingIndex(laneId, startNode); LaneTransitionData[] oldBackwardTransitions = laneEndBackwardRoutings[index].transitions; if (oldBackwardTransitions != null) { for (int i = 0; i < oldBackwardTransitions.Length; ++i) { uint sourceIndex = GetLaneEndRoutingIndex(oldBackwardTransitions[i].laneId, oldBackwardTransitions[i].startNode); laneEndForwardRoutings[sourceIndex].RemoveTransition(laneId); } } laneEndBackwardRoutings[index].Reset(); } private void UpdateHighwayLaneArrows(uint laneId, bool startNode, ArrowDirection dir) { Flags.LaneArrows? prevHighwayArrows = Flags.getHighwayLaneArrowFlags(laneId); Flags.LaneArrows newHighwayArrows = Flags.LaneArrows.None; if (prevHighwayArrows != null) newHighwayArrows = (Flags.LaneArrows)prevHighwayArrows; if (dir == ArrowDirection.Right) newHighwayArrows |= Flags.LaneArrows.Left; else if (dir == ArrowDirection.Left) newHighwayArrows |= Flags.LaneArrows.Right; else if (dir == ArrowDirection.Forward) newHighwayArrows |= Flags.LaneArrows.Forward; #if DEBUGROUTING //Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData: highway rules -- next lane {laneId} obeys highway rules. Setting highway lane arrows to {newHighwayArrows}. prevHighwayArrows={prevHighwayArrows}"); #endif if (newHighwayArrows != prevHighwayArrows && newHighwayArrows != Flags.LaneArrows.None) { Flags.setHighwayLaneArrowFlags(laneId, newHighwayArrows, false); } } /*private int GetSegmentNodeIndex(ushort nodeId, ushort segmentId) { int i = -1; Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segId, ref NetSegment segment) { ++i; if (segId == segmentId) { return false; } return true; }); return i; }*/ internal uint GetLaneEndRoutingIndex(uint laneId, bool startNode) { return (uint)(laneId + (startNode ? 0u : (uint)NetManager.MAX_LANE_COUNT)); } public int CalcInnerSimilarLaneIndex(ushort segmentId, int laneIndex) { int ret = -1; Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate (ushort segId, ref NetSegment segment) { ret = CalcInnerSimilarLaneIndex(segment.Info.m_lanes[laneIndex]); return true; }); return ret; } public int CalcInnerSimilarLaneIndex(NetInfo.Lane laneInfo) { // note: m_direction is correct here return (byte)(laneInfo.m_direction & NetInfo.Direction.Forward) != 0 ? laneInfo.m_similarLaneIndex : laneInfo.m_similarLaneCount - laneInfo.m_similarLaneIndex - 1; } public int CalcOuterSimilarLaneIndex(ushort segmentId, int laneIndex) { int ret = -1; Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate (ushort segId, ref NetSegment segment) { ret = CalcOuterSimilarLaneIndex(segment.Info.m_lanes[laneIndex]); return true; }); return ret; } public int CalcOuterSimilarLaneIndex(NetInfo.Lane laneInfo) { // note: m_direction is correct here return (byte)(laneInfo.m_direction & NetInfo.Direction.Forward) != 0 ? laneInfo.m_similarLaneCount - laneInfo.m_similarLaneIndex - 1 : laneInfo.m_similarLaneIndex; } protected int FindLaneWithMaxOuterIndex(int[] indicesSortedByOuterIndex, int targetOuterLaneIndex) { return indicesSortedByOuterIndex[Math.Max(0, Math.Min(targetOuterLaneIndex, indicesSortedByOuterIndex.Length - 1))]; } protected int FindLaneByOuterIndex(LaneTransitionData[] laneTransitions, int num, ushort segmentId, int targetOuterLaneIndex) { for (int i = 0; i < num; ++i) { int outerIndex = CalcOuterSimilarLaneIndex(segmentId, laneTransitions[i].laneIndex); if (outerIndex == targetOuterLaneIndex) { return i; } } return -1; } protected int FindLaneByInnerIndex(LaneTransitionData[] laneTransitions, int num, ushort segmentId, int targetInnerLaneIndex) { for (int i = 0; i < num; ++i) { int innerIndex = CalcInnerSimilarLaneIndex(segmentId, laneTransitions[i].laneIndex); if (innerIndex == targetInnerLaneIndex) { return i; } } return -1; } protected bool IsOutgoingLane(ushort segmentId, bool startNode, int laneIndex) { return IsIncomingOutgoingLane(segmentId, startNode, laneIndex, false); } protected bool IsIncomingLane(ushort segmentId, bool startNode, int laneIndex) { return IsIncomingOutgoingLane(segmentId, startNode, laneIndex, true); } protected bool IsIncomingOutgoingLane(ushort segmentId, bool startNode, int laneIndex, bool incoming) { bool segIsInverted = false; Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate (ushort segId, ref NetSegment segment) { segIsInverted = (segment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None; return true; }); NetInfo.Direction dir = startNode ? NetInfo.Direction.Forward : NetInfo.Direction.Backward; dir = incoming ^ segIsInverted ? NetInfo.InvertDirection(dir) : dir; NetInfo.Direction finalDir = NetInfo.Direction.None; Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate (ushort segId, ref NetSegment segment) { finalDir = segment.Info.m_lanes[laneIndex].m_finalDirection; return true; }); return (finalDir & dir) != NetInfo.Direction.None; } protected override void HandleInvalidSegment(SegmentGeometry geometry) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[1] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || GlobalConfig.Instance.Debug.SegmentId == geometry.SegmentId); if (debug) { Log._Debug($"RoutingManager.HandleInvalidSegment({geometry.SegmentId}) called."); } #endif Flags.removeHighwayLaneArrowFlagsAtSegment(geometry.SegmentId); ResetRoutingData(geometry.SegmentId); } protected override void HandleValidSegment(SegmentGeometry geometry) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[1] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || GlobalConfig.Instance.Debug.SegmentId == geometry.SegmentId); if (debug) { Log._Debug($"RoutingManager.HandleValidSegment({geometry.SegmentId}) called."); } #endif ResetRoutingData(geometry.SegmentId); RequestRecalculation(geometry.SegmentId); } public override void OnAfterLoadData() { base.OnAfterLoadData(); RecalculateAll(); } } } ================================================ FILE: TLM/TLM/Manager/Impl/SegmentEndManager.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Geometry; using TrafficManager.Geometry.Impl; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Traffic.Impl; using TrafficManager.TrafficLight; namespace TrafficManager.Manager.Impl { public class SegmentEndManager : AbstractCustomManager, ISegmentEndManager { public static readonly SegmentEndManager Instance = new SegmentEndManager(); private ISegmentEnd[] SegmentEnds; private SegmentEndManager() { SegmentEnds = new SegmentEnd[2 * NetManager.MAX_SEGMENT_COUNT]; } protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"Segment ends:"); for (int i = 0; i < SegmentEnds.Length; ++i) { if (SegmentEnds[i] == null) { continue; } Log._Debug($"Segment end {i}: {SegmentEnds[i]}"); } } public ISegmentEnd GetSegmentEnd(ISegmentEndId endId) { return GetSegmentEnd(endId.SegmentId, endId.StartNode); } public ISegmentEnd GetSegmentEnd(ushort segmentId, bool startNode) { return SegmentEnds[GetIndex(segmentId, startNode)]; } public ISegmentEnd GetOrAddSegmentEnd(ISegmentEndId endId) { return GetOrAddSegmentEnd(endId.SegmentId, endId.StartNode); } public ISegmentEnd GetOrAddSegmentEnd(ushort segmentId, bool startNode) { ISegmentEnd end = GetSegmentEnd(segmentId, startNode); if (end != null) { return end; } SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { Log.Warning($"SegmentEndManager.GetOrAddSegmentEnd({segmentId}, {startNode}): Refusing to add segment end for invalid segment."); return null; } SegmentEndGeometry endGeo = segGeo.GetEnd(startNode); if (endGeo == null) { Log.Warning($"SegmentEndManager.GetOrAddSegmentEnd({segmentId}, {startNode}): Refusing to add segment end for invalid segment end."); return null; } return SegmentEnds[GetIndex(segmentId, startNode)] = new SegmentEnd(segmentId, startNode); } public void RemoveSegmentEnd(ISegmentEndId endId) { RemoveSegmentEnd(endId.SegmentId, endId.StartNode); } public void RemoveSegmentEnd(ushort segmentId, bool startNode) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); if (debug) { Log._Debug($"SegmentEndManager.RemoveSegmentEnd({segmentId}, {startNode}) called"); } #endif DestroySegmentEnd(GetIndex(segmentId, startNode)); } public void RemoveSegmentEnds(ushort segmentId) { RemoveSegmentEnd(segmentId, true); RemoveSegmentEnd(segmentId, false); } public bool UpdateSegmentEnd(ISegmentEndId endId) { return UpdateSegmentEnd(endId.SegmentId, endId.StartNode); } public bool UpdateSegmentEnd(ushort segmentId, bool startNode) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); #endif SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { #if DEBUG if (debug) { Log._Debug($"SegmentEndManager.UpdateSegmentEnd({segmentId}, {startNode}): Segment {segmentId} is invalid. Removing all segment ends."); } #endif RemoveSegmentEnds(segmentId); return false; } SegmentEndGeometry endGeo = segGeo.GetEnd(startNode); if (endGeo == null) { #if DEBUG if (debug) { Log._Debug($"SegmentEndManager.UpdateSegmentEnd({segmentId}, {startNode}): Segment end {segmentId} @ {startNode} is invalid. Removing segment end."); } #endif RemoveSegmentEnd(segmentId, startNode); return false; } if (TrafficPriorityManager.Instance.HasSegmentPrioritySign(segmentId, startNode) || TrafficLightSimulationManager.Instance.HasTimedSimulation(endGeo.NodeId())) { #if DEBUG if (debug) { Log._Debug($"SegmentEndManager.UpdateSegmentEnd({segmentId}, {startNode}): Segment {segmentId} @ {startNode} has timed light or priority sign. Adding segment end {segmentId} @ {startNode}"); } #endif ISegmentEnd end = GetOrAddSegmentEnd(segmentId, startNode); if (end == null) { Log.Warning($"SegmentEndManager.UpdateSegmentEnd({segmentId}, {startNode}): Failed to add segment end."); return false; } else { #if DEBUG if (debug) { Log._Debug($"SegmentEndManager.UpdateSegmentEnd({segmentId}, {startNode}): Added segment end. Updating now."); } #endif end.Update(); #if DEBUG if (debug) { Log._Debug($"SegmentEndManager.UpdateSegmentEnd({segmentId}, {startNode}): Update of segment end finished."); } #endif return true; } } else { #if DEBUG if (debug) { Log._Debug($"SegmentEndManager.UpdateSegmentEnd({segmentId}, {startNode}): Segment {segmentId} @ {startNode} neither has timed light nor priority sign. Removing segment end {segmentId} @ {startNode}"); } #endif RemoveSegmentEnd(segmentId, startNode); return false; } } private int GetIndex(ushort segmentId, bool startNode) { return (int)segmentId + (startNode ? 0 : NetManager.MAX_SEGMENT_COUNT); } /*protected override void HandleInvalidSegment(SegmentGeometry geometry) { RemoveSegmentEnds(geometry.SegmentId); } protected override void HandleValidSegment(SegmentGeometry geometry) { RemoveSegmentEnds(geometry.SegmentId); }*/ protected void DestroySegmentEnd(int index) { #if DEBUG //Log._Debug($"SegmentEndManager.DestroySegmentEnd({index}) called"); #endif SegmentEnds[index]?.Destroy(); SegmentEnds[index] = null; } public override void OnLevelUnloading() { base.OnLevelUnloading(); for (int i = 0; i < SegmentEnds.Length; ++i) { DestroySegmentEnd(i); } } } } ================================================ FILE: TLM/TLM/Manager/Impl/SpeedLimitManager.cs ================================================ using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Text; using TrafficManager.Geometry; using TrafficManager.Geometry.Impl; using TrafficManager.State; using TrafficManager.Util; using UnityEngine; namespace TrafficManager.Manager.Impl { public class SpeedLimitManager : AbstractGeometryObservingManager, ICustomDataManager>, ICustomDataManager>, ISpeedLimitManager { public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Monorail; public const float MAX_SPEED = 10f * 2f; // 1000 km/h private Dictionary vanillaLaneSpeedLimitsByNetInfoName; // For each NetInfo (by name) and lane index: game default speed limit private Dictionary> childNetInfoNamesByCustomizableNetInfoName; // For each NetInfo (by name): Parent NetInfo (name) private List customizableNetInfos; internal Dictionary CustomLaneSpeedLimitIndexByNetInfoName; // For each NetInfo (by name) and lane index: custom speed limit index internal Dictionary NetInfoByName; // For each name: NetInfo public static readonly SpeedLimitManager Instance = new SpeedLimitManager(); protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"- Not implemented -"); // TODO implement } public readonly List AvailableSpeedLimits; private SpeedLimitManager() { AvailableSpeedLimits = new List(); AvailableSpeedLimits.Add(10); AvailableSpeedLimits.Add(20); AvailableSpeedLimits.Add(30); AvailableSpeedLimits.Add(40); AvailableSpeedLimits.Add(50); AvailableSpeedLimits.Add(60); AvailableSpeedLimits.Add(70); AvailableSpeedLimits.Add(80); AvailableSpeedLimits.Add(90); AvailableSpeedLimits.Add(100); AvailableSpeedLimits.Add(110); AvailableSpeedLimits.Add(120); AvailableSpeedLimits.Add(130); AvailableSpeedLimits.Add(0); vanillaLaneSpeedLimitsByNetInfoName = new Dictionary(); CustomLaneSpeedLimitIndexByNetInfoName = new Dictionary(); customizableNetInfos = new List(); childNetInfoNamesByCustomizableNetInfoName = new Dictionary>(); NetInfoByName = new Dictionary(); } /// /// Determines if custom speed limits may be assigned to the given segment. /// /// /// /// public bool MayHaveCustomSpeedLimits(ushort segmentId, ref NetSegment segment) { if ((segment.m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) return false; ItemClass connectionClass = segment.Info.GetConnectionClass(); return (connectionClass.m_service == ItemClass.Service.Road || (connectionClass.m_service == ItemClass.Service.PublicTransport && (connectionClass.m_subService == ItemClass.SubService.PublicTransportTrain || connectionClass.m_subService == ItemClass.SubService.PublicTransportTram || connectionClass.m_subService == ItemClass.SubService.PublicTransportMetro || connectionClass.m_subService == ItemClass.SubService.PublicTransportMonorail))); } /// /// Determines if custom speed limits may be assigned to the given lane info /// /// /// public bool MayHaveCustomSpeedLimits(NetInfo.Lane laneInfo) { return (laneInfo.m_laneType & LANE_TYPES) != NetInfo.LaneType.None && (laneInfo.m_vehicleType & VEHICLE_TYPES) != VehicleInfo.VehicleType.None; } /// /// Determines the currently set speed limit for the given segment and lane direction in terms of discrete speed limit levels. /// An in-game speed limit of 2.0 (e.g. on highway) is hereby translated into a discrete speed limit value of 100 (km/h). /// /// /// /// public ushort GetCustomSpeedLimit(ushort segmentId, NetInfo.Direction finalDir) { // calculate the currently set mean speed limit if (segmentId == 0 || (Singleton.instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) { return 0; } var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; int laneIndex = 0; float meanSpeedLimit = 0f; uint validLanes = 0; while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; NetInfo.Direction d = laneInfo.m_finalDirection; if (d != finalDir) goto nextIter; if (!MayHaveCustomSpeedLimits(laneInfo)) goto nextIter; ushort? setSpeedLimit = Flags.getLaneSpeedLimit(curLaneId); if (setSpeedLimit != null) meanSpeedLimit += ToGameSpeedLimit((ushort)setSpeedLimit); // custom speed limit else meanSpeedLimit += laneInfo.m_speedLimit; // game default ++validLanes; nextIter: curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; laneIndex++; } if (validLanes > 0) meanSpeedLimit /= (float)validLanes; ushort ret = LaneToCustomSpeedLimit(meanSpeedLimit); return ret; } /// /// Determines the average default speed limit for a given NetInfo object in terms of discrete speed limit levels. /// An in-game speed limit of 2.0 (e.g. on highway) is hereby translated into a discrete speed limit value of 100 (km/h). /// /// /// /// public ushort GetAverageDefaultCustomSpeedLimit(NetInfo segmentInfo, NetInfo.Direction? finalDir=null) { float meanSpeedLimit = 0f; uint validLanes = 0; for (int i = 0; i < segmentInfo.m_lanes.Length; ++i) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[i]; NetInfo.Direction d = laneInfo.m_finalDirection; if (finalDir != null && d != finalDir) continue; if (!MayHaveCustomSpeedLimits(laneInfo)) continue; meanSpeedLimit += laneInfo.m_speedLimit; ++validLanes; } if (validLanes > 0) meanSpeedLimit /= (float)validLanes; ushort ret = LaneToCustomSpeedLimit(meanSpeedLimit); return ret; } /// /// Determines the average custom speed limit for a given NetInfo object in terms of discrete speed limit levels. /// An in-game speed limit of 2.0 (e.g. on highway) is hereby translated into a discrete speed limit value of 100 (km/h). /// /// /// /// public ushort GetAverageCustomSpeedLimit(ushort segmentId, ref NetSegment segment, NetInfo segmentInfo, NetInfo.Direction? finalDir = null) { // calculate the currently set mean speed limit float meanSpeedLimit = 0f; uint validLanes = 0; uint curLaneId = segment.m_lanes; for (byte laneIndex = 0; laneIndex < segmentInfo.m_lanes.Length; ++laneIndex) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; NetInfo.Direction d = laneInfo.m_finalDirection; if (finalDir != null && d != finalDir) continue; if (!MayHaveCustomSpeedLimits(laneInfo)) continue; meanSpeedLimit += GetLockFreeGameSpeedLimit(segmentId, laneIndex, curLaneId, laneInfo); curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; ++validLanes; } if (validLanes > 0) meanSpeedLimit /= (float)validLanes; return (ushort)Mathf.Round(meanSpeedLimit); } /// /// Determines the currently set speed limit for the given lane in terms of discrete speed limit levels. /// An in-game speed limit of 2.0 (e.g. on highway) is hereby translated into a discrete speed limit value of 100 (km/h). /// /// /// public ushort GetCustomSpeedLimit(uint laneId) { // check custom speed limit ushort? setSpeedLimit = Flags.getLaneSpeedLimit(laneId); if (setSpeedLimit != null) { return (ushort)setSpeedLimit; } // check default speed limit ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; if (!MayHaveCustomSpeedLimits(segmentId, ref Singleton.instance.m_segments.m_buffer[segmentId])) { return 0; } var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; int laneIndex = 0; while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { if (curLaneId == laneId) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; if (!MayHaveCustomSpeedLimits(laneInfo)) return 0; ushort ret = LaneToCustomSpeedLimit(laneInfo.m_speedLimit); return ret; } laneIndex++; curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; } Log.Warning($"Speed limit for lane {laneId} could not be determined."); return 0; // no speed limit found } /// /// Determines the currently set speed limit for the given lane in terms of game (floating point) speed limit levels /// /// /// public float GetGameSpeedLimit(uint laneId) { return ToGameSpeedLimit(GetCustomSpeedLimit(laneId)); } public float GetLockFreeGameSpeedLimit(ushort segmentId, byte laneIndex, uint laneId, NetInfo.Lane laneInfo) { if (! Options.customSpeedLimitsEnabled || ! MayHaveCustomSpeedLimits(laneInfo)) { return laneInfo.m_speedLimit; } float speedLimit = 0; ushort?[] fastArray = Flags.laneSpeedLimitArray[segmentId]; if (fastArray != null && fastArray.Length > laneIndex && fastArray[laneIndex] != null) { speedLimit = ToGameSpeedLimit((ushort)fastArray[laneIndex]); } else { speedLimit = laneInfo.m_speedLimit; } return speedLimit; } /// /// Converts a custom speed limit to a game speed limit. /// /// /// public float ToGameSpeedLimit(ushort customSpeedLimit) { if (customSpeedLimit == 0) return MAX_SPEED; return (float)customSpeedLimit / 50f; } /// /// Converts a lane speed limit to a custom speed limit. /// /// /// public ushort LaneToCustomSpeedLimit(float laneSpeedLimit, bool roundToSignLimits=true) { laneSpeedLimit /= 2f; // 1 == 100 km/h if (! roundToSignLimits) { return (ushort)Mathf.Round(laneSpeedLimit * 100f); } // translate the floating point speed limit into our discrete version ushort speedLimit = 0; if (laneSpeedLimit < 0.15f) speedLimit = 10; else if (laneSpeedLimit < 1.35f) speedLimit = (ushort)((ushort)Mathf.Round(laneSpeedLimit * 10f) * 10u); return speedLimit; } /// /// Explicitly stores currently set speed limits for all segments of the specified NetInfo /// /// public void FixCurrentSpeedLimits(NetInfo info) { if (info == null) { #if DEBUG Log.Warning($"SpeedLimitManager.FixCurrentSpeedLimits: info is null!"); #endif return; } if (info.name == null) { #if DEBUG Log.Warning($"SpeedLimitManager.FixCurrentSpeedLimits: info.name is null!"); #endif return; } if (!customizableNetInfos.Contains(info)) return; for (uint laneId = 1; laneId < NetManager.MAX_LANE_COUNT; ++laneId) { if (!Services.NetService.IsLaneValid(laneId)) continue; ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; NetInfo laneInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; if (laneInfo.name != info.name && (!childNetInfoNamesByCustomizableNetInfoName.ContainsKey(info.name) || !childNetInfoNamesByCustomizableNetInfoName[info.name].Contains(laneInfo.name))) continue; Flags.setLaneSpeedLimit(laneId, GetCustomSpeedLimit(laneId)); } } /// /// Explicitly clear currently set speed limits for all segments of the specified NetInfo /// /// public void ClearCurrentSpeedLimits(NetInfo info) { if (info == null) { #if DEBUG Log.Warning($"SpeedLimitManager.ClearCurrentSpeedLimits: info is null!"); #endif return; } if (info.name == null) { #if DEBUG Log.Warning($"SpeedLimitManager.ClearCurrentSpeedLimits: info.name is null!"); #endif return; } if (!customizableNetInfos.Contains(info)) return; for (uint laneId = 1; laneId < NetManager.MAX_LANE_COUNT; ++laneId) { if (!Services.NetService.IsLaneValid(laneId)) continue; NetInfo laneInfo = Singleton.instance.m_segments.m_buffer[Singleton.instance.m_lanes.m_buffer[laneId].m_segment].Info; if (laneInfo.name != info.name && (!childNetInfoNamesByCustomizableNetInfoName.ContainsKey(info.name) || !childNetInfoNamesByCustomizableNetInfoName[info.name].Contains(laneInfo.name))) continue; Flags.removeLaneSpeedLimit(laneId); } } /// /// Determines the game default speed limit of the given NetInfo. /// /// the NetInfo of which the game default speed limit should be determined /// if true, custom speed limit are rounded to speed limits available as speed limit sign /// public ushort GetVanillaNetInfoSpeedLimit(NetInfo info, bool roundToSignLimits = true) { if (info == null) { #if DEBUG Log.Warning($"SpeedLimitManager.GetVanillaNetInfoSpeedLimit: info is null!"); #endif return 0; } if (info.m_netAI == null) { #if DEBUG Log.Warning($"SpeedLimitManager.GetVanillaNetInfoSpeedLimit: info.m_netAI is null!"); #endif return 0; } if (info.name == null) { #if DEBUG Log.Warning($"SpeedLimitManager.GetVanillaNetInfoSpeedLimit: info.name is null!"); #endif return 0; } /*if (! (info.m_netAI is RoadBaseAI)) return 0;*/ //string infoName = ((RoadBaseAI)info.m_netAI).m_info.name; string infoName = info.name; float[] vanillaSpeedLimits; if (!vanillaLaneSpeedLimitsByNetInfoName.TryGetValue(infoName, out vanillaSpeedLimits)) { return 0; } float? maxSpeedLimit = null; foreach (float speedLimit in vanillaSpeedLimits) { if (maxSpeedLimit == null || speedLimit > maxSpeedLimit) { maxSpeedLimit = speedLimit; } } if (maxSpeedLimit == null) return 0; return LaneToCustomSpeedLimit((float)maxSpeedLimit, roundToSignLimits); } /// /// Determines the custom speed limit of the given NetInfo. /// /// the NetInfo of which the custom speed limit should be determined /// public int GetCustomNetInfoSpeedLimitIndex(NetInfo info) { if (info == null) { #if DEBUG Log.Warning($"SpeedLimitManager.SetCustomNetInfoSpeedLimitIndex: info is null!"); #endif return -1; } if (info.name == null) { #if DEBUG Log.Warning($"SpeedLimitManager.SetCustomNetInfoSpeedLimitIndex: info.name is null!"); #endif return -1; } /*if (!(info.m_netAI is RoadBaseAI)) return -1;*/ //string infoName = ((RoadBaseAI)info.m_netAI).m_info.name; string infoName = info.name; int speedLimitIndex; if (!CustomLaneSpeedLimitIndexByNetInfoName.TryGetValue(infoName, out speedLimitIndex)) { return AvailableSpeedLimits.IndexOf(GetVanillaNetInfoSpeedLimit(info, true)); } return speedLimitIndex; } /// /// Sets the custom speed limit of the given NetInfo. /// /// the NetInfo for which the custom speed limit should be set /// public void SetCustomNetInfoSpeedLimitIndex(NetInfo info, int customSpeedLimitIndex) { if (info == null) { #if DEBUG Log.Warning($"SetCustomNetInfoSpeedLimitIndex: info is null!"); #endif return; } if (info.name == null) { #if DEBUG Log.Warning($"SetCustomNetInfoSpeedLimitIndex: info.name is null!"); #endif return; } /*if (!(info.m_netAI is RoadBaseAI)) return;*/ /*RoadBaseAI baseAI = (RoadBaseAI)info.m_netAI; string infoName = baseAI.m_info.name;*/ string infoName = info.name; CustomLaneSpeedLimitIndexByNetInfoName[infoName] = customSpeedLimitIndex; float gameSpeedLimit = ToGameSpeedLimit(AvailableSpeedLimits[customSpeedLimitIndex]); // save speed limit in all NetInfos Log._Debug($"Updating parent NetInfo {infoName}: Setting speed limit to {gameSpeedLimit}"); UpdateNetInfoGameSpeedLimit(info, gameSpeedLimit); List childNetInfoNames; if (childNetInfoNamesByCustomizableNetInfoName.TryGetValue(infoName, out childNetInfoNames)) { foreach (string childNetInfoName in childNetInfoNames) { NetInfo childNetInfo; if (NetInfoByName.TryGetValue(childNetInfoName, out childNetInfo)) { Log._Debug($"Updating child NetInfo {childNetInfoName}: Setting speed limit to {gameSpeedLimit}"); CustomLaneSpeedLimitIndexByNetInfoName[childNetInfoName] = customSpeedLimitIndex; UpdateNetInfoGameSpeedLimit(childNetInfo, gameSpeedLimit); } } } } private void UpdateNetInfoGameSpeedLimit(NetInfo info, float gameSpeedLimit) { if (info == null) { #if DEBUG Log.Warning($"SpeedLimitManager.UpdateNetInfoGameSpeedLimit: info is null!"); #endif return; } if (info.name == null) { #if DEBUG Log.Warning($"SpeedLimitManager.UpdateNetInfoGameSpeedLimit: info.name is null!"); #endif return; } if (info.m_lanes == null) { #if DEBUG Log.Warning($"SpeedLimitManager.UpdateNetInfoGameSpeedLimit: info.name is null!"); #endif return; } Log._Debug($"Updating speed limit of NetInfo {info.name} to {gameSpeedLimit}"); foreach (NetInfo.Lane lane in info.m_lanes) { // TODO refactor check if ((lane.m_vehicleType & VEHICLE_TYPES) != VehicleInfo.VehicleType.None) { lane.m_speedLimit = gameSpeedLimit; } } } /// /// Converts a vehicle's velocity to a custom speed. /// /// /// public ushort VehicleToCustomSpeed(float vehicleSpeed) { return LaneToCustomSpeedLimit(vehicleSpeed / 8f, false); } /// /// Sets the speed limit of a given lane. /// /// /// /// /// /// /// public bool SetSpeedLimit(ushort segmentId, uint laneIndex, NetInfo.Lane laneInfo, uint laneId, ushort speedLimit) { if (!MayHaveCustomSpeedLimits(laneInfo)) { return false; } if (!AvailableSpeedLimits.Contains(speedLimit)) { return false; } if (!Services.NetService.IsLaneValid(laneId)) { return false; } Flags.setLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit); return true; } /// /// Sets the speed limit of a given segment and lane direction. /// /// /// /// /// public bool SetSpeedLimit(ushort segmentId, NetInfo.Direction finalDir, ushort speedLimit) { if (!MayHaveCustomSpeedLimits(segmentId, ref Singleton.instance.m_segments.m_buffer[segmentId])) { return false; } if (!AvailableSpeedLimits.Contains(speedLimit)) { return false; } NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; if (segmentInfo == null) { #if DEBUG Log.Warning($"SpeedLimitManager.SetSpeedLimit: info is null!"); #endif return false; } if (segmentInfo.m_lanes == null) { #if DEBUG Log.Warning($"SpeedLimitManager.SetSpeedLimit: info.name is null!"); #endif return false; } uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; int laneIndex = 0; while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; NetInfo.Direction d = laneInfo.m_finalDirection; if (d != finalDir) goto nextIter; if (!MayHaveCustomSpeedLimits(laneInfo)) goto nextIter; #if DEBUG Log._Debug($"SpeedLimitManager: Setting speed limit of lane {curLaneId} to {speedLimit}"); #endif Flags.setLaneSpeedLimit(curLaneId, speedLimit); nextIter: curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; laneIndex++; } return true; } public List GetCustomizableNetInfos() { return customizableNetInfos; } public override void OnBeforeLoadData() { base.OnBeforeLoadData(); // determine vanilla speed limits and customizable NetInfos SteamHelper.DLC_BitMask dlcMask = SteamHelper.GetOwnedDLCMask(); int numLoaded = PrefabCollection.LoadedCount(); vanillaLaneSpeedLimitsByNetInfoName.Clear(); customizableNetInfos.Clear(); CustomLaneSpeedLimitIndexByNetInfoName.Clear(); childNetInfoNamesByCustomizableNetInfoName.Clear(); NetInfoByName.Clear(); List mainNetInfos = new List(); Log.Info($"SpeedLimitManager.OnBeforeLoadData: {numLoaded} NetInfos loaded."); for (uint i = 0; i < numLoaded; ++i) { NetInfo info = PrefabCollection.GetLoaded(i); if (info == null || info.m_netAI == null || !(info.m_netAI is RoadBaseAI || info.m_netAI is MetroTrackAI || info.m_netAI is TrainTrackBaseAI) || !(info.m_dlcRequired == 0 || (uint)(info.m_dlcRequired & dlcMask) != 0u)) { if (info == null) Log.Warning($"SpeedLimitManager.OnBeforeLoadData: NetInfo @ {i} is null!"); continue; } string infoName = info.name; if (infoName == null) { Log.Warning($"SpeedLimitManager.OnBeforeLoadData: NetInfo name @ {i} is null!"); continue; } if (!vanillaLaneSpeedLimitsByNetInfoName.ContainsKey(infoName)) { if (info.m_lanes == null) { Log.Warning($"SpeedLimitManager.OnBeforeLoadData: NetInfo lanes @ {i} is null!"); continue; } Log.Info($"Loaded road NetInfo: {infoName}"); NetInfoByName[infoName] = info; mainNetInfos.Add(info); float[] vanillaLaneSpeedLimits = new float[info.m_lanes.Length]; for (int k = 0; k < info.m_lanes.Length; ++k) { vanillaLaneSpeedLimits[k] = info.m_lanes[k].m_speedLimit; } vanillaLaneSpeedLimitsByNetInfoName[infoName] = vanillaLaneSpeedLimits; } } mainNetInfos.Sort(delegate(NetInfo a, NetInfo b) { bool aRoad = a.m_netAI is RoadBaseAI; bool bRoad = b.m_netAI is RoadBaseAI; if (aRoad != bRoad) { if (aRoad) return -1; else return 1; } bool aTrain = a.m_netAI is TrainTrackBaseAI; bool bTrain = b.m_netAI is TrainTrackBaseAI; if (aTrain != bTrain) { if (aTrain) return 1; else return -1; } bool aMetro = a.m_netAI is MetroTrackAI; bool bMetro = b.m_netAI is MetroTrackAI; if (aMetro != bMetro) { if (aMetro) return 1; else return -1; } if (aRoad && bRoad) { bool aHighway = ((RoadBaseAI)a.m_netAI).m_highwayRules; bool bHighway = ((RoadBaseAI)b.m_netAI).m_highwayRules; if (aHighway != bHighway) { if (aHighway) return 1; else return -1; } } int aNumVehicleLanes = 0; foreach (NetInfo.Lane lane in a.m_lanes) { if ((lane.m_laneType & LANE_TYPES) != NetInfo.LaneType.None) ++aNumVehicleLanes; } int bNumVehicleLanes = 0; foreach (NetInfo.Lane lane in b.m_lanes) { if ((lane.m_laneType & LANE_TYPES) != NetInfo.LaneType.None) ++bNumVehicleLanes; } int res = aNumVehicleLanes.CompareTo(bNumVehicleLanes); if (res == 0) { return a.name.CompareTo(b.name); } else { return res; } }); // identify parent NetInfos int x = 0; while (x < mainNetInfos.Count) { NetInfo info = mainNetInfos[x]; string infoName = info.name; // find parent with prefix name bool foundParent = false; for (int y = 0; y < mainNetInfos.Count; ++y) { NetInfo parentInfo = mainNetInfos[y]; if (info.m_placementStyle == ItemClass.Placement.Procedural && !infoName.Equals(parentInfo.name) && infoName.StartsWith(parentInfo.name)) { Log.Info($"Identified child NetInfo {infoName} of parent {parentInfo.name}"); List childNetInfoNames; if (!childNetInfoNamesByCustomizableNetInfoName.TryGetValue(parentInfo.name, out childNetInfoNames)) { childNetInfoNamesByCustomizableNetInfoName[parentInfo.name] = childNetInfoNames = new List(); } childNetInfoNames.Add(info.name); NetInfoByName[infoName] = info; foundParent = true; break; } } if (foundParent) { mainNetInfos.RemoveAt(x); } else { ++x; } } customizableNetInfos = mainNetInfos; } protected override void HandleInvalidSegment(SegmentGeometry geometry) { NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[geometry.SegmentId].Info; uint curLaneId = Singleton.instance.m_segments.m_buffer[geometry.SegmentId].m_lanes; int laneIndex = 0; while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; ushort? setSpeedLimit = Flags.getLaneSpeedLimit(curLaneId); Flags.setLaneSpeedLimit(curLaneId, null); curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; laneIndex++; } } protected override void HandleValidSegment(SegmentGeometry geometry) { } public bool LoadData(List data) { bool success = true; Log.Info($"Loading lane speed limit data. {data.Count} elements"); foreach (Configuration.LaneSpeedLimit laneSpeedLimit in data) { try { if (!Services.NetService.IsLaneValid(laneSpeedLimit.laneId)) { Log._Debug($"SpeedLimitManager.LoadData: Skipping lane {laneSpeedLimit.laneId}: Lane is invalid"); continue; } ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneSpeedLimit.laneId].m_segment; NetInfo info = Singleton.instance.m_segments.m_buffer[segmentId].Info; int customSpeedLimitIndex = GetCustomNetInfoSpeedLimitIndex(info); Log._Debug($"SpeedLimitManager.LoadData: Handling lane {laneSpeedLimit.laneId}: Custom speed limit index of segment {segmentId} info ({info}, name={info?.name}, lanes={info?.m_lanes} is {customSpeedLimitIndex}"); if (customSpeedLimitIndex < 0 || AvailableSpeedLimits[customSpeedLimitIndex] != laneSpeedLimit.speedLimit) { // lane speed limit differs from default speed limit Log._Debug($"SpeedLimitManager.LoadData: Loading lane speed limit: lane {laneSpeedLimit.laneId} = {laneSpeedLimit.speedLimit}"); Flags.setLaneSpeedLimit(laneSpeedLimit.laneId, laneSpeedLimit.speedLimit); } else { Log._Debug($"SpeedLimitManager.LoadData: Skipping lane speed limit of lane {laneSpeedLimit.laneId} ({laneSpeedLimit.speedLimit})"); } } catch (Exception e) { // ignore, as it's probably corrupt save data. it'll be culled on next save Log.Warning("SpeedLimitManager.LoadData: Error loading speed limits: " + e.ToString()); success = false; } } return success; } List ICustomDataManager>.SaveData(ref bool success) { List ret = new List(); foreach (KeyValuePair e in Flags.getAllLaneSpeedLimits()) { try { Configuration.LaneSpeedLimit laneSpeedLimit = new Configuration.LaneSpeedLimit(e.Key, e.Value); Log._Debug($"Saving speed limit of lane {laneSpeedLimit.laneId}: {laneSpeedLimit.speedLimit}"); ret.Add(laneSpeedLimit); } catch (Exception ex) { Log.Error($"Exception occurred while saving lane speed limit @ {e.Key}: {ex.ToString()}"); success = false; } } return ret; } public bool LoadData(Dictionary data) { bool success = true; Log.Info($"Loading custom default speed limit data. {data.Count} elements"); foreach (KeyValuePair e in data) { NetInfo netInfo = null; if (!NetInfoByName.TryGetValue(e.Key, out netInfo)) continue; ushort customSpeedLimit = LaneToCustomSpeedLimit(e.Value, true); int customSpeedLimitIndex = AvailableSpeedLimits.IndexOf(customSpeedLimit); if (customSpeedLimitIndex >= 0) { SetCustomNetInfoSpeedLimitIndex(netInfo, customSpeedLimitIndex); } } return success; } Dictionary ICustomDataManager>.SaveData(ref bool success) { Dictionary ret = new Dictionary(); foreach (KeyValuePair e in CustomLaneSpeedLimitIndexByNetInfoName) { try { ushort customSpeedLimit = AvailableSpeedLimits[e.Value]; float gameSpeedLimit = ToGameSpeedLimit(customSpeedLimit); ret.Add(e.Key, gameSpeedLimit); } catch (Exception ex) { Log.Error($"Exception occurred while saving custom default speed limits @ {e.Key}: {ex.ToString()}"); success = false; } } return ret; } #if DEBUG /*public Dictionary GetDefaultSpeedLimits() { Dictionary ret = new Dictionary(); int numLoaded = PrefabCollection.LoadedCount(); for (uint i = 0; i < numLoaded; ++i) { NetInfo info = PrefabCollection.GetLoaded(i); ushort defaultSpeedLimit = GetAverageDefaultCustomSpeedLimit(info, NetInfo.Direction.Forward); ret.Add(info, defaultSpeedLimit); Log._Debug($"Loaded NetInfo: {info.name}, placementStyle={info.m_placementStyle}, availableIn={info.m_availableIn}, thumbnail={info.m_Thumbnail} connectionClass.service: {info.GetConnectionClass().m_service.ToString()}, connectionClass.subService: {info.GetConnectionClass().m_subService.ToString()}, avg. default speed limit: {defaultSpeedLimit}"); } return ret; }*/ #endif } } ================================================ FILE: TLM/TLM/Manager/Impl/TrafficLightManager.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Geometry; using TrafficManager.Geometry.Impl; using TrafficManager.State; using TrafficManager.TrafficLight; using TrafficManager.Util; namespace TrafficManager.Manager.Impl { /// /// Manages traffic light toggling /// public class TrafficLightManager : AbstractCustomManager, ICustomDataManager>, ICustomDataManager, ITrafficLightManager { public static readonly TrafficLightManager Instance = new TrafficLightManager(); protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"- Not implemented -"); // TODO implement } public bool SetTrafficLight(ushort nodeId, bool flag, ref NetNode node) { UnableReason reason; return SetTrafficLight(nodeId, flag, ref node, out reason); } public bool SetTrafficLight(ushort nodeId, bool flag, ref NetNode node, out UnableReason reason) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == nodeId) Log._Debug($"TrafficLightManager.SetTrafficLight: called for node {nodeId}, flag={flag}"); #endif if (! IsTrafficLightToggleable(nodeId, flag, ref node, out reason)) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == nodeId) Log._Debug($"TrafficLightManager.SetTrafficLight: Traffic light @ {nodeId} is not toggleable"); #endif if (reason != UnableReason.HasTimedLight || !flag) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == nodeId) Log._Debug($"TrafficLightManager.SetTrafficLight: ... but has timed light and we want to enable it"); #endif return false; } } NetNode.Flags flags = node.m_flags | NetNode.Flags.CustomTrafficLights; if ((bool)flag) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == nodeId) Log._Debug($"Adding traffic light @ node {nodeId}"); #endif flags |= NetNode.Flags.TrafficLights; TrafficPriorityManager.Instance.RemovePrioritySignsFromNode(nodeId); } else { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == nodeId) Log._Debug($"Removing traffic light @ node {nodeId}"); #endif flags &= ~NetNode.Flags.TrafficLights; } #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == nodeId) Log._Debug($"TrafficLightManager.SetTrafficLight: Setting traffic light at node {nodeId} -- flags={flags}"); #endif node.m_flags = flags; Constants.ManagerFactory.GeometryManager.MarkAsUpdated(NodeGeometry.Get(nodeId), true); return true; } public bool AddTrafficLight(ushort nodeId, ref NetNode node) { UnableReason reason; return AddTrafficLight(nodeId, ref node, out reason); } public bool AddTrafficLight(ushort nodeId, ref NetNode node, out UnableReason reason) { TrafficPriorityManager.Instance.RemovePrioritySignsFromNode(nodeId); return SetTrafficLight(nodeId, true, ref node, out reason); } public bool RemoveTrafficLight(ushort nodeId, ref NetNode node) { UnableReason reason; return RemoveTrafficLight(nodeId, ref node, out reason); } public bool RemoveTrafficLight(ushort nodeId, ref NetNode node, out UnableReason reason) { return SetTrafficLight(nodeId, false, ref node, out reason); } public bool ToggleTrafficLight(ushort nodeId, ref NetNode node) { return SetTrafficLight(nodeId, !HasTrafficLight(nodeId, ref node), ref node); } public bool ToggleTrafficLight(ushort nodeId, ref NetNode node, out UnableReason reason) { return SetTrafficLight(nodeId, !HasTrafficLight(nodeId, ref node), ref node, out reason); } public bool IsTrafficLightToggleable(ushort nodeId, bool flag, ref NetNode node, out UnableReason reason) { if (!flag && TrafficLightSimulationManager.Instance.HasTimedSimulation(nodeId)) { reason = UnableReason.HasTimedLight; #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == nodeId) Log._Debug($"Cannot toggle traffic lights at node {nodeId}: Node has a timed traffic light"); #endif return false; } if (flag && !LogicUtil.CheckFlags((uint)node.m_flags, (uint)(NetNode.Flags.Created | NetNode.Flags.Deleted | NetNode.Flags.Junction), (uint)(NetNode.Flags.Created | NetNode.Flags.Junction))) { reason = UnableReason.NoJunction; #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == nodeId) Log._Debug($"Cannot toggle traffic lights at node {nodeId}: Node is not a junction"); #endif return false; } if (!flag && LogicUtil.CheckFlags((uint)node.m_flags, (uint)(NetNode.Flags.LevelCrossing), (uint)(NetNode.Flags.LevelCrossing))) { reason = UnableReason.IsLevelCrossing; #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == nodeId) Log._Debug($"Cannot toggle traffic lights at node {nodeId}: Node is a level crossing"); #endif return false; } int numRoads = 0; int numTrainTracks = 0; int numMonorailTracks = 0; int numPedSegments = 0; Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { NetInfo info = segment.Info; if (info.m_class.m_service == ItemClass.Service.Road) { ++numRoads; } else if ((info.m_vehicleTypes & VehicleInfo.VehicleType.Train) != VehicleInfo.VehicleType.None) { ++numTrainTracks; } else if ((info.m_vehicleTypes & VehicleInfo.VehicleType.Monorail) != VehicleInfo.VehicleType.None) { ++numMonorailTracks; } if (info.m_hasPedestrianLanes) { ++numPedSegments; } return true; }); if (numRoads >= 2 || numTrainTracks >= 2 || numMonorailTracks >= 2 || numPedSegments != 0) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == nodeId) Log._Debug($"Can toggle traffic lights at node {nodeId}: numRoads={numRoads} numTrainTracks={numTrainTracks} numMonorailTracks={numMonorailTracks} numPedSegments={numPedSegments}"); #endif reason = UnableReason.None; return true; } #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == nodeId) Log._Debug($"Cannot toggle traffic lights at node {nodeId}: Insufficient segments. numRoads={numRoads} numTrainTracks={numTrainTracks} numMonorailTracks={numMonorailTracks} numPedSegments={numPedSegments}"); #endif reason = UnableReason.InsufficientSegments; return false; } public bool IsTrafficLightEnablable(ushort nodeId, ref NetNode node, out UnableReason reason) { return IsTrafficLightToggleable(nodeId, true, ref node, out reason); } public bool HasTrafficLight(ushort nodeId, ref NetNode node) { return LogicUtil.CheckFlags((uint)node.m_flags, (uint)(NetNode.Flags.Created | NetNode.Flags.Deleted | NetNode.Flags.TrafficLights), (uint)(NetNode.Flags.Created | NetNode.Flags.TrafficLights)); } [Obsolete] public bool LoadData(string data) { bool success = true; var trafficLightDefs = data.Split(','); Log.Info($"Loading junction traffic light data (old method)"); foreach (var split in trafficLightDefs.Select(def => def.Split(':')).Where(split => split.Length > 1)) { try { Log._Debug($"Traffic light split data: {split[0]} , {split[1]}"); var nodeId = Convert.ToUInt16(split[0]); uint flag = Convert.ToUInt16(split[1]); if (!Services.NetService.IsNodeValid(nodeId)) continue; Flags.setNodeTrafficLight(nodeId, flag > 0); } catch (Exception e) { // ignore as it's probably bad save data. Log.Error($"Error setting the NodeTrafficLights: " + e.ToString()); success = false; } } return success; } [Obsolete] public string SaveData(ref bool success) { return null; } public bool LoadData(List data) { bool success = true; Log.Info($"Loading toggled traffic lights (new method)"); foreach (Configuration.NodeTrafficLight nodeLight in data) { try { if (!Services.NetService.IsNodeValid(nodeLight.nodeId)) continue; Log._Debug($"Setting traffic light @ {nodeLight.nodeId} to {nodeLight.trafficLight}"); Services.NetService.ProcessNode(nodeLight.nodeId, delegate (ushort nodeId, ref NetNode node) { SetTrafficLight(nodeLight.nodeId, nodeLight.trafficLight, ref node); return true; }); //Flags.setNodeTrafficLight(nodeLight.nodeId, nodeLight.trafficLight); } catch (Exception e) { // ignore as it's probably bad save data. Log.Error($"Error setting the NodeTrafficLights @ {nodeLight.nodeId}: " + e.ToString()); success = false; } } return success; } List ICustomDataManager>.SaveData(ref bool success) { return null; /*List ret = new List(); for (uint nodeId = 0; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { try { if (!Flags.mayHaveTrafficLight(nodeId)) continue; bool? hasTrafficLight = Flags.isNodeTrafficLight(nodeId); if (hasTrafficLight == null) continue; if ((bool)hasTrafficLight) { Log._Debug($"Saving that node {nodeId} has a traffic light"); } else { Log._Debug($"Saving that node {nodeId} does not have a traffic light"); } ret.Add(new Configuration.NodeTrafficLight(nodeId, (bool)hasTrafficLight)); } catch (Exception e) { Log.Error($"Exception occurred while saving node traffic light @ {nodeId}: {e.ToString()}"); success = false; } } return ret;*/ } } } ================================================ FILE: TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs ================================================ using System; using ColossalFramework; using TrafficManager.Geometry; using System.Collections.Generic; using TrafficManager.State; using TrafficManager.Custom.AI; using TrafficManager.Util; using TrafficManager.TrafficLight; using TrafficManager.Traffic; using System.Linq; using CSUtil.Commons; using TrafficManager.TrafficLight.Impl; using TrafficManager.Geometry.Impl; using CSUtil.Commons.Benchmark; using TrafficManager.TrafficLight.Data; namespace TrafficManager.Manager.Impl { public class TrafficLightSimulationManager : AbstractGeometryObservingManager, ICustomDataManager>, ITrafficLightSimulationManager { public static readonly TrafficLightSimulationManager Instance = new TrafficLightSimulationManager(); public const int SIM_MOD = 64; /// /// For each node id: traffic light simulation assigned to the node /// public TrafficLightSimulation[] TrafficLightSimulations; //public Dictionary TrafficLightSimulations = new Dictionary(); private TrafficLightSimulationManager() { TrafficLightSimulations = new TrafficLightSimulation[NetManager.MAX_NODE_COUNT]; for (int i = 0; i < TrafficLightSimulations.Length; ++i) { TrafficLightSimulations[i] = new TrafficLightSimulation((ushort)i); } } protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"Traffic light simulations:"); for (int i = 0; i < TrafficLightSimulations.Length; ++i) { if (! TrafficLightSimulations[i].HasSimulation()) { continue; } Log._Debug($"Simulation {i}: {TrafficLightSimulations[i]}"); } } public void SimulationStep() { int frame = (int)(Services.SimulationService.CurrentFrameIndex & (SIM_MOD - 1)); int minIndex = frame * (NetManager.MAX_NODE_COUNT / SIM_MOD); int maxIndex = (frame + 1) * (NetManager.MAX_NODE_COUNT / SIM_MOD) - 1; ushort failedNodeId = 0; try { for (int nodeId = minIndex; nodeId <= maxIndex; ++nodeId) { failedNodeId = (ushort)nodeId; TrafficLightSimulations[nodeId].SimulationStep(); } failedNodeId = 0; } catch (Exception ex) { Log.Error($"Error occured while simulating traffic light @ node {failedNodeId}: {ex.ToString()}"); if (failedNodeId != 0) { RemoveNodeFromSimulation((ushort)failedNodeId); } } } /// /// Adds a manual traffic light simulation to the node with the given id /// /// public bool SetUpManualTrafficLight(ushort nodeId) { return TrafficLightSimulations[nodeId].SetUpManualTrafficLight(); } /// /// Adds a timed traffic light simulation to the node with the given id /// /// public bool SetUpTimedTrafficLight(ushort nodeId, IList nodeGroup) { // TODO improve signature if (! TrafficLightSimulations[nodeId].SetUpTimedTrafficLight(nodeGroup)) { return false; } return true; } /// /// Destroys the traffic light and removes it /// /// /// public void RemoveNodeFromSimulation(ushort nodeId, bool destroyGroup, bool removeTrafficLight) { #if DEBUG Log._Debug($"TrafficLightSimulationManager.RemoveNodeFromSimulation({nodeId}, {destroyGroup}, {removeTrafficLight}) called."); #endif if (! TrafficLightSimulations[nodeId].HasSimulation()) { return; } TrafficLightManager tlm = TrafficLightManager.Instance; if (TrafficLightSimulations[nodeId].IsTimedLight()) { // remove/destroy all timed traffic lights in group List oldNodeGroup = new List(TrafficLightSimulations[nodeId].TimedLight.NodeGroup); foreach (var timedNodeId in oldNodeGroup) { if (! TrafficLightSimulations[timedNodeId].HasSimulation()) { continue; } if (destroyGroup || timedNodeId == nodeId) { //Log._Debug($"Slave: Removing simulation @ node {timedNodeId}"); //TrafficLightSimulations[timedNodeId].Destroy(); RemoveNodeFromSimulation(timedNodeId); if (removeTrafficLight) { Constants.ServiceFactory.NetService.ProcessNode(timedNodeId, delegate (ushort nId, ref NetNode node) { tlm.RemoveTrafficLight(timedNodeId, ref node); return true; }); } } else { if (TrafficLightSimulations[timedNodeId].IsTimedLight()) { TrafficLightSimulations[timedNodeId].TimedLight.RemoveNodeFromGroup(nodeId); } } } } //Flags.setNodeTrafficLight(nodeId, false); //sim.DestroyTimedTrafficLight(); //TrafficLightSimulations[nodeId].DestroyManualTrafficLight(); RemoveNodeFromSimulation(nodeId); if (removeTrafficLight) { Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { tlm.RemoveTrafficLight(nodeId, ref node); return true; }); } } public bool HasSimulation(ushort nodeId) { return TrafficLightSimulations[nodeId].HasSimulation(); } public bool HasManualSimulation(ushort nodeId) { return TrafficLightSimulations[nodeId].IsManualLight(); } public bool HasTimedSimulation(ushort nodeId) { return TrafficLightSimulations[nodeId].IsTimedLight(); } public bool HasActiveTimedSimulation(ushort nodeId) { return TrafficLightSimulations[nodeId].IsTimedLightRunning(); } public bool HasActiveSimulation(ushort nodeId) { return TrafficLightSimulations[nodeId].IsSimulationRunning(); } private void RemoveNodeFromSimulation(ushort nodeId) { #if DEBUG Log._Debug($"TrafficLightSimulationManager.RemoveNodeFromSimulation({nodeId}) called."); #endif TrafficLightSimulations[nodeId].Destroy(); } public override void OnLevelUnloading() { base.OnLevelUnloading(); for (uint nodeId = 0; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { TrafficLightSimulations[nodeId].Destroy(); } } protected override void HandleInvalidNode(NodeGeometry geometry) { RemoveNodeFromSimulation(geometry.NodeId, false, true); } protected override void HandleValidNode(NodeGeometry geometry) { if (!TrafficLightSimulations[geometry.NodeId].HasSimulation()) { //Log._Debug($"TrafficLightSimulationManager.HandleValidNode({geometry.NodeId}): Node is not controlled by a custom traffic light simulation."); return; } if (! Flags.mayHaveTrafficLight(geometry.NodeId)) { Log._Debug($"TrafficLightSimulationManager.HandleValidNode({geometry.NodeId}): Node must not have a traffic light: Removing traffic light simulation."); RemoveNodeFromSimulation(geometry.NodeId, false, true); return; } foreach (SegmentEndGeometry end in geometry.SegmentEndGeometries) { if (end == null) continue; Log._Debug($"TrafficLightSimulationManager.HandleValidNode({geometry.NodeId}): Adding live traffic lights to segment {end.SegmentId}"); // housekeep timed light CustomSegmentLightsManager.Instance.GetSegmentLights(end.SegmentId, end.StartNode).Housekeeping(true, true); } // ensure there is a physical traffic light Constants.ServiceFactory.NetService.ProcessNode(geometry.NodeId, delegate (ushort nodeId, ref NetNode node) { Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(geometry.NodeId, ref node); return true; }); TrafficLightSimulations[geometry.NodeId].Update(); } public bool LoadData(List data) { bool success = true; Log.Info($"Loading {data.Count} timed traffic lights (new method)"); TrafficLightManager tlm = TrafficLightManager.Instance; HashSet nodesWithSimulation = new HashSet(); foreach (Configuration.TimedTrafficLights cnfTimedLights in data) { nodesWithSimulation.Add(cnfTimedLights.nodeId); } Dictionary masterNodeIdBySlaveNodeId = new Dictionary(); Dictionary> nodeGroupByMasterNodeId = new Dictionary>(); foreach (Configuration.TimedTrafficLights cnfTimedLights in data) { try { // TODO most of this should not be necessary at all if the classes around TimedTrafficLights class were properly designed List currentNodeGroup = cnfTimedLights.nodeGroup.Distinct().ToList(); // enforce uniqueness of node ids if (!currentNodeGroup.Contains(cnfTimedLights.nodeId)) currentNodeGroup.Add(cnfTimedLights.nodeId); // remove any nodes that are not configured to have a simulation currentNodeGroup = new List(currentNodeGroup.Intersect(nodesWithSimulation)); // remove invalid nodes from the group; find if any of the nodes in the group is already a master node ushort masterNodeId = 0; int foundMasterNodes = 0; for (int i = 0; i < currentNodeGroup.Count;) { ushort nodeId = currentNodeGroup[i]; if (!Services.NetService.IsNodeValid(currentNodeGroup[i])) { currentNodeGroup.RemoveAt(i); continue; } else if (nodeGroupByMasterNodeId.ContainsKey(nodeId)) { // this is a known master node if (foundMasterNodes > 0) { // we already found another master node. ignore this node. currentNodeGroup.RemoveAt(i); continue; } // we found the first master node masterNodeId = nodeId; ++foundMasterNodes; } ++i; } if (masterNodeId == 0) { // no master node defined yet, set the first node as a master node masterNodeId = currentNodeGroup[0]; } // ensure the master node is the first node in the list (TimedTrafficLights depends on this at the moment...) currentNodeGroup.Remove(masterNodeId); currentNodeGroup.Insert(0, masterNodeId); // update the saved node group and master-slave info nodeGroupByMasterNodeId[masterNodeId] = currentNodeGroup; foreach (ushort nodeId in currentNodeGroup) { masterNodeIdBySlaveNodeId[nodeId] = masterNodeId; } } catch (Exception e) { Log.Warning($"Error building timed traffic light group for TimedNode {cnfTimedLights.nodeId} (NodeGroup: {string.Join(", ", cnfTimedLights.nodeGroup.Select(x => x.ToString()).ToArray())}): " + e.ToString()); success = false; } } foreach (Configuration.TimedTrafficLights cnfTimedLights in data) { try { if (!masterNodeIdBySlaveNodeId.ContainsKey(cnfTimedLights.nodeId)) continue; ushort masterNodeId = masterNodeIdBySlaveNodeId[cnfTimedLights.nodeId]; List nodeGroup = nodeGroupByMasterNodeId[masterNodeId]; Log._Debug($"Adding timed light at node {cnfTimedLights.nodeId}. NodeGroup: {string.Join(", ", nodeGroup.Select(x => x.ToString()).ToArray())}"); SetUpTimedTrafficLight(cnfTimedLights.nodeId, nodeGroup); int j = 0; foreach (Configuration.TimedTrafficLightsStep cnfTimedStep in cnfTimedLights.timedSteps) { Log._Debug($"Loading timed step {j} at node {cnfTimedLights.nodeId}"); ITimedTrafficLightsStep step = TrafficLightSimulations[cnfTimedLights.nodeId].TimedLight.AddStep(cnfTimedStep.minTime, cnfTimedStep.maxTime, (TrafficLight.StepChangeMetric)cnfTimedStep.changeMetric, cnfTimedStep.waitFlowBalance); foreach (KeyValuePair e in cnfTimedStep.segmentLights) { if (!Services.NetService.IsSegmentValid(e.Key)) continue; e.Value.nodeId = cnfTimedLights.nodeId; Log._Debug($"Loading timed step {j}, segment {e.Key} at node {cnfTimedLights.nodeId}"); ICustomSegmentLights lights = null; if (!step.CustomSegmentLights.TryGetValue(e.Key, out lights)) { Log._Debug($"No segment lights found at timed step {j} for segment {e.Key}, node {cnfTimedLights.nodeId}"); continue; } Configuration.CustomSegmentLights cnfLights = e.Value; Log._Debug($"Loading pedestrian light @ seg. {e.Key}, step {j}: {cnfLights.pedestrianLightState} {cnfLights.manualPedestrianMode}"); lights.ManualPedestrianMode = cnfLights.manualPedestrianMode; lights.PedestrianLightState = cnfLights.pedestrianLightState; bool first = true; // v1.10.2 transitional code foreach (KeyValuePair e2 in cnfLights.customLights) { Log._Debug($"Loading timed step {j}, segment {e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}"); ICustomSegmentLight light = null; if (!lights.CustomLights.TryGetValue(e2.Key, out light)) { Log._Debug($"No segment light found for timed step {j}, segment {e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}"); // v1.10.2 transitional code START if (first) { first = false; if (!lights.CustomLights.TryGetValue(CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE, out light)) { Log._Debug($"No segment light found for timed step {j}, segment {e.Key}, DEFAULT vehicleType {CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE} at node {cnfTimedLights.nodeId}"); continue; } } else { // v1.10.2 transitional code END continue; // v1.10.2 transitional code START } // v1.10.2 transitional code END } Configuration.CustomSegmentLight cnfLight = e2.Value; light.InternalCurrentMode = (TrafficLight.LightMode)cnfLight.currentMode; // TODO improve & remove light.SetStates(cnfLight.mainLight, cnfLight.leftLight, cnfLight.rightLight, false); } } ++j; } } catch (Exception e) { // ignore, as it's probably corrupt save data. it'll be culled on next save Log.Warning("Error loading data from TimedNode (new method): " + e.ToString()); success = false; } } foreach (Configuration.TimedTrafficLights cnfTimedLights in data) { try { var timedNode = TrafficLightSimulations[cnfTimedLights.nodeId].TimedLight; timedNode.Housekeeping(); if (cnfTimedLights.started) { timedNode.Start(cnfTimedLights.currentStep); } } catch (Exception e) { Log.Warning($"Error starting timed light @ {cnfTimedLights.nodeId}: " + e.ToString()); success = false; } } return success; } public List SaveData(ref bool success) { List ret = new List(); for (uint nodeId = 0; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { try { if (! TrafficLightSimulations[nodeId].IsTimedLight()) { continue; } Log._Debug($"Going to save timed light at node {nodeId}."); var timedNode = TrafficLightSimulations[nodeId].TimedLight; timedNode.OnGeometryUpdate(); Configuration.TimedTrafficLights cnfTimedLights = new Configuration.TimedTrafficLights(); ret.Add(cnfTimedLights); cnfTimedLights.nodeId = timedNode.NodeId; cnfTimedLights.nodeGroup = new List(timedNode.NodeGroup); cnfTimedLights.started = timedNode.IsStarted(); int stepIndex = timedNode.CurrentStep; if (timedNode.IsStarted() && timedNode.GetStep(timedNode.CurrentStep).IsInEndTransition()) { // if in end transition save the next step stepIndex = (stepIndex + 1) % timedNode.NumSteps(); } cnfTimedLights.currentStep = stepIndex; cnfTimedLights.timedSteps = new List(); for (var j = 0; j < timedNode.NumSteps(); j++) { Log._Debug($"Saving timed light step {j} at node {nodeId}."); ITimedTrafficLightsStep timedStep = timedNode.GetStep(j); Configuration.TimedTrafficLightsStep cnfTimedStep = new Configuration.TimedTrafficLightsStep(); cnfTimedLights.timedSteps.Add(cnfTimedStep); cnfTimedStep.minTime = timedStep.MinTime; cnfTimedStep.maxTime = timedStep.MaxTime; cnfTimedStep.changeMetric = (int)timedStep.ChangeMetric; cnfTimedStep.waitFlowBalance = timedStep.WaitFlowBalance; cnfTimedStep.segmentLights = new Dictionary(); foreach (KeyValuePair e in timedStep.CustomSegmentLights) { Log._Debug($"Saving timed light step {j}, segment {e.Key} at node {nodeId}."); ICustomSegmentLights segLights = e.Value; Configuration.CustomSegmentLights cnfSegLights = new Configuration.CustomSegmentLights(); ushort lightsNodeId = segLights.NodeId; if (lightsNodeId == 0 || lightsNodeId != timedNode.NodeId) { Log.Warning($"Inconsistency detected: Timed traffic light @ node {timedNode.NodeId} contains custom traffic lights for the invalid segment ({segLights.SegmentId}) at step {j}: nId={lightsNodeId}"); continue; } cnfSegLights.nodeId = lightsNodeId; // TODO not needed cnfSegLights.segmentId = segLights.SegmentId; // TODO not needed cnfSegLights.customLights = new Dictionary(); cnfSegLights.pedestrianLightState = segLights.PedestrianLightState; cnfSegLights.manualPedestrianMode = segLights.ManualPedestrianMode; cnfTimedStep.segmentLights.Add(e.Key, cnfSegLights); Log._Debug($"Saving pedestrian light @ seg. {e.Key}, step {j}: {cnfSegLights.pedestrianLightState} {cnfSegLights.manualPedestrianMode}"); foreach (KeyValuePair e2 in segLights.CustomLights) { Log._Debug($"Saving timed light step {j}, segment {e.Key}, vehicleType {e2.Key} at node {nodeId}."); ICustomSegmentLight segLight = e2.Value; Configuration.CustomSegmentLight cnfSegLight = new Configuration.CustomSegmentLight(); cnfSegLights.customLights.Add(e2.Key, cnfSegLight); cnfSegLight.nodeId = lightsNodeId; // TODO not needed cnfSegLight.segmentId = segLights.SegmentId; // TODO not needed cnfSegLight.currentMode = (int)segLight.CurrentMode; cnfSegLight.leftLight = segLight.LightLeft; cnfSegLight.mainLight = segLight.LightMain; cnfSegLight.rightLight = segLight.LightRight; } } } } catch (Exception e) { Log.Error($"Exception occurred while saving timed traffic light @ {nodeId}: {e.ToString()}"); success = false; } } return ret; } } } ================================================ FILE: TLM/TLM/Manager/Impl/TrafficMeasurementManager.cs ================================================ using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Text; using TrafficManager.Geometry; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Util; using UnityEngine; namespace TrafficManager.Manager.Impl { public class TrafficMeasurementManager : AbstractCustomManager, ITrafficMeasurementManager { public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car; public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; public static readonly TrafficMeasurementManager Instance = new TrafficMeasurementManager(); public const ushort REF_REL_SPEED_PERCENT_DENOMINATOR = 100; public const ushort REF_REL_SPEED = 10000; public struct LaneTrafficData { /// /// Number of seen vehicles since last speed measurement /// public ushort trafficBuffer; /// /// Number of seen vehicles before last speed measurement /// public ushort lastTrafficBuffer; /// /// All-time max. traffic buffer /// public ushort maxTrafficBuffer; /// /// Accumulated speeds since last traffic measurement /// public uint accumulatedSpeeds; /// /// Current lane mean speed, per ten thousands /// public ushort meanSpeed; public override string ToString() { return $"[LaneTrafficData\n" + "\t" + $"trafficBuffer = {trafficBuffer}\n" + "\t" + $"lastTrafficBuffer = {lastTrafficBuffer}\n" + "\t" + $"maxTrafficBuffer = {maxTrafficBuffer}\n" + "\t" + $"trafficBuffer = {trafficBuffer}\n" + "\t" + $"accumulatedSpeeds = {accumulatedSpeeds}\n" + "\t" + $"meanSpeed = {meanSpeed}\n" + "LaneTrafficData]"; } } public struct SegmentDirTrafficData { public ushort meanSpeed; public override string ToString() { return $"[SegmentDirTrafficData\n" + "\t" + $"meanSpeed = {meanSpeed}\n" + "SegmentDirTrafficData]"; } } /// /// Traffic data per segment and lane /// private LaneTrafficData[][] laneTrafficData; /// /// Traffic data per segment and traffic direction /// internal SegmentDirTrafficData[] segmentDirTrafficData; private uint[] meanSpeeds = { 0, 0 }; private int[] meanSpeedLanes = { 0, 0 }; private TrafficMeasurementManager() { laneTrafficData = new LaneTrafficData[NetManager.MAX_SEGMENT_COUNT][]; segmentDirTrafficData = new SegmentDirTrafficData[NetManager.MAX_SEGMENT_COUNT * 2]; for (int i = 0; i < segmentDirTrafficData.Length; ++i) { segmentDirTrafficData[i].meanSpeed = REF_REL_SPEED; } ResetTrafficStats(); } protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"Lane traffic data:"); if (laneTrafficData == null) { Log._Debug($"\t"); } else { for (int i = 0; i < laneTrafficData.Length; ++i) { if (laneTrafficData[i] == null) { continue; } Log._Debug($"\tSegment {i}:"); for (int k = 0; k < laneTrafficData[i].Length; ++k) { Log._Debug($"\t\tLane {k}: {laneTrafficData[i][k]}"); } } } Log._Debug($"Segment direction traffic data:"); if (segmentDirTrafficData == null) { Log._Debug($"\t"); } else { for (int i = 0; i < segmentDirTrafficData.Length; ++i) { Log._Debug($"\tIndex {i}: {segmentDirTrafficData[i]}"); } } } public ushort CalcLaneRelativeMeanSpeed(ushort segmentId, byte laneIndex, uint laneId, NetInfo.Lane laneInfo) { if (laneTrafficData[segmentId] == null || laneIndex >= laneTrafficData[segmentId].Length) { return REF_REL_SPEED; } ushort currentBuf = laneTrafficData[segmentId][laneIndex].trafficBuffer; ushort curRelSpeed = REF_REL_SPEED; // we use integer division here because it's faster if (currentBuf > 0) { uint laneVehicleSpeedLimit = Math.Min(3u * 8u, (uint)((Options.customSpeedLimitsEnabled ? SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(segmentId, laneIndex, laneId, laneInfo) : laneInfo.m_speedLimit) * 8f)); if (laneVehicleSpeedLimit <= 0) { // fallback: custom lanes may not have valid values set for speed limit laneVehicleSpeedLimit = 1; } curRelSpeed = (ushort)Math.Min((uint)REF_REL_SPEED, ((laneTrafficData[segmentId][laneIndex].accumulatedSpeeds * (uint)REF_REL_SPEED) / currentBuf) / laneVehicleSpeedLimit); // 0 .. 10000, m_speedLimit of highway is 2, actual max. vehicle speed on highway is 16, that's why we use x*8 == x<<3 (don't ask why CO uses different units for velocity) if (curRelSpeed >= (uint)GlobalConfig.Instance.DynamicLaneSelection.VolumeMeasurementRelSpeedThreshold * (uint)REF_REL_SPEED_PERCENT_DENOMINATOR) { ushort lastBuf = laneTrafficData[segmentId][laneIndex].lastTrafficBuffer; ushort maxBuf = laneTrafficData[segmentId][laneIndex].maxTrafficBuffer; float factor = Mathf.Clamp01(1f - (float)lastBuf / (float)maxBuf); curRelSpeed = (ushort)(curRelSpeed + (uint)(factor * (float)((uint)REF_REL_SPEED - (uint)curRelSpeed))); } } return curRelSpeed; } public void SimulationStep(ushort segmentId, ref NetSegment segment) { GlobalConfig conf = GlobalConfig.Instance; // calculate traffic density NetInfo segmentInfo = segment.Info; uint curLaneId = segment.m_lanes; int numLanes = segmentInfo.m_lanes.Length; if (laneTrafficData[segmentId] == null || laneTrafficData[segmentId].Length < numLanes) { laneTrafficData[segmentId] = new LaneTrafficData[numLanes]; for (int i = 0; i < numLanes; ++i) { //laneTrafficData[segmentId][i] = new LaneTrafficData(); laneTrafficData[segmentId][i].meanSpeed = REF_REL_SPEED; } } // calculate max./min. lane speed for (int i = 0; i < 2; ++i) { meanSpeeds[i] = 0; meanSpeedLanes[i] = 0; } curLaneId = segment.m_lanes; byte laneIndex = 0; while (laneIndex < numLanes && curLaneId != 0u) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; if ((laneInfo.m_laneType & LANE_TYPES) != NetInfo.LaneType.None && (laneInfo.m_vehicleType & VEHICLE_TYPES) != VehicleInfo.VehicleType.None) { int dirIndex = GetDirIndex(laneInfo.m_finalDirection); // calculate reported mean speed ushort newRelSpeed = CalcLaneRelativeMeanSpeed(segmentId, laneIndex, curLaneId, segment.Info.m_lanes[laneIndex]); meanSpeeds[dirIndex] += newRelSpeed; ++meanSpeedLanes[dirIndex]; laneTrafficData[segmentId][laneIndex].meanSpeed = newRelSpeed; ushort trafficBuffer = laneTrafficData[segmentId][laneIndex].trafficBuffer; // remember historic data laneTrafficData[segmentId][laneIndex].lastTrafficBuffer = trafficBuffer; if (trafficBuffer > laneTrafficData[segmentId][laneIndex].maxTrafficBuffer) { laneTrafficData[segmentId][laneIndex].maxTrafficBuffer = trafficBuffer; } // reset buffers if (conf.AdvancedVehicleAI.MaxTrafficBuffer > 0) { if (laneTrafficData[segmentId][laneIndex].trafficBuffer > conf.AdvancedVehicleAI.MaxTrafficBuffer) { laneTrafficData[segmentId][laneIndex].accumulatedSpeeds /= (laneTrafficData[segmentId][laneIndex].trafficBuffer / conf.AdvancedVehicleAI.MaxTrafficBuffer); laneTrafficData[segmentId][laneIndex].trafficBuffer = (ushort)conf.AdvancedVehicleAI.MaxTrafficBuffer; } else if (laneTrafficData[segmentId][laneIndex].trafficBuffer == conf.AdvancedVehicleAI.MaxTrafficBuffer) { laneTrafficData[segmentId][laneIndex].accumulatedSpeeds = 0; laneTrafficData[segmentId][laneIndex].trafficBuffer = 0; } } else { laneTrafficData[segmentId][laneIndex].accumulatedSpeeds = 0; laneTrafficData[segmentId][laneIndex].trafficBuffer = 0; } } laneIndex++; curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; } for (int i = 0; i < 2; ++i) { int segDirIndex = i == 0 ? GetDirIndex(segmentId, NetInfo.Direction.Forward) : GetDirIndex(segmentId, NetInfo.Direction.Backward); if (meanSpeedLanes[i] > 0) { segmentDirTrafficData[segDirIndex].meanSpeed = (ushort)Math.Min(REF_REL_SPEED, (meanSpeeds[i] / meanSpeedLanes[i])); } else { segmentDirTrafficData[segDirIndex].meanSpeed = REF_REL_SPEED; } } } public bool GetLaneTrafficData(ushort segmentId, byte laneIndex, out LaneTrafficData trafficData) { if (laneTrafficData[segmentId] == null || laneIndex >= laneTrafficData[segmentId].Length) { trafficData = default(LaneTrafficData); return false; } else { trafficData = laneTrafficData[segmentId][laneIndex]; return true; } } public void DestroySegmentStats(ushort segmentId) { laneTrafficData[segmentId] = null; int fwdIndex = GetDirIndex(segmentId, NetInfo.Direction.Forward); int backIndex = GetDirIndex(segmentId, NetInfo.Direction.Backward); segmentDirTrafficData[fwdIndex] = default(SegmentDirTrafficData); segmentDirTrafficData[fwdIndex].meanSpeed = REF_REL_SPEED; segmentDirTrafficData[backIndex] = default(SegmentDirTrafficData); segmentDirTrafficData[backIndex].meanSpeed = REF_REL_SPEED; } public void ResetTrafficStats() { for (int i = 0; i < NetManager.MAX_SEGMENT_COUNT; ++i) { DestroySegmentStats((ushort)i); } } public void AddTraffic(ushort segmentId, byte laneIndex, ushort speed) { if (laneTrafficData[segmentId] == null || laneIndex >= laneTrafficData[segmentId].Length) return; laneTrafficData[segmentId][laneIndex].trafficBuffer = (ushort)Math.Min(65535u, (uint)laneTrafficData[segmentId][laneIndex].trafficBuffer + 1u); laneTrafficData[segmentId][laneIndex].accumulatedSpeeds += (uint)speed; } internal int GetDirIndex(ushort segmentId, NetInfo.Direction dir) { return (int)segmentId + (dir == NetInfo.Direction.Backward ? NetManager.MAX_SEGMENT_COUNT : 0); } internal int GetDirIndex(NetInfo.Direction dir) { return dir == NetInfo.Direction.Backward ? 1 : 0; } public override void OnLevelUnloading() { base.OnLevelUnloading(); ResetTrafficStats(); } } } ================================================ FILE: TLM/TLM/Manager/Impl/TrafficPriorityManager.cs ================================================ using System; using System.Collections.Generic; using ColossalFramework; using TrafficManager.TrafficLight; using TrafficManager.Custom.AI; using UnityEngine; using TrafficManager.State; using System.Threading; using TrafficManager.Util; using TrafficManager.Traffic; using TrafficManager.Geometry; using CSUtil.Commons; using TrafficManager.Geometry.Impl; using static TrafficManager.Traffic.Data.PrioritySegment; using TrafficManager.Traffic.Data; namespace TrafficManager.Manager.Impl { public class TrafficPriorityManager : AbstractGeometryObservingManager, ICustomDataManager>, ICustomDataManager>, ITrafficPriorityManager { public static readonly TrafficPriorityManager Instance = new TrafficPriorityManager(); public enum UnableReason { None, NoJunction, HasTimedLight, InvalidSegment, NotIncoming } /// /// List of segments that are connected to roads with timed traffic lights or priority signs. Index: segment id /// private PrioritySegment[] PrioritySegments = null; private PrioritySegment[] invalidPrioritySegments; private TrafficPriorityManager() { PrioritySegments = new PrioritySegment[NetManager.MAX_SEGMENT_COUNT]; invalidPrioritySegments = new PrioritySegment[NetManager.MAX_SEGMENT_COUNT]; } protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"Priority signs:"); for (int i = 0; i < PrioritySegments.Length; ++i) { if (PrioritySegments[i].IsDefault()) { continue; } Log._Debug($"Segment {i}: {PrioritySegments[i]}"); } } protected void AddInvalidPrioritySegment(ushort segmentId, ref PrioritySegment prioritySegment) { invalidPrioritySegments[segmentId] = prioritySegment; } public bool MayNodeHavePrioritySigns(ushort nodeId) { UnableReason reason; return MayNodeHavePrioritySigns(nodeId, out reason); } public bool MayNodeHavePrioritySigns(ushort nodeId, out UnableReason reason) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); #endif if (!Services.NetService.CheckNodeFlags(nodeId, NetNode.Flags.Created | NetNode.Flags.Deleted | NetNode.Flags.Junction, NetNode.Flags.Created | NetNode.Flags.Junction)) { reason = UnableReason.NoJunction; #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=false, reason={reason}"); } #endif return false; } if (TrafficLightSimulationManager.Instance.HasTimedSimulation(nodeId)) { reason = UnableReason.HasTimedLight; #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=false, reason={reason}"); } #endif return false; } //Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=true"); reason = UnableReason.None; return true; } public bool MaySegmentHavePrioritySign(ushort segmentId, bool startNode) { UnableReason reason; return MaySegmentHavePrioritySign(segmentId, startNode, out reason); } public bool MaySegmentHavePrioritySign(ushort segmentId, bool startNode, out UnableReason reason) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); #endif if (! Services.NetService.IsSegmentValid(segmentId)) { reason = UnableReason.InvalidSegment; #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); } #endif return false; } if (! MayNodeHavePrioritySigns(Services.NetService.GetSegmentNodeId(segmentId, startNode), out reason)) { #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); } #endif return false; } SegmentEndGeometry endGeo = SegmentGeometry.Get(segmentId)?.GetEnd(startNode); if (endGeo.OutgoingOneWay) { reason = UnableReason.NotIncoming; #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); } #endif return false; } #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=true"); } #endif reason = UnableReason.None; return true; } public bool MaySegmentHavePrioritySign(ushort segmentId) { UnableReason reason; return MaySegmentHavePrioritySign(segmentId, out reason); } public bool MaySegmentHavePrioritySign(ushort segmentId, out UnableReason reason) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); #endif if (!Services.NetService.IsSegmentValid(segmentId)) { reason = UnableReason.InvalidSegment; #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, result=false, reason={reason}"); } #endif return false; } bool ret = (MaySegmentHavePrioritySign(segmentId, true, out reason) || MaySegmentHavePrioritySign(segmentId, false, out reason)); #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, result={ret}, reason={reason}"); } #endif return ret; } public bool HasSegmentPrioritySign(ushort segmentId) { return !PrioritySegments[segmentId].IsDefault(); } public bool HasSegmentPrioritySign(ushort segmentId, bool startNode) { return PrioritySegments[segmentId].HasPrioritySignAtNode(startNode); } public bool HasNodePrioritySign(ushort nodeId) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); #endif bool ret = false; Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { if (HasSegmentPrioritySign(segmentId, nodeId == segment.m_startNode)) { ret = true; return false; } return true; }); #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.HasNodePrioritySign: nodeId={nodeId}, result={ret}"); } #endif return ret; } public bool SetPrioritySign(ushort segmentId, bool startNode, PriorityType type) { UnableReason reason; return SetPrioritySign(segmentId, startNode, type, out reason); } public bool SetPrioritySign(ushort segmentId, bool startNode, PriorityType type, out UnableReason reason) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); #endif bool ret = true; reason = UnableReason.None; if (type != PriorityType.None && ! MaySegmentHavePrioritySign(segmentId, startNode, out reason)) { #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.SetPrioritySign: Segment {segmentId} @ {startNode} may not have a priority sign: {reason}"); } #endif ret = false; type = PriorityType.None; } if (type != PriorityType.None) { SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { Log.Error($"TrafficPriorityManager.SetPrioritySign: No geometry information available for segment {segmentId}"); reason = UnableReason.InvalidSegment; return false; } ushort nodeId = segGeo.GetNodeId(startNode); Services.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { TrafficLightManager.Instance.RemoveTrafficLight(nodeId, ref node); return true; }); } if (startNode) { PrioritySegments[segmentId].startType = type; } else { PrioritySegments[segmentId].endType = type; } SegmentEndManager.Instance.UpdateSegmentEnd(segmentId, startNode); #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.SetPrioritySign: segmentId={segmentId}, startNode={startNode}, type={type}, result={ret}, reason={reason}"); } #endif return ret; } public void RemovePrioritySignsFromNode(ushort nodeId) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); if (debug) { Log._Debug($"TrafficPriorityManager.RemovePrioritySignsFromNode: nodeId={nodeId}"); } #endif Services.NetService.IterateNodeSegments(nodeId, delegate(ushort segmentId, ref NetSegment segment) { RemovePrioritySignFromSegmentEnd(segmentId, nodeId == segment.m_startNode); return true; }); } public void RemovePrioritySignsFromSegment(ushort segmentId) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); if (debug) { Log._Debug($"TrafficPriorityManager.RemovePrioritySignsFromSegment: segmentId={segmentId}"); } #endif RemovePrioritySignFromSegmentEnd(segmentId, true); RemovePrioritySignFromSegmentEnd(segmentId, false); } public void RemovePrioritySignFromSegmentEnd(ushort segmentId, bool startNode) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); if (debug) { Log._Debug($"TrafficPriorityManager.RemovePrioritySignFromSegment: segmentId={segmentId}, startNode={startNode}"); } #endif if (startNode) { PrioritySegments[segmentId].startType = PriorityType.None; } else { PrioritySegments[segmentId].endType = PriorityType.None; } SegmentEndManager.Instance.UpdateSegmentEnd(segmentId, startNode); } public PriorityType GetPrioritySign(ushort segmentId, bool startNode) { return startNode ? PrioritySegments[segmentId].startType : PrioritySegments[segmentId].endType; } public byte CountPrioritySignsAtNode(ushort nodeId, PriorityType sign) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); #endif byte ret = 0; Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { if (GetPrioritySign(segmentId, segment.m_startNode == nodeId) == sign) { ++ret; } return true; }); #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.CountPrioritySignsAtNode: nodeId={nodeId}, sign={sign}, result={ret}"); } #endif return ret; } public bool HasPriority(ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Position curPos, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, ref NetNode transitNode) { SegmentEndGeometry endGeo = SegmentGeometry.Get(curPos.m_segment)?.GetEnd(startNode); if (endGeo == null) { #if DEBUG Log.Warning($"TrafficPriorityManager.HasPriority({vehicleId}): No segment end geometry found for segment {curPos.m_segment} @ {startNode}"); return true; #endif } /*SegmentEnd end = SegmentEndManager.Instance.GetSegmentEnd(curPos.m_segment, startNode); if (end == null) { #if DEBUG Log.Warning($"TrafficPriorityManager.HasPriority({vehicleId}): No segment end found for segment {curPos.m_segment} @ {startNode}"); return true; #endif } ushort transitNodeId = end.NodeId;*/ #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || transitNodeId == GlobalConfig.Instance.Debug.NodeId); if (debug) { Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Checking vehicle {vehicleId} at node {transitNodeId}. Coming from seg. {curPos.m_segment}, start {startNode}, lane {curPos.m_lane}, going to seg. {nextPos.m_segment}, lane {nextPos.m_lane}"); } #else bool debug = false; #endif if ((vehicle.m_flags & Vehicle.Flags.Spawned) == 0) { #if DEBUG if (debug) Log.Warning($"TrafficPriorityManager.HasPriority({vehicleId}): Vehicle is not spawned."); #endif return true; } if ((vehicle.m_flags & Vehicle.Flags.Emergency2) != 0) { // target vehicle is on emergency #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Vehicle is on emergency."); #endif return true; } if (vehicle.Info.m_vehicleType == VehicleInfo.VehicleType.Monorail) { // monorails do not obey priority signs #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Vehicle is a monorail."); #endif return true; } PriorityType curSign = GetPrioritySign(curPos.m_segment, startNode); if (curSign == PriorityType.None) { #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Sign is None @ seg. {curPos.m_segment}, start {startNode} -> setting to Main"); #endif curSign = PriorityType.Main; } bool onMain = curSign == PriorityType.Main; /*if (! Services.VehicleService.IsVehicleValid(vehicleId)) { curEnd.RequestCleanup(); return true; }*/ // calculate approx. time after which the transit node will be reached float targetTimeToTransitNode = Single.NaN; if (Options.simAccuracy <= 1) { Vector3 targetToNode = transitNode.m_position - vehicle.GetLastFramePosition(); Vector3 targetVel = vehicle.GetLastFrameVelocity(); float targetSpeed = targetVel.magnitude; float targetDistanceToTransitNode = targetToNode.magnitude; if (targetSpeed > 0) targetTimeToTransitNode = targetDistanceToTransitNode / targetSpeed; else targetTimeToTransitNode = 0; } #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): estimated target time to transit node {transitNodeId} is {targetTimeToTransitNode} for vehicle {vehicleId}"); #endif ArrowDirection targetToDir = endGeo.GetDirection(nextPos.m_segment); // absolute target direction of target vehicle // iterate over all cars approaching the transit node and check if the target vehicle should be prioritized VehicleStateManager vehStateManager = VehicleStateManager.Instance; CustomSegmentLightsManager segLightsManager = CustomSegmentLightsManager.Instance; NodeGeometry transitNodeGeo = NodeGeometry.Get(transitNodeId); foreach (SegmentEndGeometry otherEndGeo in transitNodeGeo.SegmentEndGeometries) { if (otherEndGeo == null) { continue; } ushort otherSegmentId = otherEndGeo.SegmentId; if (otherSegmentId == curPos.m_segment) { continue; } bool otherStartNode = otherEndGeo.StartNode; if (otherEndGeo.OutgoingOneWay) { // not an incoming segment continue; } ICustomSegmentLights otherLights = null; if (Options.trafficLightPriorityRules) { otherLights = segLightsManager.GetSegmentLights(otherSegmentId, otherStartNode, false); } PriorityType otherSign = GetPrioritySign(otherSegmentId, otherStartNode); if (otherSign == PriorityType.None) { otherSign = PriorityType.Main; //continue; } bool incomingOnMain = otherSign == PriorityType.Main; ISegmentEnd incomingEnd = SegmentEndManager.Instance.GetSegmentEnd(otherSegmentId, otherStartNode); if (incomingEnd == null) { #if DEBUG if (debug) Log.Error($"TrafficPriorityManager.HasPriority({vehicleId}): No segment end found for other segment {otherSegmentId} @ {otherStartNode}"); #endif continue; } ArrowDirection incomingFromDir = endGeo.GetDirection(otherSegmentId); // absolute incoming direction of incoming vehicle #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): checking other segment {otherSegmentId} @ {transitNodeId}"); #endif ushort incomingVehicleId = incomingEnd.FirstRegisteredVehicleId; while (incomingVehicleId != 0) { #if DEBUG if (debug) { Log._Debug(""); Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): checking other vehicle {incomingVehicleId} @ seg. {otherSegmentId}"); } #endif if (IsConflictingVehicle(debug, transitNode.m_position, targetTimeToTransitNode, vehicleId, ref vehicle, ref curPos, transitNodeId, startNode, ref nextPos, onMain, endGeo, targetToDir, incomingVehicleId, ref Singleton.instance.m_vehicles.m_buffer[incomingVehicleId], ref vehStateManager.VehicleStates[incomingVehicleId], incomingOnMain, otherEndGeo, otherLights, incomingFromDir)) { #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): incoming vehicle {incomingVehicleId} is conflicting."); } #endif return false; } // check next incoming vehicle incomingVehicleId = vehStateManager.VehicleStates[incomingVehicleId].nextVehicleIdOnSegment; } } #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): No conflicting incoming vehicles found."); } #endif return true; } private bool IsConflictingVehicle(bool debug, Vector3 transitNodePos, float targetTimeToTransitNode, ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Position curPos, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, bool onMain, SegmentEndGeometry endGeo, ArrowDirection targetToDir, ushort incomingVehicleId, ref Vehicle incomingVehicle, ref VehicleState incomingState, bool incomingOnMain, SegmentEndGeometry incomingEndGeo, ICustomSegmentLights incomingLights, ArrowDirection incomingFromDir) { #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Checking against other vehicle {incomingVehicleId}."); Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): TARGET is coming from seg. {curPos.m_segment}, start {startNode}, lane {curPos.m_lane}, going to seg. {nextPos.m_segment}, lane {nextPos.m_lane}"); Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): INCOMING is coming from seg. {incomingState.currentSegmentId}, start {incomingState.currentStartNode}, lane {incomingState.currentLaneIndex}, going to seg. {incomingState.nextSegmentId}, lane {incomingState.nextLaneIndex}\nincoming state: {incomingState}"); } #endif if ((incomingState.flags & VehicleState.Flags.Spawned) == VehicleState.Flags.None) { #if DEBUG if (debug) Log.Warning($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming vehicle is not spawned."); #endif return false; } if (incomingVehicle.Info.m_vehicleType == VehicleInfo.VehicleType.Monorail) { // monorails and cars do not collide #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming vehicle is a monorail."); } #endif return false; } ArrowDirection incomingToRelDir = incomingEndGeo.GetDirection(incomingState.nextSegmentId); // relative target direction of incoming vehicle if (incomingLights != null) { ICustomSegmentLight incomingLight = incomingLights.GetCustomLight(incomingState.currentLaneIndex); #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Detected traffic light. Incoming state ({incomingToRelDir}): {incomingLight.GetLightState(incomingToRelDir)}"); #endif if (incomingLight.IsRed(incomingToRelDir)) { #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming traffic light is red."); #endif return false; } } if (incomingState.JunctionTransitState != VehicleJunctionTransitState.None) { Vector3 incomingVel = incomingVehicle.GetLastFrameVelocity(); bool incomingStateChangedRecently = incomingState.IsJunctionTransitStateNew(); if (incomingState.JunctionTransitState == VehicleJunctionTransitState.Approach || incomingState.JunctionTransitState == VehicleJunctionTransitState.Leave ) { if ((incomingState.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None) { float incomingSqrSpeed = incomingVel.sqrMagnitude; if (!incomingStateChangedRecently && incomingSqrSpeed <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity) { #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is LEAVING or APPROACHING but not moving. -> BLOCKED"); #endif incomingState.JunctionTransitState = VehicleJunctionTransitState.Blocked; incomingStateChangedRecently = true; return false; } } // incoming vehicle is (1) entering the junction or (2) leaving Vector3 incomingPos = incomingVehicle.GetLastFramePosition(); Vector3 incomingToNode = transitNodePos - incomingPos; // check if incoming vehicle moves towards node float dot = Vector3.Dot(incomingToNode, incomingVel); if (dot <= 0) { #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is moving away from the transit node ({dot}). *IGNORING*"); #endif return false; } #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is moving towards the transit node ({dot}). Distance: {incomingToNode.magnitude}"); #endif // check if estimated approach time of the incoming vehicle is within bounds (only if incoming vehicle is far enough away from the junction and target vehicle is moving) if (Options.simAccuracy <= 1 && !Single.IsInfinity(targetTimeToTransitNode) && !Single.IsNaN(targetTimeToTransitNode) && incomingToNode.sqrMagnitude > GlobalConfig.Instance.PriorityRules.MaxPriorityCheckSqrDist ) { // check speeds float incomingSpeed = incomingVel.magnitude; float incomingDistanceToTransitNode = incomingToNode.magnitude; float incomingTimeToTransitNode = Single.NaN; if (incomingSpeed > 0) incomingTimeToTransitNode = incomingDistanceToTransitNode / incomingSpeed; else incomingTimeToTransitNode = Single.PositiveInfinity; float timeDiff = Mathf.Abs(incomingTimeToTransitNode - targetTimeToTransitNode); if (timeDiff > GlobalConfig.Instance.PriorityRules.MaxPriorityApproachTime) { #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} needs {incomingTimeToTransitNode} time units to get to the node where target needs {targetTimeToTransitNode} time units (diff = {timeDiff}). Difference to large. *IGNORING*"); #endif return false; } else { #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} needs {incomingTimeToTransitNode} time units to get to the node where target needs {targetTimeToTransitNode} time units (diff = {timeDiff}). Difference within bounds. Priority check required."); #endif } } else { #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming is stopped."); #endif } } if (!incomingStateChangedRecently && (incomingState.JunctionTransitState == VehicleJunctionTransitState.Blocked/* || (incomingState.JunctionTransitState == VehicleJunctionTransitState.Stop && vehicleId < incomingVehicleId)*/) ) { #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is BLOCKED and has waited a bit or is STOP and targetVehicleId {vehicleId} < incomingVehicleId {incomingVehicleId}. *IGNORING*"); #endif // incoming vehicle waits because the junction is blocked or it does not get priority and we waited for some time. Allow target vehicle to enter the junciton. return false; } // check priority rules if (HasVehiclePriority(debug, vehicleId, ref vehicle, ref curPos, transitNodeId, startNode, ref nextPos, onMain, targetToDir, incomingVehicleId, ref incomingVehicle, ref incomingState, incomingOnMain, incomingFromDir, incomingToRelDir)) { #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is not conflicting."); #endif return false; } else { #if DEBUG if (debug) Log._Debug($"==========> TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} IS conflicting."); #endif return true; } } else { #if DEBUG if (debug) Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} (main) is not conflicting ({incomingState.JunctionTransitState})."); #endif return false; } } /// /// Implements priority checking for two vehicles approaching or waiting at a junction. /// /// /// id of the junction /// target vehicle for which priority is being checked /// target vehicle data /// target vehicle current path position /// target vehicle next path position /// true if the target vehicle is coming from a main road /// possibly conflicting incoming vehicle /// incoming vehicle current path position /// incoming vehicle next path position /// true if the incoming vehicle is coming from a main road /// true if the target vehicle has priority, false otherwise private bool HasVehiclePriority(bool debug, ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Position curPos, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, bool onMain, ArrowDirection targetToDir, ushort incomingVehicleId, ref Vehicle incomingVehicle, ref VehicleState incomingState, bool incomingOnMain, ArrowDirection incomingFromDir, ArrowDirection incomingToRelDir) { #if DEBUG if (debug) { Log._Debug(""); Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): *** Checking if vehicle {vehicleId} (main road = {onMain}) @ (seg. {curPos.m_segment}, start {startNode}, lane {curPos.m_lane}) -> (seg. {nextPos.m_segment}, lane {nextPos.m_lane}) has priority over {incomingVehicleId} (main road = {incomingOnMain}) @ (seg. {incomingState.currentSegmentId}, start {incomingState.currentStartNode}, lane {incomingState.currentLaneIndex}) -> (seg. {incomingState.nextSegmentId}, lane {incomingState.nextLaneIndex})."); } #endif if (targetToDir == ArrowDirection.None || incomingFromDir == ArrowDirection.None || incomingToRelDir == ArrowDirection.None) { #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Invalid directions given: targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}"); } #endif return true; } if (curPos.m_segment == incomingState.currentSegmentId) { // both vehicles are coming from the same segment. do not apply priority rules in this case. #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Both vehicles come from the same segment. *IGNORING*"); } #endif return true; } /* FORWARD * | * | * LEFT --- + --- RIGHT * | * | * TURN /* * - Target car is always coming from TURN. * - Target car is going to `targetToDir` (relative to TURN). * - Incoming car is coming from `incomingFromDir` (relative to TURN). * - Incoming car is going to `incomingToRelDir` (relative to `incomingFromDir`). */ #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): targetToDir: {targetToDir.ToString()}, incomingFromDir: {incomingFromDir.ToString()}, incomingToRelDir: {incomingToRelDir.ToString()}"); } #endif if (Services.SimulationService.LeftHandDrive) { // mirror situation for left-hand traffic systems targetToDir = ArrowDirectionUtil.InvertLeftRight(targetToDir); incomingFromDir = ArrowDirectionUtil.InvertLeftRight(incomingFromDir); incomingToRelDir = ArrowDirectionUtil.InvertLeftRight(incomingToRelDir); #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): LHD! targetToDir: {targetToDir.ToString()}, incomingFromDir: {incomingFromDir.ToString()}, incomingToRelDir: {incomingToRelDir.ToString()}"); } #endif } #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}"); } #endif /* * (1) COLLISION DETECTION */ bool sameTargets = nextPos.m_segment == incomingState.nextSegmentId; bool wouldCollide = DetectCollision(debug, ref curPos, transitNodeId, startNode, ref nextPos, ref incomingState, targetToDir, incomingFromDir, incomingToRelDir, vehicleId, incomingVehicleId); if (!wouldCollide) { // both vehicles would not collide. allow both to pass. #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} would not collide. NO CONFLICT."); } #endif return true; } // -> vehicles would collide #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} would collide. Checking priority rules."); } #endif /* * (2) CHECK PRIORITY RULES */ bool ret; if ((!onMain && !incomingOnMain) || (onMain && incomingOnMain)) { // both vehicles are on the same priority level: check common priority rules (left yields to right, left turning vehicles yield to others) ret = HasPriorityOnSameLevel(debug, targetToDir, incomingFromDir, incomingToRelDir, vehicleId, incomingVehicleId); #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} are on the same priority level. Checking commong priority rules. ret={ret}"); } #endif } else { // both vehicles are on a different priority level: prioritize vehicle on main road ret = onMain; #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} are on a different priority. Prioritizing vehicle on main road. ret={ret}"); } #endif } if (ret) { // check if the incoming vehicle is leaving (though the target vehicle has priority) bool incomingIsLeaving = incomingState.JunctionTransitState == VehicleJunctionTransitState.Leave; #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): >>> Car {vehicleId} has priority over {incomingVehicleId}. incomingIsLeaving={incomingIsLeaving}"); } #endif return !incomingIsLeaving; } else { // the target vehicle must wait #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): >>> Car {vehicleId} must wait for {incomingVehicleId}. returning FALSE."); } #endif return false; } } /// /// Checks if two vehicles are on a collision course. /// /// enable debugging /// junction node /// incoming vehicle state /// absolute target vehicle destination direction /// absolute incoming vehicle source direction /// relative incoming vehicle destination direction /// (optional) target vehicle id /// (optional) incoming vehicle id /// true if both vehicles are on a collision course, false otherwise public bool DetectCollision(bool debug, ref PathUnit.Position curPos, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, ref VehicleState incomingState, ArrowDirection targetToDir, ArrowDirection incomingFromDir, ArrowDirection incomingToRelDir, ushort vehicleId=0, ushort incomingVehicleId=0 ) { bool sameTargets = nextPos.m_segment == incomingState.nextSegmentId; bool wouldCollide; bool incomingIsLeaving = incomingState.JunctionTransitState == VehicleJunctionTransitState.Leave; if (sameTargets) { // both are going to the same segment #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target and incoming are going to the same segment."); } #endif if (nextPos.m_lane == incomingState.nextLaneIndex) { // both are going to the same lane: lane order is always incorrect #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target and incoming are going to the same segment AND lane. lane order is incorrect!"); } #endif wouldCollide = true; } else { // both are going to a different lane: check lane order #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target and incoming are going to the same segment BUT NOT to the same lane. Determining if lane order is correct."); } #endif switch (targetToDir) { case ArrowDirection.Left: case ArrowDirection.Turn: default: // (should not happen) // target & incoming are going left: stay left wouldCollide = !IsLaneOrderConflictFree(debug, nextPos.m_segment, transitNodeId, nextPos.m_lane, incomingState.nextLaneIndex); // stay left #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir}. Checking if lane {nextPos.m_lane} is LEFT to {incomingState.nextLaneIndex}. would collide? {wouldCollide}"); } #endif break; case ArrowDirection.Forward: // target is going forward/turn switch (incomingFromDir) { case ArrowDirection.Left: case ArrowDirection.Forward: // target is going forward, incoming is coming from left/forward: stay right wouldCollide = !IsLaneOrderConflictFree(debug, nextPos.m_segment, transitNodeId, incomingState.nextLaneIndex, nextPos.m_lane); // stay right #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir} and incoming is coming from {incomingFromDir}. Checking if lane {nextPos.m_lane} is RIGHT to {incomingState.nextLaneIndex}. would collide? {wouldCollide}"); } #endif break; case ArrowDirection.Right: // target is going forward, incoming is coming from right: stay left wouldCollide = !IsLaneOrderConflictFree(debug, nextPos.m_segment, transitNodeId, nextPos.m_lane, incomingState.nextLaneIndex); // stay left #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir} and incoming is coming from {incomingFromDir}. Checking if lane {nextPos.m_lane} is LEFT to {incomingState.nextLaneIndex}. would collide? {wouldCollide}"); } #endif break; case ArrowDirection.Turn: // (should not happen) default: // (should not happen) wouldCollide = false; #if DEBUG if (debug) { Log.Warning($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir} and incoming is coming from {incomingFromDir} (SHOULD NOT HAPPEN). would collide? {wouldCollide}"); } #endif break; } break; case ArrowDirection.Right: // target is going right: stay right wouldCollide = !IsLaneOrderConflictFree(debug, nextPos.m_segment, transitNodeId, incomingState.nextLaneIndex, nextPos.m_lane); // stay right #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going RIGHT. Checking if lane {nextPos.m_lane} is RIGHT to {incomingState.nextLaneIndex}. would collide? {wouldCollide}"); } #endif break; } #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): >>> would collide? {wouldCollide}"); } #endif } } else { #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target and incoming are going to different segments."); } #endif switch (targetToDir) { case ArrowDirection.Left: switch (incomingFromDir) { case ArrowDirection.Left: wouldCollide = incomingToRelDir != ArrowDirection.Right; break; case ArrowDirection.Forward: wouldCollide = incomingToRelDir != ArrowDirection.Left && incomingToRelDir != ArrowDirection.Turn; break; case ArrowDirection.Right: wouldCollide = incomingToRelDir != ArrowDirection.Right && incomingToRelDir != ArrowDirection.Turn; break; default: // (should not happen) wouldCollide = false; #if DEBUG if (debug) { Log.Warning($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir}, incoming is coming from {incomingFromDir} and going {incomingToRelDir}. SHOULD NOT HAPPEN. would collide? {wouldCollide}"); } #endif break; } break; case ArrowDirection.Forward: switch (incomingFromDir) { case ArrowDirection.Left: wouldCollide = incomingToRelDir != ArrowDirection.Right && incomingToRelDir != ArrowDirection.Turn; break; case ArrowDirection.Forward: wouldCollide = incomingToRelDir != ArrowDirection.Right && incomingToRelDir != ArrowDirection.Forward; break; case ArrowDirection.Right: wouldCollide = true; // TODO allow u-turns? break; default: // (should not happen) wouldCollide = false; #if DEBUG if (debug) { Log.Warning($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir}, incoming is coming from {incomingFromDir} and going {incomingToRelDir}. SHOULD NOT HAPPEN. would collide? {wouldCollide}"); } #endif break; } break; case ArrowDirection.Right: case ArrowDirection.Turn: default: wouldCollide = false; break; } #if DEBUG if (debug) { Log._Debug($" TrafficPriorityManager.DetectCollision({vehicleId}, {incomingVehicleId}): Target is going {targetToDir}, incoming is coming from {incomingFromDir} and going {incomingToRelDir}. would collide? {wouldCollide}"); } #endif } return wouldCollide; } /// /// Check common priority rules if both vehicles are on a collision course and on the same priority level [(main AND main) OR (!main AND !main)]: /// 1. left yields to right /// 2. left-turning vehicles must yield to straight-going vehicles /// /// enable debugging /// absolute target vehicle destination direction /// absolute incoming vehicle source direction /// relative incoming vehicle destination direction /// (optional) target vehicle id /// (optional) incoming vehicle id /// public bool HasPriorityOnSameLevel(bool debug, ArrowDirection targetToDir, ArrowDirection incomingFromDir, ArrowDirection incomingToRelDir, ushort vehicleId=0, ushort incomingVehicleId=0) { bool ret; switch (incomingFromDir) { case ArrowDirection.Left: case ArrowDirection.Right: // (1) left yields to right ret = incomingFromDir == ArrowDirection.Left; break; default: if (incomingToRelDir == ArrowDirection.Left || incomingToRelDir == ArrowDirection.Turn) { // (2) incoming vehicle must wait ret = true; } else if (targetToDir == ArrowDirection.Left || targetToDir == ArrowDirection.Turn) { // (2) target vehicle must wait ret = false; } else { // (should not happen) #if DEBUG if (debug) { Log.Warning($"TrafficPriorityManager.HasPriorityOnSameLevel({vehicleId}, {incomingVehicleId}): targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}: SHOULD NOT HAPPEN"); } #endif ret = true; } break; } #if DEBUG if (debug) { Log._Debug($"TrafficPriorityManager.HasPriorityOnSameLevel({vehicleId}, {incomingVehicleId}): targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}: ret={ret}"); } #endif return ret; } /// /// Checks if lane lies to the left of lane . /// /// enable debugging /// segment id /// transit node id /// lane index that is checked to lie left /// lane index that is checked to lie right /// public bool IsLaneOrderConflictFree(bool debug, ushort segmentId, ushort nodeId, byte leftLaneIndex, byte rightLaneIndex) { // TODO refactor try { if (leftLaneIndex == rightLaneIndex) { return false; } NetManager netManager = Singleton.instance; NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; NetInfo.Direction dir = nodeId == netManager.m_segments.m_buffer[segmentId].m_startNode ? NetInfo.Direction.Backward : NetInfo.Direction.Forward; NetInfo.Direction dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection(dir); NetInfo.Direction dir3 = Services.SimulationService.LeftHandDrive ? NetInfo.InvertDirection(dir2) : dir2; NetInfo.Lane leftLane = segmentInfo.m_lanes[leftLaneIndex]; NetInfo.Lane rightLane = segmentInfo.m_lanes[rightLaneIndex]; #if DEBUG if (debug) { Log._Debug($" IsLaneOrderConflictFree({segmentId}, {leftLaneIndex}, {rightLaneIndex}): dir={dir}, dir2={dir2}, dir3={dir3} laneDir={leftLane.m_direction}, leftLanePos={leftLane.m_position}, rightLanePos={rightLane.m_position}"); } #endif bool ret = (dir3 == NetInfo.Direction.Forward) ^ (leftLane.m_position < rightLane.m_position); return ret; } catch (Exception e) { Log.Error($"IsLaneOrderConflictFree({segmentId}, {leftLaneIndex}, {rightLaneIndex}): Error: {e.ToString()}"); } return true; } protected override void HandleInvalidSegment(SegmentGeometry geometry) { if (!PrioritySegments[geometry.SegmentId].IsDefault()) { AddInvalidPrioritySegment(geometry.SegmentId, ref PrioritySegments[geometry.SegmentId]); } RemovePrioritySignsFromSegment(geometry.SegmentId); } protected override void HandleValidSegment(SegmentGeometry geometry) { if (! MaySegmentHavePrioritySign(geometry.SegmentId, true)) { RemovePrioritySignFromSegmentEnd(geometry.SegmentId, true); } else { UpdateNode(geometry.StartNodeId()); } if (!MaySegmentHavePrioritySign(geometry.SegmentId, false)) { RemovePrioritySignFromSegmentEnd(geometry.SegmentId, false); } else { UpdateNode(geometry.EndNodeId()); } } protected override void HandleSegmentEndReplacement(NodeGeometry.SegmentEndReplacement replacement, SegmentEndGeometry newEndGeo) { ISegmentEndId oldSegmentEndId = replacement.oldSegmentEndId; ISegmentEndId newSegmentEndId = replacement.newSegmentEndId; PriorityType sign = PriorityType.None; if (oldSegmentEndId.StartNode) { sign = invalidPrioritySegments[oldSegmentEndId.SegmentId].startType; invalidPrioritySegments[oldSegmentEndId.SegmentId].startType = PriorityType.None; } else { sign = invalidPrioritySegments[oldSegmentEndId.SegmentId].endType; invalidPrioritySegments[oldSegmentEndId.SegmentId].endType = PriorityType.None; } if (sign == PriorityType.None) { return; } Log._Debug($"TrafficPriorityManager.HandleSegmentEndReplacement({replacement}): Segment replacement detected: {oldSegmentEndId.SegmentId} -> {newSegmentEndId.SegmentId}\n" + $"Moving priority sign {sign} to new segment." ); SetPrioritySign(newSegmentEndId.SegmentId, newSegmentEndId.StartNode, sign); } protected void UpdateNode(ushort nodeId) { UnableReason reason; if (! MayNodeHavePrioritySigns(nodeId, out reason)) { RemovePrioritySignsFromNode(nodeId); return; } } public override void OnLevelUnloading() { base.OnLevelUnloading(); for (int i = 0; i < PrioritySegments.Length; ++i) { RemovePrioritySignsFromSegment((ushort)i); } for (int i = 0; i < invalidPrioritySegments.Length; ++i) { invalidPrioritySegments[i].Reset(); } } [Obsolete] public bool LoadData(List data) { bool success = true; Log.Info($"Loading {data.Count} priority segments (old method)"); foreach (var segment in data) { try { if (segment.Length < 3) continue; if ((PriorityType)segment[2] == PriorityType.None) { continue; } ushort nodeId = (ushort)segment[0]; ushort segmentId = (ushort)segment[1]; PriorityType sign = (PriorityType)segment[2]; if (!Services.NetService.IsNodeValid(nodeId)) { continue; } if (!Services.NetService.IsSegmentValid(segmentId)) { continue; } SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { Log.Error($"TrafficPriorityManager.LoadData: No geometry information available for segment {segmentId}"); continue; } bool startNode = segGeo.StartNodeId() == nodeId; SetPrioritySign(segmentId, startNode, sign); } catch (Exception e) { // ignore, as it's probably corrupt save data. it'll be culled on next save Log.Warning("Error loading data from Priority segments: " + e.ToString()); success = false; } } return success; } [Obsolete] public List SaveData(ref bool success) { return null; } public bool LoadData(List data) { bool success = true; Log.Info($"Loading {data.Count} priority segments (new method)"); foreach (var prioSegData in data) { try { if ((PriorityType)prioSegData.priorityType == PriorityType.None) { continue; } if (!Services.NetService.IsNodeValid(prioSegData.nodeId)) { continue; } if (!Services.NetService.IsSegmentValid(prioSegData.segmentId)) { continue; } SegmentGeometry segGeo = SegmentGeometry.Get(prioSegData.segmentId); if (segGeo == null) { Log.Error($"TrafficPriorityManager.SaveData: No geometry information available for segment {prioSegData.segmentId}"); continue; } bool startNode = segGeo.StartNodeId() == prioSegData.nodeId; Log._Debug($"Loading priority sign {(PriorityType)prioSegData.priorityType} @ seg. {prioSegData.segmentId}, start node? {startNode}"); SetPrioritySign(prioSegData.segmentId, startNode, (PriorityType)prioSegData.priorityType); } catch (Exception e) { // ignore, as it's probably corrupt save data. it'll be culled on next save Log.Warning("Error loading data from Priority segments: " + e.ToString()); success = false; } } return success; } List ICustomDataManager>.SaveData(ref bool success) { List ret = new List(); for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { try { if (! Services.NetService.IsSegmentValid((ushort)segmentId) || ! HasSegmentPrioritySign((ushort)segmentId)) { continue; } SegmentGeometry segGeo = SegmentGeometry.Get((ushort)segmentId); if (segGeo == null) { Log.Error($"TrafficPriorityManager.SaveData: No geometry information available for segment {segmentId}"); continue; } PriorityType startSign = GetPrioritySign((ushort)segmentId, true); if (startSign != PriorityType.None) { ushort startNodeId = segGeo.StartNodeId(); if (Services.NetService.IsNodeValid(startNodeId)) { Log._Debug($"Saving priority sign of type {startSign} @ start node {startNodeId} of segment {segmentId}"); ret.Add(new Configuration.PrioritySegment((ushort)segmentId, startNodeId, (int)startSign)); } } PriorityType endSign = GetPrioritySign((ushort)segmentId, false); if (endSign != PriorityType.None) { ushort endNodeId = segGeo.EndNodeId(); if (Services.NetService.IsNodeValid(endNodeId)) { Log._Debug($"Saving priority sign of type {endSign} @ end node {endNodeId} of segment {segmentId}"); ret.Add(new Configuration.PrioritySegment((ushort)segmentId, endNodeId, (int)endSign)); } } } catch (Exception e) { Log.Error($"Exception occurred while saving priority segment @ seg. {segmentId}: {e.ToString()}"); success = false; } } return ret; } } } ================================================ FILE: TLM/TLM/Manager/Impl/TurnOnRedManager.cs ================================================ using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Text; using TrafficManager.Geometry; using TrafficManager.Geometry.Impl; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Traffic.Data; using TrafficManager.Traffic.Impl; using TrafficManager.Util; using static TrafficManager.Geometry.Impl.NodeGeometry; namespace TrafficManager.Manager.Impl { public class TurnOnRedManager : AbstractGeometryObservingManager, ITurnOnRedManager { public static TurnOnRedManager Instance { get; private set; } = new TurnOnRedManager(); public TurnOnRedSegments[] TurnOnRedSegments { get; private set; } private TurnOnRedManager() { TurnOnRedSegments = new TurnOnRedSegments[2 * NetManager.MAX_SEGMENT_COUNT]; } protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"Turn-on-red segments:"); for (int i = 0; i < TurnOnRedSegments.Length; ++i) { Log._Debug($"Segment end {i}: {TurnOnRedSegments[i]}"); } } protected override void HandleValidSegment(SegmentGeometry geometry) { UpdateSegment(geometry); } protected override void HandleInvalidSegment(SegmentGeometry geometry) { ResetSegment(geometry.SegmentId); } protected void UpdateSegment(SegmentGeometry geometry) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[25]; if (debug) { Log._Debug($"TurnOnRedManager.UpdateSegment({geometry.SegmentId}) called."); } #endif ResetSegment(geometry.SegmentId); SegmentEndGeometry startEndGeo = geometry.GetEnd(true); if (startEndGeo != null) { UpdateSegmentEnd(startEndGeo); } SegmentEndGeometry endEndGeo = geometry.GetEnd(false); if (endEndGeo != null) { UpdateSegmentEnd(endEndGeo); } } protected void UpdateSegmentEnd(SegmentEndGeometry endGeo) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[25]; if (debug) { Log._Debug($"TurnOnRedManager.UpdateSegmentEnd({endGeo.SegmentId}, {endGeo.StartNode}) called."); } #endif // check if traffic can flow to the node and that there is at least one outgoing segment if (endGeo.OutgoingOneWay || endGeo.NumOutgoingSegments <= 0) { #if DEBUG if (debug) { Log._Debug($"TurnOnRedManager.UpdateSegmentEnd({endGeo.SegmentId}, {endGeo.StartNode}): outgoing one-way or insufficient number of outgoing segments."); } #endif return; } bool lhd = Services.SimulationService.LeftHandDrive; ushort nodeId = endGeo.NodeId(); // check node // note that we must not check for the `TrafficLights` flag here because the flag might not be loaded yet bool nodeValid = false; Services.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { nodeValid = (node.m_flags & NetNode.Flags.LevelCrossing) == NetNode.Flags.None && node.Info?.m_class?.m_service != ItemClass.Service.Beautification; return true; }); if (! nodeValid) { #if DEBUG if (debug) { Log._Debug($"TurnOnRedManager.UpdateSegmentEnd({endGeo.SegmentId}, {endGeo.StartNode}): node invalid"); } #endif return; } // get left/right segments ushort leftSegmentId = 0; ushort rightSegmentId = 0; Services.NetService.ProcessSegment(endGeo.SegmentId, delegate (ushort segId, ref NetSegment seg) { seg.GetLeftAndRightSegments(nodeId, out leftSegmentId, out rightSegmentId); return true; }); #if DEBUG if (debug) { Log._Debug($"TurnOnRedManager.UpdateSegmentEnd({endGeo.SegmentId}, {endGeo.StartNode}): got left/right segments: {leftSegmentId}/{rightSegmentId}"); } #endif // validate left/right segments according to geometric properties if (leftSegmentId != 0 && !endGeo.IsLeftSegment(leftSegmentId)) { #if DEBUG if (debug) { Log._Debug($"TurnOnRedManager.UpdateSegmentEnd({endGeo.SegmentId}, {endGeo.StartNode}): left segment is not geometrically left"); } #endif leftSegmentId = 0; } if (rightSegmentId != 0 && !endGeo.IsRightSegment(rightSegmentId)) { #if DEBUG if (debug) { Log._Debug($"TurnOnRedManager.UpdateSegmentEnd({endGeo.SegmentId}, {endGeo.StartNode}): right segment is not geometrically right"); } #endif rightSegmentId = 0; } // check for incoming one-ways if (leftSegmentId != 0 && SegmentGeometry.Get(leftSegmentId).GetEnd(nodeId).IncomingOneWay) { #if DEBUG if (debug) { Log._Debug($"TurnOnRedManager.UpdateSegmentEnd({endGeo.SegmentId}, {endGeo.StartNode}): left segment is incoming one-way"); } #endif leftSegmentId = 0; } if (rightSegmentId != 0 && SegmentGeometry.Get(rightSegmentId).GetEnd(nodeId).IncomingOneWay) { #if DEBUG if (debug) { Log._Debug($"TurnOnRedManager.UpdateSegmentEnd({endGeo.SegmentId}, {endGeo.StartNode}): right segment is incoming one-way"); } #endif rightSegmentId = 0; } if (endGeo.IncomingOneWay) { if (lhd && rightSegmentId != 0 || !lhd && leftSegmentId != 0) { // special case: one-way to one-way in non-preferred direction #if DEBUG if (debug) { Log._Debug($"TurnOnRedManager.UpdateSegmentEnd({endGeo.SegmentId}, {endGeo.StartNode}): source is incoming one-way. checking for one-way in non-preferred direction"); } #endif ushort targetSegmentId = lhd ? rightSegmentId : leftSegmentId; SegmentEndGeometry targetEndGeo = SegmentGeometry.Get(targetSegmentId)?.GetEnd(nodeId); if (targetEndGeo == null || !targetEndGeo.OutgoingOneWay) { if (targetEndGeo == null) { Log.Error($"TurnOnRedManager.UpdateSegmentEnd({endGeo.SegmentId}, {endGeo.StartNode}): One-way to one-way: Target segment end geometry not found for segment id {targetSegmentId} @ {nodeId}"); } // disallow turn in non-preferred direction #if DEBUG if (debug) { Log._Debug($"TurnOnRedManager.UpdateSegmentEnd({endGeo.SegmentId}, {endGeo.StartNode}): turn in non-preferred direction {(lhd ? "right" : "left")} disallowed"); } #endif if (lhd) { rightSegmentId = 0; } else { leftSegmentId = 0; } } } } else if (lhd) { // default case (LHD): turn in preferred direction rightSegmentId = 0; } else { // default case (RHD): turn in preferred direction leftSegmentId = 0; } int index = GetIndex(endGeo.SegmentId, endGeo.StartNode); TurnOnRedSegments[index].leftSegmentId = leftSegmentId; TurnOnRedSegments[index].rightSegmentId = rightSegmentId; #if DEBUG if (debug) { Log._Debug($"TurnOnRedManager.UpdateSegmentEnd({endGeo.SegmentId}, {endGeo.StartNode}): Finished calculation. leftSegmentId={leftSegmentId}, rightSegmentId={rightSegmentId}"); } #endif } protected void ResetSegment(ushort segmentId) { TurnOnRedSegments[GetIndex(segmentId, true)].Reset(); TurnOnRedSegments[GetIndex(segmentId, false)].Reset(); } public override void OnBeforeLoadData() { base.OnBeforeLoadData(); // JunctionRestrictionsManager requires our data during loading of custom data for (uint i = 0; i < NetManager.MAX_SEGMENT_COUNT; ++i) { SegmentGeometry geo = SegmentGeometry.Get((ushort)i); if (geo != null && geo.IsValid()) { HandleValidSegment(geo); } } } public override void OnLevelUnloading() { base.OnLevelUnloading(); for (int i = 0; i < TurnOnRedSegments.Length; ++i) { TurnOnRedSegments[i].Reset(); } } public int GetIndex(ushort segmentId, bool startNode) { return (int)segmentId + (startNode ? 0 : NetManager.MAX_SEGMENT_COUNT); } } } ================================================ FILE: TLM/TLM/Manager/Impl/UtilityManager.cs ================================================ using ColossalFramework; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.State; using TrafficManager.Geometry; using CSUtil.Commons; using TrafficManager.Custom.AI; using System.Threading; using UnityEngine; using TrafficManager.Geometry.Impl; namespace TrafficManager.Manager.Impl { public class UtilityManager : AbstractCustomManager, IUtilityManager { public static UtilityManager Instance { get; private set; } = null; static UtilityManager() { Instance = new UtilityManager(); } public void ClearTraffic() { try { Monitor.Enter(Singleton.instance); for (uint i = 0; i < Singleton.instance.m_vehicles.m_size; ++i) { if ( (Singleton.instance.m_vehicles.m_buffer[i].m_flags & Vehicle.Flags.Created) != 0) Singleton.instance.ReleaseVehicle((ushort)i); } TrafficMeasurementManager.Instance.ResetTrafficStats(); } catch (Exception ex) { Log.Error($"Error occured while trying to clear traffic: {ex.ToString()}"); } finally { Monitor.Exit(Singleton.instance); } } public void RemoveParkedVehicles() { try { Monitor.Enter(Singleton.instance); for (uint i = 0; i < Singleton.instance.m_parkedVehicles.m_size; ++i) { if ( (Singleton.instance.m_parkedVehicles.m_buffer[i].m_flags & (ushort)VehicleParked.Flags.Created) != 0) Singleton.instance.ReleaseParkedVehicle((ushort)i); } } catch (Exception ex) { Log.Error($"Error occured while trying to remove parked vehicles: {ex.ToString()}"); } finally { Monitor.Exit(Singleton.instance); } } public void PrintAllDebugInfo() { Log._Debug($"UtilityManager.PrintAllDebugInfo(): Pausing simulation."); Singleton.instance.ForcedSimulationPaused = true; Log._Debug("=== Flags.PrintDebugInfo() ==="); try { Flags.PrintDebugInfo(); } catch (Exception e) { Log.Error($"Error occurred while printing debug info for flags: {e}"); } Log._Debug("=== SegmentGeometry.PrintDebugInfo() ==="); try { SegmentGeometry.PrintDebugInfo(); } catch (Exception e) { Log.Error($"Error occurred while printing debug info for segment geometries: {e}"); } Log._Debug("=== NodeGeometry.PrintDebugInfo() ==="); try { NodeGeometry.PrintDebugInfo(); } catch (Exception e) { Log.Error($"Error occurred while printing debug info for node geometries: {e}"); } foreach (ICustomManager manager in LoadingExtension.RegisteredManagers) { try { manager.PrintDebugInfo(); } catch (Exception e) { Log.Error($"Error occurred while printing debug info for manager {manager.GetType().Name}: {e}"); } } Log._Debug($"UtilityManager.PrintAllDebugInfo(): Unpausing simulation."); Singleton.instance.ForcedSimulationPaused = false; } public void ResetStuckEntities() { Log.Info($"UtilityManager.RemoveStuckEntities() called."); Log.Info($"UtilityManager.RemoveStuckEntities(): Pausing simulation."); Singleton.instance.ForcedSimulationPaused = true; Log.Info($"UtilityManager.RemoveStuckEntities(): Waiting for all paths."); Singleton.instance.WaitForAllPaths(); Log.Info($"UtilityManager.RemoveStuckEntities(): Resetting citizen instances that are waiting for a path."); for (uint citizenInstanceId = 1; citizenInstanceId < CitizenManager.MAX_INSTANCE_COUNT; ++citizenInstanceId) { //Log._Debug($"UtilityManager.RemoveStuckEntities(): Processing instance {citizenInstanceId}."); if ((Singleton.instance.m_instances.m_buffer[citizenInstanceId].m_flags & CitizenInstance.Flags.WaitingPath) != CitizenInstance.Flags.None) { CitizenAI ai = Singleton.instance.m_instances.m_buffer[citizenInstanceId].Info.m_citizenAI; if (Singleton.instance.m_instances.m_buffer[citizenInstanceId].m_path != 0u) { Log.Info($"Resetting stuck citizen instance {citizenInstanceId} (waiting for path)"); Singleton.instance.ReleasePath(Singleton.instance.m_instances.m_buffer[citizenInstanceId].m_path); Singleton.instance.m_instances.m_buffer[citizenInstanceId].m_path = 0u; } Singleton.instance.m_instances.m_buffer[citizenInstanceId].m_flags &= ~(CitizenInstance.Flags.WaitingTransport | CitizenInstance.Flags.EnteringVehicle | CitizenInstance.Flags.BoredOfWaiting | CitizenInstance.Flags.WaitingTaxi | CitizenInstance.Flags.WaitingPath); } else { #if DEBUG if (Singleton.instance.m_instances.m_buffer[citizenInstanceId].m_path == 0 && (Singleton.instance.m_instances.m_buffer[citizenInstanceId].m_flags & CitizenInstance.Flags.Character) != CitizenInstance.Flags.None) { Log._Debug($"Found potential floating citizen instance: {citizenInstanceId} Source building: {Singleton.instance.m_instances.m_buffer[citizenInstanceId].m_sourceBuilding} Target building: {Singleton.instance.m_instances.m_buffer[citizenInstanceId].m_targetBuilding} Distance to target position: {(Singleton.instance.m_instances.m_buffer[citizenInstanceId].GetLastFramePosition() - (Vector3)Singleton.instance.m_instances.m_buffer[citizenInstanceId].m_targetPos).magnitude}"); } #endif } } Log.Info($"UtilityManager.RemoveStuckEntities(): Resetting vehicles that are waiting for a path."); for (uint vehicleId = 1; vehicleId < VehicleManager.MAX_VEHICLE_COUNT; ++vehicleId) { //Log._Debug($"UtilityManager.RemoveStuckEntities(): Processing vehicle {vehicleId}."); if ((Singleton.instance.m_vehicles.m_buffer[vehicleId].m_flags & Vehicle.Flags.WaitingPath) != 0) { if (Singleton.instance.m_vehicles.m_buffer[vehicleId].m_path != 0u) { Log.Info($"Resetting stuck vehicle {vehicleId} (waiting for path)"); Singleton.instance.ReleasePath(Singleton.instance.m_vehicles.m_buffer[vehicleId].m_path); Singleton.instance.m_vehicles.m_buffer[vehicleId].m_path = 0u; } Singleton.instance.m_vehicles.m_buffer[vehicleId].m_flags &= ~Vehicle.Flags.WaitingPath; } } Log.Info($"UtilityManager.RemoveStuckEntities(): Resetting vehicles that are parking and where no parked vehicle is assigned to the driver."); for (uint vehicleId = 1; vehicleId < VehicleManager.MAX_VEHICLE_COUNT; ++vehicleId) { //Log._Debug($"UtilityManager.RemoveStuckEntities(): Processing vehicle {vehicleId}."); if ((Singleton.instance.m_vehicles.m_buffer[vehicleId].m_flags & Vehicle.Flags.Parking) != 0) { ushort driverInstanceId = CustomPassengerCarAI.GetDriverInstanceId((ushort)vehicleId, ref Singleton.instance.m_vehicles.m_buffer[vehicleId]); uint citizenId = Singleton.instance.m_instances.m_buffer[(int)driverInstanceId].m_citizen; if (citizenId != 0u && Singleton.instance.m_citizens.m_buffer[citizenId].m_parkedVehicle == 0) { Log.Info($"Resetting vehicle {vehicleId} (parking without parked vehicle)"); Singleton.instance.m_vehicles.m_buffer[vehicleId].m_flags &= ~Vehicle.Flags.Parking; } } } Log.Info($"UtilityManager.RemoveStuckEntities(): Unpausing simulation."); Singleton.instance.ForcedSimulationPaused = false; } } } ================================================ FILE: TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using CSUtil.Commons; using CSUtil.Commons.Benchmark; using GenericGameBridge.Service; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Custom.AI; using TrafficManager.Custom.PathFinding; using TrafficManager.Geometry.Impl; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Traffic.Data; using TrafficManager.UI; using TrafficManager.Util; using UnityEngine; using static TrafficManager.Traffic.Data.PrioritySegment; namespace TrafficManager.Manager.Impl { public class VehicleBehaviorManager : AbstractCustomManager, IVehicleBehaviorManager { public const float MIN_SPEED = 8f * 0.2f; // 10 km/h public const float ICY_ROADS_MIN_SPEED = 8f * 0.4f; // 20 km/h public const float ICY_ROADS_STUDDED_MIN_SPEED = 8f * 0.8f; // 40 km/h public const float WET_ROADS_MAX_SPEED = 8f * 2f; // 100 km/h public const float WET_ROADS_FACTOR = 0.75f; public const float BROKEN_ROADS_MAX_SPEED = 8f * 1.6f; // 80 km/h public const float BROKEN_ROADS_FACTOR = 0.75f; public const VehicleInfo.VehicleType RECKLESS_VEHICLE_TYPES = VehicleInfo.VehicleType.Car; private static PathUnit.Position DUMMY_POS = default(PathUnit.Position); private static readonly uint[] POW2MASKS = new uint[] { 1u, 2u, 4u, 8u, 16u, 32u, 64u, 128u, 256u, 512u, 1024u, 2048u, 4096u, 8192u, 16384u, 32768u, 65536u, 131072u, 262144u, 524288u, 1048576u, 2097152u, 4194304u, 8388608u, 16777216u, 33554432u, 67108864u, 134217728u, 268435456u, 536870912u, 1073741824u, 2147483648u }; public static readonly VehicleBehaviorManager Instance = new VehicleBehaviorManager(); private VehicleBehaviorManager() { } public bool IsSpaceReservationAllowed(ushort transitNodeId, PathUnit.Position sourcePos, PathUnit.Position targetPos) { if (!Options.timedLightsEnabled) { return true; } if (TrafficLightSimulationManager.Instance.HasActiveTimedSimulation(transitNodeId)) { RoadBaseAI.TrafficLightState vehLightState; RoadBaseAI.TrafficLightState pedLightState; #if DEBUG Vehicle dummyVeh = default(Vehicle); #endif CustomRoadAI.GetTrafficLightState( #if DEBUG 0, ref dummyVeh, #endif transitNodeId, sourcePos.m_segment, sourcePos.m_lane, targetPos.m_segment, ref Singleton.instance.m_segments.m_buffer[sourcePos.m_segment], 0, out vehLightState, out pedLightState); if (vehLightState == RoadBaseAI.TrafficLightState.Red) { return false; } } return true; } /// /// Checks for traffic lights and priority signs when changing segments (for road & rail vehicles). /// Sets the maximum allowed speed if segment change is not allowed (otherwise has to be set by the calling method). /// /// vehicle id /// vehicle data /// last frame squared velocity /// previous path position /// previous target node /// previous lane /// current path position /// transit node /// current lane /// next path position /// next target node /// maximum allowed speed (only valid if method returns false) /// true, if the vehicle may change segments, false otherwise. public bool MayChangeSegment(ushort frontVehicleId, ref Vehicle vehicleData, float sqrVelocity, ref PathUnit.Position prevPos, ref NetSegment prevSegment, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, ref NetNode targetNode, uint laneID, ref PathUnit.Position nextPosition, ushort nextTargetNodeId, out float maxSpeed) { return MayChangeSegment(frontVehicleId, ref Constants.ManagerFactory.VehicleStateManager.VehicleStates[frontVehicleId], ref vehicleData, sqrVelocity, ref prevPos, ref prevSegment, prevTargetNodeId, prevLaneID, ref position, targetNodeId, ref targetNode, laneID, ref nextPosition, nextTargetNodeId, out maxSpeed); } protected bool MayChangeSegment(ushort frontVehicleId, ref VehicleState vehicleState, ref Vehicle vehicleData, float sqrVelocity, ref PathUnit.Position prevPos, ref NetSegment prevSegment, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, ref NetNode targetNode, uint laneID, ref PathUnit.Position nextPosition, ushort nextTargetNodeId, out float maxSpeed) { //public bool MayChangeSegment(ushort frontVehicleId, ref VehicleState vehicleState, ref Vehicle vehicleData, float sqrVelocity, bool isRecklessDriver, ref PathUnit.Position prevPos, ref NetSegment prevSegment, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, ref NetNode targetNode, uint laneID, ref PathUnit.Position nextPosition, ushort nextTargetNodeId, out float maxSpeed) { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || targetNodeId == GlobalConfig.Instance.Debug.NodeId); #endif #if BENCHMARK //using (var bm = new Benchmark(null, "MayDespawn")) { #endif if (prevTargetNodeId != targetNodeId || (vehicleData.m_blockCounter == 255 && !VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) // NON-STOCK CODE ) { // method should only be called if targetNodeId == prevTargetNode vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave; maxSpeed = 0f; return true; } #if BENCHMARK //} #endif if (vehicleState.JunctionTransitState == VehicleJunctionTransitState.Leave) { // vehicle was already allowed to leave the junction maxSpeed = 0f; return true; } if ((vehicleState.JunctionTransitState == VehicleJunctionTransitState.Stop || vehicleState.JunctionTransitState == VehicleJunctionTransitState.Blocked) && vehicleState.lastTransitStateUpdate >> VehicleState.JUNCTION_RECHECK_SHIFT >= Constants.ServiceFactory.SimulationService.CurrentFrameIndex >> VehicleState.JUNCTION_RECHECK_SHIFT) { // reuse recent result maxSpeed = 0f; return false; } bool isRecklessDriver = vehicleState.recklessDriver; var netManager = Singleton.instance; bool hasActiveTimedSimulation = (Options.timedLightsEnabled && TrafficLightSimulationManager.Instance.HasActiveTimedSimulation(targetNodeId)); bool hasTrafficLightFlag = (targetNode.m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None; if (hasActiveTimedSimulation && !hasTrafficLightFlag) { TrafficLightManager.Instance.AddTrafficLight(targetNodeId, ref targetNode); } bool hasTrafficLight = hasTrafficLightFlag || hasActiveTimedSimulation; bool checkTrafficLights = true; bool isTargetStartNode = prevSegment.m_startNode == targetNodeId; bool isLevelCrossing = (targetNode.m_flags & NetNode.Flags.LevelCrossing) != NetNode.Flags.None; if ((vehicleData.Info.m_vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail)) == VehicleInfo.VehicleType.None) { // check if to check space #if DEBUG if (debug) Log._Debug($"CustomVehicleAI.MayChangeSegment: Vehicle {frontVehicleId} is not a train."); #endif // stock priority signs if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) == (Vehicle.Flags)0 && ((NetLane.Flags)netManager.m_lanes.m_buffer[prevLaneID].m_flags & (NetLane.Flags.YieldStart | NetLane.Flags.YieldEnd)) != NetLane.Flags.None && (targetNode.m_flags & (NetNode.Flags.Junction | NetNode.Flags.TrafficLights | NetNode.Flags.OneWayIn)) == NetNode.Flags.Junction) { if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram || vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Train) { if ((vehicleData.m_flags2 & Vehicle.Flags2.Yielding) == (Vehicle.Flags2)0) { if (sqrVelocity < 0.01f) { vehicleData.m_flags2 |= Vehicle.Flags2.Yielding; } else { vehicleState.JunctionTransitState = VehicleJunctionTransitState.Stop; } maxSpeed = 0f; return false; } else { vehicleData.m_waitCounter = (byte)Mathf.Min((int)(vehicleData.m_waitCounter + 1), 4); if (vehicleData.m_waitCounter < 4) { maxSpeed = 0f; return false; } vehicleData.m_flags2 &= ~Vehicle.Flags2.Yielding; vehicleData.m_waitCounter = 0; } } else if (sqrVelocity > 0.01f) { vehicleState.JunctionTransitState = VehicleJunctionTransitState.Stop; maxSpeed = 0f; return false; } } // entering blocked junctions if (MustCheckSpace(prevPos.m_segment, isTargetStartNode, ref targetNode, isRecklessDriver)) { #if BENCHMARK //using (var bm = new Benchmark(null, "CheckSpace")) { #endif // check if there is enough space var len = vehicleState.totalLength + 4f; if (!netManager.m_lanes.m_buffer[laneID].CheckSpace(len)) { var sufficientSpace = false; if (nextPosition.m_segment != 0 && netManager.m_lanes.m_buffer[laneID].m_length < 30f) { NetNode.Flags nextTargetNodeFlags = netManager.m_nodes.m_buffer[nextTargetNodeId].m_flags; if ((nextTargetNodeFlags & (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) != NetNode.Flags.Junction || netManager.m_nodes.m_buffer[nextTargetNodeId].CountSegments() == 2) { uint nextLaneId = PathManager.GetLaneID(nextPosition); if (nextLaneId != 0u) { sufficientSpace = netManager.m_lanes.m_buffer[nextLaneId].CheckSpace(len); } } } if (!sufficientSpace) { maxSpeed = 0f; #if DEBUG if (debug) Log._Debug($"Vehicle {frontVehicleId}: Setting JunctionTransitState to BLOCKED"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Blocked; return false; } } #if BENCHMARK //} #endif } bool isJoinedJunction = ((NetLane.Flags)netManager.m_lanes.m_buffer[prevLaneID].m_flags & NetLane.Flags.JoinedJunction) != NetLane.Flags.None; checkTrafficLights = !isJoinedJunction || isLevelCrossing; } else { #if DEBUG if (debug) Log._Debug($"CustomVehicleAI.MayChangeSegment: Vehicle {frontVehicleId} is a train/metro/monorail."); #endif if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Monorail) { // vanilla traffic light flags are not rendered on monorail tracks checkTrafficLights = hasActiveTimedSimulation; } else if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Train) { // vanilla traffic light flags are not rendered on train tracks, except for level crossings checkTrafficLights = hasActiveTimedSimulation || isLevelCrossing; } } if (vehicleState.JunctionTransitState == VehicleJunctionTransitState.Blocked) { #if DEBUG if (debug) Log._Debug($"Vehicle {frontVehicleId}: Setting JunctionTransitState from BLOCKED to APPROACH"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Approach; } ITrafficPriorityManager prioMan = TrafficPriorityManager.Instance; ICustomSegmentLightsManager segLightsMan = CustomSegmentLightsManager.Instance; if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) == 0 || isLevelCrossing) { if (hasTrafficLight && checkTrafficLights) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Node {targetNodeId} has a traffic light."); } #endif RoadBaseAI.TrafficLightState vehicleLightState; bool stopCar = false; #if BENCHMARK //using (var bm = new Benchmark(null, "CheckTrafficLight")) { #endif var destinationInfo = targetNode.Info; uint currentFrameIndex = Singleton.instance.m_currentFrameIndex; uint targetNodeLower8Bits = (uint)((targetNodeId << 8) / 32768); RoadBaseAI.TrafficLightState pedestrianLightState; bool vehicles; bool pedestrians; CustomRoadAI.GetTrafficLightState( #if DEBUG frontVehicleId, ref vehicleData, #endif targetNodeId, prevPos.m_segment, prevPos.m_lane, position.m_segment, ref prevSegment, currentFrameIndex - targetNodeLower8Bits, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians); if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Car && isRecklessDriver && !isLevelCrossing) { vehicleLightState = RoadBaseAI.TrafficLightState.Green; } #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle {frontVehicleId} has TL state {vehicleLightState} at node {targetNodeId}"); #endif uint random = currentFrameIndex - targetNodeLower8Bits & 255u; if (!vehicles && random >= 196u) { vehicles = true; RoadBaseAI.SetTrafficLightState(targetNodeId, ref prevSegment, currentFrameIndex - targetNodeLower8Bits, vehicleLightState, pedestrianLightState, vehicles, pedestrians); } switch (vehicleLightState) { case RoadBaseAI.TrafficLightState.RedToGreen: if (random < 60u) { stopCar = true; } break; case RoadBaseAI.TrafficLightState.Red: stopCar = true; break; case RoadBaseAI.TrafficLightState.GreenToRed: if (random >= 30u) { stopCar = true; } break; } #if BENCHMARK //} #endif // Turn-on-red: Check if turning in the preferred direction, and if turning while it's red is allowed if ( Options.turnOnRedEnabled && stopCar && (vehicleState.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None && sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxYieldVelocity * GlobalConfig.Instance.PriorityRules.MaxYieldVelocity && !isRecklessDriver ) { IJunctionRestrictionsManager junctionRestrictionsManager = Constants.ManagerFactory.JunctionRestrictionsManager; ITurnOnRedManager turnOnRedMan = Constants.ManagerFactory.TurnOnRedManager; bool lhd = Constants.ServiceFactory.SimulationService.LeftHandDrive; int torIndex = turnOnRedMan.GetIndex(prevPos.m_segment, isTargetStartNode); if ( (turnOnRedMan.TurnOnRedSegments[torIndex].leftSegmentId == position.m_segment && junctionRestrictionsManager.IsTurnOnRedAllowed(lhd, prevPos.m_segment, isTargetStartNode)) || (turnOnRedMan.TurnOnRedSegments[torIndex].rightSegmentId == position.m_segment && junctionRestrictionsManager.IsTurnOnRedAllowed(!lhd, prevPos.m_segment, isTargetStartNode)) ) { #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle may turn on red to target segment {position.m_segment}, lane {position.m_lane}"); #endif stopCar = false; } } // check priority rules at unprotected traffic lights if (!stopCar && Options.prioritySignsEnabled && Options.trafficLightPriorityRules && segLightsMan.IsSegmentLight(prevPos.m_segment, isTargetStartNode)) { bool hasPriority = true; #if BENCHMARK //using (var bm = new Benchmark(null, "CheckPriorityRulesAtTTL")) { #endif hasPriority = prioMan.HasPriority(frontVehicleId, ref vehicleData, ref prevPos, targetNodeId, isTargetStartNode, ref position, ref targetNode); #if BENCHMARK //} #endif if (!hasPriority) { // green light but other cars are incoming and they have priority: stop #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Green traffic light (or turn on red allowed) but detected traffic with higher priority: stop."); #endif stopCar = true; } } if (stopCar) { #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to STOP"); #endif if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram || vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Train) { vehicleData.m_flags2 |= Vehicle.Flags2.Yielding; vehicleData.m_waitCounter = 0; } vehicleState.JunctionTransitState = VehicleJunctionTransitState.Stop; maxSpeed = 0f; vehicleData.m_blockCounter = 0; return false; } else { #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE ({vehicleLightState})"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave; if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram || vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Train) { vehicleData.m_flags2 &= ~Vehicle.Flags2.Yielding; vehicleData.m_waitCounter = 0; } } } else if (Options.prioritySignsEnabled && vehicleData.Info.m_vehicleType != VehicleInfo.VehicleType.Monorail) { #if BENCHMARK //using (var bm = new Benchmark(null, "CheckPriorityRules")) { #endif #if DEBUG //bool debug = destinationNodeId == 10864; //bool debug = destinationNodeId == 13531; //bool debug = false;// targetNodeId == 5027; #endif //bool debug = false; #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle is arriving @ seg. {prevPos.m_segment} ({position.m_segment}, {nextPosition.m_segment}), node {targetNodeId} which is not a traffic light."); #endif var sign = prioMan.GetPrioritySign(prevPos.m_segment, isTargetStartNode); if (sign != PrioritySegment.PriorityType.None) { #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle is arriving @ seg. {prevPos.m_segment} ({position.m_segment}, {nextPosition.m_segment}), node {targetNodeId} which is not a traffic light and is a priority segment."); #endif #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): JunctionTransitState={vehicleState.JunctionTransitState.ToString()}"); #endif if (vehicleState.JunctionTransitState == VehicleJunctionTransitState.None) { #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to APPROACH (prio)"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Approach; } if (vehicleState.JunctionTransitState != VehicleJunctionTransitState.Leave) { bool hasPriority; switch (sign) { case PriorityType.Stop: #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): STOP sign. waittime={vehicleState.waitTime}, sqrVelocity={sqrVelocity}"); #endif maxSpeed = 0f; if (vehicleState.waitTime < GlobalConfig.Instance.PriorityRules.MaxPriorityWaitTime) { #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to STOP (wait) waitTime={vehicleState.waitTime}"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Stop; if (sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity * GlobalConfig.Instance.PriorityRules.MaxStopVelocity) { vehicleState.waitTime++; //float minStopWaitTime = Singleton.instance.m_randomizer.UInt32(3); if (vehicleState.waitTime >= 2) { if (Options.simAccuracy >= 4) { vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave; } else { hasPriority = prioMan.HasPriority(frontVehicleId, ref vehicleData, ref prevPos, targetNodeId, isTargetStartNode, ref position, ref targetNode); #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): hasPriority={hasPriority}"); #endif if (!hasPriority) { vehicleData.m_blockCounter = 0; return false; } #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE (min wait timeout)"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave; } #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle must first come to a full stop in front of the stop sign."); #endif return false; } } else { #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle has come to a full stop."); #endif vehicleState.waitTime = 0; return false; } } else { #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Max. wait time exceeded. Setting JunctionTransitState to LEAVE (max wait timeout)"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave; } break; case PriorityType.Yield: #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): YIELD sign. waittime={vehicleState.waitTime}"); #endif if (vehicleState.waitTime < GlobalConfig.Instance.PriorityRules.MaxPriorityWaitTime) { vehicleState.waitTime++; #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to STOP (wait)"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Stop; if (sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxYieldVelocity * GlobalConfig.Instance.PriorityRules.MaxYieldVelocity || Options.simAccuracy <= 2) { if (Options.simAccuracy >= 4) { vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave; } else { hasPriority = prioMan.HasPriority(frontVehicleId, ref vehicleData, ref prevPos, targetNodeId, isTargetStartNode, ref position, ref targetNode); #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): hasPriority: {hasPriority}"); #endif if (!hasPriority) { vehicleData.m_blockCounter = 0; maxSpeed = 0f; return false; } else { #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE (no incoming cars)"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave; } } } else { #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle has not yet reached yield speed (reduce {sqrVelocity} by {vehicleState.reduceSqrSpeedByValueToYield})"); #endif // vehicle has not yet reached yield speed maxSpeed = GlobalConfig.Instance.PriorityRules.MaxYieldVelocity; return false; } } else { #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE (max wait timeout)"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave; } break; case PriorityType.Main: default: #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): MAIN sign. waittime={vehicleState.waitTime}"); #endif maxSpeed = 0f; if (Options.simAccuracy == 4) return true; if (vehicleState.waitTime < GlobalConfig.Instance.PriorityRules.MaxPriorityWaitTime) { vehicleState.waitTime++; #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to STOP (wait)"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Stop; hasPriority = prioMan.HasPriority(frontVehicleId, ref vehicleData, ref prevPos, targetNodeId, isTargetStartNode, ref position, ref targetNode); #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): {hasPriority}"); #endif if (!hasPriority) { vehicleData.m_blockCounter = 0; return false; } #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE (no conflicting car)"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave; } else { #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Max. wait time exceeded. Setting JunctionTransitState to LEAVE (max wait timeout)"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave; } return true; } } else if (sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity * GlobalConfig.Instance.PriorityRules.MaxStopVelocity && (vehicleState.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None) { // vehicle is not moving. reset allowance to leave junction #if DEBUG if (debug) Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState from LEAVE to BLOCKED (speed to low)"); #endif vehicleState.JunctionTransitState = VehicleJunctionTransitState.Blocked; maxSpeed = 0f; return false; } } #if BENCHMARK //} #endif } } maxSpeed = 0f; // maxSpeed should be set by caller return true; } /// /// Checks for traffic lights and priority signs when changing segments (for rail vehicles). /// Sets the maximum allowed speed if segment change is not allowed (otherwise has to be set by the calling method). /// /// vehicle id /// vehicle data /// last frame squared velocity /// previous path position /// previous target node /// previous lane /// current path position /// transit node /// current lane /// maximum allowed speed (only valid if method returns false) /// true, if the vehicle may change segments, false otherwise. public bool MayChangeSegment(ushort frontVehicleId, ref Vehicle vehicleData, float sqrVelocity, ref PathUnit.Position prevPos, ref NetSegment prevSegment, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, ref NetNode targetNode, uint laneID, out float maxSpeed) { return MayChangeSegment(frontVehicleId, ref Constants.ManagerFactory.VehicleStateManager.VehicleStates[frontVehicleId], ref vehicleData, sqrVelocity, ref prevPos, ref prevSegment, prevTargetNodeId, prevLaneID, ref position, targetNodeId, ref targetNode, laneID, ref DUMMY_POS, 0, out maxSpeed); } /// /// Checks if a vehicle must check if the subsequent segment is empty while going from segment /// through node . /// /// source segment id /// is transit node start node of source segment? /// transit node /// reckless driver? /// protected bool MustCheckSpace(ushort segmentId, bool startNode, ref NetNode node, bool isRecklessDriver) { bool checkSpace; if (isRecklessDriver) { checkSpace = (node.m_flags & NetNode.Flags.LevelCrossing) != NetNode.Flags.None; } else { if (Options.junctionRestrictionsEnabled) { checkSpace = !JunctionRestrictionsManager.Instance.IsEnteringBlockedJunctionAllowed(segmentId, startNode); } else { checkSpace = (node.m_flags & (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) == NetNode.Flags.Junction && node.CountSegments() != 2; } } return checkSpace; } public bool MayDespawn(ref Vehicle vehicleData) { return !Options.disableDespawning || ((vehicleData.m_flags2 & (Vehicle.Flags2.Blown | Vehicle.Flags2.Floating)) != 0) || (vehicleData.m_flags & Vehicle.Flags.Parking) != 0; } public float CalcMaxSpeed(ushort vehicleId, ref VehicleState state, VehicleInfo vehicleInfo, PathUnit.Position position, ref NetSegment segment, Vector3 pos, float maxSpeed) { if (Singleton.instance.m_treatWetAsSnow) { DistrictManager districtManager = Singleton.instance; byte district = districtManager.GetDistrict(pos); DistrictPolicies.CityPlanning cityPlanningPolicies = districtManager.m_districts.m_buffer[(int)district].m_cityPlanningPolicies; if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.StuddedTires) != DistrictPolicies.CityPlanning.None) { if (Options.strongerRoadConditionEffects) { if (maxSpeed > ICY_ROADS_STUDDED_MIN_SPEED) maxSpeed = ICY_ROADS_STUDDED_MIN_SPEED + (float)(255 - segment.m_wetness) * 0.0039215686f * (maxSpeed - ICY_ROADS_STUDDED_MIN_SPEED); } else { maxSpeed *= 1f - (float)segment.m_wetness * 0.0005882353f; // vanilla: -15% .. ±0% } districtManager.m_districts.m_buffer[(int)district].m_cityPlanningPoliciesEffect |= DistrictPolicies.CityPlanning.StuddedTires; } else { if (Options.strongerRoadConditionEffects) { if (maxSpeed > ICY_ROADS_MIN_SPEED) maxSpeed = ICY_ROADS_MIN_SPEED + (float)(255 - segment.m_wetness) * 0.0039215686f * (maxSpeed - ICY_ROADS_MIN_SPEED); } else { maxSpeed *= 1f - (float)segment.m_wetness * 0.00117647066f; // vanilla: -30% .. ±0% } } } else { if (Options.strongerRoadConditionEffects) { float minSpeed = Math.Min(maxSpeed * WET_ROADS_FACTOR, WET_ROADS_MAX_SPEED); // custom: -25% .. 0 if (maxSpeed > minSpeed) maxSpeed = minSpeed + (float)(255 - segment.m_wetness) * 0.0039215686f * (maxSpeed - minSpeed); } else { maxSpeed *= 1f - (float)segment.m_wetness * 0.0005882353f; // vanilla: -15% .. ±0% } } if (Options.strongerRoadConditionEffects) { float minSpeed = Math.Min(maxSpeed * BROKEN_ROADS_FACTOR, BROKEN_ROADS_MAX_SPEED); if (maxSpeed > minSpeed) { maxSpeed = minSpeed + (float)segment.m_condition * 0.0039215686f * (maxSpeed - minSpeed); } } else { maxSpeed *= 1f + (float)segment.m_condition * 0.0005882353f; // vanilla: ±0% .. +15 % } maxSpeed = ApplyRealisticSpeeds(maxSpeed, vehicleId, ref state, vehicleInfo); maxSpeed = Math.Max(MIN_SPEED, maxSpeed); // at least 10 km/h return maxSpeed; } public uint GetStaticVehicleRand(ushort vehicleId) { return vehicleId % 100u; } public uint GetTimedVehicleRand(ushort vehicleId) { uint intv = VehicleState.MAX_TIMED_RAND / 2u; uint range = intv * (uint)(vehicleId % (100u / intv)); // is one of [0, 50] uint step = VehicleStateManager.Instance.VehicleStates[vehicleId].timedRand; if (step >= intv) { step = VehicleState.MAX_TIMED_RAND - step; } return range + step; } public float ApplyRealisticSpeeds(float speed, ushort vehicleId, ref VehicleState state, VehicleInfo vehicleInfo) { if (Options.realisticSpeeds) { float vehicleRand = 0.01f * (float)GetTimedVehicleRand(vehicleId); if (vehicleInfo.m_isLargeVehicle) { speed *= 0.75f + vehicleRand * 0.25f; // a little variance, 0.75 .. 1 } else if (state.recklessDriver) { speed *= 1.1f + vehicleRand * 0.5f; // woohooo, 1.1 .. 1.6 } else { speed *= 0.8f + vehicleRand * 0.5f; // a little variance, 0.8 .. 1.3 } } else if (state.recklessDriver) { speed *= 1.5f; } return speed; } public bool IsRecklessDriver(ushort vehicleId, ref Vehicle vehicleData) { if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0) { return true; } if (Options.evacBussesMayIgnoreRules && vehicleData.Info.GetService() == ItemClass.Service.Disaster) { return true; } if (Options.recklessDrivers == 3) { return false; } if ((vehicleData.Info.m_vehicleType & RECKLESS_VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { return false; } return (uint)vehicleId % Options.getRecklessDriverModulo() == 0; } public int FindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref VehicleState vehicleState, uint currentLaneId, PathUnit.Position currentPathPos, NetInfo currentSegInfo, PathUnit.Position next1PathPos, NetInfo next1SegInfo, PathUnit.Position next2PathPos, NetInfo next2SegInfo, PathUnit.Position next3PathPos, NetInfo next3SegInfo, PathUnit.Position next4PathPos) { try { GlobalConfig conf = GlobalConfig.Instance; #if DEBUG bool debug = false; if (conf.Debug.Switches[17]) { ushort nodeId = Services.NetService.GetSegmentNodeId(currentPathPos.m_segment, currentPathPos.m_offset < 128); debug = (conf.Debug.VehicleId == 0 || conf.Debug.VehicleId == vehicleId) && (conf.Debug.NodeId == 0 || conf.Debug.NodeId == nodeId); } if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): currentLaneId={currentLaneId}, currentPathPos=[seg={currentPathPos.m_segment}, lane={currentPathPos.m_lane}, off={currentPathPos.m_offset}] next1PathPos=[seg={next1PathPos.m_segment}, lane={next1PathPos.m_lane}, off={next1PathPos.m_offset}] next2PathPos=[seg={next2PathPos.m_segment}, lane={next2PathPos.m_lane}, off={next2PathPos.m_offset}] next3PathPos=[seg={next3PathPos.m_segment}, lane={next3PathPos.m_lane}, off={next3PathPos.m_offset}] next4PathPos=[seg={next4PathPos.m_segment}, lane={next4PathPos.m_lane}, off={next4PathPos.m_offset}]"); } #endif if (vehicleState.lastAltLaneSelSegmentId == currentPathPos.m_segment) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping alternative lane selection: Already calculated."); } #endif return next1PathPos.m_lane; } vehicleState.lastAltLaneSelSegmentId = currentPathPos.m_segment; bool recklessDriver = vehicleState.recklessDriver; // cur -> next1 float vehicleLength = 1f + vehicleState.totalLength; bool startNode = currentPathPos.m_offset < 128; uint currentFwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentLaneId, startNode); #if DEBUG if (currentFwdRoutingIndex < 0 || currentFwdRoutingIndex >= RoutingManager.Instance.laneEndForwardRoutings.Length) { Log.Error($"Invalid array index: currentFwdRoutingIndex={currentFwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.laneEndForwardRoutings.Length} (currentLaneId={currentLaneId}, startNode={startNode})"); } #endif if (!RoutingManager.Instance.laneEndForwardRoutings[currentFwdRoutingIndex].routed) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): No forward routing for next path position available."); } #endif return next1PathPos.m_lane; } LaneTransitionData[] currentFwdTransitions = RoutingManager.Instance.laneEndForwardRoutings[currentFwdRoutingIndex].transitions; if (currentFwdTransitions == null) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): No forward transitions found for current lane {currentLaneId} at startNode {startNode}."); } #endif return next1PathPos.m_lane; } VehicleInfo vehicleInfo = vehicleData.Info; float vehicleMaxSpeed = vehicleInfo.m_maxSpeed / 8f; float vehicleCurSpeed = vehicleData.GetLastFrameVelocity().magnitude / 8f; float bestStayMeanSpeed = 0f; float bestStaySpeedDiff = float.PositiveInfinity; // best speed difference on next continuous lane int bestStayTotalLaneDist = int.MaxValue; byte bestStayNext1LaneIndex = next1PathPos.m_lane; float bestOptMeanSpeed = 0f; float bestOptSpeedDiff = float.PositiveInfinity; // best speed difference on all next lanes int bestOptTotalLaneDist = int.MaxValue; byte bestOptNext1LaneIndex = next1PathPos.m_lane; bool foundSafeLaneChange = false; //bool foundClearBackLane = false; //bool foundClearFwdLane = false; //ushort reachableNext1LanesMask = 0; uint reachableNext2LanesMask = 0; uint reachableNext3LanesMask = 0; //int numReachableNext1Lanes = 0; int numReachableNext2Lanes = 0; int numReachableNext3Lanes = 0; #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Starting lane-finding algorithm now. vehicleMaxSpeed={vehicleMaxSpeed}, vehicleCurSpeed={vehicleCurSpeed} vehicleLength={vehicleLength}"); } #endif uint mask; for (int i = 0; i < currentFwdTransitions.Length; ++i) { if (currentFwdTransitions[i].segmentId != next1PathPos.m_segment) { continue; } if (!(currentFwdTransitions[i].type == LaneEndTransitionType.Default || currentFwdTransitions[i].type == LaneEndTransitionType.LaneConnection || (recklessDriver && currentFwdTransitions[i].type == LaneEndTransitionType.Relaxed)) ) { continue; } if (currentFwdTransitions[i].distance > 1) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (distance too large)"); } #endif continue; } if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, next1PathPos.m_segment, currentFwdTransitions[i].laneIndex, next1SegInfo)) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (vehicle restrictions)"); } #endif continue; } int minTotalLaneDist = int.MaxValue; if (next2PathPos.m_segment != 0) { // next1 -> next2 uint next1FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentFwdTransitions[i].laneId, !currentFwdTransitions[i].startNode); #if DEBUG if (next1FwdRoutingIndex < 0 || next1FwdRoutingIndex >= RoutingManager.Instance.laneEndForwardRoutings.Length) { Log.Error($"Invalid array index: next1FwdRoutingIndex={next1FwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.laneEndForwardRoutings.Length} (currentFwdTransitions[i].laneId={currentFwdTransitions[i].laneId}, !currentFwdTransitions[i].startNode={!currentFwdTransitions[i].startNode})"); } #endif #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exploring transitions for next1 lane id={currentFwdTransitions[i].laneId}, seg.={currentFwdTransitions[i].segmentId}, index={currentFwdTransitions[i].laneIndex}, startNode={!currentFwdTransitions[i].startNode}: {RoutingManager.Instance.laneEndForwardRoutings[next1FwdRoutingIndex]}"); } #endif if (!RoutingManager.Instance.laneEndForwardRoutings[next1FwdRoutingIndex].routed) { continue; } LaneTransitionData[] next1FwdTransitions = RoutingManager.Instance.laneEndForwardRoutings[next1FwdRoutingIndex].transitions; if (next1FwdTransitions == null) { continue; } bool foundNext1Next2 = false; for (int j = 0; j < next1FwdTransitions.Length; ++j) { if (next1FwdTransitions[j].segmentId != next2PathPos.m_segment) { continue; } if (!(next1FwdTransitions[j].type == LaneEndTransitionType.Default || next1FwdTransitions[j].type == LaneEndTransitionType.LaneConnection || (recklessDriver && next1FwdTransitions[j].type == LaneEndTransitionType.Relaxed)) ) { continue; } if (next1FwdTransitions[j].distance > 1) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 transition {next1FwdTransitions[j]} (distance too large)"); } #endif continue; } if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, next2PathPos.m_segment, next1FwdTransitions[j].laneIndex, next2SegInfo)) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 transition {next1FwdTransitions[j]} (vehicle restrictions)"); } #endif continue; } if (next3PathPos.m_segment != 0) { // next2 -> next3 uint next2FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(next1FwdTransitions[j].laneId, !next1FwdTransitions[j].startNode); #if DEBUG if (next2FwdRoutingIndex < 0 || next2FwdRoutingIndex >= RoutingManager.Instance.laneEndForwardRoutings.Length) { Log.Error($"Invalid array index: next2FwdRoutingIndex={next2FwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.laneEndForwardRoutings.Length} (next1FwdTransitions[j].laneId={next1FwdTransitions[j].laneId}, !next1FwdTransitions[j].startNode={!next1FwdTransitions[j].startNode})"); } #endif #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exploring transitions for next2 lane id={next1FwdTransitions[j].laneId}, seg.={next1FwdTransitions[j].segmentId}, index={next1FwdTransitions[j].laneIndex}, startNode={!next1FwdTransitions[j].startNode}: {RoutingManager.Instance.laneEndForwardRoutings[next2FwdRoutingIndex]}"); } #endif if (!RoutingManager.Instance.laneEndForwardRoutings[next2FwdRoutingIndex].routed) { continue; } LaneTransitionData[] next2FwdTransitions = RoutingManager.Instance.laneEndForwardRoutings[next2FwdRoutingIndex].transitions; if (next2FwdTransitions == null) { continue; } bool foundNext2Next3 = false; for (int k = 0; k < next2FwdTransitions.Length; ++k) { if (next2FwdTransitions[k].segmentId != next3PathPos.m_segment) { continue; } if (!(next2FwdTransitions[k].type == LaneEndTransitionType.Default || next2FwdTransitions[k].type == LaneEndTransitionType.LaneConnection || (recklessDriver && next2FwdTransitions[k].type == LaneEndTransitionType.Relaxed)) ) { continue; } if (next2FwdTransitions[k].distance > 1) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next2 transition {next2FwdTransitions[k]} (distance too large)"); } #endif continue; } if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, next3PathPos.m_segment, next2FwdTransitions[k].laneIndex, next3SegInfo)) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next2 transition {next2FwdTransitions[k]} (vehicle restrictions)"); } #endif continue; } if (next4PathPos.m_segment != 0) { // next3 -> next4 uint next3FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(next2FwdTransitions[k].laneId, !next2FwdTransitions[k].startNode); #if DEBUG if (next3FwdRoutingIndex < 0 || next3FwdRoutingIndex >= RoutingManager.Instance.laneEndForwardRoutings.Length) { Log.Error($"Invalid array index: next3FwdRoutingIndex={next3FwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.laneEndForwardRoutings.Length} (next2FwdTransitions[k].laneId={next2FwdTransitions[k].laneId}, !next2FwdTransitions[k].startNode={!next2FwdTransitions[k].startNode})"); } #endif #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exploring transitions for next3 lane id={next2FwdTransitions[k].laneId}, seg.={next2FwdTransitions[k].segmentId}, index={next2FwdTransitions[k].laneIndex}, startNode={!next2FwdTransitions[k].startNode}: {RoutingManager.Instance.laneEndForwardRoutings[next3FwdRoutingIndex]}"); } #endif if (!RoutingManager.Instance.laneEndForwardRoutings[next3FwdRoutingIndex].routed) { continue; } LaneTransitionData[] next3FwdTransitions = RoutingManager.Instance.laneEndForwardRoutings[next3FwdRoutingIndex].transitions; if (next3FwdTransitions == null) { continue; } // check if original next4 lane is accessible via the next3 lane bool foundNext3Next4 = false; for (int l = 0; l < next3FwdTransitions.Length; ++l) { if (next3FwdTransitions[l].segmentId != next4PathPos.m_segment) { continue; } if (!(next3FwdTransitions[l].type == LaneEndTransitionType.Default || next3FwdTransitions[l].type == LaneEndTransitionType.LaneConnection || (recklessDriver && next3FwdTransitions[l].type == LaneEndTransitionType.Relaxed)) ) { continue; } if (next3FwdTransitions[l].distance > 1) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next3 transition {next3FwdTransitions[l]} (distance too large)"); } #endif continue; } if (next3FwdTransitions[l].laneIndex == next4PathPos.m_lane) { // we found a valid routing from [current lane] (currentPathPos) to [next1 lane] (next1Pos), [next2 lane] (next2Pos), [next3 lane] (next3Pos), and [next4 lane] (next4Pos) foundNext3Next4 = true; int totalLaneDist = next1FwdTransitions[j].distance + next2FwdTransitions[k].distance + next3FwdTransitions[l].distance; if (totalLaneDist < minTotalLaneDist) { minTotalLaneDist = totalLaneDist; } #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Found candidate transition with totalLaneDist={totalLaneDist}: {currentLaneId} -> {currentFwdTransitions[i]} -> {next1FwdTransitions[j]} -> {next2FwdTransitions[k]} -> {next3FwdTransitions[l]}"); } #endif break; } } // for l if (foundNext3Next4) { foundNext2Next3 = true; } } else { foundNext2Next3 = true; } if (foundNext2Next3) { mask = POW2MASKS[next2FwdTransitions[k].laneIndex]; if ((reachableNext3LanesMask & mask) == 0) { ++numReachableNext3Lanes; reachableNext3LanesMask |= mask; } } } // for k if (foundNext2Next3) { foundNext1Next2 = true; } } else { foundNext1Next2 = true; } if (foundNext1Next2) { mask = POW2MASKS[next1FwdTransitions[j].laneIndex]; if ((reachableNext2LanesMask & mask) == 0) { ++numReachableNext2Lanes; reachableNext2LanesMask |= mask; } } } // for j if (next3PathPos.m_segment != 0 && !foundNext1Next2) { // go to next candidate next1 lane continue; } } /*mask = POW2MASKS[currentFwdTransitions[i].laneIndex]; if ((reachableNext1LanesMask & mask) == 0) { ++numReachableNext1Lanes; reachableNext1LanesMask |= mask; }*/ // This lane is a valid candidate. //bool next1StartNode = next1PathPos.m_offset < 128; //ushort next1TransitNode = 0; //Services.NetService.ProcessSegment(next1PathPos.m_segment, delegate (ushort next1SegId, ref NetSegment next1Seg) { // next1TransitNode = next1StartNode ? next1Seg.m_startNode : next1Seg.m_endNode; // return true; //}); //bool next1TransitNodeIsJunction = false; //Services.NetService.ProcessNode(next1TransitNode, delegate (ushort nId, ref NetNode node) { // next1TransitNodeIsJunction = (node.m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; // return true; //}); /* * Check if next1 lane is clear */ #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Checking for traffic on next1 lane id={currentFwdTransitions[i].laneId}."); } #endif bool laneChange = currentFwdTransitions[i].distance != 0; /*bool next1LaneClear = true; if (laneChange) { // check for traffic on next1 lane float reservedSpace = 0; Services.NetService.ProcessLane(currentFwdTransitions[i].laneId, delegate (uint next1LaneId, ref NetLane next1Lane) { reservedSpace = next1Lane.GetReservedSpace(); return true; }); if (currentFwdTransitions[i].laneIndex == next1PathPos.m_lane) { reservedSpace -= vehicleLength; } next1LaneClear = reservedSpace <= (recklessDriver ? conf.AltLaneSelectionMaxRecklessReservedSpace : conf.AltLaneSelectionMaxReservedSpace); } if (foundClearFwdLane && !next1LaneClear) { continue; }*/ /* * Check traffic on the lanes in front of the candidate lane in order to prevent vehicles from backing up traffic */ bool prevLanesClear = true; if (laneChange) { uint next1BackRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentFwdTransitions[i].laneId, currentFwdTransitions[i].startNode); #if DEBUG if (next1BackRoutingIndex < 0 || next1BackRoutingIndex >= RoutingManager.Instance.laneEndForwardRoutings.Length) { Log.Error($"Invalid array index: next1BackRoutingIndex={next1BackRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.laneEndForwardRoutings.Length} (currentFwdTransitions[i].laneId={currentFwdTransitions[i].laneId}, currentFwdTransitions[i].startNode={currentFwdTransitions[i].startNode})"); } #endif if (!RoutingManager.Instance.laneEndBackwardRoutings[next1BackRoutingIndex].routed) { continue; } LaneTransitionData[] next1BackTransitions = RoutingManager.Instance.laneEndBackwardRoutings[next1BackRoutingIndex].transitions; if (next1BackTransitions == null) { continue; } for (int j = 0; j < next1BackTransitions.Length; ++j) { if (next1BackTransitions[j].segmentId != currentPathPos.m_segment || next1BackTransitions[j].laneIndex == currentPathPos.m_lane) { continue; } if (!(next1BackTransitions[j].type == LaneEndTransitionType.Default || next1BackTransitions[j].type == LaneEndTransitionType.LaneConnection || (recklessDriver && next1BackTransitions[j].type == LaneEndTransitionType.Relaxed)) ) { continue; } if (next1BackTransitions[j].distance > 1) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 backward transition {next1BackTransitions[j]} (distance too large)"); } #endif continue; } #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Checking for upcoming traffic in front of next1 lane id={currentFwdTransitions[i].laneId}. Checking back transition {next1BackTransitions[j]}"); } #endif Services.NetService.ProcessLane(next1BackTransitions[j].laneId, delegate (uint prevLaneId, ref NetLane prevLane) { prevLanesClear = prevLane.GetReservedSpace() <= (recklessDriver ? conf.DynamicLaneSelection.MaxRecklessReservedSpace : conf.DynamicLaneSelection.MaxReservedSpace); return true; }); if (!prevLanesClear) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Back lane {next1BackTransitions[j].laneId} is not clear!"); } #endif break; } else { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Back lane {next1BackTransitions[j].laneId} is clear!"); } #endif } } } #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Checking for coming up traffic in front of next1 lane. prevLanesClear={prevLanesClear}"); } #endif if (/*foundClearBackLane*/foundSafeLaneChange && !prevLanesClear) { continue; } // calculate lane metric #if DEBUG if (currentFwdTransitions[i].laneIndex < 0 || currentFwdTransitions[i].laneIndex >= next1SegInfo.m_lanes.Length) { Log.Error($"Invalid array index: currentFwdTransitions[i].laneIndex={currentFwdTransitions[i].laneIndex}, next1SegInfo.m_lanes.Length={next1SegInfo.m_lanes.Length}"); } #endif NetInfo.Lane next1LaneInfo = next1SegInfo.m_lanes[currentFwdTransitions[i].laneIndex]; float next1MaxSpeed = SpeedLimitManager.Instance.GetLockFreeGameSpeedLimit(currentFwdTransitions[i].segmentId, currentFwdTransitions[i].laneIndex, currentFwdTransitions[i].laneId, next1LaneInfo); float targetSpeed = Math.Min(vehicleMaxSpeed, ApplyRealisticSpeeds(next1MaxSpeed, vehicleId, ref vehicleState, vehicleInfo)); ushort meanSpeed = TrafficMeasurementManager.Instance.CalcLaneRelativeMeanSpeed(currentFwdTransitions[i].segmentId, currentFwdTransitions[i].laneIndex, currentFwdTransitions[i].laneId, next1LaneInfo); float relMeanSpeedInPercent = meanSpeed / (TrafficMeasurementManager.REF_REL_SPEED / TrafficMeasurementManager.REF_REL_SPEED_PERCENT_DENOMINATOR); float randSpeed = 0f; if (conf.DynamicLaneSelection.LaneSpeedRandInterval > 0) { randSpeed = Services.SimulationService.Randomizer.Int32((uint)conf.DynamicLaneSelection.LaneSpeedRandInterval + 1u) - conf.DynamicLaneSelection.LaneSpeedRandInterval / 2f; relMeanSpeedInPercent += randSpeed; } float relMeanSpeed = relMeanSpeedInPercent / (float)TrafficMeasurementManager.REF_REL_SPEED_PERCENT_DENOMINATOR; float next1MeanSpeed = relMeanSpeed * next1MaxSpeed; /*if ( #if DEBUG conf.Debug.Switches[19] && #endif next1LaneInfo.m_similarLaneCount > 1) { float relLaneInnerIndex = ((float)RoutingManager.Instance.CalcOuterSimilarLaneIndex(next1LaneInfo) / (float)next1LaneInfo.m_similarLaneCount); float rightObligationFactor = conf.AltLaneSelectionMostOuterLaneSpeedFactor + (conf.AltLaneSelectionMostInnerLaneSpeedFactor - conf.AltLaneSelectionMostOuterLaneSpeedFactor) * relLaneInnerIndex; #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Applying obligation factor to next1 lane {currentFwdTransitions[i].laneId}: relLaneInnerIndex={relLaneInnerIndex}, rightObligationFactor={rightObligationFactor}, next1MaxSpeed={next1MaxSpeed}, relMeanSpeedInPercent={relMeanSpeedInPercent}, randSpeed={randSpeed}, next1MeanSpeed={next1MeanSpeed} => new next1MeanSpeed={Mathf.Max(rightObligationFactor * next1MaxSpeed, next1MeanSpeed)}"); } #endif next1MeanSpeed = Mathf.Min(rightObligationFactor * next1MaxSpeed, next1MeanSpeed); }*/ float speedDiff = next1MeanSpeed - targetSpeed; // > 0: lane is faster than vehicle would go. < 0: vehicle could go faster than this lane allows #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Calculated metric for next1 lane {currentFwdTransitions[i].laneId}: next1MaxSpeed={next1MaxSpeed} next1MeanSpeed={next1MeanSpeed} targetSpeed={targetSpeed} speedDiff={speedDiff} bestSpeedDiff={bestOptSpeedDiff} bestStaySpeedDiff={bestStaySpeedDiff}"); } #endif if (!laneChange) { if ((float.IsInfinity(bestStaySpeedDiff) || (bestStaySpeedDiff < 0 && speedDiff > bestStaySpeedDiff) || (bestStaySpeedDiff > 0 && speedDiff < bestStaySpeedDiff && speedDiff >= 0)) ) { bestStaySpeedDiff = speedDiff; bestStayNext1LaneIndex = currentFwdTransitions[i].laneIndex; bestStayMeanSpeed = next1MeanSpeed; bestStayTotalLaneDist = minTotalLaneDist; } } else { //bool foundFirstClearFwdLane = laneChange && !foundClearFwdLane && next1LaneClear; //bool foundFirstClearBackLane = laneChange && !foundClearBackLane && prevLanesClear; bool foundFirstSafeLaneChange = !foundSafeLaneChange && /*next1LaneClear &&*/ prevLanesClear; if (/*(foundFirstClearFwdLane && !foundClearBackLane) || (foundFirstClearBackLane && !foundClearFwdLane) ||*/ foundFirstSafeLaneChange || float.IsInfinity(bestOptSpeedDiff) || (bestOptSpeedDiff < 0 && speedDiff > bestOptSpeedDiff) || (bestOptSpeedDiff > 0 && speedDiff < bestOptSpeedDiff && speedDiff >= 0)) { bestOptSpeedDiff = speedDiff; bestOptNext1LaneIndex = currentFwdTransitions[i].laneIndex; bestOptMeanSpeed = next1MeanSpeed; bestOptTotalLaneDist = minTotalLaneDist; } /*if (foundFirstClearBackLane) { foundClearBackLane = true; } if (foundFirstClearFwdLane) { foundClearFwdLane = true; }*/ if (foundFirstSafeLaneChange) { foundSafeLaneChange = true; } } } // for i #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): best lane index: {bestOptNext1LaneIndex}, best stay lane index: {bestStayNext1LaneIndex}, path lane index: {next1PathPos.m_lane})\nbest speed diff: {bestOptSpeedDiff}, best stay speed diff: {bestStaySpeedDiff}\nfoundClearBackLane=XXfoundClearBackLaneXX, foundClearFwdLane=XXfoundClearFwdLaneXX, foundSafeLaneChange={foundSafeLaneChange}\nbestMeanSpeed={bestOptMeanSpeed}, bestStayMeanSpeed={bestStayMeanSpeed}"); } #endif if (float.IsInfinity(bestStaySpeedDiff)) { // no continuous lane found #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> no continuous lane found -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); } #endif return bestOptNext1LaneIndex; } if (float.IsInfinity(bestOptSpeedDiff)) { // no lane change found #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> no lane change found -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); } #endif return bestStayNext1LaneIndex; } // decide if vehicle should stay or change // vanishing lane change opportunity detection int vehSel = vehicleId % 6; #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): vehMod4={vehSel} numReachableNext2Lanes={numReachableNext2Lanes} numReachableNext3Lanes={numReachableNext3Lanes}"); } #endif if ((numReachableNext3Lanes == 1 && vehSel <= 2) || // 3/6 % of all vehicles will change lanes 3 segments in front (numReachableNext2Lanes == 1 && vehSel <= 4) // 2/6 % of all vehicles will change lanes 2 segments in front, 1/5 will change at the last opportunity ) { // vehicle must reach a certain lane since lane changing opportunities will vanish #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): vanishing lane change opportunities detected: numReachableNext2Lanes={numReachableNext2Lanes} numReachableNext3Lanes={numReachableNext3Lanes}, vehSel={vehSel}, bestOptTotalLaneDist={bestOptTotalLaneDist}, bestStayTotalLaneDist={bestStayTotalLaneDist}"); } #endif if (bestOptTotalLaneDist < bestStayTotalLaneDist) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> vanishing lane change opportunities -- selecting bestOptTotalLaneDist={bestOptTotalLaneDist}"); } #endif return bestOptNext1LaneIndex; } else { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> vanishing lane change opportunities -- selecting bestStayTotalLaneDist={bestStayTotalLaneDist}"); } #endif return bestStayNext1LaneIndex; } } if (bestStaySpeedDiff == 0 || bestOptMeanSpeed < 0.1f) { /* * edge cases: * (1) continuous lane is super optimal * (2) best mean speed is near zero */ #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> edge case: continuous lane is optimal ({bestStaySpeedDiff == 0}) / best mean speed is near zero ({bestOptMeanSpeed < 0.1f}) -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); } #endif return bestStayNext1LaneIndex; } if (bestStayTotalLaneDist != bestOptTotalLaneDist && Math.Max(bestStayTotalLaneDist, bestOptTotalLaneDist) > conf.DynamicLaneSelection.MaxOptLaneChanges) { /* * best route contains more lane changes than allowed: choose lane with the least number of future lane changes */ #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): maximum best total lane distance = {Math.Max(bestStayTotalLaneDist, bestOptTotalLaneDist)} > AltLaneSelectionMaxOptLaneChanges"); } #endif if (bestOptTotalLaneDist < bestStayTotalLaneDist) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> selecting lane change option for minimizing number of future lane changes -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); } #endif return bestOptNext1LaneIndex; } else { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> selecting stay option for minimizing number of future lane changes -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); } #endif return bestStayNext1LaneIndex; } } if (bestStaySpeedDiff < 0 && bestOptSpeedDiff > bestStaySpeedDiff) { // found a lane change that improves vehicle speed //float improvement = 100f * ((bestOptSpeedDiff - bestStaySpeedDiff) / ((bestStayMeanSpeed + bestOptMeanSpeed) / 2f)); ushort optImprovementInKmH = SpeedLimitManager.Instance.LaneToCustomSpeedLimit(bestOptSpeedDiff - bestStaySpeedDiff, false); float speedDiff = Mathf.Abs(bestOptMeanSpeed - vehicleCurSpeed); #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): a lane change for speed improvement is possible. optImprovementInKmH={optImprovementInKmH} km/h speedDiff={speedDiff} (bestOptMeanSpeed={bestOptMeanSpeed}, vehicleCurVelocity={vehicleCurSpeed}, foundSafeLaneChange={foundSafeLaneChange})"); } #endif if (optImprovementInKmH >= conf.DynamicLaneSelection.MinSafeSpeedImprovement && (foundSafeLaneChange || (speedDiff <= conf.DynamicLaneSelection.MaxUnsafeSpeedDiff)) ) { // speed improvement is significant #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a faster lane to change to and speed improvement is significant -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex} (foundSafeLaneChange={foundSafeLaneChange}, speedDiff={speedDiff})"); } #endif return bestOptNext1LaneIndex; } // insufficient improvement #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a faster lane to change to but speed improvement is NOT significant OR lane change is unsafe -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex} (foundSafeLaneChange={foundSafeLaneChange})"); } #endif return bestStayNext1LaneIndex; } else if (!recklessDriver && foundSafeLaneChange && bestStaySpeedDiff > 0 && bestOptSpeedDiff < bestStaySpeedDiff && bestOptSpeedDiff >= 0) { // found a lane change that allows faster vehicles to overtake float optimization = 100f * ((bestStaySpeedDiff - bestOptSpeedDiff) / ((bestStayMeanSpeed + bestOptMeanSpeed) / 2f)); #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): found a lane change that optimizes overall traffic. optimization={optimization}%"); } #endif if (optimization >= conf.DynamicLaneSelection.MinSafeTrafficImprovement) { // traffic optimization is significant #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a lane that optimizes overall traffic and traffic optimization is significant -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); } #endif return bestOptNext1LaneIndex; } // insufficient optimization #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a lane that optimizes overall traffic but optimization is NOT significant -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); } #endif return bestOptNext1LaneIndex; } // suboptimal safe lane change #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> suboptimal safe lane change detected -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); } #endif return bestStayNext1LaneIndex; } catch (Exception e) { Log.Error($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exception occurred: {e}"); } return next1PathPos.m_lane; } public bool MayFindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref VehicleState vehicleState) { GlobalConfig conf = GlobalConfig.Instance; #if DEBUG bool debug = false; // conf.Debug.Switches[17] && (conf.Debug.VehicleId == 0 || conf.Debug.VehicleId == vehicleId); if (debug) { Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}) called."); } #endif if (!Options.advancedAI) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. Advanced Vehicle AI is disabled."); } #endif return false; } if (vehicleState.heavyVehicle) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. Vehicle is heavy."); } #endif return false; } if ((vehicleState.vehicleType & (ExtVehicleType.RoadVehicle & ~ExtVehicleType.Bus)) == ExtVehicleType.None) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. vehicleType={vehicleState.vehicleType}"); } #endif return false; } uint vehicleRand = GetStaticVehicleRand(vehicleId); if (vehicleRand < 100 - (int)Options.altLaneSelectionRatio) { #if DEBUG if (debug) { Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking (randomization)."); } #endif return false; } return true; } } } ================================================ FILE: TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs ================================================ using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Text; using TrafficManager.Geometry; using TrafficManager.Geometry.Impl; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Util; namespace TrafficManager.Manager.Impl { public class VehicleRestrictionsManager : AbstractGeometryObservingManager, ICustomDataManager>, IVehicleRestrictionsManager { public const NetInfo.LaneType LANE_TYPES = NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle; public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail; public const ExtVehicleType EXT_VEHICLE_TYPES = ExtVehicleType.PassengerTrain | ExtVehicleType.CargoTrain | ExtVehicleType.PassengerCar | ExtVehicleType.Bus | ExtVehicleType.Taxi | ExtVehicleType.CargoTruck | ExtVehicleType.Service | ExtVehicleType.Emergency; public static readonly float[] PATHFIND_PENALTIES = new float[] { 10f, 100f, 1000f }; public static readonly VehicleRestrictionsManager Instance = new VehicleRestrictionsManager(); private VehicleRestrictionsManager() { } protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"- Not implemented -"); // TODO implement } /// /// For each segment id and lane index: Holds the default set of vehicle types allowed for the lane /// private ExtVehicleType?[][][] defaultVehicleTypeCache = null; /// /// Determines the allowed vehicle types that may approach the given node from the given segment. /// /// /// /// [Obsolete] public ExtVehicleType GetAllowedVehicleTypes(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode) { // TODO optimize method (don't depend on collections!) ExtVehicleType ret = ExtVehicleType.None; foreach (ExtVehicleType vehicleType in GetAllowedVehicleTypesAsSet(segmentId, nodeId, busLaneMode)) { ret |= vehicleType; } return ret; } /// /// Determines the allowed vehicle types that may approach the given node from the given segment. /// /// /// /// [Obsolete] public HashSet GetAllowedVehicleTypesAsSet(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode) { HashSet ret = new HashSet(GetAllowedVehicleTypesAsDict(segmentId, nodeId, busLaneMode).Values); return ret; } /// /// Determines the allowed vehicle types that may approach the given node from the given segment (lane-wise). /// /// /// /// public IDictionary GetAllowedVehicleTypesAsDict(ushort segmentId, ushort nodeId, VehicleRestrictionsMode busLaneMode) { IDictionary ret = new TinyDictionary(); NetManager netManager = Singleton.instance; if (segmentId == 0 || (netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None || nodeId == 0 || (netManager.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Created) == NetNode.Flags.None) { return ret; } var dir = NetInfo.Direction.Forward; var dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection(dir); NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; int numLanes = segmentInfo.m_lanes.Length; uint laneIndex = 0; while (laneIndex < numLanes && curLaneId != 0u) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; if (laneInfo.m_laneType == NetInfo.LaneType.Vehicle || laneInfo.m_laneType == NetInfo.LaneType.TransportVehicle) { if ((laneInfo.m_vehicleType & VEHICLE_TYPES) != VehicleInfo.VehicleType.None) { ushort toNodeId = (laneInfo.m_finalDirection & dir2) != NetInfo.Direction.None ? netManager.m_segments.m_buffer[segmentId].m_endNode : netManager.m_segments.m_buffer[segmentId].m_startNode; if ((laneInfo.m_finalDirection & NetInfo.Direction.Both) == NetInfo.Direction.Both || toNodeId == nodeId) { ExtVehicleType vehicleTypes = GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, busLaneMode); ret[(byte)laneIndex] = vehicleTypes; } } } curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; ++laneIndex; } return ret; } /// /// Determines the allowed vehicle types for the given segment and lane. /// /// /// /// /// /// public ExtVehicleType GetAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode) { ExtVehicleType?[] fastArray = Flags.laneAllowedVehicleTypesArray[segmentId]; if (fastArray != null && fastArray.Length > laneIndex && fastArray[laneIndex] != null) { return (ExtVehicleType)fastArray[laneIndex]; } return GetDefaultAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, busLaneMode); } /// /// Determines the default set of allowed vehicle types for a given segment and lane. /// /// /// /// /// /// public ExtVehicleType GetDefaultAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode) { // manage cached default vehicle types if (defaultVehicleTypeCache == null) { defaultVehicleTypeCache = new ExtVehicleType?[NetManager.MAX_SEGMENT_COUNT][][]; } ExtVehicleType?[] cachedDefaultTypes = null; int cacheIndex = (int)busLaneMode; if (defaultVehicleTypeCache[segmentId] != null) { cachedDefaultTypes = defaultVehicleTypeCache[segmentId][cacheIndex]; } if (cachedDefaultTypes == null || cachedDefaultTypes.Length != segmentInfo.m_lanes.Length) { ExtVehicleType?[][] segmentCache = new ExtVehicleType?[3][]; segmentCache[0] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; segmentCache[1] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; segmentCache[2] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; defaultVehicleTypeCache[segmentId] = segmentCache; cachedDefaultTypes = segmentCache[cacheIndex]; } ExtVehicleType? defaultVehicleType = cachedDefaultTypes[laneIndex]; if (defaultVehicleType == null) { defaultVehicleType = GetDefaultAllowedVehicleTypes(laneInfo, busLaneMode); cachedDefaultTypes[laneIndex] = defaultVehicleType; } return (ExtVehicleType)defaultVehicleType; } public ExtVehicleType GetDefaultAllowedVehicleTypes(NetInfo.Lane laneInfo, VehicleRestrictionsMode busLaneMode) { ExtVehicleType ret = ExtVehicleType.None; if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Bicycle) != VehicleInfo.VehicleType.None) ret |= ExtVehicleType.Bicycle; if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Tram) != VehicleInfo.VehicleType.None) ret |= ExtVehicleType.Tram; if (busLaneMode == VehicleRestrictionsMode.Restricted || (busLaneMode == VehicleRestrictionsMode.Configured && Options.banRegularTrafficOnBusLanes)) { if ((laneInfo.m_laneType & NetInfo.LaneType.TransportVehicle) != NetInfo.LaneType.None) ret |= ExtVehicleType.RoadPublicTransport | ExtVehicleType.Service | ExtVehicleType.Emergency; else if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) ret |= ExtVehicleType.RoadVehicle; } else { if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None) ret |= ExtVehicleType.RoadVehicle; } if ((laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail)) != VehicleInfo.VehicleType.None) ret |= ExtVehicleType.RailVehicle; if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Ship) != VehicleInfo.VehicleType.None) ret |= ExtVehicleType.Ship; if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Plane) != VehicleInfo.VehicleType.None) ret |= ExtVehicleType.Plane; if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Ferry) != VehicleInfo.VehicleType.None) ret |= ExtVehicleType.Ferry; if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Blimp) != VehicleInfo.VehicleType.None) ret |= ExtVehicleType.Blimp; if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.CableCar) != VehicleInfo.VehicleType.None) ret |= ExtVehicleType.CableCar; return ret; } /// /// Determines the default set of allowed vehicle types for a given lane. /// /// /// /// /// /// internal ExtVehicleType GetDefaultAllowedVehicleTypes(uint laneId, VehicleRestrictionsMode busLaneMode) { if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & NetLane.Flags.Created) == NetLane.Flags.None) return ExtVehicleType.None; ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) return ExtVehicleType.None; NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; int numLanes = segmentInfo.m_lanes.Length; uint laneIndex = 0; while (laneIndex < numLanes && curLaneId != 0u) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; if (curLaneId == laneId) { return GetDefaultAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, busLaneMode); } curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; ++laneIndex; } return ExtVehicleType.None; } /// /// Sets the allowed vehicle types for the given segment and lane. /// /// /// /// /// /// internal bool SetAllowedVehicleTypes(ushort segmentId, NetInfo segmentInfo, uint laneIndex, NetInfo.Lane laneInfo, uint laneId, ExtVehicleType allowedTypes) { if (! Services.NetService.IsLaneValid(laneId)) { return false; } if (! Services.NetService.IsSegmentValid(segmentId)) { // TODO we do not need the segmentId given here. Lane is enough return false; } allowedTypes &= GetBaseMask(segmentInfo.m_lanes[laneIndex], VehicleRestrictionsMode.Configured); // ensure default base mask Flags.setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, allowedTypes); NotifyStartEndNode(segmentId); if (OptionsManager.Instance.MayPublishSegmentChanges()) { Services.NetService.PublishSegmentChanges(segmentId); } return true; } /// /// Adds the given vehicle type to the set of allowed vehicles at the specified lane /// /// /// /// /// /// /// public void AddAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType) { if (!Services.NetService.IsLaneValid(laneId)) { return; } if (!Services.NetService.IsSegmentValid(segmentId)) { // TODO we do not need the segmentId given here. Lane is enough return; } ExtVehicleType allowedTypes = GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); allowedTypes |= vehicleType; allowedTypes &= GetBaseMask(segmentInfo.m_lanes[laneIndex], VehicleRestrictionsMode.Configured); // ensure default base mask Flags.setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, allowedTypes); NotifyStartEndNode(segmentId); if (OptionsManager.Instance.MayPublishSegmentChanges()) { Services.NetService.PublishSegmentChanges(segmentId); } } /// /// Removes the given vehicle type from the set of allowed vehicles at the specified lane /// /// /// /// /// /// /// public void RemoveAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType) { if (!Services.NetService.IsLaneValid(laneId)) { return; } if (!Services.NetService.IsSegmentValid(segmentId)) { // TODO we do not need the segmentId given here. Lane is enough return; } ExtVehicleType allowedTypes = GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); allowedTypes &= ~vehicleType; allowedTypes &= GetBaseMask(segmentInfo.m_lanes[laneIndex], VehicleRestrictionsMode.Configured); // ensure default base mask Flags.setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, allowedTypes); NotifyStartEndNode(segmentId); if (OptionsManager.Instance.MayPublishSegmentChanges()) { Services.NetService.PublishSegmentChanges(segmentId); } } public void ToggleAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType, bool add) { if (add) AddAllowedType(segmentId, segmentInfo, laneIndex, laneId, laneInfo, vehicleType); else RemoveAllowedType(segmentId, segmentInfo, laneIndex, laneId, laneInfo, vehicleType); } public bool HasSegmentRestrictions(ushort segmentId) { // TODO clean up restrictions (currently we do not check if restrictions are equal with the base type) bool ret = false; Services.NetService.IterateSegmentLanes(segmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segId, ref NetSegment segment, byte laneIndex) { ExtVehicleType defaultMask = GetDefaultAllowedVehicleTypes(laneInfo, VehicleRestrictionsMode.Unrestricted); ExtVehicleType currentMask = GetAllowedVehicleTypes(segmentId, segment.Info, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); if (defaultMask != currentMask) { ret = true; return false; } return true; }); return ret; } /// /// Determines if a vehicle may use the given lane. /// /// /// /// /// /// /// public bool MayUseLane(ExtVehicleType type, ushort segmentId, byte laneIndex, NetInfo segmentInfo) { if (type == ExtVehicleType.None /* || type == ExtVehicleType.Tram*/) return true; /*if (laneInfo == null) laneInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes[laneIndex];*/ if (segmentInfo == null || laneIndex >= segmentInfo.m_lanes.Length) { return true; } NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; if ((laneInfo.m_vehicleType & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train)) == VehicleInfo.VehicleType.None) return true; if (!Options.vehicleRestrictionsEnabled) { return (GetDefaultAllowedVehicleTypes(laneInfo, VehicleRestrictionsMode.Configured) & type) != ExtVehicleType.None; } return ((GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured) & type) != ExtVehicleType.None); } /// /// Determines the maximum allowed set of vehicles (the base mask) for a given lane /// /// /// public ExtVehicleType GetBaseMask(NetInfo.Lane laneInfo, VehicleRestrictionsMode includeBusLanes) { return GetDefaultAllowedVehicleTypes(laneInfo, includeBusLanes); } /// /// Determines the maximum allowed set of vehicles (the base mask) for a given lane /// /// /// public ExtVehicleType GetBaseMask(uint laneId, VehicleRestrictionsMode includeBusLanes) { if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & NetLane.Flags.Created) == NetLane.Flags.None) return ExtVehicleType.None; ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) return ExtVehicleType.None; NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; int numLanes = segmentInfo.m_lanes.Length; uint laneIndex = 0; while (laneIndex < numLanes && curLaneId != 0u) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; if (curLaneId == laneId) { return GetBaseMask(laneInfo, includeBusLanes); } curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; ++laneIndex; } return ExtVehicleType.None; } public bool IsAllowed(ExtVehicleType? allowedTypes, ExtVehicleType vehicleType) { return allowedTypes == null || ((ExtVehicleType)allowedTypes & vehicleType) != ExtVehicleType.None; } public bool IsBicycleAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.Bicycle); } public bool IsBusAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.Bus); } public bool IsCargoTrainAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.CargoTrain); } public bool IsCargoTruckAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.CargoTruck); } public bool IsEmergencyAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.Emergency); } public bool IsPassengerCarAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.PassengerCar); } public bool IsPassengerTrainAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.PassengerTrain); } public bool IsServiceAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.Service); } public bool IsTaxiAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.Taxi); } public bool IsTramAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.Tram); } public bool IsBlimpAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.Blimp); } public bool IsCableCarAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.CableCar); } public bool IsFerryAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.Ferry); } public bool IsRailVehicleAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.RailVehicle); } public bool IsRoadVehicleAllowed(ExtVehicleType? allowedTypes) { return IsAllowed(allowedTypes, ExtVehicleType.RoadVehicle); } public bool IsRailLane(NetInfo.Lane laneInfo) { return (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Train) != VehicleInfo.VehicleType.None; } public bool IsRoadLane(NetInfo.Lane laneInfo) { return (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Car) != VehicleInfo.VehicleType.None; } public bool IsTramLane(NetInfo.Lane laneInfo) { return (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Tram) != VehicleInfo.VehicleType.None; } public bool IsRailSegment(NetInfo segmentInfo) { ItemClass connectionClass = segmentInfo.GetConnectionClass(); return connectionClass.m_service == ItemClass.Service.PublicTransport && connectionClass.m_subService == ItemClass.SubService.PublicTransportTrain; } public bool IsRoadSegment(NetInfo segmentInfo) { ItemClass connectionClass = segmentInfo.GetConnectionClass(); return connectionClass.m_service == ItemClass.Service.Road; } public bool IsMonorailSegment(NetInfo segmentInfo) { ItemClass connectionClass = segmentInfo.GetConnectionClass(); return connectionClass.m_service == ItemClass.Service.PublicTransport && connectionClass.m_subService == ItemClass.SubService.PublicTransportMonorail; } internal void ClearCache(ushort segmentId) { if (defaultVehicleTypeCache != null) { defaultVehicleTypeCache[segmentId] = null; } } internal void ClearCache() { defaultVehicleTypeCache = null; } public void NotifyStartEndNode(ushort segmentId) { // TODO this is hacky. Instead of notifying geometry observers we should add a seperate notification mechanic // notify observers of start node and end node (e.g. for separate traffic lights) ushort startNodeId = Singleton.instance.m_segments.m_buffer[segmentId].m_startNode; ushort endNodeId = Singleton.instance.m_segments.m_buffer[segmentId].m_endNode; if (startNodeId != 0) { Constants.ManagerFactory.GeometryManager.MarkAsUpdated(NodeGeometry.Get(startNodeId)); } if (endNodeId != 0) { Constants.ManagerFactory.GeometryManager.MarkAsUpdated(NodeGeometry.Get(endNodeId)); } } protected override void HandleInvalidSegment(SegmentGeometry geometry) { Flags.resetSegmentVehicleRestrictions(geometry.SegmentId); ClearCache(geometry.SegmentId); } protected override void HandleValidSegment(SegmentGeometry geometry) { } public override void OnLevelUnloading() { base.OnLevelUnloading(); ClearCache(); } public bool LoadData(List data) { bool success = true; Log.Info($"Loading lane vehicle restriction data. {data.Count} elements"); foreach (Configuration.LaneVehicleTypes laneVehicleTypes in data) { try { if (!Services.NetService.IsLaneValid(laneVehicleTypes.laneId)) continue; ExtVehicleType baseMask = GetBaseMask(laneVehicleTypes.laneId, VehicleRestrictionsMode.Configured); ExtVehicleType maskedType = laneVehicleTypes.vehicleTypes & baseMask; Log._Debug($"Loading lane vehicle restriction: lane {laneVehicleTypes.laneId} = {laneVehicleTypes.vehicleTypes}, masked = {maskedType}"); if (maskedType != baseMask) { Flags.setLaneAllowedVehicleTypes(laneVehicleTypes.laneId, maskedType); } else { Log._Debug($"Masked type does not differ from base type. Ignoring."); } } catch (Exception e) { // ignore, as it's probably corrupt save data. it'll be culled on next save Log.Warning("Error loading data from vehicle restrictions: " + e.ToString()); success = false; } } return success; } public List SaveData(ref bool success) { List ret = new List(); foreach (KeyValuePair e in Flags.getAllLaneAllowedVehicleTypes()) { try { ret.Add(new Configuration.LaneVehicleTypes(e.Key, e.Value)); } catch (Exception ex) { Log.Error($"Exception occurred while saving lane vehicle restrictions @ {e.Key}: {ex.ToString()}"); success = false; } } return ret; } } } ================================================ FILE: TLM/TLM/Manager/Impl/VehicleStateManager.cs ================================================ using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Text; using System.Threading; using TrafficManager.Custom.AI; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.Traffic.Data; using UnityEngine; namespace TrafficManager.Manager.Impl { public class VehicleStateManager : AbstractCustomManager, IVehicleStateManager { public static readonly VehicleStateManager Instance = new VehicleStateManager(); public const VehicleInfo.VehicleType VEHICLE_TYPES = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Monorail; /// /// Known vehicles and their current known positions. Index: vehicle id /// public VehicleState[] VehicleStates { get; private set; } = null; static VehicleStateManager() { Instance = new VehicleStateManager(); } protected override void InternalPrintDebugInfo() { base.InternalPrintDebugInfo(); Log._Debug($"Vehicle states:"); for (int i = 0; i < VehicleStates.Length; ++i) { if ((VehicleStates[i].flags & VehicleState.Flags.Spawned) == VehicleState.Flags.None) { continue; } Log._Debug($"Vehicle {i}: {VehicleStates[i]}"); } } private VehicleStateManager() { VehicleStates = new VehicleState[VehicleManager.MAX_VEHICLE_COUNT]; for (uint i = 0; i < VehicleManager.MAX_VEHICLE_COUNT; ++i) { VehicleStates[i] = new VehicleState((ushort)i); } } internal void LogTraffic(ushort vehicleId) { LogTraffic(vehicleId, ref VehicleStates[vehicleId]); } protected void LogTraffic(ushort vehicleId, ref VehicleState state) { if (state.currentSegmentId == 0) { return; } #if MEASUREDENSITY ushort length = (ushort)state.totalLength; if (length == 0) { return; } #endif state.StepRand(); TrafficMeasurementManager.Instance.AddTraffic(state.currentSegmentId, state.currentLaneIndex #if MEASUREDENSITY , length #endif , (ushort) state.Velocity); } internal void OnCreateVehicle(ushort vehicleId, ref Vehicle vehicleData) { if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created || (vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnCreateVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}"); #endif return; } #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnCreateVehicle({vehicleId}): calling OnCreate for vehicle {vehicleId}"); #endif VehicleStates[vehicleId].OnCreate(ref vehicleData); } internal ExtVehicleType OnStartPathFind(ushort vehicleId, ref Vehicle vehicleData, ExtVehicleType? vehicleType) { if ((vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None || (vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnStartPathFind({vehicleId}, {vehicleType}): unhandled vehicle! type: {vehicleData.Info.m_vehicleType}"); #endif return ExtVehicleType.None; } /*ushort connectedVehicleId = vehicleId; while (connectedVehicleId != 0) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnStartPathFind({vehicleId}, {vehicleType}): overriding vehicle type for connected vehicle {connectedVehicleId} of vehicle {vehicleId} (leading)"); #endif VehicleStates[connectedVehicleId].vehicleType = type; connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_leadingVehicle; }*/ ExtVehicleType ret = VehicleStates[vehicleId].OnStartPathFind(ref vehicleData, vehicleType); ushort connectedVehicleId = vehicleId; while (true) { connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; if (connectedVehicleId == 0) { break; } #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnStartPathFind({vehicleId}, {vehicleType}): overriding vehicle type for connected vehicle {connectedVehicleId} of vehicle {vehicleId} (trailing)"); #endif VehicleStates[connectedVehicleId].OnStartPathFind(ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId], vehicleType); } return ret; } internal void OnSpawnVehicle(ushort vehicleId, ref Vehicle vehicleData) { if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Spawned)) != (Vehicle.Flags.Created | Vehicle.Flags.Spawned) || (vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnSpawnVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}, path: {vehicleData.m_path}"); #endif return; } #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnSpawnVehicle({vehicleId}): calling OnSpawn for vehicle {vehicleId}"); #endif ushort connectedVehicleId = vehicleId; while (connectedVehicleId != 0) { VehicleStates[connectedVehicleId].OnSpawn(ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId]); connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; } } public void SetNextVehicleIdOnSegment(ushort vehicleId, ushort nextVehicleId) { VehicleStates[vehicleId].nextVehicleIdOnSegment = nextVehicleId; } public void SetPreviousVehicleIdOnSegment(ushort vehicleId, ushort previousVehicleId) { VehicleStates[vehicleId].previousVehicleIdOnSegment = previousVehicleId; } internal void UpdateVehiclePosition(ushort vehicleId, ref Vehicle vehicleData) { ushort connectedVehicleId = vehicleId; while (connectedVehicleId != 0) { UpdateVehiclePosition(ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId], ref VehicleStates[connectedVehicleId]); connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; } } protected void UpdateVehiclePosition(ref Vehicle vehicleData, ref VehicleState state) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.UpdateVehiclePosition({state.vehicleId}) called"); #endif if (vehicleData.m_path == 0 || (vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0 || (state.lastPathId == vehicleData.m_path && state.lastPathPositionIndex == vehicleData.m_pathPositionIndex) ) { return; } PathManager pathManager = Singleton.instance; // update vehicle position for timed traffic lights and priority signs int coarsePathPosIndex = vehicleData.m_pathPositionIndex >> 1; PathUnit.Position curPathPos; PathUnit.Position nextPathPos = default(PathUnit.Position); //if ((vehicleData.m_pathPositionIndex & 1) == 0) { curPathPos = pathManager.m_pathUnits.m_buffer[vehicleData.m_path].GetPosition(coarsePathPosIndex); pathManager.m_pathUnits.m_buffer[vehicleData.m_path].GetNextPosition(coarsePathPosIndex, out nextPathPos); /*} else { uint firstUnitId = vehicleData.m_path; int firstCoarsePathPosIndex = coarsePathPosIndex; bool invalid = false; if (PathUnit.GetNextPosition(ref firstUnitId, ref firstCoarsePathPosIndex, out curPathPos, out invalid)) { uint secondUnitId = firstUnitId; int secondCoarsePathPosIndex = firstCoarsePathPosIndex; PathUnit.GetNextPosition(ref secondUnitId, ref secondCoarsePathPosIndex, out nextPathPos, out invalid); } }*/ state.UpdatePosition(ref vehicleData, ref curPathPos, ref nextPathPos); } internal void OnDespawnVehicle(ushort vehicleId, ref Vehicle vehicleData) { if ((vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None || (vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Spawned)) == 0) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnDespawnVehicle({vehicleId}): unhandled vehicle! type: {vehicleData.Info.m_vehicleType}"); #endif return; } ushort /*connectedVehicleId = vehicleId; while (connectedVehicleId != 0) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnDespawnVehicle({vehicleId}): calling OnDespawn for connected vehicle {connectedVehicleId} of vehicle {vehicleId} (leading)"); #endif VehicleStates[connectedVehicleId].OnDespawn(); connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_leadingVehicle; } */connectedVehicleId = vehicleId; while (connectedVehicleId != 0) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnDespawnVehicle({vehicleId}): calling OnDespawn for connected vehicle {connectedVehicleId} of vehicle {vehicleId} (trailing)"); #endif VehicleStates[connectedVehicleId].OnDespawn(); connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; } } internal void OnReleaseVehicle(ushort vehicleId, ref Vehicle vehicleData) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnReleaseVehicle({vehicleId}) called."); #endif if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created || (vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnReleaseVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}"); #endif return; } #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleStateManager.OnReleaseVehicle({vehicleId}): calling OnRelease for vehicle {vehicleId}"); #endif VehicleStates[vehicleId].OnRelease(ref vehicleData); } internal void InitAllVehicles() { Log._Debug("VehicleStateManager: InitAllVehicles()"); VehicleManager vehicleManager = Singleton.instance; for (uint vehicleId = 0; vehicleId < VehicleManager.MAX_VEHICLE_COUNT; ++vehicleId) { Services.VehicleService.ProcessVehicle((ushort)vehicleId, delegate (ushort vId, ref Vehicle vehicle) { if ((vehicle.m_flags & Vehicle.Flags.Created) == 0) { return true; } OnCreateVehicle(vId, ref vehicle); if ((vehicle.m_flags & Vehicle.Flags.Emergency2) != 0) { OnStartPathFind(vId, ref vehicle, ExtVehicleType.Emergency); } if ((vehicle.m_flags & Vehicle.Flags.Spawned) == 0) { return true; } OnSpawnVehicle(vId, ref vehicle); return true; }); } } public ushort GetFrontVehicleId(ushort vehicleId, ref Vehicle vehicleData) { bool reversed = (vehicleData.m_flags & Vehicle.Flags.Reversed) != 0; ushort frontVehicleId = vehicleId; if (reversed) { frontVehicleId = vehicleData.GetLastVehicle(vehicleId); } else { frontVehicleId = vehicleData.GetFirstVehicle(vehicleId); } return frontVehicleId; } public override void OnLevelUnloading() { base.OnLevelUnloading(); for (int i = 0; i < VehicleStates.Length; ++i) { VehicleStates[i].OnDespawn(); } } public override void OnAfterLoadData() { base.OnAfterLoadData(); InitAllVehicles(); } } } ================================================ FILE: TLM/TLM/Manager/README.md ================================================ # TM:PE -- /Manager Manager classes that allow for creating and controlling custom behavior. ## Classes - **AbstractCustomManager**: Abstract base class for all custom managers. Provides virtual callback methods for performing actions on loading/saving/unloading the game. - **AbstractNodeGeometryObservingManager**: Abstract base class for all custom managers that need to react to changes in node geometry. - **AbstractSegmentGeometryObservingManager**: Abstract base class for all custom managers that need to react to changes in segment geometry. - **AdvancedParkingManager**: Implements the main Parking AI logic (TODO: currently not everything is in there, some logic is still in the **CustomPassengerCarAI**, **CustomResidentAI**, **CustomTouristAI** and **CustomHumanAI** classes). - **CustomSegmentLightsManager**: Manages all custom traffic lights that control actual traffic at in-game junctions - **ExtBuildingManager**: Manages extended building information that is used by the Parking AI - **ExtCitizenInstanceManager**: Manages extended citizen instance information that is used by the Parking AI - **ICustomDataManager**: Interface that is implemented by all managers that need to load/save data together with the savegame (note that the class **SerializableDataExtension** needs to know these managers during load/save). - **ICustomManager**: Interface that is implemented by all managers. - **JunctionRestrictionManager**: Manages all junction restrictions (zebra crossings, entering blocked junctions, lane changing when going straight and u-turns). - **LaneArrowManager**: Manages all custom lane arrows. - **LaneConnectionManager**: Manages all custom lane connections that are drawn by the player. - **OptionsManager**: Manages loading/saving mod options - **ParkingRestrictionsManager**: Manages all custom parking restrictions where cars may be prohibited to park on selected road segments. - **RoutingManager**: Implements custom path-finding logic for lane arrows, lane connections and highway rules (but not the actual Advanced Vehicle AI cost calculation, this is being done in **CustomPathFind**) - **SegmentEndManager**: Manages all segment ends that keep track of vehicles approaching at priority signs and timed traffic lights - **SpeedLimitManager**: Manages custom speed limits. - **TrafficLightManager**: Offers traffic light toggling functionality and controls which junctions may have traffic lights - **TrafficLightSimulationManager**: Manages the creation/deletion of manual and timed lights. Delegates the simulation of timed traffic lights (entry point: **SimulationStep**). - **TrafficMeasurementManager**: Manages traffic measurement data used by the Advanced Vehicle AI - **TrafficPriorityManager**: Manages priority signs and implements priority rules at junctions with priority signs (entry point: **HasVehiclePriority**). - **UtilityManager**: Offers auxiliary functions that must be executed within the simulation thread - **VehicleBehaviorManager**: Implements vehicle behavior (mainly at junctions). Traffic light states and priority rule checking is delegated here. - **VehicleRestrictionsManager**: Manages custom vehicle restrictions (custom bans for cars, cargo trucks, busses, etc.). - **VehicleStateManager**: Manages vehicle states (both positional and general vehicle states are stored). ================================================ FILE: TLM/TLM/PrintTransportLines.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; namespace TrafficManager { public class PrintTransportLines { #if XXX public void run() { for (int i = 0; i < TransportManager.MAX_LINE_COUNT; ++i) { /*if (TransportManager.instance.m_lines.m_buffer[i].Info.m_transportType != TransportInfo.TransportType.Tram) { continue; }*/ //Log.Message("Transport line " + i + ":"); if ((TransportManager.instance.m_lines.m_buffer[i].m_flags & TransportLine.Flags.Created) == TransportLine.Flags.None) { //Log.Message("\tTransport line is not created."); } //Log.Message("\tFlags: " + TransportManager.instance.m_lines.m_buffer[i].m_flags + ", cat: " + TransportManager.instance.m_lines.m_buffer[i].Info.category + ", type: " + TransportManager.instance.m_lines.m_buffer[i].Info.m_transportType + ", name: " + TransportManager.instance.GetLineName((ushort)i)); ushort firstStopNodeId = TransportManager.instance.m_lines.m_buffer[i].m_stops; ushort stopNodeId = firstStopNodeId; Vector3 lastNodePos = Vector3.zero; int index = 1; while (stopNodeId != 0) { Vector3 pos = NetManager.instance.m_nodes.m_buffer[stopNodeId].m_position; if (NetManager.instance.m_nodes.m_buffer[stopNodeId].m_problems != Notification.Problem.None) { Log.Message("\tStop node #" + index + " -- " + stopNodeId + ": Flags: " + NetManager.instance.m_nodes.m_buffer[stopNodeId].m_flags + ", Transport line: " + NetManager.instance.m_nodes.m_buffer[stopNodeId].m_transportLine + ", Problems: " + NetManager.instance.m_nodes.m_buffer[stopNodeId].m_problems + " Pos: " + pos + ", Dist. to lat pos: " + (lastNodePos - pos).magnitude); Log.Message("\tFlags: " + TransportManager.instance.m_lines.m_buffer[i].m_flags + ", cat: " + TransportManager.instance.m_lines.m_buffer[i].Info.category + ", type: " + TransportManager.instance.m_lines.m_buffer[i].Info.m_transportType + ", name: " + TransportManager.instance.GetLineName((ushort)i)); } lastNodePos = pos; ushort nextSegment = TransportLine.GetNextSegment(stopNodeId); if (nextSegment != 0) { stopNodeId = NetManager.instance.m_segments.m_buffer[(int)nextSegment].m_endNode; } else { break; } ++index; if (stopNodeId == firstStopNodeId) { break; } if (index > 10000) { Log.Message("Too many iterations!"); break; } } } } #endif } } ================================================ FILE: TLM/TLM/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("TLM")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("TLM")] [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("70591292-D092-4DC5-AFB9-3AA950E240BE")] // 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/TLM/README.md ================================================ # TM:PE -- / This is the project root directory. ## Classes - **CodeProfiler**: Helper class for profiling - **Constants**: Well, constant things - **LoadingExtension**: Extends **LoadingExtensionBase**. Performs detouring of game methods and checks for incompatible mods. - **ThreadingExtension**: Extends **ThreadingExtensionBase**. Calls several custom **SimulationStep** methods. - **TrafficManagerMod**: Implements **IUserMod**. Mod main class. Defines mod version and required game version. - **TrafficManagerMode**: Enum to express the menu visibility state (None=closed, Activated=open) ================================================ FILE: TLM/TLM/Resources/incompatible_mods.txt ================================================ 1581695572;Traffic Manager: President Edition 1348361731;Traffic Manager: President Edition ALPHA/DEBUG 498363759;Traffic Manager + Improved AI 563720449;Traffic Manager + Improved AI (Japanese Ver.) 492391912;Improvedd AI (Traffic++) 409184143;Traffic++ 626024868;Traffic++ V2 407335588;No Despawn Mod 600733054;No On-Street Parking 1628112268;RightTurnNoStop 411833858;Toggle Traffic Lights 512341354;Central Services Dispatcher (WtM) 844180955;City Drive 529979180;CSL Service Reserve 433249875;[ARIS] Enhanced Hearse AI 583556014;Enhanced Hearse AI [Fixed for v1.4+] 813835241;Enhanced Hearse AI [1.6] 439582006;[ARIS] Enhanced Garbage Truck AI 583552152;Enhanced Garbage Truck AI [Fixed for v1.4+] 813835391;Enhanced Garbage Truck AI [1.6] 413847191;SOM - Services Optimisation Module 418556522;Road Anarchy 954034590;Road Anarchy V2 726005715;Roads United Core+ 680748394;Roads United: North America 532863263;Multi-track Station Enabler 442957897;Multi-track Station Enabler 553184329;Sharp Junction Angles 478820060;Network Extensions Project (v1) 658653260;Network Nodes Editor [Experimental] 929114228;New Roads for Network Extensions 436253779;Road Protractor 651610627;Road Color Changer Continued 422554572;81 Tiles Updated 414702884;Zoneable Pedestrian Paths 631694768;Extended Road Upgrade 649522495;District Service Limit 428094792;[ARIS] Remove Stuck Vehicles 587530437;Remove Stuck Vehicles [Fixed for v1.4+] 813834836;Remove Stuck Vehicles [1.6] 421028969;[ARIS] Skylines Overwatch 583538182;Skylines Overwatch [Fixed for v1.3+] 813833476;Skylines Overwatch [1.6] 408209297;Extended Road Upgrade 417926819;Road Assistant 631930385;Realistic Vehicle Speeds ================================================ FILE: TLM/TLM/Resources/lang.txt ================================================ Switch_traffic_lights Switch traffic lights Add_priority_signs Add priority signs Manual_traffic_lights Manual traffic lights Timed_traffic_lights Timed traffic lights Change_lane_arrows Change lane arrows Clear_Traffic Clear Traffic Disable_despawning Disable despawning Enable_despawning Enable despawning NODE_IS_LIGHT Junction has a traffic light.\nDelete the traffic light by choosing "Switch traffic lights" and clicking on this node. NODE_IS_TIMED_LIGHT Junction is part of a timed script.\nSelect "Timed traffic lights", click on this node and click on "Remove" first. Select_nodes_windowTitle Select nodes Select_nodes Select nodes Node Node Deselect_all_nodes Deselect all nodes Setup_timed_traffic_light Setup timed traffic light State State Skip Skip up up down down View View Edit Edit Delete Delete Timed_traffic_lights_manager Timed traffic lights manager Add_step Add step Remove_timed_traffic_light Remove timed traffic light Min._Time: Min. Time: Max._Time: Max. Time: Save Save Add Add Sensitivity Sensitivity Very_Low Very Low Low Low Medium Medium High High Very_high Very high Extreme_long_green/red_phases Extreme long green/red phases Very_long_green/red_phases Very long green/red phases Long_green/red_phases Long green/red phases Moderate_green/red_phases Moderate green/red phases Short_green/red_phases Short green/red phases Very_short_green/red_phases Very short green/red phases Extreme_short_green/red_phases Extreme short green/red phases Hide_counters Hide counters Show_counters Show counters Start Start Stop Stop Enable_test_mode_(stay_in_current_step) Enable test mode (stay in current step) avg._flow avg. flow avg._wait avg. wait min/max min/max Lane Lane Set_Speed Set Speed {0} Max_speed Max speed Segment Segment incoming incoming Enable_Advanced_Vehicle_AI Enable Advanced Vehicle AI Vehicles_may_enter_blocked_junctions Vehicles may enter blocked junctions All_vehicles_may_ignore_lane_arrows All vehicles may ignore lane arrows Busses_may_ignore_lane_arrows Buses may ignore lane arrows Reckless_driving Reckless driving TMPE_Title Traffic Manager: President Edition Settings_are_defined_for_each_savegame_separately Settings are defined for each savegame separately Simulation_accuracy Simulation accuracy (higher accuracy reduces performance) Enable_highway_specific_lane_merging/splitting_rules Enable highway specific lane merging/splitting rules Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Drivers like to change their lane (only applied if Advanced AI is enabled) Maintenance Maintenance Very_often Very often Often Often Sometimes Sometimes Rarely Rarely Very_rarely Very rarely Only_if_necessary Only if necessary Nodes_and_segments Nodes and segments Lanes Lanes Path_Of_Evil_(10_%) Path Of Evil (10 %) Rush_Hour_(5_%) Rush Hour (5 %) Minor_Complaints_(2_%) Minor Complaints (2 %) Holy_City_(0_%) Holy City (0 %) Forget_toggled_traffic_lights Forget toggled traffic lights Road_is_already_in_a_group! Road is already in a group! All_selected_roads_must_be_of_the_same_type! All selected roads must be of the same type! Create_group Create group Delete_group Delete group Add_zoning Add zoning Remove_zoning Remove zoning Lane_Arrow_Changer_Disabled_Highway The lane arrow changer for this lane is disabled because you activated the highway rule system. Add_junction_to_timed_light Add a junction to this traffic light Remove_junction_from_timed_light Remove a junction from this traffic light Select_junction Select a junction Cancel Cancel Speed_limits Speed limits Persistently_visible_overlays Persistently visible overlays Priority_signs Priority signs Vehicles_may_do_u-turns_at_junctions Vehicles may do u-turns at junctions Vehicles_going_straight_may_change_lanes_at_junctions Vehicles going straight on may change lanes at junctions Allow_u-turns Allow u-turns Allow_lane_changing_for_vehicles_going_straight Allow lane changing for vehicles going straight Allow_vehicles_to_enter_a_blocked_junction Allow vehicles to enter a blocked junction Road_condition_has_a_bigger_impact_on_vehicle_speed Road condition has a bigger impact on vehicle speed Vehicle_restrictions Vehicle restrictions Copy Copy Paste Paste Invert Invert Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions Apply vehicle restrictions to all road segments between two junctions Allow_all_vehicles Allow all vehicles Ban_all_vehicles Ban all vehicles Set_all_traffic_lights_to_red Set all traffic lights to red Rotate_left Rotate left Rotate_right Rotate right Name Name Apply Apply Select_a_timed_traffic_light_program Select a timed traffic light program The_chosen_traffic_light_program_is_incompatible_to_this_junction The chosen traffic light pattern is incompatible with this junction Advanced_AI_cannot_be_activated Advanced AI cannot be activated The_Advanced_Vehicle_AI_cannot_be_activated The Advanced Vehicle AI cannot be activated because you are already using another mod that modifies vehicle behavior (e.g. Improved AI or Traffic++). Enable_dynamic_path_calculation Enable dynamic path calculation Lane_Arrow_Changer_Disabled_Connection The lane arrow changer for this lane is disabled because you have created lane connections by hand. Lane_connector Lane connector Connected_lanes Connected lanes Use_alternative_view_mode Use alternative view mode Road_type Road type Default_speed_limit Default speed limit Unit_system Unit system Metric Metric Imperial Imperial Use_more_CPU_cores_for_route_calculation_if_available Use more CPU cores for route calculation (if available) Activated_features Activated features Junction_restrictions Junction restrictions Prohibit_spawning_of_pocket_cars Prohibit cims to spawn pocket cars Reset_stuck_cims_and_vehicles Reset stuck cims and vehicles Default_speed_limits Default speed limits Looking_for_a_parking_spot Looking for a parking spot Driving_to_a_parking_spot Driving to a parking spot Driving_to_another_parking_spot Driving to another parking spot Entering_vehicle Entering vehicle Walking_to_car Walking to car Using_public_transport Using public transport Walking Walking Thinking_of_a_good_parking_spot Thinking of a good parking spot Switch_view Switch view Outgoing_demand Outgoing demand Incoming_demand Incoming demand Advanced_Vehicle_AI Advanced Vehicle AI Heavy_trucks_prefer_outer_lanes_on_highways Heavy vehicles prefer outer lanes on highways Parking_AI Parking AI Enable_more_realistic_parking Enable more realistic parking Reset_custom_speed_limits Reset custom speed limits Reload_global_configuration Reload global configuration Reset_global_configuration Reset global configuration General General Gameplay Gameplay Overlays Overlays Realistic_speeds Realistic speeds Evacuation_busses_may_ignore_traffic_rules Evacuation buses may ignore traffic rules Evacuation_busses_may_only_be_used_to_reach_a_shelter Evacuation buses may only be used to reach a shelter Vehicle_behavior Vehicle behavior Policies_&_Restrictions Policies & Restrictions At_junctions At junctions In_case_of_emergency In case of emergency Show_lane-wise_speed_limits Show lane-wise speed limits Language Language Game_language Game language requires_game_restart requires game restart Customizations_come_into_effect_instantaneously Customizations come into effect instantaneously Options Options Lock_main_menu_button_position Lock main menu button position Lock_main_menu_position Lock main menu position Recalculating_lane_routing Recalculating lane routing Please_wait Please wait Parking_restrictions Parking restrictions Simulation Simulation On_roads On roads Ban_private_cars_and_trucks_on_bus_lanes Ban private cars and trucks on bus lanes default default flow_ratio flow ratio wait_ratio wait ratio After_min._time_has_elapsed_switch_to_next_step_if After min. time has elapsed, switch to next step if Adaptive_step_switching Adaptive step switching Dynamic_lane_section Dynamic lane selection Percentage_of_vehicles_performing_dynamic_lane_section Percentage of vehicles performing dynamic lane selection Vehicle_restrictions_aggression Vehicle restrictions aggression Strict Strict Show_path-find_stats Show path-find stats Remove_this_vehicle Remove this vehicle Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights Vehicles follow priority rules at junctions with timed traffic lights Enable_tutorial_messages Enable tutorial messages TMPE_TUTORIAL_HEAD_MainMenu Traffic Manager: President Edition TMPE_TUTORIAL_BODY_MainMenu Welcome to TM:PE!\n\nUser manual: http://www.viathinksoft.de/tmpe TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool Junction restrictions TMPE_TUTORIAL_BODY_JunctionRestrictionsTool Control how vehicles and pedestrians shall behave at junctions.\n\n1. Click on the junction you want to manage\n2. Click on the appropriate icon to toggle restrictions.\n\nAvailable restrictions:\n- Allow/Disallow lane changing for vehicle going straight on\n- Allow/Disallow u-turns\n- Allow/Disallow vehicles to enter a blocked junction\n- Allow/disallow pedestrians to cross the street TMPE_TUTORIAL_HEAD_LaneArrowTool Lane arrows TMPE_TUTORIAL_BODY_LaneArrowTool Restrict the set of directions that vehicles are allowed to take.\n\n1. Click on a road segment next to a junction\n2. Select the allowed directions. TMPE_TUTORIAL_HEAD_LaneConnectorTool Lane connector TMPE_TUTORIAL_BODY_LaneConnectorTool Connect two or more lanes with each other in order to tell which lanes vehicles may use.\n\n1. Click on a lane changing point (grey circles).\n2. Click on a source marker.\n3. Click on a target marker to connect it with the source marker.\n4. Click anywhere with your secondary mouse button to return back to source marker selection.\n\nAvailable hotkeys:\n\n- Delete or Backspace: Remove all lane connections\n- Shift + S: Cycle through all available "stay on lane" patterns TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool Manual traffic lights TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Try out custom traffic lights.\n\n1. Click on a junction\n2. Use the tool to control traffic lights. TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool Parking restrictions TMPE_TUTORIAL_BODY_ParkingRestrictionsTool Control where parking is allowed.\n\nClick on the "P" icons.\n\nAvailable hotkeys:\n\n- Shift: Hold while clicking to apply parking restrictions to multiple road segments at once TMPE_TUTORIAL_HEAD_PrioritySignsTool Priority signs TMPE_TUTORIAL_BODY_PrioritySignsTool Define priority rules at junctions.\n\n1. Click on a junction.\n2. Click on the blank spots to cycle through the available priority signs (priority road, yield, stop).\n\nAvailable hotkeys:\n\n- Shift: Hold Shift to add priority signs to multiple road segments at once. Click again while holding Shift to cycle through all available patterns. TMPE_TUTORIAL_HEAD_SpeedLimitsTool Speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool Set up speed restrictions.\n\n1. In the window, click on the speed limit you want to set.\n2. Click on a road segment to change the speed limit.\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply speed limits to multiple road segments at once.\n- Ctrl: Hold Ctrl to control speed limits per individual lane. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool Timed traffic lights TMPE_TUTORIAL_BODY_TimedTrafficLightsTool Set up timed traffic lights.\n\n1. Click on a junction.\n2. In the window, click on "Add step".\n3. Click on the overlay elements to set desired traffic lights states.\n4. Click on "Add".\n5. Repeat as desired. TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Toggle traffic lights TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool Add or remove traffic lights to/from junctions.\n\nClick on a junction to toggle traffic lights. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, buses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. Public_transport Public transport Prevent_excessive_transfers_at_public_transport_stations Prevent unnecessary transfers at public transport stations Compact_main_menu Compact main menu Window_transparency Window transparency Overlay_transparency Overlay transparency Remove_this_citizen Remove this citizen Show_error_message_if_a_mod_incompatibility_is_detected Show error message if a mod incompatibility is detected Remove_parked_vehicles Remove parked vehicles Node_is_level_crossing This junction is a level crossing.\nYou cannot disable traffic lights here. Experimental_features Experimental features Turn_on_red Turn on red Vehicles_may_turn_on_red Vehicles may turn at red traffic lights Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets ================================================ FILE: TLM/TLM/Resources/lang_de.txt ================================================ Switch_traffic_lights Ampeln setzen Add_priority_signs Vorfahrtsschilder Manual_traffic_lights Manuelle Ampeln Timed_traffic_lights Zeitgesteuerte Ampeln Change_lane_arrows Richtungspfeile Clear_Traffic Verkehr löschen Disable_despawning Despawn ausschalten Enable_despawning Despawn einschalten NODE_IS_LIGHT Die Kreuzung hat eine Ampel.\nLösche zunächst die Ampel, indem du auf "Ampeln setzen" und dann auf die Kreuzung klickst. NODE_IS_TIMED_LIGHT Die Kreuzung wird von einer Zeitsteuerung kontrolliert.\nWähle "Zeitgesteuerte Ampeln" aus, klicke auf die Kreuzung und klicke auf "Entfernen". Select_nodes_windowTitle Auswahl der Kreuzungen Select_nodes Wähle eine oder mehrere Kreuzungen aus Node Knotenpunkt Deselect_all_nodes Auswahl löschen Setup_timed_traffic_light Zeitgesteuerte Ampel einrichten State Schritt Skip Weiter up hoch down runter View Anzeigen Edit Ändern Delete Löschen Timed_traffic_lights_manager Verwaltung zeitgesteuerter Ampeln Add_step Schritt hinzufügen Remove_timed_traffic_light Ampel entfernen Min._Time: Min. Zeit: Max._Time: Max. Zeit: Save Speichern Add Hinzufügen Sensitivity Empfindlichkeit Very_Low Sehr niedrig Low Niedrig Medium Mittel High Hoch Very_high Sehr hoch Extreme_long_green/red_phases Extrem lange Grün-/Rotphasen Very_long_green/red_phases Sehr lange Grün-/Rotphasen Long_green/red_phases Lange Grün-/Rotphasen Moderate_green/red_phases Moderate Grün-/Rotphasen Short_green/red_phases Kurze Grün-/Rotphasen Very_short_green/red_phases Sehr kurze Grün-/Rotphasen Extreme_short_green/red_phases Extrem kurze Grün-/Rotphasen Hide_counters Zähler verstecken Show_counters Zähler anzeigen Start Start Stop Stopp Enable_test_mode_(stay_in_current_step) Testmodus aktivieren (im aktuellen Schritt bleiben) avg._flow fahrend avg._wait wartend min/max min/max Lane Fahrspur Set_Speed Geschwindigkeit setzen {0} Max_speed Max. Geschwindigkeit Segment Segment incoming eingehend Enable_Advanced_Vehicle_AI Verbesserte Fahrzeug-KI aktivieren Vehicles_may_enter_blocked_junctions Fahrzeuge dürfen in blockierte Kreuzungen einfahren All_vehicles_may_ignore_lane_arrows Alle Fahrzeuge dürfen Richtungspfeile ignorieren Busses_may_ignore_lane_arrows Busse dürfen Richtungspfeile ignorieren Reckless_driving Rücksichtsloses Fahren TMPE_Title Traffic Manager: President Edition Settings_are_defined_for_each_savegame_separately Einstellungen werden pro Spielstand gespeichert Simulation_accuracy Genauigkeit der Simulation (höhere Genauigkeit reduziert Performance) Enable_highway_specific_lane_merging/splitting_rules Aktiviere spezifische Einordnungsregeln auf der Autobahn Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Autofahrer wechseln ihre Spur (wird nur angewendet wenn die KI eingeschaltet ist) Maintenance Wartung Very_often Sehr häufig Often Häufig Sometimes Manchmal Rarely Selten Very_rarely Sehr selten Only_if_necessary Nur falls nötig Nodes_and_segments Knoten und Segmente Lanes Fahrspuren Path_Of_Evil_(10_%) Pfad des Bösen (10 %) Rush_Hour_(5_%) Feierabendverkehr (5 %) Minor_Complaints_(2_%) Einige Beschwerden (2 %) Holy_City_(0_%) Heilige Stadt (0 %) Forget_toggled_traffic_lights Gesetzte/Entfernte Ampeln vergessen Road_is_already_in_a_group! Straße ist bereits in einer Gruppe! All_selected_roads_must_be_of_the_same_type! Alle ausgewählten Straßen müssen vom gleichen Typ sein! Create_group Gruppe erstellen Delete_group Gruppe löschen Add_zoning Zonen aktivieren Remove_zoning Zonen löschen Lane_Arrow_Changer_Disabled_Highway Die Richtungspfeile für diese Spur können nicht geändert werden, weil du die speziellen Regeln für Autobahnen aktiviert hast. Add_junction_to_timed_light Kreuzung zur Ampel hinzufügen Remove_junction_from_timed_light Kreuzung von der Ampel entfernen Select_junction Wähle eine Kreuzung aus Cancel Abbrechen Speed_limits Geschwindigkeitsbeschränkungen Persistently_visible_overlays Dauerhaft sichtbare Overlays Priority_signs Vorfahrtsschilder Vehicles_may_do_u-turns_at_junctions Fahrzeuge dürfen an Kreuzungen wenden Vehicles_going_straight_may_change_lanes_at_junctions Fahrz. dürfen die Spur wechseln, wenn sie an Kreuzungen geradeaus fahren Allow_u-turns Wenden erlauben Allow_lane_changing_for_vehicles_going_straight Spurwechsel für Fahrzeuge erlauben, die geradeaus fahren Allow_vehicles_to_enter_a_blocked_junction Erlaube Fahrzeuge, in eine blockierte Kreuzung einzufahren Road_condition_has_a_bigger_impact_on_vehicle_speed Der Straßenzustand hat einen größeren Einfluss auf die Geschwindigkeit Vehicle_restrictions Fahrzeugbeschränkungen Copy Kopieren Paste Einfügen Invert Invertieren Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions Wende Fahrzeugbeschränkungen auf alle Straßensegmente zwischen zwei Kreuzungen an Allow_all_vehicles Erlaube alle Fahrzeuge Ban_all_vehicles Verbiete alle Fahrzeuge Set_all_traffic_lights_to_red Alle Ampeln auf rot setzen Rotate_left Links rotieren Rotate_right Rechts rotieren Name Name Apply Anwenden Select_a_timed_traffic_light_program Wähle ein zeitgesteuertes Ampelprogramm aus The_chosen_traffic_light_program_is_incompatible_to_this_junction Das ausgewählte Ampelprogramm ist inkompatibel zu dieser Kreuzung Advanced_AI_cannot_be_activated Erweiterte Fahrzeug-KI kann nicht aktiviert werden The_Advanced_Vehicle_AI_cannot_be_activated Die erweiterte Fahrzeug-KI kann nicht aktiviert werden, weil du bereits einen anderen Mod verwendest, der das Verhalten von Fahrzeugen verändern (z.B. Improved AI oder Traffic++). Enable_dynamic_path_calculation Aktiviere die dynamische Pfadberechnung Lane_Arrow_Changer_Disabled_Connection Die Richtungspfeile für diese Spur können nicht geändert werden, weil du Spuren von Hand verbunden hast. Lane_connector Fahrspurverbinder Connected_lanes Verbundene Fahrspuren Use_alternative_view_mode Verwende den alternativen Anzeigemodus Road_type Straßentyp Default_speed_limit Standard-Geschwindigkeitsbeschränkung Unit_system Einheitensystem Metric Metrisch Imperial Imperial Use_more_CPU_cores_for_route_calculation_if_available Verwende mehr CPU-Kerne zur Routenberechnung (falls verfügbar) Activated_features Aktivierte Features Junction_restrictions Kreuzungsbeschränkungen Prohibit_spawning_of_pocket_cars Verbiete Cims das Spawnen von Autos "aus der Tasche heraus" Reset_stuck_cims_and_vehicles Setze steckengebliebene Cims und Fahrzeuge zurück Default_speed_limits Standard-Geschwindigkeitsbeschränkungen Looking_for_a_parking_spot Sucht einen Parkplatz Driving_to_a_parking_spot Fährt zum Parkplatz Driving_to_another_parking_spot Fährt zu einem anderen Parkplatz Entering_vehicle Steigt ins Auto ein Walking_to_car Läuft zum Auto Using_public_transport Verwendet ÖPNV Walking Läuft Thinking_of_a_good_parking_spot Erinnert sich an einen Parkplatz Switch_view Ansicht wechseln Outgoing_demand Ausgehende Nachfrage Incoming_demand Eingehende Nachfrage Advanced_Vehicle_AI Verbesserte Fahrzeug-KI Heavy_trucks_prefer_outer_lanes_on_highways Schwere Laster bevorzugen die äußeren Spuren auf Autobahnen Parking_AI Parkplatz-KI Enable_more_realistic_parking Aktiviere realistisches Parken Reset_custom_speed_limits Geschwindigkeitsbegrenzungen auf Standard zurücksetzen Reload_global_configuration Globale Konfiguration neuladen Reset_global_configuration Globale Konfiguration zurücksetzen General Allgemein Gameplay Gameplay Overlays Overlays Realistic_speeds Realistische Geschwindigkeiten Evacuation_busses_may_ignore_traffic_rules Evakuierungsbusse dürfen Verkehrsregeln missachten Evacuation_busses_may_only_be_used_to_reach_a_shelter Evakuierungsbusse dienen nur zum Erreichen einer Notunterkunft Vehicle_behavior Fahrzeugverhalten Policies_&_Restrictions Richtlinien & Einschränkungen At_junctions An Kreuzungen In_case_of_emergency Im Notfall Show_lane-wise_speed_limits Zeige spurweise Geschwindigkeitsbeschränkungen Language Sprache Game_language Spielsprache requires_game_restart erfordert einen Neustart des Spiels Customizations_come_into_effect_instantaneously Anpassungen treten sofort in Kraft Options Optionen Lock_main_menu_button_position Position der Hauptmenü-Schaltfläche sperren Lock_main_menu_position Position des Hauptmenüs sperren Recalculating_lane_routing Berechne Routenführung neu Please_wait Bitte warten Parking_restrictions Parkverbote Simulation Simulation On_roads Auf Straßen Ban_private_cars_and_trucks_on_bus_lanes Verbiete private Fahrzeuge und Lieferwagen auf Busspuren default Standard flow_ratio fahrende Fzg. wait_ratio wartende Fzg. After_min._time_has_elapsed_switch_to_next_step_if Wenn die min. Zeit abläuft, wechsle zum nächsten Schritt falls Adaptive_step_switching Adaptive Schrittumschaltung Dynamic_lane_section Dynamische Fahrspurwahl Percentage_of_vehicles_performing_dynamic_lane_section Prozentsatz der Fahrzeuge mit dynamischer Fahrspurwahl Vehicle_restrictions_aggression Auslegung der Fahrzeugbeschränkungen Strict Strict Show_path-find_stats Path-Find Statistik anzeigen Remove_this_vehicle Dieses Fahrzeug entfernen Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights Fahrzeuge befolgen an zeitgesteuerten Ampeln die Vorrangsregelung Enable_tutorial_messages Tutorial aktivieren TMPE_TUTORIAL_HEAD_MainMenu Traffic Manager: President Edition TMPE_TUTORIAL_BODY_MainMenu Willkommen bei TM:PE!\n\nBenutzerhandbuch: http://www.viathinksoft.de/tmpe TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool Kreuzungsbeschränkungen TMPE_TUTORIAL_BODY_JunctionRestrictionsTool Steuere, wie Fahrzeuge und Fußgänger sich an Kreuzungen zu verhalten haben.\n\n1. Klicke auf die Kreuzung, die du kontrollieren möchtest.\n2. Klicke auf das entsprechende Symbol, um die Beschränkungen ein- oder auszuschalten.\n\nVerfügbare Beschränkungen:\n- Erlaube/Verbiete Spurwechsel für Fahrzeuge, die geradeaus fahren.\n- Erlaube/Verbiete das Wenden.\n- Erlaube/Verbiete Fahrzeugen, in die blockierte Kreuzung einzufahren.\n- Erlaube/Verbiete Fußgängern, die Straße zu überqueren. TMPE_TUTORIAL_HEAD_LaneArrowTool Richtungspfeile TMPE_TUTORIAL_BODY_LaneArrowTool Limitiere die Richtungen, in die Fahrzeuge fahren dürfen.\n\n1. Klicke auf ein Straßensegment nahe einer Kreuzung.\n2. Wähle die erlaubten Richtungen aus. TMPE_TUTORIAL_HEAD_LaneConnectorTool Fahrspurverbinder TMPE_TUTORIAL_BODY_LaneConnectorTool Verbinde zwei oder mehr Fahrspuren miteinander, um Fahrzeugen nur bestimmte Fahrspurwechsel zu erlauben.\n\n1. Klicke auf einen Spurwechselpunkt (grauer Kreis).\n2. Klicke auf eine Quellspurmarkierung.\n3. Klicke auf eine Zielspurmarkierung, um die Zielspur mit der Quellspur zu verbinden.\n4. Klicke irgendwo mit der sekundären Maustaste, um zur Qullspurselektion zurückzukehren.\n\nVerfügbare Tastenkombinationen:\n\n-Entf. oder Rücktaste: Lösche alle Fahrspurverbindungen\n- Umschalt + S: Wechsle durch alle verfügbaren "Bleib auf der Spur"-Vorlagen. TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool Manuelle Ampeln TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Probiere die Ampelschaltungen von TM:PE aus.\n\n1. Klicke auf eine Kreuzung.\n2. Verwende das Werkzeug, um die Ampel zu kontrollieren. TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool Parkverbote TMPE_TUTORIAL_BODY_ParkingRestrictionsTool Steuere, wo das Parken erlaubt ist.\n\nKlicke auf die "P"-Symbole.\n\nVerfügbare Tastenkombinationen:\n\n- Umschalt: Halte die Taste beim Klicken, um Parkverbote auch auf benachbarte Straßensegmente anzuwenden. TMPE_TUTORIAL_HEAD_PrioritySignsTool Vorfahrtsschilder TMPE_TUTORIAL_BODY_PrioritySignsTool Definiere Vorfahrtsregeln an Kreuzungen.\n\n1. Klicke auf eine Kreuzung.\n2. Klicke auf eine der leeren Punkte, um zwischen den verfügbaren Vorfahrtsschildern umzuschalten (Hauptstraße, Vorfahrt gewähren, Stopp).\n\nVerfügbare Tastenkombinationen:\n\n- Umschalt: Halte die Taste, um Vorfahrtsschilder auch auf benachbarten Kreuzungen zu setzen. Klicke nochmals während du Umschalt gedrückt hälst, um alle verfügbaren Vorlagen durchzuwechseln. TMPE_TUTORIAL_HEAD_SpeedLimitsTool Geschwindigkeitsbeschränkungen TMPE_TUTORIAL_BODY_SpeedLimitsTool Setze Geschwindigkeitsbeschränkungen.\n\n1. Wähle im Fenster eine Geschwindigkeitsbegrenzung aus.\n2. Klicke auf ein Straßensegment, um die ausgewählte Begrenzung anzuwenden.\n\nVerfügbare Tastenkombinationen:\n\n- Umschalt: Halte die Taste beim Klicken, um Geschwindigkeitsbegrenzungen auch auf benachbarte Straßensegmente anzuwenden.\n- Strg: Halte die Taste beim Klicken, um Geschwindigkeitsbegrenzungen für einzelne Fahrspuren zu setzen. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool Zeitgesteuerte Ampelschaltungen TMPE_TUTORIAL_BODY_TimedTrafficLightsTool Definiere zeitgesteuerte Ampeln.\n\n1. Klicke auf eine Kreuzung.\n2. Klicke im Fenster auf "Schritt hinzufügen".\n3. Klick auf die Overlayelemente, um den gewünschten Zustand der Ampeln festzulegen.\n4. Klicke auf "Hinzufügen".\n5. Wiederhole die obigen Schritte bei Bedarf. TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Ampeln setzen TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool Füge Ampeln hinzu oder lösche sie von Kreuzungen.\n\nKlicke auf eine Kreuzung, um ihre Ampel ein- oder auszuschalten. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Fahrzeugbeschränkungen TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Erlaube oder verbiete Fahrzeugen, bestimmte Straßensegmente zu benutzen.\n\n1. Klicke auf ein Straßensegment.\n2. Klicke auf die Symbole, um die Beschränkungen ein- oder auszuschalten.\n\nUnterschiedene Fahrzeugtypen:\n\n- Straßenfahrzeuge: KFZ, Busse, Taxen, Lieferwagen/Laster, Dienstleistungen, Notfallfahrzeuge\n- Züge: Passagierzüge, Frachtzüge\n\nVerfügbare Tastenkombinationen:\n\n- Umschalt: Halte die Taste beim Klicken, um Fahrzeugbeschränkungen auch auf benachbarte Straßen-/Schienensegmente anzuwenden. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Standard-Geschwindigkeitsbegrenzungen TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Benutze die Pfeiltasten in der oberen Hälfte des Fensters, um zwischen allen verfügbaren Straßensegmenten umzuschalten.\n2. Wähle auf die gleiche Weise in der unteren Hälfte die gewünschte Geschwindigkeitsbegrenzung aus.\n3. Klicke auf "Speichern" um die Auswahl für in der Zukunft gebaute Straßensegmente des gewählten Typs anzuwenden. Klicke auf "Speichern & Anwenden", um die Auswahl auch für bereits gebaute Straßen des gewählten Typs anzuwenden. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Füge einen neuen Zeitschritt hinzu TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Klick auf eine Ampel innerhalb des Overlays, um ihren Zustand zu ändern. Verwende die "Change mode"-Schaltfläche im Overlay, um Ampeln für verschiedene Richtungen hinzuzufügen.\n2. Gebe eine Minimal- und Maximaldauer für den Zeitschritt ein. Sobald die Minimalzeit verstrichen ist, wird die Ampelschaltung die ankommendenen Fahrzeuge messen und die Zahlen für alle Richtungen miteinander vergleichen.\n3. (optional) Konfiguriere die adaptive Schrittumschaltung. Im Standard wird der Schritt gewechselt, wenn (grob) weniger Fahrzeuge die Kreuzung durchqueren als Fahrzeuge warten.\n4. (optional) Konfiguriere die Sensitivität der Ampelschaltung. Ziehe den Regler z.B. nach links, um die Ampel weniger sensibel für wartende Fahrzeuge zu machen. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Kopiere eine zeitgesteuerte Ampel TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Klicke auf eine andere Kreuzung, um die Ampelschaltung dort einzufügen.\n\nKlicke irgendwo mit der sekundären Maustaste, um die Operation abzubrechen. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Füge der Ampelschaltung eine andere Kreuzung hinzu TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Klicke auf eine andere Kreuzung, um sie der Schaltung hinzuzufügen. Die Ampelschaltung wird danach beide Kreuzungen gleichzeitig steuern.\n\nKlicke irgendwo mit der sekundären Maustaste, um die Operation abzubrechen. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Entferne eine Kreuzung von der Ampelschaltung. TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Klicke auf eine der Kreuzungen, die momentan von der Ampelschaltungen kontrolliert werden. Die selektierte Ampel wird entfernt, so dass die Ampelschaltung die Kreuzung nicht mehr kontrolliert.\n\nKlicke irgendwo mit der sekundären Maustaste, um die Operation abzubrechen. Public_transport Öffentlicher Personennahverkehr Prevent_excessive_transfers_at_public_transport_stations Verhindere übertriebenes Umsteigen an Haltestellen Compact_main_menu Kompaktes Hauptmenü Window_transparency Fenstertransparenz Overlay_transparency Overlaytransparenz Remove_this_citizen Diesen Cim entfernen Show_error_message_if_a_mod_incompatibility_is_detected Zeige Fehlermeldung, wenn eine Mod-Inkompatibilität erkannt wurde Remove_parked_vehicles Entferne geparkte Autos Node_is_level_crossing Diese Kreuzung ist ein Bahnübergang.\nDu kannst die Ampeln hier nicht entfernen. Experimental_features Experimentelle Features Turn_on_red Turn on red Vehicles_may_turn_on_red Fahrzeuge dürfen an roten Ampeln abbiegen Also_apply_to_left/right_turns_between_one-way_streets Gilt auch für Links- & Rechtskurven zwischen Einbahnstraßen ================================================ FILE: TLM/TLM/Resources/lang_es.txt ================================================ Switch_traffic_lights Agregar/Borrar semáforos Add_priority_signs Agregar señales de tránsito Manual_traffic_lights Semáforos manuales Timed_traffic_lights Semáforos programados Change_lane_arrows Editar flechas de pistas Clear_Traffic Limpiar Tráfico Disable_despawning Desactivar desaparición Enable_despawning Activar desaparición NODE_IS_LIGHT El cruce tiene semáforos.\nBorra los semáforos seleccionando "Agregar/Borrar semáforos" y haz clic en este cruce. NODE_IS_TIMED_LIGHT El cruce es parte de un programa temporizado.\nSelecciona "Semáforos programados", haz clic en este cruce y luego en "Eliminar". Select_nodes_windowTitle Seleccionar cruces Select_nodes Seleccionar cruces Node Cruce Deselect_all_nodes Deseleccionar todos los cruces Setup_timed_traffic_light Ajustar semáforos programados State Estado Skip Saltar up arriba down abajo View Ver Edit Editar Delete Borrar Timed_traffic_lights_manager Administrador de semáforos programados Add_step Añadir fase Remove_timed_traffic_light Borrar semáforo programado Min._Time: Tiempo min: Max._Time: Tiempo máx: Save Guardar Add Agregar Sensitivity Sensibilidad Very_Low Muy Baja Low Baja Medium Media High Alta Very_high Muy alta Extreme_long_green/red_phases Fases verde/rojo extremadamente largas Very_long_green/red_phases Fases verde/rojo muy largas Long_green/red_phases Fases verde/rojo largas Moderate_green/red_phases Fases verde/rojo moderadas Short_green/red_phases Fases verde/rojo cortas Very_short_green/red_phases Fases verde/rojo muy cortas Extreme_short_green/red_phases Fases verde/rojo extremadamente cortas Hide_counters Ocultar contadores Show_counters Mostrar contadores Start Iniciar Stop Detener Enable_test_mode_(stay_in_current_step) Activar modo de prueba (mantiene la fase actual) avg._flow Tránsito promedio avg._wait Espera promedio min/max min/máx Lane Vía Set_Speed Ajustar Velocidad {0} Max_speed Velocidad Máxima Segment Segmento incoming viniendo Enable_Advanced_Vehicle_AI Activar IA de vehículos avanzada Vehicles_may_enter_blocked_junctions Vehículos pueden entrar a cruces bloqueados All_vehicles_may_ignore_lane_arrows Todos los vehículos pueden ignorar las flechas de las vías Busses_may_ignore_lane_arrows Buses pueden ignorar flechas de las vías Reckless_driving Conducción agresiva (BETA) TMPE_Title Traffic Manager: President Edition Settings_are_defined_for_each_savegame_separately Los ajustes son definidos para cada partida por separado Simulation_accuracy Precisión de la simulación (más alto reduce el rendimiento) Enable_highway_specific_lane_merging/splitting_rules Activar control de unión/división de vías de autopista Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Conductores prefieren cambiar de vía (sólo si IA Avanzada está activada) Maintenance Mantenimiento Very_often Muy frecuentemente Often Frecuentemente Sometimes A veces Rarely Raramente Very_rarely Muy raramente Only_if_necessary Sólo si es necesario Nodes_and_segments Cruces y segmentos Lanes Vías Path_Of_Evil_(10_%) El mismo infierno (10 %) Rush_Hour_(5_%) Hora Punta (5 %) Minor_Complaints_(2_%) Colisiones menores (2 %) Holy_City_(0_%) Ciudad divina (0 %) Forget_toggled_traffic_lights Borrar semáforos (no afecta semáforos programados) Road_is_already_in_a_group! ¡La calle ya está en un grupo! All_selected_roads_must_be_of_the_same_type! ¡Todas las calles deben ser del mismo tipo! Create_group Crear Grupo Delete_group Borrar Grupo Add_zoning Agregar zona Remove_zoning Borrar zona Lane_Arrow_Changer_Disabled_Highway El editor de flechas está desactivado debido a que has activado el control de autopistas. Add_junction_to_timed_light Agregar cruce a este conjunto temporizador Remove_junction_from_timed_light Remover cruce de este conjunnto temporizador Select_junction Seleccionar un cruce Cancel Cancelar Speed_limits Límites de velocidad Persistently_visible_overlays Datos mostrados en el juego Priority_signs Señales de prioridad Vehicles_may_do_u-turns_at_junctions Los vehículos pueden hacer giros en "U" en los cruces Vehicles_going_straight_may_change_lanes_at_junctions Los vehículos que circulan recto pueden cambiar de pista en intersecciones Allow_u-turns Permitir giros en "U" Allow_lane_changing_for_vehicles_going_straight Permitir cambio de pista a los vehículos que circulan recto Allow_vehicles_to_enter_a_blocked_junction Permitor que los vehículos ingresen a cruces bloqueados Road_condition_has_a_bigger_impact_on_vehicle_speed La condición de la calle tiene un mayor impacto en la rapidez de los vehículos Vehicle_restrictions Restricción vehicular Copy Copiar Paste Pegar Invert Invertir Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions Aplicar restricción vehicular a todas los segmentos de calle entre dos cruces Allow_all_vehicles Permitir a todos los vehículos Ban_all_vehicles Prohibir a todos los vehículos Set_all_traffic_lights_to_red Poner todos los semáforos en rojo Rotate_left Girar a la izq. Rotate_right Girar a la der. Name Nombre Apply Aplicar Select_a_timed_traffic_light_program Seleccionar un semáforo programado The_chosen_traffic_light_program_is_incompatible_to_this_junction El programa de semáforo elegido es incompatible para este cruce Advanced_AI_cannot_be_activated IA Avanzada no puede ser activada The_Advanced_Vehicle_AI_cannot_be_activated La IA de vehículos avanzada no puede ser activada debido a que estás usando algún otro mod que modifica el comportamiento de los vehículos (por ejemplo Improved AI o Traffic++) Enable_dynamic_path_calculation Activar el cálculo dinámico de las rutas Lane_Arrow_Changer_Disabled_Connection El modificador de flechas de la carretera fue desactivado porque has creado las conexiones manualmente. Lane_connector Conector de carriles Connected_lanes Carriles conectados Use_alternative_view_mode Usar modo de vista alternativo Road_type Tipo de carretera Default_speed_limit Límite de velocidad por defecto Unit_system Sistema de medida Metric Métrico Imperial Imperial Use_more_CPU_cores_for_route_calculation_if_available Usar más nucleos del procesador para el cálculo de rutas (si está disponible) Activated_features Características activadas Junction_restrictions Restricciones del cruce Prohibit_spawning_of_pocket_cars Prohibir la creación de autos pequeños Reset_stuck_cims_and_vehicles Restablecer cims y vehículos atascados Default_speed_limits Límites de velocidad por defecto Looking_for_a_parking_spot Buscando un estacionamiento Driving_to_a_parking_spot Conduciendo a un estacionamiento Driving_to_another_parking_spot Conduciendo a otro estacionamiento Entering_vehicle Vehículo entrante Walking_to_car Caminando al auto Using_public_transport Usando transporte público Walking Caminando Thinking_of_a_good_parking_spot Pensando en un buen estacionamiento Switch_view Cambiar vista Outgoing_demand Demanda saliente Incoming_demand Demanda entrante Advanced_Vehicle_AI IA de vehículos avanzada Heavy_trucks_prefer_outer_lanes_on_highways Camiones prefieren los carriles exteriores en las autopistas Parking_AI IA de estacionamientos Enable_more_realistic_parking Activar estacionamientos más realista Reset_custom_speed_limits Restablecer límites de velocidad personalizados Reload_global_configuration Recargar configuración global Reset_global_configuration Restablecer configuración global General General Gameplay Jugabilidad Overlays Capas de información Realistic_speeds Velocidades realistas Evacuation_busses_may_ignore_traffic_rules Los buses de evacuación pueden ignorar reglas de tránsito Evacuation_busses_may_only_be_used_to_reach_a_shelter Los buses de evacuación sólo pueden usarse para llegar a un refugio Vehicle_behavior Comportamiento vehícular Policies_&_Restrictions Normas y restricciones At_junctions En los cruces In_case_of_emergency En caso de emergencia Show_lane-wise_speed_limits Mostrar límite de velocidad en el carril Language Idioma Game_language Idioma del juego requires_game_restart Requiere reiniciar el juego Customizations_come_into_effect_instantaneously Los modificaciones tendrán efecto instantáneamente Options Opciones Lock_main_menu_button_position Bloquear la posición del menú principal Lock_main_menu_position Bloquear posición del menú principal Recalculating_lane_routing Recalculando la ruta del carril Please_wait Espera por favor Parking_restrictions Restricciones de los estacionamientos Simulation Simulation On_roads On roads Ban_private_cars_and_trucks_on_bus_lanes Ban private cars and trucks on bus lanes default default flow_ratio flow ratio wait_ratio wait ratio After_min._time_has_elapsed_switch_to_next_step_if After min. time has elapsed, switch to next step if Adaptive_step_switching Adaptive step switching Dynamic_lane_section Dynamic lane selection Percentage_of_vehicles_performing_dynamic_lane_section Percentage of vehicles performing dynamic lane selection Vehicle_restrictions_aggression Vehicle restrictions aggression Strict Strict Show_path-find_stats Show path-find stats Remove_this_vehicle Remove this vehicle Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights Vehicles follow priority rules at junctions with timed traffic lights Enable_tutorial_messages Enable tutorial messages TMPE_TUTORIAL_HEAD_MainMenu Traffic Manager: President Edition TMPE_TUTORIAL_BODY_MainMenu Welcome to TM:PE!\n\nUser manual: http://www.viathinksoft.de/tmpe TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool Junction restrictions TMPE_TUTORIAL_BODY_JunctionRestrictionsTool Control how vehicles and pedestrians shall behave at junctions.\n\n1. Click on the junction you want to manage\n2. Click on the appropriate icon to toggle restrictions.\n\nAvailable restrictions:\n- Allow/Disallow lane changing for vehicle going straight on\n- Allow/Disallow u-turns\n- Allow/Disallow vehicles to enter a blocked junction\n- Allow/disallow pedestrians to cross the street TMPE_TUTORIAL_HEAD_LaneArrowTool Lane arrows TMPE_TUTORIAL_BODY_LaneArrowTool Restrict the set of directions that vehicles are allowed to take.\n\n1. Click on a road segment next to a junction\n2. Select the allowed directions. TMPE_TUTORIAL_HEAD_LaneConnectorTool Lane connector TMPE_TUTORIAL_BODY_LaneConnectorTool Connect two or more lanes with each other in order to tell which lanes vehicles may use.\n\n1. Click on a lane changing point (grey circles).\n2. Click on a source marker.\n3. Click on a target marker to connect it with the source marker.\n4. Click anywhere with your secondary mouse button to return back to source marker selection.\n\nAvailable hotkeys:\n\n- Delete or Backspace: Remove all lane connections\n- Shift + S: Cycle through all available "stay on lane" patterns TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool Manual traffic lights TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Try out custom traffic lights.\n\n1. Click on a junction\n2. Use the tool to control traffic lights. TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool Parking restrictions TMPE_TUTORIAL_BODY_ParkingRestrictionsTool Control where parking is allowed.\n\nClick on the "P" icons.\n\nAvailable hotkeys:\n\n- Shift: Hold while clicking to apply parking restrictions to multiple road segments at once TMPE_TUTORIAL_HEAD_PrioritySignsTool Priority signs TMPE_TUTORIAL_BODY_PrioritySignsTool Define priority rules at junctions.\n\n1. Click on a junction.\n2. Click on the blank spots to cycle through the available priority signs (priority road, yield, stop).\n\nAvailable hotkeys:\n\n- Shift: Hold Shift to add priority signs to multiple road segments at once. Click again while holding Shift to cycle through all available patterns. TMPE_TUTORIAL_HEAD_SpeedLimitsTool Speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool Set up speed restrictions.\n\n1. In the window, click on the speed limit you want to set.\n2. Click on a road segment to change the speed limit.\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply speed limits to multiple road segments at once.\n- Ctrl: Hold Ctrl to control speed limits per individual lane. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool Timed traffic lights TMPE_TUTORIAL_BODY_TimedTrafficLightsTool Set up timed traffic lights.\n\n1. Click on a junction.\n2. In the window, click on "Add step".\n3. Click on the overlay elements to set desired traffic lights states.\n4. Click on "Add".\n5. Repeat as desired. TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Toggle traffic lights TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool Add or remove traffic lights to/from junctions.\n\nClick on a junction to toggle traffic lights. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, busses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. Public_transport Public transport Prevent_excessive_transfers_at_public_transport_stations Prevent unnecessary transfers at public transport stations Compact_main_menu Compact main menu Window_transparency Window transparency Overlay_transparency Overlay transparency Remove_this_citizen Remove this citizen Show_error_message_if_a_mod_incompatibility_is_detected Show error message if a mod incompatibility is detected Remove_parked_vehicles Remove parked vehicles Node_is_level_crossing This junction is a level crossing.\nYou cannot disable traffic lights here. Experimental_features Experimental features Turn_on_red Turn on red Vehicles_may_turn_on_red Los vehículos pueden girar en los semáforos rojos. Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets ================================================ FILE: TLM/TLM/Resources/lang_fr.txt ================================================ Switch_traffic_lights (Dés)activer les feux tricolores Add_priority_signs Ajouter des signes de priorité Manual_traffic_lights Feux tricolores manuels Timed_traffic_lights Feux tricolores chronométrés Change_lane_arrows Changer les directions de voies Clear_Traffic Nettoyer le trafic Disable_despawning Désactiver la disparition Enable_despawning Activer la disparition NODE_IS_LIGHT Cette intersection a des feux rouges.\nSupprimez-les en sélectionnant "(Dés)activer les feux tricolores" et en cliquant sur l'intersection. NODE_IS_TIMED_LIGHT Cette intersection a des feux chronométrés.\nSélectionnez "Feux tricolores chronométrés", cliquer sur l'intersection, puis sur "Retirer" d'abord. Select_nodes_windowTitle Sélection des intersections Select_nodes Sélectionnez des intersections Node Intersection Deselect_all_nodes Désélectionner toutes les intersections Setup_timed_traffic_light Créer un chronométrage des feux State Etat Skip Passer up haut down bas View Voir Edit Modifier Delete Suppr. Timed_traffic_lights_manager Gestionnaire de Feux tricolores chronométrés Add_step Ajouter un état Remove_timed_traffic_light Retirer le chronométrage Min._Time: Temps Min: Max._Time: Temps Max: Save Sauvegarder Add Ajouter Sensitivity Sensibilité Very_Low Très Basse Low Basse Medium Moyenne High Haute Very_high Très haute Extreme_long_green/red_phases Phases rouge/vert extrêmement longues Very_long_green/red_phases Phases rouge/vert très longues Long_green/red_phases Phases rouge/vert longues Moderate_green/red_phases Phases rouge/vert moyennement longues Short_green/red_phases Phases rouge/vert courtes Very_short_green/red_phases Phases rouge/vert très courtes Extreme_short_green/red_phases Phases rouge/vert extrêmement courtes Hide_counters Masquer les compteurs Show_counters Afficher les compteurs Start Démarrer Stop Stop Enable_test_mode_(stay_in_current_step) Activer le mode test (rester dans l'état actuel) avg._flow flux moy. avg._wait attente moy. min/max min/max Lane Voie Set_Speed Régler la vitesse {0} Max_speed Vitesse max. Segment Segment incoming bientôt Enable_Advanced_Vehicle_AI Activer l'IA avancée des véhicules Vehicles_may_enter_blocked_junctions Les véhicules peuvent entrer dans les intersections bloquées All_vehicles_may_ignore_lane_arrows Tous les véhicules peuvent ignorer les directions des voies Busses_may_ignore_lane_arrows Les bus peuvent ignorer les directions des voies Reckless_driving Conduite téméraire (fonctionnalité Bêta) TMPE_Title Traffic Manager: Edition Président Settings_are_defined_for_each_savegame_separately Paramètres définis pour chaque sauvegarde séparément Simulation_accuracy Précision de la simulation (une plus haute précision réduit les performances) Enable_highway_specific_lane_merging/splitting_rules Activer les règles spécifiques de divergence/convergence sur les autoroutes Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Les conducteurs veulent changer de voies: (seulement si l'IA avancée est activée) Maintenance Maintenance Very_often Très souvent Often Souvent Sometimes Parfois Rarely Rarement Very_rarely Très rarement Only_if_necessary Seulement si nécessaire Nodes_and_segments Nœuds et segments Lanes Voies Path_Of_Evil_(10_%) Chemin du Mal (10 %) Rush_Hour_(5_%) Heures de Pointe (5 %) Minor_Complaints_(2_%) Plaintes mineures (2 %) Holy_City_(0_%) Ville Sainte (0 %) Forget_toggled_traffic_lights Oublier les feux tricolores retirés Road_is_already_in_a_group! Cette route est déjà dans un groupe ! All_selected_roads_must_be_of_the_same_type! Toutes les routes sélectionnées doivent être du même type! Create_group Créer un groupe Delete_group Supprimer un groupe Add_zoning Ajouter le zonage Remove_zoning Retirer le zonage Lane_Arrow_Changer_Disabled_Highway Le modificateur de directions pour cette voie est désactivé car vous avez activé le système de règles pour autoroutes. Add_junction_to_timed_light Ajouter une intersection à ce feu tricolore Remove_junction_from_timed_light Retirer une intersection de ce feu tricolore Select_junction Sélectionnez une intersection Cancel Annuler Speed_limits Limitations de vitesse Persistently_visible_overlays Annonces en superposition visibles de façon permanente Priority_signs Signes de priorité Vehicles_may_do_u-turns_at_junctions Les véhicules peuvent faire demi-tour aux intersections Vehicles_going_straight_may_change_lanes_at_junctions Les véhicules allant tout droit peuvent changer de voie aux intersections Allow_u-turns Autoriser les demi-tours Allow_lane_changing_for_vehicles_going_straight Autoriser le changement de voie pour les véhicules allant tout droit Allow_vehicles_to_enter_a_blocked_junction Autoriser les véhicules à entrer dans une jonction bloquée Road_condition_has_a_bigger_impact_on_vehicle_speed La condition de la voirie a une plus grand impact sur la vitesse des véhicules Vehicle_restrictions Restrictions des véhicules Copy Copier Paste Coller Invert Inverser Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions Appliquer les restrictions de véhicules à tous les segments de route entre deux jonctions Allow_all_vehicles Autoriser tous les véhicules Ban_all_vehicles Bannir tous les véhicules Set_all_traffic_lights_to_red Mettre tous les feux au rouge Rotate_left Rotation vers la gauche Rotate_right Rotation vers la droite Name Nommer Apply Appliquer Select_a_timed_traffic_light_program Sélectionner un programme de chronométrage de feux The_chosen_traffic_light_program_is_incompatible_to_this_junction Le modèle de chronométrage choisi est incompatible avec cette jonction Advanced_AI_cannot_be_activated L'IA avancée ne peut pas être activée The_Advanced_Vehicle_AI_cannot_be_activated L'IA avancée de véhicule ne peut être activée car vous utilisez déjà un autre mod qui modifie modifie le comportement des véhicules (par exemple Improved AI ou Traffic++). Enable_dynamic_path_calculation Activer le calcul dynamique des chemins Lane_Arrow_Changer_Disabled_Connection Le modificateur des directions de voies est désactivé pour cette voie car vous avez manuellement créé des connections de voies. Lane_connector Connecteur de voies Connected_lanes Voies connectées Use_alternative_view_mode Utiliser le mode de vue alternatif Road_type Type de route Default_speed_limit Limite de vitesse par défaut Unit_system Système d'unités Metric Système métrique Imperial Système impérial Use_more_CPU_cores_for_route_calculation_if_available Utiliser plus de cœurs du CPU pour le calcul de trajets (si disponible) Activated_features caractéristiques activées Junction_restrictions Restrictions de jonction Prohibit_spawning_of_pocket_cars Prohibit spawning of pocket cars Reset_stuck_cims_and_vehicles Réinit. citoyens et véhicules coincés Default_speed_limits Limites des vitesse par défaut Looking_for_a_parking_spot Cherche une place de stationnement Driving_to_a_parking_spot Se dirige vers un stationnement Driving_to_another_parking_spot Se dirige vers un autre stationnement Entering_vehicle Entre dans un véhicule Walking_to_car Se dirige vers une voiture Using_public_transport Utilise les transports en commun Walking Marche Thinking_of_a_good_parking_spot Réfléchi à un bon stationnement Switch_view Changer de vue Outgoing_demand Demande sortante Incoming_demand Demande entrante Advanced_Vehicle_AI IA avancée des véhicules Heavy_trucks_prefer_outer_lanes_on_highways Les véhicules lourds préfèrent les voies extérieures sur les autoroutes Parking_AI IA de stationnement Enable_more_realistic_parking Activer le stationnement plus réaliste Reset_custom_speed_limits Réinitialiser les limites de vitesse persos Reload_global_configuration Recharger la config. globale Reset_global_configuration Réinitialiser la config. globale General Général Gameplay Gameplay Overlays Interface Realistic_speeds Vitesses réalistes Evacuation_busses_may_ignore_traffic_rules Les bus d'évacuation peuvent ignorer les règles de traffic Evacuation_busses_may_only_be_used_to_reach_a_shelter Les bus d'évacuation peuvent peut-être n'être utilisés que pour atteindre un abri Vehicle_behavior Comportement des véhicules Policies_&_Restrictions Restrictions et politiques At_junctions Aux jonctions In_case_of_emergency En cas d'urgence Show_lane-wise_speed_limits Voir limites de vitesse par voie Language Langue Game_language Langue du jeu requires_game_restart nécessite le redémarrage du jeu Customizations_come_into_effect_instantaneously Les personnalisations prennent effet instantanément Options Options Lock_main_menu_button_position Verrouiller la position du bouton du menu principal Lock_main_menu_position Verrouiller la position du menu principal Recalculating_lane_routing Recalcul en cours des routing des voies Please_wait Merci de patienter Parking_restrictions Restrictions de stationnement Simulation Simulation On_roads Sur les routes Ban_private_cars_and_trucks_on_bus_lanes Interdire les voitures privées et les camions dans les couloirs de bus default par défaut flow_ratio ratio de mouvement wait_ratio ratio d'attente After_min._time_has_elapsed_switch_to_next_step_if Après le temps minimum écoulé, passez à l'étape suivante si Adaptive_step_switching Commutation d'étape adaptive Dynamic_lane_section Sélection de voie dynamique Percentage_of_vehicles_performing_dynamic_lane_section Pourcentage de véhicules effectuant une sélection de voie dynamique Vehicle_restrictions_aggression Restrictions agressives des véhicules Strict Stricte Show_path-find_stats Afficher le chemin d'acès des stats Remove_this_vehicle Retirer ce véhicule Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights Les véhicules suivent les règles de priorité aux carrefours avec des feux de circulations temporiés Enable_tutorial_messages Activier les messages du didactitiel TMPE_TUTORIAL_HEAD_MainMenu Gestionnaire de traffic : Édition Président TMPE_TUTORIAL_BODY_MainMenu Bienvenue à TM:PE !\n\nManuel d'utilisateur : http://www.viathinksoft.de/tmpe TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool Restrictions aux carrefours TMPE_TUTORIAL_BODY_JunctionRestrictionsTool Comment contrôler le comportement des véhicules et des piétons aux carrefours.\n\n1. Cliquer sur le carrefour que vous voulez gérer\n2. Cliquez sur l'icône appropriée pour basculer entre les restrictions.\n\nRestrictions disponibles :\n- Autoriser/Interdire le changement de voie pour que le véhicule continue tout droit\n- Autoriser/Interdire les demi-tours\n- Autoriser/Interdire aux véhicules d'entrer dans un carrefour bloqué\n- Autoriser/Interdire aux pietons de traverser la rue TMPE_TUTORIAL_HEAD_LaneArrowTool Flèche des voies de circulation TMPE_TUTORIAL_BODY_LaneArrowTool Restreindre l'ensemble des directions que les véhicules sont autorisés à prendre.\n\n1. Cliquez sur un segment de route à côté d'une intersection\n2. Sélectionnez les directions autorisées. TMPE_TUTORIAL_HEAD_LaneConnectorTool Connecteur des voies de circulation TMPE_TUTORIAL_BODY_LaneConnectorTool Reliez deux ou plusieurs voies entre elles afin de déterminer les voies que les véhicules peuvent utiliser.\n\n1. Cliquez sur une intersection (cercles gris).\n2. Cliquez sur un marqueur de source.\n3. Cliquez sur un marqueur cible pour le relier au marqueur source.\n4. Cliquez n'importe où avec votre bouton secondaire de la souris pour revenir à la sélection du marqueur source.\n\nTouches de raccourci disponibles :\n\n- Supprimer ou Retour arrière : Supprimer toutes les connexions de voie\n- Shift + S : Parcourez tous les modèles disponibles "rester sur la voie" TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool Manuel des feux de circulation TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Essayez les feux de circulation personnalisés.\n\n1. Cliquez sur une intersection\n2. Utilisez l'outil pour contrôler les feux de circulation. TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool Restrictions de stationnement TMPE_TUTORIAL_BODY_ParkingRestrictionsTool Contrôler où le stationnement est autorisé.\n\nCliquez sur les icônes "P".\n\nTouches de raccourci disponibles :\n\n- Shift : Maintenez la touche enfoncée tout en cliquant pour appliquer des restrictions de stationnement à plusieurs segments de route à la fois TMPE_TUTORIAL_HEAD_PrioritySignsTool Signes prioritaires TMPE_TUTORIAL_BODY_PrioritySignsTool Définir les règles de priorité aux intersations.\n\n1. Cliquer sur l'intersection.\n2. Cliquez sur les cercles vides pour parcourir les panneaux de priorité disponibles (route prioritaire, cédez le passage, stop, vide).\n\nTouches de raccourci disponibles :\n\n- Shift : Maintenez la touche Maj enfoncée pour ajouter des signes de priorité à plusieurs segments de route à la fois. Cliquez à nouveau tout en maintenant la touche Maj enfoncée pour parcourir tous les motifs disponibles. TMPE_TUTORIAL_HEAD_SpeedLimitsTool Limites de vitesse TMPE_TUTORIAL_BODY_SpeedLimitsTool Configurer les restrictions de limitation de vitesse.\n\n1. Dans la fenêtre, cliquez sur la limite de vitesse que vous souhaitez définir.\n2. Cliquez sur un segment de route pour changer la limitation de vitesse.\n\nTouches de raccourci disponibles :\n\n- Shift : Maintenez la touche Maj enfoncée tout en cliquant pour appliquer des limitations de vitesse à plusieurs segments de route à la fois.\n- Ctrl : Maintenez la touche Ctrl enfoncée pour contrôler les limitations de vitesse par voie individuelle. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool Feux de circulation chronométrés TMPE_TUTORIAL_BODY_TimedTrafficLightsTool Mettre en place des feux de circulation chronométrés.\n\n1. Cliquez sur une intersection.\n2. Dans la fenêtre, cliquez sur "Ajouter une étape".\n3. Cliquez sur les éléments de superposition pour définir les états de feux de circulation souhaités.\n4. Cliquez sur "Ajouter".\n5. Répétez comme vous le désiré. TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Basculer les feux de circulation TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool Ajouter ou supprimer des feux de circulation/aux intersections.\n\nCliquez sur une intersection pour basculer les feux de circulation.. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Restrictions de véhicules TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Interdire les véhicules sur certaines portion de route.\n\n1. Cliquez sur une portion ou segment de route.\n2. Cliquez sur les icônes pour faire basculer les restrictions.\n\nDistinction des types de véhicules :\n\n- Véhicules routiers : Voitures de tourisme, autobus, taxis, camions de fret, véhicules de service, véhicules d'urgence\n- Véhicules ferroviaires : Trains de voyageurs, trains de marchandises\n\nTouches de raccourci disponibles :\n\n- Shift : Maintenez la touche Maj enfoncée tout en cliquant pour appliquer des restrictions à plusieurs portions ou segments de route à la fois. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Limites de vitesse par défaut TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Utilisez les flèches dans la moitié supérieure pour parcourir tous les types de routes.\n2. Dans la moitié inférieure, sélectionnez une limite de vitesse.\n3. Cliquez sur "Enregistrer" pour définir la limite de vitesse sélectionnée par défaut pour les futurs segments de route du type sélectionné. Cliquez sur "Enregistrer et appliquer" pour également mettre à jour toutes les routes existantes du type sélectionné. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Ajouter une étape chronométrée TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Pendant le jeu, cliquez sur les feux de circulation pour changer leur état. Utilisez le bouton "Changer de mode" pour ajouter des feux de circulation directionnels.\n2. Entrez à la fois une durée minimale et maximale pour l'étape. Après le temps minimum écoulé, le feu de circulation comptera et comparera les véhicules qui approchent.\n3. En option, sélectionnez un type de changement d'étape. Par défaut, l'étape change si à peu près moins de véhicules roulent qu'en attente.\n4. En option, ajustez la sensibilité de la lumière. Par exemple, déplacez le curseur vers la gauche pour rendre le feu de circulation temporisé moins sensible pour les véhicules en attente. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copier un feu de circulation minuté TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Cliquez sur un autre carrefour pour coller le feu de circulation chronométré.\n\nCliquez n'importe où avec votre bouton secondaire de la souris pour annuler l'opération. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Ajouter à l'intersection ou au carrefour un feu de circulation minuté TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Cliquez sur une autre intersection pour l'ajouter. Les deux lumières seront jointes de sorte que le programme chronométré contrôlera ensuite les deux intersections à la fois.\n\nCliquez n'importe où avec votre bouton secondaire de la souris pour annuler l'opération. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Retirer à l'intersection le feu de circulation minuté TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Cliquez sur l'une des jonctions contrôlées par ce programme temporisé. Le feu de signalisation sélectionné sera supprimé de telle sorte que le programme chronométré ne le gérera plus.\n\nCliquez n'importe où avec votre bouton secondaire de la souris pour annuler l'opération. Public_transport Transport public Prevent_excessive_transfers_at_public_transport_stations Empêcher les transferts inutiles dans les stations de transport public Compact_main_menu Menu principal compact Window_transparency Fenêtre transparente Overlay_transparency Superposition de transparence Remove_this_citizen Retirer ce citoyen Show_error_message_if_a_mod_incompatibility_is_detected Afficher le message d'erreur si une incompatibilité d'un mod est détectée Remove_parked_vehicles Enlever les véhicules stationnés Node_is_level_crossing Cette intersection est un passage à niveau.\nous ne pouvez pas désactiver les feux de circulation ici. Experimental_features Experimental features Turn_on_red Tourner aux feux rouges Vehicles_may_turn_on_red Les véhicules peuvent tourner aux feux rouges Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets ================================================ FILE: TLM/TLM/Resources/lang_it.txt ================================================ Switch_traffic_lights Attiva/Disattiva semaforo Add_priority_signs Aggiungi segnale stradale Manual_traffic_lights Semaforo (manuale) Timed_traffic_lights Semaforo (temporizzato) Change_lane_arrows Modifica frecce di direzione Clear_Traffic Resetta traffico Disable_despawning Disabilita il despawn Enable_despawning Abilita il despawn NODE_IS_LIGHT In questo incrocio c'è un semaforo.\nElimina il semaforo scegliendo "Attiva/Disattiva semaforo" NODE_IS_TIMED_LIGHT In questo incrocio c'è un semaforo temporizzato\nPrima seleziona "Semaforo (temporizzato)", clicca su questo nodo e poi "Rimuovi". Select_nodes_windowTitle Seleziona nodi Select_nodes Seleziona nodi Node Nodo Deselect_all_nodes Deseleziona tutti i nodi Setup_timed_traffic_light Configura semaforo temporizzato State Stato Skip Salta up Su down Giù View Vedi Edit Modifica Delete Elimina Timed_traffic_lights_manager Gestore semafori temporizzati Add_step Aggiungi stato Remove_timed_traffic_light Rimuovi semaforo temporizzato Min._Time: Min. Tempo: Max._Time: Max. Tempo: Save Salva Add Aggiungi Sensitivity Sensibilità Very_Low Molto basso Low Basso Medium Medio High Alto Very_high Molto alto Extreme_long_green/red_phases Passaggio verde/rosso estremamente lungo Very_long_green/red_phases Passaggio verde/rosso molto lungo Long_green/red_phases Passaggio verde/rosso lungo Moderate_green/red_phases Passaggio verde/rosso moderato Short_green/red_phases Passaggio verde/rosso corto Very_short_green/red_phases Passaggio verde/rosso molto corto Extreme_short_green/red_phases Passaggio verde/rosso estremamente corto Hide_counters Nascondi countdown Show_counters Mostra countdown Start Avvia Stop Stop Enable_test_mode_(stay_in_current_step) Abilita modalità test (rimani nello step corrente) avg._flow Flusso medio avg._wait Attesa media min/max min/max Lane Corsia Set_Speed Imposta velocità {0} Max_speed Velocità massima Segment Segmento incoming In arrivo Enable_Advanced_Vehicle_AI Abilita IA dei Veicoli Avanzata Vehicles_may_enter_blocked_junctions I veicoli possono entrare/occupare un incrocio bloccato All_vehicles_may_ignore_lane_arrows Tutti i veicoli possono ignorare le frecce di direzione Busses_may_ignore_lane_arrows I bus possono ignorare le frecce di direzione Reckless_driving Guida spericolata TMPE_Title Traffic Manager: President Edition Settings_are_defined_for_each_savegame_separately I settaggi possono essere impostati separatamente per ogni salvataggio Simulation_accuracy Precisione simulazione (Un'elevata precisione riduce le performance) Enable_highway_specific_lane_merging/splitting_rules Abilita regolazione specifica per le corsie autostradali Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Agli automobilisti piace cambiare corsia (applicata solo se attiva la IA dei Veicoli Avanzata) Maintenance Manutenzione Very_often Molto di frequente Often Di frequente Sometimes A volte Rarely Raramente Very_rarely Molto raramente Only_if_necessary Solo se necessario Nodes_and_segments Nodi e segmenti Lanes Corsie Path_Of_Evil_(10_%) Molto elevata (10 %) Rush_Hour_(5_%) Elevata (5 %) Minor_Complaints_(2_%) Modesta (2 %) Holy_City_(0_%) Pacifica (0 %) Forget_toggled_traffic_lights "Dimentica" semafori impostati Road_is_already_in_a_group! La strada è già in un gruppo! All_selected_roads_must_be_of_the_same_type! Tutte le strade selezionate devono essere dello stesso tipo! Create_group Crea gruppo Delete_group Elimina gruppo Add_zoning Aggiungi zoning Remove_zoning Rimuovi zoning Lane_Arrow_Changer_Disabled_Highway La modifica delle frecce di direzione per questa corsia è disabilitata perchè è attivo il sistema di regole autostradali predefinito. Puoi disabilitarlo nelle opzioni alla voce "Abilita regolazione specifica per le corsie autostradali". Add_junction_to_timed_light Aggiungi un incrocio a questo semaforo Remove_junction_from_timed_light Rimuovi un incrocio a questo semaforo Select_junction Seleziona un incrocio Cancel Annulla Speed_limits Limiti di velocità Persistently_visible_overlays Overlays visibili persistentemente Priority_signs Segnali stradali Vehicles_may_do_u-turns_at_junctions I veicoli possono effettuare una svolta a "U" all'incrocio Vehicles_going_straight_may_change_lanes_at_junctions I veicoli che proseguono dritto possono cambiare corsia all'incrocio Allow_u-turns Permetti le svolte a "U" Allow_lane_changing_for_vehicles_going_straight Permetti il cambio corsia per i veicoli che proseguono dritto all'incrocio Allow_vehicles_to_enter_a_blocked_junction Permetti ai veicoli di entrare/occupare un incrocio bloccato/intasato Road_condition_has_a_bigger_impact_on_vehicle_speed La condizione della strada ha un grosso impatto sulla velocità dei veicoli Vehicle_restrictions Restrizioni veicoli Copy Copia Paste Incolla Invert Inverti Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions Applica le restrizioni al tratto di strada compreso tra due incroci Allow_all_vehicles Permetti l'accesso a tutti i veicoli Ban_all_vehicles Banna tutti i veicoli Set_all_traffic_lights_to_red Imposta tutti i semafori sul Rosso Rotate_left Ruota a sinistra Rotate_right Ruota a destra Name Nome Apply Applica Select_a_timed_traffic_light_program Seleziona pattern per il semaforo temporizzato The_chosen_traffic_light_program_is_incompatible_to_this_junction Il pattern scelto è incompatibile con questo incrocio Advanced_AI_cannot_be_activated La IA Avanzata non può essere attivata The_Advanced_Vehicle_AI_cannot_be_activated La IA Avanzata non può essere attivata perchè stai usando un'altra mod che modifica il comportamento dei veicoli (es. Improved AI o Traffic++). Enable_dynamic_path_calculation Abilita calcolo del percorso dinamico Lane_Arrow_Changer_Disabled_Connection La modifica delle frecce di direzione per questa corsia è disabilitata perchè hai creato la connessione della corsia a mano. Lane_connector Connettore corsia Connected_lanes Corsie connesse Use_alternative_view_mode Utilizza modilità vista alternativa Road_type Tipo di strada Default_speed_limit Limite velocità di default Unit_system Unità di misura Metric Metrico Imperial Imperiale Use_more_CPU_cores_for_route_calculation_if_available Utilizza più core della CPU per il calcolo del percorso (se disponile) Activated_features Features attivate Junction_restrictions Restrizioni incrocio Prohibit_spawning_of_pocket_cars Vieta ai "cims" di spawnare "pocket cars" Reset_stuck_cims_and_vehicles Resetta cims e veicoli bloccati Default_speed_limits Limiti di velocità di default Looking_for_a_parking_spot In cerca di un parcheggio Driving_to_a_parking_spot Guidando verso un parcheggio Driving_to_another_parking_spot Guidando verso un altro parcheggio Entering_vehicle Entrando nel veicolo Walking_to_car Camminando verso l'auto Using_public_transport Utilizzando il trasporto pubblico Walking Camminando Thinking_of_a_good_parking_spot Pensando ad un buon parcheggio Switch_view Cambia vista Outgoing_demand Outgoing demand Incoming_demand Incoming demand Advanced_Vehicle_AI IA Avanzata Heavy_trucks_prefer_outer_lanes_on_highways I veicoli pesanti preferiscono corsie esterne sulle autostrade Parking_AI Parking AI Enable_more_realistic_parking Abilita utilizzo più realistico dei parcheggi Reset_custom_speed_limits Resetta i limiti di velocita personalizzati Reload_global_configuration Ricarica configurazione globale Reset_global_configuration Resetta configurazione globale General Generale Gameplay Gameplay Overlays Overlays Realistic_speeds Velocità realistica dei veicoli Evacuation_busses_may_ignore_traffic_rules I bus di evacuazione possono ignorare le regole stradali Evacuation_busses_may_only_be_used_to_reach_a_shelter I bus di evacuazione possono essere usati solo per raggiungere un rifugio Vehicle_behavior Comportamente del veicolo Policies_&_Restrictions Policies & Restrizioni At_junctions Agli incroci In_case_of_emergency In caso di emergenza Show_lane-wise_speed_limits Mostra limite velocità per ogni corsia Language Lingua Game_language Lingua di gioco requires_game_restart Richiede il riavvio del gioco Customizations_come_into_effect_instantaneously Le modifiche avranno effetto immediatamente Options Opzioni Lock_main_menu_button_position Blocca posizione del bottone (menu) Lock_main_menu_position Blocca posizione del menu Recalculating_lane_routing Ricalcolo percorso corsia Please_wait Attendere prego Parking_restrictions Divieti di sosta Simulation Simulazione On_roads Su strade Ban_private_cars_and_trucks_on_bus_lanes Vieta alle auto e ai camion di utilizzare le corsie riservate ai bus default predefinito flow_ratio Indice flusso wait_ratio Indice attesa After_min._time_has_elapsed_switch_to_next_step_if Dopo min. tempo trascorso, passare al passo successivo se Adaptive_step_switching Azionamento adattivo prossimo stato Dynamic_lane_section Selezione corsia dinamica Percentage_of_vehicles_performing_dynamic_lane_section Percentuale di veicoli che effettuano la selezione dinamica della corsia Vehicle_restrictions_aggression Rigorosità restrizioni veicoli Strict Rigoroso Show_path-find_stats Mostra statistiche path-find Remove_this_vehicle Rimuovi questo veicolo Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights I veicoli seguono le regole di precedenza agli incroci con semafori a tempo Enable_tutorial_messages Enable tutorial messages TMPE_TUTORIAL_HEAD_MainMenu Traffic Manager: President Edition TMPE_TUTORIAL_BODY_MainMenu Welcome to TM:PE!\n\nUser manual: http://www.viathinksoft.de/tmpe TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool Junction restrictions TMPE_TUTORIAL_BODY_JunctionRestrictionsTool Control how vehicles and pedestrians shall behave at junctions.\n\n1. Click on the junction you want to manage\n2. Click on the appropriate icon to toggle restrictions.\n\nAvailable restrictions:\n- Allow/Disallow lane changing for vehicle going straight on\n- Allow/Disallow u-turns\n- Allow/Disallow vehicles to enter a blocked junction\n- Allow/disallow pedestrians to cross the street TMPE_TUTORIAL_HEAD_LaneArrowTool Lane arrows TMPE_TUTORIAL_BODY_LaneArrowTool Restrict the set of directions that vehicles are allowed to take.\n\n1. Click on a road segment next to a junction\n2. Select the allowed directions. TMPE_TUTORIAL_HEAD_LaneConnectorTool Lane connector TMPE_TUTORIAL_BODY_LaneConnectorTool Connect two or more lanes with each other in order to tell which lanes vehicles may use.\n\n1. Click on a lane changing point (grey circles).\n2. Click on a source marker.\n3. Click on a target marker to connect it with the source marker.\n4. Click anywhere with your secondary mouse button to return back to source marker selection.\n\nAvailable hotkeys:\n\n- Delete or Backspace: Remove all lane connections\n- Shift + S: Cycle through all available "stay on lane" patterns TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool Manual traffic lights TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Try out custom traffic lights.\n\n1. Click on a junction\n2. Use the tool to control traffic lights. TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool Parking restrictions TMPE_TUTORIAL_BODY_ParkingRestrictionsTool Control where parking is allowed.\n\nClick on the "P" icons.\n\nAvailable hotkeys:\n\n- Shift: Hold while clicking to apply parking restrictions to multiple road segments at once TMPE_TUTORIAL_HEAD_PrioritySignsTool Priority signs TMPE_TUTORIAL_BODY_PrioritySignsTool Define priority rules at junctions.\n\n1. Click on a junction.\n2. Click on the blank spots to cycle through the available priority signs (priority road, yield, stop).\n\nAvailable hotkeys:\n\n- Shift: Hold Shift to add priority signs to multiple road segments at once. Click again while holding Shift to cycle through all available patterns. TMPE_TUTORIAL_HEAD_SpeedLimitsTool Speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool Set up speed restrictions.\n\n1. In the window, click on the speed limit you want to set.\n2. Click on a road segment to change the speed limit.\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply speed limits to multiple road segments at once.\n- Ctrl: Hold Ctrl to control speed limits per individual lane. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool Timed traffic lights TMPE_TUTORIAL_BODY_TimedTrafficLightsTool Set up timed traffic lights.\n\n1. Click on a junction.\n2. In the window, click on "Add step".\n3. Click on the overlay elements to set desired traffic lights states.\n4. Click on "Add".\n5. Repeat as desired. TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Toggle traffic lights TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool Add or remove traffic lights to/from junctions.\n\nClick on a junction to toggle traffic lights. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, busses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. Public_transport Public transport Prevent_excessive_transfers_at_public_transport_stations Prevent unnecessary transfers at public transport stations Compact_main_menu Compact main menu Window_transparency Window transparency Overlay_transparency Overlay transparency Remove_this_citizen Remove this citizen Show_error_message_if_a_mod_incompatibility_is_detected Show error message if a mod incompatibility is detected Remove_parked_vehicles Remove parked vehicles Node_is_level_crossing This junction is a level crossing.\nYou cannot disable traffic lights here. Experimental_features Experimental features Turn_on_red Girare ai semafori rossi Vehicles_may_turn_on_red I veicoli possono girare ai semafori rossi Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets ================================================ FILE: TLM/TLM/Resources/lang_ja.txt ================================================ Switch_traffic_lights 信号の付け外し Add_priority_signs 優先関係標識の追加 Manual_traffic_lights 信号の手動切替 Timed_traffic_lights 時間設定付き信号 Change_lane_arrows 車線矢印の変更 Clear_Traffic 移動中車両の除去 Disable_despawning スタック除去無効化 Enable_despawning スタック除去有効化 NODE_IS_LIGHT この交差点には信号があります。\n信号を削除するには「信号の付け外し」を選んでこの交差点をクリックしてください。 NODE_IS_TIMED_LIGHT この交差点には時間設定付き信号があります。\nまず「時間設定付き信号」を選んでこの交差点をクリックして「削除」をクリックしてください。 Select_nodes_windowTitle 交差点の選択 Select_nodes 交差点を選択してください Node 交差点 Deselect_all_nodes 全交差点の選択を解除 Setup_timed_traffic_light 時間設定付き信号の設定 State 状態 Skip 次へ up 上へ down 下へ View 表示 Edit 編集 Delete 削除 Timed_traffic_lights_manager 時間設定付き信号の管理 Add_step 状態の追加 Remove_timed_traffic_light 時間設定付き信号の削除 Min._Time: 最短時間: Max._Time: 最長時間: Save 保存 Add 追加 Sensitivity 感度 Very_Low とても低い Low 低い Medium 中くらい High 高い Very_high とても高い Extreme_long_green/red_phases 混雑してきてもほとんど切り替えない Very_long_green/red_phases 混雑してきてもあまり切り替えない Long_green/red_phases 混雑具合によってそこそこに切り替える Moderate_green/red_phases 混雑具合によって適度に切り替える Short_green/red_phases 混雑具合によって細かく切り替える Very_short_green/red_phases 混雑具合によってとても細かく切り替える Extreme_short_green/red_phases 混雑具合によって非常に細かく切り替える Hide_counters カウンターの非表示 Show_counters カウンターの表示 Start 開始 Stop 停止 Enable_test_mode_(stay_in_current_step) テストモード有効 (現在の状態に留まる) avg._flow 平均流量 avg._wait 平均待ち min/max 最短/最長 Lane 車線 Set_Speed 速度{0}に設定 Max_speed 最大速度 Segment 道路 incoming 台流入 Enable_Advanced_Vehicle_AI Advanced Vehicle AIを有効にする Vehicles_may_enter_blocked_junctions 交差点内での停車を許可 All_vehicles_may_ignore_lane_arrows 全車両に対して車線矢印の無視を許可 Busses_may_ignore_lane_arrows バスに対して車線矢印の無視を許可 Reckless_driving 無謀運転 (ベータ機能/いくらかの市民が交通ルールを無視します) TMPE_Title Traffic Manager: President Edition Settings_are_defined_for_each_savegame_separately 設定は各セーブデータ毎に保存されます Simulation_accuracy シミュレーションの正確さ (高いとパフォーマンスが低下します) Enable_highway_specific_lane_merging/splitting_rules 高速道路特有の車線合流/分離ルールを適用する Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): 車線変更の頻度 (Advanced AI有効時のみ適用されます) Maintenance メンテナンス Very_often 非常に頻繁 Often 頻繁 Sometimes 時々 Rarely まれ Very_rarely 非常にまれ Only_if_necessary 必要時のみ Nodes_and_segments 交差点と道路の表示 Lanes 車線 Path_Of_Evil_(10_%) 悪路 (10 %) Rush_Hour_(5_%) ラッシュアワー (5 %) Minor_Complaints_(2_%) ほとんど不満なし (2 %) Holy_City_(0_%) 天国 (0 %) Forget_toggled_traffic_lights 付け外しした信号を元に戻す Road_is_already_in_a_group! この道路は既にグループ内にあります! All_selected_roads_must_be_of_the_same_type! 選択する道路は全て同じタイプである必要があります! Create_group グループの作成 Delete_group グループの削除 Add_zoning 区間の追加 Remove_zoning 区間の削除 Lane_Arrow_Changer_Disabled_Highway 高速道路ルールシステムを適用しているため、この車線の矢印は変更できません Add_junction_to_timed_light この信号に交差点を追加 Remove_junction_from_timed_light この信号から交差点を削除 Select_junction 交差点を選択してください Cancel キャンセル Speed_limits 速度制限 Persistently_visible_overlays オーバーレイ表示し続ける情報 Priority_signs 優先関係標識 Vehicles_may_do_u-turns_at_junctions 交差点でのUターンを許可 Vehicles_going_straight_may_change_lanes_at_junctions 直進車両の交差点での車線変更を許可 Allow_u-turns Uターンを許可 Allow_lane_changing_for_vehicles_going_straight 直進車両の車線変更を許可 Allow_vehicles_to_enter_a_blocked_junction 交差点内での停車を許可 Road_condition_has_a_bigger_impact_on_vehicle_speed 路面状況がより大きな影響を車両速度に与える Vehicle_restrictions 車両規制 Copy コピー Paste 貼り付け Invert 反転 Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions 交差点間の全道路に車両規制を適用 Allow_all_vehicles 全車両を許可 Ban_all_vehicles 全車両を禁止 Set_all_traffic_lights_to_red 全信号を赤に設定 Rotate_left 左に回転 Rotate_right 右に回転 Name 名前 Apply 適用 Select_a_timed_traffic_light_program 時間設定付き信号のプログラムを選択してください The_chosen_traffic_light_program_is_incompatible_to_this_junction 選択された信号パターンはこの交差点と互換性がありません Advanced_AI_cannot_be_activated Advanced Vehicle AIは有効にできません The_Advanced_Vehicle_AI_cannot_be_activated Advanced Vehicle AIは、車両の振る舞いを変える他のMOD(例えばImproved AIやTraffic++)を既に使っているため、有効にできません. Enable_dynamic_path_calculation Enable dynamic path calculation Lane_Arrow_Changer_Disabled_Connection The lane arrow changer for this lane is disabled because you have created lane connections by hand. Lane_connector Lane connector Connected_lanes Connected lanes Use_alternative_view_mode Use alternative view mode Road_type Road type Default_speed_limit Default speed limit Unit_system Unit system Metric Metric Imperial Imperial Use_more_CPU_cores_for_route_calculation_if_available Use more CPU cores for route calculation (if available) Activated_features Activated features Junction_restrictions Junction restrictions Prohibit_spawning_of_pocket_cars Prohibit spawning of pocket cars Reset_stuck_cims_and_vehicles Reset stuck cims and vehicles Default_speed_limits Default speed limits Looking_for_a_parking_spot Looking for a parking spot Driving_to_a_parking_spot Driving to a parking spot Driving_to_another_parking_spot Driving to another parking spot Entering_vehicle Entering vehicle Walking_to_car Walking to car Using_public_transport Using public transport Walking Walking Thinking_of_a_good_parking_spot Thinking of a good parking spot Switch_view Switch view Outgoing_demand Outgoing demand Incoming_demand Incoming demand Advanced_Vehicle_AI Advanced Vehicle AI Heavy_trucks_prefer_outer_lanes_on_highways Heavy vehicles prefer outer lanes on highways Parking_AI Parking AI Enable_more_realistic_parking Enable more realistic parking Reset_custom_speed_limits Reset custom speed limits Reload_global_configuration Reload global configuration Reset_global_configuration Reset global configuration General General Gameplay Gameplay Overlays Overlays Realistic_speeds Realistic speeds Evacuation_busses_may_ignore_traffic_rules Evacuation busses may ignore traffic rules Evacuation_busses_may_only_be_used_to_reach_a_shelter Evacuation busses may only be used to reach a shelter Vehicle_behavior Vehicle behavior Policies_&_Restrictions Policies & Restrictions At_junctions At junctions In_case_of_emergency In case of emergency Show_lane-wise_speed_limits Show lane-wise speed limits Language Language Game_language Game language requires_game_restart requires game restart Customizations_come_into_effect_instantaneously Customizations come into effect instantaneously Options Options Lock_main_menu_button_position Lock main menu button position Lock_main_menu_position Lock main menu position Recalculating_lane_routing Recalculating lane routing Please_wait Please wait Parking_restrictions Parking restrictions Simulation Simulation On_roads On roads Ban_private_cars_and_trucks_on_bus_lanes Ban private cars and trucks on bus lanes default default flow_ratio flow ratio wait_ratio wait ratio After_min._time_has_elapsed_switch_to_next_step_if After min. time has elapsed, switch to next step if Adaptive_step_switching Adaptive step switching Dynamic_lane_section Dynamic lane selection Percentage_of_vehicles_performing_dynamic_lane_section Percentage of vehicles performing dynamic lane selection Vehicle_restrictions_aggression Vehicle restrictions aggression Strict Strict Show_path-find_stats Show path-find stats Remove_this_vehicle Remove this vehicle Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights Vehicles follow priority rules at junctions with timed traffic lights Enable_tutorial_messages Enable tutorial messages TMPE_TUTORIAL_HEAD_MainMenu Traffic Manager: President Edition TMPE_TUTORIAL_BODY_MainMenu Welcome to TM:PE!\n\nUser manual: http://www.viathinksoft.de/tmpe TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool Junction restrictions TMPE_TUTORIAL_BODY_JunctionRestrictionsTool Control how vehicles and pedestrians shall behave at junctions.\n\n1. Click on the junction you want to manage\n2. Click on the appropriate icon to toggle restrictions.\n\nAvailable restrictions:\n- Allow/Disallow lane changing for vehicle going straight on\n- Allow/Disallow u-turns\n- Allow/Disallow vehicles to enter a blocked junction\n- Allow/disallow pedestrians to cross the street TMPE_TUTORIAL_HEAD_LaneArrowTool Lane arrows TMPE_TUTORIAL_BODY_LaneArrowTool Restrict the set of directions that vehicles are allowed to take.\n\n1. Click on a road segment next to a junction\n2. Select the allowed directions. TMPE_TUTORIAL_HEAD_LaneConnectorTool Lane connector TMPE_TUTORIAL_BODY_LaneConnectorTool Connect two or more lanes with each other in order to tell which lanes vehicles may use.\n\n1. Click on a lane changing point (grey circles).\n2. Click on a source marker.\n3. Click on a target marker to connect it with the source marker.\n4. Click anywhere with your secondary mouse button to return back to source marker selection.\n\nAvailable hotkeys:\n\n- Delete or Backspace: Remove all lane connections\n- Shift + S: Cycle through all available "stay on lane" patterns TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool Manual traffic lights TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Try out custom traffic lights.\n\n1. Click on a junction\n2. Use the tool to control traffic lights. TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool Parking restrictions TMPE_TUTORIAL_BODY_ParkingRestrictionsTool Control where parking is allowed.\n\nClick on the "P" icons.\n\nAvailable hotkeys:\n\n- Shift: Hold while clicking to apply parking restrictions to multiple road segments at once TMPE_TUTORIAL_HEAD_PrioritySignsTool Priority signs TMPE_TUTORIAL_BODY_PrioritySignsTool Define priority rules at junctions.\n\n1. Click on a junction.\n2. Click on the blank spots to cycle through the available priority signs (priority road, yield, stop).\n\nAvailable hotkeys:\n\n- Shift: Hold Shift to add priority signs to multiple road segments at once. Click again while holding Shift to cycle through all available patterns. TMPE_TUTORIAL_HEAD_SpeedLimitsTool Speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool Set up speed restrictions.\n\n1. In the window, click on the speed limit you want to set.\n2. Click on a road segment to change the speed limit.\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply speed limits to multiple road segments at once.\n- Ctrl: Hold Ctrl to control speed limits per individual lane. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool Timed traffic lights TMPE_TUTORIAL_BODY_TimedTrafficLightsTool Set up timed traffic lights.\n\n1. Click on a junction.\n2. In the window, click on "Add step".\n3. Click on the overlay elements to set desired traffic lights states.\n4. Click on "Add".\n5. Repeat as desired. TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Toggle traffic lights TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool Add or remove traffic lights to/from junctions.\n\nClick on a junction to toggle traffic lights. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, busses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. Public_transport Public transport Prevent_excessive_transfers_at_public_transport_stations Prevent unnecessary transfers at public transport stations Compact_main_menu Compact main menu Window_transparency Window transparency Overlay_transparency Overlay transparency Remove_this_citizen Remove this citizen Show_error_message_if_a_mod_incompatibility_is_detected Show error message if a mod incompatibility is detected Remove_parked_vehicles Remove parked vehicles Node_is_level_crossing This junction is a level crossing.\nYou cannot disable traffic lights here. Experimental_features Experimental features Turn_on_red Turn on red Vehicles_may_turn_on_red 車両が赤信号で曲がることがある Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets ================================================ FILE: TLM/TLM/Resources/lang_ko.txt ================================================ Switch_traffic_lights 신호등 추가/제거 Add_priority_signs 교통 표지판 추가 Manual_traffic_lights 신호등 직접 통제 Timed_traffic_lights 신호등 시간표 설정 Change_lane_arrows 도로 화살표 변경 Clear_Traffic 모든 교통차량 초기화 Disable_despawning 차 사라짐 비활성화 Enable_despawning 차 사라짐 활성화 NODE_IS_LIGHT 교차로에 신호등을 생성합니다.\n'신호등 추가/제거'를 통해 이 도로 교차로에 신호등을 추가하거나 제거 할 수 있습니다. NODE_IS_TIMED_LIGHT 이미 신호등 시간표가 설정되어 있습니다.\n'신호등 시간표 설정'에서 '제거'를 통해 삭제해야지만 가능합니다. Select_nodes_windowTitle 선택된 교차로 목록 Select_nodes 교차로를 선택하세요 Node 도로 Deselect_all_nodes 모든 교차로 선택 취소 Setup_timed_traffic_light 신호등 시간 설정 State 상태 Skip 스킵 up 위로 down 아래로 View 보기 Edit 수정 Delete 삭제 Timed_traffic_lights_manager 신호등 시간표 관리자 Add_step 단계 추가 Remove_timed_traffic_light 설정된 신호등 시간표 제거 Min._Time: 최소 시간 : Max._Time: 최대 시간 : Save 저장 Add 추가 Sensitivity 신호등 주기 범위 Very_Low 매우 낮음 Low 낮음 Medium 중간 High 높음 Very_high 매우 높음 Extreme_long_green/red_phases 극도로 긴 신호등 주기 변화 Very_long_green/red_phases 매우 긴 신호등 주기 변화 Long_green/red_phases 긴 신호등 주기 변화 Moderate_green/red_phases 평범한 신호등주기 변화 Short_green/red_phases 짧은 신호등주기 변화 Very_short_green/red_phases 매우 짧은 신호등주기 변화 Extreme_short_green/red_phases 극도로 짧은 신호등주기 변화 Hide_counters 카운터 숨기기 Show_counters 카운터 보이기 Start 시작 Stop 중지 Enable_test_mode_(stay_in_current_step) 테스트 모드 활성화 (현 상태를 유지한 채 진행) avg._flow 평균 흐름 avg._wait 평균 대기 min/max 최소/최대 Lane 차선 Set_Speed 설정된 속도 {0} Max_speed 최고 속도 Segment 세그먼트(구간) incoming 들어오는 Enable_Advanced_Vehicle_AI 발전된 차량 AI 활성화 Vehicles_may_enter_blocked_junctions 차량이 진입 금지도로에 들어갈 수 있습니다. All_vehicles_may_ignore_lane_arrows 모든 차량이 도로 화살표를 무시 할 수 있습니다. Busses_may_ignore_lane_arrows 버스가 도로 화살표를 무시 할 수 있습니다. Reckless_driving 난폭 운전 (베타 기능) TMPE_Title 트래픽 매니저 : 대통령 에디션 Settings_are_defined_for_each_savegame_separately 설정은 각 저장된 게임마다 별개로 작동합니다. Simulation_accuracy 시뮬레이션 정확도 (높을수록 더 많은 연산을 요구합니다) Enable_highway_specific_lane_merging/splitting_rules 고속도로 특정도로 병합/분할 기능 활성화 Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): 운전자가 차선을 변경합니다 (발전된 차량 AI를 사용 중일 때만 적용됩니다) Maintenance 유지보수 (추가정보) Very_often 매우 자주 Often 자주 Sometimes 가끔 Rarely 드물게 Very_rarely 매우 드물게 Only_if_necessary 필요한 경우에만 Nodes_and_segments 도로와 세그먼트(노드) Lanes 차선 Path_Of_Evil_(10_%) 악마의 도로 (10 %) Rush_Hour_(5_%) 출퇴근정도의 혼잡 (5 %) Minor_Complaints_(2_%) 사소한 불평들 (2 %) Holy_City_(0_%) 평온한 도시 (0 %) Forget_toggled_traffic_lights 선택된 신호등 잊기 Road_is_already_in_a_group! 도로에 이미 완성된 그룹이 있습니다! All_selected_roads_must_be_of_the_same_type! 같은 유형의 도로가 선택되어 있습니다! Create_group 그룹 생성 Delete_group 그룹 제거 Add_zoning 지역설정 추가 Remove_zoning 지역설정 제거 Lane_Arrow_Changer_Disabled_Highway 고속도로 시스템 규칙으로 인해 이 도로 화살표는 변경 할 수 없습니다(발전된 차량 AI기능 참고) Add_junction_to_timed_light 교차로 신호등 추가 Remove_junction_from_timed_light 교차로 신호등 제거 Select_junction 교차로 선택 Cancel 취소 Speed_limits 속도 제한 Persistently_visible_overlays 화면에 출력할 코드 선택 Priority_signs 교통 표지판 Vehicles_may_do_u-turns_at_junctions 교차로에서 차량이 유턴 할 수 있습니다. Vehicles_going_straight_may_change_lanes_at_junctions 교차로에서 차량이 차선을 변경 할 수 있습니다. Allow_u-turns 유턴 허가 Allow_lane_changing_for_vehicles_going_straight 직진하는 차량의 차선 변경을 허가 Allow_vehicles_to_enter_a_blocked_junction 차량 출입금지 도로에 차량 출입 허가 Road_condition_has_a_bigger_impact_on_vehicle_speed 도로상태가 차량속도에 크게 영향을 미칩니다 Vehicle_restrictions 차량 제한 Copy 복사 Paste 붙이기 Invert 반전 Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions 두 교차로 구간에 동일한 차량 제한 적용 Allow_all_vehicles 모든 차량 허가 Ban_all_vehicles 모든 차량 금지 Set_all_traffic_lights_to_red 모든 신호등을 빨간불로 설정 Rotate_left 반시계방향 신호회전 Rotate_right 시계방향 신호회전 Name 이름 Apply 적용 Select_a_timed_traffic_light_program 선택된 신호등 프로그램 The_chosen_traffic_light_program_is_incompatible_to_this_junction 선택한 교통 신호규칙이 이 교차로와 맞지 않습니다 Advanced_AI_cannot_be_activated 발전된 AI 활성화 불가 The_Advanced_Vehicle_AI_cannot_be_activated 이미 다른 모드로 차량 AI 향상을 사용 중이므로 발전된 차량 AI를 사용 할 수 없습니다.(예 : 향상된 AI, 트래픽 C++) Enable_dynamic_path_calculation 동적인 경로 계산 활성화 Lane_Arrow_Changer_Disabled_Connection 차선 연결로 이미 수정된 교차로이므로 차선변경을 할 수 없습니다. Lane_connector 차선 연결 Connected_lanes 연결된 차선 Use_alternative_view_mode 대체보기 모드 사용 Road_type 도로 유형 Default_speed_limit 기본 속도 제한 Unit_system 유닛 시스템 Metric 미터법 Imperial 마일법 Use_more_CPU_cores_for_route_calculation_if_available 경로 계산을 위한 더 많은 CPU코어 사용(가능한 경우) Activated_features 기능 활성화 Junction_restrictions 교차로 규칙 Prohibit_spawning_of_pocket_cars 시민들의 포켓차량 소환 금지 Reset_stuck_cims_and_vehicles 갇혀있는 시민과 차량 초기화 Default_speed_limits 기본 속도 제한 Looking_for_a_parking_spot 주차공간 찾는 중 Driving_to_a_parking_spot 해당 주차공간으로 운전 중 Driving_to_another_parking_spot 다른 주차공간으로 운전 중 Entering_vehicle 차량 탑승 중 Walking_to_car 차 있던 곳으로 가는 중 Using_public_transport 대중교통 이용 중 Walking 걷는 중 Thinking_of_a_good_parking_spot 주차하기에 좋은 장소를 생각 중 Switch_view 보기 전환 Outgoing_demand 나가는 수요 Incoming_demand 들어오는 수요 Advanced_Vehicle_AI 발전된 차량 AI Heavy_trucks_prefer_outer_lanes_on_highways 무거운(화물)차량은 고속도로 바깥 차선을 선호 Parking_AI 주차 AI Enable_more_realistic_parking 좀 더 현실적인 주차 활성화 Reset_custom_speed_limits 커스텀 속도 초기화 Reload_global_configuration 글로벌(global) 설정 불러오기 Reset_global_configuration 글로벌(global) 설정 초기화 General 기본 Gameplay 게임플레이 Overlays 화면표시 Realistic_speeds 현실적인 속도 Evacuation_busses_may_ignore_traffic_rules 대피 버스는 교통규칙을 무시합니다 Evacuation_busses_may_only_be_used_to_reach_a_shelter 대피 버스는 대피소 도착을 위한 용도로만 사용할 수 있습니다 Vehicle_behavior 차량 제한 Policies_&_Restrictions 정책 및 제한 At_junctions 교차로 설정 In_case_of_emergency 긴급 상황 설정 Show_lane-wise_speed_limits 차선 속도제한 보기 Language 언어 Game_language 게임 언어 requires_game_restart 게임 재시작 후 적용됩니다 Customizations_come_into_effect_instantaneously 커스텀 기능이 즉시 적용됩니다 Options 설정 Lock_main_menu_button_position TMPE 모드버튼 위치 잠금 Lock_main_menu_position TMPE 모드매뉴 위치 잠금 Recalculating_lane_routing 모든 경로 재 계산중 Please_wait 잠시만 기다려주십시오 Parking_restrictions 주차 제한 Simulation 시뮬레이션 On_roads 도로 설정 Ban_private_cars_and_trucks_on_bus_lanes 버스전용 차선에 승용차 및 트럭 출입을 금지합니다 default 기본값 flow_ratio 차량 이동량 wait_ratio 차량 대기량 After_min._time_has_elapsed_switch_to_next_step_if 최소시간 경과후 다음단계로 넘어갑니다 : Adaptive_step_switching 적응형 신호 변경 Dynamic_lane_section 유동적 차량 차선변경 빈도 Percentage_of_vehicles_performing_dynamic_lane_section 유동적 차량 차선변경 빈도 백분율 Vehicle_restrictions_aggression 진입불가 도로 회피 빈도 Strict 엄격하게 제어함 Show_path-find_stats 현재 경로를 찾는 차량수를 PFs 수치로 표기합니다 Remove_this_vehicle 해당 차량을 제거합니다 Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights 차량이 신호등 시간표가 구성된 교차로에서 교통 표지판 규칙을 준수합니다 Enable_tutorial_messages 튜토리얼 메세지 활성화 TMPE_TUTORIAL_HEAD_MainMenu Traffic Manager: President Edition TMPE_TUTORIAL_BODY_MainMenu TM:PE에 오신것을 환영합니다!\n\n영문 가이드: http://www.viathinksoft.de/tmpe TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool 교차로 규칙 도움말 TMPE_TUTORIAL_BODY_JunctionRestrictionsTool 차량과 보행자가 교차로에서 어떠한 규칙을 따라야하는지 통제할 수 있습니다\n\n1. 규칙을 설정하고 싶은 교차로를 클릭합니다\n2. 통제하고 싶은 구역 아이콘을 클릭합니다\n\n적용할 수 있는 규칙 :\n- 직진하면서 차선 변경 허용/비허용\n- 유턴 허용/비허용\n- 꼬리물기 허용/비허용\n- 보행자의 횡단보도 허용/비허용 TMPE_TUTORIAL_HEAD_LaneArrowTool 도로 화살표 변경 도움말 TMPE_TUTORIAL_BODY_LaneArrowTool 차량이 회전할 수 있는 방향을 통제할 수 있습니다.\n\n1. 변경하고 싶은 교차로 도로구간을 클릭합니다\n2. 화살표를 클릭하여 방향을 지정합니다. TMPE_TUTORIAL_HEAD_LaneConnectorTool 차선 연결 도움말 TMPE_TUTORIAL_BODY_LaneConnectorTool 두개 이상의 차선을 연결하여 차량이 해당 차선으로 이동하도록 명령할 수 있습니다.\n\n1. 차선을 변경할 포인트를 클릭합니다(회색 원)\n2. 시작할 차선을 클릭합니다\n3. 연결하고 싶은 차선 원을 클릭하여 연결합니다\n4. 빈공간을 우클릭하면 다시 전 단계로 돌아갈 수 있습니다\n\n사용할 수 있는 단축키 :\n\n- Delete , Backspace : 모든 연결된 차선들을 삭제합니다\n- Shift + S : 직진차선 교차점에서 원을 돌며 패턴을 적용 할 수 있습니다 TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool 신호등 직접 통제 도움말 TMPE_TUTORIAL_BODY_ManualTrafficLightsTool 신호등을 직접 통제해 볼 수 있습니다\n\n1. 교차로를 클릭합니다\n2. 신호등 통제툴을 통해 신호를 통제할 수 있습니다 TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool 주차 제한 도움말 TMPE_TUTORIAL_BODY_ParkingRestrictionsTool 어디에 주차를 허용할 것인지 통제 할 수 있습니다.\n\n"P"아이콘을 클릭합니다.\n\n사용가능한 단축키 :\n\n- Shift : 쉬프트키를 누른상태로 클릭하면 교차로내의 모든 도로구간에 해당 규칙을 적용합니다 TMPE_TUTORIAL_HEAD_PrioritySignsTool 교통 표지판 추가 도움말 TMPE_TUTORIAL_BODY_PrioritySignsTool 교차로에서 교통 표지판 규칙을 준수하게 만들 수 있습니다.\n\n1. 교차로를 클릭합니다.\n2. 검정색 원을 클릭하여 사용 가능한 교통표지판을 규정합니다(우선도로, 양보, 정지).\n\n사용할 수 있는 단축키 :\n\n- Shift : 쉬프트키를 누른 상태로 해당 도로구간에서 추가하고 싶은 표지판을 클릭합니다. 다시 클릭하면 원을 돌면서 사용 가능한 패턴으로 교통 표지판 규칙이 설정됩니다 TMPE_TUTORIAL_HEAD_SpeedLimitsTool 속도 제한 도움말 TMPE_TUTORIAL_BODY_SpeedLimitsTool 속도를 준수하도록 제한 할 수 있습니다.\n\n1. 속도 제한 패널창에서, 제한하고 싶은 속도를 클릭합니다.\n 해당 도로구간을 클릭하여 속도 제한을 변경합니다\n\n사용할 수 있는 단축키 :\n\n- Shift : 쉬프트키를 누른 상태로 도로구간을 클릭하면 한번에 적용 됩니다.\n- Ctrl : 컨트롤키를 누른 상태로 각 차선마다 속도를 제한 할 수 있습니다 TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool 신호등 시간표 설정 도움말 TMPE_TUTORIAL_BODY_TimedTrafficLightsTool 신호등 시간표를 구성 할 수 있습니다.\n\n1. 교차로를 클릭합니다.\n2. 선택된 교차로 목록 패널에서 "신호등 시간 설정"을 클릭합니다.\n3. 신호등 시간표 관리자 패널에서 현재 구성된 단계들을 볼 수 있습니다.\n4. "단계 추가"를 클릭합니다.\n5. 원하는 만큼 계속 반복합니다. TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool 신호등 추가/제거 도움말 TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool 교차로에 있는 신호등을 추가/제거 할 수 있습니다.\n\n교차로를 클릭하여 신호등을 추가/제거 하십시오. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool 차량 제한 도움말 TMPE_TUTORIAL_BODY_VehicleRestrictionsTool 특정 도로구간에 차량접근을 통제 할 수 있습니다.\n\n1. 도로구간을 클릭하십시오.\n2. 아이콘을 클릭하여 접근을 통제하십시오.\n\n 유형별 차량 종류 :\n\n- 도로 차량 : 승용차, 버스, 택시, 화물차, 서비스차량, 긴급차량\n- 철도 차량 : 여객기차, 화물기차\n\n사용할 수 있는 단축키 :\n\n- Shift : 쉬프트키를 누른 상태로 도로구간을 클릭하면 한번에 해당차량 접근을 통제할 수 있습니다. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults 기본 속도 제한 도움말 TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. 화살표를 통해 도로 유형을 정합니다.\n2. 아래에 위치한 화살표를 통해 제한할 속도를 정합니다.\n3. "저장"을 클릭하면 앞으로 설치되는 해당 도로유형이 설정된 속도 제한값으로 설정됩니다.\n4. "저장 & 적용"을 클릭하면 모든 이전에 설치되어 있던 해당 도로도 해당 속도제한 규칙이 적용됩니다. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep 시간 구성하는 방법 TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. 신호등 변경 툴에서, 신호등을 클릭하여 신호를 변경할 수 있습니다. "Change mode"를 클릭하면 신호등 방향을 구체적으로 설정 할 수 있습니다\n2. 최소시간과 최대시간은 분으로 구성되어 있습니다. 시간이 경과되면 접근하는 차량 수를 신호등이 비교를 합니다.\n3. 설정을 통해 다음단계로 어떻게 넘어갈 것인지 구성하십시오. 기본적으로는 운전하는 차량 수보다 대기 중인 차량수가 많으면 다음 단계로 변경됩니다.\n4. 신호등 주기 설정을 조절할 수 있습니다. 예를 들어, 슬라이드를 왼쪽으로 움직이게 하면 대기중인 차가 신호가 변경된 후 덜 혼잡하게 지나갈 수 있습니다. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy 신호등 시간표 복제 방법 TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy 클릭을 통해 다른 교차로에 같은 신호등 시간표를 적용할 수 있습니다.\n\n원하는 곳을 클릭하여 복제한 후 마우스 우클릭을 통해 복제를 종료할 수 있습니다. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction 교차로에 신호등 시간표 추가하는 방법 TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction 다른 교차로를 클릭하여 추가하십시오. 선택된 교차로의 신호등들은 구성된 시간표와 신호에 맞게 같이 작동되게 됩니다.\n\n교차로에서 신호등 시간표를 추가하는 것을 중단하려면 마우스 우클릭을 클릭하십시오. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction 교차로에 신호등 시간표 제거하는 방법 TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction 신호등 시간표가 구성되어 있는 교차로를 클릭하십시오. 클릭된 신호등은 더 이상 해당 시간표 통제를 받지않게 됩니다.\n\n교차로에서 신호등 시간표를 제거하는 것을 중단하려면 마우스 우클릭을 클릭하십시오. Public_transport 대중 교통 Prevent_excessive_transfers_at_public_transport_stations 시민들의 다른 대중교통 노선 이용 시 패턴 최적화 Compact_main_menu TMPE 메뉴 아이콘 크게/작게 설정 Window_transparency 패널 투명도(오른쪽으로 갈수록 투명함) Overlay_transparency 보조 아이콘 투명도(도로 위 표지판, 속도제한 아이콘 투명도 등) Remove_this_citizen 해당 시민 삭제 Show_error_message_if_a_mod_incompatibility_is_detected 모드와 비 호환되는 모드 발견 시 에러 보여주기 Remove_parked_vehicles 주차된 차량 제거하기 Node_is_level_crossing 해당 교차로는 평면 교차로입니다.\n따라서 신호등을 설치할 수 없습니다 Experimental_features Experimental features Turn_on_red Turn on red Vehicles_may_turn_on_red 빨간 신호등에서 차량이 돌아올 수 있습니다. Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets ================================================ FILE: TLM/TLM/Resources/lang_nl.txt ================================================ Switch_traffic_lights Stoplichten omwisselen Add_priority_signs Plaats voorrangsborden Manual_traffic_lights Handmatige stoplichten Timed_traffic_lights Tijdgestuurde stoplichten Change_lane_arrows Voorsorteerpijlen aanpassen Clear_Traffic Wegen vrijmaken Disable_despawning Voertuigdverdwijning uitschakelen Enable_despawning Voertuigverdwijning inschakelen NODE_IS_LIGHT De kruising heeft een stoplicht.\nJe kunt het stoplicht verwijderen door eerst op "stoplichten omwisselen" te klikken en daarna op de kruising. NODE_IS_TIMED_LIGHT De kruising heeft tijdgestuurde stoplichten.\nJe kunt de tijdsturing verwijderen door op "Tijdgestuurde stoplichten" te klikken, daarn op de kruising en daarna op "Verwijderen". Select_nodes_windowTitle Kruisingselectie Select_nodes Selecteert één of meerdere kruisingen Node Kruising Deselect_all_nodes Deselecteer alle kruisingen Setup_timed_traffic_light Tijdgestuurde stoplichten inrichten State Stap Skip Verder up omhoog down omlaag View Weergeven Edit Wijzigen Delete Verwijderen Timed_traffic_lights_manager Beheer van tijdgestuurde stoplichten Add_step Stap toevoegen Remove_timed_traffic_light Stoplichten verwijderen Min._Time: Min. tijd: Max._Time: Max. tijd: Save Opslaan Add Toevoegen Sensitivity Gevoeligheid Very_Low Zeer laag Low Laag Medium Middelhoog High Hoog Very_high Zeer hoog Extreme_long_green/red_phases Extreem lange groen-/roodfasen Very_long_green/red_phases Zeer lange groen-/roodfasen Long_green/red_phases Lange groen-/roodfasen Moderate_green/red_phases Normale groen-/roodfasen Short_green/red_phases korte groen-/roodfasen Very_short_green/red_phases Zeer korte groen-/roodfasen Extreme_short_green/red_phases Extreem korten groen-/roodfasen Hide_counters Tellers verbergen Show_counters Tellers tonen Start Start Stop Stop Enable_test_mode_(stay_in_current_step) Testmodus activeren (huidige stap vasthouden) avg._flow rijdend avg._wait wachtend min/max min/max Lane Rijstrook Set_Speed Snelheid instellen {0} Max_speed Max. snelheid Segment Segment incoming inkomend Enable_Advanced_Vehicle_AI Verbeterde voertuig-KI activeren Vehicles_may_enter_blocked_junctions Voertuigen moge geblokkeerde kruisingen oprijden All_vehicles_may_ignore_lane_arrows Alle voertuigen mogen voorsorteerpijlen negeren Busses_may_ignore_lane_arrows Bussen mogen voorsorteerpijlen negeren Reckless_driving Roekeloos rijden TMPE_Title Traffic Manager: Presidentiële editie Settings_are_defined_for_each_savegame_separately Instellingen worden per spelstand opgeslagen Simulation_accuracy Genauigkeit der Simulation (hogere precisie verlaagt prestaties) Enable_highway_specific_lane_merging/splitting_rules Activeer speciale verkeersregels op snelwegen Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Autobestuurders wisselen van rijstrook (heeft alleen effect als de KI ingeschakeld is) Maintenance Onderhoud Very_often Zeer vaak Often Vaak Sometimes Regelmatig Rarely Zelden Very_rarely Zeer zelden Only_if_necessary Alleen indien nodig Nodes_and_segments Kruisingen en segmenten Lanes Rijkstroken Path_Of_Evil_(10_%) Route des kwaads (10 %) Rush_Hour_(5_%) Spitsuur (5 %) Minor_Complaints_(2_%) Enige klachten (2 %) Holy_City_(0_%) Heilige stad (0 %) Forget_toggled_traffic_lights Stoplichtaanpassingen vergeten Road_is_already_in_a_group! Weg behoort reeds tot een groep! All_selected_roads_must_be_of_the_same_type! Alle geselecteerde wegen moeten van hetzelfde soort zijn! Create_group Gruppe aanmaken Delete_group Gruppe verwijderen Add_zoning Zones activeren Remove_zoning Zones verwijderen Lane_Arrow_Changer_Disabled_Highway Het is niet mogelijk de voorrangspijlen op deze rijstroom te veranderen, omdat je de speciale regels voor snelwegen geactiveerd hebt. Add_junction_to_timed_light Kruising aan stoplicht toevoegen Remove_junction_from_timed_light Kruising van stoplicht verwijderen Select_junction Selecteer een kruising Cancel Afbreken Speed_limits Snelheidslimiet Persistently_visible_overlays Permanent zichtbare projecties Priority_signs Voorrangsborden Vehicles_may_do_u-turns_at_junctions Voertuigen kunnen u-bochten op kruisingen maken Vehicles_going_straight_may_change_lanes_at_junctions Voertuigen mogen van rijstrook wisselen als ze rechtdoor op kruisingen rijden Allow_u-turns U-bochten toestaan Allow_lane_changing_for_vehicles_going_straight Wisselen van rijstook toestaan voor voertuigen die rechtdoor rijden Allow_vehicles_to_enter_a_blocked_junction Sta voertuigen toe om een geblokkeerde kruising op te rijden Road_condition_has_a_bigger_impact_on_vehicle_speed De wegtoestand heeft een grotere invloed op de voertuigsnelheid Vehicle_restrictions Voertuigbeperkingen Copy Kopiëren Paste Invoegen Invert Inverteren Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions Pas voertuigbeperkingen toe op alle wegsegmenten tussen twee kruisingen Allow_all_vehicles Sta alle voertuigen toe Ban_all_vehicles Verbied alle voertuigen Set_all_traffic_lights_to_red Alle stoplichten op rood zetten Rotate_left Linksom draaien Rotate_right Rechtsom draaien Name Naam Apply Toepassen Select_a_timed_traffic_light_program Selecteer een programma voor tijdgestuurde stoplichten The_chosen_traffic_light_program_is_incompatible_to_this_junction Het geselecteerde stoplichtenprogramma is niet compatibel met deze kruising Advanced_AI_cannot_be_activated Verbeterde voertuig-KI kan niet worden geactiveerd The_Advanced_Vehicle_AI_cannot_be_activated De verbeterde voertuig-KI kan niet geactiveerd worden, omdat je reeds aan andere mod gebruikt, die het gedrag van voertuigen verandert (bijv. Improved AI of Traffic++). Enable_dynamic_path_calculation Activeer dynamische routeberekening Changes_made_with_this_tool_will_take_full_effect_after_some_minutes Changes made with this tool will take full effect after some minutes Lane_Arrow_Changer_Disabled_Connection Voor deze rijstrook kan de rijstrookpijl niet aangepast worden, omdat je handmatig rijstrookverbindingen hebt gemaakt. Lane_connector Rijstrookverbinder Connected_lanes Verbonden rijstroken Use_alternative_view_mode Gebruik alternatief aanzicht Road_type Wegsoort Default_speed_limit Standaard snelheidslimiet Unit_system Eenhedenstelsel Metric Metrisch Imperial Emperisch Use_more_CPU_cores_for_route_calculation_if_available Gebruik meer processorkernen voor routeberekening indien beschikbaar Activated_features Geactiveerde functies Junction_restrictions Splitsingsbeperkingen Prohibit_spawning_of_pocket_cars Verbied opduiken van broekzakautomobielen Reset_stuck_cims_and_vehicles Reset vastgelopen inwoners en voertuigen Default_speed_limits Standaard snelheidslimieten Looking_for_a_parking_spot Op zoek naar een parkeerplaats Driving_to_a_parking_spot Rijdt naar een parkeerplaats Driving_to_another_parking_spot Rijdt naar een andere parkeerplaats Entering_vehicle Stapt in voertuig Walking_to_car Loopt naar auto Using_public_transport Gebruikt openbaar vervoer Walking Wandelt Thinking_of_a_good_parking_spot Na aan het denken over een goede parkeerplaats Switch_view Aanzicht wisselen Outgoing_demand Uitgaande vraag Incoming_demand Inkomende vraag Advanced_Vehicle_AI Geavanceerde voertuig-KI Heavy_trucks_prefer_outer_lanes_on_highways Zware vrachtverkeer verkiest buitenste rijstroken op snelwegen Parking_AI Parkeer-KI Enable_more_realistic_parking Schakel realistischer parkeren in Reset_custom_speed_limits Reset aangepaste snelheidslimieten Reload_global_configuration Herlaad algemene configuratie Reset_global_configuration Reset algemene configuratie General Algemeen Gameplay Spelverloop Overlays Transparanten Realistic_speeds Realistische snelheden Evacuation_busses_may_ignore_traffic_rules Evacuatiebussen mogen verkeersregels overtreden Evacuation_busses_may_only_be_used_to_reach_a_shelter Evacuatiebussen mogen enkel gebruikt worden om schuilkelders te bereiken Vehicle_behavior Vortuiggedrag Policies_&_Restrictions Beleidsregels & beperkingen At_junctions Bij splitsingen In_case_of_emergency In geval van nood Show_lane-wise_speed_limits Toon rijstrookspecifieke snelheidslimieten Language Taal Game_language Taal van spel requires_game_restart Vereist spelherstart Customizations_come_into_effect_instantaneously Aanpassingen zijn onmiddelijk van toepassing Options Instellingen Lock_main_menu_button_position Vergendel positie van hoofdmenuknop Lock_main_menu_position Vergrendel positie van hoofdmenu Recalculating_lane_routing Rijstrookroutering wordt herberekend Please_wait Even geduld graag Parking_restrictions Parkeerbeperkingen On_roads On roads Ban_private_cars_and_trucks_on_bus_lanes Ban private cars and trucks on bus lanes default default flow_ratio flow ratio wait_ratio wait ratio After_min._time_has_elapsed_switch_to_next_step_if After min. time has elapsed, switch to next step if Adaptive_step_switching Adaptive step switching Dynamic_lane_section Dynamic lane selection Percentage_of_vehicles_performing_dynamic_lane_section Percentage of vehicles performing dynamic lane selection Vehicle_restrictions_aggression Vehicle restrictions aggression Strict Strict Show_path-find_stats Show path-find stats Remove_this_vehicle Remove this vehicle Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights Vehicles follow priority rules at junctions with timed traffic lights Enable_tutorial_messages Enable tutorial messages TMPE_TUTORIAL_HEAD_MainMenu Traffic Manager: President Edition TMPE_TUTORIAL_BODY_MainMenu Welcome to TM:PE!\n\nUser manual: http://www.viathinksoft.de/tmpe TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool Junction restrictions TMPE_TUTORIAL_BODY_JunctionRestrictionsTool Control how vehicles and pedestrians shall behave at junctions.\n\n1. Click on the junction you want to manage\n2. Click on the appropriate icon to toggle restrictions.\n\nAvailable restrictions:\n- Allow/Disallow lane changing for vehicle going straight on\n- Allow/Disallow u-turns\n- Allow/Disallow vehicles to enter a blocked junction\n- Allow/disallow pedestrians to cross the street TMPE_TUTORIAL_HEAD_LaneArrowTool Lane arrows TMPE_TUTORIAL_BODY_LaneArrowTool Restrict the set of directions that vehicles are allowed to take.\n\n1. Click on a road segment next to a junction\n2. Select the allowed directions. TMPE_TUTORIAL_HEAD_LaneConnectorTool Lane connector TMPE_TUTORIAL_BODY_LaneConnectorTool Connect two or more lanes with each other in order to tell which lanes vehicles may use.\n\n1. Click on a lane changing point (grey circles).\n2. Click on a source marker.\n3. Click on a target marker to connect it with the source marker.\n4. Click anywhere with your secondary mouse button to return back to source marker selection.\n\nAvailable hotkeys:\n\n- Delete or Backspace: Remove all lane connections\n- Shift + S: Cycle through all available "stay on lane" patterns TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool Manual traffic lights TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Try out custom traffic lights.\n\n1. Click on a junction\n2. Use the tool to control traffic lights. TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool Parking restrictions TMPE_TUTORIAL_BODY_ParkingRestrictionsTool Control where parking is allowed.\n\nClick on the "P" icons.\n\nAvailable hotkeys:\n\n- Shift: Hold while clicking to apply parking restrictions to multiple road segments at once TMPE_TUTORIAL_HEAD_PrioritySignsTool Priority signs TMPE_TUTORIAL_BODY_PrioritySignsTool Define priority rules at junctions.\n\n1. Click on a junction.\n2. Click on the blank spots to cycle through the available priority signs (priority road, yield, stop).\n\nAvailable hotkeys:\n\n- Shift: Hold Shift to add priority signs to multiple road segments at once. Click again while holding Shift to cycle through all available patterns. TMPE_TUTORIAL_HEAD_SpeedLimitsTool Speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool Set up speed restrictions.\n\n1. In the window, click on the speed limit you want to set.\n2. Click on a road segment to change the speed limit.\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply speed limits to multiple road segments at once.\n- Ctrl: Hold Ctrl to control speed limits per individual lane. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool Timed traffic lights TMPE_TUTORIAL_BODY_TimedTrafficLightsTool Set up timed traffic lights.\n\n1. Click on a junction.\n2. In the window, click on "Add step".\n3. Click on the overlay elements to set desired traffic lights states.\n4. Click on "Add".\n5. Repeat as desired. TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Toggle traffic lights TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool Add or remove traffic lights to/from junctions.\n\nClick on a junction to toggle traffic lights. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, busses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. Public_transport Public transport Prevent_excessive_transfers_at_public_transport_stations Prevent unnecessary transfers at public transport stations Compact_main_menu Compact main menu Window_transparency Window transparency Overlay_transparency Overlay transparency Remove_this_citizen Remove this citizen Show_error_message_if_a_mod_incompatibility_is_detected Show error message if a mod incompatibility is detected Remove_parked_vehicles Remove parked vehicles Node_is_level_crossing This junction is a level crossing.\nYou cannot disable traffic lights here. Experimental_features Experimental features Turn_on_red Turn on red Vehicles_may_turn_on_red Voertuigen kunnen bij rode verkeerslichten draaien Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets ================================================ FILE: TLM/TLM/Resources/lang_pl.txt ================================================ Switch_traffic_lights Włącznik sygnalizacji Add_priority_signs Dodaj znaki pierwszeństwa Manual_traffic_lights Sygnalizacja Ręczna Timed_traffic_lights Sygnalizacja Czasowa Change_lane_arrows Zmień strzałki pasów Clear_Traffic Wyczyść ruch Disable_despawning Zablokuj znikanie pojazdów Enable_despawning Odblokuj znikanie pojazdów NODE_IS_LIGHT Skrzyżowanie posiada sygnalizację. \nUsuń sygnalizację poprzez "Włącznik sygnalizacji" i kliknięcie na tym węźle. NODE_IS_TIMED_LIGHT Skrzyżowanie jest częścią skryptu czasowego. \nWybierz "Sygnalizacja Czasowa", najpierw kliknij na tym węźle i potem na "Usuń". Select_nodes_windowTitle Wybór węzłów Select_nodes Wybierz węzły Node Węzeł Deselect_all_nodes Odznacz wszystkie węzły Setup_timed_traffic_light Ustaw czasową sygnalizację świetlną State Stan Skip Pomiń up w górę down w dół View Wyświetl Edit Edytuj Delete Skasuj Timed_traffic_lights_manager Menedżer Sygnalizacji czasowej Add_step Dodaj krok Remove_timed_traffic_light Usuń sygnalizację czasową Min._Time: Min. Czas: Max._Time: Max. Czas: Save Zapisz Add Dodaj Sensitivity Czułość Very_Low Bardzo niska Low Niska Medium Średnia High Wysoka Very_high Bardzo wysoka Extreme_long_green/red_phases Ekstremalnie długi ziel./czerw. Very_long_green/red_phases Bardzo długi ziel./czerw. Long_green/red_phases Długi ziel./czerw. Moderate_green/red_phases Średni okres ziel./czerw. Short_green/red_phases Krótki ziel./czerw. Very_short_green/red_phases Bardzo krótki ziel./czerw. Extreme_short_green/red_phases Ekstremalnie krótki ziel./czerw. Hide_counters Ukryj liczniki Show_counters Pokaż liczniki Start Start Stop Stop Enable_test_mode_(stay_in_current_step) Aktywuj tryb testu (pozostań w tym kroku) avg._flow śr. przepływ avg._wait śr. oczekiwanie min/max min/max Lane Pas Set_Speed Ustaw Prędkość {0} Max_speed Max Prędkość Segment Segment incoming nadjeżdżające Enable_Advanced_Vehicle_AI Włącz Zaawansowaną Sztuczną Inteligencję Pojazdów Vehicles_may_enter_blocked_junctions Pojazdy mogą wjeżdżać na zablokowane skrzyżowania All_vehicles_may_ignore_lane_arrows Wszystkie pojazdy mogą ignorować strzałki pasów Busses_may_ignore_lane_arrows Autobusy mogą ignorować strzałki pasów Reckless_driving Lekkomyślna jazda (funkcja w wer. BETA) TMPE_Title Menedżer ruchu: Edycja Prezydencka Settings_are_defined_for_each_savegame_separately ustawienia są definiowane oddzielnie dla każdego zapisu Simulation_accuracy Dokładność symulacji (większa dokładność zmiejsza wydajność gry) Enable_highway_specific_lane_merging/splitting_rules Aktywuj Zmienione zasady podziału/łączenia pasów dla autostrad Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Chęć kierowców do zmiany pasa (tylko gdy Zaawansowana SI jest włączona) Maintenance Konserwacja (dodatkowe informacje) Very_often Bardzo często Often Często Sometimes Czasami Rarely Rzadko Very_rarely Bardzo Rzadko Only_if_necessary Tylko jeśli konieczna Nodes_and_segments Pokaż węzły i segmenty Lanes Pasy Path_Of_Evil_(10_%) Diabelska Ścieżka (10 %) Rush_Hour_(5_%) Godziny Szczytu (5 %) Minor_Complaints_(2_%) Drobne Skargi (2 %) Holy_City_(0_%) Święte Miasto (0 %) Forget_toggled_traffic_lights Unuń ustawione sygnalizacje czasowe Road_is_already_in_a_group! Droga jest już w grupie All_selected_roads_must_be_of_the_same_type! Wszystkie zaznaczone drogi muszą być tego samego typu Create_group Stwórz grupę Delete_group Skasuj grupę Add_zoning Aktywuj Strefy Remove_zoning Skasuj Strefy Lane_Arrow_Changer_Disabled_Highway Zmiana strzałek pasów została wyłączona dla tego pasa ponieważ aktywowałeś/łaś Zmienione zasady łączenia pasów dla Autostrad Add_junction_to_timed_light Dodaj skrzyżowanie do tego węzła sygnalizacji Remove_junction_from_timed_light Wyklucz skrzyżowanie z tego węzła sygnalizacji Select_junction Wybierz skrzyżowanie Cancel Anuluj Speed_limits Ograniczenia prędkości Persistently_visible_overlays Trwale widoczne nakładki Priority_signs Znaki pierwszeństwa Vehicles_may_do_u-turns_at_junctions Pojazdy mogą zawracać na skrzyżowaniach Vehicles_going_straight_may_change_lanes_at_junctions Pojazdy jadące na wprost mogą zmieniać pasy ruchu na skrzyżowaniach Allow_u-turns Zezwól na zawracanie Allow_lane_changing_for_vehicles_going_straight Zezwól na zmianę pasa ruchu dla pojazdów jadących na wprost Allow_vehicles_to_enter_a_blocked_junction Zezwól pojazdom na wjazd na zablokowane skrzyżowanie Road_condition_has_a_bigger_impact_on_vehicle_speed Kondycja nawierzchni drogi ma większy wpływ na prędkość pojazdu Vehicle_restrictions Ograniczenia ruchu pojazdów Copy Kopiuj Paste Wklej Invert Odwróć Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions Zastosuj ograniczenia ruchu pojazdów do wszystkich segmentów drogi pomiędzy dwoma skrzyżowaniami Allow_all_vehicles Zezwól dla wszystkich pojazdów Ban_all_vehicles Zablokuj dla wszystkich pojazdów Set_all_traffic_lights_to_red Ustaw wszystkie sygnalizatory na sygnał czerwony Rotate_left Obróć w lewo Rotate_right Obróć w prawo Name Nazwa Apply Zastosuj Select_a_timed_traffic_light_program Wybierz program dla czasowej sygnalizacji świetlnej The_chosen_traffic_light_program_is_incompatible_to_this_junction Wybrany szablon sygnalizacji jest niekompatybilny z tym skrzyżowaniem Advanced_AI_cannot_be_activated Nie można włączyć Zaawansowanej SI The_Advanced_Vehicle_AI_cannot_be_activated Zaawansowana SI nie może zostać włączona, ponieważ aktywowana została inna modyfikacja zmieniająca zachowanie pojazdów (np:. Improved AI lub Traffic++). Enable_dynamic_path_calculation Aktywuj dynamiczne wyznaczanie ścieżki Lane_Arrow_Changer_Disabled_Connection Zmiana strzałek pasów ruchu jest niedostępna, ponieważ pasy ruchu zostały połączone ręcznie. Lane_connector Połącz pasy ruchu Connected_lanes Połączenia pasów ruchu Use_alternative_view_mode Użyj alternatywnego widoku Road_type Typ drogi Default_speed_limit Domyślny limit prędkości Unit_system System jednostek Metric Metryczne Imperial Imperialne Use_more_CPU_cores_for_route_calculation_if_available Użyj więcej rdzeni procesora do obliczania tras (jeśli dostępne) Activated_features Aktywowane funkcje Junction_restrictions Ograniczenia na skrzyżowaniach Prohibit_spawning_of_pocket_cars Zabroń używania "kieszonkowych samochodów" Reset_stuck_cims_and_vehicles Zresetuj zablokowane samochody i ludzi Default_speed_limits Standardowe limity prędkości Looking_for_a_parking_spot Poszukuje miejsca parkingowego Driving_to_a_parking_spot Jedzie do miejsca parkingowego Driving_to_another_parking_spot Jedzie do innego miejsca parkingowego Entering_vehicle Wsiada do samochodu Walking_to_car Idzie do samochodu Using_public_transport Używa transportu publicznego Walking Idzie Thinking_of_a_good_parking_spot Myśli o odpowiednim miejscem parkingowym Switch_view Zmień widok Outgoing_demand Zapotrzebowanie - wychodzące Incoming_demand Zapotrzebowanie - przychodzące Advanced_Vehicle_AI Zaawansowana SI pojazdów Heavy_trucks_prefer_outer_lanes_on_highways Samochody ciężarowe preferują zewnętrzne pasy na autostradach Parking_AI SI parkowania Enable_more_realistic_parking Aktywuj bardziej realistyczny system parkowania samochodów Reset_custom_speed_limits Zresetuj limity prędkości użytkownika Reload_global_configuration Przeładuj ustawienia globalne Reset_global_configuration Zresetuj ustawienia globalne General Ogólne Gameplay Rozgrywka Overlays Nakładki Realistic_speeds Realistyczne prędkości Evacuation_busses_may_ignore_traffic_rules Autobusy ewakuacyjne mogą ignorować przepisy Evacuation_busses_may_only_be_used_to_reach_a_shelter Autobusy ewakuacyjne mogą przewozić tylko do schroniena Vehicle_behavior Zachowanie pojazdu Policies_&_Restrictions Zasady i Ograniczenia At_junctions Na strzyżowaniach In_case_of_emergency W sytuacjach nadzwyczajnych Show_lane-wise_speed_limits Pokaż limity prędkości dla pasów Language Język Game_language Język gry requires_game_restart wymagane ponowne uruchomienie gry Customizations_come_into_effect_instantaneously Modyfikacje ustawień mają natychmiastowy efekt Options Opcje Lock_main_menu_button_position Zablokuj pozycję przycisku menu głównego Lock_main_menu_position Zablokuj pozycję menu głównego Recalculating_lane_routing Ponowne obliczanie tras Please_wait Proszę czekać Parking_restrictions Ograniczenia parkingowe Simulation Symulacja On_roads Na drogach Ban_private_cars_and_trucks_on_bus_lanes Zakaz jazdy buspasem dla samochodów osobowych i ciężarówek default domyślny flow_ratio wskaźnik przepływu wait_ratio wskaźnik oczekiwania After_min._time_has_elapsed_switch_to_next_step_if Po upływie min. czasu, przejdź do następnego kroku Adaptive_step_switching Adaptacyjna zmiana kroku Dynamic_lane_section Obszar dynamicznej zmiany pasa ruchu Percentage_of_vehicles_performing_dynamic_lane_section Procent pojazdów zmieniajacych pas w obszarze dynamicznej zmiany pasa ruchu Vehicle_restrictions_aggression Agrasywność ograniczeń ruchu pojazdów Strict Ścisła Show_path-find_stats Pokaż statystyki znajdowania tras Remove_this_vehicle Usuń ten pojazd Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights Pojazdy poruszają się zgodnie z regułami pierwszeństwa na skrzyżowaniach z sygnalizacją czasową Enable_tutorial_messages Włącz podpowiedzi TMPE_TUTORIAL_HEAD_MainMenu Traffic Manager: President Edition TMPE_TUTORIAL_BODY_MainMenu Witaj w TM:PE!\n\nPodręcznik użytkownika(Ang.): http://www.viathinksoft.de/tmpe \n\nTranslacja Krzychu1245. Zauważyłeś/łaś literówkę, błąd, cokolwiek do poprawy? Odezwij się na Steam;) TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool Ograniczenia na skrzyżowaniach TMPE_TUTORIAL_BODY_JunctionRestrictionsTool Decyduj jak pojazdy i piesi mają się zachowywać na skrzyżowaniach.\n\n1. Wybierz skrzyżowanie do ustawienia zasad\n2. Kliknij na odpowiedniej ikonie by zmienić ograniczenia.\n\nDostępne zasady:\n- Pozwól/Zabroń: zmiany pasa ruchu pojazdom jadącym na wprost \n- Pozwól/Zabroń: zawracanie\n- Pozwól/Zabroń: wjazd na zablokowane/zakorkowane skrzyżowanie\n- Pozwól/Zabroń: przejście pieszych przez drogę TMPE_TUTORIAL_HEAD_LaneArrowTool Narzędzie zmiany kierunków pasów ruchu TMPE_TUTORIAL_BODY_LaneArrowTool Ustaw możliwe kierunki w którym pojazd może się poruszać z danego pasa ruchu.\n\n1. Wybierz segment drogi tuż przy skrzyżowaniu\n2. Ustaw dozwolone kierunki ruchu. TMPE_TUTORIAL_HEAD_LaneConnectorTool Połącz pasy ruchu TMPE_TUTORIAL_BODY_LaneConnectorTool Połącz dwa lub więcej pasów ruchu ze sobą, aby wyznaczyć możliwe ścieżki poruszającym sie nimi pojazdom.\n\n1. Kliknij na wybranym punkcie (szare okręgi).\n2. Wybierz jeden z kolorowych okręgów symbolizujących koniec pasa.\n3. Następnie kliknij na inny, aby je ze sobą połączyć. \n4. Kliknij gdziekolwiek prawym klawiszem myszy, aby powtórzyć wybór końcowego znacznika.\n\nSkróty klawiszowe:\n\n- Delete lub Backspace: Usuń wszystkie połącznia\n- Shift + S: Przełączaj między dostępnymi schematami "pozostań na pasie" TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool Ręczna sygnalizaja świetlna TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Wypróbuj własną sygnalizację świetlną.\n\n1. Kliknij na skrzyżowaniu\n2. Użyj narzędzia do sterowania ręczną sygnalizacją świetlną. TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool Ograniczenia parkingowe TMPE_TUTORIAL_BODY_ParkingRestrictionsTool Decyduj gdzie możliwe jest parkowanie.\n\nKliknij na ikonę "P".\n\nSkróty klawiszowe:\n\n- Shift: przytrzymaj podczas klikania, aby zastowować ograniczenia parkingowe dla kilku segmentów drogi jednocześnie TMPE_TUTORIAL_HEAD_PrioritySignsTool Znaki pierwszeństwa TMPE_TUTORIAL_BODY_PrioritySignsTool Zdefiniuj zasady pierwszeństwa na skrzyżowaniach.\n\n1. Kliknij na skrzyżowaniu.\n2. Klikaj na wybranym półprzezroczystym okręgu by zmienić pierwszeństwo na tym odcinku(droga z pierwszeństwem, ustąp pierwszeństwa, stop).\n\nSkróty klawiszowe:\n\n- Shift: Przytrzymuj Shift, aby dodać znaki pierwszeństwa do kilku segmentów drogi jednocześnie. Kliknij ponownie, przytrzymując klawisz Shift, aby zmieniać pomiędzy dostępnymi schematami. TMPE_TUTORIAL_HEAD_SpeedLimitsTool Limity prędkości TMPE_TUTORIAL_BODY_SpeedLimitsTool Ustaw limity prędkości.\n\n1. W okienku, kliknij na wybrany limit prędkości.\n2. Kliknij na wybrany odcinek drogi, aby zastosować limit.\n\nSkróty klawiszowe:\n\n- Shift: Przytrzymaj Shift podczas wybierania, aby zastosować limit to wielu segmentów.\n- Ctrl: Przytrzymaj Ctrl, aby ustawić limit dla konkretnego pasa ruchu. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool Czasowa sygnalizacja świetlna TMPE_TUTORIAL_BODY_TimedTrafficLightsTool Ustawianie czasowej sygnalizacji.\n\n1. Wybierz skrzyżowanie.\n2. W okienku, kliknij "Ustaw czasową sygnalizację świetlną", następnie "Dodaj krok".\n3. Ustaw pożądany stan sygnalizacji, klikając na wybrany sygnalizator.\n4. Kliknij "Dodaj".\n5. Powtórz kroki jeśli konieczne. TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Przełącz na sygnalizację świetlną TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool Dodaj lub usuń sygnalizację świetlną ze skrzyżowania.\n\nKliknij na wybranym skrzyżowaniu, aby przełączyć stan. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Ograniczenia ruchu pojazdów TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Zabroń wjazdu pojazdom na konkretnych odcinkach drogi.\n\n1. Kliknij na odcinku drogi.\n2. Kliknij na wybranej ikonie, aby wł/wył ograniczenie.\n\nDostępne typy pojazdów:\n\n- Pojazdy drogowe: samochody, autobust, taxi, ciężarówki, pojazdy usługowe, pojazdy uprzywilejowane\n- Pojazdy szynowe: pociągi pasażerskie, towarowe\n\nSkróty klawiszowe:\n\n- Shift: Przytrzymaj Shift podczas klikania, aby zastosować ograniczenia do kilku odcinków drogi jednocześnie. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Domyślne limity prędkości TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Użyj strzałek u góry, aby przęłączać pomiędzy dostępnymi typami dróg.\n2. Poniżej, wybierz limit prędkości.\n3. Naciśnij "Zapisz", aby ustawić wybrany limit prędkości jako domyślny dla nowo wybudowanych dróg wybranego typu. Naciścij "Zapisz i zastosuj", aby zaktualizować wszystkie już istniejące drogi wybranego typu. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Dodaj krok TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. W ramach nakładki, kliknij na sygnalizatorze, aby zmienić jego stan. Użyj przycisku "Zmiana trybu", aby dodać kierunkowe sygnalizatory.\n2. Ustaw minimalny oraz maksymalny czas aktualnego kroku. Po upłynięciu minimalnego czasu sygnalizacja zacznie zliczać i porównywać ilość nadjeżdżających pojazdów.\n3. Opcjonalnie, wybierz tryb zmiany kroku. Domyślnie, krok zostaje zmieniony jeśli liczba poruszających się pojazdów jest dużo mniejsza niż oczekujących.\n4. Opcjonalnie, możesz dostosować czułość. Na przykład, przesuń suwak w lewo by zmniejszyć czułość na oczekujące pojazdy. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Kopiuj czasową sygnalizację TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Kliknij na innym skrzyżowaniu, aby wkleić skopiowane ustawienie.\n\nKliknij gdziekolwiek prawym przyciskiem myszy, aby anulować operację. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Dodaj skrzyżowanie do czasowej sygnalizacji TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Kliknij na innym skrzyżowaniu, aby je dodać. Obydwa skrzyżowania zostaną połączone, ustawienia czasowej sygnalizacji będą działać na nich jednocześnie.\n\nKliknij gdziekolwiek prawym klawiszem myszy, aby anulować operację. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Usuń skrzyżowanie spod kontroli czasowej sygnalizacji TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Kliknij na jednym ze skrzyżować kontrolowanych przez czasową sygnalizację. Wybrane skrzyżowanie zostanie usunięte spod kontroli programu czasowej sygnalizacji.\n\nKliknij gdziekolwiek prawym klawiszem myszy, aby anulować operację. Public_transport Transport publiczny Prevent_excessive_transfers_at_public_transport_stations Zapobiegaj nadmiernej liczbie przesiadek na przystankach transportu publicznego Compact_main_menu Kompaktowe menu główne Window_transparency Przezroczystość okna Overlay_transparency Przezroczystość nakł. Remove_this_citizen Usuń tego mieszkańca Show_error_message_if_a_mod_incompatibility_is_detected Pokaż informację o błędzie jeśli wykryto niezgodność modów Remove_parked_vehicles Usuń zaparkowane pojazdy Node_is_level_crossing To jest przejazd kolejowy. \nNie możesz tutaj wyłączyć sygnalizacji! Experimental_features Funkcje eksperymentalne Turn_on_red Skręć w prawo na czerwonym Vehicles_may_turn_on_red Pojazdy mogą skręcić w prawo na czerwonym świetle Also_apply_to_left/right_turns_between_one-way_streets Uwzględnia również skręt w lewo/prawo pomiędzy drogami jednokierunkowymi ================================================ FILE: TLM/TLM/Resources/lang_pt.txt ================================================ Switch_traffic_lights Interruptor de Semáforos Add_priority_signs Placas de preferência Manual_traffic_lights Semáforo manual Timed_traffic_lights Semáforos cronometrados Change_lane_arrows Alterar setas de pista Clear_Traffic Limpar tráfego Disable_despawning Desativar despawning Enable_despawning Ativar despawning NODE_IS_LIGHT O cruzamento tem um semáforo.\nExclua o semáforo, escolhendo "Interruptor de Semáforos" e clicando em seu cruzamento. NODE_IS_TIMED_LIGHT Esta junção é parte de um script cronometrado.\nPrimeiro selecione "Semáforos cronometrados", selecione este cruzamento e clique em remover. Select_nodes_windowTitle Selecione um cruzamento Select_nodes Selecione cruzamento(s) Node Cruzamento Deselect_all_nodes Cancelar a seleção de todos os cruzamentos Setup_timed_traffic_light Criar semáforo cronometrado State Estado Skip Pular up cima down baixo View Ver Edit Editar Delete Apagar Timed_traffic_lights_manager Gerenciador de semáforos cronometrados Add_step Adicionar Passo Remove_timed_traffic_light Remover semáforo cronometrado Min._Time: Tempo Min.: Max._Time: Tempo Max.: Save Salvar Add Adicionar Sensitivity Sensibilidade Very_Low Muito Baixa Low Baixa Medium Média High Alta Very_high Muito Alta Extreme_long_green/red_phases Fases verde/vermelho extremamente longa Very_long_green/red_phases Fases verde/vermelho muito longa Long_green/red_phases Fases verde/vermelho longa Moderate_green/red_phases Fases verde/vermelho moderada Short_green/red_phases Fases verde/vermelho curta Very_short_green/red_phases Fases verde/vermelho muito curta Extreme_short_green/red_phases Fases verde/vermelho extremamente curta Hide_counters Ocultar contadores Show_counters Mostrar contadores Start Iniciar Stop Parar Enable_test_mode_(stay_in_current_step) Ativar modo de teste (Mantém no passo atual) avg._flow fluxo médio avg._wait Espera média min/max min/max Lane Pista Set_Speed Setar velocidade {0} Max_speed Velocidade Máxima Segment Segmento incoming entrando Enable_Advanced_Vehicle_AI Ativar IA avançada de veículo Vehicles_may_enter_blocked_junctions Veículos podem entrar em cruzamentos bloqueados All_vehicles_may_ignore_lane_arrows Todos os veículos podem ignorar setas da pista Busses_may_ignore_lane_arrows ônibus podem ignorar setas da pista Reckless_driving Direção imprudente (recurso BETA) TMPE_Title Traffic Manager: President Edition Settings_are_defined_for_each_savegame_separately As configurações são definidas para cada jogo salvo separadamente Simulation_accuracy A precisão da simulação (maior precisão reduz o desempenho) Enable_highway_specific_lane_merging/splitting_rules Habilitar regras de divisão e mesclagem de rodovia Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Motoristas gostam de mudar sua faixa:\n(só é aplicado se IA avançada de veiculo estiver habilitado) Maintenance Manunteção Very_often Muitas vezes Often Frequentemente Sometimes Às vezes Rarely Raramente Very_rarely Muito raramente Only_if_necessary Apenas se necessário Nodes_and_segments Cruzamentos e segmentos Lanes Faixas Path_Of_Evil_(10_%) Caminho do mal (10 %) Rush_Hour_(5_%) Hora do Rush (5 %) Minor_Complaints_(2_%) Reclamações de menor importância (2 %) Holy_City_(0_%) Cidade Santa (0 %) Forget_toggled_traffic_lights Esquecer os semáforos alternados Road_is_already_in_a_group! Pista já esta em um grupo! All_selected_roads_must_be_of_the_same_type! Todas as pistas selecionadas devem ser do mesmo tipo! Create_group Criar grupo Delete_group Apagar grupo Add_zoning Adicionar zoneamento Remove_zoning Remover zoneamento Lane_Arrow_Changer_Disabled_Highway O trocador de seta de pista esta desabilitado porque você ativou o sistema de regras para rodovia. Add_junction_to_timed_light Adicionar cruzamento ao semáforo Remove_junction_from_timed_light Remover cruzamento do semáforo Select_junction Selecionar cruzamento Cancel Cancelar Speed_limits Limite de Velocidade Persistently_visible_overlays Sobreposições persistentemente visíveis Priority_signs Placas de preferência Vehicles_may_do_u-turns_at_junctions Veículos podem fazer retorno nos cruzamentos Vehicles_going_straight_may_change_lanes_at_junctions Veículos seguindo reto pode mudar de faixa nos cruzamentos Allow_u-turns Permitir retornos Allow_lane_changing_for_vehicles_going_straight Permitir mudança de faixa para veículos seguindo reto Allow_vehicles_to_enter_a_blocked_junction Permitir entrada de veículos em cruzamentos bloqueados Road_condition_has_a_bigger_impact_on_vehicle_speed Condição de estrada tem um impacto maior na velocidade do veículo Vehicle_restrictions Restrições de veículos Copy Copiar Paste Colar Invert Inveter Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions Aplicar restrições de veículos para todos os segmentos de estrada entre dois cruzamentos Allow_all_vehicles Permitir todos os veículos Ban_all_vehicles Proibir todos os veículos Set_all_traffic_lights_to_red Definir todos os semáforos para vermelho Rotate_left Girar para a esquerda Rotate_right Girar para a direita Name Nome Apply Aplicar Select_a_timed_traffic_light_program Selecione um programa de semáforo cronometrado The_chosen_traffic_light_program_is_incompatible_to_this_junction O padrão de semáforo escolhido é incompatível com este cruzamento Advanced_AI_cannot_be_activated IA avançada não pode ser ativada The_Advanced_Vehicle_AI_cannot_be_activated a IA avançada não pode ser ativada porque você já esta usando outro mod que modifica o comportamento do veículo (Traffic++, Improved AI por exemplo) Enable_dynamic_path_calculation Ativar o cálculo de caminho dinâmico Lane_Arrow_Changer_Disabled_Connection O trocador de seta de pista esta desabilitado para essa pista porque você criou uma conexão de pista manualmente. Lane_connector Conector de pistas Connected_lanes Pistas conectadas Use_alternative_view_mode Usar modo de visão alternativo Road_type Tipo de pista Default_speed_limit Limite de velocidade padrão Unit_system Sistema de unidade Metric Metrico Imperial Imperial Use_more_CPU_cores_for_route_calculation_if_available Usar mais nucleos de CPU para calcular a rota (se disponivel) Activated_features Recursos ativados Junction_restrictions Restrições de junção Prohibit_spawning_of_pocket_cars Proibir o spawn de carros de bolso Reset_stuck_cims_and_vehicles Resetar carros e cims presos Default_speed_limits Limites de velocidade padrão Looking_for_a_parking_spot Procurando uma vaga de estacionamento Driving_to_a_parking_spot Dirigindo para uma vaga de estacionamento Driving_to_another_parking_spot Dirigindo para outra vaga de estacionamento Entering_vehicle Entrando no veículo Walking_to_car Caminhando para carro Using_public_transport Usando transporte público Walking Caminhando Thinking_of_a_good_parking_spot Pensando em uma boa vaga de estacionamento Switch_view Trocar visão Outgoing_demand Demanda de saída Incoming_demand Demanda de entrada Advanced_Vehicle_AI IA avançada de veículo Heavy_trucks_prefer_outer_lanes_on_highways Os veículos pesados preferem faixas externas nas rodovias Parking_AI IA de estacionamento Enable_more_realistic_parking Habilitar estacionamento mais realista Reset_custom_speed_limits Resetar limites de velocidade modificados Reload_global_configuration Recarregar configuração global Reset_global_configuration Resetar configuração global General Geral Gameplay Gameplay Overlays Sobreposições Realistic_speeds Velocidades realistas Evacuation_busses_may_ignore_traffic_rules Os ônibus de evacuação podem ignorar as regras de trânsito Evacuation_busses_may_only_be_used_to_reach_a_shelter Os ônibus de evacuação só podem ser usados para alcançar um abrigo Vehicle_behavior Comportamento do veículo Policies_&_Restrictions Políticas e Restrições At_junctions Nas junções In_case_of_emergency Em caso de emergência Show_lane-wise_speed_limits Mostrar limites de velocidade por faixa. Language Linguagem Game_language Linguagem do Jogo requires_game_restart Requer reinicialização do jogo Customizations_come_into_effect_instantaneously Personalizações entram em vigor instantaneamente Options Opções Lock_main_menu_button_position Travar posição do botão de menu principal Lock_main_menu_position Travar posição do menu principal Recalculating_lane_routing Recalculando o roteamento de faixa Please_wait Por favor, aguarde Parking_restrictions Restrições de estacionamento Simulation Simulation On_roads On roads Ban_private_cars_and_trucks_on_bus_lanes Ban private cars and trucks on bus lanes default default flow_ratio flow ratio wait_ratio wait ratio After_min._time_has_elapsed_switch_to_next_step_if After min. time has elapsed, switch to next step if Adaptive_step_switching Adaptive step switching Dynamic_lane_section Dynamic lane selection Percentage_of_vehicles_performing_dynamic_lane_section Percentage of vehicles performing dynamic lane selection Vehicle_restrictions_aggression Vehicle restrictions aggression Strict Strict Show_path-find_stats Show path-find stats Remove_this_vehicle Remove this vehicle Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights Vehicles follow priority rules at junctions with timed traffic lights Enable_tutorial_messages Enable tutorial messages TMPE_TUTORIAL_HEAD_MainMenu Traffic Manager: President Edition TMPE_TUTORIAL_BODY_MainMenu Welcome to TM:PE!\n\nUser manual: http://www.viathinksoft.de/tmpe TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool Junction restrictions TMPE_TUTORIAL_BODY_JunctionRestrictionsTool Control how vehicles and pedestrians shall behave at junctions.\n\n1. Click on the junction you want to manage\n2. Click on the appropriate icon to toggle restrictions.\n\nAvailable restrictions:\n- Allow/Disallow lane changing for vehicle going straight on\n- Allow/Disallow u-turns\n- Allow/Disallow vehicles to enter a blocked junction\n- Allow/disallow pedestrians to cross the street TMPE_TUTORIAL_HEAD_LaneArrowTool Lane arrows TMPE_TUTORIAL_BODY_LaneArrowTool Restrict the set of directions that vehicles are allowed to take.\n\n1. Click on a road segment next to a junction\n2. Select the allowed directions. TMPE_TUTORIAL_HEAD_LaneConnectorTool Lane connector TMPE_TUTORIAL_BODY_LaneConnectorTool Connect two or more lanes with each other in order to tell which lanes vehicles may use.\n\n1. Click on a lane changing point (grey circles).\n2. Click on a source marker.\n3. Click on a target marker to connect it with the source marker.\n4. Click anywhere with your secondary mouse button to return back to source marker selection.\n\nAvailable hotkeys:\n\n- Delete or Backspace: Remove all lane connections\n- Shift + S: Cycle through all available "stay on lane" patterns TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool Manual traffic lights TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Try out custom traffic lights.\n\n1. Click on a junction\n2. Use the tool to control traffic lights. TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool Parking restrictions TMPE_TUTORIAL_BODY_ParkingRestrictionsTool Control where parking is allowed.\n\nClick on the "P" icons.\n\nAvailable hotkeys:\n\n- Shift: Hold while clicking to apply parking restrictions to multiple road segments at once TMPE_TUTORIAL_HEAD_PrioritySignsTool Priority signs TMPE_TUTORIAL_BODY_PrioritySignsTool Define priority rules at junctions.\n\n1. Click on a junction.\n2. Click on the blank spots to cycle through the available priority signs (priority road, yield, stop).\n\nAvailable hotkeys:\n\n- Shift: Hold Shift to add priority signs to multiple road segments at once. Click again while holding Shift to cycle through all available patterns. TMPE_TUTORIAL_HEAD_SpeedLimitsTool Speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool Set up speed restrictions.\n\n1. In the window, click on the speed limit you want to set.\n2. Click on a road segment to change the speed limit.\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply speed limits to multiple road segments at once.\n- Ctrl: Hold Ctrl to control speed limits per individual lane. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool Timed traffic lights TMPE_TUTORIAL_BODY_TimedTrafficLightsTool Set up timed traffic lights.\n\n1. Click on a junction.\n2. In the window, click on "Add step".\n3. Click on the overlay elements to set desired traffic lights states.\n4. Click on "Add".\n5. Repeat as desired. TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Toggle traffic lights TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool Add or remove traffic lights to/from junctions.\n\nClick on a junction to toggle traffic lights. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, busses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. Public_transport Public transport Prevent_excessive_transfers_at_public_transport_stations Prevent unnecessary transfers at public transport stations Compact_main_menu Compact main menu Window_transparency Window transparency Overlay_transparency Overlay transparency Remove_this_citizen Remove this citizen Show_error_message_if_a_mod_incompatibility_is_detected Show error message if a mod incompatibility is detected Remove_parked_vehicles Remove parked vehicles Node_is_level_crossing This junction is a level crossing.\nYou cannot disable traffic lights here. Experimental_features Experimental features Turn_on_red Virar nos semáforos vermelhos Vehicles_may_turn_on_red Veículos podem virar nos semáforos vermelhos Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets ================================================ FILE: TLM/TLM/Resources/lang_ru.txt ================================================ Switch_traffic_lights Установка светофора Add_priority_signs Знаки приоритета Manual_traffic_lights Ручной светофор Timed_traffic_lights Настраиваемый светофор Change_lane_arrows Стрелки движения Clear_Traffic Очистить трафик Disable_despawning Выключить исчезновение машин Enable_despawning Включить исчезновение машин NODE_IS_LIGHT На перекрёстке работает светофор.\nДля его удаления выберите "Установка светофора" и нажмите на этот узел. NODE_IS_TIMED_LIGHT Перекрёсток содержит скрипт настраиваемого светофора.\nВыберите "Настраиваемый светофор", нажмите на этот узел и выберите "Удалить". Select_nodes_windowTitle Список узлов Select_nodes Выберите перекрёстки Node Узел Deselect_all_nodes Очистить список Setup_timed_traffic_light Настройка интервала времени State Статус Skip Пропустить up вверх down вниз View Смотреть Edit Править Delete Удалить Timed_traffic_lights_manager Управление настраиваемым светофором Add_step Добавить шаг Remove_timed_traffic_light Выключить настраиваемый светофор Min._Time: Мин. время: Max._Time: Макс. время: Save Сохранить Add Добавить Sensitivity Чувствительность Very_Low Очень медленно Low Замедленное Medium Среднее High Высокое Very_high Очень высоко Extreme_long_green/red_phases Слишком долгие фазы зеленый/красный Very_long_green/red_phases Очень долгие фазы зеленый/красный Long_green/red_phases Долгие фазы зеленый/красный Moderate_green/red_phases Умеренные фазы зеленый/красный Short_green/red_phases Короткие фазы зеленый/красный Very_short_green/red_phases Очень короткие фазы зеленый/красный Extreme_short_green/red_phases Слишком короткие фазы зеленый/красный Hide_counters Скрыть счётчики Show_counters Показать счётчики Start Начать Stop Остановить Enable_test_mode_(stay_in_current_step) Тестовый режим (остаться в текущем шаге) avg._flow средний поток avg._wait среднее ожидание min/max мин/макс Lane Полоса Set_Speed Установка скорости {0} Max_speed Макс. скорость Segment Сегмент incoming входящее Enable_Advanced_Vehicle_AI Активировать продвинутый AI Vehicles_may_enter_blocked_junctions Разрешить заезжать на заблокированные перекрёстки All_vehicles_may_ignore_lane_arrows Весь транспорт может игнорировать стрелки движения Busses_may_ignore_lane_arrows Автобусы могут игнорировать стрелки движения Reckless_driving Опасное вождение TMPE_Title Traffic Manager: President Edition Settings_are_defined_for_each_savegame_separately Настройки для каждого сохранения свои Simulation_accuracy Точность моделирования (высокое значение снижает производительность) Enable_highway_specific_lane_merging/splitting_rules Включить особые правила слияния/пересечения полос на шоссе Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Водители могут перестраиваться на более свободные полосы (только при продвинутом AI): Maintenance Инструменты Very_often Очень часто Often Часто Sometimes Иногда Rarely Редко Very_rarely Очень редко Only_if_necessary Только при необходимости Nodes_and_segments Узлы и сегменты Lanes Полосы Path_Of_Evil_(10_%) Дорога зла (10 %) Rush_Hour_(5_%) Час пик (5 %) Minor_Complaints_(2_%) Немного жалоб (2 %) Holy_City_(0_%) Святой город (0 %) Forget_toggled_traffic_lights Забыть расположение светофоров Road_is_already_in_a_group! Дорога уже находится в группе! All_selected_roads_must_be_of_the_same_type! Все выбранные дороги должны быть одинакового типа! Create_group Создать группу Delete_group Удалить группу Add_zoning Добавить зонирование Remove_zoning Удалить зонирование Lane_Arrow_Changer_Disabled_Highway Изменение стрелки полосы для этого перекрестка отключено, по причине вашей активации системы правил шоссе. Add_junction_to_timed_light Добавить перекрёсток в список Remove_junction_from_timed_light Убрать перекрёсток из списка Select_junction Добавление перекрёстка в список Cancel Отмена Speed_limits Ограничение скорости Persistently_visible_overlays Постоянно видимые оверлеи Priority_signs Знаки приоритета Vehicles_may_do_u-turns_at_junctions Транспорт может разворачиваться на перекрёстках Vehicles_going_straight_may_change_lanes_at_junctions Транспорт, идущий прямо, может перестраиваться на соседнюю полосу Allow_u-turns Разрешить разворачиваться Allow_lane_changing_for_vehicles_going_straight Транспорт, идущий прямо, может менять полосу Allow_vehicles_to_enter_a_blocked_junction Разрешить заезжать на заблокированные перекрёстки Road_condition_has_a_bigger_impact_on_vehicle_speed Состояние дороги оказывает большее влияние на скорость Vehicle_restrictions Ограничения транспорта Copy Копировать Paste Вставить Invert Инвертировать Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions Ввести ограничения для транспорта на все участки дороги между двумя перекрёстками Allow_all_vehicles Разрешить всему транспорту Ban_all_vehicles Запретить всему транспорту Set_all_traffic_lights_to_red Включить везде красный свет Rotate_left Повернуть налево Rotate_right Повернуть направо Name Название Apply Применить Select_a_timed_traffic_light_program Выбор режима настраиваемого светофора The_chosen_traffic_light_program_is_incompatible_to_this_junction Выбранный режим светофора несовместим с данным перекрёстком Advanced_AI_cannot_be_activated Продвинутый AI не может быть активирован по причине используемого вами другого мода, который изменяет поведение транспорта (например Improved AI, Traffic++). The_Advanced_Vehicle_AI_cannot_be_activated Продвинутый AI не активирован, потому что вами уже используется иной мод, что меняет поведение транспорта (например Improved AI, Traffic++). Enable_dynamic_path_calculation Включить динамическое вычисление пути Lane_Arrow_Changer_Disabled_Connection Переключатель стрелки движения для этого перекрёстка отключён, потому что вы создали соединение перекрёстка вручную. Lane_connector Линии движения Connected_lanes Связанные маршруты Use_alternative_view_mode Альтернативный вид просмотра Road_type Тип дороги Default_speed_limit Ограничение скорости по-умолчанию Unit_system Системы единиц Metric Метрическая Imperial Международная Use_more_CPU_cores_for_route_calculation_if_available Многоядерный процессор для вычисления маршрута (при наличии) Activated_features Активированные опции Junction_restrictions Ограничения на перекрёстках Prohibit_spawning_of_pocket_cars Запрет появления "карманных"(из сумки) автомобилей Reset_stuck_cims_and_vehicles Удаление застрявших пешеходов и транспорта Default_speed_limits Ограничения скорости по умолчанию Looking_for_a_parking_spot Поиск места для парковки Driving_to_a_parking_spot Движение к месту парковки Driving_to_another_parking_spot Движение на другую парковку Entering_vehicle Посадка в транспорт Walking_to_car Идёт к автомобилю Using_public_transport Использует общественный транспорт Walking Идёт Thinking_of_a_good_parking_spot Думает о хорошем месте для парковки Switch_view Переключение обзора Outgoing_demand Исходящее требование Incoming_demand Входящее требование Advanced_Vehicle_AI Продвинутый AI транспорта Heavy_trucks_prefer_outer_lanes_on_highways Большегрузные траки предпочитают крайние полосы на шоссе Parking_AI AI парковки Enable_more_realistic_parking Включить более реалистичную парковку Reset_custom_speed_limits Сбросить свои ограничения скорости Reload_global_configuration Перезагрузить общую конфигурацию Reset_global_configuration Сбросить общую конфигурацию General Общее Gameplay Геймплей Overlays Оверлеи Realistic_speeds Реалистичные скорости Evacuation_busses_may_ignore_traffic_rules Автобусы эвакуации игнорируют правила движения Evacuation_busses_may_only_be_used_to_reach_a_shelter Эвакуационные автобусы, только чтобы добраться до аварийного убежища Vehicle_behavior Поведение транспорта Policies_&_Restrictions Политики и Ограничения At_junctions На перекрёстках In_case_of_emergency В случае крайней необходимости Show_lane-wise_speed_limits Показывать ограничения скорости по полосам Language Локализация Game_language Язык игры requires_game_restart требуется перезапуск игры Customizations_come_into_effect_instantaneously Настройки применяются сразу Options Свойства Lock_main_menu_button_position Блокировка позиции кнопки Главного меню Lock_main_menu_position Блокировка позиции Главного меню Recalculating_lane_routing Обновление маршрута Please_wait Подождите, пожалуйста Parking_restrictions Ограничения парковки Simulation Моделирование On_roads На дорогах Ban_private_cars_and_trucks_on_bus_lanes Запрет частных автомобилей и грузовиков на автобусных дорогах default По умолчанию flow_ratio плотность движения wait_ratio плотность пробок After_min._time_has_elapsed_switch_to_next_step_if После небольшого ожидания, переключитесь на следующий шаг, если Adaptive_step_switching Адаптивное переключение ступеней Dynamic_lane_section Выбор динамической полосы Percentage_of_vehicles_performing_dynamic_lane_section Процент транспортных средств с динамическим выбором полосы движения Vehicle_restrictions_aggression Следование транспортным ограничениям Strict Строгое Show_path-find_stats Показать статистику поиска пути Remove_this_vehicle Удалить этот транспорт Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights Следование правилам приоритета на перекрёстках с настраиваемыми светофорами Enable_tutorial_messages Включить обучающие сообщения TMPE_TUTORIAL_HEAD_MainMenu Traffic Manager: President Edition TMPE_TUTORIAL_BODY_MainMenu Welcome to TM:PE!\n\nUser manual: http://www.viathinksoft.de/tmpe TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool Ограничения на перекрёстках TMPE_TUTORIAL_BODY_JunctionRestrictionsTool Контроль поведения транспортных средств и пешеходов на перекрёстках.\n\n1. Нажмите на перекрёсток, которым вы хотите управлять.\n2. Нажмите на соответствующий значок, чтобы переключить ограничения.\n\nДоступные ограничения:\n- Разрешить/Запретить смену полосы для автомобиля, идущего прямо\n- Разрешить/запретить разворот\n- Разрешить/Запретить въезд на заблокированный перекрёсток\n- Разрешить/Запретить пешеходам переходить улицу TMPE_TUTORIAL_HEAD_LaneArrowTool Стрелки дорожной разметки TMPE_TUTORIAL_BODY_LaneArrowTool Ограничение направлений транспортных средств.\n\n1. Нажмите на сегмент дороги рядом с перекрестком.\n2. Выберите разрешённые маршруты. TMPE_TUTORIAL_HEAD_LaneConnectorTool Линии движения TMPE_TUTORIAL_BODY_LaneConnectorTool Соедините две или более линии вместе, чтобы транспортные средства могли использовать разные полосы.\n\n1. Нажмите на точку смены полосы (серый круг).\n2. Нажмите на маркер исходной линии.\n3. Нажмите маркер цели, чтобы связать его с маркером источника.\n4. Щёлкните в любом месте с помощью дополнительной кнопки мыши, чтобы вернуться к выбору маркера источника.\n\nДоступные горячие клавиши:\n\n- Delete or Backspace: Удалить все Линии движения\n- Shift + S: Пролистайте все доступные шаблоны "Остаться на полосе" TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool Ручной светофор TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Попробуйте настраивать светофоры.\n\n1. Нажмите на перекрёсток\n2. Используйте инструмент для управления светофорами. TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool Ограничения парковки TMPE_TUTORIAL_BODY_ParkingRestrictionsTool Контролирование парковки.\n\nНажмите на значок "P".\n\nГорячая клавиша:\n\n- Shift: Удерживайте нажатой кнопку, чтобы применить ограничения на парковку для нескольких сегментов дороги TMPE_TUTORIAL_HEAD_PrioritySignsTool Знаки приоритета TMPE_TUTORIAL_BODY_PrioritySignsTool Определение правил приоритета на перекрёстках.\n\n1. Нажмите на перекрёсток.\n2. Нажмите на пустые ячейки, чтобы посмотреть доступные знаки приоритета (Главная дорога, Второстепенная дорога, Stop).\n\nГорячая клавиша:\n\n- Shift: Удерживайте Shift, чтобы добавить знаки приоритета сразу нескольким сегментам дороги. Нажмите ещё раз, удерживая Shift, чтобы просмотреть все доступные шаблоны. TMPE_TUTORIAL_HEAD_SpeedLimitsTool Ограничение скорости TMPE_TUTORIAL_BODY_SpeedLimitsTool Настройка ограничений скорости.\n\n1. В окне нажмите на ограничение скорости, которое вы хотите установить.\n2. Нажмите на сегмент дороги, чтобы изменить ограничение скорости.\n\nДоступные горячие клавиши:\n\n- Shift: Удерживайте нажатой клавишу Shift, чтобы одновременно применять ограничения скорости для нескольких сегментов дороги.\n- Ctrl: Удерживайте Ctrl, чтобы контролировать ограничения скорости на каждую полосу. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool Настраиваемые светофоры TMPE_TUTORIAL_BODY_TimedTrafficLightsTool Настройка светофоров.\n\n1. Нажмите на перекрёсток. В окне нажмите "Настройка интервала времени"\n2. В окне нажмите "Добавить шаг".\n3. Нажмите на элементы наложения(оверлеи), чтобы установить требуемые режимы светофоров.\n4. Нажмите "Добавить".\n5. Повторите по необходимости. TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Расположение светофоров TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool Добавление или удаление светофоров на перекрёстках.\n\nНажмите на перекрёсток для управления светофорами. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Ограничение движения типов транспорта TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Разрешить или запретить типам транспорта использовать определённые сегменты дороги.\n\n1. Нажмите на сегмент дороги.\n2. Нажмите на значки, чтобы переключить ограничения.\n\nИспользуемые типы транспортных средств:\n\n- Дорожные транспортные средства: легковые автомобили, автобусы, такси, грузовые автомобили, служебные транспортные средства, аварийные транспортные средства\n- Железнодорожные транспортные средства: пассажирские поезда, грузовые поезда\n\nГорячая клавиша:\n\n- Shift: Удерживайте нажатой клавишу Shift, одновременно применяя ограничения для нескольких сегментов дороги. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Стандартные ограничения скорости TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Используйте клавиши со стрелками в верхней половине окна для переключения между всеми доступными сегментами дороги.\n2. В нижней половине выберите ограничение скорости.\n3. Нажмите "Сохранить", чтобы установить выбранный предел скорости по умолчанию для будущих сегментов дороги выбранного типа. Нажмите "Сохранить и применить", чтобы обновить все существующие дороги выбранного типа. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Добавить временной шаг TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Внутри игрового оверлея нажмите на светофоры, чтобы изменить их состояние. Используйте кнопку "Изменить режим", чтобы добавить стационарные светофоры.\n2. Введите как минимальную, так и максимальную продолжительность для шага. Очень скоро светофор начнёт считать и сравнивать приближающиеся транспортные средства.\n3. (опционально) Настройте адаптивное шаговое переключение. По умолчанию этот шаг изменяется когда, примерно, меньше транспортных средств пересекает перекрёсток, чем ожидающего транспорта.\n4. При необходимости отрегулируйте чувствительность переключения. Например, переместите ползунок влево, чтобы сделать настраиваемый светофор менее чувствительным к ожидающему транспорту. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Скопируйте настраиваемый светофор TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Нажмите на другой перекрёсток, чтобы разместить настраиваемый светофор.\n\nЩёлкните в любом месте дополнительной кнопкой мышки, чтобы отменить операцию. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Добавить другой перекрёсток в шаблон светофора TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Нажмите на другой перекрёсток, чтобы добавить его. Программа с таймером будет контролировать светофоры на обоих перекрёстках одновременно.\n\nЩёлкните в любом месте дополнительной кнопкой мышки, чтобы отменить операцию. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Удалите перекрёсток из схемы настраиваемого светофора TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Нажмите на один из перекрёстков, которые контролируются этой программой времени. Выбранный светофор будет удален таким образом, что программа с таймером больше не будет управлять им.\n\nЩёлкните в любом месте дополнительной кнопкой мышки, чтобы отменить операцию. Public_transport Общественный транспорт Prevent_excessive_transfers_at_public_transport_stations Предотвращение ненужных пересадок на общественном транспорте Compact_main_menu Компактное Главное меню Window_transparency Прозрачность окна Overlay_transparency Прозрачность элементов(оверлеев) TM:PE Remove_this_citizen Удалить этого гражданина Show_error_message_if_a_mod_incompatibility_is_detected Показывать сообщение об ошибке при несовместимости мода Remove_parked_vehicles Удалить припаркованный транспорт Node_is_level_crossing Это перекрёсток уровней.\nЗдесь нельзя отключить светофоры. Experimental_features Experimental features Turn_on_red Turn on red Vehicles_may_turn_on_red Транспортные средства могут повернуть на красный светофор Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets ================================================ FILE: TLM/TLM/Resources/lang_template.txt ================================================ Switch_traffic_lights Add_priority_signs Manual_traffic_lights Timed_traffic_lights Change_lane_arrows Clear_Traffic Disable_despawning Enable_despawning NODE_IS_LIGHT NODE_IS_TIMED_LIGHT Select_nodes_windowTitle Select_nodes Node Deselect_all_nodes Setup_timed_traffic_light State Skip up down View Edit Delete Timed_traffic_lights_manager Add_step Remove_timed_traffic_light Min._Time: Max._Time: Save Add Sensitivity Very_Low Low Medium High Very_high Extreme_long_green/red_phases Very_long_green/red_phases Long_green/red_phases Moderate_green/red_phases Short_green/red_phases Very_short_green/red_phases Extreme_short_green/red_phases Hide_counters Show_counters Start Stop Enable_test_mode_(stay_in_current_step) avg._flow avg._wait min/max Lane Set_Speed Max_speed Segment incoming Enable_Advanced_Vehicle_AI Vehicles_may_enter_blocked_junctions All_vehicles_may_ignore_lane_arrows Busses_may_ignore_lane_arrows Reckless_driving TMPE_Title Settings_are_defined_for_each_savegame_separately Simulation_accuracy Enable_highway_specific_lane_merging/splitting_rules Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): Maintenance Very_often Often Sometimes Rarely Very_rarely Only_if_necessary Nodes_and_segments Lanes Path_Of_Evil_(10_%) Rush_Hour_(5_%) Minor_Complaints_(2_%) Holy_City_(0_%) Forget_toggled_traffic_lights Road_is_already_in_a_group! All_selected_roads_must_be_of_the_same_type! Create_group Delete_group Add_zoning Remove_zoning Lane_Arrow_Changer_Disabled_Highway Add_junction_to_timed_light Remove_junction_from_timed_light Select_junction Cancel Speed_limits Persistently_visible_overlays Priority_signs Vehicles_may_do_u-turns_at_junctions Vehicles_going_straight_may_change_lanes_at_junctions Allow_u-turns Allow_lane_changing_for_vehicles_going_straight Allow_vehicles_to_enter_a_blocked_junction Road_condition_has_a_bigger_impact_on_vehicle_speed Vehicle_restrictions Copy Paste Invert Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions Allow_all_vehicles Ban_all_vehicles Set_all_traffic_lights_to_red Rotate_left Rotate_right Name Apply Select_a_timed_traffic_light_program The_chosen_traffic_light_program_is_incompatible_to_this_junction Advanced_AI_cannot_be_activated The_Advanced_Vehicle_AI_cannot_be_activated Enable_dynamic_path_calculation Lane_Arrow_Changer_Disabled_Connection Lane_connector Connected_lanes Use_alternative_view_mode Road_type Default_speed_limit Unit_system Metric Imperial Use_more_CPU_cores_for_route_calculation_if_available Activated_features Junction_restrictions Prohibit_spawning_of_pocket_cars Reset_stuck_cims_and_vehicles Default_speed_limits Looking_for_a_parking_spot Driving_to_a_parking_spot Driving_to_another_parking_spot Entering_vehicle Walking_to_car Using_public_transport Walking Thinking_of_a_good_parking_spot Switch_view Outgoing_demand Incoming_demand Advanced_Vehicle_AI Heavy_trucks_prefer_outer_lanes_on_highways Parking_AI Enable_more_realistic_parking Reset_custom_speed_limits Reload_global_configuration Reset_global_configuration General Gameplay Overlays Realistic_speeds Evacuation_busses_may_ignore_traffic_rules Evacuation_busses_may_only_be_used_to_reach_a_shelter Vehicle_behavior Policies_&_Restrictions At_junctions In_case_of_emergency Show_lane-wise_speed_limits Language Game_language requires_game_restart Customizations_come_into_effect_instantaneously Options Lock_main_menu_button_position Lock_main_menu_position Recalculating_lane_routing Please_wait Parking_restrictions Simulation On_roads Ban_private_cars_and_trucks_on_bus_lanes default flow_ratio wait_ratio After_min._time_has_elapsed_switch_to_next_step_if Adaptive_step_switching Dynamic_lane_section Percentage_of_vehicles_performing_dynamic_lane_section Vehicle_restrictions_aggression Strict Show_path-find_stats Remove_this_vehicle Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights Enable_tutorial_messages TMPE_TUTORIAL_HEAD_MainMenu TMPE_TUTORIAL_BODY_MainMenu TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool TMPE_TUTORIAL_BODY_JunctionRestrictionsTool TMPE_TUTORIAL_HEAD_LaneArrowTool TMPE_TUTORIAL_BODY_LaneArrowTool TMPE_TUTORIAL_HEAD_LaneConnectorTool TMPE_TUTORIAL_BODY_LaneConnectorTool TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool TMPE_TUTORIAL_BODY_ManualTrafficLightsTool TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool TMPE_TUTORIAL_BODY_ParkingRestrictionsTool TMPE_TUTORIAL_HEAD_PrioritySignsTool TMPE_TUTORIAL_BODY_PrioritySignsTool TMPE_TUTORIAL_HEAD_SpeedLimitsTool TMPE_TUTORIAL_BODY_SpeedLimitsTool TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool TMPE_TUTORIAL_BODY_TimedTrafficLightsTool TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool TMPE_TUTORIAL_BODY_VehicleRestrictionsTool TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Public_transport Prevent_excessive_transfers_at_public_transport_stations Compact_main_menu Window_transparency Overlay_transparency Remove_this_citizen Show_error_message_if_a_mod_incompatibility_is_detected Remove_parked_vehicles Node_is_level_crossing Experimental_features Turn_on_red Vehicles_may_turn_on_red Also_apply_to_left/right_turns_between_one-way_streets ================================================ FILE: TLM/TLM/Resources/lang_zh-tw.txt ================================================ Switch_traffic_lights 增刪號誌燈 Add_priority_signs 增訂優先通行權 Manual_traffic_lights 手控號誌燈 Timed_traffic_lights 號誌燈時相設定 Change_lane_arrows 改變車道遵行方向 Clear_Traffic 消除所有車輛 Disable_despawning 久塞車輛永不消失 Enable_despawning 允許久塞車輛消失 NODE_IS_LIGHT 具有預設燈號的道路交岔點。\n選用「增刪號誌燈」並點選該道路交岔點來刪除號誌燈。 NODE_IS_TIMED_LIGHT 具有自訂程序燈號的道路交岔點。\n先選用「號誌燈時相設定」,再點選該道路交岔點並點選「移除」以移除設定。 Select_nodes_windowTitle 請點選道路交岔點 Select_nodes 請點選道路交岔點 Node 道路交岔點 Deselect_all_nodes 取消選擇所有道路交岔點 Setup_timed_traffic_light 設定號誌燈時相 State 狀態 Skip 跳過 up 上移 down 下移 View 檢視 Edit 編輯 Delete 刪除 Timed_traffic_lights_manager 號誌燈時相設定工具 Add_step 增加時相 Remove_timed_traffic_light 移除號誌燈時相設定 Min._Time: 最短時間 Max._Time: 最長時間 Save 儲存 Add 增加 Sensitivity 靈敏度 Very_Low 非常低 Low 低 Medium 中 High 高 Very_high 非常高 Extreme_long_green/red_phases 最長的紅、綠燈時相 Very_long_green/red_phases 非常長的紅、綠燈時相 Long_green/red_phases 稍長的紅、綠燈時相 Moderate_green/red_phases 中等的紅、綠燈時相 Short_green/red_phases 稍短的紅、綠燈時相 Very_short_green/red_phases 非常短的紅、綠燈時相 Extreme_short_green/red_phases 最短的紅、綠燈時相 Hide_counters 隱藏讀秒 Show_counters 顯示讀秒 Start 啟用 Stop 停止 Enable_test_mode_(stay_in_current_step) 允許測試模式(停留在當前時相) avg._flow 平均通過 avg._wait 平均停等 min/max 最短/最長 Lane 車道 Set_Speed 設定速限 {0} Max_speed 最高速限 Segment 路段 incoming 進入 Enable_Advanced_Vehicle_AI 啟用加強版行車 AI Vehicles_may_enter_blocked_junctions 塞車回堵時不用保持路口淨空。 All_vehicles_may_ignore_lane_arrows 所有車輛可忽略車道遵行方向 Busses_may_ignore_lane_arrows 公車可忽略車道遵行方向 Reckless_driving 違規駕駛率(功能測試中) TMPE_Title 交通管理器:尊爵版 Settings_are_defined_for_each_savegame_separately 每個存檔保有各自獨立的設定 Simulation_accuracy 模擬精準度(較高的精準度會降低效能) Enable_highway_specific_lane_merging/splitting_rules 施行高速公路獨特的車道單邊出入規則 Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): 駕駛人變換車道的習慣(僅適用於加強版 AI 啟用時) Maintenance 錯誤修復 Very_often 很頻繁 Often 經常 Sometimes 有時候 Rarely 偶爾 Very_rarely 甚少 Only_if_necessary 僅必要時 Nodes_and_segments 道路交岔點和路段 Lanes 車道 Path_Of_Evil_(10_%) 馬路我最大(10%) Rush_Hour_(5_%) 上班趕時間(5%) Minor_Complaints_(2_%) 對不起借過(2%) Holy_City_(0_%) 守法乖寶寶(0%) Forget_toggled_traffic_lights 放棄設定的交通燈號 Road_is_already_in_a_group! 道路已在連鎖群組中 All_selected_roads_must_be_of_the_same_type! 所有選擇的道路必須是同類型的 Create_group 創建連鎖群組 Delete_group 刪除連鎖群組 Add_zoning 增加區劃 Remove_zoning 移除區劃 Lane_Arrow_Changer_Disabled_Highway 改變車道遵行方向的功能已關閉,因為您已施行高速公路特規行駛系統 Add_junction_to_timed_light 將此交岔路口加入號誌燈時相連鎖 Remove_junction_from_timed_light 將此交岔路口移出號誌燈時相連鎖 Select_junction 選取一處道路交叉點 Cancel 取消 Speed_limits 速限 Persistently_visible_overlays 固定檢視的項目 Priority_signs 優先通行權標誌 Vehicles_may_do_u-turns_at_junctions 車輛可在路口迴轉 Vehicles_going_straight_may_change_lanes_at_junctions 直行車可在交岔路口上變換車道 Allow_u-turns 允許迴轉 Allow_lane_changing_for_vehicles_going_straight 允許直行車變換車道 Allow_vehicles_to_enter_a_blocked_junction 允許塞車回堵時不用保持路口淨空 Road_condition_has_a_bigger_impact_on_vehicle_speed 道路狀態對車速有較大的影響 Vehicle_restrictions 車種限定 Copy 複製 Paste 貼上 Invert 對調選擇 Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions 對兩個路口間的全部路段套用車種限定 Allow_all_vehicles 通行所有車種 Ban_all_vehicles 禁行所有車種 Set_all_traffic_lights_to_red 將所有號誌燈設為紅燈 Rotate_left 向左轉動 Rotate_right 向右轉動 Name 名稱 Apply 套用 Select_a_timed_traffic_light_program 請點選一個號誌燈的時相程序 The_chosen_traffic_light_program_is_incompatible_to_this_junction 所選的號誌燈時相不適用於此交叉路口 Advanced_AI_cannot_be_activated 加強版 AI 無法啟動 The_Advanced_Vehicle_AI_cannot_be_activated 加強版車輛AI無法啟動,因為您所使用的其它車輛行控模組正作動中(例如 Improved AI 或 Traffic++) Enable_dynamic_path_calculation 啟用動態的路線運算 Lane_Arrow_Changer_Disabled_Connection 因為您已手動創建了車道的連接,故在此車道上無法使用改變遵行方向的功能 Lane_connector 車道連接工具 Connected_lanes 已連接車道 Use_alternative_view_mode 使用替代瀏覽模式 Road_type 道路類型 Default_speed_limit 預設道路速限 Unit_system 單位制 Metric 公制 Imperial 英制 Use_more_CPU_cores_for_route_calculation_if_available 使用更多的CPU核心來做路線運算 (若有的話) Activated_features 已啟用的功能 Junction_restrictions 交岔路口動線連接制定 Prohibit_spawning_of_pocket_cars 禁止行人憑空變出汽車移動 Reset_stuck_cims_and_vehicles 重置卡死的行人和車輛 Default_speed_limits 預設速限 Looking_for_a_parking_spot 尋找停車位中 Driving_to_a_parking_spot 開往停車位中 Driving_to_another_parking_spot 開往另一處停車位中 Entering_vehicle 上車中 Walking_to_car 走向汽車中 Using_public_transport 使用大眾交通工具中 Walking 走路中 Thinking_of_a_good_parking_spot 盤算一個好停車位中 Switch_view 切換檢視 Outgoing_demand 出貨需求 Incoming_demand 進貨需求 Advanced_Vehicle_AI 加強版行車AI Heavy_trucks_prefer_outer_lanes_on_highways 大型車在高速公路上優先行駛於外側車道 Parking_AI 道路停車AI Enable_more_realistic_parking 啟用更多現實中的停車位 Reset_custom_speed_limits 重置自訂速限 Reload_global_configuration 重新載入所有設定 Reset_global_configuration 重置所有設定 General 普通 Gameplay 遊戲 Overlays 重疊 Realistic_speeds 現實速度 Evacuation_busses_may_ignore_traffic_rules 緊急疏散公車可忽略交通規則 Evacuation_busses_may_only_be_used_to_reach_a_shelter 緊急疏散公車僅可用於到達避難所 Vehicle_behavior 車輛行為 Policies_&_Restrictions 政策與限制 At_junctions 位在交岔路口 In_case_of_emergency 在緊急狀況下 Show_lane-wise_speed_limits 顯示車道速限 Language 語言 Game_language 遊戲顯示語言 requires_game_restart 需要重新啟動遊戲 Customizations_come_into_effect_instantaneously 自訂值即時生效 Options 選項 Lock_main_menu_button_position 鎖定主選單的選項位置 Lock_main_menu_position 鎖定主選單的位置 Recalculating_lane_routing 重新規劃行駛路線 Please_wait 請稍候 Parking_restrictions 停車限制 Simulation Simulation On_roads On roads Ban_private_cars_and_trucks_on_bus_lanes Ban private cars and trucks on bus lanes default default flow_ratio flow ratio wait_ratio wait ratio After_min._time_has_elapsed_switch_to_next_step_if After min. time has elapsed, switch to next step if Adaptive_step_switching Adaptive step switching Dynamic_lane_section Dynamic lane selection Percentage_of_vehicles_performing_dynamic_lane_section Percentage of vehicles performing dynamic lane selection Vehicle_restrictions_aggression Vehicle restrictions aggression Strict Strict Show_path-find_stats Show path-find stats Remove_this_vehicle Remove this vehicle Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights Vehicles follow priority rules at junctions with timed traffic lights Enable_tutorial_messages Enable tutorial messages TMPE_TUTORIAL_HEAD_MainMenu Traffic Manager: President Edition TMPE_TUTORIAL_BODY_MainMenu Welcome to TM:PE!\n\nUser manual: http://www.viathinksoft.de/tmpe TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool Junction restrictions TMPE_TUTORIAL_BODY_JunctionRestrictionsTool Control how vehicles and pedestrians shall behave at junctions.\n\n1. Click on the junction you want to manage\n2. Click on the appropriate icon to toggle restrictions.\n\nAvailable restrictions:\n- Allow/Disallow lane changing for vehicle going straight on\n- Allow/Disallow u-turns\n- Allow/Disallow vehicles to enter a blocked junction\n- Allow/disallow pedestrians to cross the street TMPE_TUTORIAL_HEAD_LaneArrowTool Lane arrows TMPE_TUTORIAL_BODY_LaneArrowTool Restrict the set of directions that vehicles are allowed to take.\n\n1. Click on a road segment next to a junction\n2. Select the allowed directions. TMPE_TUTORIAL_HEAD_LaneConnectorTool Lane connector TMPE_TUTORIAL_BODY_LaneConnectorTool Connect two or more lanes with each other in order to tell which lanes vehicles may use.\n\n1. Click on a lane changing point (grey circles).\n2. Click on a source marker.\n3. Click on a target marker to connect it with the source marker.\n4. Click anywhere with your secondary mouse button to return back to source marker selection.\n\nAvailable hotkeys:\n\n- Delete or Backspace: Remove all lane connections\n- Shift + S: Cycle through all available "stay on lane" patterns TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool Manual traffic lights TMPE_TUTORIAL_BODY_ManualTrafficLightsTool Try out custom traffic lights.\n\n1. Click on a junction\n2. Use the tool to control traffic lights. TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool Parking restrictions TMPE_TUTORIAL_BODY_ParkingRestrictionsTool Control where parking is allowed.\n\nClick on the "P" icons.\n\nAvailable hotkeys:\n\n- Shift: Hold while clicking to apply parking restrictions to multiple road segments at once TMPE_TUTORIAL_HEAD_PrioritySignsTool Priority signs TMPE_TUTORIAL_BODY_PrioritySignsTool Define priority rules at junctions.\n\n1. Click on a junction.\n2. Click on the blank spots to cycle through the available priority signs (priority road, yield, stop).\n\nAvailable hotkeys:\n\n- Shift: Hold Shift to add priority signs to multiple road segments at once. Click again while holding Shift to cycle through all available patterns. TMPE_TUTORIAL_HEAD_SpeedLimitsTool Speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool Set up speed restrictions.\n\n1. In the window, click on the speed limit you want to set.\n2. Click on a road segment to change the speed limit.\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply speed limits to multiple road segments at once.\n- Ctrl: Hold Ctrl to control speed limits per individual lane. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool Timed traffic lights TMPE_TUTORIAL_BODY_TimedTrafficLightsTool Set up timed traffic lights.\n\n1. Click on a junction.\n2. In the window, click on "Add step".\n3. Click on the overlay elements to set desired traffic lights states.\n4. Click on "Add".\n5. Repeat as desired. TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool Toggle traffic lights TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool Add or remove traffic lights to/from junctions.\n\nClick on a junction to toggle traffic lights. TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool Vehicle restrictions TMPE_TUTORIAL_BODY_VehicleRestrictionsTool Ban vehicles from certain road segments.\n\n1. Click on a road segment.\n2. Click on the icons to toggle restrictions.\n\nDistinguished vehicle types:\n\n- Road vehicles: Passenger cars, busses, taxis, cargo trucks, service vehicles, emergency vehicles\n- Rail vehicles: Passenger trains, cargo trains\n\nAvailable hotkeys:\n\n- Shift: Hold Shift while clicking to apply restrictions to multiple road segments at once. TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults Default speed limits TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. Use the arrows in the upper half to cycle through all road types.\n2. In the lower half, select a speed limit.\n3. Click on "Save" to set the selected speed limit as default for future road segments of the selected type. Click on "Save & Apply" to also update all existing roads of the selected type. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Add a timed step TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. Within the in-game overlay, click on the traffic lights to change their state. Use the "Change mode" button to add directional traffic lights.\n2. Enter both a minimum and maximum duration for the step. After the min. time has elapsed the traffic light will count and compare approaching vehicles.\n3. Optionally, select a step switching type. Per default, the step changes if roughly less vehicles are driving than waiting.\n4. Optionally, adjust the light's sensitivity. For example, move the slider to the left to make the timed traffic light less sensitive for waiting vehicles. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copy a timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Click on another junction to paste the timed traffic light.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Add a junction to the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Click on another junction to add it. Both lights will be joined such that the timed program will then control both junctions at once.\n\nClick anywhere with your secondary mouse button to cancel the operation. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Remove a junction from the timed traffic light TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Click on one of the junctions that are controlled by this timed program. The selected traffic light will be removed such that the timed programm will no longer manage it.\n\nClick anywhere with your secondary mouse button to cancel the operation. Public_transport Public transport Prevent_excessive_transfers_at_public_transport_stations Prevent unnecessary transfers at public transport stations Compact_main_menu Compact main menu Window_transparency Window transparency Overlay_transparency Overlay transparency Remove_this_citizen Remove this citizen Show_error_message_if_a_mod_incompatibility_is_detected Show error message if a mod incompatibility is detected Remove_parked_vehicles Remove parked vehicles Node_is_level_crossing This junction is a level crossing.\nYou cannot disable traffic lights here. Experimental_features Experimental features Turn_on_red Turn on red Vehicles_may_turn_on_red 車輛可能會在紅色交通燈處轉彎 Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets ================================================ FILE: TLM/TLM/Resources/lang_zh.txt ================================================ Switch_traffic_lights 红绿灯增删 Add_priority_signs 添加路口优先权 Manual_traffic_lights 手动控制红绿灯 Timed_traffic_lights 红绿灯定时设置 Change_lane_arrows 变更车道方向 Clear_Traffic 清除所有车辆 Disable_despawning 禁止车辆消失 Enable_despawning 允许车辆消失 NODE_IS_LIGHT 路口有红绿灯。\n请选择「红绿灯增删」,并点选该路口来删除红绿灯。 NODE_IS_TIMED_LIGHT 路口有自定时红绿灯,\n请选择「红绿灯定时设置」,再点选该路口并点选「删除」以删除设定。 Select_nodes_windowTitle 请选择路口 Select_nodes 请选择路口 Node 路口 Deselect_all_nodes 取消选择所选路口 Setup_timed_traffic_light 设置红绿灯定时 State 状态 Skip 跳过 up 往上 down 往下 View 视图 Edit 编辑 Delete 删除 Timed_traffic_lights_manager 红绿灯定时设置工具 Add_step 添加定时 Remove_timed_traffic_light 删除红绿灯定时设置 Min._Time: 最短时间(秒) Max._Time: 最长时间(秒) Save 保存 Add 添加 Sensitivity 灵敏度 Very_Low 非常低 Low 低 Medium 中 High 高 Very_high 非常高 Extreme_long_green/red_phases 极长的红绿灯时间 Very_long_green/red_phases 非常长的红绿灯时间 Long_green/red_phases 较长的红绿灯时间 Moderate_green/red_phases 适当的红绿灯时间 Short_green/red_phases 较短的红绿灯时间 Very_short_green/red_phases 非常短的红绿灯时间 Extreme_short_green/red_phases 极短的红绿灯时间 Hide_counters 隐藏计数器 Show_counters 显示计数器 Start 启动 Stop 停止 Enable_test_mode_(stay_in_current_step) 启用测试模式(保持当前步骤) avg._flow 平均通行 avg._wait 平均等待 min/max 最短/最长 Lane 车道 Set_Speed 设定限速 {0} Max_speed 最高限速 Segment 路段 incoming 进入 Enable_Advanced_Vehicle_AI 启用高级车辆AI Vehicles_may_enter_blocked_junctions 车辆可以进入堵塞路口加塞 All_vehicles_may_ignore_lane_arrows 所有车辆可忽略车道方向 Busses_may_ignore_lane_arrows 公交车可忽略车道方向 Reckless_driving 违规驾驶 TMPE_Title 交通管理:总统版 Settings_are_defined_for_each_savegame_separately 设置于每个存档独立存储\n(载入存档时会读取该存档中所存储的存档时TMPE的设置.建议载入存档后设置再存档,以后就不用再重复设置) Simulation_accuracy 模拟真实度(高真实度会影响性能) Enable_highway_specific_lane_merging/splitting_rules 实行高速公路特定车道合流/分离规则 Drivers_want_to_change_lanes_(only_applied_if_Advanced_AI_is_enabled): 司机倾向变更车道(仅适用于高级AI启用时) Maintenance 维护 Very_often 总是 Often 经常 Sometimes 偶尔 Rarely 很少 Very_rarely 极少 Only_if_necessary 仅在必要时 Nodes_and_segments 道路节点和段 Lanes 车道 Path_Of_Evil_(10_%) 无视法纪 (10 %) Rush_Hour_(5_%) 鲁莽驾驶 (5 %) Minor_Complaints_(2_%) 稍有违法 (2 %) Holy_City_(0_%) 遵纪守法 (0 %) Forget_toggled_traffic_lights 重置所有红绿灯设置 Road_is_already_in_a_group! 道路已经在组合中 All_selected_roads_must_be_of_the_same_type! 所有选中的道路必须是同一类型的 Create_group 创建组合 Delete_group 删除组合 Add_zoning 添加分区 Remove_zoning 删除分区 Lane_Arrow_Changer_Disabled_Highway 不能改变这条车道的箭头因已启用高速公路特定规则 Add_junction_to_timed_light 向定时红绿灯添加一路口 Remove_junction_from_timed_light 从定时红绿灯移除一路口 Select_junction 选择一路口 Cancel 取消 Speed_limits 道路限速设置 Persistently_visible_overlays 持续可见的覆盖 Priority_signs 优先通行权设置 Vehicles_may_do_u-turns_at_junctions 车辆可在路口掉头 Vehicles_going_straight_may_change_lanes_at_junctions 直行车辆可以在路口变更车道 Allow_u-turns 允许车辆掉头 Allow_lane_changing_for_vehicles_going_straight 允许直行车辆变更车道 Allow_vehicles_to_enter_a_blocked_junction 允许车辆进入堵塞路口 Road_condition_has_a_bigger_impact_on_vehicle_speed 道路状况对车辆速度有较大影响 Vehicle_restrictions 道路车种限制 Copy 复制 Paste 粘贴 Invert 反转 Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions 应用车种限制设定至两个路口间所有路段 Allow_all_vehicles 允许所有车种通行 Ban_all_vehicles 禁止所有车种通行 Set_all_traffic_lights_to_red 将所有红绿灯设为红灯 Rotate_left 向左旋转 Rotate_right 向右旋转 Name 名称 Apply 应用 Select_a_timed_traffic_light_program 请选择一个红绿灯来设定定时 The_chosen_traffic_light_program_is_incompatible_to_this_junction 所选择的红绿灯模式不能用于此路口 Advanced_AI_cannot_be_activated 高级车辆AI无法启动 The_Advanced_Vehicle_AI_cannot_be_activated 高级车辆AI无法启动,因为您已启用其他车辆控制MOD Enable_dynamic_path_calculation 启用动态路径计算 Lane_Arrow_Changer_Disabled_Connection 车道箭头不可改变因为你已经手动创建车道连接 Lane_connector 车道连接工具 Connected_lanes 已连接车道 Use_alternative_view_mode 使用替代视图模式 Road_type 道路类型 Default_speed_limit 默认道路限速 Unit_system 单元制度 Metric 公制 Imperial 英制 Use_more_CPU_cores_for_route_calculation_if_available 使用更多的CPU内核来计算路线(如果可用) Activated_features 启用功能 Junction_restrictions 路口管制 Prohibit_spawning_of_pocket_cars 禁止袖珍车产生 Reset_stuck_cims_and_vehicles 重置卡住的行人和车辆 Default_speed_limits 默认速度限制 Looking_for_a_parking_spot 寻找停车位 Driving_to_a_parking_spot 行驶到停车位 Driving_to_another_parking_spot 行驶到另一个停车位 Entering_vehicle 进入车辆 Walking_to_car 走向汽车 Using_public_transport 使用公共交通 Walking 步行 Thinking_of_a_good_parking_spot 找一个好停车位 Switch_view 切换视图 Outgoing_demand 出货需求 Incoming_demand 进货需求 Advanced_Vehicle_AI 高级车辆AI Heavy_trucks_prefer_outer_lanes_on_highways 大型车辆在高速公路上优先行驶外侧车道 Parking_AI 停车AI Enable_more_realistic_parking 启用更真实的停车位 Reset_custom_speed_limits 重置自定义速度限制 Reload_global_configuration 重新加载所有设定 Reset_global_configuration 重置所有设定 General 常规 Gameplay 游戏 Overlays 覆盖 Realistic_speeds 真实车速 Evacuation_busses_may_ignore_traffic_rules 应急疏散巴士可忽略交通规则 Evacuation_busses_may_only_be_used_to_reach_a_shelter 应急疏散巴士仅可用于到达避难所 Vehicle_behavior 车辆行为 Policies_&_Restrictions 政策与限制 At_junctions 位于路口 In_case_of_emergency 在紧急情况下 Show_lane-wise_speed_limits 显示道路各车道限速设置 Language 语言 Game_language 同游戏语言 requires_game_restart 重启后生效 Customizations_come_into_effect_instantaneously 订制模拟真实度将立即生效 Options 选项 Lock_main_menu_button_position 锁定主菜单按钮位置 Lock_main_menu_position 锁定主菜单位置 Recalculating_lane_routing 重新计算车道路线 Please_wait 请稍候 Parking_restrictions 路边停车限制 Simulation 模拟 On_roads 专用道路 Ban_private_cars_and_trucks_on_bus_lanes 禁止私家车和卡车驶入公交车专用道 default 默认 flow_ratio 通行比例 wait_ratio 等候比例 After_min._time_has_elapsed_switch_to_next_step_if 在设定最短时间过后,跳转至下一步骤,如果 Adaptive_step_switching 渐进逐步切换 Dynamic_lane_section 智能车道选择 Percentage_of_vehicles_performing_dynamic_lane_section 进行智能车道选择的车辆百分比 Vehicle_restrictions_aggression 车辆交规严格度 Strict 严苛 Show_path-find_stats 显示车辆寻找路线数据 Remove_this_vehicle 移除此车辆 Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights 优先通行规则适用于带自定时红绿灯的路口 Enable_tutorial_messages 启用教程信息 TMPE_TUTORIAL_HEAD_MainMenu 交通管理:总统版 TMPE_TUTORIAL_BODY_MainMenu 欢迎使用TM:PE!\n\n使用手册:http://www.viathinksoft.de/tmpe TMPE_TUTORIAL_HEAD_JunctionRestrictionsTool 路口管制 TMPE_TUTORIAL_BODY_JunctionRestrictionsTool 控制车辆和行人在路口的行为。\n\n1. 单击你想要设置的路口\n2. 单击合适的图标来切换限制\n\n可选限制(允许/不许):\n- 直行车辆在路口变更车道\n- 车辆在路口掉头\n- 车辆进入堵塞路口\n- 行人横穿道路 TMPE_TUTORIAL_HEAD_LaneArrowTool 变更车道方向 TMPE_TUTORIAL_BODY_LaneArrowTool 限制车辆的可选方向。\n\n1. 单击路口前的路段\n2. 选择允许的方向 TMPE_TUTORIAL_HEAD_LaneConnectorTool 车道连接工具 TMPE_TUTORIAL_BODY_LaneConnectorTool 连接2+条车道来告知车辆可用的车道。\n\n1. 单击一个车道变更点(灰色圆圈)\n2. 单击一个来源车道\n3. 单击目标车道来建立连接\n4. 任意位置单击右键返回来源车道选择\n\n可用热键:\n\n- Delete or Backspace:移除所有车道连接\n- Shift + S:(循环)查看所有"stay on lane"模式 TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool 手动控制红绿灯 TMPE_TUTORIAL_BODY_ManualTrafficLightsTool 尝试自定时的红绿灯。\n\n1. 单击一个路口\n2. 使用此工具来控制红绿灯 TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool 路边停车限制 TMPE_TUTORIAL_BODY_ParkingRestrictionsTool 控制是否允许停车。\n\n单击"P"标志\n\n可用热键:\n\n- Shift:按住可一次性应用路边停车限制至多个路段 TMPE_TUTORIAL_HEAD_PrioritySignsTool 优先通行权设置 TMPE_TUTORIAL_BODY_PrioritySignsTool 设置路口的优先通行权。\n\n1. 单击一个路口\n2. 单击空白点来(循环)选择可用的优先通行权(干道、减速让行、停车让行)\n\n可用热键:\n\n- Shift:按住可一次性应用优先通行权至多个路段;按住时单击可(循环)选择可用的优先通行权 TMPE_TUTORIAL_HEAD_SpeedLimitsTool 道路限速设置 TMPE_TUTORIAL_BODY_SpeedLimitsTool 设置道路限速。\n\n1. 在窗口中单击你想要设置的限速\n2. 单击一个路段来应用限速\n\n可用热键:\n\n- Shift:按住可一次性应用限速至多个路段\n- Ctrl:按住来单独设置每条车道的限速 TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool 红绿灯定时设置 TMPE_TUTORIAL_BODY_TimedTrafficLightsTool 设置红绿灯定时。\n\n1. 单击一个路口\n2. 在窗口中单击"添加定时"\n3. 单击覆盖元素来设置所要的红绿灯状态\n4. 单击"添加"\n5. 按需重复 TMPE_TUTORIAL_HEAD_ToggleTrafficLightsTool 红绿灯增删 TMPE_TUTORIAL_BODY_ToggleTrafficLightsTool 对路口增加/删除红绿灯。\n\n单击一个路口来增删红绿灯 TMPE_TUTORIAL_HEAD_VehicleRestrictionsTool 道路车种限制 TMPE_TUTORIAL_BODY_VehicleRestrictionsTool 禁止车辆通过特定路段。\n\n1. 单击一个路段\n2. 单击图标来切换限制\n\n车辆种类:\n\n- 公路车辆:客车、公交车、出租车、货车、服务车辆、应急车辆\n- 轨道车辆:客运列车、货运列车\n\n可用热键:\n\n- Shift:按住可一次性应用车种限制至多个路段 TMPE_TUTORIAL_HEAD_SpeedLimitsTool_Defaults 默认速度限制 TMPE_TUTORIAL_BODY_SpeedLimitsTool_Defaults 1. 在上半部分使用箭头来(循环)选择所有的道路种类\n2. 在下半部分选择限速\n3. 单击"保存"来设置所选限速为新建该种类道路的默认;单击"保存 & 应用"来同时更新所选种类已有的道路 TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep 添加定时 TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep 1. 单击红绿灯来切换他们的状态;使用"改变模式"按钮来添加定向的红绿灯\n2. 输入一个最小和最大区间:经过最小时间后红绿灯将统计并比较一段时间内的来车\n3. 可选红绿灯变换类型:默认情况下通行车辆少于等待车辆时变换\n4. 可选调整红绿灯的敏感度:例如移动滑条至左侧来使自定时红绿灯对等待车辆较不敏感 TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy 复制自定时红绿灯 TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy 单击另一个路口来粘贴。\n\n任意位置单击右键来取消 TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction 向自定时红绿灯添加一个路口 TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction 单击另一个路口来添加:两个红绿灯将被连接从而定时器将会同时控制两个路口\n\n任意位置单击右键来取消 TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction 从自定时红绿灯移除一个路口 TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction 单击一个由该定时器控制的路口:所选的红绿灯将被移除从而该定时器将不再控制它\n\n任意位置单击右键来取消 Public_transport 公共交通 Prevent_excessive_transfers_at_public_transport_stations 公共交通避免非必要换乘 Compact_main_menu 紧凑主菜单 Window_transparency 窗口透明度 Overlay_transparency 覆盖透明度 Remove_this_citizen 移除此居民 Show_error_message_if_a_mod_incompatibility_is_detected 检测到MOD不兼容时提示 Remove_parked_vehicles 移除停放的车辆 Node_is_level_crossing 此节点级别交汇,不可禁用红绿灯。 Experimental_features Experimental features Turn_on_red Turn on red Vehicles_may_turn_on_red 车辆红灯可转弯 Also_apply_to_left/right_turns_between_one-way_streets Also apply to left & right turns between one-way streets ================================================ FILE: TLM/TLM/State/ConfigData/AdvancedVehicleAI.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.State.ConfigData { public class AdvancedVehicleAI { /// /// Junction randomization for randomized lane selection /// public uint LaneRandomizationJunctionSel = 3; /// /// Cost factor for lane randomization /// public float LaneRandomizationCostFactor = 1f; /// /// minimum base lane changing cost /// public float LaneChangingBaseMinCost = 1.1f; /// /// maximum base lane changing cost /// public float LaneChangingBaseMaxCost = 1.5f; /// /// base cost for changing lanes in front of junctions /// public float LaneChangingJunctionBaseCost = 2f; /// /// base cost for traversing junctions /// public float JunctionBaseCost = 0.1f; /// /// > 1 lane changing cost factor /// public float MoreThanOneLaneChangingCostFactor = 2f; /// /// Relative factor for lane traffic cost calculation /// public float TrafficCostFactor = 4f; /// /// lane density random interval /// public float LaneDensityRandInterval = 20f; /// /// Threshold for resetting traffic buffer /// public uint MaxTrafficBuffer = 10; } } ================================================ FILE: TLM/TLM/State/ConfigData/Debug.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Traffic; using static TrafficManager.Traffic.Data.ExtCitizenInstance; namespace TrafficManager.State.ConfigData { #if DEBUG public class Debug { public bool[] Switches = { false, // 0: path-finding debug log false, // 1: routing basic debug log false, // 2: parking ai debug log (basic) false, // 3: do not actually repair stuck vehicles/cims, just report false, // 4: parking ai debug log (extended) false, // 5: geometry debug log false, // 6: debug parking AI distance issue false, // 7: debug TTL false, // 8: debug routing false, // 9: debug vehicle to segment end linking false, // 10: prevent routing recalculation on global configuration reload false, // 11: debug junction restrictions false, // 12: - unused - false, // 13: priority rules debug false, // 14: disable GUI overlay of citizens having a valid path false, // 15: disable checking of other vehicles for trams false, // 16: debug TramBaseAI.SimulationStep (2) false, // 17: debug alternative lane selection false, // 18: transport line path-find debugging false, // 19: enable obligation to drive on the right hand side of the road false, // 20: debug realistic public transport false, // 21: debug "CalculateSegmentPosition" false, // 22: parking ai debug log (vehicles) false, // 23: debug lane connections false, // 24: debug resource loading false // 25: debug turn-on-red }; public int NodeId = 0; public int SegmentId = 0; public int StartSegmentId = 0; public int EndSegmentId = 0; public int VehicleId = 0; public int CitizenInstanceId = 0; public uint CitizenId = 0; public uint SourceBuildingId = 0; public uint TargetBuildingId = 0; public ExtVehicleType ExtVehicleType = ExtVehicleType.None; public ExtPathMode ExtPathMode = ExtPathMode.None; } #endif } ================================================ FILE: TLM/TLM/State/ConfigData/DynamicLaneSelection.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.State.ConfigData { public class DynamicLaneSelection { /// /// Maximum allowed reserved space on previous vehicle lane /// public float MaxReservedSpace = 0.5f; /// /// Maximum allowed reserved space on previous vehicle lane (for reckless drivers) /// public float MaxRecklessReservedSpace = 10f; /// /// Lane speed randomization interval /// public float LaneSpeedRandInterval = 5f; /// /// Maximum number of considered lane changes /// public int MaxOptLaneChanges = 2; /// /// Maximum allowed speed difference for safe lane changes /// public float MaxUnsafeSpeedDiff = 0.4f; /// /// Minimum required speed improvement for safe lane changes /// public float MinSafeSpeedImprovement = 25f; /// /// Minimum required traffic flow improvement for safe lane changes /// public float MinSafeTrafficImprovement = 20f; /// /// Minimum relative speed (in %) where volume measurement starts /// public ushort VolumeMeasurementRelSpeedThreshold = 50; } } ================================================ FILE: TLM/TLM/State/ConfigData/Main.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.UI.MainMenu; namespace TrafficManager.State.ConfigData { public class Main { /// /// Main menu button position /// public int MainMenuButtonX = 464; public int MainMenuButtonY = 10; public bool MainMenuButtonPosLocked = false; /// /// Main menu position /// public int MainMenuX = MainMenuPanel.DEFAULT_MENU_X; public int MainMenuY = MainMenuPanel.DEFAULT_MENU_Y; public bool MainMenuPosLocked = false; /// /// Already displayed tutorial messages /// public string[] DisplayedTutorialMessages = new string[0]; /// /// Determines if tutorial messages shall show up /// public bool EnableTutorial = true; /// /// Determines if the main menu shall be displayed in a tiny format /// public bool TinyMainMenu = true; /// /// User interface transparency /// public byte GuiTransparency = 30; /// /// Overlay transparency /// public byte OverlayTransparency = 40; /// /// Extended mod compatibility check /// public bool ShowCompatibilityCheckErrorMessage = false; public void AddDisplayedTutorialMessage(string messageKey) { HashSet newMessages = DisplayedTutorialMessages != null ? new HashSet(DisplayedTutorialMessages) : new HashSet(); newMessages.Add(messageKey); DisplayedTutorialMessages = newMessages.ToArray(); } } } ================================================ FILE: TLM/TLM/State/ConfigData/ParkingAI.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.State.ConfigData { public class ParkingAI { /// /// Target position randomization to allow opposite road-side parking /// public uint ParkingSpacePositionRand = 32; /// /// parking space search in vicinity is randomized. Cims do not always select the nearest parking space possible. /// A value of 1u always selects the nearest parking space. /// A value of 2u selects the nearest parking space with 50% chance, the next one with 25%, then 12.5% and so on. /// A value of 3u selects the nearest parking space with 66.67% chance, the next one with 22.22%, then 7.4% and so on. /// A value of 4u selects the nearest parking space with 75% chance, the next one with 18.75%, then 4.6875% and so on. /// A value of N selects the nearest parking space with (N-1)/N chance, the next one with (1-(N-1)/N)*(N-1)/N, then (1-(N-1)/N)^2*(N-1)/N and so on. /// public uint VicinityParkingSpaceSelectionRand = 4u; /// /// maximum number of parking attempts for passenger cars /// public int MaxParkingAttempts = 10; /// /// maximum required squared distance between citizen instance and parked vehicle before the parked car is turned into a vehicle /// public float MaxParkedCarInstanceSwitchSqrDistance = 6f; /// /// maximum distance between building and pedestrian lane /// public float MaxBuildingToPedestrianLaneDistance = 96f; /// /// Maximum allowed distance between home/source building and parked car when travelling home without forced to use the car /// public float MaxParkedCarDistanceToHome = 256f; /// /// minimum required distance between target building and parked car for using a car /// public float MaxParkedCarDistanceToBuilding = 512f; /// /// public transport demand increment on path-find failure /// public uint PublicTransportDemandIncrement = 10u; /// /// public transport demand increment if waiting time was exceeded /// public uint PublicTransportDemandWaitingIncrement = 3u; /// /// public transport demand decrement on simulation step /// public uint PublicTransportDemandDecrement = 1u; /// /// public transport demand decrement on path-find success /// public uint PublicTransportDemandUsageDecrement = 7u; /// /// parking space demand decrement on simulation step /// public uint ParkingSpaceDemandDecrement = 1u; /// /// minimum parking space demand delta when a passenger car could be spawned /// public int MinSpawnedCarParkingSpaceDemandDelta = -5; /// /// maximum parking space demand delta when a passenger car could be spawned /// public int MaxSpawnedCarParkingSpaceDemandDelta = 3; /// /// minimum parking space demand delta when a parking spot could be found /// public int MinFoundParkPosParkingSpaceDemandDelta = -5; /// /// maximum parking space demand delta when a parking spot could be found /// public int MaxFoundParkPosParkingSpaceDemandDelta = 3; /// /// parking space demand increment when no parking spot could be found while trying to park /// public uint FailedParkingSpaceDemandIncrement = 5u; /// /// parking space demand increment when no parking spot could be found while trying to spawn a parked vehicle /// public uint FailedSpawnParkingSpaceDemandIncrement = 10u; } } ================================================ FILE: TLM/TLM/State/ConfigData/PathFinding.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.State.ConfigData { public class PathFinding { /// /// penalty for busses not driving on bus lanes /// public float PublicTransportLanePenalty = 10f; /// /// reward for public transport staying on transport lane /// public float PublicTransportLaneReward = 0.1f; /// /// maximum penalty for heavy vehicles driving on an inner lane /// public float HeavyVehicleMaxInnerLanePenalty = 0.5f; /// /// Junction randomization for randomized lane selection /// public uint HeavyVehicleInnerLanePenaltySegmentSel = 3; /// /// artifical lane distance for vehicles that change to lanes which have an incompatible lane arrow configuration /// public byte IncompatibleLaneDistance = 2; /// /// artifical lane distance for u-turns /// public int UturnLaneDistance = 2; /// /// Maximum walking distance /// public float MaxWalkingDistance = 2500f; /// /// Minimum penalty for entering public transport vehicles /// public float PublicTransportTransitionMinPenalty = 0f; /// /// Maximum penalty for entering public transport vehicles /// public float PublicTransportTransitionMaxPenalty = 100f; } } ================================================ FILE: TLM/TLM/State/ConfigData/PriorityRules.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.State.ConfigData { public class PriorityRules { /// /// maximum incoming vehicle square distance to junction for priority signs /// public float MaxPriorityCheckSqrDist = 225f; /// /// maximum junction approach time for priority signs /// public float MaxPriorityApproachTime = 15f; /// /// maximum waiting time at priority signs /// public uint MaxPriorityWaitTime = 100; /// /// Maximum yield velocity /// public float MaxYieldVelocity = 2.5f; /// /// Maximum stop velocity /// public float MaxStopVelocity = 0.1f; } } ================================================ FILE: TLM/TLM/State/ConfigData/TimedTrafficLights.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.TrafficLight; namespace TrafficManager.State.ConfigData { public class TimedTrafficLights { /// /// TTL wait/flow calculation mode /// public FlowWaitCalcMode FlowWaitCalcMode = FlowWaitCalcMode.Mean; /// /// Default TTL flow-to-wait ratio /// public float FlowToWaitRatio = 0.8f; /// /// TTL smoothing factor for flowing/waiting vehicles /// public float SmoothingFactor = 0.1f; } } ================================================ FILE: TLM/TLM/State/Configuration.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using System.Xml.Serialization; using TrafficManager.State; // TODO this class should be moved to TrafficManager.State, but the deserialization fails if we just do that now. Anyway, we should get rid of these crazy lists of arrays. So let's move the class when we decide rework the load/save system. namespace TrafficManager { [Serializable] public class Configuration { [Serializable] public class LaneSpeedLimit { public uint laneId; public ushort speedLimit; public LaneSpeedLimit(uint laneId, ushort speedLimit) { this.laneId = laneId; this.speedLimit = speedLimit; } } [Serializable] public class LaneVehicleTypes { public uint laneId; public Traffic.ExtVehicleType vehicleTypes; public LaneVehicleTypes(uint laneId, Traffic.ExtVehicleType vehicleTypes) { this.laneId = laneId; this.vehicleTypes = vehicleTypes; } } [Serializable] public class TimedTrafficLights { public ushort nodeId; public List nodeGroup; public bool started; public int currentStep; public List timedSteps; } [Serializable] public class TimedTrafficLightsStep { public int minTime; public int maxTime; public int changeMetric; public float waitFlowBalance; public Dictionary segmentLights; } [Serializable] public class CustomSegmentLights { public ushort nodeId; public ushort segmentId; public Dictionary customLights; public RoadBaseAI.TrafficLightState? pedestrianLightState; public bool manualPedestrianMode; } [Serializable] public class CustomSegmentLight { public ushort nodeId; public ushort segmentId; public int currentMode; public RoadBaseAI.TrafficLightState leftLight; public RoadBaseAI.TrafficLightState mainLight; public RoadBaseAI.TrafficLightState rightLight; } [Serializable] public class SegmentNodeConf { public ushort segmentId; public SegmentNodeFlags startNodeFlags = null; public SegmentNodeFlags endNodeFlags = null; public SegmentNodeConf(ushort segmentId) { this.segmentId = segmentId; } } [Serializable] public class ParkingRestriction { public ushort segmentId; public bool forwardParkingAllowed; public bool backwardParkingAllowed; public ParkingRestriction(ushort segmentId) { this.segmentId = segmentId; forwardParkingAllowed = true; backwardParkingAllowed = true; } } [Serializable] public class SegmentNodeFlags { public bool? uturnAllowed = null; public bool? turnOnRedAllowed = null; // controls near turns // TODO fix naming when the serialization system is updated public bool? farTurnOnRedAllowed = null; public bool? straightLaneChangingAllowed = null; public bool? enterWhenBlockedAllowed = null; public bool? pedestrianCrossingAllowed = null; public bool IsDefault() { bool uturnIsDefault = uturnAllowed == null || (bool)uturnAllowed == Options.allowUTurns; bool turnOnRedIsDefault = turnOnRedAllowed == null || (bool)turnOnRedAllowed; bool farTurnOnRedIsDefault = farTurnOnRedAllowed == null || (bool)farTurnOnRedAllowed; bool straightChangeIsDefault = straightLaneChangingAllowed == null || (bool)straightLaneChangingAllowed == Options.allowLaneChangesWhileGoingStraight; bool enterWhenBlockedIsDefault = enterWhenBlockedAllowed == null || (bool)enterWhenBlockedAllowed == Options.allowEnterBlockedJunctions; bool pedCrossingIsDefault = pedestrianCrossingAllowed == null || (bool)pedestrianCrossingAllowed; return uturnIsDefault && turnOnRedIsDefault && farTurnOnRedIsDefault && straightChangeIsDefault && enterWhenBlockedIsDefault && pedCrossingIsDefault; } public override string ToString() { return $"uturnAllowed={uturnAllowed}, turnOnRedAllowed={turnOnRedAllowed}, farTurnOnRedAllowed={farTurnOnRedAllowed}, straightLaneChangingAllowed={straightLaneChangingAllowed}, enterWhenBlockedAllowed={enterWhenBlockedAllowed}, pedestrianCrossingAllowed={pedestrianCrossingAllowed}"; } } [Serializable] public class LaneConnection { public uint lowerLaneId; public uint higherLaneId; public bool lowerStartNode; public LaneConnection(uint lowerLaneId, uint higherLaneId, bool lowerStartNode) { if (lowerLaneId >= higherLaneId) throw new ArgumentException(); this.lowerLaneId = lowerLaneId; this.higherLaneId = higherLaneId; this.lowerStartNode = lowerStartNode; } } [Serializable] public class LaneArrowData { public uint laneId; public uint arrows; public LaneArrowData(uint laneId, uint arrows) { this.laneId = laneId; this.arrows = arrows; } } [Serializable] public class PrioritySegment { public ushort segmentId; public ushort nodeId; public int priorityType; public PrioritySegment(ushort segmentId, ushort nodeId, int priorityType) { this.segmentId = segmentId; this.nodeId = nodeId; this.priorityType = priorityType; } } [Serializable] public class NodeTrafficLight { public ushort nodeId; public bool trafficLight; public NodeTrafficLight(ushort nodeId, bool trafficLight) { this.nodeId = nodeId; this.trafficLight = trafficLight; } } [Serializable] public class ExtCitizenInstanceData { public uint instanceId; public int pathMode; public int failedParkingAttempts; public ushort parkingSpaceLocationId; public int parkingSpaceLocation; public ushort parkingPathStartPositionSegment; public byte parkingPathStartPositionLane; public byte parkingPathStartPositionOffset; public uint returnPathId; public int returnPathState; public float lastDistanceToParkedCar; public ExtCitizenInstanceData(uint instanceId) { this.instanceId = instanceId; pathMode = 0; failedParkingAttempts = 0; parkingSpaceLocationId = 0; parkingSpaceLocation = 0; parkingPathStartPositionSegment = 0; parkingPathStartPositionLane = 0; parkingPathStartPositionOffset = 0; returnPathId = 0; returnPathState = 0; lastDistanceToParkedCar = 0; } } [Serializable] public class ExtCitizenData { public uint citizenId; public int lastTransportMode; public ExtCitizenData(uint citizenId) { this.citizenId = citizenId; lastTransportMode = 0; } } /// /// Stored ext. citizen data /// public List ExtCitizens = new List(); /// /// Stored ext. citizen instance data /// public List ExtCitizenInstances = new List(); /// /// Stored toggled traffic lights /// public List ToggledTrafficLights = new List(); /// /// Stored lane connections /// public List LaneConnections = new List(); /// /// Stored lane arrows /// public List LaneArrows = new List(); /// /// Stored lane speed limits /// public List LaneSpeedLimits = new List(); /// /// Stored vehicle restrictions /// public List LaneAllowedVehicleTypes = new List(); /// /// Timed traffic lights /// public List TimedLights = new List(); /// /// Segment-at-Node configurations /// public List SegmentNodeConfs = new List(); /// /// Custom default speed limits (in game speed units) /// public Dictionary CustomDefaultSpeedLimits = new Dictionary(); /// /// Priority segments /// public List CustomPrioritySegments = new List(); /// /// Parking restrictions /// public List ParkingRestrictions = new List(); [Obsolete] public string NodeTrafficLights = ""; [Obsolete] public string NodeCrosswalk = ""; [Obsolete] public string LaneFlags = ""; [Obsolete] public List PrioritySegments = new List(); [Obsolete] public List NodeDictionary = new List(); [Obsolete] public List ManualSegments = new List(); [Obsolete] public List TimedNodes = new List(); [Obsolete] public List TimedNodeGroups = new List(); [Obsolete] public List TimedNodeSteps = new List(); [Obsolete] public List TimedNodeStepSegments = new List(); } } ================================================ FILE: TLM/TLM/State/Flags.cs ================================================ #define DEBUGFLAGSx using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Text; using System.Threading; using TrafficManager.Geometry; using TrafficManager.Manager; using TrafficManager.Manager.Impl; using TrafficManager.Traffic; using TrafficManager.Util; namespace TrafficManager.State { [Obsolete] public class Flags { [Flags] public enum LaneArrows { // compatible with NetLane.Flags None = 0, Forward = 16, Left = 32, Right = 64, LeftForward = 48, LeftRight = 96, ForwardRight = 80, LeftForwardRight = 112 } public enum LaneArrowChangeResult { Invalid, HighwayArrows, LaneConnection, Success } public static readonly uint lfr = (uint)NetLane.Flags.LeftForwardRight; /// /// For each lane: Defines the lane arrows which are set /// private static LaneArrows?[] laneArrowFlags = null; /// /// For each lane (by id): list of lanes that are connected with this lane by the T++ lane connector /// key 1: source lane id /// key 2: at start node? /// values: target lane id /// internal static uint[][][] laneConnections = null; /// /// For each lane: Defines the currently set speed limit /// private static Dictionary laneSpeedLimit = null; // TODO remove internal static ushort?[][] laneSpeedLimitArray; // for faster, lock-free access, 1st index: segment id, 2nd index: lane index /// /// For each lane: Defines the lane arrows which are set in highway rule mode (they are not saved) /// private static LaneArrows?[] highwayLaneArrowFlags = null; /// /// For each lane: Defines the allowed vehicle types /// internal static ExtVehicleType?[][] laneAllowedVehicleTypesArray; // for faster, lock-free access, 1st index: segment id, 2nd index: lane index private static object laneSpeedLimitLock = new object(); internal static void PrintDebugInfo() { Log.Info("------------------------"); Log.Info("--- LANE ARROW FLAGS ---"); Log.Info("------------------------"); for (uint i = 0; i < laneArrowFlags.Length; ++i) { if (highwayLaneArrowFlags[i] != null || laneArrowFlags[i] != null) { Log.Info($"Lane {i}: valid? {Constants.ServiceFactory.NetService.IsLaneValid(i)}"); } if (highwayLaneArrowFlags[i] != null) { Log.Info($"\thighway arrows: {highwayLaneArrowFlags[i]}"); } if (laneArrowFlags[i] != null) { Log.Info($"\tcustom arrows: {laneArrowFlags[i]}"); } } Log.Info("------------------------"); Log.Info("--- LANE CONNECTIONS ---"); Log.Info("------------------------"); for (uint i = 0; i < laneConnections.Length; ++i) { if (laneConnections[i] == null) continue; ushort segmentId = Singleton.instance.m_lanes.m_buffer[i].m_segment; Log.Info($"Lane {i}: valid? {Constants.ServiceFactory.NetService.IsLaneValid(i)}, seg. valid? {Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)}"); for (int x = 0; x < 2; ++x) { if (laneConnections[i][x] == null) continue; ushort nodeId = x == 0 ? Singleton.instance.m_segments.m_buffer[segmentId].m_startNode : Singleton.instance.m_segments.m_buffer[segmentId].m_endNode; Log.Info($"\tNode idx {x} ({nodeId}, seg. {segmentId}): valid? {Constants.ServiceFactory.NetService.IsNodeValid(nodeId)}"); for (int y = 0; y < laneConnections[i][x].Length; ++y) { if (laneConnections[i][x][y] == 0) continue; Log.Info($"\t\tEntry {y}: {laneConnections[i][x][y]} (valid? {Constants.ServiceFactory.NetService.IsLaneValid(laneConnections[i][x][y])})"); } } } Log.Info("-------------------------"); Log.Info("--- LANE SPEED LIMITS ---"); Log.Info("-------------------------"); for (uint i = 0; i < laneSpeedLimitArray.Length; ++i) { if (laneSpeedLimitArray[i] == null) continue; Log.Info($"Segment {i}: valid? {Constants.ServiceFactory.NetService.IsSegmentValid((ushort)i)}"); for (int x = 0; x < laneSpeedLimitArray[i].Length; ++x) { if (laneSpeedLimitArray[i][x] == null) continue; Log.Info($"\tLane idx {x}: {laneSpeedLimitArray[i][x]}"); } } Log.Info("---------------------------------"); Log.Info("--- LANE VEHICLE RESTRICTIONS ---"); Log.Info("---------------------------------"); for (uint i = 0; i < laneAllowedVehicleTypesArray.Length; ++i) { if (laneAllowedVehicleTypesArray[i] == null) continue; Log.Info($"Segment {i}: valid? {Constants.ServiceFactory.NetService.IsSegmentValid((ushort)i)}"); for (int x = 0; x < laneAllowedVehicleTypesArray[i].Length; ++x) { if (laneAllowedVehicleTypesArray[i][x] == null) continue; Log.Info($"\tLane idx {x}: {laneAllowedVehicleTypesArray[i][x]}"); } } } [Obsolete] public static bool mayHaveTrafficLight(ushort nodeId) { if (nodeId <= 0) { return false; } if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) { //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (not created). flags={Singleton.instance.m_nodes.m_buffer[nodeId].m_flags}"); Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; return false; } ItemClass connectionClass = Singleton.instance.m_nodes.m_buffer[nodeId].Info.GetConnectionClass(); if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Junction) == NetNode.Flags.None && connectionClass.m_service != ItemClass.Service.PublicTransport ) { //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (no junction or not public transport). flags={Singleton.instance.m_nodes.m_buffer[nodeId].m_flags} connectionClass={connectionClass?.m_service}"); Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; return false; } if (connectionClass == null || (connectionClass.m_service != ItemClass.Service.Road && connectionClass.m_service != ItemClass.Service.PublicTransport)) { //Log._Debug($"Flags: Node {nodeId} may not have a traffic light (no connection class). connectionClass={connectionClass?.m_service}"); Singleton.instance.m_nodes.m_buffer[nodeId].m_flags &= ~NetNode.Flags.TrafficLights; return false; } return true; } [Obsolete] public static bool setNodeTrafficLight(ushort nodeId, bool flag) { if (nodeId <= 0) return false; #if DEBUGFLAGS Log._Debug($"Flags: Set node traffic light: {nodeId}={flag}"); #endif if (!mayHaveTrafficLight(nodeId)) { //Log.Warning($"Flags: Refusing to add/delete traffic light to/from node: {nodeId} {flag}"); return false; } Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { NetNode.Flags flags = node.m_flags | NetNode.Flags.CustomTrafficLights; if ((bool)flag) { #if DEBUGFLAGS Log._Debug($"Adding traffic light @ node {nId}"); #endif flags |= NetNode.Flags.TrafficLights; } else { #if DEBUGFLAGS Log._Debug($"Removing traffic light @ node {nId}"); #endif flags &= ~NetNode.Flags.TrafficLights; } node.m_flags = flags; return true; }); return true; } [Obsolete] internal static bool isNodeTrafficLight(ushort nodeId) { if (nodeId <= 0) return false; if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) return false; return (Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.TrafficLights) != NetNode.Flags.None; } /// /// Removes lane connections that point from lane to lane at node . /// /// /// /// /// private static bool RemoveSingleLaneConnection(uint sourceLaneId, uint targetLaneId, bool startNode) { #if DEBUGFLAGS Log._Debug($"Flags.CleanupLaneConnections({sourceLaneId}, {targetLaneId}, {startNode}) called."); #endif int nodeArrayIndex = startNode ? 0 : 1; if (laneConnections[sourceLaneId] == null || laneConnections[sourceLaneId][nodeArrayIndex] == null) return false; uint[] srcLaneConnections = laneConnections[sourceLaneId][nodeArrayIndex]; bool ret = false; int remainingConnections = 0; for (int i = 0; i < srcLaneConnections.Length; ++i) { if (srcLaneConnections[i] != targetLaneId) { ++remainingConnections; } else { ret = true; srcLaneConnections[i] = 0; } } if (remainingConnections <= 0) { laneConnections[sourceLaneId][nodeArrayIndex] = null; if (laneConnections[sourceLaneId][1 - nodeArrayIndex] == null) laneConnections[sourceLaneId] = null; // total cleanup return ret; } if (remainingConnections != srcLaneConnections.Length) { laneConnections[sourceLaneId][nodeArrayIndex] = new uint[remainingConnections]; int k = 0; for (int i = 0; i < srcLaneConnections.Length; ++i) { if (srcLaneConnections[i] == 0) continue; laneConnections[sourceLaneId][nodeArrayIndex][k++] = srcLaneConnections[i]; } } return ret; } /// /// Removes any lane connections that exist between two given lanes /// /// /// /// /// internal static bool RemoveLaneConnection(uint lane1Id, uint lane2Id, bool startNode1) { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}) called."); #endif bool lane1Valid = CheckLane(lane1Id); bool lane2Valid = CheckLane(lane2Id); bool ret = false; if (! lane1Valid) { // remove all incoming/outgoing lane connections RemoveLaneConnections(lane1Id); ret = true; } if (! lane2Valid) { // remove all incoming/outgoing lane connections RemoveLaneConnections(lane2Id); ret = true; } if (lane1Valid || lane2Valid) { ushort commonNodeId; bool startNode2; LaneConnectionManager.Instance.GetCommonNodeId(lane1Id, lane2Id, startNode1, out commonNodeId, out startNode2); // TODO refactor if (commonNodeId == 0) { Log.Warning($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}): Could not identify common node between lanes {lane1Id} and {lane2Id}"); } if (RemoveSingleLaneConnection(lane1Id, lane2Id, startNode1)) ret = true; if (RemoveSingleLaneConnection(lane2Id, lane1Id, startNode2)) ret = true; } #if DEBUGCONN if (debug) Log._Debug($"Flags.RemoveLaneConnection({lane1Id}, {lane2Id}, {startNode1}). ret={ret}"); #endif return ret; } /// /// Removes all incoming/outgoing lane connections of the given lane /// /// /// internal static void RemoveLaneConnections(uint laneId, bool? startNode=null) { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug($"Flags.RemoveLaneConnections({laneId}, {startNode}) called. laneConnections[{laneId}]={laneConnections[laneId]}"); #endif if (laneConnections[laneId] == null) return; bool laneValid = CheckLane(laneId); bool clearBothSides = startNode == null || !laneValid; #if DEBUGCONN if (debug) Log._Debug($"Flags.RemoveLaneConnections({laneId}, {startNode}): laneValid={laneValid}, clearBothSides={clearBothSides}"); #endif int? nodeArrayIndex = null; if (!clearBothSides) { nodeArrayIndex = (bool)startNode ? 0 : 1; } for (int k = 0; k <= 1; ++k) { if (nodeArrayIndex != null && k != (int)nodeArrayIndex) continue; bool startNode1 = k == 0; if (laneConnections[laneId][k] == null) continue; for (int i = 0; i < laneConnections[laneId][k].Length; ++i) { uint otherLaneId = laneConnections[laneId][k][i]; ushort commonNodeId; bool startNode2; LaneConnectionManager.Instance.GetCommonNodeId(laneId, otherLaneId, startNode1, out commonNodeId, out startNode2); // TODO refactor if (commonNodeId == 0) { Log.Warning($"Flags.RemoveLaneConnections({laneId}, {startNode}): Could not identify common node between lanes {laneId} and {otherLaneId}"); } RemoveSingleLaneConnection(otherLaneId, laneId, startNode2); } laneConnections[laneId][k] = null; } if (clearBothSides) laneConnections[laneId] = null; } /// /// adds lane connections between two given lanes /// /// /// /// /// internal static bool AddLaneConnection(uint lane1Id, uint lane2Id, bool startNode1) { bool lane1Valid = CheckLane(lane1Id); bool lane2Valid = CheckLane(lane2Id); if (!lane1Valid) { // remove all incoming/outgoing lane connections RemoveLaneConnections(lane1Id); } if (!lane2Valid) { // remove all incoming/outgoing lane connections RemoveLaneConnections(lane2Id); } if (!lane1Valid || !lane2Valid) return false; ushort commonNodeId; bool startNode2; LaneConnectionManager.Instance.GetCommonNodeId(lane1Id, lane2Id, startNode1, out commonNodeId, out startNode2); // TODO refactor if (commonNodeId != 0) { CreateLaneConnection(lane1Id, lane2Id, startNode1); CreateLaneConnection(lane2Id, lane1Id, startNode2); return true; } else return false; } /// /// Adds a lane connection from lane to lane at node /// Assumes that both lanes are valid. /// /// /// /// private static void CreateLaneConnection(uint sourceLaneId, uint targetLaneId, bool startNode) { if (laneConnections[sourceLaneId] == null) { laneConnections[sourceLaneId] = new uint[2][]; } int nodeArrayIndex = startNode ? 0 : 1; if (laneConnections[sourceLaneId][nodeArrayIndex] == null) { laneConnections[sourceLaneId][nodeArrayIndex] = new uint[] { targetLaneId }; return; } uint[] oldConnections = laneConnections[sourceLaneId][nodeArrayIndex]; laneConnections[sourceLaneId][nodeArrayIndex] = new uint[oldConnections.Length + 1]; Array.Copy(oldConnections, laneConnections[sourceLaneId][nodeArrayIndex], oldConnections.Length); laneConnections[sourceLaneId][nodeArrayIndex][oldConnections.Length] = targetLaneId; } internal static bool CheckLane(uint laneId) { // TODO refactor if (laneId <= 0) return false; if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) return false; ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; if (segmentId <= 0) return false; if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) return false; return true; } public static void setLaneSpeedLimit(uint laneId, ushort? speedLimit) { if (!CheckLane(laneId)) return; ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; uint laneIndex = 0; while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { if (curLaneId == laneId) { setLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit); return; } laneIndex++; curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; } } public static void removeLaneSpeedLimit(uint laneId) { setLaneSpeedLimit(laneId, null); } public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, ushort speedLimit) { if (segmentId <= 0 || laneIndex < 0) return; if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) return; NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; if (laneIndex >= segmentInfo.m_lanes.Length) { return; } // find the lane id uint laneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; for (int i = 0; i < laneIndex; ++i) { if (laneId == 0) return; // no valid lane found laneId = Singleton.instance.m_lanes.m_buffer[laneId].m_nextLane; } setLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit); } public static void setLaneSpeedLimit(ushort segmentId, uint laneIndex, uint laneId, ushort? speedLimit) { if (segmentId <= 0 || laneIndex < 0 || laneId <= 0) return; if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) return; if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) return; NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; if (laneIndex >= segmentInfo.m_lanes.Length) { return; } try { Monitor.Enter(laneSpeedLimitLock); #if DEBUGFLAGS Log._Debug($"Flags.setLaneSpeedLimit: setting speed limit of lane index {laneIndex} @ seg. {segmentId} to {speedLimit}"); #endif if (speedLimit == null) { laneSpeedLimit.Remove(laneId); if (laneSpeedLimitArray[segmentId] == null) return; if (laneIndex >= laneSpeedLimitArray[segmentId].Length) return; laneSpeedLimitArray[segmentId][laneIndex] = null; } else { laneSpeedLimit[laneId] = (ushort)speedLimit; // save speed limit into the fast-access array. // (1) ensure that the array is defined and large enough if (laneSpeedLimitArray[segmentId] == null) { laneSpeedLimitArray[segmentId] = new ushort?[segmentInfo.m_lanes.Length]; } else if (laneSpeedLimitArray[segmentId].Length < segmentInfo.m_lanes.Length) { var oldArray = laneSpeedLimitArray[segmentId]; laneSpeedLimitArray[segmentId] = new ushort?[segmentInfo.m_lanes.Length]; Array.Copy(oldArray, laneSpeedLimitArray[segmentId], oldArray.Length); } // (2) insert the custom speed limit laneSpeedLimitArray[segmentId][laneIndex] = speedLimit; } } finally { Monitor.Exit(laneSpeedLimitLock); } } public static void setLaneAllowedVehicleTypes(uint laneId, ExtVehicleType vehicleTypes) { if (laneId <= 0) return; if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) return; ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; if (segmentId <= 0) return; if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) return; NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; uint laneIndex = 0; while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { if (curLaneId == laneId) { setLaneAllowedVehicleTypes(segmentId, laneIndex, laneId, vehicleTypes); return; } laneIndex++; curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; } } public static void setLaneAllowedVehicleTypes(ushort segmentId, uint laneIndex, uint laneId, ExtVehicleType vehicleTypes) { if (segmentId <= 0 || laneIndex < 0 || laneId <= 0) return; if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) return; if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) return; NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; if (laneIndex >= segmentInfo.m_lanes.Length) { return; } #if DEBUGFLAGS Log._Debug($"Flags.setLaneAllowedVehicleTypes: setting allowed vehicles of lane index {laneIndex} @ seg. {segmentId} to {vehicleTypes.ToString()}"); #endif // save allowed vehicle types into the fast-access array. // (1) ensure that the array is defined and large enough if (laneAllowedVehicleTypesArray[segmentId] == null) { laneAllowedVehicleTypesArray[segmentId] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; } else if (laneAllowedVehicleTypesArray[segmentId].Length < segmentInfo.m_lanes.Length) { var oldArray = laneAllowedVehicleTypesArray[segmentId]; laneAllowedVehicleTypesArray[segmentId] = new ExtVehicleType?[segmentInfo.m_lanes.Length]; Array.Copy(oldArray, laneAllowedVehicleTypesArray[segmentId], oldArray.Length); } // (2) insert the custom speed limit laneAllowedVehicleTypesArray[segmentId][laneIndex] = vehicleTypes; } public static void resetSegmentVehicleRestrictions(ushort segmentId) { if (segmentId <= 0) return; #if DEBUGFLAGS Log._Debug($"Flags.resetSegmentVehicleRestrictions: Resetting vehicle restrictions of segment {segmentId}."); #endif laneAllowedVehicleTypesArray[segmentId] = null; } public static void resetSegmentArrowFlags(ushort segmentId) { if (segmentId <= 0) return; #if DEBUGFLAGS Log._Debug($"Flags.resetSegmentArrowFlags: Resetting lane arrows of segment {segmentId}."); #endif NetManager netManager = Singleton.instance; NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; int numLanes = segmentInfo.m_lanes.Length; int laneIndex = 0; while (laneIndex < numLanes && curLaneId != 0u) { #if DEBUGFLAGS Log._Debug($"Flags.resetSegmentArrowFlags: Resetting lane arrows of segment {segmentId}: Resetting lane {curLaneId}."); #endif laneArrowFlags[curLaneId] = null; curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; ++laneIndex; } } public static bool setLaneArrowFlags(uint laneId, LaneArrows flags, bool overrideHighwayArrows=false) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}) called"); #endif if (!mayHaveLaneArrows(laneId)) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): lane must not have lane arrows"); #endif removeLaneArrowFlags(laneId); return false; } if (!overrideHighwayArrows && highwayLaneArrowFlags[laneId] != null) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): highway arrows may not be overridden"); #endif return false; // disallow custom lane arrows in highway rule mode } if (overrideHighwayArrows) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): overriding highway arrows"); #endif highwayLaneArrowFlags[laneId] = null; } #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): setting flags"); #endif laneArrowFlags[laneId] = flags; return applyLaneArrowFlags(laneId, false); } public static void setHighwayLaneArrowFlags(uint laneId, LaneArrows flags, bool check=true) { if (check && !mayHaveLaneArrows(laneId)) { removeLaneArrowFlags(laneId); return; } highwayLaneArrowFlags[laneId] = flags; #if DEBUGFLAGS Log._Debug($"Flags.setHighwayLaneArrowFlags: Setting highway arrows of lane {laneId} to {flags}"); #endif applyLaneArrowFlags(laneId, false); } public static bool toggleLaneArrowFlags(uint laneId, bool startNode, LaneArrows flags, out LaneArrowChangeResult res) { if (!mayHaveLaneArrows(laneId)) { removeLaneArrowFlags(laneId); res = LaneArrowChangeResult.Invalid; return false; } if (highwayLaneArrowFlags[laneId] != null) { res = LaneArrowChangeResult.HighwayArrows; return false; // disallow custom lane arrows in highway rule mode } if (LaneConnectionManager.Instance.HasConnections(laneId, startNode)) { // TODO refactor res = LaneArrowChangeResult.LaneConnection; return false; // custom lane connection present } LaneArrows? arrows = laneArrowFlags[laneId]; if (arrows == null) { // read currently defined arrows uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; laneFlags &= lfr; // filter arrows arrows = (LaneArrows)laneFlags; } arrows ^= flags; laneArrowFlags[laneId] = arrows; if (applyLaneArrowFlags(laneId, false)) { res = LaneArrowChangeResult.Success; return true; } else { res = LaneArrowChangeResult.Invalid; return false; } } internal static bool mayHaveLaneArrows(uint laneId, bool? startNode=null) { if (laneId <= 0) return false; NetManager netManager = Singleton.instance; if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) return false; ushort segmentId = netManager.m_lanes.m_buffer[laneId].m_segment; var dir = NetInfo.Direction.Forward; var dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection(dir); NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; int numLanes = segmentInfo.m_lanes.Length; int laneIndex = 0; int wIter = 0; while (laneIndex < numLanes && curLaneId != 0u) { ++wIter; if (wIter >= 100) { Log.Error("Too many iterations in Flags.mayHaveLaneArrows!"); break; } if (curLaneId == laneId) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; bool isStartNode = (laneInfo.m_finalDirection & dir2) == NetInfo.Direction.None; if (startNode != null && isStartNode != startNode) return false; ushort nodeId = isStartNode ? netManager.m_segments.m_buffer[segmentId].m_startNode : netManager.m_segments.m_buffer[segmentId].m_endNode; if ((netManager.m_nodes.m_buffer[nodeId].m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) != NetNode.Flags.Created) return false; return (netManager.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; } curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; ++laneIndex; } return false; } public static ushort? getLaneSpeedLimit(uint laneId) { try { Monitor.Enter(laneSpeedLimitLock); ushort speedLimit; if (laneId <= 0 || !laneSpeedLimit.TryGetValue(laneId, out speedLimit)) { return null; } return speedLimit; } finally { Monitor.Exit(laneSpeedLimitLock); } } internal static IDictionary getAllLaneSpeedLimits() { IDictionary ret = new Dictionary(); try { Monitor.Enter(laneSpeedLimitLock); ret = new Dictionary(laneSpeedLimit); } finally { Monitor.Exit(laneSpeedLimitLock); } return ret; } internal static IDictionary getAllLaneAllowedVehicleTypes() { IDictionary ret = new Dictionary(); for (uint segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { Constants.ServiceFactory.NetService.ProcessSegment((ushort)segmentId, delegate (ushort segId, ref NetSegment segment) { if ((segment.m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) return true; ExtVehicleType?[] allowedTypes = laneAllowedVehicleTypesArray[segId]; if (allowedTypes == null) { return true; } Constants.ServiceFactory.NetService.IterateSegmentLanes(segId, ref segment, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort sId, ref NetSegment seg, byte laneIndex) { if (laneInfo.m_vehicleType == VehicleInfo.VehicleType.None) { return true; } if (laneIndex >= allowedTypes.Length) { return true; } ExtVehicleType? allowedType = allowedTypes[laneIndex]; if (allowedType == null) { return true; } ret.Add(laneId, (ExtVehicleType)allowedType); return true; }); return true; }); } return ret; } public static LaneArrows? getLaneArrowFlags(uint laneId) { return laneArrowFlags[laneId]; } public static LaneArrows? getHighwayLaneArrowFlags(uint laneId) { return highwayLaneArrowFlags[laneId]; } public static void removeHighwayLaneArrowFlags(uint laneId) { #if DEBUGFLAGS Log._Debug($"Flags.removeHighwayLaneArrowFlags: Removing highway arrows of lane {laneId}"); #endif if (highwayLaneArrowFlags[laneId] != null) { highwayLaneArrowFlags[laneId] = null; applyLaneArrowFlags(laneId, false); } } public static void applyAllFlags() { for (uint i = 0; i < laneArrowFlags.Length; ++i) { applyLaneArrowFlags(i); } } public static bool applyLaneArrowFlags(uint laneId, bool check=true) { #if DEBUGFLAGS Log._Debug($"Flags.applyLaneArrowFlags({laneId}, {check}) called"); #endif if (laneId <= 0) return true; if (check && !mayHaveLaneArrows(laneId)) { removeLaneArrowFlags(laneId); return false; } LaneArrows? hwArrows = highwayLaneArrowFlags[laneId]; LaneArrows? arrows = laneArrowFlags[laneId]; uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; if (hwArrows != null) { laneFlags &= ~lfr; // remove all arrows laneFlags |= (uint)hwArrows; // add highway arrows } else if (arrows != null) { LaneArrows flags = (LaneArrows)arrows; laneFlags &= ~lfr; // remove all arrows laneFlags |= (uint)flags; // add desired arrows } #if DEBUGFLAGS Log._Debug($"Flags.applyLaneArrowFlags: Setting lane flags of lane {laneId} to {((NetLane.Flags)laneFlags).ToString()}"); #endif Singleton.instance.m_lanes.m_buffer[laneId].m_flags = Convert.ToUInt16(laneFlags); return true; } public static LaneArrows getFinalLaneArrowFlags(uint laneId, bool check=true) { if (! mayHaveLaneArrows(laneId)) { #if DEBUGFLAGS Log._Debug($"Lane {laneId} may not have lane arrows"); #endif return LaneArrows.None; } uint ret = 0; LaneArrows? hwArrows = highwayLaneArrowFlags[laneId]; LaneArrows? arrows = laneArrowFlags[laneId]; if (hwArrows != null) { ret &= ~lfr; // remove all arrows ret |= (uint)hwArrows; // add highway arrows } else if (arrows != null) { LaneArrows flags = (LaneArrows)arrows; ret &= ~lfr; // remove all arrows ret |= (uint)flags; // add desired arrows } else { Constants.ServiceFactory.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { ret = lane.m_flags; ret &= (uint)LaneArrows.LeftForwardRight; return true; }); } return (LaneArrows)ret; } public static void removeLaneArrowFlags(uint laneId) { if (laneId <= 0) return; if (highwayLaneArrowFlags[laneId] != null) return; // modification of arrows in highway rule mode is forbidden laneArrowFlags[laneId] = null; uint laneFlags = (uint)Singleton.instance.m_lanes.m_buffer[laneId].m_flags; if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags & (NetLane.Flags.Created | NetLane.Flags.Deleted)) == NetLane.Flags.Created) { Singleton.instance.m_lanes.m_buffer[laneId].m_flags &= (ushort)~lfr; } } internal static void removeHighwayLaneArrowFlagsAtSegment(ushort segmentId) { if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & (NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) return; int i = 0; uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; while (i < Singleton.instance.m_segments.m_buffer[segmentId].Info.m_lanes.Length && curLaneId != 0u) { Flags.removeHighwayLaneArrowFlags(curLaneId); curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; ++i; } // foreach lane } public static void clearHighwayLaneArrows() { for (uint i = 0; i < Singleton.instance.m_lanes.m_size; ++i) { highwayLaneArrowFlags[i] = null; } } public static void resetSpeedLimits() { try { Monitor.Enter(laneSpeedLimitLock); laneSpeedLimit.Clear(); for (int i = 0; i < Singleton.instance.m_segments.m_size; ++i) { laneSpeedLimitArray[i] = null; } } finally { Monitor.Exit(laneSpeedLimitLock); } } internal static void OnLevelUnloading() { for (uint i = 0; i < laneConnections.Length; ++i) { laneConnections[i] = null; } for (uint i = 0; i < laneSpeedLimitArray.Length; ++i) { laneSpeedLimitArray[i] = null; } try { Monitor.Enter(laneSpeedLimitLock); laneSpeedLimit.Clear(); } finally { Monitor.Exit(laneSpeedLimitLock); } for (uint i = 0; i < laneAllowedVehicleTypesArray.Length; ++i) { laneAllowedVehicleTypesArray[i] = null; } for (uint i = 0; i < laneArrowFlags.Length; ++i) { laneArrowFlags[i] = null; } for (uint i = 0; i < highwayLaneArrowFlags.Length; ++i) { highwayLaneArrowFlags[i] = null; } } static Flags() { laneConnections = new uint[NetManager.MAX_LANE_COUNT][][]; laneSpeedLimitArray = new ushort?[NetManager.MAX_SEGMENT_COUNT][]; laneSpeedLimit = new Dictionary(); laneAllowedVehicleTypesArray = new ExtVehicleType?[NetManager.MAX_SEGMENT_COUNT][]; laneArrowFlags = new LaneArrows?[NetManager.MAX_LANE_COUNT]; highwayLaneArrowFlags = new LaneArrows?[NetManager.MAX_LANE_COUNT]; } public static void OnBeforeLoadData() { } } } ================================================ FILE: TLM/TLM/State/GlobalConfig.cs ================================================ #define RUSHHOUR using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Xml; using System.Xml.Serialization; using TrafficManager.Manager; using TrafficManager.State.ConfigData; using TrafficManager.Traffic; using TrafficManager.TrafficLight; using TrafficManager.Util; namespace TrafficManager.State { [XmlRootAttribute("GlobalConfig", Namespace = "http://www.viathinksoft.de/tmpe", IsNullable = false)] public class GlobalConfig : GenericObservable { public const string FILENAME = "TMPE_GlobalConfig.xml"; public const string BACKUP_FILENAME = FILENAME + ".bak"; private static int LATEST_VERSION = 17; #if DEBUG private static uint lastModificationCheckFrame = 0; #endif public static int? RushHourParkingSearchRadius { get; private set; } = null; #if RUSHHOUR private static DateTime? rushHourConfigModifiedTime = null; private const string RUSHHOUR_CONFIG_FILENAME = "RushHourOptions.xml"; #endif public static GlobalConfig Instance { get { return instance; } private set { if (value != null && instance != null) { value.Observers = instance.Observers; value.ObserverLock = instance.ObserverLock; } instance = value; if (instance != null) { instance.NotifyObservers(instance); } } } private static GlobalConfig instance = null; //private object ObserverLock = new object(); /// /// Holds a list of observers which are being notified as soon as the configuration is updated /// //private List> observers = new List>(); static GlobalConfig() { Reload(); } internal static void OnLevelUnloading() { #if RUSHHOUR rushHourConfigModifiedTime = null; RushHourParkingSearchRadius = null; #endif } private static DateTime ModifiedTime = DateTime.MinValue; /// /// Configuration version /// public int Version = LATEST_VERSION; /// /// Language to use (if null then the game's language is being used) /// public string LanguageCode = null; #if DEBUG public Debug Debug = new Debug(); #endif public AdvancedVehicleAI AdvancedVehicleAI = new AdvancedVehicleAI(); public DynamicLaneSelection DynamicLaneSelection = new DynamicLaneSelection(); public Main Main = new Main(); public ParkingAI ParkingAI = new ParkingAI(); public PathFinding PathFinding = new PathFinding(); public PriorityRules PriorityRules = new PriorityRules(); public TimedTrafficLights TimedTrafficLights = new TimedTrafficLights(); internal static void WriteConfig() { ModifiedTime = WriteConfig(Instance); } private static GlobalConfig WriteDefaultConfig(GlobalConfig oldConfig, bool resetAll, out DateTime modifiedTime) { Log._Debug($"Writing default config..."); GlobalConfig conf = new GlobalConfig(); if (!resetAll && oldConfig != null) { conf.Main.MainMenuButtonX = oldConfig.Main.MainMenuButtonX; conf.Main.MainMenuButtonY = oldConfig.Main.MainMenuButtonY; conf.Main.MainMenuX = oldConfig.Main.MainMenuX; conf.Main.MainMenuY = oldConfig.Main.MainMenuY; conf.Main.MainMenuButtonPosLocked = oldConfig.Main.MainMenuButtonPosLocked; conf.Main.MainMenuPosLocked = oldConfig.Main.MainMenuPosLocked; conf.Main.GuiTransparency = oldConfig.Main.GuiTransparency; conf.Main.OverlayTransparency = oldConfig.Main.OverlayTransparency; conf.Main.TinyMainMenu = oldConfig.Main.TinyMainMenu; conf.Main.EnableTutorial = oldConfig.Main.EnableTutorial; conf.Main.DisplayedTutorialMessages = oldConfig.Main.DisplayedTutorialMessages; } modifiedTime = WriteConfig(conf); return conf; } private static DateTime WriteConfig(GlobalConfig config, string filename=FILENAME) { try { Log.Info($"Writing global config to file '{filename}'..."); XmlSerializer serializer = new XmlSerializer(typeof(GlobalConfig)); using (TextWriter writer = new StreamWriter(filename)) { serializer.Serialize(writer, config); } } catch (Exception e) { Log.Error($"Could not write global config: {e.ToString()}"); } try { return File.GetLastWriteTime(FILENAME); } catch (Exception e) { Log.Warning($"Could not determine modification date of global config: {e.ToString()}"); return DateTime.Now; } } public static GlobalConfig Load(out DateTime modifiedTime) { try { modifiedTime = File.GetLastWriteTime(FILENAME); Log.Info($"Loading global config from file '{FILENAME}'..."); using (FileStream fs = new FileStream(FILENAME, FileMode.Open)) { XmlSerializer serializer = new XmlSerializer(typeof(GlobalConfig)); Log.Info($"Global config loaded."); GlobalConfig conf = (GlobalConfig)serializer.Deserialize(fs); if (LoadingExtension.IsGameLoaded #if DEBUG && !conf.Debug.Switches[10] #endif ) { Constants.ManagerFactory.RoutingManager.RequestFullRecalculation(); } #if DEBUG if (conf.Debug == null) { conf.Debug = new Debug(); } #endif if (conf.AdvancedVehicleAI == null) { conf.AdvancedVehicleAI = new AdvancedVehicleAI(); } if (conf.DynamicLaneSelection == null) { conf.DynamicLaneSelection = new DynamicLaneSelection(); } if (conf.ParkingAI == null) { conf.ParkingAI = new ParkingAI(); } if (conf.PathFinding == null) { conf.PathFinding = new PathFinding(); } if (conf.PriorityRules == null) { conf.PriorityRules = new PriorityRules(); } if (conf.TimedTrafficLights == null) { conf.TimedTrafficLights = new TimedTrafficLights(); } return conf; } } catch (Exception e) { Log.Warning($"Could not load global config: {e} Generating default config."); return WriteDefaultConfig(null, false, out modifiedTime); } } public static void Reload(bool checkVersion=true) { DateTime modifiedTime; GlobalConfig conf = Load(out modifiedTime); if (checkVersion && conf.Version != -1 && conf.Version < LATEST_VERSION) { // backup old config and reset string filename = BACKUP_FILENAME; try { int backupIndex = 0; while (File.Exists(filename)) { filename = BACKUP_FILENAME + "." + backupIndex; ++backupIndex; } WriteConfig(conf, filename); } catch (Exception e) { Log.Warning($"Error occurred while saving backup config to '{filename}': {e.ToString()}"); } Reset(conf); } else { Instance = conf; ModifiedTime = WriteConfig(Instance); } } public static void Reset(GlobalConfig oldConfig, bool resetAll=false) { Log.Info($"Resetting global config."); DateTime modifiedTime; Instance = WriteDefaultConfig(oldConfig, resetAll, out modifiedTime); ModifiedTime = modifiedTime; } private static void ReloadIfNewer() { try { DateTime modifiedTime = File.GetLastWriteTime(FILENAME); if (modifiedTime > ModifiedTime) { Log.Info($"Detected modification of global config."); Reload(false); } } catch (Exception) { Log.Warning("Could not determine modification date of global config."); } } #if RUSHHOUR private static void ReloadRushHourConfigIfNewer() { // TODO refactor try { DateTime newModifiedTime = File.GetLastWriteTime(RUSHHOUR_CONFIG_FILENAME); if (rushHourConfigModifiedTime != null) { if (newModifiedTime <= rushHourConfigModifiedTime) return; } rushHourConfigModifiedTime = newModifiedTime; XmlDocument doc = new XmlDocument(); doc.Load(RUSHHOUR_CONFIG_FILENAME); XmlNode root = doc.DocumentElement; XmlNode betterParkingNode = root.SelectSingleNode("OptionPanel/data/BetterParking"); XmlNode parkingSpaceRadiusNode = root.SelectSingleNode("OptionPanel/data/ParkingSearchRadius"); if ("True".Equals(betterParkingNode.InnerText)) { RushHourParkingSearchRadius = int.Parse(parkingSpaceRadiusNode.InnerText); } Log._Debug($"RushHour config has changed. Setting searchRadius={RushHourParkingSearchRadius}"); } catch (Exception ex) { Log.Error("GlobalConfig.ReloadRushHourConfigIfNewer: " + ex.ToString()); } } #endif public void SimulationStep() { #if RUSHHOUR if (LoadingExtension.IsRushHourLoaded) { ReloadRushHourConfigIfNewer(); } #endif } /*public IDisposable Subscribe(IObserver observer) { try { Monitor.Enter(ObserverLock); Log.Info($"Adding {observer} as observer of global config"); observers.Add(observer); } finally { Monitor.Exit(ObserverLock); } return new GenericUnsubscriber(observers, observer, ObserverLock); }*/ /*protected void NotifyObservers() { //Log.Warning($"NodeGeometry.NotifyObservers(): CurrentSegmentReplacement={CurrentSegmentReplacement}"); List> myObservers = new List>(observers); // in case somebody unsubscribes while iterating over subscribers foreach (IObserver observer in myObservers) { try { Log.Info($"Notifying global config observer {observer}"); observer.OnUpdate(this); } catch (Exception e) { Log.Error($"GlobalConfig.NotifyObservers: An exception occured while notifying an observer of global config: {e}"); } } }*/ } } ================================================ FILE: TLM/TLM/State/Options.cs ================================================ using System; using System.Collections.Generic; using System.Text; using UnityEngine; using ColossalFramework; using ColossalFramework.UI; using ICities; using TrafficManager.Geometry; using TrafficManager.State; using TrafficManager.UI; using ColossalFramework.Plugins; using ColossalFramework.Globalization; using TrafficManager.Manager; using CSUtil.Commons; using System.Reflection; using TrafficManager.Manager.Impl; namespace TrafficManager.State { public class Options : MonoBehaviour { private static UIDropDown languageDropdown = null; private static UIDropDown simAccuracyDropdown = null; //private static UIDropDown laneChangingRandomizationDropdown = null; private static UICheckBox instantEffectsToggle = null; private static UICheckBox lockButtonToggle = null; private static UICheckBox lockMenuToggle = null; private static UISlider guiTransparencySlider = null; private static UISlider overlayTransparencySlider = null; private static UICheckBox tinyMenuToggle = null; private static UICheckBox enableTutorialToggle = null; private static UICheckBox showCompatibilityCheckErrorToggle = null; private static UICheckBox realisticSpeedsToggle = null; private static UIDropDown recklessDriversDropdown = null; private static UICheckBox relaxedBussesToggle = null; private static UICheckBox allRelaxedToggle = null; private static UICheckBox evacBussesMayIgnoreRulesToggle = null; private static UICheckBox prioritySignsOverlayToggle = null; private static UICheckBox timedLightsOverlayToggle = null; private static UICheckBox speedLimitsOverlayToggle = null; private static UICheckBox vehicleRestrictionsOverlayToggle = null; private static UICheckBox parkingRestrictionsOverlayToggle = null; private static UICheckBox junctionRestrictionsOverlayToggle = null; private static UICheckBox connectedLanesOverlayToggle = null; private static UICheckBox nodesOverlayToggle = null; private static UICheckBox vehicleOverlayToggle = null; #if DEBUG private static UICheckBox citizenOverlayToggle = null; private static UICheckBox buildingOverlayToggle = null; #endif private static UICheckBox allowEnterBlockedJunctionsToggle = null; private static UICheckBox allowUTurnsToggle = null; private static UICheckBox allowNearTurnOnRedToggle = null; private static UICheckBox allowFarTurnOnRedToggle = null; private static UICheckBox allowLaneChangesWhileGoingStraightToggle = null; private static UICheckBox trafficLightPriorityRulesToggle = null; private static UIDropDown vehicleRestrictionsAggressionDropdown = null; private static UICheckBox banRegularTrafficOnBusLanesToggle = null; private static UICheckBox disableDespawningToggle = null; private static UICheckBox strongerRoadConditionEffectsToggle = null; private static UICheckBox prohibitPocketCarsToggle = null; private static UICheckBox advancedAIToggle = null; private static UICheckBox realisticPublicTransportToggle = null; private static UISlider altLaneSelectionRatioSlider = null; private static UICheckBox highwayRulesToggle = null; private static UICheckBox preferOuterLaneToggle = null; private static UICheckBox showLanesToggle = null; #if QUEUEDSTATS private static UICheckBox showPathFindStatsToggle = null; #endif private static UIButton resetStuckEntitiesBtn = null; private static UICheckBox enablePrioritySignsToggle = null; private static UICheckBox enableTimedLightsToggle = null; private static UICheckBox enableCustomSpeedLimitsToggle = null; private static UICheckBox enableVehicleRestrictionsToggle = null; private static UICheckBox enableParkingRestrictionsToggle = null; private static UICheckBox enableJunctionRestrictionsToggle = null; private static UICheckBox turnOnRedEnabledToggle = null; private static UICheckBox enableLaneConnectorToggle = null; private static UIButton removeParkedVehiclesBtn = null; #if DEBUG private static UIButton resetSpeedLimitsBtn = null; private static List debugSwitchFields = new List(); private static List debugValueFields = new List(); private static UITextField pathCostMultiplicatorField = null; private static UITextField pathCostMultiplicator2Field = null; #endif private static UIButton reloadGlobalConfBtn = null; private static UIButton resetGlobalConfBtn = null; public static bool instantEffects = true; public static int simAccuracy = 0; //public static int laneChangingRandomization = 2; public static bool realisticSpeeds = true; public static int recklessDrivers = 3; public static bool relaxedBusses = false; public static bool allRelaxed = false; public static bool evacBussesMayIgnoreRules = false; public static bool prioritySignsOverlay = false; public static bool timedLightsOverlay = false; public static bool speedLimitsOverlay = false; public static bool vehicleRestrictionsOverlay = false; public static bool parkingRestrictionsOverlay = false; public static bool junctionRestrictionsOverlay = false; public static bool connectedLanesOverlay = false; #if QUEUEDSTATS public static bool showPathFindStats = #if DEBUG true; #else false; #endif #endif #if DEBUG public static bool nodesOverlay = false; public static bool vehicleOverlay = false; public static bool citizenOverlay = false; public static bool buildingOverlay = false; #else public static bool nodesOverlay = false; public static bool vehicleOverlay = false; public static bool citizenOverlay = false; public static bool buildingOverlay = false; #endif public static bool allowEnterBlockedJunctions = false; public static bool allowUTurns = false; public static bool allowNearTurnOnRed = false; public static bool allowFarTurnOnRed = false; public static bool allowLaneChangesWhileGoingStraight = false; public static bool trafficLightPriorityRules = false; public static bool banRegularTrafficOnBusLanes = false; public static bool advancedAI = false; public static bool realisticPublicTransport = false; public static byte altLaneSelectionRatio = 0; public static bool highwayRules = false; #if DEBUG public static bool showLanes = true; #else public static bool showLanes = false; #endif public static bool strongerRoadConditionEffects = false; public static bool prohibitPocketCars = false; public static bool disableDespawning = false; public static bool preferOuterLane = false; //public static byte publicTransportUsage = 1; public static bool prioritySignsEnabled = true; public static bool timedLightsEnabled = true; public static bool customSpeedLimitsEnabled = true; public static bool vehicleRestrictionsEnabled = true; public static bool parkingRestrictionsEnabled = true; public static bool junctionRestrictionsEnabled = true; public static bool turnOnRedEnabled = true; public static bool laneConnectorEnabled = true; public static VehicleRestrictionsAggression vehicleRestrictionsAggression = VehicleRestrictionsAggression.Medium; public static bool MenuRebuildRequired { get { return false; } internal set { if (value) { if (LoadingExtension.BaseUI != null) { LoadingExtension.BaseUI.RebuildMenu(); } } } } public static void makeSettings(UIHelperBase helper) { // tabbing code is borrowed from RushHour mod // https://github.com/PropaneDragon/RushHour/blob/release/RushHour/Options/OptionHandler.cs UIHelper actualHelper = helper as UIHelper; UIComponent container = actualHelper.self as UIComponent; UITabstrip tabStrip = container.AddUIComponent(); tabStrip.relativePosition = new Vector3(0, 0); tabStrip.size = new Vector2(container.width - 20, 40); UITabContainer tabContainer = container.AddUIComponent(); tabContainer.relativePosition = new Vector3(0, 40); tabContainer.size = new Vector2(container.width - 20, container.height - tabStrip.height - 20); tabStrip.tabPages = tabContainer; int tabIndex = 0; // GENERAL AddOptionTab(tabStrip, Translation.GetString("General"));// tabStrip.AddTab(Translation.GetString("General"), tabTemplate, true); tabStrip.selectedIndex = tabIndex; UIPanel currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; currentPanel.autoLayout = true; currentPanel.autoLayoutDirection = LayoutDirection.Vertical; currentPanel.autoLayoutPadding.top = 5; currentPanel.autoLayoutPadding.left = 10; currentPanel.autoLayoutPadding.right = 10; UIHelper panelHelper = new UIHelper(currentPanel); var generalGroup = panelHelper.AddGroup(Translation.GetString("General")); string[] languageLabels = new string[Translation.AVAILABLE_LANGUAGE_CODES.Count + 1]; languageLabels[0] = Translation.GetString("Game_language"); for (int i = 0; i < Translation.AVAILABLE_LANGUAGE_CODES.Count; ++i) { languageLabels[i + 1] = Translation.LANGUAGE_LABELS[Translation.AVAILABLE_LANGUAGE_CODES[i]]; } int languageIndex = 0; string curLangCode = GlobalConfig.Instance.LanguageCode; if (curLangCode != null) { languageIndex = Translation.AVAILABLE_LANGUAGE_CODES.IndexOf(curLangCode); if (languageIndex < 0) { languageIndex = 0; } else { ++languageIndex; } } languageDropdown = generalGroup.AddDropdown(Translation.GetString("Language") + ":", languageLabels, languageIndex, onLanguageChanged) as UIDropDown; lockButtonToggle = generalGroup.AddCheckbox(Translation.GetString("Lock_main_menu_button_position"), GlobalConfig.Instance.Main.MainMenuButtonPosLocked, onLockButtonChanged) as UICheckBox; lockMenuToggle = generalGroup.AddCheckbox(Translation.GetString("Lock_main_menu_position"), GlobalConfig.Instance.Main.MainMenuPosLocked, onLockMenuChanged) as UICheckBox; tinyMenuToggle = generalGroup.AddCheckbox(Translation.GetString("Compact_main_menu"), GlobalConfig.Instance.Main.TinyMainMenu, onTinyMenuChanged) as UICheckBox; guiTransparencySlider = generalGroup.AddSlider(Translation.GetString("Window_transparency") + ":", 0, 90, 5, GlobalConfig.Instance.Main.GuiTransparency, onGuiTransparencyChanged) as UISlider; guiTransparencySlider.parent.Find("Label").width = 500; overlayTransparencySlider = generalGroup.AddSlider(Translation.GetString("Overlay_transparency") + ":", 0, 90, 5, GlobalConfig.Instance.Main.OverlayTransparency, onOverlayTransparencyChanged) as UISlider; overlayTransparencySlider.parent.Find("Label").width = 500; enableTutorialToggle = generalGroup.AddCheckbox(Translation.GetString("Enable_tutorial_messages"), GlobalConfig.Instance.Main.EnableTutorial, onEnableTutorialsChanged) as UICheckBox; showCompatibilityCheckErrorToggle = generalGroup.AddCheckbox(Translation.GetString("Show_error_message_if_a_mod_incompatibility_is_detected"), GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage, onShowCompatibilityCheckErrorChanged) as UICheckBox; var simGroup = panelHelper.AddGroup(Translation.GetString("Simulation")); simAccuracyDropdown = simGroup.AddDropdown(Translation.GetString("Simulation_accuracy") + ":", new string[] { Translation.GetString("Very_high"), Translation.GetString("High"), Translation.GetString("Medium"), Translation.GetString("Low"), Translation.GetString("Very_Low") }, simAccuracy, onSimAccuracyChanged) as UIDropDown; instantEffectsToggle = simGroup.AddCheckbox(Translation.GetString("Customizations_come_into_effect_instantaneously"), instantEffects, onInstantEffectsChanged) as UICheckBox; // GAMEPLAY ++tabIndex; AddOptionTab(tabStrip, Translation.GetString("Gameplay")); tabStrip.selectedIndex = tabIndex; currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; currentPanel.autoLayout = true; currentPanel.autoLayoutDirection = LayoutDirection.Vertical; currentPanel.autoLayoutPadding.top = 5; currentPanel.autoLayoutPadding.left = 10; currentPanel.autoLayoutPadding.right = 10; panelHelper = new UIHelper(currentPanel); var vehBehaviorGroup = panelHelper.AddGroup(Translation.GetString("Vehicle_behavior")); recklessDriversDropdown = vehBehaviorGroup.AddDropdown(Translation.GetString("Reckless_driving") + ":", new string[] { Translation.GetString("Path_Of_Evil_(10_%)"), Translation.GetString("Rush_Hour_(5_%)"), Translation.GetString("Minor_Complaints_(2_%)"), Translation.GetString("Holy_City_(0_%)") }, recklessDrivers, onRecklessDriversChanged) as UIDropDown; recklessDriversDropdown.width = 300; realisticSpeedsToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Realistic_speeds"), realisticSpeeds, onRealisticSpeedsChanged) as UICheckBox; if (SteamHelper.IsDLCOwned(SteamHelper.DLC.SnowFallDLC)) { strongerRoadConditionEffectsToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Road_condition_has_a_bigger_impact_on_vehicle_speed"), strongerRoadConditionEffects, onStrongerRoadConditionEffectsChanged) as UICheckBox; } disableDespawningToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Disable_despawning"), disableDespawning, onDisableDespawningChanged) as UICheckBox; var vehAiGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI")); advancedAIToggle = vehAiGroup.AddCheckbox(Translation.GetString("Enable_Advanced_Vehicle_AI"), advancedAI, onAdvancedAIChanged) as UICheckBox; altLaneSelectionRatioSlider = vehAiGroup.AddSlider(Translation.GetString("Dynamic_lane_section") + ":", 0, 100, 5, altLaneSelectionRatio, onAltLaneSelectionRatioChanged) as UISlider; altLaneSelectionRatioSlider.parent.Find("Label").width = 450; var parkAiGroup = panelHelper.AddGroup(Translation.GetString("Parking_AI")); prohibitPocketCarsToggle = parkAiGroup.AddCheckbox(Translation.GetString("Enable_more_realistic_parking"), prohibitPocketCars, onProhibitPocketCarsChanged) as UICheckBox; var ptGroup = panelHelper.AddGroup(Translation.GetString("Public_transport")); realisticPublicTransportToggle = ptGroup.AddCheckbox(Translation.GetString("Prevent_excessive_transfers_at_public_transport_stations"), realisticPublicTransport, onRealisticPublicTransportChanged) as UICheckBox; // VEHICLE RESTRICTIONS ++tabIndex; AddOptionTab(tabStrip, Translation.GetString("Policies_&_Restrictions")); tabStrip.selectedIndex = tabIndex; currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; currentPanel.autoLayout = true; currentPanel.autoLayoutDirection = LayoutDirection.Vertical; currentPanel.autoLayoutPadding.top = 5; currentPanel.autoLayoutPadding.left = 10; currentPanel.autoLayoutPadding.right = 10; panelHelper = new UIHelper(currentPanel); var atJunctionsGroup = panelHelper.AddGroup(Translation.GetString("At_junctions")); #if DEBUG allRelaxedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("All_vehicles_may_ignore_lane_arrows"), allRelaxed, onAllRelaxedChanged) as UICheckBox; #endif relaxedBussesToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Busses_may_ignore_lane_arrows"), relaxedBusses, onRelaxedBussesChanged) as UICheckBox; allowEnterBlockedJunctionsToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_enter_blocked_junctions"), allowEnterBlockedJunctions, onAllowEnterBlockedJunctionsChanged) as UICheckBox; allowUTurnsToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_do_u-turns_at_junctions"), allowUTurns, onAllowUTurnsChanged) as UICheckBox; allowNearTurnOnRedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_may_turn_on_red"), allowNearTurnOnRed, onAllowNearTurnOnRedChanged) as UICheckBox; allowFarTurnOnRedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Also_apply_to_left/right_turns_between_one-way_streets"), allowFarTurnOnRed, onAllowFarTurnOnRedChanged) as UICheckBox; allowLaneChangesWhileGoingStraightToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_going_straight_may_change_lanes_at_junctions"), allowLaneChangesWhileGoingStraight, onAllowLaneChangesWhileGoingStraightChanged) as UICheckBox; trafficLightPriorityRulesToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("Vehicles_follow_priority_rules_at_junctions_with_timed_traffic_lights"), trafficLightPriorityRules, onTrafficLightPriorityRulesChanged) as UICheckBox; Indent(allowFarTurnOnRedToggle); var onRoadsGroup = panelHelper.AddGroup(Translation.GetString("On_roads")); vehicleRestrictionsAggressionDropdown = onRoadsGroup.AddDropdown(Translation.GetString("Vehicle_restrictions_aggression") + ":", new string[] { Translation.GetString("Low"), Translation.GetString("Medium"), Translation.GetString("High"), Translation.GetString("Strict") }, (int)vehicleRestrictionsAggression, onVehicleRestrictionsAggressionChanged) as UIDropDown; banRegularTrafficOnBusLanesToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Ban_private_cars_and_trucks_on_bus_lanes"), banRegularTrafficOnBusLanes, onBanRegularTrafficOnBusLanesChanged) as UICheckBox; highwayRulesToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Enable_highway_specific_lane_merging/splitting_rules"), highwayRules, onHighwayRulesChanged) as UICheckBox; preferOuterLaneToggle = onRoadsGroup.AddCheckbox(Translation.GetString("Heavy_trucks_prefer_outer_lanes_on_highways"), preferOuterLane, onPreferOuterLaneChanged) as UICheckBox; if (SteamHelper.IsDLCOwned(SteamHelper.DLC.NaturalDisastersDLC)) { var inCaseOfEmergencyGroup = panelHelper.AddGroup(Translation.GetString("In_case_of_emergency")); evacBussesMayIgnoreRulesToggle = inCaseOfEmergencyGroup.AddCheckbox(Translation.GetString("Evacuation_busses_may_ignore_traffic_rules"), evacBussesMayIgnoreRules, onEvacBussesMayIgnoreRulesChanged) as UICheckBox; } // OVERLAYS ++tabIndex; AddOptionTab(tabStrip, Translation.GetString("Overlays")); tabStrip.selectedIndex = tabIndex; currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; currentPanel.autoLayout = true; currentPanel.autoLayoutDirection = LayoutDirection.Vertical; currentPanel.autoLayoutPadding.top = 5; currentPanel.autoLayoutPadding.left = 10; currentPanel.autoLayoutPadding.right = 10; panelHelper = new UIHelper(currentPanel); prioritySignsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Priority_signs"), prioritySignsOverlay, onPrioritySignsOverlayChanged) as UICheckBox; timedLightsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Timed_traffic_lights"), timedLightsOverlay, onTimedLightsOverlayChanged) as UICheckBox; speedLimitsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Speed_limits"), speedLimitsOverlay, onSpeedLimitsOverlayChanged) as UICheckBox; vehicleRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Vehicle_restrictions"), vehicleRestrictionsOverlay, onVehicleRestrictionsOverlayChanged) as UICheckBox; parkingRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Parking_restrictions"), parkingRestrictionsOverlay, onParkingRestrictionsOverlayChanged) as UICheckBox; junctionRestrictionsOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Junction_restrictions"), junctionRestrictionsOverlay, onJunctionRestrictionsOverlayChanged) as UICheckBox; connectedLanesOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Connected_lanes"), connectedLanesOverlay, onConnectedLanesOverlayChanged) as UICheckBox; nodesOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Nodes_and_segments"), nodesOverlay, onNodesOverlayChanged) as UICheckBox; showLanesToggle = panelHelper.AddCheckbox(Translation.GetString("Lanes"), showLanes, onShowLanesChanged) as UICheckBox; #if DEBUG vehicleOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Vehicles"), vehicleOverlay, onVehicleOverlayChanged) as UICheckBox; citizenOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Citizens"), citizenOverlay, onCitizenOverlayChanged) as UICheckBox; buildingOverlayToggle = panelHelper.AddCheckbox(Translation.GetString("Buildings"), buildingOverlay, onBuildingOverlayChanged) as UICheckBox; #endif // MAINTENANCE ++tabIndex; AddOptionTab(tabStrip, Translation.GetString("Maintenance")); tabStrip.selectedIndex = tabIndex; currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; currentPanel.autoLayout = true; currentPanel.autoLayoutDirection = LayoutDirection.Vertical; currentPanel.autoLayoutPadding.top = 5; currentPanel.autoLayoutPadding.left = 10; currentPanel.autoLayoutPadding.right = 10; panelHelper = new UIHelper(currentPanel); var maintenanceGroup = panelHelper.AddGroup(Translation.GetString("Maintenance")); resetStuckEntitiesBtn = maintenanceGroup.AddButton(Translation.GetString("Reset_stuck_cims_and_vehicles"), onClickResetStuckEntities) as UIButton; removeParkedVehiclesBtn = maintenanceGroup.AddButton(Translation.GetString("Remove_parked_vehicles"), onClickRemoveParkedVehicles) as UIButton; #if DEBUG resetSpeedLimitsBtn = maintenanceGroup.AddButton(Translation.GetString("Reset_custom_speed_limits"), onClickResetSpeedLimits) as UIButton; #endif reloadGlobalConfBtn = maintenanceGroup.AddButton(Translation.GetString("Reload_global_configuration"), onClickReloadGlobalConf) as UIButton; resetGlobalConfBtn = maintenanceGroup.AddButton(Translation.GetString("Reset_global_configuration"), onClickResetGlobalConf) as UIButton; #if QUEUEDSTATS showPathFindStatsToggle = maintenanceGroup.AddCheckbox(Translation.GetString("Show_path-find_stats"), showPathFindStats, onShowPathFindStatsChanged) as UICheckBox; #endif var featureGroup = panelHelper.AddGroup(Translation.GetString("Activated_features")) as UIHelper; enablePrioritySignsToggle = featureGroup.AddCheckbox(Translation.GetString("Priority_signs"), prioritySignsEnabled, onPrioritySignsEnabledChanged) as UICheckBox; enableTimedLightsToggle = featureGroup.AddCheckbox(Translation.GetString("Timed_traffic_lights"), timedLightsEnabled, onTimedLightsEnabledChanged) as UICheckBox; enableCustomSpeedLimitsToggle = featureGroup.AddCheckbox(Translation.GetString("Speed_limits"), customSpeedLimitsEnabled, onCustomSpeedLimitsEnabledChanged) as UICheckBox; enableVehicleRestrictionsToggle = featureGroup.AddCheckbox(Translation.GetString("Vehicle_restrictions"), vehicleRestrictionsEnabled, onVehicleRestrictionsEnabledChanged) as UICheckBox; enableParkingRestrictionsToggle = featureGroup.AddCheckbox(Translation.GetString("Parking_restrictions"), parkingRestrictionsEnabled, onParkingRestrictionsEnabledChanged) as UICheckBox; enableJunctionRestrictionsToggle = featureGroup.AddCheckbox(Translation.GetString("Junction_restrictions"), junctionRestrictionsEnabled, onJunctionRestrictionsEnabledChanged) as UICheckBox; turnOnRedEnabledToggle = featureGroup.AddCheckbox(Translation.GetString("Turn_on_red"), turnOnRedEnabled, onTurnOnRedEnabledChanged) as UICheckBox; enableLaneConnectorToggle = featureGroup.AddCheckbox(Translation.GetString("Lane_connector"), laneConnectorEnabled, onLaneConnectorEnabledChanged) as UICheckBox; Indent(turnOnRedEnabledToggle); #if DEBUG // GLOBAL CONFIG /* AddOptionTab(tabStrip, Translation.GetString("Global_configuration"));// tabStrip.AddTab(Translation.GetString("General"), tabTemplate, true); tabStrip.selectedIndex = tabIndex; currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; currentPanel.autoLayout = true; currentPanel.autoLayoutDirection = LayoutDirection.Vertical; currentPanel.autoLayoutPadding.top = 5; currentPanel.autoLayoutPadding.left = 10; currentPanel.autoLayoutPadding.right = 10; panelHelper = new UIHelper(currentPanel); GlobalConfig globalConf = GlobalConfig.Instance; var aiTrafficMeasurementConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("General")); aiTrafficMeasurementConfGroup.AddSlider(Translation.GetString("Live_traffic_buffer_size"), 0f, 10000f, 100f, globalConf.MaxTrafficBuffer, onMaxTrafficBufferChanged); aiTrafficMeasurementConfGroup.AddSlider(Translation.GetString("Path-find_traffic_buffer_size"), 0f, 10000f, 100f, globalConf.MaxPathFindTrafficBuffer, onMaxPathFindTrafficBufferChanged); aiTrafficMeasurementConfGroup.AddSlider(Translation.GetString("Max._congestion_measurements"), 0f, 1000f, 10f, globalConf.MaxNumCongestionMeasurements, onMaxNumCongestionMeasurementsChanged); var aiLaneSelParamConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_selection_parameters")); aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_density_randomization"), 0f, 100f, 1f, globalConf.LaneDensityRandInterval, onLaneDensityRandIntervalChanged); aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_density_discretization"), 0f, 100f, 1f, globalConf.LaneDensityDiscretization, onLaneTrafficDiscretizationChanged); aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_spread_randomization"), 0f, 100f, 1f, globalConf.LaneUsageRandInterval, onLaneUsageRandIntervalChanged); aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Lane_spread_discretization"), 0f, 100f, 1f, globalConf.LaneUsageDiscretization, onLaneUsageDiscretizationChanged); aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Congestion_rel._velocity_threshold"), 0f, 100f, 1f, globalConf.CongestionSqrSpeedThreshold, onCongestionSqrSpeedThresholdChanged); aiLaneSelParamConfGroup.AddSlider(Translation.GetString("Max._walking_distance"), 0f, 10000f, 100f, globalConf.MaxWalkingDistance, onMaxWalkingDistanceChanged); var aiLaneSelFactorConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_selection_factors")); aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Spread_randomization_factor"), 1f, 5f, 0.05f, globalConf.UsageCostFactor, onUsageCostFactorChanged); aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Traffic_avoidance_factor"), 1f, 5f, 0.05f, globalConf.TrafficCostFactor, onTrafficCostFactorChanged); aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Public_transport_lane_penalty"), 1f, 50f, 0.5f, globalConf.PublicTransportLanePenalty, onPublicTransportLanePenaltyChanged); aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Public_transport_lane_reward"), 0f, 1f, 0.05f, globalConf.PublicTransportLaneReward, onPublicTransportLaneRewardChanged); aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Heavy_vehicle_max._inner_lane_penalty"), 0f, 5f, 0.05f, globalConf.HeavyVehicleMaxInnerLanePenalty, onHeavyVehicleMaxInnerLanePenaltyChanged); aiLaneSelFactorConfGroup.AddSlider(Translation.GetString("Vehicle_restrictions_penalty"), 0f, 1000f, 25f, globalConf.VehicleRestrictionsPenalty, onVehicleRestrictionsPenaltyChanged); var aiLaneChangeParamConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_changing_parameters")); aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("U-turn_lane_distance"), 1f, 5f, 1f, globalConf.UturnLaneDistance, onUturnLaneDistanceChanged); aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Incompatible_lane_distance"), 1f, 5f, 1f, globalConf.IncompatibleLaneDistance, onIncompatibleLaneDistanceChanged); aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Lane_changing_randomization_modulo"), 1f, 100f, 1f, globalConf.RandomizedLaneChangingModulo, onRandomizedLaneChangingModuloChanged); aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Min._controlled_segments_in_front_of_highway_interchanges"), 1f, 10f, 1f, globalConf.MinHighwayInterchangeSegments, onMinHighwayInterchangeSegmentsChanged); aiLaneChangeParamConfGroup.AddSlider(Translation.GetString("Max._controlled_segments_in_front_of_highway_interchanges"), 1f, 30f, 1f, globalConf.MaxHighwayInterchangeSegments, onMaxHighwayInterchangeSegmentsChanged); var aiLaneChangeFactorConfGroup = panelHelper.AddGroup(Translation.GetString("Advanced_Vehicle_AI") + ": " + Translation.GetString("Lane_changing_cost_factors")); aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("On_city_roads"), 1f, 5f, 0.05f, globalConf.CityRoadLaneChangingBaseCost, onCityRoadLaneChangingBaseCostChanged); aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("On_highways"), 1f, 5f, 0.05f, globalConf.HighwayLaneChangingBaseCost, onHighwayLaneChangingBaseCostChanged); aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("In_front_of_highway_interchanges"), 1f, 5f, 0.05f, globalConf.HighwayInterchangeLaneChangingBaseCost, onHighwayInterchangeLaneChangingBaseCostChanged); aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("For_heavy_vehicles"), 1f, 5f, 0.05f, globalConf.HeavyVehicleLaneChangingCostFactor, onHeavyVehicleLaneChangingCostFactorChanged); aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("On_congested_roads"), 1f, 5f, 0.05f, globalConf.CongestionLaneChangingCostFactor, onCongestionLaneChangingCostFactorChanged); aiLaneChangeFactorConfGroup.AddSlider(Translation.GetString("When_changing_multiple_lanes_at_once"), 1f, 5f, 0.05f, globalConf.MoreThanOneLaneChangingCostFactor, onMoreThanOneLaneChangingCostFactorChanged); var aiParkingLaneChangeFactorConfGroup = panelHelper.AddGroup(Translation.GetString("Parking_AI") + ": " + Translation.GetString("General")); */ // DEBUG /*++tabIndex; settingsButton = tabStrip.AddTab("Debug", tabTemplate, true); settingsButton.textPadding = new RectOffset(10, 10, 10, 10); settingsButton.autoSize = true; settingsButton.tooltip = "Debug"; currentPanel = tabStrip.tabContainer.components[tabIndex] as UIPanel; currentPanel.autoLayout = true; currentPanel.autoLayoutDirection = LayoutDirection.Vertical; currentPanel.autoLayoutPadding.top = 5; currentPanel.autoLayoutPadding.left = 10; currentPanel.autoLayoutPadding.right = 10; panelHelper = new UIHelper(currentPanel); debugSwitchFields.Clear(); for (int i = 0; i < Debug.Switches.Length; ++i) { int index = i; string varName = $"Debug switch #{i}"; debugSwitchFields.Add(panelHelper.AddCheckbox(varName, Debug.Switches[i], delegate (bool newVal) { onBoolValueChanged(varName, newVal, ref Debug.Switches[index]); }) as UICheckBox); } debugValueFields.Clear(); for (int i = 0; i < debugValues.Length; ++i) { int index = i; string varName = $"Debug value #{i}"; debugValueFields.Add(panelHelper.AddTextfield(varName, String.Format("{0:0.##}", debugValues[i]), delegate(string newValStr) { onFloatValueChanged(varName, newValStr, ref debugValues[index]); }, null) as UITextField); }*/ #endif tabStrip.selectedIndex = 0; } private static void Indent(T component) where T : UIComponent { UIPanel panel = component.parent as UIPanel; panel.autoLayout = false; component.relativePosition += new Vector3(30, 0); } private static UIButton AddOptionTab(UITabstrip tabStrip, string caption) { UIButton tabButton = tabStrip.AddTab(caption); tabButton.normalBgSprite = "SubBarButtonBase"; tabButton.disabledBgSprite = "SubBarButtonBaseDisabled"; tabButton.focusedBgSprite = "SubBarButtonBaseFocused"; tabButton.hoveredBgSprite = "SubBarButtonBaseHovered"; tabButton.pressedBgSprite = "SubBarButtonBasePressed"; tabButton.textPadding = new RectOffset(10, 10, 10, 10); tabButton.autoSize = true; tabButton.tooltip = caption; return tabButton; } private static bool checkGameLoaded() { if (!SerializableDataExtension.StateLoading && !LoadingExtension.IsGameLoaded) { UIView.library.ShowModal("ExceptionPanel").SetMessage("Nope!", Translation.GetString("Settings_are_defined_for_each_savegame_separately") + ". https://www.viathinksoft.de/tmpe/#options", false); return false; } return true; } private static void onGuiTransparencyChanged(float newVal) { if (!checkGameLoaded()) return; setGuiTransparency((byte)Mathf.RoundToInt(newVal)); guiTransparencySlider.tooltip = Translation.GetString("Window_transparency") + ": " + GlobalConfig.Instance.Main.GuiTransparency + " %"; GlobalConfig.WriteConfig(); Log._Debug($"GuiTransparency changed to {GlobalConfig.Instance.Main.GuiTransparency}"); } private static void onOverlayTransparencyChanged(float newVal) { if (!checkGameLoaded()) return; setOverlayTransparency((byte)Mathf.RoundToInt(newVal)); overlayTransparencySlider.tooltip = Translation.GetString("Overlay_transparency") + ": " + GlobalConfig.Instance.Main.OverlayTransparency + " %"; GlobalConfig.WriteConfig(); Log._Debug($"OverlayTransparency changed to {GlobalConfig.Instance.Main.OverlayTransparency}"); } private static void onAltLaneSelectionRatioChanged(float newVal) { if (!checkGameLoaded()) return; setAltLaneSelectionRatio((byte)Mathf.RoundToInt(newVal)); altLaneSelectionRatioSlider.tooltip = Translation.GetString("Percentage_of_vehicles_performing_dynamic_lane_section") + ": " + altLaneSelectionRatio + " %"; Log._Debug($"altLaneSelectionRatio changed to {altLaneSelectionRatio}"); } private static void onPrioritySignsOverlayChanged(bool newPrioritySignsOverlay) { if (!checkGameLoaded()) return; Log._Debug($"prioritySignsOverlay changed to {newPrioritySignsOverlay}"); prioritySignsOverlay = newPrioritySignsOverlay; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } private static void onTimedLightsOverlayChanged(bool newTimedLightsOverlay) { if (!checkGameLoaded()) return; Log._Debug($"timedLightsOverlay changed to {newTimedLightsOverlay}"); timedLightsOverlay = newTimedLightsOverlay; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } private static void onSpeedLimitsOverlayChanged(bool newSpeedLimitsOverlay) { if (!checkGameLoaded()) return; Log._Debug($"speedLimitsOverlay changed to {newSpeedLimitsOverlay}"); speedLimitsOverlay = newSpeedLimitsOverlay; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } private static void onVehicleRestrictionsOverlayChanged(bool newVehicleRestrictionsOverlay) { if (!checkGameLoaded()) return; Log._Debug($"vehicleRestrictionsOverlay changed to {newVehicleRestrictionsOverlay}"); vehicleRestrictionsOverlay = newVehicleRestrictionsOverlay; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } private static void onParkingRestrictionsOverlayChanged(bool newParkingRestrictionsOverlay) { if (!checkGameLoaded()) return; Log._Debug($"parkingRestrictionsOverlay changed to {newParkingRestrictionsOverlay}"); parkingRestrictionsOverlay = newParkingRestrictionsOverlay; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } private static void onJunctionRestrictionsOverlayChanged(bool newValue) { if (!checkGameLoaded()) return; Log._Debug($"junctionRestrictionsOverlay changed to {newValue}"); junctionRestrictionsOverlay = newValue; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } private static void onConnectedLanesOverlayChanged(bool newValue) { if (!checkGameLoaded()) return; Log._Debug($"connectedLanesOverlay changed to {newValue}"); connectedLanesOverlay = newValue; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } private static void onLanguageChanged(int newLanguageIndex) { bool localeChanged = false; if (newLanguageIndex <= 0) { GlobalConfig.Instance.LanguageCode = null; GlobalConfig.WriteConfig(); MenuRebuildRequired = true; localeChanged = true; } else if (newLanguageIndex - 1 < Translation.AVAILABLE_LANGUAGE_CODES.Count) { GlobalConfig.Instance.LanguageCode = Translation.AVAILABLE_LANGUAGE_CODES[newLanguageIndex - 1]; GlobalConfig.WriteConfig(); MenuRebuildRequired = true; localeChanged = true; } else { Log.Warning($"Options.onLanguageChanged: Invalid language index: {newLanguageIndex}"); } if (localeChanged) { MethodInfo onChangedHandler = typeof(OptionsMainPanel).GetMethod("OnLocaleChanged", BindingFlags.Instance | BindingFlags.NonPublic); if (onChangedHandler != null) { onChangedHandler.Invoke(UIView.library.Get("OptionsPanel"), new object[0] { }); } } } private static void onLockButtonChanged(bool newValue) { Log._Debug($"Button lock changed to {newValue}"); LoadingExtension.BaseUI.MainMenuButton.SetPosLock(newValue); GlobalConfig.Instance.Main.MainMenuButtonPosLocked = newValue; GlobalConfig.WriteConfig(); } private static void onLockMenuChanged(bool newValue) { Log._Debug($"Menu lock changed to {newValue}"); LoadingExtension.BaseUI.MainMenu.SetPosLock(newValue); GlobalConfig.Instance.Main.MainMenuPosLocked = newValue; GlobalConfig.WriteConfig(); } private static void onTinyMenuChanged(bool newValue) { Log._Debug($"Menu tiny changed to {newValue}"); GlobalConfig.Instance.Main.TinyMainMenu = newValue; GlobalConfig.Instance.NotifyObservers(GlobalConfig.Instance); GlobalConfig.WriteConfig(); } private static void onEnableTutorialsChanged(bool newValue) { Log._Debug($"Enable tutorial messages changed to {newValue}"); GlobalConfig.Instance.Main.EnableTutorial = newValue; GlobalConfig.WriteConfig(); } private static void onShowCompatibilityCheckErrorChanged(bool newValue) { Log._Debug($"Show mod compatibility error changed to {newValue}"); GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage = newValue; GlobalConfig.WriteConfig(); } private static void onInstantEffectsChanged(bool newValue) { if (!checkGameLoaded()) return; Log._Debug($"Instant effects changed to {newValue}"); instantEffects = newValue; } private static void onSimAccuracyChanged(int newAccuracy) { if (!checkGameLoaded()) return; Log._Debug($"Simulation accuracy changed to {newAccuracy}"); simAccuracy = newAccuracy; } private static void onVehicleRestrictionsAggressionChanged(int newValue) { if (!checkGameLoaded()) return; Log._Debug($"vehicleRestrictionsAggression changed to {newValue}"); setVehicleRestrictionsAggression((VehicleRestrictionsAggression)newValue); } /*private static void onLaneChangingRandomizationChanged(int newLaneChangingRandomization) { if (!checkGameLoaded()) return; Log._Debug($"Lane changing frequency changed to {newLaneChangingRandomization}"); laneChangingRandomization = newLaneChangingRandomization; }*/ private static void onRecklessDriversChanged(int newRecklessDrivers) { if (!checkGameLoaded()) return; Log._Debug($"Reckless driver amount changed to {newRecklessDrivers}"); recklessDrivers = newRecklessDrivers; } private static void onRelaxedBussesChanged(bool newRelaxedBusses) { if (!checkGameLoaded()) return; Log._Debug($"Relaxed busses changed to {newRelaxedBusses}"); relaxedBusses = newRelaxedBusses; } private static void onAllRelaxedChanged(bool newAllRelaxed) { if (!checkGameLoaded()) return; Log._Debug($"All relaxed changed to {newAllRelaxed}"); allRelaxed = newAllRelaxed; } private static void onAdvancedAIChanged(bool newAdvancedAI) { if (!checkGameLoaded()) return; Log._Debug($"advancedAI changed to {newAdvancedAI}"); setAdvancedAI(newAdvancedAI); } private static void onHighwayRulesChanged(bool newHighwayRules) { if (!checkGameLoaded()) return; bool changed = newHighwayRules != highwayRules; if (!changed) { return; } Log._Debug($"Highway rules changed to {newHighwayRules}"); highwayRules = newHighwayRules; Flags.clearHighwayLaneArrows(); Flags.applyAllFlags(); RoutingManager.Instance.RequestFullRecalculation(); } private static void onPreferOuterLaneChanged(bool val) { if (!checkGameLoaded()) return; preferOuterLane = val; } private static void onPrioritySignsEnabledChanged(bool val) { if (!checkGameLoaded()) return; MenuRebuildRequired = true; prioritySignsEnabled = val; if (!val) { setPrioritySignsOverlay(false); setTrafficLightPriorityRules(false); } } private static void onTimedLightsEnabledChanged(bool val) { if (!checkGameLoaded()) return; MenuRebuildRequired = true; timedLightsEnabled = val; if (!val) { setTimedLightsOverlay(false); setTrafficLightPriorityRules(false); } } private static void onCustomSpeedLimitsEnabledChanged(bool val) { if (!checkGameLoaded()) return; MenuRebuildRequired = true; customSpeedLimitsEnabled = val; if (!val) setSpeedLimitsOverlay(false); } private static void onVehicleRestrictionsEnabledChanged(bool val) { if (!checkGameLoaded()) return; MenuRebuildRequired = true; vehicleRestrictionsEnabled = val; if (!val) setVehicleRestrictionsOverlay(false); } private static void onParkingRestrictionsEnabledChanged(bool val) { if (!checkGameLoaded()) return; MenuRebuildRequired = true; parkingRestrictionsEnabled = val; if (!val) setParkingRestrictionsOverlay(false); } private static void onJunctionRestrictionsEnabledChanged(bool val) { if (!checkGameLoaded()) return; MenuRebuildRequired = true; junctionRestrictionsEnabled = val; if (!val) { setAllowUTurns(false); setAllowEnterBlockedJunctions(false); setAllowLaneChangesWhileGoingStraight(false); setTurnOnRedEnabled(false); setJunctionRestrictionsOverlay(false); } } private static void onTurnOnRedEnabledChanged(bool val) { if (!checkGameLoaded()) return; setTurnOnRedEnabled(val); } private static void onLaneConnectorEnabledChanged(bool val) { if (!checkGameLoaded()) return; bool changed = val != laneConnectorEnabled; if (!changed) { return; } MenuRebuildRequired = true; laneConnectorEnabled = val; RoutingManager.Instance.RequestFullRecalculation(); if (!val) setConnectedLanesOverlay(false); } private static void onEvacBussesMayIgnoreRulesChanged(bool value) { if (!checkGameLoaded()) return; Log._Debug($"evacBussesMayIgnoreRules changed to {value}"); evacBussesMayIgnoreRules = value; } private static void onAllowEnterBlockedJunctionsChanged(bool newValue) { if (!checkGameLoaded()) return; if (newValue && !junctionRestrictionsEnabled) { setAllowEnterBlockedJunctions(false); return; } Log._Debug($"allowEnterBlockedJunctions changed to {newValue}"); setAllowEnterBlockedJunctions(newValue); } private static void onAllowUTurnsChanged(bool newValue) { if (!checkGameLoaded()) return; if (newValue && !junctionRestrictionsEnabled) { setAllowUTurns(false); return; } Log._Debug($"allowUTurns changed to {newValue}"); setAllowUTurns(newValue); } private static void onAllowNearTurnOnRedChanged(bool newValue) { if (!checkGameLoaded()) return; if (newValue && !turnOnRedEnabled) { setAllowNearTurnOnRed(false); setAllowFarTurnOnRed(false); return; } Log._Debug($"allowNearTurnOnRed changed to {newValue}"); setAllowNearTurnOnRed(newValue); if (! newValue) { setAllowFarTurnOnRed(false); } } private static void onAllowFarTurnOnRedChanged(bool newValue) { if (!checkGameLoaded()) return; if (newValue && (!turnOnRedEnabled || !allowNearTurnOnRed)) { setAllowFarTurnOnRed(false); return; } Log._Debug($"allowFarTurnOnRed changed to {newValue}"); setAllowFarTurnOnRed(newValue); } private static void onAllowLaneChangesWhileGoingStraightChanged(bool newValue) { if (!checkGameLoaded()) return; if (newValue && !junctionRestrictionsEnabled) { setAllowLaneChangesWhileGoingStraight(false); return; } Log._Debug($"allowLaneChangesWhileGoingStraight changed to {newValue}"); allowLaneChangesWhileGoingStraight = newValue; } private static void onTrafficLightPriorityRulesChanged(bool newValue) { if (!checkGameLoaded()) return; if (newValue && !prioritySignsEnabled) { setTrafficLightPriorityRules(false); return; } Log._Debug($"trafficLightPriorityRules changed to {newValue}"); trafficLightPriorityRules = newValue; if (newValue) { setPrioritySignsEnabled(true); setTimedLightsEnabled(true); } } private static void onBanRegularTrafficOnBusLanesChanged(bool newValue) { if (!checkGameLoaded()) return; Log._Debug($"banRegularTrafficOnBusLanes changed to {newValue}"); banRegularTrafficOnBusLanes = newValue; VehicleRestrictionsManager.Instance.ClearCache(); UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } private static void onStrongerRoadConditionEffectsChanged(bool newStrongerRoadConditionEffects) { if (!checkGameLoaded()) return; Log._Debug($"strongerRoadConditionEffects changed to {newStrongerRoadConditionEffects}"); strongerRoadConditionEffects = newStrongerRoadConditionEffects; } private static void onProhibitPocketCarsChanged(bool newValue) { if (!checkGameLoaded()) return; Log._Debug($"prohibitPocketCars changed to {newValue}"); prohibitPocketCars = newValue; if (prohibitPocketCars) { AdvancedParkingManager.Instance.OnEnableFeature(); } else { AdvancedParkingManager.Instance.OnDisableFeature(); } } private static void onRealisticPublicTransportChanged(bool newValue) { if (!checkGameLoaded()) return; Log._Debug($"realisticPublicTransport changed to {newValue}"); realisticPublicTransport = newValue; } private static void onRealisticSpeedsChanged(bool value) { if (!checkGameLoaded()) return; Log._Debug($"realisticSpeeds changed to {value}"); realisticSpeeds = value; } private static void onDisableDespawningChanged(bool value) { if (!checkGameLoaded()) return; Log._Debug($"disableDespawning changed to {value}"); disableDespawning = value; } private static void onNodesOverlayChanged(bool newNodesOverlay) { if (!checkGameLoaded()) return; Log._Debug($"Nodes overlay changed to {newNodesOverlay}"); nodesOverlay = newNodesOverlay; } private static void onShowLanesChanged(bool newShowLanes) { if (!checkGameLoaded()) return; Log._Debug($"Show lanes changed to {newShowLanes}"); showLanes = newShowLanes; } private static void onVehicleOverlayChanged(bool newVal) { if (!checkGameLoaded()) return; Log._Debug($"Vehicle overlay changed to {newVal}"); vehicleOverlay = newVal; } private static void onCitizenOverlayChanged(bool newVal) { if (!checkGameLoaded()) return; Log._Debug($"Citizen overlay changed to {newVal}"); citizenOverlay = newVal; } private static void onBuildingOverlayChanged(bool newVal) { if (!checkGameLoaded()) return; Log._Debug($"Building overlay changed to {newVal}"); buildingOverlay = newVal; } #if QUEUEDSTATS private static void onShowPathFindStatsChanged(bool newVal) { if (!checkGameLoaded()) return; Log._Debug($"Show path-find stats changed to {newVal}"); showPathFindStats = newVal; } #endif private static void onFloatValueChanged(string varName, string newValueStr, ref float var) { if (!checkGameLoaded()) return; try { float newValue = Single.Parse(newValueStr); var = newValue; Log._Debug($"{varName} changed to {newValue}"); } catch (Exception e) { Log.Warning($"An invalid value was inserted: '{newValueStr}'. Error: {e.ToString()}"); //UIView.library.ShowModal("ExceptionPanel").SetMessage("Invalid value", "An invalid value was inserted.", false); } } private static void onBoolValueChanged(string varName, bool newVal, ref bool var) { if (!checkGameLoaded()) return; var = newVal; Log._Debug($"{varName} changed to {newVal}"); } private static void onClickResetStuckEntities() { if (!checkGameLoaded()) return; Constants.ServiceFactory.SimulationService.AddAction(() => { UtilityManager.Instance.ResetStuckEntities(); }); } private static void onClickRemoveParkedVehicles() { if (!checkGameLoaded()) return; Constants.ServiceFactory.SimulationService.AddAction(() => { UtilityManager.Instance.RemoveParkedVehicles(); }); } private static void onClickResetSpeedLimits() { if (!checkGameLoaded()) return; Flags.resetSpeedLimits(); } private static void onClickReloadGlobalConf() { GlobalConfig.Reload(); } private static void onClickResetGlobalConf() { GlobalConfig.Reset(null, true); } public static void setSimAccuracy(int newAccuracy) { simAccuracy = newAccuracy; if (simAccuracyDropdown != null) simAccuracyDropdown.selectedIndex = newAccuracy; } public static void setVehicleRestrictionsAggression(VehicleRestrictionsAggression val) { bool changed = vehicleRestrictionsAggression != val; vehicleRestrictionsAggression = val; if (changed && vehicleRestrictionsAggressionDropdown != null) { vehicleRestrictionsAggressionDropdown.selectedIndex = (int)val; } } /*public static void setLaneChangingRandomization(int newLaneChangingRandomization) { laneChangingRandomization = newLaneChangingRandomization; if (laneChangingRandomizationDropdown != null) laneChangingRandomizationDropdown.selectedIndex = newLaneChangingRandomization; }*/ public static void setRecklessDrivers(int newRecklessDrivers) { recklessDrivers = newRecklessDrivers; if (recklessDriversDropdown != null) recklessDriversDropdown.selectedIndex = newRecklessDrivers; } internal static bool isStockLaneChangerUsed() { return !advancedAI; } public static void setRelaxedBusses(bool newRelaxedBusses) { relaxedBusses = newRelaxedBusses; if (relaxedBussesToggle != null) relaxedBussesToggle.isChecked = newRelaxedBusses; } public static void setAllRelaxed(bool newAllRelaxed) { allRelaxed = newAllRelaxed; if (allRelaxedToggle != null) allRelaxedToggle.isChecked = newAllRelaxed; } public static void setHighwayRules(bool newHighwayRules) { highwayRules = newHighwayRules; if (highwayRulesToggle != null) highwayRulesToggle.isChecked = highwayRules; } public static void setPreferOuterLane(bool val) { preferOuterLane = val; if (preferOuterLaneToggle != null) preferOuterLaneToggle.isChecked = preferOuterLane; } public static void setShowLanes(bool newShowLanes) { showLanes = newShowLanes; if (showLanesToggle != null) showLanesToggle.isChecked = newShowLanes; } public static void setAdvancedAI(bool newAdvancedAI) { bool changed = newAdvancedAI != advancedAI; advancedAI = newAdvancedAI; if (changed && advancedAIToggle != null) { advancedAIToggle.isChecked = newAdvancedAI; } if (changed && !newAdvancedAI) { setAltLaneSelectionRatio(0); } } public static void setGuiTransparency(byte val) { bool changed = val != GlobalConfig.Instance.Main.GuiTransparency; GlobalConfig.Instance.Main.GuiTransparency = val; if (changed && guiTransparencySlider != null) { guiTransparencySlider.value = val; } } public static void setOverlayTransparency(byte val) { bool changed = val != GlobalConfig.Instance.Main.OverlayTransparency; GlobalConfig.Instance.Main.OverlayTransparency = val; if (changed && overlayTransparencySlider != null) { overlayTransparencySlider.value = val; } } public static void setAltLaneSelectionRatio(byte val) { bool changed = val != altLaneSelectionRatio; altLaneSelectionRatio = val; if (changed && altLaneSelectionRatioSlider != null) { altLaneSelectionRatioSlider.value = val; } if (changed && altLaneSelectionRatio > 0) { setAdvancedAI(true); } } public static void setEvacBussesMayIgnoreRules(bool value) { if (! SteamHelper.IsDLCOwned(SteamHelper.DLC.NaturalDisastersDLC)) value = false; evacBussesMayIgnoreRules = value; if (evacBussesMayIgnoreRulesToggle != null) evacBussesMayIgnoreRulesToggle.isChecked = value; } public static void setInstantEffects(bool value) { instantEffects = value; if (instantEffectsToggle != null) instantEffectsToggle.isChecked = value; } public static void setMayEnterBlockedJunctions(bool newMayEnterBlockedJunctions) { allowEnterBlockedJunctions = newMayEnterBlockedJunctions; if (allowEnterBlockedJunctionsToggle != null) allowEnterBlockedJunctionsToggle.isChecked = newMayEnterBlockedJunctions; } public static void setStrongerRoadConditionEffects(bool newStrongerRoadConditionEffects) { if (!SteamHelper.IsDLCOwned(SteamHelper.DLC.SnowFallDLC)) { newStrongerRoadConditionEffects = false; } strongerRoadConditionEffects = newStrongerRoadConditionEffects; if (strongerRoadConditionEffectsToggle != null) strongerRoadConditionEffectsToggle.isChecked = newStrongerRoadConditionEffects; } public static void setProhibitPocketCars(bool newValue) { bool valueChanged = newValue != prohibitPocketCars; prohibitPocketCars = newValue; if (prohibitPocketCarsToggle != null) prohibitPocketCarsToggle.isChecked = newValue; } public static void setRealisticPublicTransport(bool newValue) { bool valueChanged = newValue != realisticPublicTransport; realisticPublicTransport = newValue; if (realisticPublicTransportToggle != null) realisticPublicTransportToggle.isChecked = newValue; } public static void setRealisticSpeeds(bool newValue) { realisticSpeeds = newValue; if (realisticSpeedsToggle != null) realisticSpeedsToggle.isChecked = newValue; } public static void setDisableDespawning(bool value) { disableDespawning = value; if (disableDespawningToggle != null) disableDespawningToggle.isChecked = value; } public static void setAllowUTurns(bool value) { allowUTurns = value; if (allowUTurnsToggle != null) allowUTurnsToggle.isChecked = value; Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); } public static void setAllowNearTurnOnRed(bool newValue) { allowNearTurnOnRed = newValue; if (allowNearTurnOnRedToggle != null) allowNearTurnOnRedToggle.isChecked = newValue; Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); } public static void setAllowFarTurnOnRed(bool newValue) { allowFarTurnOnRed = newValue; if (allowFarTurnOnRedToggle != null) allowFarTurnOnRedToggle.isChecked = newValue; Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); } public static void setAllowLaneChangesWhileGoingStraight(bool value) { allowLaneChangesWhileGoingStraight = value; if (allowLaneChangesWhileGoingStraightToggle != null) allowLaneChangesWhileGoingStraightToggle.isChecked = value; Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); } public static void setAllowEnterBlockedJunctions(bool value) { allowEnterBlockedJunctions = value; if (allowEnterBlockedJunctionsToggle != null) allowEnterBlockedJunctionsToggle.isChecked = value; Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); } public static void setTrafficLightPriorityRules(bool value) { trafficLightPriorityRules = value; if (trafficLightPriorityRulesToggle != null) trafficLightPriorityRulesToggle.isChecked = value; } public static void setBanRegularTrafficOnBusLanes(bool value) { banRegularTrafficOnBusLanes = value; if (banRegularTrafficOnBusLanesToggle != null) banRegularTrafficOnBusLanesToggle.isChecked = value; VehicleRestrictionsManager.Instance.ClearCache(); UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setPrioritySignsOverlay(bool newPrioritySignsOverlay) { prioritySignsOverlay = newPrioritySignsOverlay; if (prioritySignsOverlayToggle != null) prioritySignsOverlayToggle.isChecked = newPrioritySignsOverlay; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setTimedLightsOverlay(bool newTimedLightsOverlay) { timedLightsOverlay = newTimedLightsOverlay; if (timedLightsOverlayToggle != null) timedLightsOverlayToggle.isChecked = newTimedLightsOverlay; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setSpeedLimitsOverlay(bool newSpeedLimitsOverlay) { speedLimitsOverlay = newSpeedLimitsOverlay; if (speedLimitsOverlayToggle != null) speedLimitsOverlayToggle.isChecked = newSpeedLimitsOverlay; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setVehicleRestrictionsOverlay(bool newVehicleRestrictionsOverlay) { vehicleRestrictionsOverlay = newVehicleRestrictionsOverlay; if (vehicleRestrictionsOverlayToggle != null) vehicleRestrictionsOverlayToggle.isChecked = newVehicleRestrictionsOverlay; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setParkingRestrictionsOverlay(bool newParkingRestrictionsOverlay) { parkingRestrictionsOverlay = newParkingRestrictionsOverlay; if (parkingRestrictionsOverlayToggle != null) parkingRestrictionsOverlayToggle.isChecked = newParkingRestrictionsOverlay; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setJunctionRestrictionsOverlay(bool newValue) { junctionRestrictionsOverlay = newValue; if (junctionRestrictionsOverlayToggle != null) junctionRestrictionsOverlayToggle.isChecked = newValue; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setConnectedLanesOverlay(bool newValue) { connectedLanesOverlay = newValue; if (connectedLanesOverlayToggle != null) connectedLanesOverlayToggle.isChecked = newValue; } public static void setNodesOverlay(bool newNodesOverlay) { nodesOverlay = newNodesOverlay; if (nodesOverlayToggle != null) nodesOverlayToggle.isChecked = newNodesOverlay; UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); } public static void setVehicleOverlay(bool newVal) { vehicleOverlay = newVal; if (vehicleOverlayToggle != null) vehicleOverlayToggle.isChecked = newVal; } public static void setPrioritySignsEnabled(bool newValue) { MenuRebuildRequired = true; prioritySignsEnabled = newValue; if (enablePrioritySignsToggle != null) enablePrioritySignsToggle.isChecked = newValue; if (!newValue) setPrioritySignsOverlay(false); } public static void setTimedLightsEnabled(bool newValue) { MenuRebuildRequired = true; timedLightsEnabled = newValue; if (enableTimedLightsToggle != null) enableTimedLightsToggle.isChecked = newValue; if (!newValue) setTimedLightsOverlay(false); } public static void setCustomSpeedLimitsEnabled(bool newValue) { MenuRebuildRequired = true; customSpeedLimitsEnabled = newValue; if (enableCustomSpeedLimitsToggle != null) enableCustomSpeedLimitsToggle.isChecked = newValue; if (!newValue) setSpeedLimitsOverlay(false); } public static void setVehicleRestrictionsEnabled(bool newValue) { MenuRebuildRequired = true; vehicleRestrictionsEnabled = newValue; if (enableVehicleRestrictionsToggle != null) enableVehicleRestrictionsToggle.isChecked = newValue; if (!newValue) setVehicleRestrictionsOverlay(false); } public static void setParkingRestrictionsEnabled(bool newValue) { MenuRebuildRequired = true; parkingRestrictionsEnabled = newValue; if (enableParkingRestrictionsToggle != null) enableParkingRestrictionsToggle.isChecked = newValue; if (!newValue) setParkingRestrictionsOverlay(false); } public static void setJunctionRestrictionsEnabled(bool newValue) { MenuRebuildRequired = true; junctionRestrictionsEnabled = newValue; if (enableJunctionRestrictionsToggle != null) enableJunctionRestrictionsToggle.isChecked = newValue; if (!newValue) setJunctionRestrictionsOverlay(false); } public static void setTurnOnRedEnabled(bool newValue) { turnOnRedEnabled = newValue; if (turnOnRedEnabledToggle != null) turnOnRedEnabledToggle.isChecked = newValue; if (!newValue) { setAllowNearTurnOnRed(false); setAllowFarTurnOnRed(false); } } public static void setLaneConnectorEnabled(bool newValue) { MenuRebuildRequired = true; laneConnectorEnabled = newValue; if (enableLaneConnectorToggle != null) enableLaneConnectorToggle.isChecked = newValue; if (!newValue) setConnectedLanesOverlay(false); } #if QUEUEDSTATS public static void setShowPathFindStats(bool value) { showPathFindStats = value; if (showPathFindStatsToggle != null) showPathFindStatsToggle.isChecked = value; } #endif /*internal static int getLaneChangingRandomizationTargetValue() { int ret = 100; switch (laneChangingRandomization) { case 0: ret = 2; break; case 1: ret = 4; break; case 2: ret = 10; break; case 3: ret = 20; break; case 4: ret = 50; break; } return ret; }*/ /*internal static float getLaneChangingProbability() { switch (laneChangingRandomization) { case 0: return 0.5f; case 1: return 0.25f; case 2: return 0.1f; case 3: return 0.05f; case 4: return 0.01f; } return 0.01f; }*/ internal static int getRecklessDriverModulo() { switch (recklessDrivers) { case 0: return 10; case 1: return 20; case 2: return 50; case 3: return 10000; } return 10000; } } } ================================================ FILE: TLM/TLM/State/README.md ================================================ # TM:PE -- /State Configuration and classes for loading and saving. ## Classes - **Configuration**: Serializable classes that is used to store the custom game state together with the savegame data. - **Flags**: Custom game state storage backend (deprecated, marked for deletion: Custom game state information should be accessed through the *Manager classes). - **GlobalConfig**: Represents the global configuration that is stored in TMPE_GlobalConfig.xml - **Options**: Holds player-set options. Builds the options dialog. - **SerializableDataExtension**: Main load/save class. Serializes/Deserializes **Configuration** instances to/from savegame data. ================================================ FILE: TLM/TLM/State/SerializableDataExtension.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Threading; using ColossalFramework; using ICities; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using UnityEngine; using Random = UnityEngine.Random; using Timer = System.Timers.Timer; using TrafficManager.State; using TrafficManager.Custom.AI; using TrafficManager.UI; using TrafficManager.Manager; using TrafficManager.Traffic; using ColossalFramework.UI; using TrafficManager.Util; using System.Linq; using CSUtil.Commons; using TrafficManager.Manager.Impl; using TrafficManager.Geometry.Impl; namespace TrafficManager.State { public class SerializableDataExtension : SerializableDataExtensionBase { private const string DataId = "TrafficManager_v1.0"; private static ISerializableData _serializableData; private static Configuration _configuration; public static bool StateLoading = false; public override void OnCreated(ISerializableData serializableData) { _serializableData = serializableData; } public override void OnReleased() { } public override void OnLoadData() { Log.Info("Loading Traffic Manager: PE Data"); StateLoading = true; bool loadingSucceeded = true; try { Log.Info("Initializing flags"); Flags.OnBeforeLoadData(); } catch (Exception e) { Log.Error($"OnLoadData: Error while initializing Flags: {e.ToString()}"); loadingSucceeded = false; } try { Log.Info("Initializing node geometries"); NodeGeometry.OnBeforeLoadData(); } catch (Exception e) { Log.Error($"OnLoadData: Error while initializing NodeGeometry: {e.ToString()}"); loadingSucceeded = false; } try { Log.Info("Initializing segment geometries"); SegmentGeometry.OnBeforeLoadData(); } catch (Exception e) { Log.Error($"OnLoadData: Error while initializing SegmentGeometry: {e.ToString()}"); loadingSucceeded = false; } foreach (ICustomManager manager in LoadingExtension.RegisteredManagers) { try { Log.Info($"OnBeforeLoadData: {manager.GetType().Name}"); manager.OnBeforeLoadData(); } catch (Exception e) { Log.Error($"OnLoadData: Error while initializing {manager.GetType().Name}: {e.ToString()}"); loadingSucceeded = false; } } Log.Info("Initialization done. Loading mod data now."); try { byte[] data = _serializableData.LoadData(DataId); DeserializeData(data); } catch (Exception e) { Log.Error($"OnLoadData: Error while deserializing data: {e.ToString()}"); loadingSucceeded = false; } // load options try { byte[] options = _serializableData.LoadData("TMPE_Options"); if (options != null) { if (! OptionsManager.Instance.LoadData(options)) { loadingSucceeded = false; } } } catch (Exception e) { Log.Error($"OnLoadData: Error while loading options: {e.ToString()}"); loadingSucceeded = false; } if (loadingSucceeded) Log.Info("OnLoadData completed successfully."); else { Log.Info("An error occurred while loading."); //UIView.library.ShowModal("ExceptionPanel").SetMessage("An error occurred while loading", "Traffic Manager: President Edition detected an error while loading. Please do NOT save this game under the old filename, otherwise your timed traffic lights, custom lane arrows, etc. are in danger. Instead, please navigate to http://steamcommunity.com/sharedfiles/filedetails/?id=583429740 and follow the steps under 'In case problems arise'.", true); } StateLoading = false; foreach (ICustomManager manager in LoadingExtension.RegisteredManagers) { try { Log.Info($"OnAfterLoadData: {manager.GetType().Name}"); manager.OnAfterLoadData(); } catch (Exception e) { Log.Error($"OnLoadData: Error while initializing {manager.GetType().Name}: {e.ToString()}"); loadingSucceeded = false; } } } private static void DeserializeData(byte[] data) { bool error = false; try { if (data != null && data.Length != 0) { Log.Info($"Loading Data from New Load Routine! Length={data.Length}"); var memoryStream = new MemoryStream(); memoryStream.Write(data, 0, data.Length); memoryStream.Position = 0; var binaryFormatter = new BinaryFormatter(); _configuration = (Configuration)binaryFormatter.Deserialize(memoryStream); } else { Log.Info("No data to deserialize!"); } } catch (Exception e) { Log.Error($"Error deserializing data: {e.ToString()}"); Log.Info(e.StackTrace); error = true; } if (!error) { LoadDataState(out error); } if (error) { throw new ApplicationException("An error occurred while loading"); } } private static void LoadDataState(out bool error) { error = false; Log.Info("Loading State from Config"); if (_configuration == null) { Log.Warning("Configuration NULL, Couldn't load save data. Possibly a new game?"); return; } TrafficPriorityManager prioMan = TrafficPriorityManager.Instance; // load ext. citizens if (_configuration.ExtCitizens != null) { if (!ExtCitizenManager.Instance.LoadData(_configuration.ExtCitizens)) { error = true; } } else { Log.Info("Ext. citizen data structure undefined!"); } // load ext. citizen instances if (_configuration.ExtCitizenInstances != null) { if (!ExtCitizenInstanceManager.Instance.LoadData(_configuration.ExtCitizenInstances)) { error = true; } } else { Log.Info("Ext. citizen instance data structure undefined!"); } // load priority segments if (_configuration.PrioritySegments != null) { if (! TrafficPriorityManager.Instance.LoadData(_configuration.PrioritySegments)) { error = true; } } else { Log.Info("Priority segments data structure (old) undefined!"); } if (_configuration.CustomPrioritySegments != null) { if (!TrafficPriorityManager.Instance.LoadData(_configuration.CustomPrioritySegments)) { error = true; } } else { Log.Info("Priority segments data structure (new) undefined!"); } // load parking restrictions if (_configuration.ParkingRestrictions != null) { if (!ParkingRestrictionsManager.Instance.LoadData(_configuration.ParkingRestrictions)) { error = true; } } else { Log.Info("Parking restrctions structure undefined!"); } // load vehicle restrictions (warning: has to be done before loading timed lights!) if (_configuration.LaneAllowedVehicleTypes != null) { if (! VehicleRestrictionsManager.Instance.LoadData(_configuration.LaneAllowedVehicleTypes)) { error = true; } } else { Log.Info("Vehicle restrctions structure undefined!"); } NetManager netManager = Singleton.instance; if (_configuration.TimedLights != null) { if (! TrafficLightSimulationManager.Instance.LoadData(_configuration.TimedLights)) { error = true; } } else { Log.Info("Timed traffic lights data structure undefined!"); } // load toggled traffic lights (old method) if (_configuration.NodeTrafficLights != null) { if (! TrafficLightManager.Instance.LoadData(_configuration.NodeTrafficLights)) { error = true; } } else { Log.Info("Junction traffic lights data structure (old) undefined!"); } // load toggled traffic lights (new method) if (_configuration.ToggledTrafficLights != null) { if (!TrafficLightManager.Instance.LoadData(_configuration.ToggledTrafficLights)) { error = true; } } else { Log.Info("Junction traffic lights data structure (new) undefined!"); } // load lane arrrows (old method) if (_configuration.LaneFlags != null) { if (!LaneArrowManager.Instance.LoadData(_configuration.LaneFlags)) { error = true; } } else { Log.Info("Lane arrow data structure (old) undefined!"); } // load lane arrows (new method) if (_configuration.LaneArrows != null) { if (!LaneArrowManager.Instance.LoadData(_configuration.LaneArrows)) { error = true; } } else { Log.Info("Lane arrow data structure (new) undefined!"); } // load lane connections if (_configuration.LaneConnections != null) { if (!LaneConnectionManager.Instance.LoadData(_configuration.LaneConnections)) { error = true; } } else { Log.Info("Lane connection data structure undefined!"); } // Load custom default speed limits if (_configuration.CustomDefaultSpeedLimits != null) { if (!SpeedLimitManager.Instance.LoadData(_configuration.CustomDefaultSpeedLimits)) { error = true; } } // load speed limits if (_configuration.LaneSpeedLimits != null) { if (!SpeedLimitManager.Instance.LoadData(_configuration.LaneSpeedLimits)) { error = true; } } else { Log.Info("Lane speed limit structure undefined!"); } // Load segment-at-node flags if (_configuration.SegmentNodeConfs != null) { if (!JunctionRestrictionsManager.Instance.LoadData(_configuration.SegmentNodeConfs)) { error = true; } } else { Log.Info("Segment-at-node structure undefined!"); } } public override void OnSaveData() { bool success = true; /*try { Log.Info("Recalculating segment geometries"); SegmentGeometry.OnBeforeSaveData(); } catch (Exception e) { Log.Error($"OnSaveData: Exception occurred while calling SegmentGeometry.OnBeforeSaveData: {e.ToString()}"); error = true; }*/ foreach (ICustomManager manager in LoadingExtension.RegisteredManagers) { try { Log.Info($"OnBeforeSaveData: {manager.GetType().Name}"); manager.OnBeforeSaveData(); } catch (Exception e) { Log.Error($"OnSaveData: Error while notifying {manager.GetType().Name}.OnBeforeSaveData: {e.ToString()}"); success = false; } } try { Log.Info("Saving Mod Data."); var configuration = new Configuration(); TrafficPriorityManager prioMan = TrafficPriorityManager.Instance; configuration.ExtCitizens = ExtCitizenManager.Instance.SaveData(ref success); configuration.ExtCitizenInstances = ExtCitizenInstanceManager.Instance.SaveData(ref success); configuration.PrioritySegments = ((ICustomDataManager>)TrafficPriorityManager.Instance).SaveData(ref success); configuration.CustomPrioritySegments = ((ICustomDataManager>)TrafficPriorityManager.Instance).SaveData(ref success); configuration.SegmentNodeConfs = JunctionRestrictionsManager.Instance.SaveData(ref success); configuration.TimedLights = TrafficLightSimulationManager.Instance.SaveData(ref success); //configuration.NodeTrafficLights = ((ICustomDataManager)TrafficLightManager.Instance).SaveData(ref success); //configuration.ToggledTrafficLights = ((ICustomDataManager>)TrafficLightManager.Instance).SaveData(ref success); configuration.LaneFlags = ((ICustomDataManager)LaneArrowManager.Instance).SaveData(ref success); configuration.LaneArrows = ((ICustomDataManager>)LaneArrowManager.Instance).SaveData(ref success); configuration.LaneConnections = LaneConnectionManager.Instance.SaveData(ref success); configuration.LaneSpeedLimits = ((ICustomDataManager>)SpeedLimitManager.Instance).SaveData(ref success); configuration.CustomDefaultSpeedLimits = ((ICustomDataManager>)SpeedLimitManager.Instance).SaveData(ref success); configuration.LaneAllowedVehicleTypes = VehicleRestrictionsManager.Instance.SaveData(ref success); configuration.ParkingRestrictions = ParkingRestrictionsManager.Instance.SaveData(ref success); try { // save options _serializableData.SaveData("TMPE_Options", OptionsManager.Instance.SaveData(ref success)); } catch (Exception ex) { Log.Error("Unexpected error while saving options: " + ex.Message); success = false; } var binaryFormatter = new BinaryFormatter(); var memoryStream = new MemoryStream(); try { binaryFormatter.Serialize(memoryStream, configuration); memoryStream.Position = 0; Log.Info($"Save data byte length {memoryStream.Length}"); _serializableData.SaveData(DataId, memoryStream.ToArray()); } catch (Exception ex) { Log.Error("Unexpected error while saving data: " + ex.ToString()); success = false; } finally { memoryStream.Close(); } foreach (ICustomManager manager in LoadingExtension.RegisteredManagers) { try { Log.Info($"OnAfterSaveData: {manager.GetType().Name}"); manager.OnAfterSaveData(); } catch (Exception e) { Log.Error($"OnSaveData: Error while notifying {manager.GetType().Name}.OnAfterSaveData: {e.ToString()}"); success = false; } } } catch (Exception e) { success = false; Log.Error($"Error occurred while saving data: {e.ToString()}"); //UIView.library.ShowModal("ExceptionPanel").SetMessage("An error occurred while saving", "Traffic Manager: President Edition detected an error while saving. To help preventing future errors, please navigate to http://steamcommunity.com/sharedfiles/filedetails/?id=583429740 and follow the steps under 'In case problems arise'.", true); } } } } ================================================ FILE: TLM/TLM/TLM.csproj ================================================  Debug AnyCPU {7422AE58-8B0A-401C-9404-F4A438EFFE10} Library Properties TrafficManager TrafficManager v3.5 512 true full false bin\Debug\ DEBUG;PF2;QUEUEDSTATS;PARKINGAI;SPEEDLIMITS;ROUTING;JUNCTIONRESTRICTIONS;VEHICLERESTRICTIONS;ADVANCEDAI;CUSTOMTRAFFICLIGHTS prompt 4 true ..\TMPE.ruleset pdbonly true bin\Release\ PF2;QUEUEDSTATS;PARKINGAI;SPEEDLIMITS;ROUTING;JUNCTIONRESTRICTIONS;VEHICLERESTRICTIONS;ADVANCEDAI;CUSTOMTRAFFICLIGHTS prompt 4 true ..\TMPE.ruleset true bin\DebugGeometry\ DEBUG;PF2;QUEUEDSTATS;DEBUGGEO;DEBUGVSTATE;DEBUGNEWPF;DEBUGCOSTS;DEBUGCONN2;DEBUGTTL;DEBUGHWJUNCTIONROUTING;DEBUGROUTING;DEBUGCONN;DEBUGHK;PARKINGAI;SPEEDLIMITS;ROUTING;JUNCTIONRESTRICTIONS;VEHICLERESTRICTIONS;ADVANCEDAI;CUSTOMTRAFFICLIGHTS true 4 full AnyCPU prompt ..\TMPE.ruleset true bin\Benchmark\ DEBUG;QUEUEDSTATS;DEBUGGEO;DEBUGVSTATE;DEBUGNEWPF;DEBUGCOSTS;DEBUGCONN2;DEBUGTTL;DEBUGHWJUNCTIONROUTING;DEBUGROUTING;BENCHMARK true 4 full AnyCPU prompt ..\TMPE.ruleset true bin\PF2_Debug\ DEBUG;QUEUEDSTATS;DEBUGGEO;DEBUGVSTATE;DEBUGNEWPF;DEBUGCOSTS;DEBUGCONN2;DEBUGTTL;DEBUGHWJUNCTIONROUTING;DEBUGROUTING;DEBUGHK;PF2;PARKINGAI;SPEEDLIMITS;ROUTING;JUNCTIONRESTRICTIONS;VEHICLERESTRICTIONS;ADVANCEDAI;CUSTOMTRAFFICLIGHTS true 4 full AnyCPU prompt ..\TMPE.ruleset ..\dependencies\Assembly-CSharp.dll ..\dependencies\ColossalManaged.dll ..\dependencies\ICities.dll ..\dependencies\System.Core.dll ..\dependencies\UnityEngine.dll ..\dependencies\UnityEngine.Networking.dll ..\dependencies\UnityEngine.UI.dll {f8759084-df5b-4a54-b73c-824640a8fa3f} CSUtil.CameraControl {D3ADE06E-F493-4819-865A-3BB44FEEDF01} CSUtil.Commons {3f2f7926-5d51-4880-a2b7-4594a10d7e54} TMPE.CitiesGameBridge {663b991f-32a1-46e1-a4d3-540f8ea7f386} TMPE.GenericGameBridge mkdir "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(TargetName)" del /Q "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(TargetName)\*" xcopy /y "$(TargetDir)TrafficManager.dll" "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(TargetName)" xcopy /y "$(TargetDir)TMPE.CitiesGameBridge.dll" "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(TargetName)" xcopy /y "$(TargetDir)TMPE.GenericGameBridge.dll" "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(TargetName)" xcopy /y "$(TargetDir)CSUtil.CameraControl.dll" "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(TargetName)" xcopy /y "$(TargetDir)CSUtil.Commons.dll" "$(LOCALAPPDATA)\Colossal Order\Cities_Skylines\Addons\Mods\$(TargetName)" ================================================ FILE: TLM/TLM/TMPE.csproj ================================================  Debug AnyCPU {7422AE58-8B0A-401C-9404-F4A438EFFE10} Library Properties TrafficManager TrafficManager v3.5 512 true full false bin\Debug\ DEBUG;QUEUEDSTATS prompt 0 true pdbonly true bin\Release\ prompt 4 true bin\QueuedStats\ QUEUEDSTATS true true pdbonly AnyCPU prompt MinimumRecommendedRules.ruleset ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Cities_Skylines\Cities_Data\Managed\Assembly-CSharp.dll ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Cities_Skylines\Cities_Data\Managed\ColossalManaged.dll ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Cities_Skylines\Cities_Data\Managed\ICities.dll C:\Program Files (x86)\Steam\steamapps\common\Cities_Skylines\Managed\System.Core.dll ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Cities_Skylines\Cities_Data\Managed\UnityEngine.dll ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Cities_Skylines\Cities_Data\Managed\UnityEngine.Networking.dll ..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Cities_Skylines\Cities_Data\Managed\UnityEngine.UI.dll {3f2f7926-5d51-4880-a2b7-4594a10d7e54} CitiesGameBridge {663b991f-32a1-46e1-a4d3-540f8ea7f386} GenericGameBridge {d3ade06e-f493-4819-865a-3bb44feedf01} Util mkdir "C:\Program Files (x86)\Steam\steamapps\workshop\content\255710\583429740" del /Q "C:\Program Files (x86)\Steam\steamapps\workshop\content\255710\583429740\*" xcopy /y "$(TargetDir)$(TargetName).*" "C:\Program Files (x86)\Steam\steamapps\workshop\content\255710\583429740" ================================================ FILE: TLM/TLM/ThreadingExtension.cs ================================================ using System; using System.Reflection; using ColossalFramework; using ICities; using TrafficManager.Custom.AI; using UnityEngine; using TrafficManager.State; using TrafficManager.Manager; using TrafficManager.UI; using CSUtil.Commons; using CSUtil.Commons.Benchmark; using TrafficManager.UI.MainMenu; using static TrafficManager.LoadingExtension; using TrafficManager.Util; using System.Collections.Generic; using ColossalFramework.UI; using System.Runtime.InteropServices; using System.Linq; using System.Linq.Expressions; namespace TrafficManager { public sealed class ThreadingExtension : ThreadingExtensionBase { //int ticksSinceLastMinuteUpdate = 0; ITrafficLightSimulationManager tlsMan = Constants.ManagerFactory.TrafficLightSimulationManager; IGeometryManager geoMan = Constants.ManagerFactory.GeometryManager; IRoutingManager routeMan = Constants.ManagerFactory.RoutingManager; IUtilityManager utilMan = Constants.ManagerFactory.UtilityManager; bool firstFrame = true; public override void OnCreated(IThreading threading) { base.OnCreated(threading); //ticksSinceLastMinuteUpdate = 0; } public override void OnBeforeSimulationTick() { base.OnBeforeSimulationTick(); geoMan.SimulationStep(); routeMan.SimulationStep(); } public override void OnBeforeSimulationFrame() { base.OnBeforeSimulationFrame(); if (firstFrame) { firstFrame = false; Log.Info($"ThreadingExtension.OnBeforeSimulationFrame: First frame detected. Checking detours."); List missingDetours = new List(); foreach (Detour detour in LoadingExtension.Detours) { if (! RedirectionHelper.IsRedirected(detour.OriginalMethod, detour.CustomMethod)) { missingDetours.Add($"{detour.OriginalMethod.DeclaringType.Name}.{detour.OriginalMethod.Name} with {detour.OriginalMethod.GetParameters().Length} parameters ({detour.OriginalMethod.DeclaringType.AssemblyQualifiedName})"); } } Log.Info($"ThreadingExtension.OnBeforeSimulationFrame: First frame detected. Detours checked. Result: {missingDetours.Count} missing detours"); if (missingDetours.Count > 0) { string error = "Traffic Manager: President Edition detected an incompatibility with another mod! You can continue playing but it's NOT recommended. Traffic Manager will not work as expected. See TMPE.log for technical details."; Log.Error(error); string log = "The following methods were overriden by another mod:"; foreach (string missingDetour in missingDetours) { log += $"\n\t{missingDetour}"; } Log.Info(log); if (GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage) { Singleton.instance.m_ThreadingWrapper.QueueMainThread(() => { UIView.library.ShowModal("ExceptionPanel").SetMessage("Incompatibility Issue", error, true); }); } } } if (Options.timedLightsEnabled) { tlsMan.SimulationStep(); } } /*public override void OnAfterSimulationFrame() { base.OnAfterSimulationFrame(); routeMan.SimulationStep(); ++ticksSinceLastMinuteUpdate; if (ticksSinceLastMinuteUpdate > 60 * 60) { ticksSinceLastMinuteUpdate = 0; GlobalConfig.Instance.SimulationStep(); #if DEBUG DebugMenuPanel.PrintTransportStats(); #endif } }*/ public override void OnUpdate(float realTimeDelta, float simulationTimeDelta) { base.OnUpdate(realTimeDelta, simulationTimeDelta); #if !TAM #if BENCHMARK using (var bm = new Benchmark()) { #endif if (ToolsModifierControl.toolController == null || LoadingExtension.BaseUI == null) { return; } TrafficManagerTool tmTool = UIBase.GetTrafficManagerTool(false); if (tmTool != null && ToolsModifierControl.toolController.CurrentTool != tmTool && LoadingExtension.BaseUI.IsVisible()) { LoadingExtension.BaseUI.Close(); } if (Input.GetKeyDown(KeyCode.Escape)) { LoadingExtension.BaseUI.Close(); } #if BENCHMARK } #endif #endif } } } ================================================ FILE: TLM/TLM/Traffic/Data/ExtBuilding.cs ================================================ using ColossalFramework; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.State; using UnityEngine; namespace TrafficManager.Traffic.Data { public struct ExtBuilding { /// /// Building id /// public ushort buildingId; /// /// Current parking space demand (0-100) /// public byte parkingSpaceDemand; /// /// Current incoming public transport demand (0-100) /// public byte incomingPublicTransportDemand; /// /// Current outgoing public transport demand (0-100) /// public byte outgoingPublicTransportDemand; public override string ToString() { return $"[ExtBuilding {base.ToString()}\n" + "\t" + $"buildingId = {buildingId}\n" + "\t" + $"parkingSpaceDemand = {parkingSpaceDemand}\n" + "\t" + $"incomingPublicTransportDemand = {incomingPublicTransportDemand}\n" + "\t" + $"outgoingPublicTransportDemand = {outgoingPublicTransportDemand}\n" + "ExtBuilding]"; } internal ExtBuilding(ushort buildingId) { this.buildingId = buildingId; parkingSpaceDemand = 0; incomingPublicTransportDemand = 0; outgoingPublicTransportDemand = 0; } public bool IsValid() { return Constants.ServiceFactory.BuildingService.IsBuildingValid(buildingId); } internal void Reset() { parkingSpaceDemand = 0; incomingPublicTransportDemand = 0; outgoingPublicTransportDemand = 0; } internal void AddParkingSpaceDemand(uint delta) { parkingSpaceDemand = (byte)Math.Min(100, (int)parkingSpaceDemand + delta); RequestColorUpdate(); } internal void RemoveParkingSpaceDemand(uint delta) { parkingSpaceDemand = (byte)Math.Max(0, (int)parkingSpaceDemand - delta); RequestColorUpdate(); } internal void ModifyParkingSpaceDemand(Vector3 parkPos, int minDelta=-10, int maxDelta=10) { Vector3 buildingPos = Singleton.instance.m_buildings.m_buffer[buildingId].m_position; float distance = Mathf.Clamp((parkPos - buildingPos).magnitude, 0f, GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding); float delta = (float)(maxDelta - minDelta) * (distance / GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding) + (float)minDelta; parkingSpaceDemand = (byte)Mathf.Clamp((int)parkingSpaceDemand + (int)Mathf.Round(delta), 0, 100); RequestColorUpdate(); } internal void AddPublicTransportDemand(uint delta, bool outgoing) { byte oldDemand = outgoing ? outgoingPublicTransportDemand : incomingPublicTransportDemand; byte newDemand = (byte)Math.Min(100, (int)oldDemand + delta); if (outgoing) outgoingPublicTransportDemand = newDemand; else incomingPublicTransportDemand = newDemand; RequestColorUpdate(); } internal void RemovePublicTransportDemand(uint delta, bool outgoing) { byte oldDemand = outgoing ? outgoingPublicTransportDemand : incomingPublicTransportDemand; byte newDemand = (byte)Math.Max(0, (int)oldDemand - delta); if (outgoing) outgoingPublicTransportDemand = newDemand; else incomingPublicTransportDemand = newDemand; RequestColorUpdate(); } private void RequestColorUpdate() { Singleton.instance.UpdateBuildingColors(buildingId); } } } ================================================ FILE: TLM/TLM/Traffic/Data/ExtCitizen.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.State; namespace TrafficManager.Traffic.Data { public struct ExtCitizen { [Flags] public enum ExtTransportMode { /// /// No information about which mode of transport is used /// None = 0, /// /// Travelling by car /// Car = 1, /// /// Travelling by means of public transport /// PublicTransport = 2 } public uint citizenId; /// /// Mode of transport that is currently used to reach a destination /// public ExtTransportMode transportMode; /// /// Mode of transport that was previously used to reach a destination /// public ExtTransportMode lastTransportMode; /// /// Previous building location /// public Citizen.Location lastLocation; public override string ToString() { return $"[ExtCitizen\n" + "\t" + $"citizenId = {citizenId}\n" + "\t" + $"transportMode = {transportMode}\n" + "\t" + $"lastTransportMode = {transportMode}\n" + "\t" + $"lastLocation = {lastLocation}\n" + "ExtCitizen]"; } internal ExtCitizen(uint citizenId) { this.citizenId = citizenId; transportMode = ExtTransportMode.None; lastTransportMode = ExtTransportMode.None; lastLocation = Citizen.Location.Moving; ResetLastLocation(); } internal bool IsValid() { return Constants.ServiceFactory.CitizenService.IsCitizenValid(citizenId); } internal void Reset() { #if DEBUG bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == citizenId; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; if (fineDebug) { Log.Warning($"ExtCitizen.Reset({citizenId}): Resetting ext. citizen {citizenId}"); } #endif transportMode = ExtTransportMode.None; lastTransportMode = ExtTransportMode.None; ResetLastLocation(); } private void ResetLastLocation() { Citizen.Location loc = Citizen.Location.Moving; Constants.ServiceFactory.CitizenService.ProcessCitizen(citizenId, delegate (uint citId, ref Citizen citizen) { loc = citizen.CurrentLocation; return true; }); lastLocation = loc; } } } ================================================ FILE: TLM/TLM/Traffic/Data/ExtCitizenInstance.cs ================================================ using ColossalFramework; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Custom.PathFinding; using TrafficManager.State; using UnityEngine; using static TrafficManager.Custom.PathFinding.CustomPathManager; namespace TrafficManager.Traffic.Data { public struct ExtCitizenInstance { public enum ExtPathState { /// /// No path /// None = 0, /// /// Path is currently being calculated /// Calculating = 1, /// /// Path-finding has succeeded /// Ready = 2, /// /// Path-finding has failed /// Failed = 3 } public enum ExtSoftPathState { /// /// No path /// None = 0, /// /// Path is currently being calculated /// Calculating = 1, /// /// Path-finding has succeeded and must be handled appropriately /// Ready = 2, /// /// Path-finding has failed and must be handled appropriately /// FailedHard = 3, /// /// Path-finding must be retried (soft path-find failure) /// FailedSoft = 4, /// /// Path-finding result must not be handled by the citizen because the path will be transferred to a vehicle /// Ignore = 5 } public enum ExtPathType { /// /// Mixed path /// None = 0, /// /// Walking path /// WalkingOnly = 1, /// /// Driving path /// DrivingOnly = 2 } public enum ExtPathMode { None = 0, /// /// Indicates that the citizen requires a walking path to their parked car /// RequiresWalkingPathToParkedCar = 1, /// /// Indicates that a walking path to the parked car is being calculated /// CalculatingWalkingPathToParkedCar = 2, /// /// Indicates that the citizen is walking to their parked car /// WalkingToParkedCar = 3, /// /// Indicates that the citizen is close to their parked car /// ApproachingParkedCar = 4, /// /// Indicates that the citizen has reached their parked car and requires a car path now /// RequiresCarPath = 5, /// /// Indicates that a direct car path to the target is being calculated /// CalculatingCarPathToTarget = 6, /// /// Indicates that a car path to a known parking spot near the target is being calculated /// CalculatingCarPathToKnownParkPos = 7, /// /// Indicates that the citizen is currently driving on a direct path to target /// DrivingToTarget = 8, /// /// Indiciates that the citizen is currently driving to a known parking spot near the target /// DrivingToKnownParkPos = 9, /// /// Indicates that the vehicle is being parked on an alternative parking position /// RequiresWalkingPathToTarget = 10, /// /// Indicates that parking has failed /// ParkingFailed = 11, /// /// Indicates that a path to an alternative parking position is being calculated /// CalculatingCarPathToAltParkPos = 12, /// /// Indicates that the vehicle is on a path to an alternative parking position /// DrivingToAltParkPos = 13, /// /// Indicates that a walking path to target is being calculated /// CalculatingWalkingPathToTarget = 14, /// /// Indicates that the citizen is currently walking to the target /// WalkingToTarget = 15, /// /// (DEPRECATED) Indicates that the citizen is using public transport (bus/train/tram/subway) to reach the target /// __Deprecated__PublicTransportToTarget = 16, /// /// Indicates that the citizen is using a taxi to reach the target /// TaxiToTarget = 17, /// /// Indicates that the driving citizen requires a direct path to target (driving/public transport) /// where possible transitions between different modes of transport happen as required (thus no search /// for parking spaces is performed beforehand) /// RequiresMixedCarPathToTarget = 18, } public enum ExtParkingSpaceLocation { /// /// No parking space location /// None = 0, /// /// Road-side parking space /// RoadSide = 1, /// /// Building parking space /// Building = 2 } public ushort instanceId; /// /// Citizen path mode (used for Parking AI) /// public ExtPathMode pathMode; /// /// Number of times a formerly found parking space is already occupied after reaching its position /// public int failedParkingAttempts; /// /// Segment id / Building id where a parking space has been found /// public ushort parkingSpaceLocationId; /// /// Type of object (segment/building) where a parking space has been found /// public ExtParkingSpaceLocation parkingSpaceLocation; /// /// Path position that is used as a start position when parking fails /// public PathUnit.Position? parkingPathStartPosition; /// /// Walking path from (alternative) parking spot to target (only used to check if there is a valid walking path, not actually used at the moment) /// public uint returnPathId; /// /// State of the return path /// public ExtPathState returnPathState; /// /// Last known distance to the citizen's parked car /// public float lastDistanceToParkedCar; /// /// Specifies whether the last path-finding started at an outside connection /// public bool atOutsideConnection; public override string ToString() { return $"[ExtCitizenInstance\n" + "\t" + $"instanceId = {instanceId}\n" + "\t" + $"pathMode = {pathMode}\n" + "\t" + $"failedParkingAttempts = {failedParkingAttempts}\n" + "\t" + $"parkingSpaceLocationId = {parkingSpaceLocationId}\n" + "\t" + $"parkingSpaceLocation = {parkingSpaceLocation}\n" + "\t" + $"parkingPathStartPosition = {parkingPathStartPosition}\n" + "\t" + $"returnPathId = {returnPathId}\n" + "\t" + $"returnPathState = {returnPathState}\n" + "\t" + $"lastDistanceToParkedCar = {lastDistanceToParkedCar}\n" + "\t" + $"atOutsideConnection = {atOutsideConnection}\n" + "ExtCitizenInstance]"; } internal ExtCitizenInstance(ushort instanceId) { this.instanceId = instanceId; pathMode = ExtPathMode.None; failedParkingAttempts = 0; parkingSpaceLocationId = 0; parkingSpaceLocation = ExtParkingSpaceLocation.None; parkingPathStartPosition = null; returnPathId = 0; returnPathState = ExtPathState.None; lastDistanceToParkedCar = 0; atOutsideConnection = false; } internal bool IsValid() { return Constants.ServiceFactory.CitizenService.IsCitizenInstanceValid(instanceId); } public uint GetCitizenId() { uint ret = 0; Constants.ServiceFactory.CitizenService.ProcessCitizenInstance(instanceId, delegate (ushort citInstId, ref CitizenInstance citizenInst) { ret = citizenInst.m_citizen; return true; }); return ret; } internal void Reset() { #if DEBUG bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId()) && (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == Singleton.instance.m_instances.m_buffer[instanceId].m_sourceBuilding) && (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == Singleton.instance.m_instances.m_buffer[instanceId].m_targetBuilding) ; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; if (fineDebug) { Log.Warning($"ExtCitizenInstance.Reset({instanceId}): Resetting ext. citizen instance {instanceId}"); } #endif //Flags = ExtFlags.None; pathMode = ExtPathMode.None; failedParkingAttempts = 0; parkingSpaceLocation = ExtParkingSpaceLocation.None; parkingSpaceLocationId = 0; lastDistanceToParkedCar = float.MaxValue; atOutsideConnection = false; //ParkedVehiclePosition = default(Vector3); ReleaseReturnPath(); } /// /// Releases the return path /// internal void ReleaseReturnPath() { #if DEBUG bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId()) && (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == Singleton.instance.m_instances.m_buffer[instanceId].m_sourceBuilding) && (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == Singleton.instance.m_instances.m_buffer[instanceId].m_targetBuilding) ; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; #endif if (returnPathId != 0) { #if DEBUG if (debug) Log._Debug($"Releasing return path {returnPathId} of citizen instance {instanceId}. ReturnPathState={returnPathState}"); #endif Singleton.instance.ReleasePath(returnPathId); returnPathId = 0; } returnPathState = ExtPathState.None; } /// /// Checks the calculation state of the return path /// internal void UpdateReturnPathState() { #if DEBUG bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId()) && (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == Singleton.instance.m_instances.m_buffer[instanceId].m_sourceBuilding) && (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == Singleton.instance.m_instances.m_buffer[instanceId].m_targetBuilding) ; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; if (fineDebug) Log._Debug($"ExtCitizenInstance.UpdateReturnPathState() called for citizen instance {instanceId}"); #endif if (returnPathId != 0 && returnPathState == ExtPathState.Calculating) { byte returnPathFlags = CustomPathManager._instance.m_pathUnits.m_buffer[returnPathId].m_pathFindFlags; if ((returnPathFlags & PathUnit.FLAG_READY) != 0) { returnPathState = ExtPathState.Ready; #if DEBUG if (fineDebug) Log._Debug($"CustomHumanAI.CustomSimulationStep: Return path {returnPathId} SUCCEEDED. Flags={returnPathFlags}. Setting ReturnPathState={returnPathState}"); #endif } else if ((returnPathFlags & PathUnit.FLAG_FAILED) != 0) { returnPathState = ExtPathState.Failed; #if DEBUG if (debug) Log._Debug($"CustomHumanAI.CustomSimulationStep: Return path {returnPathId} FAILED. Flags={returnPathFlags}. Setting ReturnPathState={returnPathState}"); #endif } } } /// /// Starts path-finding of the walking path from parking position to target position . /// /// Parking position /// Target position /// internal bool CalculateReturnPath(Vector3 parkPos, Vector3 targetPos) { #if DEBUG bool citDebug = (GlobalConfig.Instance.Debug.CitizenInstanceId == 0 || GlobalConfig.Instance.Debug.CitizenInstanceId == instanceId) && (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId()) && (GlobalConfig.Instance.Debug.SourceBuildingId == 0 || GlobalConfig.Instance.Debug.SourceBuildingId == Singleton.instance.m_instances.m_buffer[instanceId].m_sourceBuilding) && (GlobalConfig.Instance.Debug.TargetBuildingId == 0 || GlobalConfig.Instance.Debug.TargetBuildingId == Singleton.instance.m_instances.m_buffer[instanceId].m_targetBuilding) ; bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; #endif ReleaseReturnPath(); PathUnit.Position parkPathPos; PathUnit.Position targetPathPos = default(PathUnit.Position); bool foundParkPathPos = CustomPathManager.FindCitizenPathPosition(parkPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, false, false, out parkPathPos); bool foundTargetPathPos = foundParkPathPos && CustomPathManager.FindCitizenPathPosition(targetPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, false, false, out targetPathPos); if (foundParkPathPos && foundTargetPathPos) { PathUnit.Position dummyPathPos = default(PathUnit.Position); uint pathId; PathCreationArgs args; args.extPathType = ExtCitizenInstance.ExtPathType.WalkingOnly; args.extVehicleType = ExtVehicleType.None; args.vehicleId = 0; args.spawned = true; args.buildIndex = Singleton.instance.m_currentBuildIndex; args.startPosA = parkPathPos; args.startPosB = dummyPathPos; args.endPosA = targetPathPos; args.endPosB = dummyPathPos; args.vehiclePosition = dummyPathPos; args.laneTypes = NetInfo.LaneType.Pedestrian | NetInfo.LaneType.PublicTransport; args.vehicleTypes = VehicleInfo.VehicleType.None; 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 pathId, ref Singleton.instance.m_randomizer, args)) { #if DEBUG if (debug) Log._Debug($"ExtCitizenInstance.CalculateReturnPath: Path-finding starts for return path of citizen instance {instanceId}, path={pathId}, parkPathPos.segment={parkPathPos.m_segment}, parkPathPos.lane={parkPathPos.m_lane}, targetPathPos.segment={targetPathPos.m_segment}, targetPathPos.lane={targetPathPos.m_lane}"); #endif returnPathId = pathId; returnPathState = ExtPathState.Calculating; return true; } else { #if DEBUG if (debug) Log._Debug($"ExtCitizenInstance.CalculateReturnPath: Could not create return path for citizen instance {instanceId}"); #endif } } else { #if DEBUG if (debug) Log._Debug($"ExtCitizenInstance.CalculateReturnPath: Could not find path position(s) for either the parking position or target position of citizen instance {instanceId}: foundParkPathPos={foundParkPathPos} foundTargetPathPos={foundTargetPathPos}"); #endif } return false; } /// /// Determines the path type through evaluating the current path mode. /// /// public ExtPathType GetPathType() { switch (pathMode) { case ExtPathMode.CalculatingCarPathToAltParkPos: case ExtPathMode.CalculatingCarPathToKnownParkPos: case ExtPathMode.CalculatingCarPathToTarget: case ExtPathMode.DrivingToAltParkPos: case ExtPathMode.DrivingToKnownParkPos: case ExtPathMode.DrivingToTarget: case ExtPathMode.RequiresCarPath: case ExtPathMode.RequiresMixedCarPathToTarget: case ExtPathMode.ParkingFailed: return ExtPathType.DrivingOnly; case ExtPathMode.CalculatingWalkingPathToParkedCar: case ExtPathMode.CalculatingWalkingPathToTarget: case ExtPathMode.RequiresWalkingPathToParkedCar: case ExtPathMode.RequiresWalkingPathToTarget: case ExtPathMode.ApproachingParkedCar: case ExtPathMode.WalkingToParkedCar: case ExtPathMode.WalkingToTarget: return ExtPathType.WalkingOnly; default: return ExtPathType.None; } } /// /// Converts an ExtPathState to a ExtSoftPathState. /// /// /// public static ExtSoftPathState ConvertPathStateToSoftPathState(ExtPathState state) { return (ExtSoftPathState)((int)state); } } } ================================================ FILE: TLM/TLM/Traffic/Data/PrioritySegment.cs ================================================ using System; namespace TrafficManager.Traffic.Data { /// /// A priority segment specifies the priority signs that are present at each end of a certain segment. /// public struct PrioritySegment { public enum PriorityType { None = 0, /// /// Priority road /// Main = 1, /// /// Stop sign /// Stop = 2, /// /// Yield sign /// Yield = 3 } /// /// Priority sign at start node (default: None) /// public PriorityType startType; /// /// Priority sign at end node (default: None) /// public PriorityType endType; public override string ToString() { return $"[PrioritySegment\n" + "\t" + $"startType = {startType}\n" + "\t" + $"endType = {endType}\n" + "PrioritySegment]"; } public PrioritySegment(PriorityType startType, PriorityType endType) { this.startType = startType; this.endType = endType; } public void Reset() { startType = PriorityType.None; endType = PriorityType.None; } public bool IsDefault() { return !HasPrioritySignAtNode(true) && !HasPrioritySignAtNode(false); } public bool HasPrioritySignAtNode(bool startNode) { if (startNode) { return startType != PriorityType.None; } else { return endType != PriorityType.None; } } } } ================================================ FILE: TLM/TLM/Traffic/Data/SegmentEndFlags.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Geometry.Impl; using TrafficManager.Manager; using TrafficManager.State; namespace TrafficManager.Traffic.Data { /// /// Segment end flags store junction restrictions /// public struct SegmentEndFlags { public TernaryBool uturnAllowed; public TernaryBool nearTurnOnRedAllowed; public TernaryBool farTurnOnRedAllowed; public TernaryBool straightLaneChangingAllowed; public TernaryBool enterWhenBlockedAllowed; public TernaryBool pedestrianCrossingAllowed; bool defaultUturnAllowed; bool defaultNearTurnOnRedAllowed; bool defaultFarTurnOnRedAllowed; bool defaultStraightLaneChangingAllowed; bool defaultEnterWhenBlockedAllowed; bool defaultPedestrianCrossingAllowed; public void UpdateDefaults(ushort segmentId, bool startNode, ref NetNode node) { IJunctionRestrictionsManager junctionRestrictionsManager = Constants.ManagerFactory.JunctionRestrictionsManager; if (! junctionRestrictionsManager.IsUturnAllowedConfigurable(segmentId, startNode, ref node)) { uturnAllowed = TernaryBool.Undefined; } if (! junctionRestrictionsManager.IsNearTurnOnRedAllowedConfigurable(segmentId, startNode, ref node)) { nearTurnOnRedAllowed = TernaryBool.Undefined; } if (!junctionRestrictionsManager.IsFarTurnOnRedAllowedConfigurable(segmentId, startNode, ref node)) { farTurnOnRedAllowed = TernaryBool.Undefined; } if (! junctionRestrictionsManager.IsLaneChangingAllowedWhenGoingStraightConfigurable(segmentId, startNode, ref node)) { straightLaneChangingAllowed = TernaryBool.Undefined; } if (! junctionRestrictionsManager.IsEnteringBlockedJunctionAllowedConfigurable(segmentId, startNode, ref node)) { enterWhenBlockedAllowed = TernaryBool.Undefined; } if (! junctionRestrictionsManager.IsPedestrianCrossingAllowedConfigurable(segmentId, startNode, ref node)) { pedestrianCrossingAllowed = TernaryBool.Undefined; } defaultUturnAllowed = junctionRestrictionsManager.GetDefaultUturnAllowed(segmentId, startNode, ref node); defaultNearTurnOnRedAllowed = junctionRestrictionsManager.GetDefaultNearTurnOnRedAllowed(segmentId, startNode, ref node); defaultFarTurnOnRedAllowed = junctionRestrictionsManager.GetDefaultFarTurnOnRedAllowed(segmentId, startNode, ref node); defaultStraightLaneChangingAllowed = junctionRestrictionsManager.GetDefaultLaneChangingAllowedWhenGoingStraight(segmentId, startNode, ref node); defaultEnterWhenBlockedAllowed = junctionRestrictionsManager.GetDefaultEnteringBlockedJunctionAllowed(segmentId, startNode, ref node); defaultPedestrianCrossingAllowed = junctionRestrictionsManager.GetDefaultPedestrianCrossingAllowed(segmentId, startNode, ref node); #if DEBUG if (GlobalConfig.Instance.Debug.Switches[11]) Log._Debug($"SegmentEndFlags.UpdateDefaults({segmentId}, {startNode}): Set defaults: defaultUturnAllowed={defaultUturnAllowed}, defaultNearTurnOnRedAllowed={defaultNearTurnOnRedAllowed}, defaultFarTurnOnRedAllowed={defaultFarTurnOnRedAllowed}, defaultStraightLaneChangingAllowed={defaultStraightLaneChangingAllowed}, defaultEnterWhenBlockedAllowed={defaultEnterWhenBlockedAllowed}, defaultPedestrianCrossingAllowed={defaultPedestrianCrossingAllowed}"); #endif } public bool IsUturnAllowed() { if (uturnAllowed == TernaryBool.Undefined) { return defaultUturnAllowed; } return TernaryBoolUtil.ToBool(uturnAllowed); } public bool IsNearTurnOnRedAllowed() { if (nearTurnOnRedAllowed == TernaryBool.Undefined) { return defaultNearTurnOnRedAllowed; } return TernaryBoolUtil.ToBool(nearTurnOnRedAllowed); } public bool IsFarTurnOnRedAllowed() { if (farTurnOnRedAllowed == TernaryBool.Undefined) { return defaultFarTurnOnRedAllowed; } return TernaryBoolUtil.ToBool(farTurnOnRedAllowed); } public bool IsLaneChangingAllowedWhenGoingStraight() { if (straightLaneChangingAllowed == TernaryBool.Undefined) { return defaultStraightLaneChangingAllowed; } return TernaryBoolUtil.ToBool(straightLaneChangingAllowed); } public bool IsEnteringBlockedJunctionAllowed() { if (enterWhenBlockedAllowed == TernaryBool.Undefined) { return defaultEnterWhenBlockedAllowed; } return TernaryBoolUtil.ToBool(enterWhenBlockedAllowed); } public bool IsPedestrianCrossingAllowed() { if (pedestrianCrossingAllowed == TernaryBool.Undefined) { return defaultPedestrianCrossingAllowed; } return TernaryBoolUtil.ToBool(pedestrianCrossingAllowed); } public void SetUturnAllowed(bool value) { uturnAllowed = TernaryBoolUtil.ToTernaryBool(value); } public void SetNearTurnOnRedAllowed(bool value) { nearTurnOnRedAllowed = TernaryBoolUtil.ToTernaryBool(value); } public void SetFarTurnOnRedAllowed(bool value) { farTurnOnRedAllowed = TernaryBoolUtil.ToTernaryBool(value); } public void SetLaneChangingAllowedWhenGoingStraight(bool value) { straightLaneChangingAllowed = TernaryBoolUtil.ToTernaryBool(value); } public void SetEnteringBlockedJunctionAllowed(bool value) { enterWhenBlockedAllowed = TernaryBoolUtil.ToTernaryBool(value); } public void SetPedestrianCrossingAllowed(bool value) { pedestrianCrossingAllowed = TernaryBoolUtil.ToTernaryBool(value); } public bool IsDefault() { bool uturnIsDefault = uturnAllowed == TernaryBool.Undefined || TernaryBoolUtil.ToBool(uturnAllowed) == defaultUturnAllowed; bool nearTurnOnRedIsDefault = nearTurnOnRedAllowed == TernaryBool.Undefined || TernaryBoolUtil.ToBool(nearTurnOnRedAllowed) == defaultNearTurnOnRedAllowed; bool farTurnOnRedIsDefault = farTurnOnRedAllowed == TernaryBool.Undefined || TernaryBoolUtil.ToBool(farTurnOnRedAllowed) == defaultFarTurnOnRedAllowed; bool straightChangeIsDefault = straightLaneChangingAllowed == TernaryBool.Undefined || TernaryBoolUtil.ToBool(straightLaneChangingAllowed) == defaultStraightLaneChangingAllowed; bool enterWhenBlockedIsDefault = enterWhenBlockedAllowed == TernaryBool.Undefined || TernaryBoolUtil.ToBool(enterWhenBlockedAllowed) == defaultEnterWhenBlockedAllowed; bool pedCrossingIsDefault = pedestrianCrossingAllowed == TernaryBool.Undefined || TernaryBoolUtil.ToBool(pedestrianCrossingAllowed) == defaultPedestrianCrossingAllowed; return uturnIsDefault && nearTurnOnRedIsDefault && farTurnOnRedIsDefault && straightChangeIsDefault && enterWhenBlockedIsDefault && pedCrossingIsDefault; } public void Reset(bool resetDefaults=true) { uturnAllowed = TernaryBool.Undefined; nearTurnOnRedAllowed = TernaryBool.Undefined; farTurnOnRedAllowed = TernaryBool.Undefined; straightLaneChangingAllowed = TernaryBool.Undefined; enterWhenBlockedAllowed = TernaryBool.Undefined; pedestrianCrossingAllowed = TernaryBool.Undefined; if (resetDefaults) { defaultUturnAllowed = false; defaultNearTurnOnRedAllowed = false; defaultFarTurnOnRedAllowed = false; defaultStraightLaneChangingAllowed = false; defaultEnterWhenBlockedAllowed = false; defaultPedestrianCrossingAllowed = false; } } public override string ToString() { return $"[SegmentEndFlags\n" + "\t" + $"uturnAllowed = {uturnAllowed}\n" + "\t" + $"nearTurnOnRedAllowed = {nearTurnOnRedAllowed}\n" + "\t" + $"farTurnOnRedAllowed = {farTurnOnRedAllowed}\n" + "\t" + $"straightLaneChangingAllowed = {straightLaneChangingAllowed}\n" + "\t" + $"enterWhenBlockedAllowed = {enterWhenBlockedAllowed}\n" + "\t" + $"pedestrianCrossingAllowed = {pedestrianCrossingAllowed}\n" + "SegmentEndFlags]"; } } } ================================================ FILE: TLM/TLM/Traffic/Data/SegmentFlags.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Geometry.Impl; using TrafficManager.State; namespace TrafficManager.Traffic.Data { /// /// Segment flags hold both segment end flags /// public struct SegmentFlags { public SegmentEndFlags startNodeFlags; public SegmentEndFlags endNodeFlags; public bool IsUturnAllowed(bool startNode) { return startNode ? startNodeFlags.IsUturnAllowed() : endNodeFlags.IsUturnAllowed(); } public bool IsNearTurnOnRedAllowed(bool startNode) { return startNode ? startNodeFlags.IsNearTurnOnRedAllowed() : endNodeFlags.IsNearTurnOnRedAllowed(); } public bool IsFarTurnOnRedAllowed(bool startNode) { return startNode ? startNodeFlags.IsFarTurnOnRedAllowed() : endNodeFlags.IsFarTurnOnRedAllowed(); } public bool IsLaneChangingAllowedWhenGoingStraight(bool startNode) { return startNode ? startNodeFlags.IsLaneChangingAllowedWhenGoingStraight() : endNodeFlags.IsLaneChangingAllowedWhenGoingStraight(); } public bool IsEnteringBlockedJunctionAllowed(bool startNode) { return startNode ? startNodeFlags.IsEnteringBlockedJunctionAllowed() : endNodeFlags.IsEnteringBlockedJunctionAllowed(); } public bool IsPedestrianCrossingAllowed(bool startNode) { return startNode ? startNodeFlags.IsPedestrianCrossingAllowed() : endNodeFlags.IsPedestrianCrossingAllowed(); } public TernaryBool GetUturnAllowed(bool startNode) { return startNode ? startNodeFlags.uturnAllowed : endNodeFlags.uturnAllowed; } public TernaryBool GetNearTurnOnRedAllowed(bool startNode) { return startNode ? startNodeFlags.nearTurnOnRedAllowed : endNodeFlags.nearTurnOnRedAllowed; } public TernaryBool GetFarTurnOnRedAllowed(bool startNode) { return startNode ? startNodeFlags.farTurnOnRedAllowed : endNodeFlags.farTurnOnRedAllowed; } public TernaryBool GetLaneChangingAllowedWhenGoingStraight(bool startNode) { return startNode ? startNodeFlags.straightLaneChangingAllowed : endNodeFlags.straightLaneChangingAllowed; } public TernaryBool GetEnteringBlockedJunctionAllowed(bool startNode) { return startNode ? startNodeFlags.enterWhenBlockedAllowed : endNodeFlags.enterWhenBlockedAllowed; } public TernaryBool GetPedestrianCrossingAllowed(bool startNode) { return startNode ? startNodeFlags.pedestrianCrossingAllowed : endNodeFlags.pedestrianCrossingAllowed; } public void SetUturnAllowed(bool startNode, bool value) { if (startNode) { startNodeFlags.SetUturnAllowed(value); } else { endNodeFlags.SetUturnAllowed(value); } } public void SetNearTurnOnRedAllowed(bool startNode, bool value) { if (startNode) { startNodeFlags.SetNearTurnOnRedAllowed(value); } else { endNodeFlags.SetNearTurnOnRedAllowed(value); } } public void SetFarTurnOnRedAllowed(bool startNode, bool value) { if (startNode) { startNodeFlags.SetFarTurnOnRedAllowed(value); } else { endNodeFlags.SetFarTurnOnRedAllowed(value); } } public void SetLaneChangingAllowedWhenGoingStraight(bool startNode, bool value) { if (startNode) { startNodeFlags.SetLaneChangingAllowedWhenGoingStraight(value); } else { endNodeFlags.SetLaneChangingAllowedWhenGoingStraight(value); } } public void SetEnteringBlockedJunctionAllowed(bool startNode, bool value) { if (startNode) { startNodeFlags.SetEnteringBlockedJunctionAllowed(value); } else { endNodeFlags.SetEnteringBlockedJunctionAllowed(value); } } public void SetPedestrianCrossingAllowed(bool startNode, bool value) { if (startNode) { startNodeFlags.SetPedestrianCrossingAllowed(value); } else { endNodeFlags.SetPedestrianCrossingAllowed(value); } } public bool IsDefault() { return startNodeFlags.IsDefault() && endNodeFlags.IsDefault(); } public void Reset(bool? startNode=null, bool resetDefaults=true) { if (startNode == null || (bool)startNode) { startNodeFlags.Reset(resetDefaults); } if (startNode == null || ! (bool)startNode) { endNodeFlags.Reset(resetDefaults); } } public override string ToString() { return $"[SegmentFlags\n" + "\t" + $"startNodeFlags = {startNodeFlags}\n" + "\t" + $"endNodeFlags = {endNodeFlags}\n" + "SegmentFlags]"; } } } ================================================ FILE: TLM/TLM/Traffic/Data/TurnOnRedSegments.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Traffic.Data { /** * Holds left/right turn-on-red candidate segments */ public struct TurnOnRedSegments { /** * Left segment id (or 0 if no left turn-on-red candidate segment) */ public ushort leftSegmentId; /** * Right segment id (or 0 if no right turn-on-red candidate segment) */ public ushort rightSegmentId; public void Reset() { this.leftSegmentId = 0; this.rightSegmentId = 0; } public override string ToString() { return $"[TurnOnRedSegments {base.ToString()}\n" + "\t" + $"leftSegmentId = {leftSegmentId}\n" + "\t" + $"rightSegmentId = {rightSegmentId}\n" + "SegmentEnd]"; } } } ================================================ FILE: TLM/TLM/Traffic/Data/VehicleState.cs ================================================ #define DEBUGVSTATEx #define DEBUGREGx using System; using ColossalFramework; using UnityEngine; using System.Collections.Generic; using TrafficManager.TrafficLight; using TrafficManager.Traffic; using TrafficManager.Manager; using TrafficManager.Custom.AI; using TrafficManager.State; using CSUtil.Commons; using TrafficManager.Manager.Impl; using ColossalFramework.Math; namespace TrafficManager.Traffic.Data { public struct VehicleState { public const int STATE_UPDATE_SHIFT = 6; public const int JUNCTION_RECHECK_SHIFT = 4; public const uint MAX_TIMED_RAND = 100; [Flags] public enum Flags { None = 0, Created = 1, Spawned = 1 << 1 } public VehicleJunctionTransitState JunctionTransitState { get { return junctionTransitState; } set { if (value != junctionTransitState) { lastTransitStateUpdate = Now(); } junctionTransitState = value; } } public float SqrVelocity { get { float ret = 0; Constants.ServiceFactory.VehicleService.ProcessVehicle(vehicleId, delegate (ushort vehId, ref Vehicle veh) { ret = veh.GetLastFrameVelocity().sqrMagnitude; return true; }); return ret; } } public float Velocity { get { float ret = 0; Constants.ServiceFactory.VehicleService.ProcessVehicle(vehicleId, delegate (ushort vehId, ref Vehicle veh) { ret = veh.GetLastFrameVelocity().magnitude; return true; }); return ret; } } /*public bool Valid { get { if ((Singleton.instance.m_vehicles.m_buffer[vehicleId].m_flags & Vehicle.Flags.Created) == 0) { return false; } return valid; } internal set { valid = value; } }*/ public ushort vehicleId; public uint lastPathId; public byte lastPathPositionIndex; public uint lastTransitStateUpdate; public uint lastPositionUpdate; public float totalLength; //public float sqrVelocity; //public float velocity; public int waitTime; public float reduceSqrSpeedByValueToYield; public Flags flags; public ExtVehicleType vehicleType; public bool heavyVehicle; public bool recklessDriver; public ushort currentSegmentId; public bool currentStartNode; public byte currentLaneIndex; public ushort nextSegmentId; public byte nextLaneIndex; public ushort previousVehicleIdOnSegment; public ushort nextVehicleIdOnSegment; public ushort lastAltLaneSelSegmentId; public byte timedRand; private VehicleJunctionTransitState junctionTransitState; public override string ToString() { return $"[VehicleState\n" + "\t" + $"vehicleId = {vehicleId}\n" + "\t" + $"lastPathId = {lastPathId}\n" + "\t" + $"lastPathPositionIndex = {lastPathPositionIndex}\n" + "\t" + $"JunctionTransitState = {JunctionTransitState}\n" + "\t" + $"lastTransitStateUpdate = {lastTransitStateUpdate}\n" + "\t" + $"lastPositionUpdate = {lastPositionUpdate}\n" + "\t" + $"totalLength = {totalLength}\n" + //"\t" + $"velocity = {velocity}\n" + //"\t" + $"sqrVelocity = {sqrVelocity}\n" + "\t" + $"waitTime = {waitTime}\n" + "\t" + $"reduceSqrSpeedByValueToYield = {reduceSqrSpeedByValueToYield}\n" + "\t" + $"flags = {flags}\n" + "\t" + $"vehicleType = {vehicleType}\n" + "\t" + $"heavyVehicle = {heavyVehicle}\n" + "\t" + $"recklessDriver = {recklessDriver}\n" + "\t" + $"currentSegmentId = {currentSegmentId}\n" + "\t" + $"currentStartNode = {currentStartNode}\n" + "\t" + $"currentLaneIndex = {currentLaneIndex}\n" + "\t" + $"nextSegmentId = {nextSegmentId}\n" + "\t" + $"nextLaneIndex = {nextLaneIndex}\n" + "\t" + $"previousVehicleIdOnSegment = {previousVehicleIdOnSegment}\n" + "\t" + $"nextVehicleIdOnSegment = {nextVehicleIdOnSegment}\n" + "\t" + $"lastAltLaneSelSegmentId = {lastAltLaneSelSegmentId}\n" + "\t" + $"junctionTransitState = {junctionTransitState}\n" + "\t" + $"timedRand = {timedRand}\n" + "VehicleState]"; } internal VehicleState(ushort vehicleId) { this.vehicleId = vehicleId; lastPathId = 0; lastPathPositionIndex = 0; lastTransitStateUpdate = Now(); lastPositionUpdate = Now(); totalLength = 0; waitTime = 0; reduceSqrSpeedByValueToYield = 0; flags = Flags.None; vehicleType = ExtVehicleType.None; heavyVehicle = false; recklessDriver = false; currentSegmentId = 0; currentStartNode = false; currentLaneIndex = 0; nextSegmentId = 0; nextLaneIndex = 0; previousVehicleIdOnSegment = 0; nextVehicleIdOnSegment = 0; //velocity = 0; //sqrVelocity = 0; lastAltLaneSelSegmentId = 0; junctionTransitState = VehicleJunctionTransitState.None; timedRand = 0; } /*private void Reset(bool unlink=true) { // TODO this is called in wrong places! if (unlink) Unlink(); Valid = false; totalLength = 0f; //VehicleType = ExtVehicleType.None; waitTime = 0; JunctionTransitState = VehicleJunctionTransitState.None; lastStateUpdate = 0; }*/ /*public ExtCitizenInstance GetDriverExtInstance() { ushort driverInstanceId = CustomPassengerCarAI.GetDriverInstance(vehicleId, ref Singleton.instance.m_vehicles.m_buffer[vehicleId]); if (driverInstanceId != 0) { return ExtCitizenInstanceManager.Instance.GetExtInstance(driverInstanceId); } return null; }*/ internal void Unlink() { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.Unlink({vehicleId}) called: Unlinking vehicle from all segment ends\nstate:{this}"); #endif IVehicleStateManager vehStateManager = Constants.ManagerFactory.VehicleStateManager; lastPositionUpdate = Now(); if (previousVehicleIdOnSegment != 0) { vehStateManager.SetNextVehicleIdOnSegment(previousVehicleIdOnSegment, nextVehicleIdOnSegment);// VehicleStates[previousVehicleIdOnSegment].nextVehicleIdOnSegment = nextVehicleIdOnSegment; } else if (currentSegmentId != 0) { ISegmentEnd curEnd = Constants.ManagerFactory.SegmentEndManager.GetSegmentEnd(currentSegmentId, currentStartNode); if (curEnd != null && curEnd.FirstRegisteredVehicleId == vehicleId) { curEnd.FirstRegisteredVehicleId = nextVehicleIdOnSegment; } } if (nextVehicleIdOnSegment != 0) { vehStateManager.SetPreviousVehicleIdOnSegment(nextVehicleIdOnSegment, previousVehicleIdOnSegment);// .VehicleStates[nextVehicleIdOnSegment].previousVehicleIdOnSegment = previousVehicleIdOnSegment; } nextVehicleIdOnSegment = 0; previousVehicleIdOnSegment = 0; currentSegmentId = 0; currentStartNode = false; currentLaneIndex = 0; lastPathId = 0; lastPathPositionIndex = 0; #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.Unlink({vehicleId}) finished: Unlinked vehicle from all segment ends\nstate:{this}"); #endif } private void Link(ISegmentEnd end) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.Link({vehicleId}) called: Linking vehicle to segment end {end}\nstate:{this}"); #endif ushort oldFirstRegVehicleId = end.FirstRegisteredVehicleId; if (oldFirstRegVehicleId != 0) { Constants.ManagerFactory.VehicleStateManager.SetPreviousVehicleIdOnSegment(oldFirstRegVehicleId, vehicleId);// VehicleStates[oldFirstRegVehicleId].previousVehicleIdOnSegment = vehicleId; nextVehicleIdOnSegment = oldFirstRegVehicleId; } end.FirstRegisteredVehicleId = vehicleId; #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.Link({vehicleId}) finished: Linked vehicle to segment end {end}\nstate:{this}"); #endif } internal void OnCreate(ref Vehicle vehicleData) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnCreate({vehicleId}) called: {this}"); #endif if ((flags & Flags.Created) != Flags.None) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnCreate({vehicleId}): Vehicle is already created."); #endif OnRelease(ref vehicleData); } DetermineVehicleType(ref vehicleData); reduceSqrSpeedByValueToYield = UnityEngine.Random.Range(256f, 784f); recklessDriver = false; flags = Flags.Created; #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnCreate({vehicleId}) finished: {this}"); #endif } internal ExtVehicleType OnStartPathFind(ref Vehicle vehicleData, ExtVehicleType? vehicleType) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnStartPathFind({vehicleId}, {vehicleType}) called: {this}"); #endif if ((flags & Flags.Created) == Flags.None) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnStartPathFind({vehicleId}, {vehicleType}): Vehicle has not yet been created."); #endif OnCreate(ref vehicleData); } if (vehicleType != null) { this.vehicleType = (ExtVehicleType)vehicleType; } recklessDriver = Constants.ManagerFactory.VehicleBehaviorManager.IsRecklessDriver(vehicleId, ref vehicleData); #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnStartPathFind({vehicleId}, {vehicleType}) finished: {this}"); #endif return this.vehicleType; } internal void OnSpawn(ref Vehicle vehicleData) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnSpawn({vehicleId}) called: {this}"); #endif if ((flags & Flags.Created) == Flags.None) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnSpawn({vehicleId}): Vehicle has not yet been created."); #endif OnCreate(ref vehicleData); } Unlink(); //velocity = 0; //sqrVelocity = 0; lastPathId = 0; lastPathPositionIndex = 0; lastAltLaneSelSegmentId = 0; recklessDriver = Constants.ManagerFactory.VehicleBehaviorManager.IsRecklessDriver(vehicleId, ref vehicleData); try { totalLength = vehicleData.CalculateTotalLength(vehicleId); } catch (Exception #if DEBUG e #endif ) { totalLength = 0; #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnSpawn({vehicleId}): Error occurred while calculating total length: {e}\nstate: {this}"); #endif return; } flags |= Flags.Spawned; #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnSpawn({vehicleId}) finished: {this}"); #endif } internal void UpdatePosition(ref Vehicle vehicleData, ref PathUnit.Position curPos, ref PathUnit.Position nextPos, bool skipCheck = false) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.UpdatePosition({vehicleId}) called: {this}"); #endif if ((flags & Flags.Spawned) == Flags.None) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.UpdatePosition({vehicleId}): Vehicle is not yet spawned."); #endif OnSpawn(ref vehicleData); } if (nextSegmentId != nextPos.m_segment || nextLaneIndex != nextPos.m_lane) { nextSegmentId = nextPos.m_segment; nextLaneIndex = nextPos.m_lane; } bool startNode = IsTransitNodeCurStartNode(ref curPos, ref nextPos); ISegmentEnd end = Constants.ManagerFactory.SegmentEndManager.GetSegmentEnd(curPos.m_segment, startNode); if (end == null || currentSegmentId != end.SegmentId || currentStartNode != end.StartNode || currentLaneIndex != curPos.m_lane) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.UpdatePosition({vehicleId}): Current segment end changed. seg. {currentSegmentId}, start {currentStartNode}, lane {currentLaneIndex} -> seg. {end?.SegmentId}, start {end?.StartNode}, lane {curPos.m_lane}"); #endif if (currentSegmentId != 0) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.UpdatePosition({vehicleId}): Unlinking from current segment end"); #endif Unlink(); } lastPathId = vehicleData.m_path; lastPathPositionIndex = vehicleData.m_pathPositionIndex; currentSegmentId = curPos.m_segment; currentStartNode = startNode; currentLaneIndex = curPos.m_lane; waitTime = 0; if (end != null) { #if DEBUGVSTATE if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.UpdatePosition({vehicleId}): Linking vehicle to segment end {end.SegmentId} @ {end.StartNode} ({end.NodeId}). Current position: Seg. {curPos.m_segment}, lane {curPos.m_lane}, offset {curPos.m_offset} / Next position: Seg. {nextPos.m_segment}, lane {nextPos.m_lane}, offset {nextPos.m_offset}"); #endif Link(end); JunctionTransitState = VehicleJunctionTransitState.Approach; } else { JunctionTransitState = VehicleJunctionTransitState.None; } } #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.UpdatePosition({vehicleId}) finshed: {this}"); #endif } internal void OnDespawn() { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnDespawn({vehicleId} called: {this}"); #endif if ((flags & Flags.Spawned) == Flags.None) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnDespawn({vehicleId}): Vehicle is not spawned."); #endif return; } Constants.ManagerFactory.ExtCitizenInstanceManager.ResetInstance(CustomPassengerCarAI.GetDriverInstanceId(vehicleId, ref Singleton.instance.m_vehicles.m_buffer[vehicleId])); Unlink(); currentSegmentId = 0; currentStartNode = false; currentLaneIndex = 0; lastAltLaneSelSegmentId = 0; recklessDriver = false; nextSegmentId = 0; nextLaneIndex = 0; totalLength = 0; flags &= ~Flags.Spawned; #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnDespawn({vehicleId}) finished: {this}"); #endif } internal void OnRelease(ref Vehicle vehicleData) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnRelease({vehicleId}) called: {this}"); #endif if ((flags & Flags.Created) == Flags.None) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnRelease({vehicleId}): Vehicle is not created."); #endif return; } if ((flags & Flags.Spawned) != Flags.None) { #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnRelease({vehicleId}): Vehicle is spawned."); #endif OnDespawn(); } lastPathId = 0; lastPathPositionIndex = 0; lastTransitStateUpdate = Now(); lastPositionUpdate = Now(); waitTime = 0; reduceSqrSpeedByValueToYield = 0; flags = Flags.None; vehicleType = ExtVehicleType.None; heavyVehicle = false; previousVehicleIdOnSegment = 0; nextVehicleIdOnSegment = 0; lastAltLaneSelSegmentId = 0; junctionTransitState = VehicleJunctionTransitState.None; recklessDriver = false; #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.OnRelease({vehicleId}) finished: {this}"); #endif } /// /// Determines if the junction transit state has been recently modified /// /// internal bool IsJunctionTransitStateNew() { uint frame = Constants.ServiceFactory.SimulationService.CurrentFrameIndex; return (lastTransitStateUpdate >> STATE_UPDATE_SHIFT) >= (frame >> STATE_UPDATE_SHIFT); } public void StepRand() { Randomizer rand = Constants.ServiceFactory.SimulationService.Randomizer; if (rand.UInt32(20) == 0) { timedRand = (byte)(((uint)timedRand + rand.UInt32(25)) % MAX_TIMED_RAND); } } private static ushort GetTransitNodeId(ref PathUnit.Position curPos, ref PathUnit.Position nextPos) { bool startNode = IsTransitNodeCurStartNode(ref curPos, ref nextPos); ushort transitNodeId1 = 0; Constants.ServiceFactory.NetService.ProcessSegment(curPos.m_segment, delegate (ushort segmentId, ref NetSegment segment) { transitNodeId1 = startNode ? segment.m_startNode : segment.m_endNode; return true; }); ushort transitNodeId2 = 0; Constants.ServiceFactory.NetService.ProcessSegment(nextPos.m_segment, delegate (ushort segmentId, ref NetSegment segment) { transitNodeId2 = startNode ? segment.m_startNode : segment.m_endNode; return true; }); if (transitNodeId1 != transitNodeId2) { return 0; } return transitNodeId1; } private static bool IsTransitNodeCurStartNode(ref PathUnit.Position curPos, ref PathUnit.Position nextPos) { // note: does not check if curPos and nextPos are successive path positions bool startNode; if (curPos.m_offset == 0) { startNode = true; } else if (curPos.m_offset == 255) { startNode = false; } else if (nextPos.m_offset == 0) { startNode = true; } else { startNode = false; } return startNode; } private static uint Now() { return Constants.ServiceFactory.SimulationService.CurrentFrameIndex; } private void DetermineVehicleType(ref Vehicle vehicleData) { VehicleAI ai = vehicleData.Info.m_vehicleAI; if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0) { vehicleType = ExtVehicleType.Emergency; } else { ExtVehicleType? type = DetermineVehicleTypeFromAIType(ai, false); if (type != null) { vehicleType = (ExtVehicleType)type; } else { vehicleType = ExtVehicleType.None; } } if (vehicleType == ExtVehicleType.CargoTruck) { heavyVehicle = ((CargoTruckAI)ai).m_isHeavyVehicle; } else { heavyVehicle = false; } #if DEBUG if (GlobalConfig.Instance.Debug.Switches[9]) Log._Debug($"VehicleState.DetermineVehicleType({vehicleId}): vehicleType={vehicleType}, heavyVehicle={heavyVehicle}. Info={vehicleData.Info?.name}"); #endif } private ExtVehicleType? DetermineVehicleTypeFromAIType(VehicleAI ai, bool emergencyOnDuty) { if (emergencyOnDuty) return ExtVehicleType.Emergency; switch (ai.m_info.m_vehicleType) { case VehicleInfo.VehicleType.Bicycle: return ExtVehicleType.Bicycle; case VehicleInfo.VehicleType.Car: if (ai is PassengerCarAI) return ExtVehicleType.PassengerCar; if (ai is AmbulanceAI || ai is FireTruckAI || ai is PoliceCarAI || ai is HearseAI || ai is GarbageTruckAI || ai is MaintenanceTruckAI || ai is SnowTruckAI || ai is WaterTruckAI || ai is DisasterResponseVehicleAI || ai is ParkMaintenanceVehicleAI || ai is PostVanAI) { return ExtVehicleType.Service; } if (ai is CarTrailerAI) return ExtVehicleType.None; if (ai is BusAI) return ExtVehicleType.Bus; if (ai is TaxiAI) return ExtVehicleType.Taxi; if (ai is CargoTruckAI) return ExtVehicleType.CargoTruck; break; case VehicleInfo.VehicleType.Metro: case VehicleInfo.VehicleType.Train: case VehicleInfo.VehicleType.Monorail: if (ai is CargoTrainAI) return ExtVehicleType.CargoTrain; return ExtVehicleType.PassengerTrain; case VehicleInfo.VehicleType.Tram: return ExtVehicleType.Tram; case VehicleInfo.VehicleType.Ship: if (ai is PassengerShipAI) return ExtVehicleType.PassengerShip; //if (ai is CargoShipAI) return ExtVehicleType.CargoShip; //break; case VehicleInfo.VehicleType.Plane: if (ai is PassengerPlaneAI) return ExtVehicleType.PassengerPlane; if (ai is CargoPlaneAI) return ExtVehicleType.CargoPlane; break; case VehicleInfo.VehicleType.Helicopter: //if (ai is PassengerPlaneAI) return ExtVehicleType.Helicopter; //break; case VehicleInfo.VehicleType.Ferry: return ExtVehicleType.Ferry; case VehicleInfo.VehicleType.Blimp: return ExtVehicleType.Blimp; case VehicleInfo.VehicleType.CableCar: return ExtVehicleType.CableCar; } #if DEBUGVSTATE Log._Debug($"VehicleState.DetermineVehicleType({vehicleId}): Could not determine vehicle type from ai type: {ai.GetType().ToString()}"); #endif return null; } } } ================================================ FILE: TLM/TLM/Traffic/ExtVehicleType.cs ================================================ using System; using System.Collections.Generic; using System.Text; namespace TrafficManager.Traffic { [Flags] public enum ExtVehicleType { None = 0, PassengerCar = 1, Bus = 1 << 1, Taxi = 1 << 2, CargoTruck = 1 << 3, Service = 1 << 4, Emergency = 1 << 5, PassengerTrain = 1 << 6, CargoTrain = 1 << 7, Tram = 1 << 8, Bicycle = 1 << 9, Pedestrian = 1 << 10, PassengerShip = 1 << 11, CargoShip = 1 << 12, PassengerPlane = 1 << 13, Helicopter = 1 << 14, CableCar = 1 << 15, PassengerFerry = 1 << 16, PassengerBlimp = 1 << 17, CargoPlane = 1 << 18, Plane = PassengerPlane | CargoPlane, Ship = PassengerShip | CargoShip, CargoVehicle = CargoTruck | CargoTrain | CargoShip | CargoPlane, PublicTransport = Bus | Taxi | Tram | PassengerTrain, RoadPublicTransport = Bus | Taxi, RoadVehicle = PassengerCar | Bus | Taxi | CargoTruck | Service | Emergency, RailVehicle = PassengerTrain | CargoTrain, NonTransportRoadVehicle = RoadVehicle & ~PublicTransport, Ferry = PassengerFerry, Blimp = PassengerBlimp } } ================================================ FILE: TLM/TLM/Traffic/ISegmentEnd.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Geometry; namespace TrafficManager.Traffic { public interface ISegmentEnd : ISegmentEndId { [Obsolete] ushort NodeId { get; } ushort FirstRegisteredVehicleId { get; set; } // TODO private set void Update(); void Destroy(); IDictionary[] MeasureOutgoingVehicles(bool includeStopped = true, bool debug = false); int GetRegisteredVehicleCount(); } } ================================================ FILE: TLM/TLM/Traffic/Impl/SegmentEnd.cs ================================================ #define DEBUGFRONTVEHx #define DEBUGREGx #define DEBUGMETRIC #define DEBUGMETRIC2x using System; using System.Collections.Generic; using ColossalFramework; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using TrafficManager.Custom.AI; using TrafficManager.Util; using System.Threading; using TrafficManager.State; using TrafficManager.UI; using TrafficManager.Manager; using System.Linq; using CSUtil.Commons; using TrafficManager.Geometry.Impl; using TrafficManager.Manager.Impl; using TrafficManager.Traffic.Data; /// /// A segment end describes a directional traffic segment connected to a controlled node /// (having custom traffic lights or priority signs). /// namespace TrafficManager.Traffic.Impl { public class SegmentEnd : SegmentEndId, ISegmentEnd { // TODO convert to struct [Obsolete] public ushort NodeId { get { return Constants.ServiceFactory.NetService.GetSegmentNodeId(SegmentId, StartNode); } } private int numLanes = 0; /// /// Vehicles that are traversing or will traverse this segment /// public ushort FirstRegisteredVehicleId { get; set; } = 0; // TODO private set private bool cleanupRequested = false; /// /// Vehicles that are traversing or will traverse this segment /// //private ushort[] frontVehicleIds; /// /// Number of vehicles / vehicle length going to a certain segment. /// First key: source lane index, second key: target segment id, value: total normalized vehicle length /// private IDictionary[] numVehiclesMovingToSegmentId; // minimum speed required private IDictionary[] numVehiclesGoingToSegmentId; // no minimum speed required public override string ToString() { return $"[SegmentEnd {base.ToString()}\n" + "\t" + $"NodeId = {NodeId}\n" + "\t" + $"numLanes = {numLanes}\n" + "\t" + $"FirstRegisteredVehicleId = {FirstRegisteredVehicleId}\n" + "\t" + $"cleanupRequested = {cleanupRequested}\n" + "\t" + $"numVehiclesMovingToSegmentId = " + (numVehiclesMovingToSegmentId == null ? "" : numVehiclesMovingToSegmentId.ArrayToString()) + "\n" + "\t" + $"numVehiclesGoingToSegmentId = " + (numVehiclesGoingToSegmentId == null ? "" : numVehiclesGoingToSegmentId.ArrayToString()) + "\n" + "SegmentEnd]"; } public SegmentEnd(ushort segmentId, bool startNode) : base(segmentId, startNode) { FirstRegisteredVehicleId = 0; Update(); } ~SegmentEnd() { Destroy(); } /// /// Calculates for each segment the number of cars going to this segment. /// We use integer arithmetic for better performance. /// public IDictionary[] MeasureOutgoingVehicles(bool includeStopped=true, bool debug = false) { //VehicleManager vehicleManager = Singleton.instance; //NetManager netManager = Singleton.instance; VehicleStateManager vehStateManager = VehicleStateManager.Instance; // TODO pre-calculate this uint avgSegLen = 0; Constants.ServiceFactory.NetService.ProcessSegment(SegmentId, delegate (ushort segmentId, ref NetSegment segment) { avgSegLen = (uint)segment.m_averageLength; return true; }); IDictionary[] ret = includeStopped ? numVehiclesGoingToSegmentId : numVehiclesMovingToSegmentId; // reset for (byte laneIndex = 0; laneIndex < ret.Length; ++laneIndex) { IDictionary laneMetrics = ret[laneIndex]; foreach (KeyValuePair e in laneMetrics) { laneMetrics[e.Key] = 0; } } #if DEBUGMETRIC if (debug) Log._Debug($"GetVehicleMetricGoingToSegment: Segment {SegmentId}, Node {NodeId}, includeStopped={includeStopped}."); #endif ushort vehicleId = FirstRegisteredVehicleId; int numProcessed = 0; while (vehicleId != 0) { MeasureOutgoingVehicle(debug, ret, includeStopped, avgSegLen, vehicleId, ref vehStateManager.VehicleStates[vehicleId], ref numProcessed); if ((Options.simAccuracy >= 3 && numProcessed >= 3) || (Options.simAccuracy == 2 && numProcessed >= 5) || (Options.simAccuracy == 1 && numProcessed >= 10)) { break; } vehicleId = vehStateManager.VehicleStates[vehicleId].nextVehicleIdOnSegment; } #if DEBUGMETRIC if (debug) Log._Debug($"GetVehicleMetricGoingToSegment: Calculation completed. {string.Join(", ", ret.Select(e => "[" + string.Join(", ", e.Select(x => x.Key.ToString() + "=" + x.Value.ToString()).ToArray()) + "]").ToArray())}"); #endif return ret; } protected void MeasureOutgoingVehicle(bool debug, IDictionary[] ret, bool includeStopped, uint avgSegmentLength, ushort vehicleId, ref VehicleState state, ref int numProcessed) { #if DEBUGMETRIC if (debug) Log._Debug($" MeasureOutgoingVehicle: (Segment {SegmentId}, Node {NodeId} (start={StartNode})) Checking vehicle {vehicleId}. Coming from seg. {state.currentSegmentId}, start {state.currentStartNode}, lane {state.currentLaneIndex} going to seg. {state.nextSegmentId}, lane {state.nextLaneIndex}"); #endif if ((state.flags & VehicleState.Flags.Spawned) == VehicleState.Flags.None) { #if DEBUGMETRIC if (debug) Log._Debug($" MeasureOutgoingVehicle: Vehicle {vehicleId} is unspawned. Ignoring."); #endif return; } #if DEBUGMETRIC if (state.currentSegmentId != SegmentId || state.currentStartNode != StartNode) { if (debug) Log._Debug($" MeasureOutgoingVehicle: (Segment {SegmentId}, Node {NodeId} (start={StartNode})) Vehicle {vehicleId} error: Segment end mismatch! {state.ToString()}"); //RequestCleanup(); return; } #endif if (state.nextSegmentId == 0) { #if DEBUGMETRIC if (debug) Log._Debug($" MeasureOutgoingVehicle: (Segment {SegmentId}, Node {NodeId} (start={StartNode})) Vehicle {vehicleId}: Ignoring vehicle"); #endif return; } if (state.currentLaneIndex >= ret.Length || !ret[state.currentLaneIndex].ContainsKey(state.nextSegmentId)) { #if DEBUGMETRIC if (debug) Log._Debug($" MeasureOutgoingVehicle: (Segment {SegmentId}, Node {NodeId} (start={StartNode})) Vehicle {vehicleId} is on lane {state.currentLaneIndex} and wants to go to segment {state.nextSegmentId} but one or both are invalid: {ret.CollectionToString()}"); #endif return; } if (!includeStopped && state.SqrVelocity < GlobalConfig.Instance.PriorityRules.MaxStopVelocity * GlobalConfig.Instance.PriorityRules.MaxStopVelocity) { #if DEBUGMETRIC if (debug) Log._Debug($" MeasureOutgoingVehicle: (Segment {SegmentId}, Node {NodeId}) Vehicle {vehicleId}: too slow ({state.SqrVelocity})"); #endif ++numProcessed; return; } uint normLength = 10u; if (avgSegmentLength > 0) { normLength = Math.Min(100u, (uint)(Math.Max(1u, state.totalLength) * 100u) / avgSegmentLength) + 1; // TODO +1 because the vehicle length calculation for trains/monorail in the method VehicleState.OnVehicleSpawned returns 0 (or a very small number maybe?) } #if DEBUGMETRIC if (debug) Log._Debug($" MeasureOutgoingVehicle: (Segment {SegmentId}, Node {NodeId}) NormLength of vehicle {vehicleId}: {state.totalLength} -> {normLength} (avgSegmentLength={avgSegmentLength})"); #endif ret[state.currentLaneIndex][state.nextSegmentId] += normLength; ++numProcessed; #if DEBUGMETRIC if (debug) Log._Debug($" MeasureOutgoingVehicle: (Segment {SegmentId}, Node {NodeId}) Vehicle {vehicleId}: ***ADDED*** ({state.currentSegmentId}@{state.currentLaneIndex} -> {state.nextSegmentId}@{state.nextLaneIndex})!"); #endif return; } public int GetRegisteredVehicleCount() { VehicleStateManager vehStateManager = VehicleStateManager.Instance; ushort vehicleId = FirstRegisteredVehicleId; int ret = 0; while (vehicleId != 0) { ++ret; vehicleId = vehStateManager.VehicleStates[vehicleId].nextVehicleIdOnSegment; } return ret; } public void Destroy() { UnregisterAllVehicles(); } private void UnregisterAllVehicles() { VehicleStateManager vehStateManager = VehicleStateManager.Instance; while (FirstRegisteredVehicleId != 0) { vehStateManager.VehicleStates[FirstRegisteredVehicleId].Unlink(); } } public void Update() { Constants.ServiceFactory.NetService.ProcessSegment(SegmentId, delegate(ushort segmentId, ref NetSegment segment) { StartNode = segment.m_startNode == NodeId; numLanes = segment.Info.m_lanes.Length; return true; }); SegmentGeometry segGeo = SegmentGeometry.Get(SegmentId); if (segGeo == null) { Log.Error($"SegmentEnd.Update: No geometry information available for segment {SegmentId}"); return; } ushort[] outgoingSegmentIds = SegmentGeometry.Get(SegmentId).GetOutgoingSegments(StartNode); numVehiclesMovingToSegmentId = new TinyDictionary[numLanes]; numVehiclesGoingToSegmentId = new TinyDictionary[numLanes]; Constants.ServiceFactory.NetService.IterateSegmentLanes(SegmentId, delegate (uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segmentId, ref NetSegment segment, byte laneIndex) { IDictionary numVehicleMoving = new TinyDictionary(); IDictionary numVehicleGoing = new TinyDictionary(); numVehiclesMovingToSegmentId[laneIndex] = numVehicleMoving; numVehiclesGoingToSegmentId[laneIndex] = numVehicleGoing; foreach (ushort otherSegmentId in outgoingSegmentIds) { numVehicleMoving[otherSegmentId] = 0; numVehicleGoing[otherSegmentId] = 0; } numVehicleMoving[SegmentId] = 0; numVehicleGoing[SegmentId] = 0; return true; }); } } } ================================================ FILE: TLM/TLM/Traffic/README.md ================================================ # TM:PE -- /Traffic Auxiliary and traffic related data structures. ## Classes - **ExtBuilding**: Extended building data. Used by Parking AI. - **ExtCitizenInstance**: Extended citizen instance data. Used by Parking AI. - **ExtVehicleType**: Flag enum. Allows for finer-grained vehicle types. - **PrioritySegment**: Data structure that holds priority signs. - **SegmentEnd**: Represents the traffic situation at a segment end (only instantiated if either a priority sign or a timed traffic light is present). Holds player-defined priority signs (Type), currently registered vehicles (FirstRegisteredVehicleId; linked list, see **VehicleState**) and allows to count waiting and flowing traffic at the segment end (GetVehicleMetricGoingToSegment) - **VehicleJunctionTransitState**: Enum. Allows to describe wheter a vehicle is currently approaching a junction (Approach), stopping in front of it (Stop), leaving it (Leave) or cannot pass the junction due to a traffic jam ahead (Blocked) - **VehicleState**: Stores custom information about a vehicle (e.g. its **ExtVehicleType**, its current junction transit state or total length). ================================================ FILE: TLM/TLM/Traffic/VehicleJunctionTransitState.cs ================================================ namespace TrafficManager.Traffic { public enum VehicleJunctionTransitState { /// /// Represents an unknown/ignored state /// None, /// /// Vehicle is apparoaching at a junction /// Approach, /// /// Vehicle must stop at a junction /// Stop, /// /// Vehicle is leaving the junction /// Leave, /// /// Vehicle may leave but is blocked due to traffic ahead /// Blocked } } ================================================ FILE: TLM/TLM/TrafficLight/Data/TrafficLightSimulation.cs ================================================ using System; using ColossalFramework; using TrafficManager.Geometry; using System.Collections.Generic; using TrafficManager.State; using TrafficManager.Custom.AI; using TrafficManager.Util; using TrafficManager.Manager; using CSUtil.Commons; using TrafficManager.Geometry.Impl; using TrafficManager.TrafficLight.Impl; namespace TrafficManager.TrafficLight.Data { public struct TrafficLightSimulation { /// /// Timed traffic light by node id /// public ITimedTrafficLights TimedLight { get; private set; } public ushort NodeId { get; private set; } public TrafficLightSimulationType Type { get; private set; } public override string ToString() { return $"[TrafficLightSimulation\n" + "\t" + $"NodeId = {NodeId}\n" + "\t" + $"Type = {Type}\n" + "\t" + $"TimedLight = {TimedLight}\n" + "TrafficLightSimulation]"; } public TrafficLightSimulation(ushort nodeId) { //Log._Debug($"TrafficLightSimulation: Constructor called @ node {nodeId}"); this.NodeId = nodeId; TimedLight = null; Type = TrafficLightSimulationType.None; } public bool SetUpManualTrafficLight() { if (IsTimedLight()) { return false; } Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nId, ref node); return true; }); Constants.ManagerFactory.CustomSegmentLightsManager.AddNodeLights(NodeId); Type = TrafficLightSimulationType.Manual; return true; } public bool DestroyManualTrafficLight() { if (IsTimedLight()) { return false; } if (! IsManualLight()) { return false; } Type = TrafficLightSimulationType.None; Constants.ManagerFactory.CustomSegmentLightsManager.RemoveNodeLights(NodeId); return true; } public bool SetUpTimedTrafficLight(IList nodeGroup) { if (IsManualLight()) { DestroyManualTrafficLight(); } if (IsTimedLight()) { return false; } Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nId, ref node); return true; }); Constants.ManagerFactory.CustomSegmentLightsManager.AddNodeLights(NodeId); TimedLight = new TimedTrafficLights(NodeId, nodeGroup); Type = TrafficLightSimulationType.Timed; return true; } public bool DestroyTimedTrafficLight() { if (! IsTimedLight()) { return false; } Type = TrafficLightSimulationType.None; var timedLight = TimedLight; TimedLight = null; if (timedLight != null) { timedLight.Destroy(); } return true; } public void Destroy() { DestroyTimedTrafficLight(); DestroyManualTrafficLight(); } public bool IsTimedLight() { return Type == TrafficLightSimulationType.Timed && TimedLight != null; } public bool IsManualLight() { return Type == TrafficLightSimulationType.Manual; } public bool IsTimedLightRunning() { return IsTimedLight() && TimedLight.IsStarted(); } public bool IsSimulationRunning() { return IsManualLight() || IsTimedLightRunning(); } public bool HasSimulation() { return IsManualLight() || IsTimedLight(); } public void SimulationStep() { if (! HasSimulation()) { return; } if (IsTimedLightRunning()) { TimedLight.SimulationStep(); } } public void Update() { Log._Debug($"TrafficLightSimulation.Update(): called for node {NodeId}"); if (IsTimedLight()) { TimedLight.OnGeometryUpdate(); TimedLight.Housekeeping(); } } public void Housekeeping() { // TODO improve & remove TimedLight?.Housekeeping(); // removes unused step lights } } } ================================================ FILE: TLM/TLM/TrafficLight/FlowWaitCalcMode.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.TrafficLight { public enum FlowWaitCalcMode { /// /// traffic measurements are averaged /// Mean, /// /// traffic measurements are summed up /// Total } } ================================================ FILE: TLM/TLM/TrafficLight/ICustomSegmentLight.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.TrafficLight { public interface ICustomSegmentLight : ICloneable { // TODO documentation ushort NodeId { get; } ushort SegmentId { get; } bool StartNode { get; } short ClockwiseIndex { get; } LightMode CurrentMode { get; set; } LightMode InternalCurrentMode { get; set; } // TODO should not be defined here RoadBaseAI.TrafficLightState LightLeft { get; } RoadBaseAI.TrafficLightState LightMain { get; } RoadBaseAI.TrafficLightState LightRight { get; } RoadBaseAI.TrafficLightState GetLightState(ArrowDirection dir); bool IsAnyGreen(); bool IsGreen(ArrowDirection dir); bool IsAnyInTransition(); bool IsInTransition(ArrowDirection dir); bool IsLeftGreen(); bool IsMainGreen(); bool IsRightGreen(); bool IsLeftRed(); bool IsMainRed(); bool IsRightRed(); bool IsRed(ArrowDirection dir); void UpdateVisuals(); void MakeRed(); void MakeRedOrGreen(); void ToggleMode(); void ChangeMainLight(); void ChangeLeftLight(); void ChangeRightLight(); void SetStates(RoadBaseAI.TrafficLightState? mainLight, RoadBaseAI.TrafficLightState? leftLight, RoadBaseAI.TrafficLightState? rightLight, bool calcAutoPedLight = true); } } ================================================ FILE: TLM/TLM/TrafficLight/ICustomSegmentLights.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Geometry; using TrafficManager.Manager; using TrafficManager.Traffic; namespace TrafficManager.TrafficLight { public interface ICustomSegmentLights : ICloneable, ISegmentEndId { // TODO documentation ushort NodeId { get; } IDictionary CustomLights { get; } RoadBaseAI.TrafficLightState AutoPedestrianLightState { get; set; } // TODO should not be writable bool InvalidPedestrianLight { get; set; } // TODO improve & remove RoadBaseAI.TrafficLightState? PedestrianLightState { get; set; } RoadBaseAI.TrafficLightState? InternalPedestrianLightState { get; } bool ManualPedestrianMode { get; set; } LinkedList VehicleTypes { get; } // TODO improve & remove ExtVehicleType?[] VehicleTypeByLaneIndex { get; } void CalculateAutoPedestrianLightState(bool propagate = true); bool IsAnyGreen(); bool IsAnyInTransition(); bool IsAnyLeftGreen(); bool IsAnyMainGreen(); bool IsAnyRightGreen(); bool IsAllLeftRed(); bool IsAllMainRed(); bool IsAllRightRed(); void UpdateVisuals(); uint LastChange(); void MakeRed(); void MakeRedOrGreen(); void ChangeLightPedestrian(); void SetLights(RoadBaseAI.TrafficLightState lightState); void SetLights(ICustomSegmentLights otherLights); ICustomSegmentLight GetCustomLight(byte laneIndex); ICustomSegmentLight GetCustomLight(ExtVehicleType vehicleType); bool Relocate(ushort segmentId, bool startNode, ICustomSegmentLightsManager lightsManager); ICustomSegmentLights Clone(ICustomSegmentLightsManager newLightsManager, bool performHousekeeping = true); void Housekeeping(bool mayDelete, bool calculateAutoPedLight); } } ================================================ FILE: TLM/TLM/TrafficLight/ITimedTrafficLights.cs ================================================ using System.Collections.Generic; using CSUtil.Commons; using TrafficManager.Geometry.Impl; using TrafficManager.Traffic; using TrafficManager.Util; namespace TrafficManager.TrafficLight { public interface ITimedTrafficLights : IObserver { IDictionary> Directions { get; } ushort NodeId { get; } ushort MasterNodeId { get; set; } // TODO private set short RotationOffset { get; } int CurrentStep { get; set; } bool TestMode { get; set; } // TODO private set IList NodeGroup { get; set; } // TODO private set ITimedTrafficLightsStep AddStep(int minTime, int maxTime, StepChangeMetric changeMetric, float waitFlowBalance, bool makeRed = false); long CheckNextChange(ushort segmentId, bool startNode, ExtVehicleType vehicleType, int lightType); ITimedTrafficLightsStep GetStep(int stepId); bool Housekeeping(); // TODO improve & remove bool IsMasterNode(); bool IsStarted(); bool IsInTestMode(); void SetTestMode(bool testMode); void Destroy(); ITimedTrafficLights MasterLights(); void MoveStep(int oldPos, int newPos); int NumSteps(); void RemoveStep(int id); void ResetSteps(); void RotateLeft(); void RotateRight(); void Join(ITimedTrafficLights otherTimedLight); void PasteSteps(ITimedTrafficLights sourceTimedLight); void ChangeLightMode(ushort segmentId, ExtVehicleType vehicleType, LightMode mode); void SetLights(bool noTransition = false); void SimulationStep(); void SkipStep(bool setLights = true, int prevStepRefIndex = -1); void Start(); void Start(int step); void Stop(); void OnGeometryUpdate(); void RemoveNodeFromGroup(ushort otherNodeId); } } ================================================ FILE: TLM/TLM/TrafficLight/ITimedTrafficLightsStep.cs ================================================ using System.Collections.Generic; using TrafficManager.Geometry.Impl; using TrafficManager.Manager; using TrafficManager.Traffic; namespace TrafficManager.TrafficLight { public interface ITimedTrafficLightsStep : ICustomSegmentLightsManager { // TODO documentation IDictionary CustomSegmentLights { get; } LinkedList InvalidSegmentLights { get; } int PreviousStepRefIndex { get; set; } int NextStepRefIndex { get; set; } int MinTime { get; set; } int MaxTime { get; set; } StepChangeMetric ChangeMetric { get; set; } float WaitFlowBalance { get; set; } float CurrentWait { get; } float CurrentFlow { get; } void CalcWaitFlow(bool countOnlyMovingIfGreen, int stepRefIndex, out float wait, out float flow); RoadBaseAI.TrafficLightState GetLightState(ushort segmentId, ExtVehicleType vehicleType, int lightType); float GetMetric(float flow, float wait); ICustomSegmentLights GetSegmentLights(ushort segmentId); long MaxTimeRemaining(); long MinTimeRemaining(); bool IsInStartTransition(); bool IsInEndTransition(); bool IsEndTransitionDone(); bool RelocateSegmentLights(ushort sourceSegmentId, ushort targetSegmentId); ICustomSegmentLights RemoveSegmentLights(ushort segmentId); bool SetSegmentLights(ushort segmentId, ICustomSegmentLights lights); void SetStepDone(); bool ShouldGoToNextStep(float flow, float wait, out float metric); void Start(int previousStepRefIndex = -1); bool StepDone(bool updateValues); string ToString(); void UpdateLights(); void UpdateLiveLights(); void UpdateLiveLights(bool noTransition); } } ================================================ FILE: TLM/TLM/TrafficLight/Impl/CustomSegment.cs ================================================ using System.Collections.Generic; using TrafficManager.Geometry; namespace TrafficManager.TrafficLight.Impl { class CustomSegment { public ICustomSegmentLights StartNodeLights; public ICustomSegmentLights EndNodeLights; public override string ToString() { return "[CustomSegment \n" + "\t" + $"StartNodeLights: {StartNodeLights}\n" + "\t" + $"EndNodeLights: {EndNodeLights}\n" + "CustomSegment]"; } } } ================================================ FILE: TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs ================================================ #define DEBUGVISUALSx using System; using System.Collections.Generic; using ColossalFramework; using TrafficManager.Geometry; using UnityEngine; using TrafficManager.Custom.AI; using CSUtil.Commons; using TrafficManager.State; using TrafficManager.Geometry.Impl; namespace TrafficManager.TrafficLight.Impl { /// /// Represents the traffic light (left, forward, right) at a specific segment end /// public class CustomSegmentLight : ICustomSegmentLight { [Obsolete] public ushort NodeId { get { return lights.NodeId; } } public ushort SegmentId { get { return lights.SegmentId; } } public bool StartNode { get { return lights.StartNode; } } public short ClockwiseIndex { get { return lights.ClockwiseIndex; } } public LightMode CurrentMode { get { return InternalCurrentMode; } set { if (InternalCurrentMode == value) return; InternalCurrentMode = value; EnsureModeLights(); } } public LightMode InternalCurrentMode { get; set; } = LightMode.Simple; // TODO should be private internal RoadBaseAI.TrafficLightState leftLight; internal RoadBaseAI.TrafficLightState mainLight; internal RoadBaseAI.TrafficLightState rightLight; public RoadBaseAI.TrafficLightState LightLeft { get { return leftLight; } /*private set { if (leftLight == value) return; leftLight = value; lights.OnChange(); }*/ } public RoadBaseAI.TrafficLightState LightMain { get { return mainLight; } /*private set { if (mainLight == value) return; mainLight = value; lights.OnChange(); }*/ } public RoadBaseAI.TrafficLightState LightRight { get { return rightLight; } /*private set { if (rightLight == value) return; rightLight = value; lights.OnChange(); }*/ } CustomSegmentLights lights; public override string ToString() { return $"[CustomSegmentLight seg. {SegmentId} @ node {NodeId}\n" + "\t" + $"CurrentMode: {CurrentMode}\n" + "\t" + $"LightLeft: {LightLeft}\n" + "\t" + $"LightMain: {LightMain}\n" + "\t" + $"LightRight: {LightRight}\n" + "CustomSegmentLight]"; } private void EnsureModeLights() { bool changed = false; switch (InternalCurrentMode) { case LightMode.Simple: if (leftLight != LightMain) { leftLight = LightMain; changed = true; } if (rightLight != LightMain) { rightLight = LightMain; changed = true; } break; case LightMode.SingleLeft: if (rightLight != LightMain) { rightLight = LightMain; changed = true; } break; case LightMode.SingleRight: if (leftLight != LightMain) { leftLight = LightMain; changed = true; } break; } if (changed) lights.OnChange(); } public CustomSegmentLight(CustomSegmentLights lights, RoadBaseAI.TrafficLightState mainLight) { this.lights = lights; SetStates(mainLight, leftLight, rightLight); UpdateVisuals(); } public CustomSegmentLight(CustomSegmentLights lights, RoadBaseAI.TrafficLightState mainLight, RoadBaseAI.TrafficLightState leftLight, RoadBaseAI.TrafficLightState rightLight/*, RoadBaseAI.TrafficLightState pedestrianLight*/) { this.lights = lights; SetStates(mainLight, leftLight, rightLight); UpdateVisuals(); } public void ToggleMode() { SegmentGeometry geometry = SegmentGeometry.Get(SegmentId); if (geometry == null) { Log.Error($"CustomSegmentLight.ToggleMode: No geometry information available for segment {SegmentId}"); return; } bool startNode = lights.StartNode; var hasLeftSegment = geometry.HasOutgoingLeftSegment(startNode); var hasForwardSegment = geometry.HasOutgoingStraightSegment(startNode); var hasRightSegment = geometry.HasOutgoingRightSegment(startNode); #if DEBUG Log._Debug($"ChangeMode. segment {SegmentId} @ node {NodeId}, hasOutgoingLeft={hasLeftSegment}, hasOutgoingStraight={hasForwardSegment}, hasOutgoingRight={hasRightSegment}"); #endif LightMode newMode = LightMode.Simple; if (CurrentMode == LightMode.Simple) { if (!hasLeftSegment) { newMode = LightMode.SingleRight; } else { newMode = LightMode.SingleLeft; } } else if (CurrentMode == LightMode.SingleLeft) { if (!hasForwardSegment || !hasRightSegment) { newMode = LightMode.Simple; } else { newMode = LightMode.SingleRight; } } else if (CurrentMode == LightMode.SingleRight) { if (!hasLeftSegment) { newMode = LightMode.Simple; } else { newMode = LightMode.All; } } else { newMode = LightMode.Simple; } CurrentMode = newMode; } public void ChangeMainLight() { var invertedLight = LightMain == RoadBaseAI.TrafficLightState.Green ? RoadBaseAI.TrafficLightState.Red : RoadBaseAI.TrafficLightState.Green; if (CurrentMode == LightMode.Simple) { SetStates(invertedLight, invertedLight, invertedLight); } else if (CurrentMode == LightMode.SingleLeft) { SetStates(invertedLight, null, invertedLight); } else if (CurrentMode == LightMode.SingleRight) { SetStates(invertedLight, invertedLight, null); } else { //LightMain = invertedLight; SetStates(invertedLight, null, null); } UpdateVisuals(); } public void ChangeLeftLight() { var invertedLight = LightLeft == RoadBaseAI.TrafficLightState.Green ? RoadBaseAI.TrafficLightState.Red : RoadBaseAI.TrafficLightState.Green; //LightLeft = invertedLight; SetStates(null, invertedLight, null); UpdateVisuals(); } public void ChangeRightLight() { var invertedLight = LightRight == RoadBaseAI.TrafficLightState.Green ? RoadBaseAI.TrafficLightState.Red : RoadBaseAI.TrafficLightState.Green; //LightRight = invertedLight; SetStates(null, null, invertedLight); UpdateVisuals(); } public RoadBaseAI.TrafficLightState GetLightState(ArrowDirection dir) { switch (dir) { case ArrowDirection.Left: return LightLeft; case ArrowDirection.Forward: default: return LightMain; case ArrowDirection.Right: return LightRight; case ArrowDirection.Turn: return Constants.ServiceFactory.SimulationService.LeftHandDrive ? LightRight : LightLeft; } } public bool IsGreen(ArrowDirection dir) { return GetLightState(dir) == RoadBaseAI.TrafficLightState.Green; } public bool IsInTransition(ArrowDirection dir) { RoadBaseAI.TrafficLightState state = GetLightState(dir); return state == RoadBaseAI.TrafficLightState.GreenToRed || state == RoadBaseAI.TrafficLightState.RedToGreen; } public bool IsRed(ArrowDirection dir) { return GetLightState(dir) == RoadBaseAI.TrafficLightState.Red; } public bool IsAnyGreen() { return LightMain == RoadBaseAI.TrafficLightState.Green || LightLeft == RoadBaseAI.TrafficLightState.Green || LightRight == RoadBaseAI.TrafficLightState.Green; } public bool IsAnyInTransition() { return LightMain == RoadBaseAI.TrafficLightState.RedToGreen || LightLeft == RoadBaseAI.TrafficLightState.RedToGreen || LightRight == RoadBaseAI.TrafficLightState.RedToGreen || LightMain == RoadBaseAI.TrafficLightState.GreenToRed || LightLeft == RoadBaseAI.TrafficLightState.GreenToRed || LightRight == RoadBaseAI.TrafficLightState.GreenToRed; } public bool IsLeftGreen() { return LightLeft == RoadBaseAI.TrafficLightState.Green; } public bool IsMainGreen() { return LightMain == RoadBaseAI.TrafficLightState.Green; } public bool IsRightGreen() { return LightRight == RoadBaseAI.TrafficLightState.Green; } public bool IsLeftRed() { return LightLeft == RoadBaseAI.TrafficLightState.Red; } public bool IsMainRed() { return LightMain == RoadBaseAI.TrafficLightState.Red; } public bool IsRightRed() { return LightRight == RoadBaseAI.TrafficLightState.Red; } public void UpdateVisuals() { var instance = Singleton.instance; ushort nodeId = lights.NodeId; uint currentFrameIndex = Singleton.instance.m_currentFrameIndex; uint num = (uint)(((int)nodeId << 8) / 32768); RoadBaseAI.TrafficLightState vehicleLightState; RoadBaseAI.TrafficLightState pedestrianLightState; RoadBaseAI.TrafficLightState mainLight = LightMain; RoadBaseAI.TrafficLightState leftLight = LightLeft; RoadBaseAI.TrafficLightState rightLight = LightRight; switch (CurrentMode) { case LightMode.Simple: leftLight = mainLight; rightLight = mainLight; break; case LightMode.SingleLeft: rightLight = mainLight; break; case LightMode.SingleRight: leftLight = mainLight; break; case LightMode.All: default: break; } vehicleLightState = GetVisualLightState(); pedestrianLightState = lights.PedestrianLightState == null ? RoadBaseAI.TrafficLightState.Red : (RoadBaseAI.TrafficLightState)lights.PedestrianLightState; #if DEBUGVISUALS Log._Debug($"Setting visual traffic light state of node {NodeId}, seg. {SegmentId} to vehicleState={vehicleLightState} pedState={pedestrianLightState}"); #endif uint now = ((currentFrameIndex - num) >> 8) & 1; CustomRoadAI.OriginalSetTrafficLightState(true, nodeId, ref instance.m_segments.m_buffer[SegmentId], now << 8, vehicleLightState, pedestrianLightState, false, false); CustomRoadAI.OriginalSetTrafficLightState(true, nodeId, ref instance.m_segments.m_buffer[SegmentId], (1u - now) << 8, vehicleLightState, pedestrianLightState, false, false); } public RoadBaseAI.TrafficLightState GetVisualLightState() { RoadBaseAI.TrafficLightState vehicleLightState; // any green? if (LightMain == RoadBaseAI.TrafficLightState.Green || LightLeft == RoadBaseAI.TrafficLightState.Green || LightRight == RoadBaseAI.TrafficLightState.Green) { vehicleLightState = RoadBaseAI.TrafficLightState.Green; } else // all red? if (LightMain == RoadBaseAI.TrafficLightState.Red && LightLeft == RoadBaseAI.TrafficLightState.Red && LightRight == RoadBaseAI.TrafficLightState.Red) { vehicleLightState = RoadBaseAI.TrafficLightState.Red; } else // any red+yellow? if (LightMain == RoadBaseAI.TrafficLightState.RedToGreen || LightLeft == RoadBaseAI.TrafficLightState.RedToGreen || LightRight == RoadBaseAI.TrafficLightState.RedToGreen) { vehicleLightState = RoadBaseAI.TrafficLightState.RedToGreen; } else { vehicleLightState = RoadBaseAI.TrafficLightState.GreenToRed; } return vehicleLightState; } private RoadBaseAI.TrafficLightState _checkPedestrianLight() { if (LightLeft == RoadBaseAI.TrafficLightState.Red && LightMain == RoadBaseAI.TrafficLightState.Red && LightRight == RoadBaseAI.TrafficLightState.Red) { return RoadBaseAI.TrafficLightState.Green; } return RoadBaseAI.TrafficLightState.Red; } public object Clone() { return MemberwiseClone(); } public void MakeRedOrGreen() { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId) Log._Debug($"CustomSegmentLight.MakeRedOrGreen: called for segment {SegmentId} @ {NodeId}"); #endif RoadBaseAI.TrafficLightState mainState = RoadBaseAI.TrafficLightState.Green; RoadBaseAI.TrafficLightState leftState = RoadBaseAI.TrafficLightState.Green; RoadBaseAI.TrafficLightState rightState = RoadBaseAI.TrafficLightState.Green; if (LightLeft != RoadBaseAI.TrafficLightState.Green) { leftState = RoadBaseAI.TrafficLightState.Red; } if (LightMain != RoadBaseAI.TrafficLightState.Green) { mainState = RoadBaseAI.TrafficLightState.Red; } if (LightRight != RoadBaseAI.TrafficLightState.Green) { rightState = RoadBaseAI.TrafficLightState.Red; } SetStates(mainState, leftState, rightState); } public void MakeRed() { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId) Log._Debug($"CustomSegmentLight.MakeRed: called for segment {SegmentId} @ {NodeId}"); #endif SetStates(RoadBaseAI.TrafficLightState.Red, RoadBaseAI.TrafficLightState.Red, RoadBaseAI.TrafficLightState.Red); } public void SetStates(RoadBaseAI.TrafficLightState? mainLight, RoadBaseAI.TrafficLightState? leftLight, RoadBaseAI.TrafficLightState? rightLight, bool calcAutoPedLight=true) { if ((mainLight == null || this.mainLight == mainLight) && (leftLight == null || this.leftLight == leftLight) && (rightLight == null || this.rightLight == rightLight)) return; if (mainLight != null) this.mainLight = (RoadBaseAI.TrafficLightState)mainLight; if (leftLight != null) this.leftLight = (RoadBaseAI.TrafficLightState)leftLight; if (rightLight != null) this.rightLight = (RoadBaseAI.TrafficLightState)rightLight; #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId) Log._Debug($"CustomSegmentLight.SetStates({mainLight}, {leftLight}, {rightLight}, {calcAutoPedLight}) for segment {SegmentId} @ {NodeId}: {this.mainLight} {this.leftLight} {this.rightLight}"); #endif lights.OnChange(calcAutoPedLight); } } } ================================================ FILE: TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs ================================================ #define DEBUGGETx using System; using System.Collections.Generic; using ColossalFramework; using TrafficManager.Geometry; using UnityEngine; using TrafficManager.Custom.AI; using TrafficManager.Traffic; using TrafficManager.Manager; using System.Linq; using TrafficManager.Util; using CSUtil.Commons; using TrafficManager.State; using TrafficManager.Geometry.Impl; namespace TrafficManager.TrafficLight.Impl { /// /// Represents the set of custom traffic lights located at a node /// public class CustomSegmentLights : SegmentEndId, ICustomSegmentLights { //private static readonly ExtVehicleType[] SINGLE_LANE_VEHICLETYPES = new ExtVehicleType[] { ExtVehicleType.Tram, ExtVehicleType.Service, ExtVehicleType.CargoTruck, ExtVehicleType.RoadPublicTransport | ExtVehicleType.Service, ExtVehicleType.RailVehicle }; public const ExtVehicleType DEFAULT_MAIN_VEHICLETYPE = ExtVehicleType.None; [Obsolete] public ushort NodeId { get { SegmentGeometry segGeo = SegmentGeometry.Get(SegmentId); if (segGeo == null) { Log.Info($"CustomSegmentLights.NodeId: No geometry information available for segment {SegmentId}"); return 0; } if (StartNode) return segGeo.StartNodeId(); else return segGeo.EndNodeId(); } } public short ClockwiseIndex { get { return LightsManager.ClockwiseIndexOfSegmentEnd(this); } } public uint LastChangeFrame; public bool InvalidPedestrianLight { get; set; } = false; // TODO improve & remove public IDictionary CustomLights { get; private set; } = new TinyDictionary(); public LinkedList VehicleTypes { // TODO replace collection get; private set; } = new LinkedList(); public ExtVehicleType?[] VehicleTypeByLaneIndex { get; private set; } = new ExtVehicleType?[0]; /// /// Vehicles types that have their own traffic light /// public ExtVehicleType SeparateVehicleTypes { get; private set; } = ExtVehicleType.None; public RoadBaseAI.TrafficLightState AutoPedestrianLightState { get; set; } = RoadBaseAI.TrafficLightState.Green; // TODO set should be private public RoadBaseAI.TrafficLightState? PedestrianLightState { get { if (InvalidPedestrianLight || InternalPedestrianLightState == null) return RoadBaseAI.TrafficLightState.Green; // no pedestrian crossing at this point if (ManualPedestrianMode && InternalPedestrianLightState != null) return (RoadBaseAI.TrafficLightState)InternalPedestrianLightState; else { return AutoPedestrianLightState; } } set { if (InternalPedestrianLightState == null) { #if DEBUGHK Log._Debug($"CustomSegmentLights: Refusing to change pedestrian light at segment {SegmentId}"); #endif return; } //Log._Debug($"CustomSegmentLights: Setting pedestrian light at segment {segmentId}"); InternalPedestrianLightState = value; } } public bool ManualPedestrianMode { get { return manualPedestrianMode; } set { if (!manualPedestrianMode && value) { PedestrianLightState = AutoPedestrianLightState; } manualPedestrianMode = value; } } private bool manualPedestrianMode = false; public RoadBaseAI.TrafficLightState? InternalPedestrianLightState { get; private set; } = null; private ExtVehicleType mainVehicleType = ExtVehicleType.None; protected ICustomSegmentLight MainSegmentLight { get { ICustomSegmentLight res = null; CustomLights.TryGetValue(mainVehicleType, out res); return res; } } public ICustomSegmentLightsManager LightsManager { get { return lightsManager; } set { lightsManager = value; OnChange(); } } private ICustomSegmentLightsManager lightsManager; public override string ToString() { return $"[CustomSegmentLights {base.ToString()} @ node {NodeId}\n" + "\t" + $"LastChangeFrame: {LastChangeFrame}\n" + "\t" + $"InvalidPedestrianLight: {InvalidPedestrianLight}\n" + "\t" + $"CustomLights: {CustomLights}\n" + "\t" + $"VehicleTypes: {VehicleTypes.CollectionToString()}\n" + "\t" + $"VehicleTypeByLaneIndex: {VehicleTypeByLaneIndex.ArrayToString()}\n" + "\t" + $"SeparateVehicleTypes: {SeparateVehicleTypes}\n" + "\t" + $"AutoPedestrianLightState: {AutoPedestrianLightState}\n" + "\t" + $"PedestrianLightState: {PedestrianLightState}\n" + "\t" + $"ManualPedestrianMode: {ManualPedestrianMode}\n" + "\t" + $"manualPedestrianMode: {manualPedestrianMode}\n" + "\t" + $"InternalPedestrianLightState: {InternalPedestrianLightState}\n" + "\t" + $"MainSegmentLight: {MainSegmentLight}\n" + "CustomSegmentLights]"; } public bool Relocate(ushort segmentId, bool startNode, ICustomSegmentLightsManager lightsManager) { if (Relocate(segmentId, startNode)) { this.lightsManager = lightsManager; Housekeeping(true, true); return true; } return false; } [Obsolete] protected CustomSegmentLights(ICustomSegmentLightsManager lightsManager, ushort nodeId, ushort segmentId, bool calculateAutoPedLight) : this(lightsManager, segmentId, nodeId == SegmentGeometry.Get(segmentId)?.StartNodeId(), calculateAutoPedLight) { } public CustomSegmentLights(ICustomSegmentLightsManager lightsManager, ushort segmentId, bool startNode, bool calculateAutoPedLight) : this(lightsManager, segmentId, startNode, calculateAutoPedLight, true) { } public CustomSegmentLights(ICustomSegmentLightsManager lightsManager, ushort segmentId, bool startNode, bool calculateAutoPedLight, bool performHousekeeping) : base(segmentId, startNode) { this.lightsManager = lightsManager; if (performHousekeeping) { Housekeeping(false, calculateAutoPedLight); } } public bool IsAnyGreen() { foreach (KeyValuePair e in CustomLights) { if (e.Value.IsAnyGreen()) return true; } return false; } public bool IsAnyInTransition() { foreach (KeyValuePair e in CustomLights) { if (e.Value.IsAnyInTransition()) return true; } return false; } public bool IsAnyLeftGreen() { foreach (KeyValuePair e in CustomLights) { if (e.Value.IsLeftGreen()) return true; } return false; } public bool IsAnyMainGreen() { foreach (KeyValuePair e in CustomLights) { if (e.Value.IsMainGreen()) return true; } return false; } public bool IsAnyRightGreen() { foreach (KeyValuePair e in CustomLights) { if (e.Value.IsRightGreen()) return true; } return false; } public bool IsAllLeftRed() { foreach (KeyValuePair e in CustomLights) { if (!e.Value.IsLeftRed()) return false; } return true; } public bool IsAllMainRed() { foreach (KeyValuePair e in CustomLights) { if (!e.Value.IsMainRed()) return false; } return true; } public bool IsAllRightRed() { foreach (KeyValuePair e in CustomLights) { if (!e.Value.IsRightRed()) return false; } return true; } public void UpdateVisuals() { if (MainSegmentLight == null) return; MainSegmentLight.UpdateVisuals(); } public object Clone() { return Clone(LightsManager, true); } public ICustomSegmentLights Clone(ICustomSegmentLightsManager newLightsManager, bool performHousekeeping=true) { CustomSegmentLights clone = new CustomSegmentLights(newLightsManager != null ? newLightsManager : LightsManager, SegmentId, StartNode, false, false); foreach (KeyValuePair e in CustomLights) { clone.CustomLights.Add(e.Key, (ICustomSegmentLight)e.Value.Clone()); } clone.InternalPedestrianLightState = InternalPedestrianLightState; clone.manualPedestrianMode = manualPedestrianMode; clone.VehicleTypes = new LinkedList(VehicleTypes); clone.LastChangeFrame = LastChangeFrame; clone.mainVehicleType = mainVehicleType; clone.AutoPedestrianLightState = AutoPedestrianLightState; if (performHousekeeping) { clone.Housekeeping(false, false); } return clone; } public ICustomSegmentLight GetCustomLight(byte laneIndex) { if (laneIndex >= VehicleTypeByLaneIndex.Length) { #if DEBUGGET Log._Debug($"CustomSegmentLights.GetCustomLight({laneIndex}): No vehicle type found for lane index"); #endif return MainSegmentLight; } ExtVehicleType? vehicleType = VehicleTypeByLaneIndex[laneIndex]; if (vehicleType == null) { #if DEBUGGET Log._Debug($"CustomSegmentLights.GetCustomLight({laneIndex}): No vehicle type found for lane index: lane is invalid"); #endif return MainSegmentLight; } #if DEBUGGET Log._Debug($"CustomSegmentLights.GetCustomLight({laneIndex}): Vehicle type is {vehicleType}"); #endif ICustomSegmentLight light; if (!CustomLights.TryGetValue((ExtVehicleType)vehicleType, out light)) { #if DEBUGGET Log._Debug($"CustomSegmentLights.GetCustomLight({laneIndex}): No custom light found for vehicle type {vehicleType}"); #endif return MainSegmentLight; } #if DEBUGGET Log._Debug($"CustomSegmentLights.GetCustomLight({laneIndex}): Returning custom light for vehicle type {vehicleType}"); #endif return light; } public ICustomSegmentLight GetCustomLight(ExtVehicleType vehicleType) { ICustomSegmentLight ret = null; if (!CustomLights.TryGetValue(vehicleType, out ret)) { ret = MainSegmentLight; } return ret; /*if (vehicleType != ExtVehicleType.None) Log._Debug($"No traffic light for vehicle type {vehicleType} defined at segment {segmentId}, node {nodeId}.");*/ } public void MakeRed() { foreach (KeyValuePair e in CustomLights) { e.Value.MakeRed(); } } public void MakeRedOrGreen() { foreach (KeyValuePair e in CustomLights) { e.Value.MakeRedOrGreen(); } } public void SetLights(RoadBaseAI.TrafficLightState lightState) { foreach (KeyValuePair e in CustomLights) { e.Value.SetStates(lightState, lightState, lightState, false); } CalculateAutoPedestrianLightState(); } public void SetLights(ICustomSegmentLights otherLights) { foreach (KeyValuePair e in otherLights.CustomLights) { ICustomSegmentLight ourLight = null; if (!CustomLights.TryGetValue(e.Key, out ourLight)) { continue; } ourLight.SetStates(e.Value.LightMain, e.Value.LightLeft, e.Value.LightRight, false); //ourLight.LightPedestrian = e.Value.LightPedestrian; } InternalPedestrianLightState = otherLights.InternalPedestrianLightState; manualPedestrianMode = otherLights.ManualPedestrianMode; AutoPedestrianLightState = otherLights.AutoPedestrianLightState; } public void ChangeLightPedestrian() { if (PedestrianLightState != null) { var invertedLight = PedestrianLightState == RoadBaseAI.TrafficLightState.Green ? RoadBaseAI.TrafficLightState.Red : RoadBaseAI.TrafficLightState.Green; PedestrianLightState = invertedLight; UpdateVisuals(); } } private static uint getCurrentFrame() { return Singleton.instance.m_currentFrameIndex >> 6; } public uint LastChange() { return getCurrentFrame() - LastChangeFrame; } public void OnChange(bool calculateAutoPedLight=true) { LastChangeFrame = getCurrentFrame(); if (calculateAutoPedLight) CalculateAutoPedestrianLightState(); } public void CalculateAutoPedestrianLightState(bool propagate=true) { #if DEBUGTTL bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Calculating pedestrian light state of seg. {SegmentId} @ node {NodeId}"); #endif SegmentEndGeometry segmentEndGeometry = SegmentGeometry.Get(SegmentId)?.GetEnd(StartNode); if (segmentEndGeometry == null) { Log._Debug($"Could not get SegmentEndGeometry for segment {SegmentId} @ {NodeId}."); AutoPedestrianLightState = RoadBaseAI.TrafficLightState.Green; return; } ushort nodeId = segmentEndGeometry.NodeId(); if (nodeId != NodeId) { Log.Warning($"CustomSegmentLights.CalculateAutoPedestrianLightState: Node id mismatch! segment end node is {nodeId} but we are node {NodeId}. segmentEndGeometry={segmentEndGeometry} this={this}"); return; } if (propagate) { foreach (ushort otherSegmentId in segmentEndGeometry.ConnectedSegments) { if (otherSegmentId == 0) continue; ICustomSegmentLights otherLights = LightsManager.GetSegmentLights(nodeId, otherSegmentId); if (otherLights == null) { #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Expected other (propagate) CustomSegmentLights at segment {otherSegmentId} @ {NodeId} but there was none. Original segment id: {SegmentId}"); #endif continue; } otherLights.CalculateAutoPedestrianLightState(false); } } if (IsAnyGreen()) { #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Any green at seg. {SegmentId} @ {NodeId}"); #endif AutoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; return; } #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Querying incoming segments at seg. {SegmentId} @ {NodeId}"); #endif RoadBaseAI.TrafficLightState autoPedestrianLightState = RoadBaseAI.TrafficLightState.Green; ItemClass prevConnectionClass = null; Constants.ServiceFactory.NetService.ProcessSegment(SegmentId, delegate (ushort prevSegId, ref NetSegment segment) { prevConnectionClass = segment.Info.GetConnectionClass(); return true; }); if (!segmentEndGeometry.IncomingOneWay) { // query straight segments foreach (ushort otherSegmentId in segmentEndGeometry.IncomingStraightSegments) { if (otherSegmentId == 0) continue; #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Checking incoming straight segment {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); #endif ICustomSegmentLights otherLights = LightsManager.GetSegmentLights(nodeId, otherSegmentId); if (otherLights == null) { #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Expected other (straight) CustomSegmentLights at segment {otherSegmentId} @ {NodeId} but there was none. Original segment id: {SegmentId}"); #endif continue; } SegmentGeometry otherGeo = SegmentGeometry.Get(otherSegmentId); if (otherGeo == null) { #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Expected other (straight) segment geometry at segment {otherSegmentId} @ {NodeId} (startNode={otherLights.StartNode}) but there was none. Original segment id: {SegmentId}"); #endif continue; } ItemClass nextConnectionClass = null; Constants.ServiceFactory.NetService.ProcessSegment(otherSegmentId, delegate (ushort otherSegId, ref NetSegment segment) { nextConnectionClass = segment.Info.GetConnectionClass(); return true; }); if (nextConnectionClass.m_service != prevConnectionClass.m_service) { #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Other (straight) segment {otherSegmentId} @ {NodeId} has different connection service than segment {SegmentId} ({nextConnectionClass.m_service} vs. {prevConnectionClass.m_service}). Ignoring traffic light state."); #endif continue; } if (!otherLights.IsAllMainRed()) { #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Not all main red at {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); #endif autoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; break; } } // query left/right segments if (autoPedestrianLightState == RoadBaseAI.TrafficLightState.Green) { bool lhd = Constants.ServiceFactory.SimulationService.LeftHandDrive; foreach (ushort otherSegmentId in lhd ? segmentEndGeometry.IncomingLeftSegments : segmentEndGeometry.IncomingRightSegments) { if (otherSegmentId == 0) continue; #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Checking left/right segment {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); #endif ICustomSegmentLights otherLights = LightsManager.GetSegmentLights(nodeId, otherSegmentId); if (otherLights == null) { #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Expected other (left/right) CustomSegmentLights at segment {otherSegmentId} @ {NodeId} but there was none. Original segment id: {SegmentId}"); #endif continue; } ItemClass nextConnectionClass = null; Constants.ServiceFactory.NetService.ProcessSegment(otherSegmentId, delegate (ushort otherSegId, ref NetSegment segment) { nextConnectionClass = segment.Info.GetConnectionClass(); return true; }); if (nextConnectionClass.m_service != prevConnectionClass.m_service) { #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Other (left/right) segment {otherSegmentId} @ {NodeId} has different connection service than segment {SegmentId} ({nextConnectionClass.m_service} vs. {prevConnectionClass.m_service}). Ignoring traffic light state."); #endif continue; } if ((lhd && !otherLights.IsAllRightRed()) || (!lhd && !otherLights.IsAllLeftRed())) { #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Not all left red at {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); #endif autoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; break; } } } } AutoPedestrianLightState = autoPedestrianLightState; #if DEBUGTTL if (debug) Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Calculated AutoPedestrianLightState for segment {SegmentId} @ {NodeId}: {AutoPedestrianLightState}"); #endif } // TODO improve & remove public void Housekeeping(bool mayDelete, bool calculateAutoPedLight) { #if DEBUGHK bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif // we intentionally never delete vehicle types (because we may want to retain traffic light states if a segment is upgraded or replaced) ICustomSegmentLight mainLight = MainSegmentLight; ushort nodeId = NodeId; HashSet setupLights = new HashSet(); IDictionary allAllowedTypes = Constants.ManagerFactory.VehicleRestrictionsManager.GetAllowedVehicleTypesAsDict(SegmentId, nodeId, VehicleRestrictionsMode.Restricted); // TODO improve ExtVehicleType allAllowedMask = Constants.ManagerFactory.VehicleRestrictionsManager.GetAllowedVehicleTypes(SegmentId, nodeId, VehicleRestrictionsMode.Restricted); SeparateVehicleTypes = ExtVehicleType.None; #if DEBUGHK if (debug) Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping started @ seg. {SegmentId}, node {nodeId}, allAllowedTypes={allAllowedTypes.DictionaryToString()}, allAllowedMask={allAllowedMask}"); #endif //bool addPedestrianLight = false; uint separateLanes = 0; int defaultLanes = 0; NetInfo segmentInfo = null; Constants.ServiceFactory.NetService.ProcessSegment(SegmentId, delegate (ushort segId, ref NetSegment segment) { VehicleTypeByLaneIndex = new ExtVehicleType?[segment.Info.m_lanes.Length]; segmentInfo = segment.Info; return true; }); HashSet laneIndicesWithoutSeparateLights = new HashSet(allAllowedTypes.Keys); // TODO improve // check if separate traffic lights are required bool separateLightsRequired = false; foreach (KeyValuePair e in allAllowedTypes) { if (e.Value != allAllowedMask) { separateLightsRequired = true; break; } } // set up vehicle-separated traffic lights if (separateLightsRequired) { foreach (KeyValuePair e in allAllowedTypes) { byte laneIndex = e.Key; NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; ExtVehicleType allowedTypes = e.Value; ExtVehicleType defaultMask = Constants.ManagerFactory.VehicleRestrictionsManager.GetDefaultAllowedVehicleTypes(SegmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Unrestricted); #if DEBUGHK if (debug) Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Processing lane {laneIndex} with allowedTypes={allowedTypes}, defaultMask={defaultMask}"); #endif if (laneInfo.m_vehicleType == VehicleInfo.VehicleType.Car && allowedTypes == defaultMask) { #if DEBUGHK if (debug) Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}, lane {laneIndex}: Allowed types equal default mask. Ignoring lane."); #endif // no vehicle restrictions applied, generic lights are handled further below ++defaultLanes; continue; } ExtVehicleType mask = allowedTypes & ~ExtVehicleType.Emergency; #if DEBUGHK if (debug) Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}, lane {laneIndex}: Trying to add {mask} light"); #endif ICustomSegmentLight segmentLight; if (!CustomLights.TryGetValue(mask, out segmentLight)) { // add a new light segmentLight = new CustomSegmentLight(this, RoadBaseAI.TrafficLightState.Red); if (mainLight != null) { segmentLight.CurrentMode = mainLight.CurrentMode; segmentLight.SetStates(mainLight.LightMain, mainLight.LightLeft, mainLight.LightRight, false); } #if DEBUGHK if (debug) Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}, lane {laneIndex}: Light for mask {mask} does not exist. Created new light: {segmentLight} (mainLight: {mainLight})"); #endif CustomLights.Add(mask, segmentLight); VehicleTypes.AddFirst(mask); } mainVehicleType = mask; VehicleTypeByLaneIndex[laneIndex] = mask; laneIndicesWithoutSeparateLights.Remove(laneIndex); ++separateLanes; //addPedestrianLight = true; setupLights.Add(mask); SeparateVehicleTypes |= mask; #if DEBUGHK if (debug) Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Finished processing lane {laneIndex}: mainVehicleType={mainVehicleType}, VehicleTypeByLaneIndex={VehicleTypeByLaneIndex.ArrayToString()}, laneIndicesWithoutSeparateLights={laneIndicesWithoutSeparateLights.CollectionToString()}, numLights={separateLanes}, SeparateVehicleTypes={SeparateVehicleTypes}"); #endif } } if (separateLanes == 0 || defaultLanes > 0) { #if DEBUGHK if (debug) Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Adding default main vehicle light: {DEFAULT_MAIN_VEHICLETYPE}"); #endif // generic traffic lights ICustomSegmentLight defaultSegmentLight; if (!CustomLights.TryGetValue(DEFAULT_MAIN_VEHICLETYPE, out defaultSegmentLight)) { defaultSegmentLight = new CustomSegmentLight(this, RoadBaseAI.TrafficLightState.Red); if (mainLight != null) { defaultSegmentLight.CurrentMode = mainLight.CurrentMode; defaultSegmentLight.SetStates(mainLight.LightMain, mainLight.LightLeft, mainLight.LightRight, false); } CustomLights.Add(DEFAULT_MAIN_VEHICLETYPE, defaultSegmentLight); VehicleTypes.AddFirst(DEFAULT_MAIN_VEHICLETYPE); } mainVehicleType = DEFAULT_MAIN_VEHICLETYPE; setupLights.Add(DEFAULT_MAIN_VEHICLETYPE); foreach (byte laneIndex in laneIndicesWithoutSeparateLights) { VehicleTypeByLaneIndex[laneIndex] = ExtVehicleType.None; } #if DEBUGHK if (debug) Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Added default main vehicle light: {defaultSegmentLight}"); #endif //addPedestrianLight = true; } else { //addPedestrianLight = allAllowedMask == ExtVehicleType.None || (allAllowedMask & ~ExtVehicleType.RailVehicle) != ExtVehicleType.None; } #if DEBUGHK if (debug) Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Created all necessary lights. VehicleTypeByLaneIndex={VehicleTypeByLaneIndex.ArrayToString()}, CustomLights={CustomLights.DictionaryToString()}"); #endif if (mayDelete) { // delete traffic lights for non-existing vehicle-separated configurations HashSet vehicleTypesToDelete = new HashSet(); foreach (KeyValuePair e in CustomLights) { /*if (e.Key == DEFAULT_MAIN_VEHICLETYPE) { continue; }*/ if (!setupLights.Contains(e.Key)) { vehicleTypesToDelete.Add(e.Key); } } #if DEBUGHK if (debug) Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Going to delete unnecessary lights now: vehicleTypesToDelete={vehicleTypesToDelete.CollectionToString()}"); #endif foreach (ExtVehicleType vehicleType in vehicleTypesToDelete) { CustomLights.Remove(vehicleType); VehicleTypes.Remove(vehicleType); } } if (CustomLights.ContainsKey(DEFAULT_MAIN_VEHICLETYPE) && VehicleTypes.First.Value != DEFAULT_MAIN_VEHICLETYPE) { VehicleTypes.Remove(DEFAULT_MAIN_VEHICLETYPE); VehicleTypes.AddFirst(DEFAULT_MAIN_VEHICLETYPE); } //if (addPedestrianLight) { #if DEBUGHK if (debug) Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: adding pedestrian light"); #endif if (InternalPedestrianLightState == null) { InternalPedestrianLightState = RoadBaseAI.TrafficLightState.Red; } /*} else { InternalPedestrianLightState = null; }*/ OnChange(calculateAutoPedLight); #if DEBUGHK if (debug) Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Housekeeping complete. VehicleTypeByLaneIndex={VehicleTypeByLaneIndex.ArrayToString()} CustomLights={CustomLights.DictionaryToString()}"); #endif } } } ================================================ FILE: TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs ================================================ #define DEBUGTTLx using System; using System.Collections.Generic; using ColossalFramework; using TrafficManager.Custom.AI; using TrafficManager.Geometry; using TrafficManager.Traffic; using TrafficManager.Manager; using System.Linq; using TrafficManager.Util; using System.Threading; using TrafficManager.State; using GenericGameBridge.Service; using CSUtil.Commons; using TrafficManager.Geometry.Impl; using CSUtil.Commons.Benchmark; using TrafficManager.Manager.Impl; namespace TrafficManager.TrafficLight.Impl { // TODO define TimedTrafficLights per node group, not per individual nodes public class TimedTrafficLights : ITimedTrafficLights { public ushort NodeId { get; private set; } /// /// In case the traffic light is set for a group of nodes, the master node decides /// if all member steps are done. /// public ushort MasterNodeId { get; set; // TODO private set } public List Steps = new List(); public int CurrentStep { get; set; } = 0; public IList NodeGroup { get; set; } // TODO private set public bool TestMode { get; set; } = false; // TODO private set private bool started = false; /// /// Indicates the total amount and direction of rotation that was applied to this timed traffic light /// public short RotationOffset { get; private set; } = 0; public IDictionary> Directions { get; private set; } = null; /// /// Segment ends that were set up for this timed traffic light /// private ICollection segmentEndIds = new HashSet(); public override string ToString() { return $"[TimedTrafficLights\n" + "\t" + $"NodeId = {NodeId}\n" + "\t" + $"masterNodeId = {MasterNodeId}\n" + "\t" + $"Steps = {Steps.CollectionToString()}\n" + "\t" + $"NodeGroup = {NodeGroup.CollectionToString()}\n" + "\t" + $"testMode = {TestMode}\n" + "\t" + $"started = {started}\n" + "\t" + $"Directions = {Directions.DictionaryToString()}\n" + "\t" + $"segmentEndIds = {segmentEndIds.CollectionToString()}\n" + "TimedTrafficLights]"; } public TimedTrafficLights(ushort nodeId, IEnumerable nodeGroup) { this.NodeId = nodeId; NodeGroup = new List(nodeGroup); MasterNodeId = NodeGroup[0]; UpdateDirections(NodeGeometry.Get(nodeId)); UpdateSegmentEnds(); started = false; } private TimedTrafficLights() { } public void PasteSteps(ITimedTrafficLights sourceTimedLight) { Stop(); Steps.Clear(); RotationOffset = 0; IList clockSortedSourceSegmentIds = new List(); Constants.ServiceFactory.NetService.IterateNodeSegments(sourceTimedLight.NodeId, ClockDirection.Clockwise, delegate (ushort segmentId, ref NetSegment segment) { clockSortedSourceSegmentIds.Add(segmentId); return true; }); IList clockSortedTargetSegmentIds = new List(); Constants.ServiceFactory.NetService.IterateNodeSegments(NodeId, ClockDirection.Clockwise, delegate (ushort segmentId, ref NetSegment segment) { clockSortedTargetSegmentIds.Add(segmentId); return true; }); if (clockSortedTargetSegmentIds.Count != clockSortedSourceSegmentIds.Count) { throw new Exception($"TimedTrafficLights.PasteLight: Segment count mismatch -- source node {sourceTimedLight.NodeId}: {clockSortedSourceSegmentIds.CollectionToString()} vs. target node {NodeId}: {clockSortedTargetSegmentIds.CollectionToString()}"); } for (int stepIndex = 0; stepIndex < sourceTimedLight.NumSteps(); ++stepIndex) { ITimedTrafficLightsStep sourceStep = sourceTimedLight.GetStep(stepIndex); TimedTrafficLightsStep targetStep = new TimedTrafficLightsStep(this, sourceStep.MinTime, sourceStep.MaxTime, sourceStep.ChangeMetric, sourceStep.WaitFlowBalance); for (int i = 0; i < clockSortedSourceSegmentIds.Count; ++i) { ushort sourceSegmentId = clockSortedSourceSegmentIds[i]; ushort targetSegmentId = clockSortedTargetSegmentIds[i]; SegmentGeometry segGeo = SegmentGeometry.Get(targetSegmentId); if (segGeo == null) { throw new Exception($"TimedTrafficLights.PasteSteps: No geometry information available for segment {targetSegmentId}"); } bool targetStartNode = segGeo.StartNodeId() == NodeId; ICustomSegmentLights sourceLights = sourceStep.CustomSegmentLights[sourceSegmentId]; ICustomSegmentLights targetLights = sourceLights.Clone(targetStep, false); targetStep.SetSegmentLights(targetSegmentId, targetLights); Constants.ManagerFactory.CustomSegmentLightsManager.ApplyLightModes(targetSegmentId, targetStartNode, targetLights); } Steps.Add(targetStep); } if (sourceTimedLight.IsStarted()) { Start(); } } private object rotateLock = new object(); private void Rotate(ArrowDirection dir) { if (! IsMasterNode() || NodeGroup.Count != 1 || Steps.Count <= 0) { return; } Stop(); try { Monitor.Enter(rotateLock); Log._Debug($"TimedTrafficLights.Rotate({dir}) @ node {NodeId}: Rotating timed traffic light."); if (dir != ArrowDirection.Left && dir != ArrowDirection.Right) { throw new NotSupportedException(); } IList clockSortedSegmentIds = new List(); Constants.ServiceFactory.NetService.IterateNodeSegments(NodeId, dir == ArrowDirection.Right ? ClockDirection.Clockwise : ClockDirection.CounterClockwise, delegate (ushort segmentId, ref NetSegment segment) { clockSortedSegmentIds.Add(segmentId); return true; }); Log._Debug($"TimedTrafficLights.Rotate({dir}) @ node {NodeId}: Clock-sorted segment ids: {clockSortedSegmentIds.CollectionToString()}"); if (clockSortedSegmentIds.Count <= 0) { return; } int stepIndex = -1; foreach (TimedTrafficLightsStep step in Steps) { ++stepIndex; ICustomSegmentLights bufferedLights = null; for (int sourceIndex = 0; sourceIndex < clockSortedSegmentIds.Count; ++sourceIndex) { ushort sourceSegmentId = clockSortedSegmentIds[sourceIndex]; int targetIndex = (sourceIndex + 1) % clockSortedSegmentIds.Count; ushort targetSegmentId = clockSortedSegmentIds[targetIndex]; SegmentGeometry targetSegGeo = SegmentGeometry.Get(targetSegmentId); // should never fail here Log._Debug($"TimedTrafficLights.Rotate({dir}) @ node {NodeId}: Moving light @ seg. {sourceSegmentId} to seg. {targetSegmentId} @ step {stepIndex}"); ICustomSegmentLights sourceLights = sourceIndex == 0 ? step.RemoveSegmentLights(sourceSegmentId) : bufferedLights; if (sourceLights == null) { throw new Exception($"TimedTrafficLights.Rotate({dir}): Error occurred while copying custom lights from {sourceSegmentId} to {targetSegmentId} @ step {stepIndex}: sourceLights is null @ sourceIndex={sourceIndex}, targetIndex={targetIndex}"); } bufferedLights = step.RemoveSegmentLights(targetSegmentId); sourceLights.Relocate(targetSegmentId, targetSegGeo.StartNodeId() == NodeId); if (!step.SetSegmentLights(targetSegmentId, sourceLights)) { throw new Exception($"TimedTrafficLights.Rotate({dir}): Error occurred while copying custom lights from {sourceSegmentId} to {targetSegmentId} @ step {stepIndex}: could not set lights for target segment @ sourceIndex={sourceIndex}, targetIndex={targetIndex}"); } } } switch (dir) { case ArrowDirection.Left: RotationOffset = (short)((RotationOffset + clockSortedSegmentIds.Count - 1) % clockSortedSegmentIds.Count); break; case ArrowDirection.Right: RotationOffset = (short)((RotationOffset + 1) % clockSortedSegmentIds.Count); break; } CurrentStep = 0; SetLights(true); } finally { Monitor.Exit(rotateLock); } } public void RotateLeft() { Rotate(ArrowDirection.Left); } public void RotateRight() { Rotate(ArrowDirection.Right); } private void UpdateDirections(NodeGeometry nodeGeo) { Log._Debug($">>>>> TimedTrafficLights.UpdateDirections: called for node {NodeId}"); Directions = new TinyDictionary>(); foreach (SegmentEndGeometry srcSegEndGeo in nodeGeo.SegmentEndGeometries) { if (srcSegEndGeo == null) continue; Log._Debug($"TimedTrafficLights.UpdateDirections: Processing source segment {srcSegEndGeo.SegmentId}"); SegmentGeometry srcSegGeo = srcSegEndGeo.GetSegmentGeometry(); if (srcSegGeo == null) { continue; } IDictionary dirs = new TinyDictionary(); Directions.Add(srcSegEndGeo.SegmentId, dirs); foreach (SegmentEndGeometry trgSegEndGeo in nodeGeo.SegmentEndGeometries) { if (trgSegEndGeo == null) continue; ArrowDirection dir = srcSegGeo.GetDirection(trgSegEndGeo.SegmentId, srcSegEndGeo.StartNode); if (dir == ArrowDirection.None) { Log.Error($"TimedTrafficLights.UpdateDirections: Processing source segment {srcSegEndGeo.SegmentId}, target segment {trgSegEndGeo.SegmentId}: Invalid direction {dir}"); continue; } dirs.Add(trgSegEndGeo.SegmentId, dir); Log._Debug($"TimedTrafficLights.UpdateDirections: Processing source segment {srcSegEndGeo.SegmentId}, target segment {trgSegEndGeo.SegmentId}: adding dir {dir}"); } } Log._Debug($"<<<<< TimedTrafficLights.UpdateDirections: finished for node {NodeId}: {Directions.DictionaryToString()}"); } public void OnUpdate(NodeGeometry nodeGeo) { // not required since TrafficLightSimulation handles this for us: OnGeometryUpdate() is being called. // TODO improve } public bool IsMasterNode() { return MasterNodeId == NodeId; } public ITimedTrafficLightsStep AddStep(int minTime, int maxTime, StepChangeMetric changeMetric, float waitFlowBalance, bool makeRed = false) { // TODO currently, this method must be called for each node in the node group individually if (minTime < 0) minTime = 0; if (maxTime <= 0) maxTime = 1; if (maxTime < minTime) maxTime = minTime; TimedTrafficLightsStep step = new TimedTrafficLightsStep(this, minTime, maxTime, changeMetric, waitFlowBalance, makeRed); Steps.Add(step); return step; } public void Start() { Start(0); } public void Start(int stepIndex) { // TODO currently, this method must be called for each node in the node group individually if (stepIndex < 0 || stepIndex >= Steps.Count) { stepIndex = 0; } /*if (!housekeeping()) return;*/ Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nodeId, ref NetNode node) { Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(NodeId, ref node); return true; }); foreach (TimedTrafficLightsStep step in Steps) { foreach (KeyValuePair e in step.CustomSegmentLights) { e.Value.Housekeeping(true, true); } } CheckInvalidPedestrianLights(); CurrentStep = stepIndex; Steps[stepIndex].Start(); Steps[stepIndex].UpdateLiveLights(); started = true; } private void CheckInvalidPedestrianLights() { ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; NodeGeometry nodeGeometry = NodeGeometry.Get(NodeId); //Log._Debug($"Checking for invalid pedestrian lights @ {NodeId}."); foreach (SegmentEndGeometry end in nodeGeometry.SegmentEndGeometries) { if (end == null) { continue; } ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(end.SegmentId, end.StartNode); if (lights == null) { Log.Warning($"TimedTrafficLights.CheckInvalidPedestrianLights() @ node {NodeId}: Could not retrieve segment lights for segment {end.SegmentId} @ start {end.StartNode}."); continue; } //Log._Debug($"Checking seg. {segmentId} @ {NodeId}."); bool needsAlwaysGreenPedestrian = true; int i = 0; foreach (TimedTrafficLightsStep step in Steps) { //Log._Debug($"Checking step {i}, seg. {segmentId} @ {NodeId}."); if (!step.CustomSegmentLights.ContainsKey(end.SegmentId)) { //Log._Debug($"Step {i} @ {NodeId} does not contain a segment light for seg. {segmentId}."); ++i; continue; } //Log._Debug($"Checking step {i}, seg. {segmentId} @ {NodeId}: {step.segmentLights[segmentId].PedestrianLightState} (pedestrianLightState={step.segmentLights[segmentId].pedestrianLightState}, ManualPedestrianMode={step.segmentLights[segmentId].ManualPedestrianMode}, AutoPedestrianLightState={step.segmentLights[segmentId].AutoPedestrianLightState}"); if (step.CustomSegmentLights[end.SegmentId].PedestrianLightState == RoadBaseAI.TrafficLightState.Green) { //Log._Debug($"Step {i} @ {NodeId} has a green ped. light @ seg. {segmentId}."); needsAlwaysGreenPedestrian = false; break; } ++i; } //Log._Debug($"Setting InvalidPedestrianLight of seg. {segmentId} @ {NodeId} to {needsAlwaysGreenPedestrian}."); lights.InvalidPedestrianLight = needsAlwaysGreenPedestrian; } } private void ClearInvalidPedestrianLights() { ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; NodeGeometry nodeGeometry = NodeGeometry.Get(NodeId); //Log._Debug($"Checking for invalid pedestrian lights @ {NodeId}."); foreach (SegmentEndGeometry end in nodeGeometry.SegmentEndGeometries) { if (end == null) { continue; } ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(end.SegmentId, end.StartNode); if (lights == null) { Log.Warning($"TimedTrafficLights.ClearInvalidPedestrianLights() @ node {NodeId}: Could not retrieve segment lights for segment {end.SegmentId} @ start {end.StartNode}."); continue; } lights.InvalidPedestrianLight = false; } } public void RemoveNodeFromGroup(ushort otherNodeId) { // TODO currently, this method must be called for each node in the node group individually NodeGroup.Remove(otherNodeId); if (NodeGroup.Count <= 0) { Constants.ManagerFactory.TrafficLightSimulationManager.RemoveNodeFromSimulation(NodeId, true, false); return; } MasterNodeId = NodeGroup[0]; } // TODO improve & remove public bool Housekeeping() { // TODO currently, this method must be called for each node in the node group individually //Log._Debug($"Housekeeping timed light @ {NodeId}"); if (NodeGroup == null || NodeGroup.Count <= 0) { Stop(); return false; } //Log.Warning($"Timed housekeeping: Setting master node to {NodeGroup[0]}"); MasterNodeId = NodeGroup[0]; if (IsStarted()) CheckInvalidPedestrianLights(); int i = 0; foreach (TimedTrafficLightsStep step in Steps) { foreach (CustomSegmentLights lights in step.CustomSegmentLights.Values) { //Log._Debug($"----- Housekeeping timed light at step {i}, seg. {lights.SegmentId} @ {NodeId}"); Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(lights.SegmentId, lights.StartNode).Housekeeping(true, true); lights.Housekeeping(true, true); } ++i; } return true; } public void MoveStep(int oldPos, int newPos) { // TODO currently, this method must be called for each node in the node group individually var oldStep = Steps[oldPos]; Steps.RemoveAt(oldPos); Steps.Insert(newPos, oldStep); } public void Stop() { // TODO currently, this method must be called for each node in the node group individually started = false; foreach (TimedTrafficLightsStep step in Steps) { step.Reset(); } ClearInvalidPedestrianLights(); } ~TimedTrafficLights() { Destroy(); } public void Destroy() { // TODO currently, this method must be called for each node in the node group individually started = false; DestroySegmentEnds(); Steps = null; NodeGroup = null; } public bool IsStarted() { // TODO currently, this method must be called for each node in the node group individually return started; } public int NumSteps() { // TODO currently, this method must be called for each node in the node group individually return Steps.Count; } public ITimedTrafficLightsStep GetStep(int stepId) { // TODO currently, this method must be called for each node in the node group individually return Steps[stepId]; } public void SimulationStep() { // TODO this method is currently called on each node, but should be called on the master node only #if DEBUGTTL bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif if (!IsMasterNode() || !IsStarted()) { #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} isMasterNode={IsMasterNode()} IsStarted={IsStarted()}"); #endif return; } // we are the master node /*if (!housekeeping()) { #if DEBUGTTL Log.Warning($"TTL SimStep: *STOP* NodeId={NodeId} Housekeeping detected that this timed traffic light has become invalid: {NodeId}."); #endif Stop(); return; }*/ #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={NodeId} Setting lights (1)"); #endif #if BENCHMARK //using (var bm = new Benchmark(null, "SetLights.1")) { #endif SetLights(); #if BENCHMARK //} #endif #if BENCHMARK //using (var bm = new Benchmark(null, "StepDone")) { #endif if (!Steps[CurrentStep].StepDone(true)) { #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} current step ({CurrentStep}) is not done."); #endif return; } #if BENCHMARK //} #endif // step is done #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={NodeId} Setting lights (2)"); #endif TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; if (Steps[CurrentStep].NextStepRefIndex < 0) { #if DEBUGTTL if (debug) { Log._Debug($"TimedTrafficLights.SimulationStep(): Step {CurrentStep} is done at timed light {NodeId}. Determining next step."); } #endif // next step has not yet identified yet. check for minTime=0 steps int nextStepIndex = (CurrentStep + 1) % NumSteps(); if (Steps[nextStepIndex].MinTime == 0 && Steps[nextStepIndex].ChangeMetric == Steps[CurrentStep].ChangeMetric) { #if BENCHMARK //using (var bm = new Benchmark(null, "bestNextStepIndex")) { #endif // next step has minTime=0. calculate flow/wait ratios and compare. int prevStepIndex = CurrentStep; // Steps[CurrentStep].minFlow - Steps[CurrentStep].maxWait float maxWaitFlowDiff = Steps[CurrentStep].GetMetric(Steps[CurrentStep].CurrentFlow, Steps[CurrentStep].CurrentWait); if (float.IsNaN(maxWaitFlowDiff)) maxWaitFlowDiff = float.MinValue; int bestNextStepIndex = prevStepIndex; #if DEBUGTTL if (debug) { Log._Debug($"TimedTrafficLights.SimulationStep(): Next step {nextStepIndex} has minTime = 0 at timed light {NodeId}. Old step {CurrentStep} has waitFlowDiff={maxWaitFlowDiff} (flow={Steps[CurrentStep].CurrentFlow}, wait={Steps[CurrentStep].CurrentWait})."); } #endif while (nextStepIndex != prevStepIndex) { float wait; float flow; Steps[nextStepIndex].CalcWaitFlow(false, nextStepIndex, out wait, out flow); //float flowWaitDiff = flow - wait; float flowWaitDiff = Steps[nextStepIndex].GetMetric(flow, wait); if (flowWaitDiff > maxWaitFlowDiff) { maxWaitFlowDiff = flowWaitDiff; bestNextStepIndex = nextStepIndex; } #if DEBUGTTL if (debug) { Log._Debug($"TimedTrafficLights.SimulationStep(): Checking upcoming step {nextStepIndex} @ node {NodeId}: flow={flow} wait={wait} minTime={Steps[nextStepIndex].MinTime}. bestWaitFlowDiff={bestNextStepIndex}, bestNextStepIndex={bestNextStepIndex}"); } #endif if (Steps[nextStepIndex].MinTime != 0) { int stepAfterPrev = (prevStepIndex + 1) % NumSteps(); if (nextStepIndex == stepAfterPrev) { // always switch if the next step has a minimum time assigned bestNextStepIndex = stepAfterPrev; } break; } nextStepIndex = (nextStepIndex + 1) % NumSteps(); if (Steps[nextStepIndex].ChangeMetric != Steps[CurrentStep].ChangeMetric) { break; } } if (bestNextStepIndex == CurrentStep) { #if DEBUGTTL if (debug) { Log._Debug($"TimedTrafficLights.SimulationStep(): Best next step {bestNextStepIndex} (wait/flow diff = {maxWaitFlowDiff}) equals CurrentStep @ node {NodeId}."); } #endif // restart the current step foreach (ushort slaveNodeId in NodeGroup) { if (! tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { continue; } ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].TimedLight; slaveTTL.GetStep(CurrentStep).Start(CurrentStep); slaveTTL.GetStep(CurrentStep).UpdateLiveLights(); } return; } else { #if DEBUGTTL if (debug) { Log._Debug($"TimedTrafficLights.SimulationStep(): Best next step {bestNextStepIndex} (wait/flow diff = {maxWaitFlowDiff}) does not equal CurrentStep @ node {NodeId}."); } #endif // set next step reference index for assuring a correct end transition foreach (ushort slaveNodeId in NodeGroup) { if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { continue; } ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].TimedLight; slaveTTL.GetStep(CurrentStep).NextStepRefIndex = bestNextStepIndex; } } #if BENCHMARK //} #endif } else { Steps[CurrentStep].NextStepRefIndex = nextStepIndex; } } #if BENCHMARK //using (var bm = new Benchmark(null, "SetLights.2")) { #endif SetLights(); // check if this is needed #if BENCHMARK //} #endif #if BENCHMARK //using (var bm = new Benchmark(null, "IsEndTransitionDone")) { #endif if (!Steps[CurrentStep].IsEndTransitionDone()) { #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} current step ({CurrentStep}): end transition is not done."); #endif return; } #if BENCHMARK //} #endif // ending transition (yellow) finished #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={NodeId} ending transition done. NodeGroup={string.Join(", ", NodeGroup.Select(x => x.ToString()).ToArray())}, nodeId={NodeId}, NumSteps={NumSteps()}"); #endif #if BENCHMARK //using (var bm = new Benchmark(null, "ChangeStep")) { #endif // change step int newStepIndex = Steps[CurrentStep].NextStepRefIndex; int oldStepIndex = CurrentStep; foreach (ushort slaveNodeId in NodeGroup) { if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { continue; } ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].TimedLight; slaveTTL.CurrentStep = newStepIndex; #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={slaveNodeId} setting lights of next step: {CurrentStep}"); #endif slaveTTL.GetStep(oldStepIndex).NextStepRefIndex = -1; slaveTTL.GetStep(newStepIndex).Start(oldStepIndex); slaveTTL.GetStep(newStepIndex).UpdateLiveLights(); } #if BENCHMARK //} #endif } public void SetLights(bool noTransition=false) { if (Steps.Count <= 0) { return; } TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; // set lights foreach (ushort slaveNodeId in NodeGroup) { if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { continue; } ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].TimedLight; slaveTTL.GetStep(CurrentStep).UpdateLiveLights(noTransition); } } public void SkipStep(bool setLights=true, int prevStepRefIndex=-1) { if (!IsMasterNode()) return; TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; var newCurrentStep = (CurrentStep + 1) % NumSteps(); foreach (ushort slaveNodeId in NodeGroup) { if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { continue; } ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].TimedLight; slaveTTL.GetStep(CurrentStep).SetStepDone(); slaveTTL.CurrentStep = newCurrentStep; slaveTTL.GetStep(newCurrentStep).Start(prevStepRefIndex); if (setLights) { slaveTTL.GetStep(newCurrentStep).UpdateLiveLights(); } } } public long CheckNextChange(ushort segmentId, bool startNode, ExtVehicleType vehicleType, int lightType) { var curStep = CurrentStep; var nextStep = (CurrentStep + 1) % NumSteps(); var numFrames = Steps[CurrentStep].MaxTimeRemaining(); RoadBaseAI.TrafficLightState currentState; ICustomSegmentLights segmentLights = Constants.ManagerFactory.CustomSegmentLightsManager.GetSegmentLights(segmentId, startNode, false); if (segmentLights == null) { Log._Debug($"CheckNextChange: No segment lights at node {NodeId}, segment {segmentId}"); return 99; } ICustomSegmentLight segmentLight = segmentLights.GetCustomLight(vehicleType); if (segmentLight == null) { Log._Debug($"CheckNextChange: No segment light at node {NodeId}, segment {segmentId}"); return 99; } if (lightType == 0) currentState = segmentLight.LightMain; else if (lightType == 1) currentState = segmentLight.LightLeft; else if (lightType == 2) currentState = segmentLight.LightRight; else currentState = segmentLights.PedestrianLightState == null ? RoadBaseAI.TrafficLightState.Red : (RoadBaseAI.TrafficLightState)segmentLights.PedestrianLightState; while (true) { if (nextStep == curStep) { numFrames = 99; break; } var light = Steps[nextStep].GetLightState(segmentId, vehicleType, lightType); if (light != currentState) { break; } else { numFrames += Steps[nextStep].MaxTime; } nextStep = (nextStep + 1) % NumSteps(); } return numFrames; } public void ResetSteps() { Steps.Clear(); } public void RemoveStep(int id) { Steps.RemoveAt(id); } public void OnGeometryUpdate() { NodeGeometry nodeGeometry = NodeGeometry.Get(NodeId); Log._Debug($"TimedTrafficLights.OnGeometryUpdate: called for timed traffic light @ {NodeId}. nodeGeometry={nodeGeometry}"); UpdateDirections(nodeGeometry); UpdateSegmentEnds(); if (NumSteps() <= 0) { Log._Debug($"TimedTrafficLights.OnGeometryUpdate: no steps @ {NodeId}"); return; } BackUpInvalidStepSegments(nodeGeometry); HandleNewSegments(nodeGeometry); } /// /// Moves all custom segment lights that are associated with an invalid segment to a special container for later reuse /// private void BackUpInvalidStepSegments(NodeGeometry nodeGeo) { #if DEBUGTTL bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; if (debug) Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: called for timed traffic light @ {NodeId}"); #endif ICollection validSegments = new HashSet(); foreach (SegmentEndGeometry end in nodeGeo.SegmentEndGeometries) { if (end == null) { continue; } validSegments.Add(end.SegmentId); } #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: valid segments @ {NodeId}: {validSegments.CollectionToString()}"); #endif int i = 0; foreach (TimedTrafficLightsStep step in Steps) { ICollection invalidSegmentIds = new HashSet(); foreach (KeyValuePair e in step.CustomSegmentLights) { if (! validSegments.Contains(e.Key)) { step.InvalidSegmentLights.AddLast(e.Value); invalidSegmentIds.Add(e.Key); #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: Detected invalid segment @ step {i}, node {NodeId}: {e.Key}"); #endif } } foreach (ushort invalidSegmentId in invalidSegmentIds) { #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: Removing invalid segment {invalidSegmentId} from step {i} @ node {NodeId}"); #endif step.CustomSegmentLights.Remove(invalidSegmentId); } #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments finished for TTL step {i} @ node {NodeId}: step.CustomSegmentLights={step.CustomSegmentLights.DictionaryToString()} step.InvalidSegmentLights={step.InvalidSegmentLights.CollectionToString()}"); #endif ++i; } } /// /// Processes new segments and adds them to the steps. If steps contain a custom light /// for an old invalid segment, this light is being reused for the new segment. /// /// private void HandleNewSegments(NodeGeometry nodeGeo) { #if DEBUGTTL bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; ITrafficPriorityManager prioMan = Constants.ManagerFactory.TrafficPriorityManager; //Log._Debug($"Checking for invalid pedestrian lights @ {NodeId}."); foreach (SegmentEndGeometry end in nodeGeo.SegmentEndGeometries) { if (end == null) { continue; } #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.HandleNewSegments: handling existing seg. {end.SegmentId} @ {NodeId}"); #endif if (Steps[0].CustomSegmentLights.ContainsKey(end.SegmentId)) { continue; } // segment was created RotationOffset = 0; #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.HandleNewSegments: New segment detected: {end.SegmentId} @ {NodeId}"); #endif int stepIndex = -1; foreach (TimedTrafficLightsStep step in Steps) { ++stepIndex; LinkedListNode lightsToReuseNode = step.InvalidSegmentLights.First; if (lightsToReuseNode == null) { // no old segment found: create a fresh custom light #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.HandleNewSegments: Adding new segment {end.SegmentId} to node {NodeId} without reusing old segment"); #endif if (! step.AddSegment(end.SegmentId, end.StartNode, true)) { #if DEBUGTTL if (debug) Log.Warning($"TimedTrafficLights.HandleNewSegments: Failed to add segment {end.SegmentId} @ start {end.StartNode} to node {NodeId}"); #endif } } else { // reuse old lights step.InvalidSegmentLights.RemoveFirst(); ICustomSegmentLights lightsToReuse = lightsToReuseNode.Value; #if DEBUGTTL if (debug) Log._Debug($"Replacing old segment @ {NodeId} with new segment {end.SegmentId}"); #endif lightsToReuse.Relocate(end.SegmentId, end.StartNode); step.SetSegmentLights(end.SegmentId, lightsToReuse); } } } } public ITimedTrafficLights MasterLights() { return TrafficLightSimulationManager.Instance.TrafficLightSimulations[MasterNodeId].TimedLight; } public void SetTestMode(bool testMode) { this.TestMode = false; if (!IsStarted()) return; this.TestMode = testMode; } public bool IsInTestMode() { if (!IsStarted()) { TestMode = false; } return TestMode; } public void ChangeLightMode(ushort segmentId, ExtVehicleType vehicleType, LightMode mode) { #if DEBUGTTL bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { #if DEBUGTTL if (debug) Log.Error($"TimedTrafficLights.ChangeLightMode: No geometry information available for segment {segmentId}"); #endif return; } foreach (TimedTrafficLightsStep step in Steps) { step.ChangeLightMode(segmentId, vehicleType, mode); } Constants.ManagerFactory.CustomSegmentLightsManager.SetLightMode(segmentId, segGeo.StartNodeId() == NodeId, vehicleType, mode); } public void Join(ITimedTrafficLights otherTimedLight) { TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; if (NumSteps() < otherTimedLight.NumSteps()) { // increase the number of steps at our timed lights for (int i = NumSteps(); i < otherTimedLight.NumSteps(); ++i) { ITimedTrafficLightsStep otherStep = otherTimedLight.GetStep(i); foreach (ushort slaveNodeId in NodeGroup) { if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { continue; } ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].TimedLight; slaveTTL.AddStep(otherStep.MinTime, otherStep.MaxTime, otherStep.ChangeMetric, otherStep.WaitFlowBalance, true); } } } else { // increase the number of steps at their timed lights for (int i = otherTimedLight.NumSteps(); i < NumSteps(); ++i) { ITimedTrafficLightsStep ourStep = GetStep(i); foreach (ushort slaveNodeId in otherTimedLight.NodeGroup) { if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { continue; } ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].TimedLight; slaveTTL.AddStep(ourStep.MinTime, ourStep.MaxTime, ourStep.ChangeMetric, ourStep.WaitFlowBalance, true); } } } // join groups and re-defined master node, determine mean min/max times & mean wait-flow-balances HashSet newNodeGroupSet = new HashSet(); newNodeGroupSet.UnionWith(NodeGroup); newNodeGroupSet.UnionWith(otherTimedLight.NodeGroup); List newNodeGroup = new List(newNodeGroupSet); ushort newMasterNodeId = newNodeGroup[0]; int[] minTimes = new int[NumSteps()]; int[] maxTimes = new int[NumSteps()]; float[] waitFlowBalances = new float[NumSteps()]; StepChangeMetric?[] stepChangeMetrics = new StepChangeMetric?[NumSteps()]; foreach (ushort timedNodeId in newNodeGroup) { if (!tlsMan.TrafficLightSimulations[timedNodeId].IsTimedLight()) { continue; } ITimedTrafficLights ttl = tlsMan.TrafficLightSimulations[timedNodeId].TimedLight; for (int i = 0; i < NumSteps(); ++i) { minTimes[i] += ttl.GetStep(i).MinTime; maxTimes[i] += ttl.GetStep(i).MaxTime; waitFlowBalances[i] += ttl.GetStep(i).WaitFlowBalance; StepChangeMetric metric = ttl.GetStep(i).ChangeMetric; if (metric != StepChangeMetric.Default) { if (stepChangeMetrics[i] == null) { // remember first non-default setting stepChangeMetrics[i] = metric; } else if (stepChangeMetrics[i] != metric) { // reset back to default on metric mismatch stepChangeMetrics[i] = StepChangeMetric.Default; } } } ttl.NodeGroup = newNodeGroup; ttl.MasterNodeId = newMasterNodeId; } // build means if (NumSteps() > 0) { for (int i = 0; i < NumSteps(); ++i) { minTimes[i] = Math.Max(0, minTimes[i] / newNodeGroup.Count); maxTimes[i] = Math.Max(1, maxTimes[i] / newNodeGroup.Count); waitFlowBalances[i] = Math.Max(0.001f, waitFlowBalances[i] / (float)newNodeGroup.Count); } } // apply means & reset foreach (ushort timedNodeId in newNodeGroup) { if (!tlsMan.TrafficLightSimulations[timedNodeId].IsTimedLight()) { continue; } ITimedTrafficLights ttl = tlsMan.TrafficLightSimulations[timedNodeId].TimedLight; ttl.Stop(); ttl.TestMode = false; for (int i = 0; i < NumSteps(); ++i) { ttl.GetStep(i).MinTime = minTimes[i]; ttl.GetStep(i).MaxTime = maxTimes[i]; ttl.GetStep(i).WaitFlowBalance = waitFlowBalances[i]; ttl.GetStep(i).ChangeMetric = stepChangeMetrics[i] == null ? StepChangeMetric.Default : (StepChangeMetric)stepChangeMetrics[i]; } } } private void UpdateSegmentEnds() { #if DEBUGTTL bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; if (debug) Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: called for node {NodeId}"); #endif ISegmentEndManager segEndMan = Constants.ManagerFactory.SegmentEndManager; ICollection segmentEndsToDelete = new HashSet(); // update currently set segment ends foreach (SegmentEndId endId in segmentEndIds) { #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: updating existing segment end {endId} for node {NodeId}"); #endif if (!segEndMan.UpdateSegmentEnd(endId)) { #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: segment end {endId} @ node {NodeId} is invalid"); #endif segmentEndsToDelete.Add(endId); } else { ISegmentEnd end = segEndMan.GetSegmentEnd(endId); if (end.NodeId != NodeId) { #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Segment end {end} is valid and updated but does not belong to TTL node {NodeId} anymore."); #endif segmentEndsToDelete.Add(endId); } else { #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: segment end {endId} @ node {NodeId} is valid"); #endif } } } // remove all invalid segment ends foreach (SegmentEndId endId in segmentEndsToDelete) { #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Removing invalid segment end {endId} @ node {NodeId}"); #endif segmentEndIds.Remove(endId); } // set up new segment ends NodeGeometry nodeGeo = NodeGeometry.Get(NodeId); #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Setting up new segment ends @ node {NodeId}. nodeGeo={nodeGeo}"); #endif foreach (SegmentEndGeometry endGeo in nodeGeo.SegmentEndGeometries) { if (endGeo == null) { continue; } if (segmentEndIds.Contains(endGeo)) { #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Node {NodeId} already knows segment {endGeo.SegmentId}"); #endif continue; } #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Adding segment {endGeo.SegmentId} to node {NodeId}"); #endif ISegmentEnd end = segEndMan.GetOrAddSegmentEnd(endGeo.SegmentId, endGeo.StartNode); if (end != null) { segmentEndIds.Add(end); } else { #if DEBUGTTL if (debug) Log.Warning($"TimedTrafficLights.UpdateSegmentEnds: Failed to add segment end {endGeo.SegmentId} @ {endGeo.StartNode} to node {NodeId}: GetOrAddSegmentEnd returned null."); #endif } } #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: finished for node {NodeId}: {segmentEndIds.CollectionToString()}"); #endif } private void DestroySegmentEnds() { #if DEBUGTTL bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; if (debug) Log._Debug($"TimedTrafficLights.DestroySegmentEnds: Destroying segment ends @ node {NodeId}"); #endif foreach (ISegmentEndId endId in segmentEndIds) { #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.DestroySegmentEnds: Destroying segment end {endId} @ node {NodeId}"); #endif // TODO only remove if no priority sign is located at the segment end (although this is currently not possible) Constants.ManagerFactory.SegmentEndManager.RemoveSegmentEnd(endId); } segmentEndIds.Clear(); #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLights.DestroySegmentEnds: finished for node {NodeId}"); #endif } } } ================================================ FILE: TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs ================================================ #define DEBUGSTEPx #define DEBUGTTLx #define DEBUGMETRICx using System; using System.Collections.Generic; using ColossalFramework; using TrafficManager.TrafficLight; using TrafficManager.Geometry; using TrafficManager.Custom.AI; using TrafficManager.Traffic; using TrafficManager.Manager; using TrafficManager.State; using TrafficManager.Util; using System.Linq; using CSUtil.Commons; using TrafficManager.Geometry.Impl; using CSUtil.Commons.Benchmark; using TrafficManager.Manager.Impl; namespace TrafficManager.TrafficLight.Impl { // TODO class should be completely reworked, approx. in version 1.10 public class TimedTrafficLightsStep : ITimedTrafficLightsStep { /// /// The number of time units this traffic light remains in the current state at least /// public int MinTime { get; set; } /// /// The number of time units this traffic light remains in the current state at most /// public int MaxTime { get; set; } /// /// Indicates if waiting vehicles should be measured /// public StepChangeMetric ChangeMetric { get; set; } public uint startFrame; /// /// Indicates if the step is done (internal use only) /// private bool stepDone; /// /// Frame when the GreenToRed phase started /// private uint? endTransitionStart; /// /// minimum mean "number of cars passing through" / "average segment length" /// public float CurrentFlow { get; private set; } /// /// maximum mean "number of cars waiting for green" / "average segment length" /// public float CurrentWait { get; private set; } public int PreviousStepRefIndex { get; set; } = -1; public int NextStepRefIndex { get; set; } = -1; public uint lastFlowWaitCalc = 0; private ITimedTrafficLights timedNode; public IDictionary CustomSegmentLights { get; private set; } = new TinyDictionary(); public LinkedList InvalidSegmentLights { get; private set; } = new LinkedList(); public float WaitFlowBalance { get; set; } = 1f; public override string ToString() { return $"[TimedTrafficLightsStep\n" + "\t" + $"minTime = {MinTime}\n" + "\t" + $"maxTime = {MaxTime}\n" + "\t" + $"stepChangeMode = {ChangeMetric}\n" + "\t" + $"startFrame = {startFrame}\n" + "\t" + $"stepDone = {stepDone}\n" + "\t" + $"endTransitionStart = {endTransitionStart}\n" + "\t" + $"minFlow = {CurrentFlow}\n" + "\t" + $"maxWait = {CurrentWait}\n" + "\t" + $"PreviousStepRefIndex = {PreviousStepRefIndex}\n" + "\t" + $"NextStepRefIndex = {NextStepRefIndex}\n" + "\t" + $"lastFlowWaitCalc = {lastFlowWaitCalc}\n" + "\t" + $"CustomSegmentLights = {CustomSegmentLights}\n" + "\t" + $"InvalidSegmentLights = {InvalidSegmentLights.CollectionToString()}\n" + "\t" + $"waitFlowBalance = {WaitFlowBalance}\n" + "TimedTrafficLightsStep]"; } public TimedTrafficLightsStep(ITimedTrafficLights timedNode, int minTime, int maxTime, StepChangeMetric stepChangeMode, float waitFlowBalance, bool makeRed=false) { this.MinTime = minTime; this.MaxTime = maxTime; this.ChangeMetric = stepChangeMode; this.WaitFlowBalance = waitFlowBalance; this.timedNode = timedNode; CurrentFlow = Single.NaN; CurrentWait = Single.NaN; endTransitionStart = null; stepDone = false; NodeGeometry nodeGeometry = NodeGeometry.Get(timedNode.NodeId); foreach (SegmentEndGeometry end in nodeGeometry.SegmentEndGeometries) { if (end == null) continue; if (! AddSegment(end.SegmentId, end.StartNode, makeRed)) { Log.Warning($"TimedTrafficLightsStep.ctor: Failed to add segment {end.SegmentId} @ start {end.StartNode} to node {timedNode.NodeId}"); } } } private TimedTrafficLightsStep() { } /// /// Checks if the green-to-red (=yellow) phase is finished /// /// public bool IsEndTransitionDone() { if (!timedNode.IsMasterNode()) { ITimedTrafficLights masterLights = timedNode.MasterLights(); return masterLights.GetStep(masterLights.CurrentStep).IsEndTransitionDone(); } bool ret = endTransitionStart != null && getCurrentFrame() > endTransitionStart && stepDone; //StepDone(false); #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.isEndTransitionDone() called for master NodeId={timedNode.NodeId}. CurrentStep={timedNode.CurrentStep} getCurrentFrame()={getCurrentFrame()} endTransitionStart={endTransitionStart} stepDone={stepDone} ret={ret}"); #endif return ret; } /// /// Checks if the green-to-red (=yellow) phase is currently active /// /// public bool IsInEndTransition() { if (!timedNode.IsMasterNode()) { ITimedTrafficLights masterLights = timedNode.MasterLights(); return masterLights.GetStep(masterLights.CurrentStep).IsInEndTransition(); } bool ret = endTransitionStart != null && getCurrentFrame() <= endTransitionStart && stepDone; //StepDone(false); #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.isInEndTransition() called for master NodeId={timedNode.NodeId}. CurrentStep={timedNode.CurrentStep} getCurrentFrame()={getCurrentFrame()} endTransitionStart={endTransitionStart} stepDone={stepDone} ret={ret}"); #endif return ret; } public bool IsInStartTransition() { if (!timedNode.IsMasterNode()) { ITimedTrafficLights masterLights = timedNode.MasterLights(); return masterLights.GetStep(masterLights.CurrentStep).IsInStartTransition(); } bool ret = getCurrentFrame() == startFrame && !stepDone; //!StepDone(false); #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.isInStartTransition() called for master NodeId={timedNode.NodeId}. CurrentStep={timedNode.CurrentStep} getCurrentFrame()={getCurrentFrame()} startFrame={startFrame} stepDone={stepDone} ret={ret}"); #endif return ret; } public RoadBaseAI.TrafficLightState GetLightState(ushort segmentId, ExtVehicleType vehicleType, int lightType) { ICustomSegmentLight segLight = CustomSegmentLights[segmentId].GetCustomLight(vehicleType); if (segLight != null) { switch (lightType) { case 0: return segLight.LightMain; case 1: return segLight.LightLeft; case 2: return segLight.LightRight; case 3: RoadBaseAI.TrafficLightState? pedState = CustomSegmentLights[segmentId].PedestrianLightState; return pedState == null ? RoadBaseAI.TrafficLightState.Red : (RoadBaseAI.TrafficLightState)pedState; } } return RoadBaseAI.TrafficLightState.Green; } /// /// Starts the step. /// public void Start(int previousStepRefIndex=-1) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.Start: Starting step {timedNode.CurrentStep} @ {timedNode.NodeId}"); #endif this.startFrame = getCurrentFrame(); Reset(); PreviousStepRefIndex = previousStepRefIndex; #if DEBUG /*if (GlobalConfig.Instance.Debug.Switches[2]) { if (timedNode.NodeId == 31605) { Log._Debug($"===== Step {timedNode.CurrentStep} @ node {timedNode.NodeId} ====="); Log._Debug($"minTime: {minTime} maxTime: {maxTime}"); foreach (KeyValuePair e in segmentLights) { Log._Debug($"\tSegment {e.Key}:"); Log._Debug($"\t{e.Value.ToString()}"); } } }*/ #endif } internal void Reset() { this.endTransitionStart = null; CurrentFlow = Single.NaN; CurrentWait = Single.NaN; lastFlowWaitCalc = 0; PreviousStepRefIndex = -1; NextStepRefIndex = -1; stepDone = false; } internal static uint getCurrentFrame() { return Constants.ServiceFactory.SimulationService.CurrentFrameIndex >> 6; } /// /// Updates "real-world" traffic light states according to the timed scripts /// public void UpdateLiveLights() { UpdateLiveLights(false); } public void UpdateLiveLights(bool noTransition) { try { ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; bool atEndTransition = !noTransition && (IsInEndTransition() || IsEndTransitionDone()); // = yellow bool atStartTransition = !noTransition && !atEndTransition && IsInStartTransition(); // = red + yellow #if DEBUGTTL if (timedNode == null) { Log.Error($"TimedTrafficLightsStep: timedNode is null!"); return; } #endif if (PreviousStepRefIndex >= timedNode.NumSteps()) PreviousStepRefIndex = -1; if (NextStepRefIndex >= timedNode.NumSteps()) NextStepRefIndex = -1; ITimedTrafficLightsStep previousStep = timedNode.GetStep(PreviousStepRefIndex >= 0 ? PreviousStepRefIndex : ((timedNode.CurrentStep + timedNode.NumSteps() - 1) % timedNode.NumSteps())); ITimedTrafficLightsStep nextStep = timedNode.GetStep(NextStepRefIndex >= 0 ? NextStepRefIndex : ((timedNode.CurrentStep + 1) % timedNode.NumSteps())); #if DEBUGTTL if (previousStep == null) { Log.Error($"TimedTrafficLightsStep: previousStep is null!"); //return; } if (nextStep == null) { Log.Error($"TimedTrafficLightsStep: nextStep is null!"); //return; } if (previousStep.CustomSegmentLights == null) { Log.Error($"TimedTrafficLightsStep: previousStep.segmentLights is null!"); //return; } if (nextStep.CustomSegmentLights == null) { Log.Error($"TimedTrafficLightsStep: nextStep.segmentLights is null!"); //return; } if (CustomSegmentLights == null) { Log.Error($"TimedTrafficLightsStep: segmentLights is null!"); //return; } #endif #if DEBUG //Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) called for NodeId={timedNode.NodeId}. atStartTransition={atStartTransition} atEndTransition={atEndTransition}"); #endif foreach (KeyValuePair e in CustomSegmentLights) { var segmentId = e.Key; var curStepSegmentLights = e.Value; #if DEBUG //Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) -> segmentId={segmentId} @ NodeId={timedNode.NodeId}"); #endif ICustomSegmentLights prevStepSegmentLights = null; if (!previousStep.CustomSegmentLights.TryGetValue(segmentId, out prevStepSegmentLights)) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log.Warning($"TimedTrafficLightsStep: previousStep does not contain lights for segment {segmentId}!"); #endif continue; } ICustomSegmentLights nextStepSegmentLights = null; if (!nextStep.CustomSegmentLights.TryGetValue(segmentId, out nextStepSegmentLights)) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log.Warning($"TimedTrafficLightsStep: nextStep does not contain lights for segment {segmentId}!"); #endif continue; } //segLightState.makeRedOrGreen(); // TODO temporary fix ICustomSegmentLights liveSegmentLights = customTrafficLightsManager.GetSegmentLights(segmentId, curStepSegmentLights.StartNode, false); if (liveSegmentLights == null) { Log.Warning($"TimedTrafficLightsStep.UpdateLights() @ node {timedNode.NodeId}: Could not retrieve live segment lights for segment {segmentId} @ start {curStepSegmentLights.StartNode}."); continue; } RoadBaseAI.TrafficLightState pedLightState = calcLightState((RoadBaseAI.TrafficLightState)prevStepSegmentLights.PedestrianLightState, (RoadBaseAI.TrafficLightState)curStepSegmentLights.PedestrianLightState, (RoadBaseAI.TrafficLightState)nextStepSegmentLights.PedestrianLightState, atStartTransition, atEndTransition); //Log._Debug($"TimedStep.SetLights: Setting pedestrian light state @ seg. {segmentId} to {pedLightState} {curStepSegmentLights.ManualPedestrianMode}"); liveSegmentLights.ManualPedestrianMode = curStepSegmentLights.ManualPedestrianMode; liveSegmentLights.PedestrianLightState = liveSegmentLights.AutoPedestrianLightState = pedLightState; //Log.Warning($"Step @ {timedNode.NodeId}: Segment {segmentId}: Ped.: {liveSegmentLights.PedestrianLightState.ToString()} / {liveSegmentLights.AutoPedestrianLightState.ToString()}"); #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) if (curStepSegmentLights.VehicleTypes == null) { Log.Error($"TimedTrafficLightsStep: curStepSegmentLights.VehicleTypes is null!"); return; } #endif foreach (ExtVehicleType vehicleType in curStepSegmentLights.VehicleTypes) { #if DEBUG //Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) -> segmentId={segmentId} @ NodeId={timedNode.NodeId} for vehicle {vehicleType}"); #endif ICustomSegmentLight liveSegmentLight = liveSegmentLights.GetCustomLight(vehicleType); if (liveSegmentLight == null) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"Timed step @ seg. {segmentId}, node {timedNode.NodeId} has a traffic light for {vehicleType} but the live segment does not have one."); #endif continue; } ICustomSegmentLight curStepSegmentLight = curStepSegmentLights.GetCustomLight(vehicleType); ICustomSegmentLight prevStepSegmentLight = prevStepSegmentLights.GetCustomLight(vehicleType); ICustomSegmentLight nextStepSegmentLight = nextStepSegmentLights.GetCustomLight(vehicleType); #if DEBUGTTL if (curStepSegmentLight == null) { Log.Error($"TimedTrafficLightsStep: curStepSegmentLight is null!"); //return; } if (prevStepSegmentLight == null) { Log.Error($"TimedTrafficLightsStep: prevStepSegmentLight is null!"); //return; } if (nextStepSegmentLight == null) { Log.Error($"TimedTrafficLightsStep: nextStepSegmentLight is null!"); //return; } #endif liveSegmentLight.InternalCurrentMode = curStepSegmentLight.CurrentMode; // TODO improve & remove /*curStepSegmentLight.EnsureModeLights(); prevStepSegmentLight.EnsureModeLights(); nextStepSegmentLight.EnsureModeLights();*/ RoadBaseAI.TrafficLightState mainLight = calcLightState(prevStepSegmentLight.LightMain, curStepSegmentLight.LightMain, nextStepSegmentLight.LightMain, atStartTransition, atEndTransition); RoadBaseAI.TrafficLightState leftLight = calcLightState(prevStepSegmentLight.LightLeft, curStepSegmentLight.LightLeft, nextStepSegmentLight.LightLeft, atStartTransition, atEndTransition); RoadBaseAI.TrafficLightState rightLight = calcLightState(prevStepSegmentLight.LightRight, curStepSegmentLight.LightRight, nextStepSegmentLight.LightRight, atStartTransition, atEndTransition); liveSegmentLight.SetStates(mainLight, leftLight, rightLight, false); #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) -> *SETTING* LightLeft={liveSegmentLight.LightLeft} LightMain={liveSegmentLight.LightMain} LightRight={liveSegmentLight.LightRight} for segmentId={segmentId} @ NodeId={timedNode.NodeId} for vehicle {vehicleType}"); #endif //Log._Debug($"Step @ {timedNode.NodeId}: Segment {segmentId} for vehicle type {vehicleType}: L: {liveSegmentLight.LightLeft.ToString()} F: {liveSegmentLight.LightMain.ToString()} R: {liveSegmentLight.LightRight.ToString()}"); } /*if (timedNode.NodeId == 20164) { Log._Debug($"Step @ {timedNode.NodeId}: Segment {segmentId}: {segmentLight.LightLeft.ToString()} {segmentLight.LightMain.ToString()} {segmentLight.LightRight.ToString()} {segmentLight.LightPedestrian.ToString()}"); }*/ liveSegmentLights.UpdateVisuals(); } } catch (Exception e) { Log.Error($"Exception in TimedTrafficStep.UpdateLiveLights for node {timedNode.NodeId}: {e.ToString()}"); //invalid = true; } } private RoadBaseAI.TrafficLightState calcLightState(RoadBaseAI.TrafficLightState previousState, RoadBaseAI.TrafficLightState currentState, RoadBaseAI.TrafficLightState nextState, bool atStartTransition, bool atEndTransition) { if (atStartTransition && currentState == RoadBaseAI.TrafficLightState.Green && previousState == RoadBaseAI.TrafficLightState.Red) return RoadBaseAI.TrafficLightState.RedToGreen; else if (atEndTransition && currentState == RoadBaseAI.TrafficLightState.Green && nextState == RoadBaseAI.TrafficLightState.Red) return RoadBaseAI.TrafficLightState.GreenToRed; else return currentState; } /// /// Updates timed segment lights according to "real-world" traffic light states /// public void UpdateLights() { Log._Debug($"TimedTrafficLightsStep.UpdateLights: Updating lights of timed traffic light step @ {timedNode.NodeId}"); foreach (KeyValuePair e in CustomSegmentLights) { var segmentId = e.Key; ICustomSegmentLights segLights = e.Value; Log._Debug($"TimedTrafficLightsStep.UpdateLights: Updating lights of timed traffic light step at seg. {e.Key} @ {timedNode.NodeId}"); //if (segment == 0) continue; ICustomSegmentLights liveSegLights = Constants.ManagerFactory.CustomSegmentLightsManager.GetSegmentLights(segmentId, segLights.StartNode, false); if (liveSegLights == null) { Log.Warning($"TimedTrafficLightsStep.UpdateLights() @ node {timedNode.NodeId}: Could not retrieve live segment lights for segment {segmentId} @ start {segLights.StartNode}."); continue; } segLights.SetLights(liveSegLights); Log._Debug($"TimedTrafficLightsStep.UpdateLights: Segment {segmentId} AutoPedState={segLights.AutoPedestrianLightState} live={liveSegLights.AutoPedestrianLightState}"); } } /// /// Countdown value for min. time /// /// public long MinTimeRemaining() { return Math.Max(0, startFrame + MinTime - getCurrentFrame()); } /// /// Countdown value for max. time /// /// public long MaxTimeRemaining() { return Math.Max(0, startFrame + MaxTime - getCurrentFrame()); } public void SetStepDone() { stepDone = true; } public bool StepDone(bool updateValues) { #if DEBUGTTL bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId; if (debug) { Log._Debug($"StepDone: called for node {timedNode.NodeId} @ step {timedNode.CurrentStep}"); } #endif if (!timedNode.IsMasterNode()) { ITimedTrafficLights masterLights = timedNode.MasterLights(); return masterLights.GetStep(masterLights.CurrentStep).StepDone(updateValues); } // we are the master node if (timedNode.IsInTestMode()) { return false; } if (stepDone) { return true; } if (getCurrentFrame() >= startFrame + MaxTime) { // maximum time reached. switch! #if DEBUGTTL if (debug) Log._Debug($"StepDone: step finished @ {timedNode.NodeId}"); #endif if (!stepDone && updateValues) { stepDone = true; endTransitionStart = getCurrentFrame(); } return stepDone; } if (getCurrentFrame() >= startFrame + MinTime) { float wait, flow; uint curFrame = getCurrentFrame(); //Log._Debug($"TTL @ {timedNode.NodeId}: curFrame={curFrame} lastFlowWaitCalc={lastFlowWaitCalc}"); if (lastFlowWaitCalc < curFrame) { //Log._Debug($"TTL @ {timedNode.NodeId}: lastFlowWaitCalc=curFrame wait={maxWait} flow={minFlow}"); } float newFlow = CurrentFlow; float newWait = CurrentWait; #if DEBUGMETRIC newFlow = flow; newWait = wait; #else if (ChangeMetric != StepChangeMetric.Default || Single.IsNaN(newFlow)) { newFlow = flow; } else { newFlow = GlobalConfig.Instance.TimedTrafficLights.SmoothingFactor * newFlow + (1f - GlobalConfig.Instance.TimedTrafficLights.SmoothingFactor) * flow; // some smoothing } if (Single.IsNaN(newWait)) { newWait = 0; } else if (ChangeMetric != StepChangeMetric.Default) { newWait = wait; } else { newWait = GlobalConfig.Instance.TimedTrafficLights.SmoothingFactor * newWait + (1f - GlobalConfig.Instance.TimedTrafficLights.SmoothingFactor) * wait; // some smoothing } #endif // if more cars are waiting than flowing, we change the step float metric; bool done = ShouldGoToNextStep(newFlow, newWait, out metric);// newWait > 0 && newFlow < newWait; //Log._Debug($"TTL @ {timedNode.NodeId}: newWait={newWait} newFlow={newFlow} updateValues={updateValues} stepDone={stepDone} done={done}"); if (updateValues) { CurrentFlow = newFlow; CurrentWait = newWait; //Log._Debug($"TTL @ {timedNode.NodeId}: updated minFlow=newFlow={minFlow} maxWait=newWait={maxWait}"); } #if DEBUG //Log.Message("step finished (2) @ " + nodeId); #endif if (updateValues && !stepDone && done) { stepDone = done; endTransitionStart = getCurrentFrame(); } return done; } return false; } public float GetMetric(float flow, float wait) { switch (ChangeMetric) { case StepChangeMetric.Default: default: return flow - wait; case StepChangeMetric.FirstFlow: return flow <= 0 ? 1f : 0f; case StepChangeMetric.FirstWait: return wait <= 0 ? 1f : 0f; case StepChangeMetric.NoFlow: return flow > 0 ? 1f : 0f; case StepChangeMetric.NoWait: return wait > 0 ? 1f : 0f; } } public bool ShouldGoToNextStep(float flow, float wait, out float metric) { metric = GetMetric(flow, wait); return ChangeMetric == StepChangeMetric.Default ? metric < 0 : metric == 0f; } /// /// Calculates the current metrics for flowing and waiting vehicles /// /// /// /// true if the values could be calculated, false otherwise public void CalcWaitFlow(bool countOnlyMovingIfGreen, int stepRefIndex, out float wait, out float flow) { uint numFlows = 0; uint numWaits = 0; float curTotalFlow = 0; float curTotalWait = 0; #if DEBUGTTL bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId; if (debug) { Log.Warning($"calcWaitFlow: called for node {timedNode.NodeId} @ step {stepRefIndex}"); } #else bool debug = false; #endif // TODO checking agains getCurrentFrame() is only valid if this is the current step if (countOnlyMovingIfGreen && getCurrentFrame() <= startFrame + MinTime + 1) { // during start phase all vehicles on "green" segments are counted as flowing countOnlyMovingIfGreen = false; } TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; ISegmentEndManager endMan = Constants.ManagerFactory.SegmentEndManager; IVehicleRestrictionsManager restrMan = Constants.ManagerFactory.VehicleRestrictionsManager; // loop over all timed traffic lights within the node group foreach (ushort timedNodeId in timedNode.NodeGroup) { if (!tlsMan.TrafficLightSimulations[timedNodeId].IsTimedLight()) { continue; } ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[timedNodeId].TimedLight; ITimedTrafficLightsStep slaveStep = slaveTTL.GetStep(stepRefIndex); // minimum time reached. check traffic! loop over source segments uint numNodeFlows = 0; uint numNodeWaits = 0; float curTotalNodeFlow = 0; float curTotalNodeWait = 0; foreach (KeyValuePair e in slaveStep.CustomSegmentLights) { var sourceSegmentId = e.Key; var segLights = e.Value; IDictionary directions = null; if (!slaveTTL.Directions.TryGetValue(sourceSegmentId, out directions)) { #if DEBUGTTL if (debug) { Log._Debug($"calcWaitFlow: No arrow directions defined for segment {sourceSegmentId} @ {timedNodeId}"); } #endif continue; } // one of the traffic lights at this segment is green: count minimum traffic flowing through ISegmentEnd sourceSegmentEnd = endMan.GetSegmentEnd(sourceSegmentId, segLights.StartNode); if (sourceSegmentEnd == null) { Log.Error($"TimedTrafficLightsStep.calcWaitFlow: No segment end @ seg. {sourceSegmentId} found!"); continue; // skip invalid segment } IDictionary[] movingVehiclesMetrics = null; bool countOnlyMovingIfGreenOnSegment = false; if (ChangeMetric == StepChangeMetric.Default) { countOnlyMovingIfGreenOnSegment = countOnlyMovingIfGreen; if (countOnlyMovingIfGreenOnSegment) { Constants.ServiceFactory.NetService.ProcessSegment(sourceSegmentId, delegate (ushort srcSegId, ref NetSegment segment) { if (restrMan.IsRailSegment(segment.Info)) { countOnlyMovingIfGreenOnSegment = false; } return true; }); } movingVehiclesMetrics = countOnlyMovingIfGreenOnSegment ? sourceSegmentEnd.MeasureOutgoingVehicles(false, debug) : null; } IDictionary[] allVehiclesMetrics = sourceSegmentEnd.MeasureOutgoingVehicles(true, debug); ExtVehicleType?[] vehTypeByLaneIndex = segLights.VehicleTypeByLaneIndex; #if DEBUGTTL if (debug) { Log._Debug($"calcWaitFlow: Seg. {sourceSegmentId} @ {timedNodeId}, vehTypeByLaneIndex={string.Join(", ", vehTypeByLaneIndex.Select(x => x == null ? "null" : x.ToString()).ToArray())}"); } #endif uint numSegFlows = 0; uint numSegWaits = 0; float curTotalSegFlow = 0; float curTotalSegWait = 0; // loop over source lanes for (byte laneIndex = 0; laneIndex < vehTypeByLaneIndex.Length; ++laneIndex) { ExtVehicleType? vehicleType = vehTypeByLaneIndex[laneIndex]; if (vehicleType == null) { continue; } ICustomSegmentLight segLight = segLights.GetCustomLight(laneIndex); if (segLight == null) { #if DEBUGTTL Log.Warning($"Timed traffic light step: Failed to get custom light for vehicleType {vehicleType} @ seg. {sourceSegmentId}, node {timedNode.NodeId}!"); #endif continue; } IDictionary movingVehiclesMetric = countOnlyMovingIfGreenOnSegment ? movingVehiclesMetrics[laneIndex] : null; IDictionary allVehiclesMetric = allVehiclesMetrics[laneIndex]; if (allVehiclesMetrics == null) { #if DEBUGTTL if (debug) { Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: No cars on lane {laneIndex} @ seg. {sourceSegmentId}. Vehicle types: {vehicleType}"); } #endif continue; } #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Checking lane {laneIndex} @ seg. {sourceSegmentId}. Vehicle types: {vehicleType}"); #endif // loop over target segment: calculate waiting/moving traffic uint numLaneFlows = 0; uint numLaneWaits = 0; uint curTotalLaneFlow = 0; uint curTotalLaneWait = 0; foreach (KeyValuePair f in allVehiclesMetric) { ushort targetSegmentId = f.Key; uint numVehicles = f.Value; ArrowDirection dir; if (!directions.TryGetValue(targetSegmentId, out dir)) { Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Direction undefined for target segment {targetSegmentId} @ {timedNodeId}"); continue; } uint numMovingVehicles = countOnlyMovingIfGreenOnSegment ? movingVehiclesMetric[f.Key] : numVehicles; #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Total num of cars on seg. {sourceSegmentId}, lane {laneIndex} going to seg. {targetSegmentId}: {numMovingVehicles} (all: {numVehicles})"); #endif bool addToFlow = false; switch (dir) { case ArrowDirection.Turn: addToFlow = Constants.ServiceFactory.SimulationService.LeftHandDrive ? segLight.IsRightGreen() : segLight.IsLeftGreen(); break; case ArrowDirection.Left: addToFlow = segLight.IsLeftGreen(); break; case ArrowDirection.Right: addToFlow = segLight.IsRightGreen(); break; case ArrowDirection.Forward: default: addToFlow = segLight.IsMainGreen(); break; } if (addToFlow) { curTotalLaneFlow += numMovingVehicles; ++numLaneFlows; #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: ## Vehicles @ lane {laneIndex}, seg. {sourceSegmentId} going to seg. {targetSegmentId}: COUTING as FLOWING -- numMovingVehicles={numMovingVehicles}"); #endif } else { curTotalLaneWait += numVehicles; ++numLaneWaits; #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: ## Vehicles @ lane {laneIndex}, seg. {sourceSegmentId} going to seg. {targetSegmentId}: COUTING as WAITING -- numVehicles={numVehicles}"); #endif } #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: >>>>> Vehicles @ lane {laneIndex}, seg. {sourceSegmentId} going to seg. {targetSegmentId}: curTotalLaneFlow={curTotalLaneFlow}, curTotalLaneWait={curTotalLaneWait}, numLaneFlows={numLaneFlows}, numLaneWaits={numLaneWaits}"); #endif } // foreach target segment float meanLaneFlow = 0; if (numLaneFlows > 0) { switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { case FlowWaitCalcMode.Mean: default: ++numSegFlows; meanLaneFlow = (float)curTotalLaneFlow / (float)numLaneFlows; curTotalSegFlow += meanLaneFlow; break; case FlowWaitCalcMode.Total: numSegFlows += numLaneFlows; curTotalSegFlow += curTotalLaneFlow; break; } } float meanLaneWait = 0; if (numLaneWaits > 0) { switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { case FlowWaitCalcMode.Mean: default: ++numSegWaits; meanLaneWait = (float)curTotalLaneWait / (float)numLaneWaits; curTotalSegWait += meanLaneWait; break; case FlowWaitCalcMode.Total: numSegWaits += numLaneWaits; curTotalSegWait += curTotalLaneWait; break; } } #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: >>>> Vehicles @ lane {laneIndex}, seg. {sourceSegmentId}: meanLaneFlow={meanLaneFlow}, meanLaneWait={meanLaneWait} // curTotalSegFlow={curTotalSegFlow}, curTotalSegWait={curTotalSegWait}, numSegFlows={numSegFlows}, numSegWaits={numSegWaits}"); #endif } // foreach source lane float meanSegFlow = 0; if (numSegFlows > 0) { switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { case FlowWaitCalcMode.Mean: default: ++numNodeFlows; meanSegFlow = (float)curTotalSegFlow / (float)numSegFlows; curTotalNodeFlow += meanSegFlow; break; case FlowWaitCalcMode.Total: numNodeFlows += numSegFlows; curTotalNodeFlow += curTotalSegFlow; break; } } float meanSegWait = 0; if (numSegWaits > 0) { switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { case FlowWaitCalcMode.Mean: default: ++numNodeWaits; meanSegWait = (float)curTotalSegWait / (float)numSegWaits; curTotalNodeWait += meanSegWait; break; case FlowWaitCalcMode.Total: numNodeWaits += numSegWaits; curTotalNodeWait += curTotalSegWait; break; } } #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: >>> Vehicles @ seg. {sourceSegmentId}: meanSegFlow={meanSegFlow}, meanSegWait={meanSegWait} // curTotalNodeFlow={curTotalNodeFlow}, curTotalNodeWait={curTotalNodeWait}, numNodeFlows={numNodeFlows}, numNodeWaits={numNodeWaits}"); #endif } // foreach source segment float meanNodeFlow = 0; if (numNodeFlows > 0) { switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { case FlowWaitCalcMode.Mean: default: ++numFlows; meanNodeFlow = (float)curTotalNodeFlow / (float)numNodeFlows; curTotalFlow += meanNodeFlow; break; case FlowWaitCalcMode.Total: numFlows += numNodeFlows; curTotalFlow += curTotalNodeFlow; break; } } float meanNodeWait = 0; if (numNodeWaits > 0) { switch (GlobalConfig.Instance.TimedTrafficLights.FlowWaitCalcMode) { case FlowWaitCalcMode.Mean: default: ++numWaits; meanNodeWait = (float)curTotalNodeWait / (float)numNodeWaits; curTotalWait += meanNodeWait; break; case FlowWaitCalcMode.Total: numWaits += numNodeWaits; curTotalWait += curTotalNodeWait; break; } } #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Calculated flow for source node {timedNodeId}: meanNodeFlow={meanNodeFlow} meanNodeWait={meanNodeWait} // curTotalFlow={curTotalFlow}, curTotalWait={curTotalWait}, numFlows={numFlows}, numWaits={numWaits}"); #endif } // foreach timed node float meanFlow = numFlows > 0 ? (float)curTotalFlow / (float)numFlows : 0; float meanWait = numWaits > 0 ? (float)curTotalWait / (float)numWaits : 0; meanFlow /= WaitFlowBalance; // a value smaller than 1 rewards steady traffic currents wait = (float)meanWait; flow = meanFlow; #if DEBUGTTL if (debug) Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: ***CALCULATION FINISHED*** for master node {timedNode.NodeId}: flow={flow} wait={wait}"); #endif } internal void ChangeLightMode(ushort segmentId, ExtVehicleType vehicleType, LightMode mode) { ICustomSegmentLight light = CustomSegmentLights[segmentId].GetCustomLight(vehicleType); if (light != null) { light.CurrentMode = mode; } } public ICustomSegmentLights RemoveSegmentLights(ushort segmentId) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.RemoveSegmentLights({segmentId}) called."); #endif ICustomSegmentLights ret = null; if (CustomSegmentLights.TryGetValue(segmentId, out ret)) { CustomSegmentLights.Remove(segmentId); } return ret; } public ICustomSegmentLights GetSegmentLights(ushort segmentId) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.GetSegmentLights({segmentId}) called."); #endif return GetSegmentLights(timedNode.NodeId, segmentId); } public ICustomSegmentLights GetSegmentLights(ushort nodeId, ushort segmentId) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.GetSegmentLights({nodeId}, {segmentId}) called."); #endif if (nodeId != timedNode.NodeId) { Log.Warning($"TimedTrafficLightsStep.GetSegmentLights({nodeId}, {segmentId}): TTL @ node {timedNode.NodeId} does not handle custom traffic lights for node {nodeId}"); return null; } ICustomSegmentLights customLights; if (CustomSegmentLights.TryGetValue(segmentId, out customLights)) { return customLights; } else { Log.Info($"TimedTrafficLightsStep.GetSegmentLights({nodeId}, {segmentId}): TTL @ node {timedNode.NodeId} does not know segment {segmentId}"); return null; } } public bool RelocateSegmentLights(ushort sourceSegmentId, ushort targetSegmentId) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}) called."); #endif ICustomSegmentLights sourceLights = null; if (! CustomSegmentLights.TryGetValue(sourceSegmentId, out sourceLights)) { Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Timed traffic light does not know source segment {sourceSegmentId}. Cannot relocate to {targetSegmentId}."); return false; } SegmentGeometry segGeo = SegmentGeometry.Get(targetSegmentId); if (segGeo == null) { Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): No geometry information available for target segment {targetSegmentId}"); return false; } if (segGeo.StartNodeId() != timedNode.NodeId && segGeo.EndNodeId() != timedNode.NodeId) { Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Target segment {targetSegmentId} is not connected to node {timedNode.NodeId}"); return false; } bool startNode = segGeo.StartNodeId() == timedNode.NodeId; CustomSegmentLights.Remove(sourceSegmentId); Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(targetSegmentId, startNode).Housekeeping(true, true); sourceLights.Relocate(targetSegmentId, startNode, this); CustomSegmentLights[targetSegmentId] = sourceLights; Log._Debug($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Relocated lights: {sourceSegmentId} -> {targetSegmentId} @ node {timedNode.NodeId}"); return true; } /// /// Adds a new segment to this step. It is cloned from the live custom traffic light. /// /// internal bool AddSegment(ushort segmentId, bool startNode, bool makeRed) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}) called @ node {timedNode.NodeId}."); #endif SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { Log.Error($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}): No geometry information available for segment {segmentId}"); return false; } SegmentEndGeometry endGeo = segGeo.GetEnd(startNode); if (endGeo == null) { Log.Error($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}): No end geometry information available for segment {segmentId} @ {startNode}"); return false; } if (endGeo.NodeId() != timedNode.NodeId) { Log.Error($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}): Segment {segmentId} is not connected to node {timedNode.NodeId} @ start {startNode}"); return false; } ICustomSegmentLightsManager customSegLightsMan = Constants.ManagerFactory.CustomSegmentLightsManager; ICustomSegmentLights liveLights = customSegLightsMan.GetOrLiveSegmentLights(segmentId, startNode); liveLights.Housekeeping(true, true); ICustomSegmentLights clonedLights = liveLights.Clone(this); CustomSegmentLights.Add(segmentId, clonedLights); if (makeRed) CustomSegmentLights[segmentId].MakeRed(); else CustomSegmentLights[segmentId].MakeRedOrGreen(); return customSegLightsMan.ApplyLightModes(segmentId, startNode, clonedLights); } public bool SetSegmentLights(ushort nodeId, ushort segmentId, ICustomSegmentLights lights) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.SetSegmentLights({nodeId}, {segmentId}, {lights}) called."); #endif if (nodeId != timedNode.NodeId) { Log.Warning($"TimedTrafficLightsStep.SetSegmentLights({nodeId}, {segmentId}, {lights}): TTL @ node {timedNode.NodeId} does not handle custom traffic lights for node {nodeId}"); return false; } return SetSegmentLights(segmentId, lights); } public bool SetSegmentLights(ushort segmentId, ICustomSegmentLights lights) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.SetSegmentLights({segmentId}, {lights}) called."); #endif SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { Log.Error($"TimedTrafficLightsStep.SetSegmentLights: No geometry information available for target segment {segmentId}"); return false; } if (segGeo.StartNodeId() != timedNode.NodeId && segGeo.EndNodeId() != timedNode.NodeId) { Log.Error($"TimedTrafficLightsStep.SetSegmentLights: Segment {segmentId} is not connected to node {timedNode.NodeId}"); return false; } bool startNode = segGeo.StartNodeId() == timedNode.NodeId; Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(segmentId, startNode).Housekeeping(true, true); lights.Relocate(segmentId, startNode, this); CustomSegmentLights[segmentId] = lights; Log._Debug($"TimedTrafficLightsStep.SetSegmentLights: Set lights @ seg. {segmentId}, node {timedNode.NodeId}"); return true; } public short ClockwiseIndexOfSegmentEnd(ISegmentEndId endId) { #if DEBUGTTL if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) Log._Debug($"TimedTrafficLightsStep.ClockwiseIndexOfSegmentEnd({endId}) called."); #endif SegmentEndGeometry endGeo = SegmentEndGeometry.Get(endId); if (endGeo == null) { Log.Warning($"TimedTrafficLightsStep.ClockwiseIndexOfSegmentEnd: @ node {timedNode.NodeId}: No segment end geometry found for end id {endId}"); return -1; } if (endGeo.NodeId() != timedNode.NodeId) { Log.Warning($"TimedTrafficLightsStep.ClockwiseIndexOfSegmentEnd: @ node {timedNode.NodeId} does not handle custom traffic lights for node {endGeo.NodeId()}"); return -1; } if (CustomSegmentLights.ContainsKey(endId.SegmentId)) { Log.Warning($"TimedTrafficLightsStep.ClockwiseIndexOfSegmentEnd: @ node {timedNode.NodeId} does not handle custom traffic lights for segment {endId.SegmentId}"); return -1; } short index = Constants.ManagerFactory.CustomSegmentLightsManager.ClockwiseIndexOfSegmentEnd(endId); index += timedNode.RotationOffset; return (short)(index % (endGeo.NumConnectedSegments + 1)); } // TODO IMPROVE THIS! Liskov substitution principle must hold. public ICustomSegmentLights GetSegmentLights(ushort segmentId, bool startNode, bool add = true, RoadBaseAI.TrafficLightState lightState = RoadBaseAI.TrafficLightState.Red) { throw new NotImplementedException(); } // TODO IMPROVE THIS! Liskov substitution principle must hold. public ICustomSegmentLights GetOrLiveSegmentLights(ushort segmentId, bool startNode) { throw new NotImplementedException(); } // TODO IMPROVE THIS! Liskov substitution principle must hold. public bool ApplyLightModes(ushort segmentId, bool startNode, ICustomSegmentLights otherLights) { throw new NotImplementedException(); } // TODO IMPROVE THIS! Liskov substitution principle must hold. public void SetLightMode(ushort segmentId, bool startNode, ExtVehicleType vehicleType, LightMode mode) { throw new NotImplementedException(); } // TODO IMPROVE THIS! Liskov substitution principle must hold. public void AddNodeLights(ushort nodeId) { throw new NotImplementedException(); } // TODO IMPROVE THIS! Liskov substitution principle must hold. public void RemoveNodeLights(ushort nodeId) { throw new NotImplementedException(); } // TODO IMPROVE THIS! Liskov substitution principle must hold. void ICustomSegmentLightsManager.RemoveSegmentLights(ushort segmentId) { throw new NotImplementedException(); } // TODO IMPROVE THIS! Liskov substitution principle must hold. public void RemoveSegmentLight(ushort segmentId, bool startNode) { throw new NotImplementedException(); } // TODO IMPROVE THIS! Liskov substitution principle must hold. public bool IsSegmentLight(ushort segmentId, bool startNode) { throw new NotImplementedException(); } } } ================================================ FILE: TLM/TLM/TrafficLight/LightMode.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.TrafficLight { public enum LightMode { Simple = 1, // <^> SingleLeft = 2, // <, ^> SingleRight = 3, // <^, > All = 4 // <, ^, > } } ================================================ FILE: TLM/TLM/TrafficLight/README.md ================================================ # TM:PE -- /TrafficLight Custom traffic light data structures. ## Classes - **CustomSegment**: Holds references to both possible custom traffic lights at a segment - **CustomSegmentLight**: Stores a fully-directional traffic light state (red, yellow, green for left, forward, right) for one lane at a specific segment end. - **CustomSegmentLights**: Holds a set of fully-directional traffic light states for all incoming lanes at a segment end. - **TimedTrafficLights**: Holds the timed traffic light program for one specific junction or a set of junctions. Holds all timed steps that are defined by the player (Steps). Implements the timed traffic light program simulation (SimulationStep). (TODO: rework needed) - **TimedTrafficLightsStep**: Represents one player-defined timed step for one junction, including minimum/maximum time (minTime, maxTime) and cached number of flowing/waiting vehicles (minFlow, maxWait). - **TrafficLightSimulation**: Main class representing either a manual or timed traffic light at a junction. ================================================ FILE: TLM/TLM/TrafficLight/StepChangeMetric.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.TrafficLight { public enum StepChangeMetric { /// /// Step is changed based on flow/wait comparison /// Default, /// /// Step is changed on first flow detection /// FirstFlow, /// /// Step is changed on first wait detection /// FirstWait, /// /// Step is changed if no vehicle is moving /// NoFlow, /// /// Step is changed if no vehicle is waiting /// NoWait, } } ================================================ FILE: TLM/TLM/TrafficLight/TrafficLightSimulationType.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.TrafficLight { public enum TrafficLightSimulationType { None, Manual, Timed } } ================================================ FILE: TLM/TLM/TrafficManager.cs ================================================ using GenericGameBridge.Factory; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Manager; using UnityEngine; namespace TrafficManager { /// /// Helper class to make Traffic Manager services available in-game /// public class TrafficManager : MonoBehaviour { private const string GameObjectName = "TMPE"; public IServiceFactory ServiceFactory { get { return Constants.ServiceFactory; } } public IManagerFactory ManagerFactory { get { return Constants.ManagerFactory; } } /*public void Initialize() { GameObject gameObject = new GameObject(GameObjectName); } public static void Dispose() { var gameObject = GameObject.Find(GameObjectName); if (gameObject != null) { Destroy(gameObject); } }*/ } } ================================================ FILE: TLM/TLM/TrafficManagerMod.cs ================================================ using CSUtil.Commons; using ICities; using System.Reflection; using System.Runtime.CompilerServices; using ColossalFramework; using ColossalFramework.UI; using TrafficManager.State; using TrafficManager.Util; using UnityEngine; namespace TrafficManager { public class TrafficManagerMod : IUserMod { public static readonly string Version = "10.18"; public static readonly uint GameVersion = 180609552u; public static readonly uint GameVersionA = 1u; public static readonly uint GameVersionB = 11u; public static readonly uint GameVersionC = 1u; public static readonly uint GameVersionBuild = 2u; public string Name => "TM:PE " + Version; public string Description => "Manage your city's traffic"; public void OnEnabled() { Log.Info($"TM:PE enabled. Version {Version}, Build {Assembly.GetExecutingAssembly().GetName().Version} for game version {GameVersionA}.{GameVersionB}.{GameVersionC}-f{GameVersionBuild}"); if (UIView.GetAView() != null) { OnGameIntroLoaded(); } else { LoadingManager.instance.m_introLoaded += OnGameIntroLoaded; } } public void OnDisabled() { Log.Info("TM:PE disabled."); LoadingManager.instance.m_introLoaded -= OnGameIntroLoaded; } public void OnSettingsUI(UIHelperBase helper) { Options.makeSettings(helper); } private static void OnGameIntroLoaded() { ModsCompatibilityChecker mcc = new ModsCompatibilityChecker(); mcc.PerformModCheck(); } } } ================================================ FILE: TLM/TLM/TrafficManagerMode.cs ================================================ namespace TrafficManager { public enum TrafficManagerMode { None = 0, Activated = 1 } } ================================================ FILE: TLM/TLM/UI/CustomKeyHandler.cs ================================================ using ColossalFramework.UI; using CSUtil.Commons; using UnityEngine; namespace TrafficManager.UI { public class CustomKeyHandler : UICustomControl { //TODO add more key bindings or refactor to mod key shortcut manager public void OnKeyDown(UIComponent comp, UIKeyEventParameter p) { if (p.used || p.keycode != KeyCode.Escape) return; Log._Debug("CustomKeyHandler::OnKeyDown(KeyCode.Escape) call"); p.Use(); } } } ================================================ FILE: TLM/TLM/UI/IncompatibleModsPanel.cs ================================================ using System; using System.Collections.Generic; using ColossalFramework; using ColossalFramework.Globalization; using ColossalFramework.PlatformServices; using ColossalFramework.Plugins; using ColossalFramework.UI; using CSUtil.Commons; using UnityEngine; namespace TrafficManager.UI { public class IncompatibleModsPanel : UIPanel { private UILabel title; private UIButton closeButton; private UISprite warningIcon; private UIPanel mainPanel; private UIComponent blurEffect; private static IncompatibleModsPanel _instance; public Dictionary IncompatibleMods { get; set; } public void Initialize() { Log._Debug("IncompatibleModsPanel initialize"); if (mainPanel != null) { mainPanel.OnDestroy(); } isVisible = true; mainPanel = AddUIComponent(); mainPanel.backgroundSprite = "UnlockingPanel2"; mainPanel.color = new Color32(75, 75, 135, 255); width = 600; height = 440; mainPanel.width = 600; mainPanel.height = 440; Vector2 resolution = UIView.GetAView().GetScreenResolution(); relativePosition = new Vector3(resolution.x / 2 - 300, resolution.y / 3); mainPanel.relativePosition = Vector3.zero; warningIcon = mainPanel.AddUIComponent(); warningIcon.size = new Vector2(40f, 40f); warningIcon.spriteName = "IconWarning"; warningIcon.relativePosition = new Vector3(15, 15); warningIcon.zOrder = 0; title = mainPanel.AddUIComponent(); title.autoSize = true; title.padding = new RectOffset(10, 10, 15, 15); title.relativePosition = new Vector2(60, 12); title.text = "Traffic Manager detected incompatible mods"; closeButton = mainPanel.AddUIComponent(); closeButton.eventClick += CloseButtonClick; closeButton.relativePosition = new Vector3(width - closeButton.width - 45, 15f); closeButton.normalBgSprite = "buttonclose"; closeButton.hoveredBgSprite = "buttonclosehover"; closeButton.pressedBgSprite = "buttonclosepressed"; UIPanel panel = mainPanel.AddUIComponent(); panel.relativePosition = new Vector2(20, 70); panel.size = new Vector2(565, 320); UIScrollablePanel scrollablePanel = panel.AddUIComponent(); scrollablePanel.backgroundSprite = ""; scrollablePanel.size = new Vector2(550, 340); scrollablePanel.relativePosition = new Vector3(0, 0); scrollablePanel.clipChildren = true; if (IncompatibleMods.Count != 0) { int acc = 0; UIPanel item; IncompatibleMods.ForEach((pair) => { item = CreateEntry(ref scrollablePanel, pair.Value, pair.Key); item.relativePosition = new Vector2(0, acc); item.size = new Vector2(560, 50); acc += 50; }); item = null; } scrollablePanel.FitTo(panel); scrollablePanel.scrollWheelDirection = UIOrientation.Vertical; scrollablePanel.builtinKeyNavigation = true; UIScrollbar verticalScroll = panel.AddUIComponent(); verticalScroll.stepSize = 1; verticalScroll.relativePosition = new Vector2(panel.width - 15, 0); verticalScroll.orientation = UIOrientation.Vertical; verticalScroll.size = new Vector2(20, 320); verticalScroll.incrementAmount = 25; verticalScroll.scrollEasingType = EasingType.BackEaseOut; scrollablePanel.verticalScrollbar = verticalScroll; UISlicedSprite track = verticalScroll.AddUIComponent(); track.spriteName = "ScrollbarTrack"; track.relativePosition = Vector3.zero; track.size = new Vector2(16, 320); verticalScroll.trackObject = track; UISlicedSprite thumb = track.AddUIComponent(); thumb.spriteName = "ScrollbarThumb"; thumb.autoSize = true; thumb.relativePosition = Vector3.zero; verticalScroll.thumbObject = thumb; blurEffect = GameObject.Find("ModalEffect").GetComponent(); AttachUIComponent(blurEffect.gameObject); blurEffect.size = new Vector2(resolution.x, resolution.y); blurEffect.absolutePosition = new Vector3(0, 0); blurEffect.SendToBack(); if (blurEffect != null) { blurEffect.isVisible = true; ValueAnimator.Animate("ModalEffect", delegate(float val) { blurEffect.opacity = val; }, new AnimatedFloat(0f, 1f, 0.7f, EasingType.CubicEaseOut)); } BringToFront(); } private void CloseButtonClick(UIComponent component, UIMouseEventParameter eventparam) { closeButton.eventClick -= CloseButtonClick; TryPopModal(); Hide(); Unfocus(); eventparam.Use(); } private UIPanel CreateEntry(ref UIScrollablePanel parent, string name, ulong steamId) { UIPanel panel = parent.AddUIComponent(); panel.size = new Vector2(560, 50); panel.backgroundSprite = "ContentManagerItemBackground"; UILabel label = panel.AddUIComponent(); label.text = name; label.textAlignment = UIHorizontalAlignment.Left; label.relativePosition = new Vector2(10, 15); CreateButton(panel, "Unsubscribe", (int) panel.width - 170, 10, delegate(UIComponent component, UIMouseEventParameter param) { UnsubscribeClick(component, param, steamId); }); return panel; } private void UnsubscribeClick(UIComponent component, UIMouseEventParameter eventparam, ulong steamId) { Log.Info("Trying to unsubscribe workshop item " + steamId); component.isEnabled = false; if (PlatformService.workshop.Unsubscribe(new PublishedFileId(steamId))) { IncompatibleMods.Remove(steamId); component.parent.Disable(); component.isVisible = false; Log.Info("Workshop item " + steamId + " unsubscribed"); } else { Log.Warning("Failed unsubscribing workshop item " + steamId); component.isEnabled = true; } } private UIButton CreateButton(UIComponent parent, string text, int x, int y, MouseEventHandler eventClick) { var button = parent.AddUIComponent(); button.textScale = 0.8f; button.width = 150f; button.height = 30; button.normalBgSprite = "ButtonMenu"; button.disabledBgSprite = "ButtonMenuDisabled"; button.hoveredBgSprite = "ButtonMenuHovered"; button.focusedBgSprite = "ButtonMenu"; button.pressedBgSprite = "ButtonMenuPressed"; button.textColor = new Color32(255, 255, 255, 255); button.playAudioEvents = true; button.text = text; button.relativePosition = new Vector3(x, y); button.eventClick += eventClick; return button; } private void OnEnable() { Log._Debug("IncompatibleModsPanel enabled"); PlatformService.workshop.eventUGCQueryCompleted += OnQueryCompleted; Singleton.instance.eventPluginsChanged += OnPluginsChanged; Singleton.instance.eventPluginsStateChanged += OnPluginsChanged; LocaleManager.eventLocaleChanged += OnLocaleChanged; } private void OnQueryCompleted(UGCDetails result, bool ioerror) { Log._Debug("IncompatibleModsPanel.OnQueryCompleted() - " + result.result.ToString("D") + " IO error?:" + ioerror); } private void OnPluginsChanged() { Log._Debug("IncompatibleModsPanel.OnPluginsChanged() - Plugins changed"); } private void OnDisable() { Log._Debug("IncompatibleModsPanel disabled"); PlatformService.workshop.eventUGCQueryCompleted -= this.OnQueryCompleted; Singleton.instance.eventPluginsChanged -= this.OnPluginsChanged; Singleton.instance.eventPluginsStateChanged -= this.OnPluginsChanged; LocaleManager.eventLocaleChanged -= this.OnLocaleChanged; } protected override void OnKeyDown(UIKeyEventParameter p) { if (Input.GetKey(KeyCode.Escape) || Input.GetKey(KeyCode.Return)) { TryPopModal(); p.Use(); Hide(); Unfocus(); } base.OnKeyDown(p); } private void TryPopModal() { if (UIView.HasModalInput()) { UIView.PopModal(); UIComponent component = UIView.GetModalComponent(); if (component != null) { UIView.SetFocus(component); } } if (blurEffect != null && UIView.ModalInputCount() == 0) { ValueAnimator.Animate("ModalEffect", delegate(float val) { blurEffect.opacity = val; }, new AnimatedFloat(1f, 0f, 0.7f, EasingType.CubicEaseOut), delegate() { blurEffect.Hide(); }); } } } } ================================================ FILE: TLM/TLM/UI/LinearSpriteButton.cs ================================================ using ColossalFramework.Math; using ColossalFramework.UI; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.State; using TrafficManager.Util; using UnityEngine; namespace TrafficManager.UI.MainMenu { public abstract class LinearSpriteButton : UIButton { public enum ButtonMouseState { Base, Hovered, MouseDown } public const string MENU_BUTTON_BACKGROUND = "Bg"; public const string MENU_BUTTON_FOREGROUND = "Fg"; public const string MENU_BUTTON_BASE = "Base"; public const string MENU_BUTTON_HOVERED = "Hovered"; public const string MENU_BUTTON_MOUSEDOWN = "MouseDown"; public const string MENU_BUTTON_DEFAULT = "Default"; public const string MENU_BUTTON_ACTIVE = "Active"; protected static string GetButtonBackgroundTextureId(string prefix, ButtonMouseState state, bool active) { string ret = prefix + MENU_BUTTON_BACKGROUND; switch (state) { case ButtonMouseState.Base: ret += MENU_BUTTON_BASE; break; case ButtonMouseState.Hovered: ret += MENU_BUTTON_HOVERED; break; case ButtonMouseState.MouseDown: ret += MENU_BUTTON_MOUSEDOWN; break; } ret += active ? MENU_BUTTON_ACTIVE : MENU_BUTTON_DEFAULT; return ret; } protected static string GetButtonForegroundTextureId(string prefix, string function, bool active) { string ret = prefix + MENU_BUTTON_FOREGROUND + function; ret += active ? MENU_BUTTON_ACTIVE : MENU_BUTTON_DEFAULT; return ret; } public abstract bool CanActivate(); public abstract string ButtonName { get; } public abstract string FunctionName { get; } public abstract string[] FunctionNames { get; } public abstract Texture2D AtlasTexture { get; } public abstract int Width { get; } public abstract int Height { get; } public override void Start() { string[] textureIds = new string[Enum.GetValues(typeof(ButtonMouseState)).Length * (CanActivate() ? 2 : 1) + FunctionNames.Length * 2]; int i = 0; foreach (ButtonMouseState mouseState in EnumUtil.GetValues()) { if (CanActivate()) { textureIds[i++] = GetButtonBackgroundTextureId(ButtonName, mouseState, true); } textureIds[i++] = GetButtonBackgroundTextureId(ButtonName, mouseState, false); } foreach (string function in FunctionNames) { textureIds[i++] = GetButtonForegroundTextureId(ButtonName, function, false); } foreach (string function in FunctionNames) { textureIds[i++] = GetButtonForegroundTextureId(ButtonName, function, true); } // Set the atlases for background/foreground atlas = TextureUtil.GenerateLinearAtlas("TMPE_" + ButtonName + "Atlas", AtlasTexture, textureIds.Length, textureIds); m_ForegroundSpriteMode = UIForegroundSpriteMode.Scale; UpdateProperties(); // Enable button sounds. playAudioEvents = true; } public abstract bool Active { get; } public abstract string Tooltip { get; } public abstract bool Visible { get; } public abstract void HandleClick(UIMouseEventParameter p); protected override void OnClick(UIMouseEventParameter p) { HandleClick(p); UpdateProperties(); } internal void UpdateProperties() { bool active = CanActivate() ? Active : false; m_BackgroundSprites.m_Normal = m_BackgroundSprites.m_Disabled = m_BackgroundSprites.m_Focused = GetButtonBackgroundTextureId(ButtonName, ButtonMouseState.Base, active); m_BackgroundSprites.m_Hovered = GetButtonBackgroundTextureId(ButtonName, ButtonMouseState.Hovered, active); m_PressedBgSprite = GetButtonBackgroundTextureId(ButtonName, ButtonMouseState.MouseDown, active); m_ForegroundSprites.m_Normal = m_ForegroundSprites.m_Disabled = m_ForegroundSprites.m_Focused = GetButtonForegroundTextureId(ButtonName, FunctionName, active); m_ForegroundSprites.m_Hovered = m_PressedFgSprite = GetButtonForegroundTextureId(ButtonName, FunctionName, true); tooltip = Translation.GetString(Tooltip); isVisible = Visible; this.Invalidate(); } } } ================================================ FILE: TLM/TLM/UI/MainMenu/ClearTrafficButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; using TrafficManager.Manager.Impl; namespace TrafficManager.UI.MainMenu { public class ClearTrafficButton : MenuButton { public override bool Active { get { return false; } } public override ButtonFunction Function { get { return ButtonFunction.ClearTraffic; } } public override string Tooltip { get { return "Clear_Traffic"; } } public override bool Visible { get { return true; } } public override void OnClickInternal(UIMouseEventParameter p) { ConfirmPanel.ShowModal(Translation.GetString("Clear_Traffic"), Translation.GetString("Clear_Traffic") + "?", delegate (UIComponent comp, int ret) { if (ret == 1) { Constants.ServiceFactory.SimulationService.AddAction(() => { UtilityManager.Instance.ClearTraffic(); }); } UIBase.GetTrafficManagerTool(true).SetToolMode(ToolMode.None); }); } } } ================================================ FILE: TLM/TLM/UI/MainMenu/DebugMenu.cs ================================================ #define QUEUEDSTATSx using System; using System.Linq; using ColossalFramework; using ColossalFramework.UI; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using UnityEngine; using TrafficManager.State; using TrafficManager.Custom.PathFinding; using System.Collections.Generic; using TrafficManager.Manager; using CSUtil.Commons; using TrafficManager.Manager.Impl; using TrafficManager.Util; using CSUtil.Commons.Benchmark; namespace TrafficManager.UI { #if DEBUG public class DebugMenuPanel : UIPanel { //private static UIState _uiState = UIState.None; #if QUEUEDSTATS private static bool showPathFindStats = false; #endif /*private static UIButton _buttonSwitchTraffic; private static UIButton _buttonPrioritySigns; private static UIButton _buttonManualControl; private static UIButton _buttonTimedMain; private static UIButton _buttonLaneChange; private static UIButton _buttonLaneConnector; private static UIButton _buttonVehicleRestrictions; private static UIButton _buttonJunctionRestrictions; private static UIButton _buttonSpeedLimits; private static UIButton _buttonClearTraffic; private static UIButton _buttonToggleDespawn;*/ #if DEBUG private static UITextField _goToField = null; private static UIButton _goToSegmentButton = null; private static UIButton _goToNodeButton = null; private static UIButton _goToVehicleButton = null; private static UIButton _goToParkedVehicleButton = null; private static UIButton _goToBuildingButton = null; private static UIButton _goToCitizenInstanceButton = null; private static UIButton _goToPosButton = null; private static UIButton _printDebugInfoButton = null; private static UIButton _reloadConfigButton = null; private static UIButton _recalcLinesButton = null; private static UIButton _checkDetoursButton = null; private static UIButton _noneToVehicleButton = null; private static UIButton _vehicleToNoneButton = null; private static UIButton _printFlagsDebugInfoButton = null; private static UIButton _printBenchmarkReportButton = null; private static UIButton _resetBenchmarksButton = null; #endif #if QUEUEDSTATS private static UIButton _togglePathFindStatsButton = null; #endif public static UILabel title; public override void Start() { isVisible = false; backgroundSprite = "GenericPanel"; color = new Color32(75, 75, 135, 255); width = Translation.getMenuWidth(); height = 30; //height = LoadingExtension.IsPathManagerCompatible ? 430 : 230; Vector2 resolution = UIView.GetAView().GetScreenResolution(); relativePosition = new Vector3(resolution.x - Translation.getMenuWidth() - 30f, 65f); title = AddUIComponent(); title.text = "Version " + TrafficManagerMod.Version; title.relativePosition = new Vector3(50.0f, 5.0f); int y = 30; /*_buttonSwitchTraffic = _createButton(Translation.GetString("Switch_traffic_lights"), y, clickSwitchTraffic); y += 40; height += 40; if (Options.prioritySignsEnabled) { _buttonPrioritySigns = _createButton(Translation.GetString("Add_priority_signs"), y, clickAddPrioritySigns); y += 40; height += 40; } _buttonManualControl = _createButton(Translation.GetString("Manual_traffic_lights"), y, clickManualControl); y += 40; height += 40; if (Options.timedLightsEnabled) { _buttonTimedMain = _createButton(Translation.GetString("Timed_traffic_lights"), y, clickTimedAdd); y += 40; height += 40; } _buttonLaneChange = _createButton(Translation.GetString("Change_lane_arrows"), y, clickChangeLanes); y += 40; height += 40; if (Options.laneConnectorEnabled) { _buttonLaneConnector = _createButton(Translation.GetString("Lane_connector"), y, clickLaneConnector); y += 40; height += 40; } if (Options.customSpeedLimitsEnabled) { _buttonSpeedLimits = _createButton(Translation.GetString("Speed_limits"), y, clickSpeedLimits); y += 40; height += 40; } if (Options.vehicleRestrictionsEnabled) { _buttonVehicleRestrictions = _createButton(Translation.GetString("Vehicle_restrictions"), y, clickVehicleRestrictions); y += 40; height += 40; } if (Options.junctionRestrictionsEnabled) { _buttonJunctionRestrictions = _createButton(Translation.GetString("Junction_restrictions"), y, clickJunctionRestrictions); y += 40; height += 40; } _buttonClearTraffic = _createButton(Translation.GetString("Clear_Traffic"), y, clickClearTraffic); y += 40; height += 40; _buttonToggleDespawn = _createButton(Options.enableDespawning ? Translation.GetString("Disable_despawning") : Translation.GetString("Enable_despawning"), y, ClickToggleDespawn); y += 40; height += 40;*/ #if DEBUG _goToField = CreateTextField("", y); y += 40; height += 40; _goToPosButton = _createButton("Goto position", y, clickGoToPos); y += 40; height += 40; _goToSegmentButton = _createButton("Goto segment", y, clickGoToSegment); y += 40; height += 40; _goToNodeButton = _createButton("Goto node", y, clickGoToNode); y += 40; height += 40; _goToVehicleButton = _createButton("Goto vehicle", y, clickGoToVehicle); y += 40; height += 40; _goToParkedVehicleButton = _createButton("Goto parked vehicle", y, clickGoToParkedVehicle); y += 40; height += 40; _goToBuildingButton = _createButton("Goto building", y, clickGoToBuilding); y += 40; height += 40; _goToCitizenInstanceButton = _createButton("Goto citizen inst.", y, clickGoToCitizenInstance); y += 40; height += 40; _printDebugInfoButton = _createButton("Print debug info", y, clickPrintDebugInfo); y += 40; height += 40; _reloadConfigButton = _createButton("Reload configuration", y, clickReloadConfig); y += 40; height += 40; _recalcLinesButton = _createButton("Recalculate transport lines", y, clickRecalcLines); y += 40; height += 40; _checkDetoursButton = _createButton("Remove all parked vehicles", y, clickCheckDetours); y += 40; height += 40; /*_noneToVehicleButton = _createButton("None -> Vehicle", y, clickNoneToVehicle); y += 40; height += 40; _vehicleToNoneButton = _createButton("Vehicle -> None", y, clickVehicleToNone); y += 40; height += 40;*/ #endif #if QUEUEDSTATS _togglePathFindStatsButton = _createButton("Toggle PathFind stats", y, clickTogglePathFindStats); y += 40; height += 40; #endif #if DEBUG _printFlagsDebugInfoButton = _createButton("Print flags debug info", y, clickPrintFlagsDebugInfo); y += 40; height += 40; _printBenchmarkReportButton = _createButton("Print benchmark report", y, clickPrintBenchmarkReport); y += 40; height += 40; _resetBenchmarksButton = _createButton("Reset benchmarks", y, clickResetBenchmarks); y += 40; height += 40; #endif } private UITextField CreateTextField(string str, int y) { UITextField textfield = AddUIComponent(); textfield.relativePosition = new Vector3(15f, y); textfield.horizontalAlignment = UIHorizontalAlignment.Left; textfield.text = str; textfield.textScale = 0.8f; textfield.color = Color.black; textfield.cursorBlinkTime = 0.45f; textfield.cursorWidth = 1; textfield.selectionBackgroundColor = new Color(233, 201, 148, 255); textfield.selectionSprite = "EmptySprite"; textfield.verticalAlignment = UIVerticalAlignment.Middle; textfield.padding = new RectOffset(5, 0, 5, 0); textfield.foregroundSpriteMode = UIForegroundSpriteMode.Fill; textfield.normalBgSprite = "TextFieldPanel"; textfield.hoveredBgSprite = "TextFieldPanelHovered"; textfield.focusedBgSprite = "TextFieldPanel"; textfield.size = new Vector3(190, 30); textfield.isInteractive = true; textfield.enabled = true; textfield.readOnly = false; textfield.builtinKeyNavigation = true; textfield.width = Translation.getMenuWidth() - 30; return textfield; } private UIButton _createButton(string text, int y, MouseEventHandler eventClick) { var button = AddUIComponent(); button.textScale = 0.8f; button.width = Translation.getMenuWidth()-30; button.height = 30; button.normalBgSprite = "ButtonMenu"; button.disabledBgSprite = "ButtonMenuDisabled"; button.hoveredBgSprite = "ButtonMenuHovered"; button.focusedBgSprite = "ButtonMenu"; button.pressedBgSprite = "ButtonMenuPressed"; button.textColor = new Color32(255, 255, 255, 255); button.playAudioEvents = true; button.text = text; button.relativePosition = new Vector3(15f, y); button.eventClick += delegate (UIComponent component, UIMouseEventParameter eventParam) { eventClick(component, eventParam); button.Invalidate(); }; return button; } #if DEBUG private void clickGoToPos(UIComponent component, UIMouseEventParameter eventParam) { string[] vectorElms = _goToField.text.Split(','); if (vectorElms.Length < 2) return; CSUtil.CameraControl.CameraController.Instance.GoToPos(new Vector3(float.Parse(vectorElms[0]), Camera.main.transform.position.y, float.Parse(vectorElms[1]))); } private void clickGoToSegment(UIComponent component, UIMouseEventParameter eventParam) { ushort segmentId = Convert.ToUInt16(_goToField.text); if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Created) != NetSegment.Flags.None) { CSUtil.CameraControl.CameraController.Instance.GoToSegment(segmentId); } } private void clickGoToNode(UIComponent component, UIMouseEventParameter eventParam) { ushort nodeId = Convert.ToUInt16(_goToField.text); if ((Singleton.instance.m_nodes.m_buffer[nodeId].m_flags & NetNode.Flags.Created) != NetNode.Flags.None) { CSUtil.CameraControl.CameraController.Instance.GoToNode(nodeId); } } private void clickPrintDebugInfo(UIComponent component, UIMouseEventParameter eventParam) { Constants.ServiceFactory.SimulationService.AddAction(() => { UtilityManager.Instance.PrintDebugInfo(); }); } private void clickReloadConfig(UIComponent component, UIMouseEventParameter eventParam) { GlobalConfig.Reload(); } private void clickRecalcLines(UIComponent component, UIMouseEventParameter eventParam) { SimulationManager.instance.AddAction(() => { for (int i = 0; i < TransportManager.MAX_LINE_COUNT; ++i) { if (TransportManager.instance.m_lines.m_buffer[i].m_flags == TransportLine.Flags.None) { continue; //Log.Message("\tTransport line is not created."); } Log.Info($"Recalculating transport line {i} now."); if (TransportManager.instance.m_lines.m_buffer[i].UpdatePaths((ushort)i) && TransportManager.instance.m_lines.m_buffer[i].UpdateMeshData((ushort)i) ) { Log.Info($"Transport line {i} recalculated."); } } }); } private void clickCheckDetours(UIComponent component, UIMouseEventParameter eventParam) { SimulationManager.instance.AddAction(() => { SimulationManager.instance.ForcedSimulationPaused = true; for (uint i = 0; i < VehicleManager.instance.m_parkedVehicles.m_buffer.Length; ++i) { VehicleManager.instance.ReleaseParkedVehicle((ushort)i); } SimulationManager.instance.ForcedSimulationPaused = false; }); /*Log.Info($"Screen.width: {Screen.width} Screen.height: {Screen.height}"); Log.Info($"Screen.currentResolution.width: {Screen.currentResolution.width} Screen.currentResolution.height: {Screen.currentResolution.height}"); Vector2 resolution = UIView.GetAView().GetScreenResolution(); Log.Info($"UIView.screenResolution.width: {resolution.x} UIView.screenResolution.height: {resolution.y}"); */ /*SimulationManager.instance.AddAction(() => { PrintTransportStats(); });*/ } public static void PrintTransportStats() { for (int i = 0; i < TransportManager.MAX_LINE_COUNT; ++i) { Log.Info("Transport line " + i + ":"); if ((TransportManager.instance.m_lines.m_buffer[i].m_flags & TransportLine.Flags.Created) == TransportLine.Flags.None) { Log.Info("\tTransport line is not created."); continue; } Log.Info("\tFlags: " + TransportManager.instance.m_lines.m_buffer[i].m_flags + ", cat: " + TransportManager.instance.m_lines.m_buffer[i].Info.category + ", type: " + TransportManager.instance.m_lines.m_buffer[i].Info.m_transportType + ", name: " + TransportManager.instance.GetLineName((ushort)i)); ushort firstStopNodeId = TransportManager.instance.m_lines.m_buffer[i].m_stops; ushort stopNodeId = firstStopNodeId; Vector3 lastNodePos = Vector3.zero; int index = 1; while (stopNodeId != 0) { Vector3 pos = NetManager.instance.m_nodes.m_buffer[stopNodeId].m_position; Log.Info("\tStop node #" + index + " -- " + stopNodeId + ": Flags: " + NetManager.instance.m_nodes.m_buffer[stopNodeId].m_flags + ", Transport line: " + NetManager.instance.m_nodes.m_buffer[stopNodeId].m_transportLine + ", Problems: " + NetManager.instance.m_nodes.m_buffer[stopNodeId].m_problems + " Pos: " + pos + ", Dist. to lat pos: " + (lastNodePos - pos).magnitude); if (NetManager.instance.m_nodes.m_buffer[stopNodeId].m_problems != Notification.Problem.None) { Log.Warning("\t*** PROBLEMS DETECTED ***"); } lastNodePos = pos; ushort nextSegment = TransportLine.GetNextSegment(stopNodeId); if (nextSegment != 0) { stopNodeId = NetManager.instance.m_segments.m_buffer[(int)nextSegment].m_endNode; } else { break; } ++index; if (stopNodeId == firstStopNodeId) { break; } if (index > 10000) { Log.Error("Too many iterations!"); break; } } } } private static Dictionary> customEmergencyLanes = new Dictionary>(); private void clickNoneToVehicle(UIComponent component, UIMouseEventParameter eventParam) { Dictionary ret = new Dictionary(); int numLoaded = PrefabCollection.LoadedCount(); for (uint i = 0; i < numLoaded; ++i) { NetInfo info = PrefabCollection.GetLoaded(i); if (!(info.m_netAI is RoadBaseAI)) continue; RoadBaseAI ai = (RoadBaseAI)info.m_netAI; if (!ai.m_highwayRules) continue; NetInfo.Lane[] laneInfos = info.m_lanes; for (byte k = 0; k < Math.Min(2, laneInfos.Length); ++k) { NetInfo.Lane laneInfo = laneInfos[k]; if (laneInfo.m_vehicleType == VehicleInfo.VehicleType.None) { laneInfo.m_vehicleType = VehicleInfo.VehicleType.Car; laneInfo.m_laneType = NetInfo.LaneType.Vehicle; Log._Debug($"Changing vehicle type of lane {k} @ {info.name} from None to Car, lane type from None to Vehicle"); if (!customEmergencyLanes.ContainsKey(info.name)) customEmergencyLanes.Add(info.name, new List()); customEmergencyLanes[info.name].Add(k); } } } } #endif #if QUEUEDSTATS private void clickTogglePathFindStats(UIComponent component, UIMouseEventParameter eventParam) { Update(); showPathFindStats = !showPathFindStats; } #endif #if DEBUG private void clickPrintFlagsDebugInfo(UIComponent component, UIMouseEventParameter eventParam) { Flags.PrintDebugInfo(); } private void clickPrintBenchmarkReport(UIComponent component, UIMouseEventParameter eventParam) { Constants.ServiceFactory.SimulationService.AddAction(() => { Log.Info(BenchmarkProfileProvider.Instance.CreateReport()); }); } private void clickResetBenchmarks(UIComponent component, UIMouseEventParameter eventParam) { Constants.ServiceFactory.SimulationService.AddAction(() => { BenchmarkProfileProvider.Instance.ClearProfiles(); }); } private void clickVehicleToNone(UIComponent component, UIMouseEventParameter eventParam) { foreach (KeyValuePair> e in customEmergencyLanes) { NetInfo info = PrefabCollection.FindLoaded(e.Key); if (info == null) { Log.Warning($"Could not find NetInfo by name {e.Key}"); continue; } foreach (byte index in e.Value) { if (index < 0 || index >= info.m_lanes.Length) { Log.Warning($"Illegal lane index {index} for NetInfo {e.Key}"); continue; } Log._Debug($"Resetting vehicle type of lane {index} @ {info.name}"); info.m_lanes[index].m_vehicleType = VehicleInfo.VehicleType.None; info.m_lanes[index].m_laneType = NetInfo.LaneType.None; } } customEmergencyLanes.Clear(); } private void clickGoToVehicle(UIComponent component, UIMouseEventParameter eventParam) { ushort vehicleId = Convert.ToUInt16(_goToField.text); Vehicle vehicle = Singleton.instance.m_vehicles.m_buffer[vehicleId]; if ((vehicle.m_flags & Vehicle.Flags.Created) != 0) { CSUtil.CameraControl.CameraController.Instance.GoToVehicle(vehicleId); } } private void clickGoToParkedVehicle(UIComponent component, UIMouseEventParameter eventParam) { ushort parkedVehicleId = Convert.ToUInt16(_goToField.text); VehicleParked parkedVehicle = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId]; if ((parkedVehicle.m_flags & (ushort)VehicleParked.Flags.Created) != 0) { CSUtil.CameraControl.CameraController.Instance.GoToParkedVehicle(parkedVehicleId); } } private void clickGoToBuilding(UIComponent component, UIMouseEventParameter eventParam) { ushort buildingId = Convert.ToUInt16(_goToField.text); Building building = Singleton.instance.m_buildings.m_buffer[buildingId]; if ((building.m_flags & Building.Flags.Created) != 0) { CSUtil.CameraControl.CameraController.Instance.GoToBuilding(buildingId); /*for (int index = 0; index < BuildingManager.BUILDINGGRID_RESOLUTION * BuildingManager.BUILDINGGRID_RESOLUTION; ++index) { ushort bid = Singleton.instance.m_buildingGrid[index]; while (bid != 0) { if (bid == buildingId) { int i = index / BuildingManager.BUILDINGGRID_RESOLUTION; int j = index % BuildingManager.BUILDINGGRID_RESOLUTION; Log._Debug($"Found building {buildingId} in building grid @ {index}. i={i}, j={j}"); } bid = Singleton.instance.m_buildings.m_buffer[bid].m_nextGridBuilding; } }*/ } } private void clickGoToCitizenInstance(UIComponent component, UIMouseEventParameter eventParam) { ushort citizenInstanceId = Convert.ToUInt16(_goToField.text); CitizenInstance citizenInstance = Singleton.instance.m_instances.m_buffer[citizenInstanceId]; if ((citizenInstance.m_flags & CitizenInstance.Flags.Created) != 0) { CSUtil.CameraControl.CameraController.Instance.GoToCitizenInstance(citizenInstanceId); } } #endif public override void Update() { #if QUEUEDSTATS if (showPathFindStats && title != null) { title.text = CustomPathManager.TotalQueuedPathFinds.ToString(); } #endif } } #endif } ================================================ FILE: TLM/TLM/UI/MainMenu/DespawnButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class DespawnButton : MenuButton { public override bool Active { get { return false; } } public override ButtonFunction Function { get { return Options.disableDespawning ? ButtonFunction.DespawnDisabled : ButtonFunction.DespawnEnabled; } } public override string Tooltip { get { return Options.disableDespawning ? "Enable_despawning" : "Disable_despawning"; } } public override bool Visible { get { return true; } } public override void OnClickInternal(UIMouseEventParameter p) { UIBase.GetTrafficManagerTool(true).SetToolMode(ToolMode.None); Options.setDisableDespawning(!Options.disableDespawning); } } } ================================================ FILE: TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class JunctionRestrictionsButton : MenuToolModeButton { public override ToolMode ToolMode { get { return ToolMode.JunctionRestrictions; } } public override ButtonFunction Function { get { return ButtonFunction.JunctionRestrictions; } } public override string Tooltip { get { return "Junction_restrictions"; } } public override bool Visible { get { return Options.junctionRestrictionsEnabled; } } } } ================================================ FILE: TLM/TLM/UI/MainMenu/LaneArrowsButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class LaneArrowsButton : MenuToolModeButton { public override ToolMode ToolMode { get { return ToolMode.LaneChange; } } public override ButtonFunction Function { get { return ButtonFunction.LaneArrows; } } public override string Tooltip { get { return "Change_lane_arrows"; } } public override bool Visible { get { return true; } } } } ================================================ FILE: TLM/TLM/UI/MainMenu/LaneConnectorButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class LaneConnectorButton : MenuToolModeButton { public override ToolMode ToolMode { get { return ToolMode.LaneConnector; } } public override ButtonFunction Function { get { return ButtonFunction.LaneConnector; } } public override string Tooltip { get { return "Lane_connector"; } } public override bool Visible { get { return Options.laneConnectorEnabled; } } } } ================================================ FILE: TLM/TLM/UI/MainMenu/MainMenuPanel.cs ================================================ #define QUEUEDSTATSx using System; using System.Linq; using ColossalFramework; using ColossalFramework.UI; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using UnityEngine; using TrafficManager.State; using TrafficManager.Custom.PathFinding; using System.Collections.Generic; using TrafficManager.Manager; using CSUtil.Commons; using TrafficManager.Util; namespace TrafficManager.UI.MainMenu { public class MainMenuPanel : UIPanel, IObserver { private static readonly Type[] MENU_BUTTON_TYPES = new Type[] { // first row typeof(ToggleTrafficLightsButton), typeof(ManualTrafficLightsButton), typeof(LaneArrowsButton), typeof(LaneConnectorButton), typeof(DespawnButton), typeof(ClearTrafficButton), // second row typeof(PrioritySignsButton), typeof(TimedTrafficLightsButton), typeof(JunctionRestrictionsButton), typeof(SpeedLimitsButton), typeof(VehicleRestrictionsButton), typeof(ParkingRestrictionsButton), }; public class SizeProfile { public int NUM_BUTTONS_PER_ROW { get; set; } public int NUM_ROWS { get; set; } public int VSPACING { get; set; } public int HSPACING { get; set; } public int TOP_BORDER { get; set; } public int BUTTON_SIZE { get; set; } public int MENU_WIDTH { get; set; } public int MENU_HEIGHT { get; set; } } public static readonly SizeProfile[] SIZE_PROFILES = new SizeProfile[] { new SizeProfile() { NUM_BUTTONS_PER_ROW = 6, NUM_ROWS = 2, VSPACING = 5, HSPACING = 5, TOP_BORDER = 25, BUTTON_SIZE = 30, MENU_WIDTH = 215, MENU_HEIGHT = 95 }, new SizeProfile() { NUM_BUTTONS_PER_ROW = 6, NUM_ROWS = 2, VSPACING = 5, HSPACING = 5, TOP_BORDER = 25, BUTTON_SIZE = 50, MENU_WIDTH = 335, MENU_HEIGHT = 135 } }; public const int DEFAULT_MENU_X = 85; public const int DEFAULT_MENU_Y = 60; public MenuButton[] Buttons { get; private set; } public UILabel VersionLabel { get; private set; } public UILabel StatsLabel { get; private set; } public UIDragHandle Drag { get; private set; } IDisposable confDisposable; private SizeProfile activeProfile = null; private bool started = false; //private UILabel optionsLabel; public override void Start() { GlobalConfig conf = GlobalConfig.Instance; DetermineProfile(conf); OnUpdate(conf); confDisposable = conf.Subscribe(this); isVisible = false; backgroundSprite = "GenericPanel"; color = new Color32(64, 64, 64, 240); VersionLabel = AddUIComponent(); StatsLabel = AddUIComponent(); Buttons = new MenuButton[MENU_BUTTON_TYPES.Length]; for (int i = 0; i < MENU_BUTTON_TYPES.Length; ++i) { Buttons[i] = AddUIComponent(MENU_BUTTON_TYPES[i]) as MenuButton; } var dragHandler = new GameObject("TMPE_Menu_DragHandler"); dragHandler.transform.parent = transform; dragHandler.transform.localPosition = Vector3.zero; Drag = dragHandler.AddComponent(); Drag.enabled = !GlobalConfig.Instance.Main.MainMenuPosLocked; UpdateAllSizes(); started = true; } public override void OnDestroy() { if (confDisposable != null) { confDisposable.Dispose(); } } internal void SetPosLock(bool lck) { Drag.enabled = !lck; } protected override void OnPositionChanged() { GlobalConfig config = GlobalConfig.Instance; bool posChanged = (config.Main.MainMenuX != (int)absolutePosition.x || config.Main.MainMenuY != (int)absolutePosition.y); if (posChanged) { Log._Debug($"Menu position changed to {absolutePosition.x}|{absolutePosition.y}"); config.Main.MainMenuX = (int)absolutePosition.x; config.Main.MainMenuY = (int)absolutePosition.y; GlobalConfig.WriteConfig(); } base.OnPositionChanged(); } public void OnUpdate(GlobalConfig config) { UpdatePosition(new Vector2(config.Main.MainMenuX, config.Main.MainMenuY)); if (started) { DetermineProfile(config); UpdateAllSizes(); Invalidate(); } } private void DetermineProfile(GlobalConfig conf) { int profileIndex = conf.Main.TinyMainMenu ? 0 : 1; activeProfile = SIZE_PROFILES[profileIndex]; } public void UpdateAllSizes() { UpdateSize(); UpdateDragSize(); UpdateButtons(); } private void UpdateSize() { width = activeProfile.MENU_WIDTH; height = activeProfile.MENU_HEIGHT; } private void UpdateDragSize() { Drag.width = width; Drag.height = activeProfile.TOP_BORDER; } private void UpdateButtons() { int i = 0; int y = activeProfile.TOP_BORDER; for (int row = 0; row < activeProfile.NUM_ROWS; ++row) { int x = activeProfile.HSPACING; for (int col = 0; col < activeProfile.NUM_BUTTONS_PER_ROW; ++col) { if (i >= Buttons.Length) { break; } MenuButton button = Buttons[i]; button.relativePosition = new Vector3(x, y); button.width = activeProfile.BUTTON_SIZE; button.height = activeProfile.BUTTON_SIZE; button.Invalidate(); Buttons[i++] = button; x += activeProfile.BUTTON_SIZE + activeProfile.HSPACING; } y += activeProfile.BUTTON_SIZE + activeProfile.VSPACING; } } public void UpdatePosition(Vector2 pos) { Rect rect = new Rect(pos.x, pos.y, activeProfile.MENU_WIDTH, activeProfile.MENU_HEIGHT); Vector2 resolution = UIView.GetAView().GetScreenResolution(); VectorUtil.ClampRectToScreen(ref rect, resolution); Log.Info($"Setting main menu position to [{pos.x},{pos.y}]"); absolutePosition = rect.position; Invalidate(); } } } ================================================ FILE: TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class ManualTrafficLightsButton : MenuToolModeButton { public override ToolMode ToolMode { get { return ToolMode.ManualSwitch; } } public override ButtonFunction Function { get { return ButtonFunction.ManualTrafficLights; } } public override string Tooltip { get { return "Manual_traffic_lights"; } } public override bool Visible { get { return Options.timedLightsEnabled; } } } } ================================================ FILE: TLM/TLM/UI/MainMenu/MenuButton.cs ================================================ using ColossalFramework.Math; using ColossalFramework.UI; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.State; using TrafficManager.Util; using UnityEngine; namespace TrafficManager.UI.MainMenu { public abstract class MenuButton : LinearSpriteButton { public enum ButtonFunction { LaneConnector, ClearTraffic, DespawnDisabled, DespawnEnabled, JunctionRestrictions, LaneArrows, ManualTrafficLights, PrioritySigns, SpeedLimits, TimedTrafficLights, ToggleTrafficLights, VehicleRestrictions, ParkingRestrictions } public const string MENU_BUTTON = "TMPE_MenuButton"; public const int BUTTON_SIZE = 30; public override void HandleClick(UIMouseEventParameter p) { } protected override void OnClick(UIMouseEventParameter p) { OnClickInternal(p); foreach (MenuButton button in LoadingExtension.BaseUI.MainMenu.Buttons) { button.UpdateProperties(); } } public abstract void OnClickInternal(UIMouseEventParameter p); public abstract ButtonFunction Function { get; } public override bool CanActivate() { return true; } public override string ButtonName { get { return MENU_BUTTON; } } public override string FunctionName { get { return Function.ToString(); } } public override string[] FunctionNames { get { var functions = Enum.GetValues(typeof(ButtonFunction)); string[] ret = new string[functions.Length]; for (int i = 0; i < functions.Length; ++i) { ret[i] = functions.GetValue(i).ToString(); } return ret; } } public override Texture2D AtlasTexture { get { return TextureResources.MainMenuButtonsTexture2D; } } public override int Width { get { return 50; } } public override int Height { get { return 50; } } } } ================================================ FILE: TLM/TLM/UI/MainMenu/MenuToolModeButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; namespace TrafficManager.UI.MainMenu { public abstract class MenuToolModeButton : MenuButton { public abstract ToolMode ToolMode { get; } public override bool Active { get { return this.ToolMode.Equals(UIBase.GetTrafficManagerTool(false)?.GetToolMode()); } } public override void OnClickInternal(UIMouseEventParameter p) { if (Active) { UIBase.GetTrafficManagerTool(true).SetToolMode(ToolMode.None); } else { UIBase.GetTrafficManagerTool(true).SetToolMode(this.ToolMode); } } } } ================================================ FILE: TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class ParkingRestrictionsButton : MenuToolModeButton { public override ToolMode ToolMode { get { return ToolMode.ParkingRestrictions; } } public override ButtonFunction Function { get { return ButtonFunction.ParkingRestrictions; } } public override string Tooltip { get { return "Parking_restrictions"; } } public override bool Visible { get { return Options.parkingRestrictionsEnabled; } } } } ================================================ FILE: TLM/TLM/UI/MainMenu/PrioritySignsButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class PrioritySignsButton : MenuToolModeButton { public override ToolMode ToolMode { get { return ToolMode.AddPrioritySigns; } } public override ButtonFunction Function { get { return ButtonFunction.PrioritySigns; } } public override string Tooltip { get { return "Add_priority_signs"; } } public override bool Visible { get { return Options.prioritySignsEnabled; } } } } ================================================ FILE: TLM/TLM/UI/MainMenu/README.md ================================================ # TM:PE -- /UI/MainMenu Main menu related classes. ## Classes - **ClearTrafficButton**: Triggers deletion of all spawned vehicles - **DebugMenu**: Debugging menu (originally the old main menu) - **DespawnButton**: For toggling despawn - **JunctionRestrictionsButton**: For controlling junction restrictions - **LaneArrowsButton**: For setting up custom lane arrows - **LaneConnectorButton**: For setting up custom lane connections - **MainMenuPanel**: the new (v1.9) compact main menu - **ManualTrafficLightsButton**: For setting up manual traffic lights - **MenuButton**: abstract main menu button - **MenuToolModeButton**: abstract main menu button supporting association with a specific tool mode - **OptionsLabel**: currently unused - **ParkingRestrictionsButton**: For controlling parking restrictions - **SpeedLimitsButton**: For setting up custom speed limits - **TimedTrafficLightsButton**: For setting up timed traffic lights - **ToggleTrafficLightsButton**: For adding/removing traffic lights at junctions - **VehicleRestrictionsButton**: For setting up vehicle restrictions - **VersionLabel**: Displays the current version ================================================ FILE: TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class SpeedLimitsButton : MenuToolModeButton { public override ToolMode ToolMode { get { return ToolMode.SpeedLimits; } } public override ButtonFunction Function { get { return ButtonFunction.SpeedLimits; } } public override string Tooltip { get { return "Speed_limits"; } } public override bool Visible { get { return Options.customSpeedLimitsEnabled; } } } } ================================================ FILE: TLM/TLM/UI/MainMenu/StatsLabel.cs ================================================ using ColossalFramework.UI; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Custom.PathFinding; using TrafficManager.State; using UnityEngine; namespace TrafficManager.UI.MainMenu { public class StatsLabel : UILabel { public override void Start() { size = new Vector2(MainMenuPanel.SIZE_PROFILES[0].MENU_WIDTH / 2, MainMenuPanel.SIZE_PROFILES[0].TOP_BORDER); // TODO use current size profile text = ""; relativePosition = new Vector3(5f, -20f); textAlignment = UIHorizontalAlignment.Left; anchor = UIAnchorStyle.Top | UIAnchorStyle.Left; } #if QUEUEDSTATS public override void Update() { if (Options.showPathFindStats) { uint queued = CustomPathManager.TotalQueuedPathFinds; if (queued < 1000) { textColor = Color.Lerp(Color.green, Color.yellow, (float)queued / 1000f); } else if (queued < 2500) { textColor = Color.Lerp(Color.yellow, Color.red, (float)(queued - 1000f) / 1500f); } else { textColor = Color.red; } text = CustomPathManager.TotalQueuedPathFinds.ToString() + " PFs"; } else { text = ""; m_TextColor = Color.white; } } #endif } } ================================================ FILE: TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class TimedTrafficLightsButton : MenuToolModeButton { public override ToolMode ToolMode { get { return ToolMode.TimedLightsSelectNode; } } public override ButtonFunction Function { get { return ButtonFunction.TimedTrafficLights; } } public override string Tooltip { get { return "Timed_traffic_lights"; } } public override bool Visible { get { return Options.timedLightsEnabled; } } } } ================================================ FILE: TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; namespace TrafficManager.UI.MainMenu { public class ToggleTrafficLightsButton : MenuToolModeButton { public override ToolMode ToolMode { get { return ToolMode.SwitchTrafficLight; } } public override ButtonFunction Function { get { return ButtonFunction.ToggleTrafficLights; } } public override string Tooltip { get { return "Switch_traffic_lights"; } } public override bool Visible { get { return true; } } } } ================================================ FILE: TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using ColossalFramework.UI; using TrafficManager.Manager; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { public class VehicleRestrictionsButton : MenuToolModeButton { public override ToolMode ToolMode { get { return ToolMode.VehicleRestrictions; } } public override ButtonFunction Function { get { return ButtonFunction.VehicleRestrictions; } } public override string Tooltip { get { return "Vehicle_restrictions"; } } public override bool Visible { get { return Options.vehicleRestrictionsEnabled; } } } } ================================================ FILE: TLM/TLM/UI/MainMenu/VersionLabel.cs ================================================ using ColossalFramework.UI; using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; namespace TrafficManager.UI.MainMenu { public class VersionLabel : UILabel { public override void Start() { size = new Vector2(MainMenuPanel.SIZE_PROFILES[0].MENU_WIDTH, MainMenuPanel.SIZE_PROFILES[0].TOP_BORDER); // TODO use current size profile text = "TM:PE " + TrafficManagerMod.Version; relativePosition = new Vector3(5f, 5f); textAlignment = UIHorizontalAlignment.Left; } } } ================================================ FILE: TLM/TLM/UI/README.md ================================================ # TM:PE -- /UI User interface classes. ## Classes - **CameraCtrl**: Allows to move the player camera to certain objects (used for debugging) - **SubTool**: Represents a tool that can be selected through the main menu. - **TextureResources**: Holds references to all required textures. - **ToolMode**: Enum. Allowes to describe which sub tool is currently active. - **TrafficManagerTool**: Central UI controller. Manages active sub tools and forwards UI update calls to the sub tools. - **Translation**: Internationalization functions. - **TransportDemandViewMode**: Enum. Represents the currently active demand mode when the public transport info view is active together with the Parking AI - **UIBase**: Holds an instance of the main menu and main menu button - **UIMainMenuButton**: the main menu button - **UITransportDemand**: transport demand view mode toggle window ================================================ FILE: TLM/TLM/UI/RemoveCitizenInstanceButtonExtender.cs ================================================ using ColossalFramework; using ColossalFramework.UI; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Manager.Impl; using TrafficManager.UI.MainMenu; using UnityEngine; namespace TrafficManager.UI { public class RemoveCitizenInstanceButtonExtender : MonoBehaviour { private IList buttons; public void Start() { buttons = new List(); var citizenInfoPanel = GameObject.Find("(Library) CitizenWorldInfoPanel").GetComponent(); if (citizenInfoPanel != null) { buttons.Add(AddRemoveCitizenInstanceButton(citizenInfoPanel)); } var touristInfoPanel = GameObject.Find("(Library) TouristWorldInfoPanel").GetComponent(); if (touristInfoPanel != null) { buttons.Add(AddRemoveCitizenInstanceButton(touristInfoPanel)); } } public void OnDestroy() { if (buttons == null) { return; } foreach (UIButton button in buttons) { Destroy(button.gameObject); } } protected UIButton AddRemoveCitizenInstanceButton(WorldInfoPanel panel) { UIButton button = UIView.GetAView().AddUIComponent(typeof(RemoveCitizenInstanceButton)) as RemoveCitizenInstanceButton; button.AlignTo(panel.component, UIAlignAnchor.TopRight); button.relativePosition += new Vector3(- button.width - 80f, 50f); return button; } public class RemoveCitizenInstanceButton : LinearSpriteButton { public override void Start() { base.Start(); width = Width; height = Height; } public override void HandleClick(UIMouseEventParameter p) { InstanceID instance = WorldInfoPanel.GetCurrentInstanceID(); Log._Debug($"Current citizen: {instance.Citizen}"); if (instance.Citizen != 0) { ushort citizenInstanceId = 0; Constants.ServiceFactory.CitizenService.ProcessCitizen(instance.Citizen, delegate (uint citId, ref Citizen cit) { citizenInstanceId = cit.m_instance; return true; }); Log._Debug($"Current citizen: {instance.Citizen} Instance: {citizenInstanceId}"); if (citizenInstanceId != 0) { Constants.ServiceFactory.SimulationService.AddAction(() => Constants.ServiceFactory.CitizenService.ReleaseCitizenInstance(citizenInstanceId)); } } } public override bool Active { get { return false; } } public override Texture2D AtlasTexture { get { return TextureResources.RemoveButtonTexture2D; } } public override string ButtonName { get { return "RemoveCitizenInstance"; } } public override string FunctionName { get { return "RemoveCitizenInstanceNow"; } } public override string[] FunctionNames { get { return new string[] { "RemoveCitizenInstanceNow" }; } } public override string Tooltip { get { return Translation.GetString("Remove_this_citizen"); } } public override bool Visible { get { return true; } } public override int Width { get { return 30; } } public override int Height { get { return 30; } } public override bool CanActivate() { return false; } } } } ================================================ FILE: TLM/TLM/UI/RemoveVehicleButtonExtender.cs ================================================ using ColossalFramework; using ColossalFramework.UI; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Manager.Impl; using TrafficManager.UI.MainMenu; using UnityEngine; namespace TrafficManager.UI { public class RemoveVehicleButtonExtender : MonoBehaviour { private IList buttons; public void Start() { buttons = new List(); var citizenVehicleInfoPanel = GameObject.Find("(Library) CitizenVehicleWorldInfoPanel").GetComponent(); if (citizenVehicleInfoPanel != null) { buttons.Add(AddRemoveVehicleButton(citizenVehicleInfoPanel)); } var cityServiceVehicleInfoPanel = GameObject.Find("(Library) CityServiceVehicleWorldInfoPanel").GetComponent(); if (cityServiceVehicleInfoPanel != null) { buttons.Add(AddRemoveVehicleButton(cityServiceVehicleInfoPanel)); } var publicTransportVehicleInfoPanel = GameObject.Find("(Library) PublicTransportVehicleWorldInfoPanel").GetComponent(); if (publicTransportVehicleInfoPanel != null) { buttons.Add(AddRemoveVehicleButton(publicTransportVehicleInfoPanel)); } } public void OnDestroy() { if (buttons == null) { return; } foreach (UIButton button in buttons) { Destroy(button.gameObject); } } protected UIButton AddRemoveVehicleButton(WorldInfoPanel panel) { UIButton button = UIView.GetAView().AddUIComponent(typeof(RemoveVehicleButton)) as RemoveVehicleButton; button.AlignTo(panel.component, UIAlignAnchor.TopRight); button.relativePosition += new Vector3(- button.width - 80f, 50f); return button; } public class RemoveVehicleButton : LinearSpriteButton { public override void Start() { base.Start(); width = Width; height = Height; } public override void HandleClick(UIMouseEventParameter p) { InstanceID instance = WorldInfoPanel.GetCurrentInstanceID(); Log._Debug($"Current vehicle instance: {instance.Vehicle}"); if (instance.Vehicle != 0) { Constants.ServiceFactory.SimulationService.AddAction(() => Constants.ServiceFactory.VehicleService.ReleaseVehicle(instance.Vehicle)); } else if (instance.ParkedVehicle != 0) { Constants.ServiceFactory.SimulationService.AddAction(() => Constants.ServiceFactory.VehicleService.ReleaseParkedVehicle(instance.ParkedVehicle)); } } public override bool Active { get { return false; } } public override Texture2D AtlasTexture { get { return TextureResources.RemoveButtonTexture2D; } } public override string ButtonName { get { return "RemoveVehicle"; } } public override string FunctionName { get { return "RemoveVehicleNow"; } } public override string[] FunctionNames { get { return new string[] { "RemoveVehicleNow" }; } } public override string Tooltip { get { return Translation.GetString("Remove_this_vehicle"); } } public override bool Visible { get { return true; } } public override int Width { get { return 30; } } public override int Height { get { return 30; } } public override bool CanActivate() { return false; } } } } ================================================ FILE: TLM/TLM/UI/SubTool.cs ================================================ using ColossalFramework.UI; using System; using System.Collections.Generic; using System.Text; using UnityEngine; namespace TrafficManager.UI { public abstract class SubTool { public TrafficManagerTool MainTool { get; set; } protected Texture2D WindowTexture { get { if (windowTexture == null) { windowTexture = TrafficManagerTool.AdjustAlpha(TextureResources.WindowBackgroundTexture2D, MainTool.GetWindowAlpha()); } return windowTexture; } } private Texture2D windowTexture = null; protected GUIStyle WindowStyle { get { if (windowStyle == null) { windowStyle = new GUIStyle { normal = { background = WindowTexture, textColor = Color.white }, alignment = TextAnchor.UpperCenter, fontSize = 20, border = { left = 4, top = 41, right = 4, bottom = 8 }, overflow = { bottom = 0, top = 0, right = 12, left = 12 }, contentOffset = new Vector2(0, -44), padding = { top = 55 } }; } return windowStyle; } } private GUIStyle windowStyle = null; protected Texture2D BorderlessTexture { get { if (borderlessTexture == null) { borderlessTexture = TrafficManagerTool.MakeTex(1, 1, new Color(0.5f, 0.5f, 0.5f, MainTool.GetWindowAlpha())); } return borderlessTexture; } } private Texture2D borderlessTexture = null; protected GUIStyle BorderlessStyle { get { if (borderlessStyle == null) { borderlessStyle = new GUIStyle { normal = { background = BorderlessTexture }, alignment = TextAnchor.MiddleCenter, border = { bottom = 2, top = 2, right = 2, left = 2 } }; } return borderlessStyle; } } private GUIStyle borderlessStyle = null; protected ushort HoveredNodeId { get { return TrafficManagerTool.HoveredNodeId; } set { TrafficManagerTool.HoveredNodeId = value; } } protected ushort HoveredSegmentId { get { return TrafficManagerTool.HoveredSegmentId; } set { TrafficManagerTool.HoveredSegmentId = value; } } protected ushort SelectedNodeId { get { return TrafficManagerTool.SelectedNodeId; } set { TrafficManagerTool.SelectedNodeId = value; } } protected ushort SelectedSegmentId { get { return TrafficManagerTool.SelectedSegmentId; } set { TrafficManagerTool.SelectedSegmentId = value; } } public SubTool(TrafficManagerTool mainTool) { MainTool = mainTool; } public void OnToolUpdate() { //OnLeftClickOverlay(); } float nativeWidth = 1920; float nativeHeight = 1200; /// /// Called whenever the /// public abstract void OnPrimaryClickOverlay(); public virtual void OnSecondaryClickOverlay() { } public virtual void OnToolGUI(Event e) { //set up scaling /*Vector2 resolution = UIView.GetAView().GetScreenResolution(); float rx = resolution.x / nativeWidth; float ry = resolution.y / nativeHeight; GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, new Vector3(rx, ry, 1));*/ } public abstract void RenderOverlay(RenderManager.CameraInfo cameraInfo); public virtual void Initialize() { borderlessTexture = null; borderlessStyle = null; windowTexture = null; windowStyle = null; } public virtual void Cleanup() { } public virtual void OnActivate() { } public virtual void RenderInfoOverlay(RenderManager.CameraInfo cameraInfo) { } public virtual void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { } public virtual bool IsCursorInPanel() { return LoadingExtension.BaseUI.GetMenu().containsMouse #if DEBUG || LoadingExtension.BaseUI.GetDebugMenu().containsMouse #endif ; } public virtual string GetTutorialKey() { return this.GetType().Name; } protected void DragWindow(ref Rect window) { Vector2 resolution = UIView.GetAView().GetScreenResolution(); window.x = Mathf.Clamp(window.x, 0, resolution.x - window.width); window.y = Mathf.Clamp(window.y, 0, resolution.y - window.height); bool primaryMouseDown = Input.GetMouseButton(0); if (primaryMouseDown) { GUI.DragWindow(); } } } } ================================================ FILE: TLM/TLM/UI/SubTools/JunctionRestrictionsTool.cs ================================================ #define DEBUGCONNx using ColossalFramework; using ColossalFramework.Math; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Custom.AI; using TrafficManager.State; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using TrafficManager.Util; using UnityEngine; using TrafficManager.Manager; using CSUtil.Commons; using TrafficManager.Manager.Impl; using TrafficManager.Geometry.Impl; using ColossalFramework.UI; namespace TrafficManager.UI.SubTools { public class JunctionRestrictionsTool : SubTool { private HashSet currentRestrictedNodeIds; private bool overlayHandleHovered; private readonly float junctionRestrictionsSignSize = 80f; public JunctionRestrictionsTool(TrafficManagerTool mainTool) : base(mainTool) { currentRestrictedNodeIds = new HashSet(); } public override void OnToolGUI(Event e) { /*if (SelectedNodeId != 0) { overlayHandleHovered = false; } ShowSigns(false);*/ } public override void RenderInfoOverlay(RenderManager.CameraInfo cameraInfo) { } public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { if (SelectedNodeId != 0) { // draw selected node MainTool.DrawNodeCircle(cameraInfo, SelectedNodeId, true); } if (HoveredNodeId != 0 && HoveredNodeId != SelectedNodeId && (Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Bend)) != NetNode.Flags.None) { // draw hovered node MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0)); } } public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { if (viewOnly && !Options.junctionRestrictionsOverlay) return; if (SelectedNodeId != 0) { overlayHandleHovered = false; } ShowSigns(viewOnly); } private void ShowSigns(bool viewOnly) { bool debug = false; #if DEBUG debug = !viewOnly && GlobalConfig.Instance.Debug.Switches[11]; #endif NetManager netManager = Singleton.instance; var camPos = Singleton.instance.m_simulationView.m_position; if (!viewOnly && SelectedNodeId != 0) { currentRestrictedNodeIds.Add(SelectedNodeId); } ushort updatedNodeId = 0; bool handleHovered = false; bool cursorInPanel = this.IsCursorInPanel(); foreach (ushort nodeId in currentRestrictedNodeIds) { if (!Constants.ServiceFactory.NetService.IsNodeValid(nodeId)) { continue; } Vector3 nodePos = netManager.m_nodes.m_buffer[nodeId].m_position; var diff = nodePos - camPos; if (diff.magnitude > TrafficManagerTool.MaxOverlayDistance) continue; // do not draw if too distant Vector3 screenPos; bool visible = MainTool.WorldToScreenPoint(nodePos, out screenPos); if (! visible) continue; bool viewOnlyNode = true; if (!viewOnly && nodeId == SelectedNodeId) viewOnlyNode = false; // draw junction restrictions bool update; if (drawSignHandles(debug, nodeId, ref netManager.m_nodes.m_buffer[nodeId], viewOnlyNode, !cursorInPanel, ref camPos, out update)) handleHovered = true; if (update) { updatedNodeId = nodeId; } } overlayHandleHovered = handleHovered; if (updatedNodeId != 0) { RefreshCurrentRestrictedNodeIds(updatedNodeId); } } public override void OnPrimaryClickOverlay() { bool debug = false; #if DEBUG debug = GlobalConfig.Instance.Debug.Switches[11]; #endif if (HoveredNodeId == 0) return; if (overlayHandleHovered) return; if (!debug && (Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_flags & (NetNode.Flags.Junction | NetNode.Flags.Bend)) == NetNode.Flags.None) return; SelectedNodeId = HoveredNodeId; MainTool.CheckClicked(); // prevent accidential activation of signs on node selection (TODO improve this!) } public override void OnSecondaryClickOverlay() { SelectedNodeId = 0; } public override void OnActivate() { #if DEBUGCONN Log._Debug("TppLaneConnectorTool: OnActivate"); #endif SelectedNodeId = 0; RefreshCurrentRestrictedNodeIds(); } public override void Cleanup() { #if DEBUG bool debug = GlobalConfig.Instance.Debug.Switches[11]; #endif foreach (ushort nodeId in currentRestrictedNodeIds) { JunctionRestrictionsManager.Instance.RemoveJunctionRestrictionsIfNecessary(nodeId); } RefreshCurrentRestrictedNodeIds(); } public override void Initialize() { base.Initialize(); Cleanup(); if (Options.junctionRestrictionsOverlay) { RefreshCurrentRestrictedNodeIds(); } else { currentRestrictedNodeIds.Clear(); } } private void RefreshCurrentRestrictedNodeIds(ushort forceNodeId=0) { if (forceNodeId == 0) { currentRestrictedNodeIds.Clear(); } else { currentRestrictedNodeIds.Remove(forceNodeId); } for (uint nodeId = (forceNodeId == 0 ? 1u : forceNodeId); nodeId <= (forceNodeId == 0 ? NetManager.MAX_NODE_COUNT - 1 : forceNodeId); ++nodeId) { if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { continue; } if (JunctionRestrictionsManager.Instance.HasJunctionRestrictions((ushort)nodeId)) { currentRestrictedNodeIds.Add((ushort)nodeId); } } } private bool drawSignHandles(bool debug, ushort nodeId, ref NetNode node, bool viewOnly, bool handleClick, ref Vector3 camPos, out bool stateUpdated) { bool hovered = false; stateUpdated = false; if (viewOnly && !Options.junctionRestrictionsOverlay && MainTool.GetToolMode() != ToolMode.JunctionRestrictions) return false; //NetManager netManager = Singleton.instance; var guiColor = GUI.color; Vector3 nodePos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position; for (int i = 0; i < 8; ++i) { ushort segmentId = node.GetSegment(i); if (segmentId == 0) continue; SegmentGeometry geometry = SegmentGeometry.Get(segmentId); if (geometry == null) { Log.Error($"JunctionRestrictionsTool.drawSignHandles: No geometry information available for segment {segmentId}"); continue; } bool startNode = geometry.StartNodeId() == nodeId; bool incoming = geometry.IsIncoming(startNode); int numSignsPerRow = incoming ? 2 : 1; NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; ItemClass connectionClass = segmentInfo.GetConnectionClass(); if (connectionClass.m_service != ItemClass.Service.Road) continue; // only for road junctions // draw all junction restriction signs Vector3 segmentCenterPos = Singleton.instance.m_segments.m_buffer[segmentId].m_bounds.center; Vector3 yu = (segmentCenterPos - nodePos).normalized; Vector3 xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized; float f = viewOnly ? 6f : 7f; // reserved sign size in game coordinates Vector3 centerStart = nodePos + yu * (viewOnly ? 5f : 14f); Vector3 zero = centerStart - 0.5f * (float)(numSignsPerRow-1) * f * xu; // "top left" if (viewOnly) { if (Constants.ServiceFactory.SimulationService.LeftHandDrive) zero -= xu * 8f; else zero += xu * 8f; } bool signHovered; int x = 0; int y = 0; bool hasSignInPrevRow = false; // draw "lane-changing when going straight allowed" sign at (0; 0) bool allowed = JunctionRestrictionsManager.Instance.IsLaneChangingAllowedWhenGoingStraight(segmentId, startNode); bool configurable = Constants.ManagerFactory.JunctionRestrictionsManager.IsLaneChangingAllowedWhenGoingStraightConfigurable(segmentId, startNode, ref node); if ( debug || (configurable && (!viewOnly || allowed != Constants.ManagerFactory.JunctionRestrictionsManager.GetDefaultLaneChangingAllowedWhenGoingStraight(segmentId, startNode, ref node))) ) { DrawSign(viewOnly, !configurable, ref camPos, ref xu, ref yu, f, ref zero, x, y, guiColor, allowed ? TextureResources.LaneChangeAllowedTexture2D : TextureResources.LaneChangeForbiddenTexture2D, out signHovered); if (signHovered && handleClick) { hovered = true; if (MainTool.CheckClicked()) { JunctionRestrictionsManager.Instance.ToggleLaneChangingAllowedWhenGoingStraight(segmentId, startNode); stateUpdated = true; } } ++x; hasSignInPrevRow = true; } // draw "u-turns allowed" sign at (1; 0) allowed = JunctionRestrictionsManager.Instance.IsUturnAllowed(segmentId, startNode); configurable = Constants.ManagerFactory.JunctionRestrictionsManager.IsUturnAllowedConfigurable(segmentId, startNode, ref node); if ( debug || (configurable && (!viewOnly || allowed != Constants.ManagerFactory.JunctionRestrictionsManager.GetDefaultUturnAllowed(segmentId, startNode, ref node))) ) { DrawSign(viewOnly, !configurable, ref camPos, ref xu, ref yu, f, ref zero, x, y, guiColor, allowed ? TextureResources.UturnAllowedTexture2D : TextureResources.UturnForbiddenTexture2D, out signHovered); if (signHovered && handleClick) { hovered = true; if (MainTool.CheckClicked()) { if (!JunctionRestrictionsManager.Instance.ToggleUturnAllowed(segmentId, startNode)) { // TODO MainTool.ShowTooltip(Translation.GetString("..."), Singleton.instance.m_nodes.m_buffer[nodeId].m_position); } else { stateUpdated = true; } } } x++; hasSignInPrevRow = true; } x = 0; if (hasSignInPrevRow) { ++y; hasSignInPrevRow = false; } // draw "entering blocked junctions allowed" sign at (0; 1) allowed = JunctionRestrictionsManager.Instance.IsEnteringBlockedJunctionAllowed(segmentId, startNode); configurable = Constants.ManagerFactory.JunctionRestrictionsManager.IsEnteringBlockedJunctionAllowedConfigurable(segmentId, startNode, ref node); if ( debug || (configurable && (!viewOnly || allowed != Constants.ManagerFactory.JunctionRestrictionsManager.GetDefaultEnteringBlockedJunctionAllowed(segmentId, startNode, ref node))) ) { DrawSign(viewOnly, !configurable, ref camPos, ref xu, ref yu, f, ref zero, x, y, guiColor, allowed ? TextureResources.EnterBlockedJunctionAllowedTexture2D : TextureResources.EnterBlockedJunctionForbiddenTexture2D, out signHovered); if (signHovered && handleClick) { hovered = true; if (MainTool.CheckClicked()) { JunctionRestrictionsManager.Instance.ToggleEnteringBlockedJunctionAllowed(segmentId, startNode); stateUpdated = true; } } ++x; hasSignInPrevRow = true; } // draw "pedestrian crossing allowed" sign at (1; 1) allowed = JunctionRestrictionsManager.Instance.IsPedestrianCrossingAllowed(segmentId, startNode); configurable = Constants.ManagerFactory.JunctionRestrictionsManager.IsPedestrianCrossingAllowedConfigurable(segmentId, startNode, ref node); if ( debug || (configurable && (!viewOnly || !allowed)) ) { DrawSign(viewOnly, !configurable, ref camPos, ref xu, ref yu, f, ref zero, x, y, guiColor, allowed ? TextureResources.PedestrianCrossingAllowedTexture2D : TextureResources.PedestrianCrossingForbiddenTexture2D, out signHovered); if (signHovered && handleClick) { hovered = true; if (MainTool.CheckClicked()) { JunctionRestrictionsManager.Instance.TogglePedestrianCrossingAllowed(segmentId, startNode); stateUpdated = true; } } x++; hasSignInPrevRow = true; } x = 0; if (hasSignInPrevRow) { ++y; hasSignInPrevRow = false; } if (Options.turnOnRedEnabled) { IJunctionRestrictionsManager junctionRestrictionsManager = Constants.ManagerFactory.JunctionRestrictionsManager; bool lhd = Constants.ServiceFactory.SimulationService.LeftHandDrive; // draw "turn-left-on-red allowed" sign at (2; 0) allowed = junctionRestrictionsManager.IsTurnOnRedAllowed(lhd, segmentId, startNode); configurable = junctionRestrictionsManager.IsTurnOnRedAllowedConfigurable(lhd, segmentId, startNode, ref node); if ( debug || (configurable && (!viewOnly || allowed != junctionRestrictionsManager.GetDefaultTurnOnRedAllowed(lhd, segmentId, startNode, ref node))) ) { DrawSign(viewOnly, !configurable, ref camPos, ref xu, ref yu, f, ref zero, x, y, guiColor, allowed ? TextureResources.LeftOnRedAllowedTexture2D : TextureResources.LeftOnRedForbiddenTexture2D, out signHovered); if (signHovered && handleClick) { hovered = true; if (MainTool.CheckClicked()) { junctionRestrictionsManager.ToggleTurnOnRedAllowed(lhd, segmentId, startNode); stateUpdated = true; } } hasSignInPrevRow = true; } x++; // draw "turn-right-on-red allowed" sign at (2; 1) allowed = junctionRestrictionsManager.IsTurnOnRedAllowed(!lhd, segmentId, startNode); configurable = junctionRestrictionsManager.IsTurnOnRedAllowedConfigurable(!lhd, segmentId, startNode, ref node); if ( debug || (configurable && (!viewOnly || allowed != junctionRestrictionsManager.GetDefaultTurnOnRedAllowed(!lhd, segmentId, startNode, ref node))) ) { DrawSign(viewOnly, !configurable, ref camPos, ref xu, ref yu, f, ref zero, x, y, guiColor, allowed ? TextureResources.RightOnRedAllowedTexture2D : TextureResources.RightOnRedForbiddenTexture2D, out signHovered); if (signHovered && handleClick) { hovered = true; if (MainTool.CheckClicked()) { junctionRestrictionsManager.ToggleTurnOnRedAllowed(!lhd, segmentId, startNode); stateUpdated = true; } } hasSignInPrevRow = true; } x++; } } guiColor.a = 1f; GUI.color = guiColor; return hovered; } private void DrawSign(bool viewOnly, bool small, ref Vector3 camPos, ref Vector3 xu, ref Vector3 yu, float f, ref Vector3 zero, int x, int y, Color guiColor, Texture2D signTexture, out bool hoveredHandle) { Vector3 signCenter = zero + f * (float)x * xu + f * (float)y * yu; // in game coordinates Vector3 signScreenPos; bool visible = MainTool.WorldToScreenPoint(signCenter, out signScreenPos); if (!visible) { hoveredHandle = false; return; } Vector3 diff = signCenter - camPos; var zoom = 1.0f / diff.magnitude * 100f * MainTool.GetBaseZoom(); var size = (small ? 0.75f : 1f) * (viewOnly ? 0.8f : 1f) * junctionRestrictionsSignSize * zoom; var boundingBox = new Rect(signScreenPos.x - size / 2, signScreenPos.y - size / 2, size, size); hoveredHandle = !viewOnly && TrafficManagerTool.IsMouseOver(boundingBox); guiColor.a = MainTool.GetHandleAlpha(hoveredHandle); GUI.color = guiColor; GUI.DrawTexture(boundingBox, signTexture); } } } ================================================ FILE: TLM/TLM/UI/SubTools/LaneArrowTool.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using ColossalFramework.UI; using CSUtil.Commons; using GenericGameBridge.Service; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Custom.AI; using TrafficManager.Geometry; using TrafficManager.Geometry.Impl; using TrafficManager.Manager; using TrafficManager.Manager.Impl; using TrafficManager.State; using TrafficManager.TrafficLight; using TrafficManager.Util; using UnityEngine; namespace TrafficManager.UI.SubTools { public class LaneArrowTool : SubTool { private bool _cursorInSecondaryPanel; public LaneArrowTool(TrafficManagerTool mainTool) : base(mainTool) { } public override bool IsCursorInPanel() { return base.IsCursorInPanel() || _cursorInSecondaryPanel; } public override void OnPrimaryClickOverlay() { if (HoveredNodeId == 0 || HoveredSegmentId == 0) return; var netFlags = Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_flags; if ((netFlags & NetNode.Flags.Junction) == NetNode.Flags.None) return; if (Singleton.instance.m_segments.m_buffer[HoveredSegmentId].m_startNode != HoveredNodeId && Singleton.instance.m_segments.m_buffer[HoveredSegmentId].m_endNode != HoveredNodeId) return; SelectedSegmentId = HoveredSegmentId; SelectedNodeId = HoveredNodeId; } public override void OnSecondaryClickOverlay() { if (!IsCursorInPanel()) { SelectedSegmentId = 0; SelectedNodeId = 0; } } public override void OnToolGUI(Event e) { //base.OnToolGUI(e); _cursorInSecondaryPanel = false; if (SelectedNodeId == 0 || SelectedSegmentId == 0) return; int numDirections; int numLanes = TrafficManagerTool.GetSegmentNumVehicleLanes(SelectedSegmentId, SelectedNodeId, out numDirections, LaneArrowManager.VEHICLE_TYPES); if (numLanes <= 0) { SelectedNodeId = 0; SelectedSegmentId = 0; return; } Vector3 nodePos = Singleton.instance.m_nodes.m_buffer[SelectedNodeId].m_position; Vector3 screenPos; bool visible = MainTool.WorldToScreenPoint(nodePos, out screenPos); if (!visible) return; var camPos = Singleton.instance.m_simulationView.m_position; var diff = nodePos - camPos; if (diff.magnitude > TrafficManagerTool.MaxOverlayDistance) return; // do not draw if too distant int width = numLanes * 128; var windowRect3 = new Rect(screenPos.x - width / 2, screenPos.y - 70, width, 50); GUILayout.Window(250, windowRect3, _guiLaneChangeWindow, "", BorderlessStyle); _cursorInSecondaryPanel = windowRect3.Contains(Event.current.mousePosition); } public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { NetManager netManager = Singleton.instance; //Log._Debug($"LaneArrow Overlay: {HoveredNodeId} {HoveredSegmentId} {SelectedNodeId} {SelectedSegmentId}"); if (!_cursorInSecondaryPanel && HoveredSegmentId != 0 && HoveredNodeId != 0 && (HoveredSegmentId != SelectedSegmentId || HoveredNodeId != SelectedNodeId)) { var nodeFlags = netManager.m_nodes.m_buffer[HoveredNodeId].m_flags; if ((netManager.m_segments.m_buffer[HoveredSegmentId].m_startNode == HoveredNodeId || netManager.m_segments.m_buffer[HoveredSegmentId].m_endNode == HoveredNodeId) && (nodeFlags & NetNode.Flags.Junction) != NetNode.Flags.None) { NetTool.RenderOverlay(cameraInfo, ref Singleton.instance.m_segments.m_buffer[HoveredSegmentId], MainTool.GetToolColor(false, false), MainTool.GetToolColor(false, false)); } } if (SelectedSegmentId == 0) return; NetTool.RenderOverlay(cameraInfo, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], MainTool.GetToolColor(true, false), MainTool.GetToolColor(true, false)); } private void _guiLaneChangeWindow(int num) { var info = Singleton.instance.m_segments.m_buffer[SelectedSegmentId].Info; IList laneList = Constants.ServiceFactory.NetService.GetSortedLanes(SelectedSegmentId, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], Singleton.instance.m_segments.m_buffer[SelectedSegmentId].m_startNode == SelectedNodeId, LaneArrowManager.LANE_TYPES, LaneArrowManager.VEHICLE_TYPES, true); SegmentGeometry geometry = SegmentGeometry.Get(SelectedSegmentId); if (geometry == null) { Log.Error($"LaneArrowTool._guiLaneChangeWindow: No geometry information available for segment {SelectedSegmentId}"); return; } bool startNode = geometry.StartNodeId() == SelectedNodeId; GUILayout.BeginHorizontal(); for (var i = 0; i < laneList.Count; i++) { var flags = (NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneList[i].laneId].m_flags; var style1 = new GUIStyle("button"); var style2 = new GUIStyle("button") { normal = { textColor = new Color32(255, 0, 0, 255) }, hover = { textColor = new Color32(255, 0, 0, 255) }, focused = { textColor = new Color32(255, 0, 0, 255) } }; var laneStyle = new GUIStyle { contentOffset = new Vector2(12f, 0f) }; var laneTitleStyle = new GUIStyle { contentOffset = new Vector2(36f, 2f), normal = { textColor = new Color(1f, 1f, 1f) } }; GUILayout.BeginVertical(laneStyle); GUILayout.Label(Translation.GetString("Lane") + " " + (i + 1), laneTitleStyle); GUILayout.BeginVertical(); GUILayout.BeginHorizontal(); if (!Flags.applyLaneArrowFlags(laneList[i].laneId)) { Flags.removeLaneArrowFlags(laneList[i].laneId); } Flags.LaneArrowChangeResult res = Flags.LaneArrowChangeResult.Invalid; bool buttonClicked = false; if (GUILayout.Button("←", ((flags & NetLane.Flags.Left) == NetLane.Flags.Left ? style1 : style2), GUILayout.Width(35), GUILayout.Height(25))) { buttonClicked = true; LaneArrowManager.Instance.ToggleLaneArrows(laneList[i].laneId, startNode, Flags.LaneArrows.Left, out res); } if (GUILayout.Button("↑", ((flags & NetLane.Flags.Forward) == NetLane.Flags.Forward ? style1 : style2), GUILayout.Width(25), GUILayout.Height(35))) { buttonClicked = true; LaneArrowManager.Instance.ToggleLaneArrows(laneList[i].laneId, startNode, Flags.LaneArrows.Forward, out res); } if (GUILayout.Button("→", ((flags & NetLane.Flags.Right) == NetLane.Flags.Right ? style1 : style2), GUILayout.Width(35), GUILayout.Height(25))) { buttonClicked = true; LaneArrowManager.Instance.ToggleLaneArrows(laneList[i].laneId, startNode, Flags.LaneArrows.Right, out res); } if (buttonClicked) { switch (res) { case Flags.LaneArrowChangeResult.Invalid: case Flags.LaneArrowChangeResult.Success: default: break; case Flags.LaneArrowChangeResult.HighwayArrows: MainTool.ShowTooltip(Translation.GetString("Lane_Arrow_Changer_Disabled_Highway")); break; case Flags.LaneArrowChangeResult.LaneConnection: MainTool.ShowTooltip(Translation.GetString("Lane_Arrow_Changer_Disabled_Connection")); break; } } GUILayout.EndHorizontal(); GUILayout.EndVertical(); GUILayout.EndVertical(); } GUILayout.EndHorizontal(); } } } ================================================ FILE: TLM/TLM/UI/SubTools/LaneConnectorTool.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Custom.AI; using TrafficManager.State; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using TrafficManager.Util; using UnityEngine; using TrafficManager.Manager; using CSUtil.Commons; using TrafficManager.Manager.Impl; namespace TrafficManager.UI.SubTools { public class LaneConnectorTool : SubTool { enum MarkerSelectionMode { None, SelectSource, SelectTarget } enum StayInLaneMode { None, Both, Forward, Backward } private static readonly Color DefaultNodeMarkerColor = new Color(1f, 1f, 1f, 0.4f); private NodeLaneMarker selectedMarker = null; private NodeLaneMarker hoveredMarker = null; private Dictionary> currentNodeMarkers; private StayInLaneMode stayInLaneMode = StayInLaneMode.None; //private bool initDone = false; class NodeLaneMarker { internal ushort segmentId; internal ushort nodeId; internal bool startNode; internal Vector3 position; internal Vector3 secondaryPosition; internal bool isSource; internal bool isTarget; internal uint laneId; internal int innerSimilarLaneIndex; internal int segmentIndex; internal float radius = 1f; internal Color color; internal List connectedMarkers = new List(); internal NetInfo.LaneType laneType; internal VehicleInfo.VehicleType vehicleType; } public LaneConnectorTool(TrafficManagerTool mainTool) : base(mainTool) { //Log._Debug($"TppLaneConnectorTool: Constructor called"); currentNodeMarkers = new Dictionary>(); } public override void OnToolGUI(Event e) { //Log._Debug($"TppLaneConnectorTool: OnToolGUI. SelectedNodeId={SelectedNodeId} SelectedSegmentId={SelectedSegmentId} HoveredNodeId={HoveredNodeId} HoveredSegmentId={HoveredSegmentId} IsInsideUI={MainTool.GetToolController().IsInsideUI}"); } public override void RenderInfoOverlay(RenderManager.CameraInfo cameraInfo) { ShowOverlay(true, cameraInfo); } private void ShowOverlay(bool viewOnly, RenderManager.CameraInfo cameraInfo) { if (viewOnly && !Options.connectedLanesOverlay) return; NetManager netManager = Singleton.instance; var camPos = Singleton.instance.m_simulationView.m_position; //Bounds bounds = new Bounds(Vector3.zero, Vector3.one); Ray mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition); for (uint nodeId = 1; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { continue; } // TODO refactor connection class check ItemClass connectionClass = NetManager.instance.m_nodes.m_buffer[nodeId].Info.GetConnectionClass(); if (connectionClass == null || !(connectionClass.m_service == ItemClass.Service.Road || (connectionClass.m_service == ItemClass.Service.PublicTransport && (connectionClass.m_subService == ItemClass.SubService.PublicTransportTrain || connectionClass.m_subService == ItemClass.SubService.PublicTransportMetro || connectionClass.m_subService == ItemClass.SubService.PublicTransportMonorail)))) { continue; } var diff = NetManager.instance.m_nodes.m_buffer[nodeId].m_position - camPos; if (diff.magnitude > TrafficManagerTool.MaxOverlayDistance) continue; // do not draw if too distant List nodeMarkers; bool hasMarkers = currentNodeMarkers.TryGetValue((ushort)nodeId, out nodeMarkers); if (!viewOnly && GetMarkerSelectionMode() == MarkerSelectionMode.None) { MainTool.DrawNodeCircle(cameraInfo, (ushort)nodeId, DefaultNodeMarkerColor, true); } if (hasMarkers) { foreach (NodeLaneMarker laneMarker in nodeMarkers) { if (!Constants.ServiceFactory.NetService.IsLaneValid(laneMarker.laneId)) { continue; } foreach (NodeLaneMarker targetLaneMarker in laneMarker.connectedMarkers) { // render lane connection from laneMarker to targetLaneMarker if (!Constants.ServiceFactory.NetService.IsLaneValid(targetLaneMarker.laneId)) { continue; } RenderLane(cameraInfo, laneMarker.position, targetLaneMarker.position, NetManager.instance.m_nodes.m_buffer[nodeId].m_position, laneMarker.color); } if (!viewOnly && nodeId == SelectedNodeId) { //bounds.center = laneMarker.position; bool markerIsHovered = IsLaneMarkerHovered(laneMarker, ref mouseRay);// bounds.IntersectRay(mouseRay); // draw source marker in source selection mode, // draw target marker (if segment turning angles are within bounds) and selected source marker in target selection mode bool drawMarker = (GetMarkerSelectionMode() == MarkerSelectionMode.SelectSource && laneMarker.isSource) || (GetMarkerSelectionMode() == MarkerSelectionMode.SelectTarget && ( (laneMarker.isTarget && (laneMarker.vehicleType & selectedMarker.vehicleType) != VehicleInfo.VehicleType.None && CheckSegmentsTurningAngle(selectedMarker.segmentId, ref netManager.m_segments.m_buffer[selectedMarker.segmentId], selectedMarker.startNode, laneMarker.segmentId, ref netManager.m_segments.m_buffer[laneMarker.segmentId], laneMarker.startNode) ) || laneMarker == selectedMarker)); // highlight hovered marker and selected marker bool highlightMarker = drawMarker && (laneMarker == selectedMarker || markerIsHovered); if (drawMarker) { if (highlightMarker) { laneMarker.radius = 2f; } else laneMarker.radius = 1f; } else { markerIsHovered = false; } if (markerIsHovered) { /*if (hoveredMarker != sourceLaneMarker) Log._Debug($"Marker @ lane {sourceLaneMarker.laneId} hovered");*/ hoveredMarker = laneMarker; } if (drawMarker) { //DrawLaneMarker(laneMarker, cameraInfo); RenderManager.instance.OverlayEffect.DrawCircle(cameraInfo, laneMarker.color, laneMarker.position, laneMarker.radius, laneMarker.position.y - 100f, laneMarker.position.y + 100f, false, true); } } } } } } private bool IsLaneMarkerHovered(NodeLaneMarker laneMarker, ref Ray mouseRay) { Bounds bounds = new Bounds(Vector3.zero, Vector3.one); bounds.center = laneMarker.position; if (bounds.IntersectRay(mouseRay)) return true; bounds = new Bounds(Vector3.zero, Vector3.one); bounds.center = laneMarker.secondaryPosition; return bounds.IntersectRay(mouseRay); } public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { //Log._Debug($"TppLaneConnectorTool: RenderOverlay. SelectedNodeId={SelectedNodeId} SelectedSegmentId={SelectedSegmentId} HoveredNodeId={HoveredNodeId} HoveredSegmentId={HoveredSegmentId} IsInsideUI={MainTool.GetToolController().IsInsideUI}"); // draw lane markers and connections hoveredMarker = null; ShowOverlay(false, cameraInfo); // draw bezier from source marker to mouse position in target marker selection if (SelectedNodeId != 0) { if (GetMarkerSelectionMode() == MarkerSelectionMode.SelectTarget) { Vector3 selNodePos = NetManager.instance.m_nodes.m_buffer[SelectedNodeId].m_position; ToolBase.RaycastOutput output; if (RayCastSegmentAndNode(out output)) { RenderLane(cameraInfo, selectedMarker.position, output.m_hitPos, selNodePos, selectedMarker.color); } } bool deleteAll = Input.GetKeyDown(KeyCode.Delete) || Input.GetKeyDown(KeyCode.Backspace); bool stayInLane = Input.GetKeyDown(KeyCode.S) && (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) && Singleton.instance.m_nodes.m_buffer[SelectedNodeId].CountSegments() == 2; if (stayInLane) deleteAll = true; if (deleteAll) { // remove all connections at selected node LaneConnectionManager.Instance.RemoveLaneConnectionsFromNode(SelectedNodeId); RefreshCurrentNodeMarkers(SelectedNodeId); } if (stayInLane) { // "stay in lane" switch (stayInLaneMode) { case StayInLaneMode.None: stayInLaneMode = StayInLaneMode.Both; break; case StayInLaneMode.Both: stayInLaneMode = StayInLaneMode.Forward; break; case StayInLaneMode.Forward: stayInLaneMode = StayInLaneMode.Backward; break; case StayInLaneMode.Backward: stayInLaneMode = StayInLaneMode.None; break; } if (stayInLaneMode != StayInLaneMode.None) { List nodeMarkers = GetNodeMarkers(SelectedNodeId, ref Singleton.instance.m_nodes.m_buffer[SelectedNodeId]); if (nodeMarkers != null) { selectedMarker = null; foreach (NodeLaneMarker sourceLaneMarker in nodeMarkers) { if (!sourceLaneMarker.isSource) continue; if (stayInLaneMode == StayInLaneMode.Forward || stayInLaneMode == StayInLaneMode.Backward) { if (sourceLaneMarker.segmentIndex == 0 ^ stayInLaneMode == StayInLaneMode.Backward) { continue; } } foreach (NodeLaneMarker targetLaneMarker in nodeMarkers) { if (!targetLaneMarker.isTarget || targetLaneMarker.segmentId == sourceLaneMarker.segmentId) continue; if (targetLaneMarker.innerSimilarLaneIndex == sourceLaneMarker.innerSimilarLaneIndex) { Log._Debug($"Adding lane connection {sourceLaneMarker.laneId} -> {targetLaneMarker.laneId}"); LaneConnectionManager.Instance.AddLaneConnection(sourceLaneMarker.laneId, targetLaneMarker.laneId, sourceLaneMarker.startNode); } } } } RefreshCurrentNodeMarkers(SelectedNodeId); } } } if (GetMarkerSelectionMode() == MarkerSelectionMode.None && HoveredNodeId != 0) { // draw hovered node MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0)); } } public override void OnPrimaryClickOverlay() { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug($"TppLaneConnectorTool: OnPrimaryClickOverlay. SelectedNodeId={SelectedNodeId} SelectedSegmentId={SelectedSegmentId} HoveredNodeId={HoveredNodeId} HoveredSegmentId={HoveredSegmentId}"); #endif if (IsCursorInPanel()) return; if (GetMarkerSelectionMode() == MarkerSelectionMode.None) { if (HoveredNodeId != 0) { #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: HoveredNode != 0"); #endif if (NetManager.instance.m_nodes.m_buffer[HoveredNodeId].CountSegments() < 2) { // this node cannot be configured (dead end) #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: Node is a dead end"); #endif SelectedNodeId = 0; selectedMarker = null; stayInLaneMode = StayInLaneMode.None; return; } if (SelectedNodeId != HoveredNodeId) { #if DEBUGCONN if (debug) Log._Debug($"Node {HoveredNodeId} has been selected. Creating markers."); #endif // selected node has changed. create markers List markers = GetNodeMarkers(HoveredNodeId, ref Singleton.instance.m_nodes.m_buffer[HoveredNodeId]); if (markers != null) { SelectedNodeId = HoveredNodeId; selectedMarker = null; stayInLaneMode = StayInLaneMode.None; currentNodeMarkers[SelectedNodeId] = markers; } //this.allNodeMarkers[SelectedNodeId] = GetNodeMarkers(SelectedNodeId); } } else { #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: Node {SelectedNodeId} has been deselected."); #endif // click on free spot. deselect node SelectedNodeId = 0; selectedMarker = null; stayInLaneMode = StayInLaneMode.None; return; } } if (hoveredMarker != null) { stayInLaneMode = StayInLaneMode.None; #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: hoveredMarker != null. selMode={GetMarkerSelectionMode()}"); #endif // hovered marker has been clicked if (GetMarkerSelectionMode() == MarkerSelectionMode.SelectSource) { // select source marker selectedMarker = hoveredMarker; #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: set selected marker"); #endif } else if (GetMarkerSelectionMode() == MarkerSelectionMode.SelectTarget) { // select target marker //bool success = false; if (LaneConnectionManager.Instance.RemoveLaneConnection(selectedMarker.laneId, hoveredMarker.laneId, selectedMarker.startNode)) { // try to remove connection selectedMarker.connectedMarkers.Remove(hoveredMarker); #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: removed lane connection: {selectedMarker.laneId}, {hoveredMarker.laneId}"); #endif //success = true; } else if (LaneConnectionManager.Instance.AddLaneConnection(selectedMarker.laneId, hoveredMarker.laneId, selectedMarker.startNode)) { // try to add connection selectedMarker.connectedMarkers.Add(hoveredMarker); #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: added lane connection: {selectedMarker.laneId}, {hoveredMarker.laneId}"); #endif //success = true; } /*if (success) { // connection has been modified. switch back to source marker selection Log._Debug($"TppLaneConnectorTool: switch back to source marker selection"); selectedMarker = null; selMode = MarkerSelectionMode.SelectSource; }*/ } } } public override void OnSecondaryClickOverlay() { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; #endif if (IsCursorInPanel()) return; switch (GetMarkerSelectionMode()) { case MarkerSelectionMode.None: default: #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: OnSecondaryClickOverlay: nothing to do"); #endif stayInLaneMode = StayInLaneMode.None; break; case MarkerSelectionMode.SelectSource: // deselect node #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: OnSecondaryClickOverlay: selected node id = 0"); #endif SelectedNodeId = 0; break; case MarkerSelectionMode.SelectTarget: // deselect source marker #if DEBUGCONN if (debug) Log._Debug($"TppLaneConnectorTool: OnSecondaryClickOverlay: switch to selected source mode"); #endif selectedMarker = null; break; } } public override void OnActivate() { #if DEBUGCONN bool debug = GlobalConfig.Instance.Debug.Switches[23]; if (debug) Log._Debug("TppLaneConnectorTool: OnActivate"); #endif SelectedNodeId = 0; selectedMarker = null; hoveredMarker = null; stayInLaneMode = StayInLaneMode.None; RefreshCurrentNodeMarkers(); } private void RefreshCurrentNodeMarkers(ushort forceNodeId=0) { if (forceNodeId == 0) { currentNodeMarkers.Clear(); } else { currentNodeMarkers.Remove(forceNodeId); } for (uint nodeId = (forceNodeId == 0 ? 1u : forceNodeId); nodeId <= (forceNodeId == 0 ? NetManager.MAX_NODE_COUNT-1 : forceNodeId); ++nodeId) { if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { continue; } if (! LaneConnectionManager.Instance.HasNodeConnections((ushort)nodeId)) { continue; } List nodeMarkers = GetNodeMarkers((ushort)nodeId, ref Singleton.instance.m_nodes.m_buffer[nodeId]); if (nodeMarkers == null) continue; currentNodeMarkers[(ushort)nodeId] = nodeMarkers; } } private MarkerSelectionMode GetMarkerSelectionMode() { if (SelectedNodeId == 0) return MarkerSelectionMode.None; if (selectedMarker == null) return MarkerSelectionMode.SelectSource; return MarkerSelectionMode.SelectTarget; } public override void Cleanup() { } public override void Initialize() { base.Initialize(); Cleanup(); if (Options.connectedLanesOverlay) { RefreshCurrentNodeMarkers(); } else { currentNodeMarkers.Clear(); } } private List GetNodeMarkers(ushort nodeId, ref NetNode node) { if (nodeId == 0) return null; if ((node.m_flags & NetNode.Flags.Created) == NetNode.Flags.None) return null; List nodeMarkers = new List(); LaneConnectionManager connManager = LaneConnectionManager.Instance; int offsetMultiplier = node.CountSegments() <= 2 ? 3 : 1; for (int i = 0; i < 8; i++) { ushort segmentId = node.GetSegment(i); if (segmentId == 0) continue; bool isEndNode = NetManager.instance.m_segments.m_buffer[segmentId].m_endNode == nodeId; Vector3 offset = NetManager.instance.m_segments.m_buffer[segmentId].FindDirection(segmentId, nodeId) * offsetMultiplier; NetInfo.Lane[] lanes = NetManager.instance.m_segments.m_buffer[segmentId].Info.m_lanes; uint laneId = NetManager.instance.m_segments.m_buffer[segmentId].m_lanes; for (byte laneIndex = 0; laneIndex < lanes.Length && laneId != 0; laneIndex++) { NetInfo.Lane laneInfo = lanes[laneIndex]; if ((laneInfo.m_laneType & LaneConnectionManager.LANE_TYPES) != NetInfo.LaneType.None && (laneInfo.m_vehicleType & LaneConnectionManager.VEHICLE_TYPES) != VehicleInfo.VehicleType.None) { Vector3? pos = null; bool isSource = false; bool isTarget = false; if (connManager.GetLaneEndPoint(segmentId, !isEndNode, laneIndex, laneId, laneInfo, out isSource, out isTarget, out pos)) { pos = (Vector3)pos + offset; float terrainY = Singleton.instance.SampleDetailHeightSmooth(((Vector3)pos)); Vector3 finalPos = new Vector3(((Vector3)pos).x, terrainY, ((Vector3)pos).z); nodeMarkers.Add(new NodeLaneMarker() { segmentId = segmentId, laneId = laneId, nodeId = nodeId, startNode = !isEndNode, position = finalPos, secondaryPosition = (Vector3)pos, color = colors[nodeMarkers.Count % colors.Length], isSource = isSource, isTarget = isTarget, laneType = laneInfo.m_laneType, vehicleType = laneInfo.m_vehicleType, innerSimilarLaneIndex = ((byte)(laneInfo.m_direction & NetInfo.Direction.Forward) != 0) ? laneInfo.m_similarLaneIndex : laneInfo.m_similarLaneCount - laneInfo.m_similarLaneIndex - 1, segmentIndex = i }); } } laneId = NetManager.instance.m_lanes.m_buffer[laneId].m_nextLane; } } if (nodeMarkers.Count == 0) return null; foreach (NodeLaneMarker laneMarker1 in nodeMarkers) { if (!laneMarker1.isSource) continue; uint[] connections = LaneConnectionManager.Instance.GetLaneConnections(laneMarker1.laneId, laneMarker1.startNode); if (connections == null || connections.Length == 0) continue; foreach (NodeLaneMarker laneMarker2 in nodeMarkers) { if (!laneMarker2.isTarget) continue; if (connections.Contains(laneMarker2.laneId)) laneMarker1.connectedMarkers.Add(laneMarker2); } } return nodeMarkers; } /// /// Checks if the turning angle between two segments at the given node is within bounds. /// /// /// /// /// /// /// /// private bool CheckSegmentsTurningAngle(ushort sourceSegmentId, ref NetSegment sourceSegment, bool sourceStartNode, ushort targetSegmentId, ref NetSegment targetSegment, bool targetStartNode) { NetManager netManager = Singleton.instance; NetInfo sourceSegmentInfo = netManager.m_segments.m_buffer[sourceSegmentId].Info; NetInfo targetSegmentInfo = netManager.m_segments.m_buffer[targetSegmentId].Info; float turningAngle = 0.01f - Mathf.Min(sourceSegmentInfo.m_maxTurnAngleCos, targetSegmentInfo.m_maxTurnAngleCos); if (turningAngle < 1f) { Vector3 sourceDirection; if (sourceStartNode) { sourceDirection = sourceSegment.m_startDirection; } else { sourceDirection = sourceSegment.m_endDirection; } Vector3 targetDirection; if (targetStartNode) { targetDirection = targetSegment.m_startDirection; } else { targetDirection = targetSegment.m_endDirection; } float dirDotProd = sourceDirection.x * targetDirection.x + sourceDirection.z * targetDirection.z; return dirDotProd < turningAngle; } return true; } private void RenderLane(RenderManager.CameraInfo cameraInfo, Vector3 start, Vector3 end, Vector3 middlePoint, Color color, float size = 0.1f) { Bezier3 bezier; bezier.a = start; bezier.d = end; NetSegment.CalculateMiddlePoints(bezier.a, (middlePoint - bezier.a).normalized, bezier.d, (middlePoint - bezier.d).normalized, false, false, out bezier.b, out bezier.c); RenderManager.instance.OverlayEffect.DrawBezier(cameraInfo, color, bezier, size, 0, 0, -1f, 1280f, false, true); } private bool RayCastSegmentAndNode(out ToolBase.RaycastOutput output) { ToolBase.RaycastInput input = new ToolBase.RaycastInput(Camera.main.ScreenPointToRay(Input.mousePosition), Camera.main.farClipPlane); input.m_netService.m_service = ItemClass.Service.Road; input.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; input.m_ignoreSegmentFlags = NetSegment.Flags.None; input.m_ignoreNodeFlags = NetNode.Flags.None; input.m_ignoreTerrain = true; return MainTool.DoRayCast(input, out output); } private static readonly Color32[] colors = new Color32[] { new Color32(161, 64, 206, 255), new Color32(79, 251, 8, 255), new Color32(243, 96, 44, 255), new Color32(45, 106, 105, 255), new Color32(253, 165, 187, 255), new Color32(90, 131, 14, 255), new Color32(58, 20, 70, 255), new Color32(248, 246, 183, 255), new Color32(255, 205, 29, 255), new Color32(91, 50, 18, 255), new Color32(76, 239, 155, 255), new Color32(241, 25, 130, 255), new Color32(125, 197, 240, 255), new Color32(57, 102, 187, 255), new Color32(160, 27, 61, 255), new Color32(167, 251, 107, 255), new Color32(165, 94, 3, 255), new Color32(204, 18, 161, 255), new Color32(208, 136, 237, 255), new Color32(232, 211, 202, 255), new Color32(45, 182, 15, 255), new Color32(8, 40, 47, 255), new Color32(249, 172, 142, 255), new Color32(248, 99, 101, 255), new Color32(180, 250, 208, 255), new Color32(126, 25, 77, 255), new Color32(243, 170, 55, 255), new Color32(47, 69, 126, 255), new Color32(50, 105, 70, 255), new Color32(156, 49, 1, 255), new Color32(233, 231, 255, 255), new Color32(107, 146, 253, 255), new Color32(127, 35, 26, 255), new Color32(240, 94, 222, 255), new Color32(58, 28, 24, 255), new Color32(165, 179, 240, 255), new Color32(239, 93, 145, 255), new Color32(47, 110, 138, 255), new Color32(57, 195, 101, 255), new Color32(124, 88, 213, 255), new Color32(252, 220, 144, 255), new Color32(48, 106, 224, 255), new Color32(90, 109, 28, 255), new Color32(56, 179, 208, 255), new Color32(239, 73, 177, 255), new Color32(84, 60, 2, 255), new Color32(169, 104, 238, 255), new Color32(97, 201, 238, 255), }; } } ================================================ FILE: TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Custom.AI; using TrafficManager.State; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using UnityEngine; using TrafficManager.Manager; using TrafficManager.Traffic; using TrafficManager.Manager.Impl; using TrafficManager.Geometry.Impl; using ColossalFramework.UI; namespace TrafficManager.UI.SubTools { public class ManualTrafficLightsTool : SubTool { private readonly int[] _hoveredButton = new int[2]; private readonly GUIStyle _counterStyle = new GUIStyle(); public ManualTrafficLightsTool(TrafficManagerTool mainTool) : base(mainTool) { } public override void OnSecondaryClickOverlay() { if (IsCursorInPanel()) return; Cleanup(); SelectedNodeId = 0; } public override void OnPrimaryClickOverlay() { if (IsCursorInPanel()) return; if (SelectedNodeId != 0) return; TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; TrafficPriorityManager prioMan = TrafficPriorityManager.Instance; if (!tlsMan.TrafficLightSimulations[HoveredNodeId].IsTimedLight()) { if ((Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_flags & NetNode.Flags.TrafficLights) == NetNode.Flags.None) { prioMan.RemovePrioritySignsFromNode(HoveredNodeId); TrafficLightManager.Instance.AddTrafficLight(HoveredNodeId, ref Singleton.instance.m_nodes.m_buffer[HoveredNodeId]); } if (tlsMan.SetUpManualTrafficLight(HoveredNodeId)) { SelectedNodeId = HoveredNodeId; } /*for (var s = 0; s < 8; s++) { var segment = Singleton.instance.m_nodes.m_buffer[SelectedNodeId].GetSegment(s); if (segment != 0 && !TrafficPriority.IsPrioritySegment(SelectedNodeId, segment)) { TrafficPriority.AddPrioritySegment(SelectedNodeId, segment, SegmentEnd.PriorityType.None); } }*/ } else { MainTool.ShowTooltip(Translation.GetString("NODE_IS_TIMED_LIGHT")); } } public override void OnToolGUI(Event e) { var hoveredSegment = false; if (SelectedNodeId != 0) { CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; JunctionRestrictionsManager junctionRestrictionsManager = JunctionRestrictionsManager.Instance; if (!tlsMan.HasManualSimulation(SelectedNodeId)) { return; } tlsMan.TrafficLightSimulations[SelectedNodeId].Housekeeping(); /*if (Singleton.instance.m_nodes.m_buffer[SelectedNode].CountSegments() == 2) { _guiManualTrafficLightsCrosswalk(ref Singleton.instance.m_nodes.m_buffer[SelectedNode]); return; }*/ // TODO check NodeGeometry nodeGeometry = NodeGeometry.Get(SelectedNodeId); foreach (SegmentEndGeometry end in nodeGeometry.SegmentEndGeometries) { if (end == null) continue; var position = CalculateNodePositionForSegment(Singleton.instance.m_nodes.m_buffer[SelectedNodeId], ref Singleton.instance.m_segments.m_buffer[end.SegmentId]); var segmentLights = customTrafficLightsManager.GetSegmentLights(end.SegmentId, end.StartNode, false); if (segmentLights == null) continue; bool showPedLight = segmentLights.PedestrianLightState != null && junctionRestrictionsManager.IsPedestrianCrossingAllowed(segmentLights.SegmentId, segmentLights.StartNode); Vector3 screenPos; bool visible = MainTool.WorldToScreenPoint(position, out screenPos); if (!visible) continue; var diff = position - Camera.main.transform.position; var zoom = 1.0f / diff.magnitude * 100f; // original / 2.5 var lightWidth = 41f * zoom; var lightHeight = 97f * zoom; var pedestrianWidth = 36f * zoom; var pedestrianHeight = 61f * zoom; // SWITCH MODE BUTTON var modeWidth = 41f * zoom; var modeHeight = 38f * zoom; var guiColor = GUI.color; if (showPedLight) { // pedestrian light // SWITCH MANUAL PEDESTRIAN LIGHT BUTTON hoveredSegment = RenderManualPedestrianLightSwitch(zoom, end.SegmentId, screenPos, lightWidth, segmentLights, hoveredSegment); // SWITCH PEDESTRIAN LIGHT guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == end.SegmentId && _hoveredButton[1] == 2 && segmentLights.ManualPedestrianMode); GUI.color = guiColor; var myRect3 = new Rect(screenPos.x - pedestrianWidth / 2 - lightWidth + 5f * zoom, screenPos.y - pedestrianHeight / 2 + 22f * zoom, pedestrianWidth, pedestrianHeight); switch (segmentLights.PedestrianLightState) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect3, TextureResources.PedestrianGreenLightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: default: GUI.DrawTexture(myRect3, TextureResources.PedestrianRedLightTexture2D); break; } hoveredSegment = IsPedestrianLightHovered(myRect3, end.SegmentId, hoveredSegment, segmentLights); } int lightOffset = -1; foreach (ExtVehicleType vehicleType in segmentLights.VehicleTypes) { ++lightOffset; ICustomSegmentLight segmentLight = segmentLights.GetCustomLight(vehicleType); Vector3 offsetScreenPos = screenPos; offsetScreenPos.y -= (lightHeight + 10f * zoom) * lightOffset; SetAlpha(end.SegmentId, -1); var myRect1 = new Rect(offsetScreenPos.x - modeWidth / 2, offsetScreenPos.y - modeHeight / 2 + modeHeight - 7f * zoom, modeWidth, modeHeight); GUI.DrawTexture(myRect1, TextureResources.LightModeTexture2D); hoveredSegment = GetHoveredSegment(myRect1, end.SegmentId, hoveredSegment, segmentLight); // COUNTER hoveredSegment = RenderCounter(end.SegmentId, offsetScreenPos, modeWidth, modeHeight, zoom, segmentLights, hoveredSegment); if (vehicleType != ExtVehicleType.None) { // Info sign var infoWidth = 56.125f * zoom; var infoHeight = 51.375f * zoom; int numInfos = 0; for (int k = 0; k < TrafficManagerTool.InfoSignsToDisplay.Length; ++k) { if ((TrafficManagerTool.InfoSignsToDisplay[k] & vehicleType) == ExtVehicleType.None) continue; var infoRect = new Rect(offsetScreenPos.x + modeWidth / 2f + 7f * zoom * (float)(numInfos + 1) + infoWidth * (float)numInfos, offsetScreenPos.y - infoHeight / 2f, infoWidth, infoHeight); guiColor.a = MainTool.GetHandleAlpha(false); GUI.DrawTexture(infoRect, TextureResources.VehicleInfoSignTextures[TrafficManagerTool.InfoSignsToDisplay[k]]); ++numInfos; } } if (end.OutgoingOneWay) continue; var hasLeftSegment = end.NumLeftSegments > 0; var hasForwardSegment = end.NumStraightSegments > 0; var hasRightSegment = end.NumRightSegments > 0; switch (segmentLight.CurrentMode) { case LightMode.Simple: hoveredSegment = SimpleManualSegmentLightMode(end.SegmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); break; case LightMode.SingleLeft: hoveredSegment = LeftForwardRManualSegmentLightMode(hasLeftSegment, end.SegmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment, hasForwardSegment, hasRightSegment); break; case LightMode.SingleRight: hoveredSegment = RightForwardLSegmentLightMode(end.SegmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, hasForwardSegment, hasLeftSegment, segmentLight, hasRightSegment, hoveredSegment); break; default: // left arrow light if (hasLeftSegment) hoveredSegment = LeftArrowLightMode(end.SegmentId, lightWidth, hasRightSegment, hasForwardSegment, offsetScreenPos, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); // forward arrow light if (hasForwardSegment) hoveredSegment = ForwardArrowLightMode(end.SegmentId, lightWidth, hasRightSegment, offsetScreenPos, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); // right arrow light if (hasRightSegment) hoveredSegment = RightArrowLightMode(end.SegmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); break; } } } } if (hoveredSegment) return; _hoveredButton[0] = 0; _hoveredButton[1] = 0; } public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { if (SelectedNodeId != 0) { RenderManualNodeOverlays(cameraInfo); } else { RenderManualSelectionOverlay(cameraInfo); } } private bool RenderManualPedestrianLightSwitch(float zoom, int segmentId, Vector3 screenPos, float lightWidth, ICustomSegmentLights segmentLights, bool hoveredSegment) { if (segmentLights.PedestrianLightState == null) return false; var guiColor = GUI.color; var manualPedestrianWidth = 36f * zoom; var manualPedestrianHeight = 35f * zoom; guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == segmentId && (_hoveredButton[1] == 1 || _hoveredButton[1] == 2)); GUI.color = guiColor; var myRect2 = new Rect(screenPos.x - manualPedestrianWidth / 2 - lightWidth + 5f * zoom, screenPos.y - manualPedestrianHeight / 2 - 9f * zoom, manualPedestrianWidth, manualPedestrianHeight); GUI.DrawTexture(myRect2, segmentLights.ManualPedestrianMode ? TextureResources.PedestrianModeManualTexture2D : TextureResources.PedestrianModeAutomaticTexture2D); if (!myRect2.Contains(Event.current.mousePosition)) return hoveredSegment; _hoveredButton[0] = segmentId; _hoveredButton[1] = 1; if (!MainTool.CheckClicked()) return true; segmentLights.ManualPedestrianMode = !segmentLights.ManualPedestrianMode; return true; } private bool IsPedestrianLightHovered(Rect myRect3, int segmentId, bool hoveredSegment, ICustomSegmentLights segmentLights) { if (!myRect3.Contains(Event.current.mousePosition)) return hoveredSegment; if (segmentLights.PedestrianLightState == null) return false; _hoveredButton[0] = segmentId; _hoveredButton[1] = 2; if (!MainTool.CheckClicked()) return true; if (!segmentLights.ManualPedestrianMode) { segmentLights.ManualPedestrianMode = true; } else { segmentLights.ChangeLightPedestrian(); } return true; } private bool GetHoveredSegment(Rect myRect1, int segmentId, bool hoveredSegment, ICustomSegmentLight segmentDict) { if (!myRect1.Contains(Event.current.mousePosition)) return hoveredSegment; //Log.Message("mouse in myRect1"); _hoveredButton[0] = segmentId; _hoveredButton[1] = -1; if (!MainTool.CheckClicked()) return true; segmentDict.ToggleMode(); return true; } private bool RenderCounter(int segmentId, Vector3 screenPos, float modeWidth, float modeHeight, float zoom, ICustomSegmentLights segmentLights, bool hoveredSegment) { SetAlpha(segmentId, 0); var myRectCounter = new Rect(screenPos.x - modeWidth / 2, screenPos.y - modeHeight / 2 - 6f * zoom, modeWidth, modeHeight); GUI.DrawTexture(myRectCounter, TextureResources.LightCounterTexture2D); var counterSize = 20f * zoom; var counter = segmentLights.LastChange(); var myRectCounterNum = new Rect(screenPos.x - counterSize + 15f * zoom + (counter >= 10 ? -5 * zoom : 0f), screenPos.y - counterSize + 11f * zoom, counterSize, counterSize); _counterStyle.fontSize = (int)(18f * zoom); _counterStyle.normal.textColor = new Color(1f, 1f, 1f); GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); if (!myRectCounter.Contains(Event.current.mousePosition)) return hoveredSegment; _hoveredButton[0] = segmentId; _hoveredButton[1] = 0; return true; } private bool SimpleManualSegmentLightMode(int segmentId, Vector3 screenPos, float lightWidth, float pedestrianWidth, float zoom, float lightHeight, ICustomSegmentLight segmentDict, bool hoveredSegment) { SetAlpha(segmentId, 3); var myRect4 = new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, screenPos.y - lightHeight / 2, lightWidth, lightHeight); switch (segmentDict.LightMain) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect4, TextureResources.GreenLightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: GUI.DrawTexture(myRect4, TextureResources.RedLightTexture2D); break; } if (!myRect4.Contains(Event.current.mousePosition)) return hoveredSegment; _hoveredButton[0] = segmentId; _hoveredButton[1] = 3; if (!MainTool.CheckClicked()) return true; segmentDict.ChangeMainLight(); return true; } private bool LeftForwardRManualSegmentLightMode(bool hasLeftSegment, int segmentId, Vector3 screenPos, float lightWidth, float pedestrianWidth, float zoom, float lightHeight, ICustomSegmentLight segmentDict, bool hoveredSegment, bool hasForwardSegment, bool hasRightSegment) { if (hasLeftSegment) { // left arrow light SetAlpha(segmentId, 3); var myRect4 = new Rect(screenPos.x - lightWidth / 2 - lightWidth * 2 - pedestrianWidth + 5f * zoom, screenPos.y - lightHeight / 2, lightWidth, lightHeight); switch (segmentDict.LightLeft) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect4, TextureResources.GreenLightLeftTexture2D); break; case RoadBaseAI.TrafficLightState.Red: GUI.DrawTexture(myRect4, TextureResources.RedLightLeftTexture2D); break; } if (myRect4.Contains(Event.current.mousePosition)) { _hoveredButton[0] = segmentId; _hoveredButton[1] = 3; hoveredSegment = true; if (MainTool.CheckClicked()) { segmentDict.ChangeLeftLight(); } } } // forward-right arrow light SetAlpha(segmentId, 4); var myRect5 = new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, screenPos.y - lightHeight / 2, lightWidth, lightHeight); if (hasForwardSegment && hasRightSegment) { switch (segmentDict.LightMain) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect5, TextureResources.GreenLightForwardRightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: GUI.DrawTexture(myRect5, TextureResources.RedLightForwardRightTexture2D); break; } } else if (!hasRightSegment) { switch (segmentDict.LightMain) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect5, TextureResources.GreenLightStraightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: GUI.DrawTexture(myRect5, TextureResources.RedLightStraightTexture2D); break; } } else { switch (segmentDict.LightMain) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect5, TextureResources.GreenLightRightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: GUI.DrawTexture(myRect5, TextureResources.RedLightRightTexture2D); break; } } if (!myRect5.Contains(Event.current.mousePosition)) return hoveredSegment; _hoveredButton[0] = segmentId; _hoveredButton[1] = 4; if (!MainTool.CheckClicked()) return true; segmentDict.ChangeMainLight(); return true; } private bool RightForwardLSegmentLightMode(int segmentId, Vector3 screenPos, float lightWidth, float pedestrianWidth, float zoom, float lightHeight, bool hasForwardSegment, bool hasLeftSegment, ICustomSegmentLight segmentDict, bool hasRightSegment, bool hoveredSegment) { SetAlpha(segmentId, 3); var myRect4 = new Rect(screenPos.x - lightWidth / 2 - lightWidth * 2 - pedestrianWidth + 5f * zoom, screenPos.y - lightHeight / 2, lightWidth, lightHeight); if (hasForwardSegment && hasLeftSegment) { switch (segmentDict.LightLeft) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect4, TextureResources.GreenLightForwardLeftTexture2D); break; case RoadBaseAI.TrafficLightState.Red: GUI.DrawTexture(myRect4, TextureResources.RedLightForwardLeftTexture2D); break; } } else if (!hasLeftSegment) { if (!hasRightSegment) { myRect4 = new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, screenPos.y - lightHeight / 2, lightWidth, lightHeight); } switch (segmentDict.LightMain) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect4, TextureResources.GreenLightStraightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: GUI.DrawTexture(myRect4, TextureResources.RedLightStraightTexture2D); break; } } else { if (!hasRightSegment) { myRect4 = new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, screenPos.y - lightHeight / 2, lightWidth, lightHeight); } switch (segmentDict.LightMain) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect4, TextureResources.GreenLightLeftTexture2D); break; case RoadBaseAI.TrafficLightState.Red: GUI.DrawTexture(myRect4, TextureResources.RedLightLeftTexture2D); break; } } if (myRect4.Contains(Event.current.mousePosition)) { _hoveredButton[0] = segmentId; _hoveredButton[1] = 3; hoveredSegment = true; if (MainTool.CheckClicked()) { segmentDict.ChangeMainLight(); } } var guiColor = GUI.color; // right arrow light if (hasRightSegment) guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == segmentId && _hoveredButton[1] == 4); GUI.color = guiColor; var myRect5 = new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, screenPos.y - lightHeight / 2, lightWidth, lightHeight); switch (segmentDict.LightRight) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect5, TextureResources.GreenLightRightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: GUI.DrawTexture(myRect5, TextureResources.RedLightRightTexture2D); break; } if (!myRect5.Contains(Event.current.mousePosition)) return hoveredSegment; _hoveredButton[0] = segmentId; _hoveredButton[1] = 4; if (!MainTool.CheckClicked()) return true; segmentDict.ChangeRightLight(); return true; } private bool LeftArrowLightMode(int segmentId, float lightWidth, bool hasRightSegment, bool hasForwardSegment, Vector3 screenPos, float pedestrianWidth, float zoom, float lightHeight, ICustomSegmentLight segmentDict, bool hoveredSegment) { SetAlpha(segmentId, 3); var offsetLight = lightWidth; if (hasRightSegment) offsetLight += lightWidth; if (hasForwardSegment) offsetLight += lightWidth; var myRect4 = new Rect(screenPos.x - lightWidth / 2 - offsetLight - pedestrianWidth + 5f * zoom, screenPos.y - lightHeight / 2, lightWidth, lightHeight); switch (segmentDict.LightLeft) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect4, TextureResources.GreenLightLeftTexture2D); break; case RoadBaseAI.TrafficLightState.Red: GUI.DrawTexture(myRect4, TextureResources.RedLightLeftTexture2D); break; } if (!myRect4.Contains(Event.current.mousePosition)) return hoveredSegment; _hoveredButton[0] = segmentId; _hoveredButton[1] = 3; if (!MainTool.CheckClicked()) return true; segmentDict.ChangeLeftLight(); if (!hasForwardSegment) { segmentDict.ChangeMainLight(); } return true; } private bool ForwardArrowLightMode(int segmentId, float lightWidth, bool hasRightSegment, Vector3 screenPos, float pedestrianWidth, float zoom, float lightHeight, ICustomSegmentLight segmentDict, bool hoveredSegment) { SetAlpha(segmentId, 4); var offsetLight = lightWidth; if (hasRightSegment) offsetLight += lightWidth; var myRect6 = new Rect(screenPos.x - lightWidth / 2 - offsetLight - pedestrianWidth + 5f * zoom, screenPos.y - lightHeight / 2, lightWidth, lightHeight); switch (segmentDict.LightMain) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect6, TextureResources.GreenLightStraightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: GUI.DrawTexture(myRect6, TextureResources.RedLightStraightTexture2D); break; } if (!myRect6.Contains(Event.current.mousePosition)) return hoveredSegment; _hoveredButton[0] = segmentId; _hoveredButton[1] = 4; if (!MainTool.CheckClicked()) return true; segmentDict.ChangeMainLight(); return true; } private bool RightArrowLightMode(int segmentId, Vector3 screenPos, float lightWidth, float pedestrianWidth, float zoom, float lightHeight, ICustomSegmentLight segmentDict, bool hoveredSegment) { SetAlpha(segmentId, 5); var myRect5 = new Rect(screenPos.x - lightWidth / 2 - lightWidth - pedestrianWidth + 5f * zoom, screenPos.y - lightHeight / 2, lightWidth, lightHeight); switch (segmentDict.LightRight) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect5, TextureResources.GreenLightRightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: GUI.DrawTexture(myRect5, TextureResources.RedLightRightTexture2D); break; } if (!myRect5.Contains(Event.current.mousePosition)) return hoveredSegment; _hoveredButton[0] = segmentId; _hoveredButton[1] = 5; if (!MainTool.CheckClicked()) return true; segmentDict.ChangeRightLight(); return true; } private Vector3 CalculateNodePositionForSegment(NetNode node, int segmentId) { var position = node.m_position; var segment = Singleton.instance.m_segments.m_buffer[segmentId]; if (segment.m_startNode == SelectedNodeId) { position.x += segment.m_startDirection.x * 10f; position.y += segment.m_startDirection.y * 10f; position.z += segment.m_startDirection.z * 10f; } else { position.x += segment.m_endDirection.x * 10f; position.y += segment.m_endDirection.y * 10f; position.z += segment.m_endDirection.z * 10f; } return position; } private Vector3 CalculateNodePositionForSegment(NetNode node, ref NetSegment segment) { var position = node.m_position; const float offset = 25f; if (segment.m_startNode == SelectedNodeId) { position.x += segment.m_startDirection.x * offset; position.y += segment.m_startDirection.y * offset; position.z += segment.m_startDirection.z * offset; } else { position.x += segment.m_endDirection.x * offset; position.y += segment.m_endDirection.y * offset; position.z += segment.m_endDirection.z * offset; } return position; } private void SetAlpha(int segmentId, int buttonId) { var guiColor = GUI.color; guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == segmentId && _hoveredButton[1] == buttonId); GUI.color = guiColor; } private void RenderManualSelectionOverlay(RenderManager.CameraInfo cameraInfo) { if (HoveredNodeId == 0) return; MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, false, false); /*var segment = Singleton.instance.m_segments.m_buffer[Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_segment0]; //if ((node.m_flags & NetNode.Flags.TrafficLights) == NetNode.Flags.None) return; Bezier3 bezier; bezier.a = Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_position; bezier.d = Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_position; var color = MainTool.GetToolColor(false, false); NetSegment.CalculateMiddlePoints(bezier.a, segment.m_startDirection, bezier.d, segment.m_endDirection, false, false, out bezier.b, out bezier.c); MainTool.DrawOverlayBezier(cameraInfo, bezier, color);*/ } private void RenderManualNodeOverlays(RenderManager.CameraInfo cameraInfo) { if (!TrafficLightSimulationManager.Instance.HasManualSimulation(SelectedNodeId)) { return; } MainTool.DrawNodeCircle(cameraInfo, SelectedNodeId, true, false); } public override void Cleanup() { if (SelectedNodeId == 0) return; TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; if (!tlsMan.HasManualSimulation(SelectedNodeId)) { return; } tlsMan.RemoveNodeFromSimulation(SelectedNodeId, true, false); } } } ================================================ FILE: TLM/TLM/UI/SubTools/ParkingRestrictionsTool.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using ColossalFramework.UI; using CSUtil.Commons; using GenericGameBridge.Service; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Custom.AI; using TrafficManager.Geometry; using TrafficManager.Manager; using TrafficManager.Manager.Impl; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.TrafficLight; using TrafficManager.Util; using UnityEngine; using static ColossalFramework.UI.UITextureAtlas; using static TrafficManager.Util.SegmentLaneTraverser; using static TrafficManager.Util.SegmentTraverser; namespace TrafficManager.UI.SubTools { public class ParkingRestrictionsTool : SubTool { private bool overlayHandleHovered; private Dictionary> segmentCenterByDir = new Dictionary>(); private readonly float signSize = 80f; private HashSet currentlyVisibleSegmentIds; public ParkingRestrictionsTool(TrafficManagerTool mainTool) : base(mainTool) { currentlyVisibleSegmentIds = new HashSet(); } public override void OnActivate() { } public override void OnPrimaryClickOverlay() { } public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { } public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { if (viewOnly && !Options.parkingRestrictionsOverlay) return; overlayHandleHovered = false; ShowSigns(viewOnly); } public override void Cleanup() { segmentCenterByDir.Clear(); currentlyVisibleSegmentIds.Clear(); lastCamPos = null; lastCamRot = null; } private Quaternion? lastCamRot = null; private Vector3? lastCamPos = null; private void ShowSigns(bool viewOnly) { Quaternion camRot = Camera.main.transform.rotation; Vector3 camPos = Camera.main.transform.position; NetManager netManager = Singleton.instance; ParkingRestrictionsManager parkingManager = ParkingRestrictionsManager.Instance; if (lastCamPos == null || lastCamRot == null || !lastCamRot.Equals(camRot) || !lastCamPos.Equals(camPos)) { // cache visible segments currentlyVisibleSegmentIds.Clear(); for (uint segmentId = 1; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { if (!Constants.ServiceFactory.NetService.IsSegmentValid((ushort)segmentId)) { continue; } /*if ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Untouchable) != NetSegment.Flags.None) continue;*/ if ((netManager.m_segments.m_buffer[segmentId].m_bounds.center - camPos).magnitude > TrafficManagerTool.MaxOverlayDistance) continue; // do not draw if too distant Vector3 screenPos; bool visible = MainTool.WorldToScreenPoint(netManager.m_segments.m_buffer[segmentId].m_bounds.center, out screenPos); if (!visible) continue; if (!parkingManager.MayHaveParkingRestriction((ushort)segmentId)) continue; currentlyVisibleSegmentIds.Add((ushort)segmentId); } lastCamPos = camPos; lastCamRot = camRot; } bool handleHovered = false; bool clicked = !viewOnly && MainTool.CheckClicked(); foreach (ushort segmentId in currentlyVisibleSegmentIds) { Vector3 screenPos; bool visible = MainTool.WorldToScreenPoint(netManager.m_segments.m_buffer[segmentId].m_bounds.center, out screenPos); if (!visible) continue; NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; // draw parking restrictions if (MainTool.GetToolMode() != ToolMode.SpeedLimits && (MainTool.GetToolMode() != ToolMode.VehicleRestrictions || segmentId != SelectedSegmentId)) { // no parking restrictions overlay on selected segment when in vehicle restrictions mode if (drawParkingRestrictionHandles((ushort)segmentId, clicked, ref netManager.m_segments.m_buffer[segmentId], viewOnly, ref camPos)) handleHovered = true; } } overlayHandleHovered = handleHovered; } private bool drawParkingRestrictionHandles(ushort segmentId, bool clicked, ref NetSegment segment, bool viewOnly, ref Vector3 camPos) { if (viewOnly && !Options.parkingRestrictionsOverlay) return false; Vector3 center = segment.m_bounds.center; NetManager netManager = Singleton.instance; ParkingRestrictionsManager parkingManager = ParkingRestrictionsManager.Instance; bool hovered = false; // draw parking restriction signs over mean middle points of lane beziers Dictionary segCenter; if (!segmentCenterByDir.TryGetValue(segmentId, out segCenter)) { segCenter = new Dictionary(); segmentCenterByDir.Add(segmentId, segCenter); TrafficManagerTool.CalculateSegmentCenterByDir(segmentId, segCenter); } foreach (KeyValuePair e in segCenter) { bool allowed = parkingManager.IsParkingAllowed(segmentId, e.Key); if (allowed && viewOnly) { continue; } Vector3 screenPos; bool visible = MainTool.WorldToScreenPoint(e.Value, out screenPos); if (!visible) continue; float zoom = 1.0f / (e.Value - camPos).magnitude * 100f * MainTool.GetBaseZoom(); float size = (viewOnly ? 0.8f : 1f) * signSize * zoom; Color guiColor = GUI.color; Rect boundingBox = new Rect(screenPos.x - size / 2, screenPos.y - size / 2, size, size); if (Options.speedLimitsOverlay) { boundingBox.y -= size + 10f; } bool hoveredHandle = !viewOnly && TrafficManagerTool.IsMouseOver(boundingBox); guiColor.a = MainTool.GetHandleAlpha(hoveredHandle); if (hoveredHandle) { // mouse hovering over sign hovered = true; } GUI.color = guiColor; GUI.DrawTexture(boundingBox, TextureResources.ParkingRestrictionTextures[allowed]); if (hoveredHandle && clicked && !IsCursorInPanel()) { if (parkingManager.ToggleParkingAllowed(segmentId, e.Key)) { allowed = !allowed; if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { NetInfo.Direction normDir = e.Key; if ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { normDir = NetInfo.InvertDirection(normDir); } SegmentLaneTraverser.Traverse(segmentId, SegmentTraverser.TraverseDirection.AnyDirection, SegmentTraverser.TraverseSide.AnySide, SegmentLaneTraverser.LaneStopCriterion.LaneCount, SegmentTraverser.SegmentStopCriterion.Junction, ParkingRestrictionsManager.LANE_TYPES, ParkingRestrictionsManager.VEHICLE_TYPES, delegate (SegmentLaneVisitData data) { if (data.segVisitData.initial) { return true; } bool reverse = data.segVisitData.viaStartNode == data.segVisitData.viaInitialStartNode; ushort otherSegmentId = data.segVisitData.curGeo.SegmentId; NetInfo otherSegmentInfo = netManager.m_segments.m_buffer[otherSegmentId].Info; uint laneId = data.curLanePos.laneId; byte laneIndex = data.curLanePos.laneIndex; NetInfo.Lane laneInfo = otherSegmentInfo.m_lanes[laneIndex]; NetInfo.Direction otherNormDir = laneInfo.m_finalDirection; if ((netManager.m_segments.m_buffer[otherSegmentId].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None ^ reverse) { otherNormDir = NetInfo.InvertDirection(otherNormDir); } if (otherNormDir == normDir) { parkingManager.SetParkingAllowed(otherSegmentId, laneInfo.m_finalDirection, allowed); } return true; }); } } } guiColor.a = 1f; GUI.color = guiColor; } return hovered; } } } ================================================ FILE: TLM/TLM/UI/SubTools/PrioritySignsTool.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Custom.AI; using TrafficManager.State; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using UnityEngine; using TrafficManager.Traffic; using TrafficManager.Manager; using TrafficManager.Util; using CSUtil.Commons; using TrafficManager.Geometry.Impl; using TrafficManager.Manager.Impl; using static TrafficManager.Traffic.Data.PrioritySegment; using static TrafficManager.Util.SegmentTraverser; using ColossalFramework.UI; namespace TrafficManager.UI.SubTools { public class PrioritySignsTool : SubTool { public enum PrioritySignsMassEditMode { MainYield = 0, MainStop = 1, YieldMain = 2, StopMain = 3, Delete = 4 } private HashSet currentPriorityNodeIds; private PrioritySignsMassEditMode massEditMode = PrioritySignsMassEditMode.MainYield; public PrioritySignsTool(TrafficManagerTool mainTool) : base(mainTool) { currentPriorityNodeIds = new HashSet(); } public override void OnPrimaryClickOverlay() { if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { if (HoveredSegmentId != 0) { SelectedNodeId = 0; PriorityType primaryPrioType = PriorityType.None; PriorityType secondaryPrioType = PriorityType.None; switch (massEditMode) { case PrioritySignsMassEditMode.MainYield: primaryPrioType = PriorityType.Main; secondaryPrioType = PriorityType.Yield; break; case PrioritySignsMassEditMode.MainStop: primaryPrioType = PriorityType.Main; secondaryPrioType = PriorityType.Stop; break; case PrioritySignsMassEditMode.YieldMain: primaryPrioType = PriorityType.Yield; secondaryPrioType = PriorityType.Main; break; case PrioritySignsMassEditMode.StopMain: primaryPrioType = PriorityType.Stop; secondaryPrioType = PriorityType.Main; break; case PrioritySignsMassEditMode.Delete: default: break; } SegmentTraverser.Traverse(HoveredSegmentId, TraverseDirection.AnyDirection, TraverseSide.Straight, SegmentStopCriterion.None, delegate (SegmentVisitData data) { foreach (bool startNode in Constants.ALL_BOOL) { TrafficPriorityManager.Instance.SetPrioritySign(data.curGeo.SegmentId, startNode, primaryPrioType); foreach (ushort otherSegmentId in data.curGeo.GetConnectedSegments(startNode)) { if (!data.curGeo.IsStraightSegment(otherSegmentId, startNode)) { SegmentGeometry otherGeo = SegmentGeometry.Get(otherSegmentId); if (otherGeo == null) { continue; } TrafficPriorityManager.Instance.SetPrioritySign(otherSegmentId, otherGeo.StartNodeId() == data.curGeo.GetNodeId(startNode), secondaryPrioType); } } } return true; }); // cycle mass edit mode massEditMode = (PrioritySignsMassEditMode)(((int)massEditMode + 1) % Enum.GetValues(typeof(PrioritySignsMassEditMode)).GetLength(0)); // update priority node cache RefreshCurrentPriorityNodeIds(); } return; } if (TrafficPriorityManager.Instance.HasNodePrioritySign(HoveredNodeId)) { return; } if (! MayNodeHavePrioritySigns(HoveredNodeId)) { return; } SelectedNodeId = HoveredNodeId; Log._Debug($"PrioritySignsTool.OnPrimaryClickOverlay: SelectedNodeId={SelectedNodeId}"); // update priority node cache RefreshCurrentPriorityNodeIds(); } public override void OnToolGUI(Event e) { } public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { if (MainTool.GetToolController().IsInsideUI || !Cursor.visible) { return; } if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { // draw hovered segments if (HoveredSegmentId != 0) { Color color = MainTool.GetToolColor(Input.GetMouseButton(0), false); SegmentTraverser.Traverse(HoveredSegmentId, TraverseDirection.AnyDirection, TraverseSide.Straight, SegmentStopCriterion.None, delegate (SegmentVisitData data) { NetTool.RenderOverlay(cameraInfo, ref Singleton.instance.m_segments.m_buffer[data.curGeo.SegmentId], color, color); return true; }); } else { massEditMode = PrioritySignsMassEditMode.MainYield; } return; } massEditMode = PrioritySignsMassEditMode.MainYield; if (HoveredNodeId == SelectedNodeId) { return; } // no highlight for existing priority node in sign mode if (TrafficPriorityManager.Instance.HasNodePrioritySign(HoveredNodeId)) { //Log._Debug($"PrioritySignsTool.RenderOverlay: HasNodePrioritySign({HoveredNodeId})=true"); return; } if (! TrafficPriorityManager.Instance.MayNodeHavePrioritySigns(HoveredNodeId)) { //Log._Debug($"PrioritySignsTool.RenderOverlay: MayNodeHavePrioritySigns({HoveredNodeId})=false"); return; } MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0)); } private void RefreshCurrentPriorityNodeIds() { TrafficPriorityManager tpm = TrafficPriorityManager.Instance; currentPriorityNodeIds.Clear(); for (uint nodeId = 0; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { continue; } if (!tpm.MayNodeHavePrioritySigns((ushort)nodeId)) { continue; } if (!tpm.HasNodePrioritySign((ushort)nodeId) && nodeId != SelectedNodeId) { continue; } /*if (! MainTool.IsNodeWithinViewDistance(nodeId)) { continue; }*/ currentPriorityNodeIds.Add((ushort)nodeId); } //Log._Debug($"PrioritySignsTool.RefreshCurrentPriorityNodeIds: currentPriorityNodeIds={string.Join(", ", currentPriorityNodeIds.Select(x => x.ToString()).ToArray())}"); } public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { if (viewOnly && !Options.prioritySignsOverlay) return; if (UIBase.GetTrafficManagerTool(false)?.GetToolMode() == ToolMode.JunctionRestrictions) return; ShowGUI(viewOnly); } public void ShowGUI(bool viewOnly) { try { TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; TrafficPriorityManager prioMan = TrafficPriorityManager.Instance; TrafficLightManager tlm = TrafficLightManager.Instance; Vector3 camPos = Constants.ServiceFactory.SimulationService.CameraPosition; bool clicked = !viewOnly ? MainTool.CheckClicked() : false; ushort removedNodeId = 0; bool showRemoveButton = false; foreach (ushort nodeId in currentPriorityNodeIds) { if (! Constants.ServiceFactory.NetService.IsNodeValid(nodeId)) { continue; } if (!MainTool.IsNodeWithinViewDistance(nodeId)) { continue; } NodeGeometry nodeGeo = NodeGeometry.Get(nodeId); Vector3 nodePos = default(Vector3); Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { nodePos = node.m_position; return true; }); foreach (SegmentEndGeometry endGeo in nodeGeo.SegmentEndGeometries) { if (endGeo == null) { continue; } if (endGeo.OutgoingOneWay) { continue; } ushort segmentId = endGeo.SegmentId; bool startNode = endGeo.StartNode; // calculate sign position Vector3 signPos = nodePos; Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate (ushort sId, ref NetSegment segment) { signPos += 10f * (startNode ? segment.m_startDirection : segment.m_endDirection); return true; }); Vector3 signScreenPos; if (! MainTool.WorldToScreenPoint(signPos, out signScreenPos)) { continue; } // draw sign and handle input PriorityType sign = prioMan.GetPrioritySign(segmentId, startNode); if (viewOnly && sign == PriorityType.None) { continue; } if (!viewOnly && sign != PriorityType.None) { showRemoveButton = true; } if (MainTool.DrawGenericSquareOverlayTexture(TextureResources.PrioritySignTextures[sign], camPos, signPos, 90f, !viewOnly) && clicked) { PriorityType? newSign = null; switch (sign) { case PriorityType.Main: newSign = PriorityType.Yield; break; case PriorityType.Yield: newSign = PriorityType.Stop; break; case PriorityType.Stop: newSign = PriorityType.Main; break; case PriorityType.None: default: newSign = prioMan.CountPrioritySignsAtNode(nodeId, PriorityType.Main) >= 2 ? PriorityType.Yield : PriorityType.Main; break; } if (newSign != null) { SetPrioritySign(segmentId, startNode, (PriorityType)newSign); } } // draw sign } // foreach segment end if (viewOnly) { continue; } // draw remove button and handle click if (showRemoveButton && MainTool.DrawHoverableSquareOverlayTexture(TextureResources.SignRemoveTexture2D, camPos, nodePos, 90f) && clicked) { prioMan.RemovePrioritySignsFromNode(nodeId); Log._Debug($"PrioritySignsTool.ShowGUI: Removed priority signs from node {nodeId}"); removedNodeId = nodeId; } } // foreach node if (removedNodeId != 0) { currentPriorityNodeIds.Remove(removedNodeId); SelectedNodeId = 0; } } catch (Exception e) { Log.Error(e.ToString()); } } public bool SetPrioritySign(ushort segmentId, bool startNode, PriorityType sign) { SegmentGeometry segGeo = SegmentGeometry.Get(segmentId); if (segGeo == null) { Log.Error($"PrioritySignsTool.SetPrioritySign: No geometry information available for segment {segmentId}"); return false; } ushort nodeId = segGeo.GetNodeId(startNode); // check for restrictions if (!MayNodeHavePrioritySigns(nodeId)) { Log._Debug($"PrioritySignsTool.SetPrioritySign: MayNodeHavePrioritySigns({nodeId})=false"); return false; } bool success = TrafficPriorityManager.Instance.SetPrioritySign(segmentId, startNode, sign); Log._Debug($"PrioritySignsTool.SetPrioritySign: SetPrioritySign({segmentId}, {startNode}, {sign})={success}"); if (success && (sign == PriorityType.Stop || sign == PriorityType.Yield)) { // make all undefined segments a main road Log._Debug($"PrioritySignsTool.SetPrioritySign: flagging remaining segments at node {nodeId} as main road."); NodeGeometry nodeGeo = NodeGeometry.Get(nodeId); foreach (SegmentEndGeometry endGeo in nodeGeo.SegmentEndGeometries) { if (endGeo == null) { continue; } if (endGeo.SegmentId == segmentId) { continue; } if (TrafficPriorityManager.Instance.GetPrioritySign(endGeo.SegmentId, endGeo.StartNode) == PriorityType.None) { Log._Debug($"PrioritySignsTool.SetPrioritySign: setting main priority sign for segment {endGeo.SegmentId} @ {nodeId}"); TrafficPriorityManager.Instance.SetPrioritySign(endGeo.SegmentId, endGeo.StartNode, PriorityType.Main); } } } return success; } public override void Cleanup() { //TrafficPriorityManager prioMan = TrafficPriorityManager.Instance; //foreach (PrioritySegment trafficSegment in prioMan.PrioritySegments) { // try { // trafficSegment?.Instance1?.Reset(); // trafficSegment?.Instance2?.Reset(); // } catch (Exception e) { // Log.Error($"Error occured while performing PrioritySignsTool.Cleanup: {e.ToString()}"); // } //} } public override void OnActivate() { RefreshCurrentPriorityNodeIds(); } public override void Initialize() { base.Initialize(); Cleanup(); if (Options.prioritySignsOverlay) { RefreshCurrentPriorityNodeIds(); } else { currentPriorityNodeIds.Clear(); } } private bool MayNodeHavePrioritySigns(ushort nodeId) { TrafficPriorityManager.UnableReason reason; //Log._Debug($"PrioritySignsTool.MayNodeHavePrioritySigns: Checking if node {nodeId} may have priority signs."); if (!TrafficPriorityManager.Instance.MayNodeHavePrioritySigns(nodeId, out reason)) { //Log._Debug($"PrioritySignsTool.MayNodeHavePrioritySigns: Node {nodeId} does not allow priority signs: {reason}"); if (reason == TrafficPriorityManager.UnableReason.HasTimedLight) { MainTool.ShowTooltip(Translation.GetString("NODE_IS_TIMED_LIGHT")); } return false; } //Log._Debug($"PrioritySignsTool.MayNodeHavePrioritySigns: Node {nodeId} allows priority signs"); return true; } } } ================================================ FILE: TLM/TLM/UI/SubTools/README.md ================================================ # TM:PE -- /UI/SubTool Tools that are selectable through the main menu. ## Classes - **JunctionRestrictionsTool**: Junction restrictions - **LaneArrowTool**: Lane arrow changer - **LaneConnectorTool**: Lane connector - **ManualTrafficLightsTool**: Manual traffic lights - **ParkingRestrictionsTool**: Parking restrictions - **PrioritySignsTool**: Priority signs - **SpeedLimitTool**: Speed limits - **TimedTrafficLightTool**: Timed traffic lights - **ToggleTrafficLightsTool**: Switch traffic lights - **VehicleRestrictionsTool**: Vehicle restrictions ================================================ FILE: TLM/TLM/UI/SubTools/SpeedLimitsTool.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using ColossalFramework.UI; using CSUtil.Commons; using GenericGameBridge.Service; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Custom.AI; using TrafficManager.Geometry; using TrafficManager.Manager; using TrafficManager.Manager.Impl; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.TrafficLight; using TrafficManager.Util; using UnityEngine; using static ColossalFramework.UI.UITextureAtlas; using static TrafficManager.Util.SegmentLaneTraverser; namespace TrafficManager.UI.SubTools { public class SpeedLimitsTool : SubTool { private bool _cursorInSecondaryPanel; private int curSpeedLimitIndex = 0; private bool overlayHandleHovered; private Dictionary> segmentCenterByDir = new Dictionary>(); private readonly float speedLimitSignSize = 80f; private readonly int guiSpeedSignSize = 100; private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 7 * 105, 225)); private Rect defaultsWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 280, 400, 400)); private HashSet currentlyVisibleSegmentIds; private bool defaultsWindowVisible = false; private int currentInfoIndex = -1; private int currentSpeedLimitIndex = -1; private Texture2D RoadTexture { get { if (roadTexture == null) { roadTexture = new Texture2D(guiSpeedSignSize, guiSpeedSignSize); } return roadTexture; } } private Texture2D roadTexture = null; private bool showLimitsPerLane = false; public SpeedLimitsTool(TrafficManagerTool mainTool) : base(mainTool) { currentlyVisibleSegmentIds = new HashSet(); } public override bool IsCursorInPanel() { return base.IsCursorInPanel() || _cursorInSecondaryPanel; } public override void OnActivate() { } public override void OnPrimaryClickOverlay() { } public override void OnToolGUI(Event e) { base.OnToolGUI(e); windowRect = GUILayout.Window(254, windowRect, _guiSpeedLimitsWindow, Translation.GetString("Speed_limits"), WindowStyle); if (defaultsWindowVisible) { defaultsWindowRect = GUILayout.Window(258, defaultsWindowRect, _guiDefaultsWindow, Translation.GetString("Default_speed_limits"), WindowStyle); } _cursorInSecondaryPanel = windowRect.Contains(Event.current.mousePosition) || (defaultsWindowVisible && defaultsWindowRect.Contains(Event.current.mousePosition)); //overlayHandleHovered = false; //ShowSigns(false); } public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { } public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { if (viewOnly && !Options.speedLimitsOverlay) return; overlayHandleHovered = false; ShowSigns(viewOnly); } public override void Cleanup() { segmentCenterByDir.Clear(); currentlyVisibleSegmentIds.Clear(); lastCamPos = null; lastCamRot = null; currentInfoIndex = -1; currentSpeedLimitIndex = -1; } private Quaternion? lastCamRot = null; private Vector3? lastCamPos = null; private void ShowSigns(bool viewOnly) { Quaternion camRot = Camera.main.transform.rotation; Vector3 camPos = Camera.main.transform.position; NetManager netManager = Singleton.instance; SpeedLimitManager speedLimitManager = SpeedLimitManager.Instance; if (lastCamPos == null || lastCamRot == null || !lastCamRot.Equals(camRot) || !lastCamPos.Equals(camPos)) { // cache visible segments currentlyVisibleSegmentIds.Clear(); for (uint segmentId = 1; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) { if (!Constants.ServiceFactory.NetService.IsSegmentValid((ushort)segmentId)) { continue; } /*if ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Untouchable) != NetSegment.Flags.None) continue;*/ if ((netManager.m_segments.m_buffer[segmentId].m_bounds.center - camPos).magnitude > TrafficManagerTool.MaxOverlayDistance) continue; // do not draw if too distant Vector3 screenPos; bool visible = MainTool.WorldToScreenPoint(netManager.m_segments.m_buffer[segmentId].m_bounds.center, out screenPos); if (! visible) continue; if (!speedLimitManager.MayHaveCustomSpeedLimits((ushort)segmentId, ref netManager.m_segments.m_buffer[segmentId])) continue; currentlyVisibleSegmentIds.Add((ushort)segmentId); } lastCamPos = camPos; lastCamRot = camRot; } bool handleHovered = false; foreach (ushort segmentId in currentlyVisibleSegmentIds) { Vector3 screenPos; bool visible = MainTool.WorldToScreenPoint(netManager.m_segments.m_buffer[segmentId].m_bounds.center, out screenPos); if (!visible) continue; NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; // draw speed limits if (MainTool.GetToolMode() != ToolMode.VehicleRestrictions || segmentId != SelectedSegmentId) { // no speed limit overlay on selected segment when in vehicle restrictions mode if (drawSpeedLimitHandles((ushort)segmentId, ref netManager.m_segments.m_buffer[segmentId], viewOnly, ref camPos)) handleHovered = true; } } overlayHandleHovered = handleHovered; } private void _guiDefaultsWindow(int num) { List mainNetInfos = SpeedLimitManager.Instance.GetCustomizableNetInfos(); if (mainNetInfos == null || mainNetInfos.Count <= 0) { Log._Debug($"mainNetInfos={mainNetInfos?.Count}"); DragWindow(ref defaultsWindowRect); return; } bool updateRoadTex = false; if (currentInfoIndex < 0 || currentInfoIndex >= mainNetInfos.Count) { currentInfoIndex = 0; updateRoadTex = true; Log._Debug($"set currentInfoIndex to 0"); } NetInfo info = mainNetInfos[currentInfoIndex]; if (updateRoadTex) UpdateRoadTex(info); if (currentSpeedLimitIndex < 0) { currentSpeedLimitIndex = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimitIndex(info); Log._Debug($"set currentSpeedLimitIndex to {currentSpeedLimitIndex}"); } //Log._Debug($"currentInfoIndex={currentInfoIndex} currentSpeedLimitIndex={currentSpeedLimitIndex}"); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (GUILayout.Button("X", GUILayout.Width(25))) { defaultsWindowVisible = false; } GUILayout.EndHorizontal(); // Road type label GUILayout.BeginVertical(); GUILayout.Space(10); GUILayout.Label(Translation.GetString("Road_type") + ":"); GUILayout.EndVertical(); // switch between NetInfos GUILayout.BeginHorizontal(); GUILayout.BeginVertical(); GUILayout.FlexibleSpace(); if (GUILayout.Button("←", GUILayout.Width(50))) { currentInfoIndex = (currentInfoIndex + mainNetInfos.Count - 1) % mainNetInfos.Count; info = mainNetInfos[currentInfoIndex]; currentSpeedLimitIndex = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimitIndex(info); UpdateRoadTex(info); } GUILayout.FlexibleSpace(); GUILayout.EndVertical(); GUILayout.FlexibleSpace(); GUILayout.BeginVertical(); GUILayout.FlexibleSpace(); // NetInfo thumbnail GUILayout.Box(RoadTexture, GUILayout.Height(guiSpeedSignSize)); GUILayout.FlexibleSpace(); GUILayout.EndVertical(); GUILayout.FlexibleSpace(); GUILayout.BeginVertical(); GUILayout.FlexibleSpace(); if (GUILayout.Button("→", GUILayout.Width(50))) { currentInfoIndex = (currentInfoIndex + 1) % mainNetInfos.Count; info = mainNetInfos[currentInfoIndex]; currentSpeedLimitIndex = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimitIndex(info); UpdateRoadTex(info); } GUILayout.FlexibleSpace(); GUILayout.EndVertical(); GUILayout.EndHorizontal(); GUIStyle centeredTextStyle = new GUIStyle("label"); centeredTextStyle.alignment = TextAnchor.MiddleCenter; // NetInfo name GUILayout.Label(info.name, centeredTextStyle); // Default speed limit label GUILayout.BeginVertical(); GUILayout.Space(10); GUILayout.Label(Translation.GetString("Default_speed_limit") + ":"); GUILayout.EndVertical(); // switch between speed limits GUILayout.BeginHorizontal(); GUILayout.BeginVertical(); GUILayout.FlexibleSpace(); if (GUILayout.Button("←", GUILayout.Width(50))) { currentSpeedLimitIndex = (currentSpeedLimitIndex + SpeedLimitManager.Instance.AvailableSpeedLimits.Count - 1) % SpeedLimitManager.Instance.AvailableSpeedLimits.Count; } GUILayout.FlexibleSpace(); GUILayout.EndVertical(); GUILayout.FlexibleSpace(); GUILayout.BeginVertical(); GUILayout.FlexibleSpace(); // speed limit sign GUILayout.Box(TextureResources.SpeedLimitTextures[SpeedLimitManager.Instance.AvailableSpeedLimits[currentSpeedLimitIndex]], GUILayout.Width(guiSpeedSignSize), GUILayout.Height(guiSpeedSignSize)); GUILayout.FlexibleSpace(); GUILayout.EndVertical(); GUILayout.FlexibleSpace(); GUILayout.BeginVertical(); GUILayout.FlexibleSpace(); if (GUILayout.Button("→", GUILayout.Width(50))) { currentSpeedLimitIndex = (currentSpeedLimitIndex + 1) % SpeedLimitManager.Instance.AvailableSpeedLimits.Count; } GUILayout.FlexibleSpace(); GUILayout.EndVertical(); GUILayout.EndHorizontal(); // Save & Apply GUILayout.BeginVertical(); GUILayout.Space(10); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (GUILayout.Button(Translation.GetString("Save"), GUILayout.Width(70))) { SpeedLimitManager.Instance.FixCurrentSpeedLimits(info); SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimitIndex(info, currentSpeedLimitIndex); } GUILayout.FlexibleSpace(); if (GUILayout.Button(Translation.GetString("Save") + " & " + Translation.GetString("Apply"), GUILayout.Width(160))) { SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimitIndex(info, currentSpeedLimitIndex); SpeedLimitManager.Instance.ClearCurrentSpeedLimits(info); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.EndVertical(); DragWindow(ref defaultsWindowRect); } private void UpdateRoadTex(NetInfo info) { if (info != null) { if (info.m_Atlas != null && info.m_Atlas.material != null && info.m_Atlas.material.mainTexture != null && info.m_Atlas.material.mainTexture is Texture2D) { Texture2D mainTex = (Texture2D)info.m_Atlas.material.mainTexture; SpriteInfo spriteInfo = info.m_Atlas[info.m_Thumbnail]; if (spriteInfo != null && spriteInfo.texture != null && spriteInfo.texture.width > 0 && spriteInfo.texture.height > 0) { try { roadTexture = new Texture2D((int)spriteInfo.texture.width, (int)spriteInfo.texture.height, TextureFormat.ARGB32, false); roadTexture.SetPixels(0, 0, roadTexture.width, roadTexture.height, mainTex.GetPixels((int)(spriteInfo.region.x * mainTex.width), (int)(spriteInfo.region.y * mainTex.height), (int)(spriteInfo.region.width * mainTex.width), (int)(spriteInfo.region.height * mainTex.height))); roadTexture.Apply(); return; } catch (Exception e) { Log.Warning($"Could not get texture from NetInfo {info.name}: {e.ToString()}"); } } } } // fallback to "noimage" texture roadTexture = TextureResources.NoImageTexture2D; } private void _guiSpeedLimitsWindow(int num) { GUILayout.BeginHorizontal(); Color oldColor = GUI.color; for (int i = 0; i < SpeedLimitManager.Instance.AvailableSpeedLimits.Count; ++i) { if (curSpeedLimitIndex != i) GUI.color = Color.gray; float signSize = TrafficManagerTool.AdaptWidth(guiSpeedSignSize); if (GUILayout.Button(TextureResources.SpeedLimitTextures[SpeedLimitManager.Instance.AvailableSpeedLimits[i]], GUILayout.Width(signSize), GUILayout.Height(signSize))) { curSpeedLimitIndex = i; } GUI.color = oldColor; if (i == 6) { GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); } } GUILayout.EndHorizontal(); if (GUILayout.Button(Translation.GetString("Default_speed_limits"))) { TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_Defaults"); defaultsWindowVisible = true; } showLimitsPerLane = GUILayout.Toggle(showLimitsPerLane, Translation.GetString("Show_lane-wise_speed_limits")); DragWindow(ref windowRect); } private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, bool viewOnly, ref Vector3 camPos) { if (viewOnly && !Options.speedLimitsOverlay) return false; Vector3 center = segment.m_bounds.center; NetManager netManager = Singleton.instance; bool hovered = false; ushort speedLimitToSet = viewOnly ? (ushort)0 : SpeedLimitManager.Instance.AvailableSpeedLimits[curSpeedLimitIndex]; bool showPerLane = showLimitsPerLane; if (!viewOnly) { showPerLane = showLimitsPerLane ^ (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)); } if (showPerLane) { // show individual speed limit handle per lane int numDirections; int numLanes = TrafficManagerTool.GetSegmentNumVehicleLanes(segmentId, null, out numDirections, SpeedLimitManager.VEHICLE_TYPES); NetInfo segmentInfo = segment.Info; Vector3 yu = (segment.m_endDirection - segment.m_startDirection).normalized; Vector3 xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized; /*if ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) { xu = -xu; }*/ float f = viewOnly ? 4f : 7f; // reserved sign size in game coordinates Vector3 zero = center - 0.5f * (float)(numLanes - 1 + numDirections - 1) * f * xu; uint x = 0; var guiColor = GUI.color; IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(segmentId, ref segment, null, SpeedLimitManager.LANE_TYPES, SpeedLimitManager.VEHICLE_TYPES); bool onlyMonorailLanes = sortedLanes.Count > 0; if (!viewOnly) { foreach (LanePos laneData in sortedLanes) { byte laneIndex = laneData.laneIndex; NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) == VehicleInfo.VehicleType.None) { onlyMonorailLanes = false; break; } } } HashSet directions = new HashSet(); int sortedLaneIndex = -1; foreach (LanePos laneData in sortedLanes) { ++sortedLaneIndex; uint laneId = laneData.laneId; byte laneIndex = laneData.laneIndex; NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; if (!directions.Contains(laneInfo.m_finalDirection)) { if (directions.Count > 0) ++x; // space between different directions directions.Add(laneInfo.m_finalDirection); } bool hoveredHandle = MainTool.DrawGenericSquareOverlayGridTexture(TextureResources.SpeedLimitTextures[SpeedLimitManager.Instance.GetCustomSpeedLimit(laneId)], camPos, zero, f, xu, yu, x, 0, speedLimitSignSize, !viewOnly); if (!viewOnly && !onlyMonorailLanes && (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) != VehicleInfo.VehicleType.None) { MainTool.DrawStaticSquareOverlayGridTexture(TextureResources.VehicleInfoSignTextures[ExtVehicleType.PassengerTrain], camPos, zero, f, xu, yu, x, 1, speedLimitSignSize); } if (hoveredHandle) hovered = true; if (hoveredHandle && Input.GetMouseButton(0) && !IsCursorInPanel()) { SpeedLimitManager.Instance.SetSpeedLimit(segmentId, laneIndex, laneInfo, laneId, speedLimitToSet); if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { SegmentLaneTraverser.Traverse(segmentId, SegmentTraverser.TraverseDirection.AnyDirection, SegmentTraverser.TraverseSide.AnySide, SegmentLaneTraverser.LaneStopCriterion.LaneCount, SegmentTraverser.SegmentStopCriterion.Junction, SpeedLimitManager.LANE_TYPES, SpeedLimitManager.VEHICLE_TYPES, delegate (SegmentLaneVisitData data) { if (data.segVisitData.initial) { return true; } if (sortedLaneIndex != data.sortedLaneIndex) { return true; } Constants.ServiceFactory.NetService.ProcessSegment(data.segVisitData.curGeo.SegmentId, delegate (ushort curSegmentId, ref NetSegment curSegment) { NetInfo.Lane curLaneInfo = curSegment.Info.m_lanes[data.curLanePos.laneIndex]; SpeedLimitManager.Instance.SetSpeedLimit(curSegmentId, data.curLanePos.laneIndex, curLaneInfo, data.curLanePos.laneId, speedLimitToSet); return true; }); return true; }); } } ++x; } } else { // draw speedlimits over mean middle points of lane beziers Dictionary segCenter; if (!segmentCenterByDir.TryGetValue(segmentId, out segCenter)) { segCenter = new Dictionary(); segmentCenterByDir.Add(segmentId, segCenter); TrafficManagerTool.CalculateSegmentCenterByDir(segmentId, segCenter); } foreach (KeyValuePair e in segCenter) { Vector3 screenPos; bool visible = MainTool.WorldToScreenPoint(e.Value, out screenPos); if (!visible) continue; float zoom = 1.0f / (e.Value - camPos).magnitude * 100f * MainTool.GetBaseZoom(); float size = (viewOnly ? 0.8f : 1f) * speedLimitSignSize * zoom; Color guiColor = GUI.color; Rect boundingBox = new Rect(screenPos.x - size / 2, screenPos.y - size / 2, size, size); bool hoveredHandle = !viewOnly && TrafficManagerTool.IsMouseOver(boundingBox); guiColor.a = MainTool.GetHandleAlpha(hoveredHandle); if (hoveredHandle) { // mouse hovering over sign hovered = true; } GUI.color = guiColor; GUI.DrawTexture(boundingBox, TextureResources.SpeedLimitTextures[SpeedLimitManager.Instance.GetCustomSpeedLimit(segmentId, e.Key)]); if (hoveredHandle && Input.GetMouseButton(0) && !IsCursorInPanel()) { // change the speed limit to the selected one //Log._Debug($"Setting speed limit of segment {segmentId}, dir {e.Key.ToString()} to {speedLimitToSet}"); SpeedLimitManager.Instance.SetSpeedLimit(segmentId, e.Key, speedLimitToSet); if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { NetInfo.Direction normDir = e.Key; if ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) { normDir = NetInfo.InvertDirection(normDir); } SegmentLaneTraverser.Traverse(segmentId, SegmentTraverser.TraverseDirection.AnyDirection, SegmentTraverser.TraverseSide.AnySide, SegmentLaneTraverser.LaneStopCriterion.LaneCount, SegmentTraverser.SegmentStopCriterion.Junction, SpeedLimitManager.LANE_TYPES, SpeedLimitManager.VEHICLE_TYPES, delegate (SegmentLaneVisitData data) { if (data.segVisitData.initial) { return true; } bool reverse = data.segVisitData.viaStartNode == data.segVisitData.viaInitialStartNode; ushort otherSegmentId = data.segVisitData.curGeo.SegmentId; NetInfo otherSegmentInfo = netManager.m_segments.m_buffer[otherSegmentId].Info; uint laneId = data.curLanePos.laneId; byte laneIndex = data.curLanePos.laneIndex; NetInfo.Lane laneInfo = otherSegmentInfo.m_lanes[laneIndex]; NetInfo.Direction otherNormDir = laneInfo.m_finalDirection; if ((netManager.m_segments.m_buffer[otherSegmentId].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None ^ reverse) { otherNormDir = NetInfo.InvertDirection(otherNormDir); } if (otherNormDir == normDir) { SpeedLimitManager.Instance.SetSpeedLimit(otherSegmentId, laneInfo.m_finalDirection, speedLimitToSet); } return true; }); } } guiColor.a = 1f; GUI.color = guiColor; } } return hovered; } } } ================================================ FILE: TLM/TLM/UI/SubTools/TimedTrafficLightsTool.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Custom.AI; using TrafficManager.State; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using UnityEngine; using TrafficManager.Manager; using TrafficManager.Traffic; using CSUtil.Commons; using TrafficManager.Manager.Impl; using TrafficManager.Geometry.Impl; using ColossalFramework.UI; namespace TrafficManager.UI.SubTools { public class TimedTrafficLightsTool : SubTool { private readonly GUIStyle _counterStyle = new GUIStyle(); private readonly int[] _hoveredButton = new int[2]; private bool nodeSelectionLocked = false; private List SelectedNodeIds = new List(); private bool _cursorInSecondaryPanel; private Rect _windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 480, 350)); private Rect _windowRect2 = TrafficManagerTool.MoveGUI(new Rect(0, 0, 300, 150)); private bool _timedPanelAdd = false; private int _timedEditStep = -1; private ushort _hoveredNode = 0; private bool _timedShowNumbers = false; private int _timedViewedStep = -1; private int _stepMinValue = 1; private int _stepMaxValue = 1; private StepChangeMetric _stepMetric = StepChangeMetric.Default; private float _waitFlowBalance = GlobalConfig.Instance.TimedTrafficLights.FlowToWaitRatio; private string _stepMinValueStr = "1"; private string _stepMaxValueStr = "1"; private bool timedLightActive = false; private int currentStep = -1; private int numSteps = 0; private bool inTestMode = false; private ushort nodeIdToCopy = 0; private HashSet currentTimedNodeIds; private GUIStyle layout = new GUIStyle { normal = { textColor = new Color(1f, 1f, 1f) } }; private GUIStyle layoutRed = new GUIStyle { normal = { textColor = new Color(1f, 0f, 0f) } }; private GUIStyle layoutGreen = new GUIStyle { normal = { textColor = new Color(0f, 1f, 0f) } }; private GUIStyle layoutYellow = new GUIStyle { normal = { textColor = new Color(1f, 1f, 0f) } }; public TimedTrafficLightsTool(TrafficManagerTool mainTool) : base(mainTool) { currentTimedNodeIds = new HashSet(); } public override bool IsCursorInPanel() { return base.IsCursorInPanel() || _cursorInSecondaryPanel; } private void RefreshCurrentTimedNodeIds(ushort forceNodeId=0) { TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; if (forceNodeId == 0) { currentTimedNodeIds.Clear(); } else { currentTimedNodeIds.Remove(forceNodeId); } for (uint nodeId = (forceNodeId == 0 ? 1u : forceNodeId); nodeId <= (forceNodeId == 0 ? NetManager.MAX_NODE_COUNT - 1 : forceNodeId); ++nodeId) { if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { continue; } if (tlsMan.HasTimedSimulation((ushort)nodeId)) { currentTimedNodeIds.Add((ushort)nodeId); } } } public override void OnActivate() { TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; RefreshCurrentTimedNodeIds(); nodeSelectionLocked = false; foreach (ushort nodeId in currentTimedNodeIds) { if (!Constants.ServiceFactory.NetService.IsNodeValid(nodeId)) { continue; } tlsMan.TrafficLightSimulations[nodeId].Housekeeping(); } } public override void OnSecondaryClickOverlay() { if (!IsCursorInPanel()) { Cleanup(); MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); } } public override void OnPrimaryClickOverlay() { if (HoveredNodeId <= 0 || nodeSelectionLocked) return; TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; switch (MainTool.GetToolMode()) { case ToolMode.TimedLightsSelectNode: case ToolMode.TimedLightsShowLights: if (MainTool.GetToolMode() == ToolMode.TimedLightsShowLights) { MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); ClearSelectedNodes(); } if (! tlsMan.HasTimedSimulation(HoveredNodeId)) { if (IsNodeSelected(HoveredNodeId)) { RemoveSelectedNode(HoveredNodeId); } else { AddSelectedNode(HoveredNodeId); } } else { if (SelectedNodeIds.Count == 0) { //timedSim.housekeeping(); var timedLight = tlsMan.TrafficLightSimulations[HoveredNodeId].TimedLight; if (timedLight != null) { SelectedNodeIds = new List(timedLight.NodeGroup); MainTool.SetToolMode(ToolMode.TimedLightsShowLights); } } else { MainTool.ShowTooltip(Translation.GetString("NODE_IS_TIMED_LIGHT")); } } break; case ToolMode.TimedLightsAddNode: if (SelectedNodeIds.Count <= 0) { MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); return; } if (SelectedNodeIds.Contains(HoveredNodeId)) return; //bool mayEnterBlocked = Options.mayEnterBlockedJunctions; ITimedTrafficLights existingTimedLight = null; foreach (var nodeId in SelectedNodeIds) { if (!tlsMan.HasTimedSimulation(nodeId)) { continue; } //mayEnterBlocked = timedNode.vehiclesMayEnterBlockedJunctions; existingTimedLight = tlsMan.TrafficLightSimulations[nodeId].TimedLight; } /*if (timedSim2 != null) timedSim2.housekeeping();*/ ITimedTrafficLights timedLight2 = null; if (! tlsMan.HasTimedSimulation(HoveredNodeId)) { var nodeGroup = new List(); nodeGroup.Add(HoveredNodeId); tlsMan.SetUpTimedTrafficLight(HoveredNodeId, nodeGroup); } timedLight2 = tlsMan.TrafficLightSimulations[HoveredNodeId].TimedLight; timedLight2.Join(existingTimedLight); ClearSelectedNodes(); foreach (ushort nodeId in timedLight2.NodeGroup) { RefreshCurrentTimedNodeIds(nodeId); AddSelectedNode(nodeId); } MainTool.SetToolMode(ToolMode.TimedLightsShowLights); break; case ToolMode.TimedLightsRemoveNode: if (SelectedNodeIds.Count <= 0) { MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); return; } if (SelectedNodeIds.Contains(HoveredNodeId)) { tlsMan.RemoveNodeFromSimulation(HoveredNodeId, false, false); RefreshCurrentTimedNodeIds(HoveredNodeId); } RemoveSelectedNode(HoveredNodeId); MainTool.SetToolMode(ToolMode.TimedLightsShowLights); break; case ToolMode.TimedLightsCopyLights: if (nodeIdToCopy == 0 || !tlsMan.HasTimedSimulation(nodeIdToCopy)) { MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); return; } // compare geometry NodeGeometry sourceNodeGeo = NodeGeometry.Get(nodeIdToCopy); NodeGeometry targetNodeGeo = NodeGeometry.Get(HoveredNodeId); if (sourceNodeGeo.NumSegmentEnds != targetNodeGeo.NumSegmentEnds) { MainTool.ShowTooltip(Translation.GetString("The_chosen_traffic_light_program_is_incompatible_to_this_junction")); return; } // check for existing simulation if (tlsMan.HasTimedSimulation(HoveredNodeId)) { MainTool.ShowTooltip(Translation.GetString("NODE_IS_TIMED_LIGHT")); return; } ITimedTrafficLights sourceTimedLights = tlsMan.TrafficLightSimulations[nodeIdToCopy].TimedLight; // copy `nodeIdToCopy` to `HoveredNodeId` tlsMan.SetUpTimedTrafficLight(HoveredNodeId, new List { HoveredNodeId }); tlsMan.TrafficLightSimulations[HoveredNodeId].TimedLight.PasteSteps(sourceTimedLights); RefreshCurrentTimedNodeIds(HoveredNodeId); Cleanup(); AddSelectedNode(HoveredNodeId); MainTool.SetToolMode(ToolMode.TimedLightsShowLights); break; } } public override void OnToolGUI(Event e) { base.OnToolGUI(e); switch (MainTool.GetToolMode()) { case ToolMode.TimedLightsSelectNode: _guiTimedTrafficLightsNode(); break; case ToolMode.TimedLightsShowLights: case ToolMode.TimedLightsAddNode: case ToolMode.TimedLightsRemoveNode: _guiTimedTrafficLights(); break; case ToolMode.TimedLightsCopyLights: _guiTimedTrafficLightsCopy(); break; } } public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { bool onlySelected = MainTool.GetToolMode() == ToolMode.TimedLightsRemoveNode; //Log._Debug($"nodeSelLocked={nodeSelectionLocked} HoveredNodeId={HoveredNodeId} IsNodeSelected={IsNodeSelected(HoveredNodeId)} onlySelected={onlySelected} isinsideui={MainTool.GetToolController().IsInsideUI} cursorVis={Cursor.visible}"); if (!nodeSelectionLocked && HoveredNodeId != 0 && (!IsNodeSelected(HoveredNodeId) ^ onlySelected) && !MainTool.GetToolController().IsInsideUI && Cursor.visible && Flags.mayHaveTrafficLight(HoveredNodeId) ) { MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, false, false); } if (SelectedNodeIds.Count <= 0) return; foreach (var index in SelectedNodeIds) { MainTool.DrawNodeCircle(cameraInfo, index, true, false); } } private void _guiTimedControlPanel(int num) { //Log._Debug("guiTimedControlPanel"); try { TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; if (MainTool.GetToolMode() == ToolMode.TimedLightsAddNode || MainTool.GetToolMode() == ToolMode.TimedLightsRemoveNode) { GUILayout.Label(Translation.GetString("Select_junction")); if (GUILayout.Button(Translation.GetString("Cancel"))) { MainTool.SetToolMode(ToolMode.TimedLightsShowLights); } else { DragWindow(ref _windowRect); return; } } if (! tlsMan.HasTimedSimulation(SelectedNodeIds[0])) { MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); //Log._Debug("nodesim or timednodemain is null"); DragWindow(ref _windowRect); return; } var timedNodeMain = tlsMan.TrafficLightSimulations[SelectedNodeIds[0]].TimedLight; if (Event.current.type == EventType.Layout) { timedLightActive = tlsMan.HasActiveTimedSimulation(SelectedNodeIds[0]); currentStep = timedNodeMain.CurrentStep; inTestMode = timedNodeMain.IsInTestMode(); numSteps = timedNodeMain.NumSteps(); } if (!timedLightActive && numSteps > 0 && !_timedPanelAdd && _timedEditStep < 0 && _timedViewedStep < 0) { _timedViewedStep = 0; foreach (var nodeId in SelectedNodeIds) { tlsMan.TrafficLightSimulations[nodeId].TimedLight?.GetStep(_timedViewedStep).UpdateLiveLights(true); } } for (var i = 0; i < timedNodeMain.NumSteps(); i++) { GUILayout.BeginHorizontal(); if (_timedEditStep != i) { if (timedLightActive) { if (i == currentStep) { GUILayout.BeginVertical(); GUILayout.Space(5); String labelStr = Translation.GetString("State") + " " + (i + 1) + ": (" + Translation.GetString("min/max") + ")" + timedNodeMain.GetStep(i).MinTimeRemaining() + "/" + timedNodeMain.GetStep(i).MaxTimeRemaining(); float flow = Single.NaN; float wait = Single.NaN; if (inTestMode) { try { timedNodeMain.GetStep(timedNodeMain.CurrentStep).CalcWaitFlow(true, timedNodeMain.CurrentStep, out wait, out flow); } catch (Exception e) { Log.Warning("calcWaitFlow in UI: This is not thread-safe: " + e.ToString()); } } else { wait = timedNodeMain.GetStep(i).CurrentWait; flow = timedNodeMain.GetStep(i).CurrentFlow; } if (!Single.IsNaN(flow) && !Single.IsNaN(wait)) labelStr += " " + Translation.GetString("avg._flow") + ": " + String.Format("{0:0.##}", flow) + " " + Translation.GetString("avg._wait") + ": " + String.Format("{0:0.##}", wait); GUIStyle labelLayout = layout; if (inTestMode && !Single.IsNaN(wait) && !Single.IsNaN(flow)) { float metric; if (timedNodeMain.GetStep(i).ShouldGoToNextStep(flow, wait, out metric)) labelLayout = layoutRed; else labelLayout = layoutGreen; } else { bool inEndTransition = false; try { inEndTransition = timedNodeMain.GetStep(i).IsInEndTransition(); } catch (Exception e) { Log.Error("Error while determining if timed traffic light is in end transition: " + e.ToString()); } labelLayout = inEndTransition ? layoutYellow : layoutGreen; } GUILayout.Label(labelStr, labelLayout); GUILayout.Space(5); GUILayout.EndVertical(); if (GUILayout.Button(Translation.GetString("Skip"), GUILayout.Width(80))) { foreach (var nodeId in SelectedNodeIds) { tlsMan.TrafficLightSimulations[nodeId].TimedLight?.SkipStep(); } } } else { GUILayout.Label(Translation.GetString("State") + " " + (i + 1) + ": " + timedNodeMain.GetStep(i).MinTime + " - " + timedNodeMain.GetStep(i).MaxTime, layout); } } else { GUIStyle labelLayout = layout; if (_timedViewedStep == i) { labelLayout = layoutGreen; } GUILayout.Label(Translation.GetString("State") + " " + (i + 1) + ": " + timedNodeMain.GetStep(i).MinTime + " - " + timedNodeMain.GetStep(i).MaxTime, labelLayout); if (_timedEditStep < 0) { GUILayout.BeginHorizontal(GUILayout.Width(100)); if (i > 0) { if (GUILayout.Button(Translation.GetString("up"), GUILayout.Width(48))) { foreach (var nodeId in SelectedNodeIds) { tlsMan.TrafficLightSimulations[nodeId].TimedLight?.MoveStep(i, i - 1); } _timedViewedStep = i - 1; } } else { GUILayout.Space(50); } if (i < numSteps - 1) { if (GUILayout.Button(Translation.GetString("down"), GUILayout.Width(48))) { foreach (var nodeId in SelectedNodeIds) { tlsMan.TrafficLightSimulations[nodeId].TimedLight?.MoveStep(i, i + 1); } _timedViewedStep = i + 1; } } else { GUILayout.Space(50); } GUILayout.EndHorizontal(); if (GUILayout.Button(Translation.GetString("View"), GUILayout.Width(70))) { _timedPanelAdd = false; _timedViewedStep = i; foreach (var nodeId in SelectedNodeIds) { tlsMan.TrafficLightSimulations[nodeId].TimedLight?.GetStep(i).UpdateLiveLights(true); } } if (GUILayout.Button(Translation.GetString("Edit"), GUILayout.Width(65))) { _timedPanelAdd = false; _timedEditStep = i; _timedViewedStep = -1; _stepMinValue = timedNodeMain.GetStep(i).MinTime; _stepMaxValue = timedNodeMain.GetStep(i).MaxTime; _stepMetric = timedNodeMain.GetStep(i).ChangeMetric; _waitFlowBalance = timedNodeMain.GetStep(i).WaitFlowBalance; _stepMinValueStr = _stepMinValue.ToString(); _stepMaxValueStr = _stepMaxValue.ToString(); nodeSelectionLocked = true; foreach (var nodeId in SelectedNodeIds) { tlsMan.TrafficLightSimulations[nodeId].TimedLight?.GetStep(i).UpdateLiveLights(true); } } if (GUILayout.Button(Translation.GetString("Delete"), GUILayout.Width(70))) { _timedPanelAdd = false; _timedViewedStep = -1; foreach (var nodeId in SelectedNodeIds) { tlsMan.TrafficLightSimulations[nodeId].TimedLight?.RemoveStep(i); } } } } } else { nodeSelectionLocked = true; int oldStepMinValue = _stepMinValue; int oldStepMaxValue = _stepMaxValue; // Editing step GUILayout.Label(Translation.GetString("Min._Time:"), GUILayout.Width(75)); _stepMinValueStr = GUILayout.TextField(_stepMinValueStr, GUILayout.Height(20)); if (!Int32.TryParse(_stepMinValueStr, out _stepMinValue)) _stepMinValue = oldStepMinValue; GUILayout.Label(Translation.GetString("Max._Time:"), GUILayout.Width(75)); _stepMaxValueStr = GUILayout.TextField(_stepMaxValueStr, GUILayout.Height(20)); if (!Int32.TryParse(_stepMaxValueStr, out _stepMaxValue)) _stepMaxValue = oldStepMaxValue; if (GUILayout.Button(Translation.GetString("Save"), GUILayout.Width(70))) { if (_stepMinValue < 0) _stepMinValue = 0; if (_stepMaxValue <= 0) _stepMaxValue = 1; if (_stepMaxValue < _stepMinValue) _stepMaxValue = _stepMinValue; if (_waitFlowBalance <= 0) _waitFlowBalance = GlobalConfig.Instance.TimedTrafficLights.FlowToWaitRatio; foreach (var nodeId in SelectedNodeIds) { var step = tlsMan.TrafficLightSimulations[nodeId].TimedLight?.GetStep(_timedEditStep); if (step != null) { step.MinTime = _stepMinValue; step.MaxTime = _stepMaxValue; step.ChangeMetric = _stepMetric; step.WaitFlowBalance = _waitFlowBalance; step.UpdateLights(); } } _timedViewedStep = _timedEditStep; _timedEditStep = -1; nodeSelectionLocked = false; } GUILayout.EndHorizontal(); BuildStepChangeMetricDisplay(true); BuildFlowPolicyDisplay(true); GUILayout.BeginHorizontal(); } GUILayout.EndHorizontal(); } // foreach step GUILayout.BeginHorizontal(); if (_timedEditStep < 0 && !timedLightActive) { if (_timedPanelAdd) { nodeSelectionLocked = true; // new step int oldStepMinValue = _stepMinValue; int oldStepMaxValue = _stepMaxValue; GUILayout.Label(Translation.GetString("Min._Time:"), GUILayout.Width(65)); _stepMinValueStr = GUILayout.TextField(_stepMinValueStr, GUILayout.Height(20)); if (!Int32.TryParse(_stepMinValueStr, out _stepMinValue)) _stepMinValue = oldStepMinValue; GUILayout.Label(Translation.GetString("Max._Time:"), GUILayout.Width(65)); _stepMaxValueStr = GUILayout.TextField(_stepMaxValueStr, GUILayout.Height(20)); if (!Int32.TryParse(_stepMaxValueStr, out _stepMaxValue)) _stepMaxValue = oldStepMaxValue; if (GUILayout.Button(Translation.GetString("Add"), GUILayout.Width(70))) { TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_AddStep"); if (_stepMinValue < 0) _stepMinValue = 0; if (_stepMaxValue <= 0) _stepMaxValue = 1; if (_stepMaxValue < _stepMinValue) _stepMaxValue = _stepMinValue; if (_waitFlowBalance <= 0) _waitFlowBalance = 1f; foreach (var nodeId in SelectedNodeIds) { tlsMan.TrafficLightSimulations[nodeId].TimedLight?.AddStep(_stepMinValue, _stepMaxValue, _stepMetric, _waitFlowBalance); } _timedPanelAdd = false; _timedViewedStep = timedNodeMain.NumSteps() - 1; } if (GUILayout.Button("X", GUILayout.Width(22))) { _timedPanelAdd = false; } GUILayout.EndHorizontal(); BuildStepChangeMetricDisplay(true); BuildFlowPolicyDisplay(true); GUILayout.BeginHorizontal(); } else { if (_timedEditStep < 0) { if (GUILayout.Button(Translation.GetString("Add_step"))) { TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_AddStep"); _timedPanelAdd = true; nodeSelectionLocked = true; _timedViewedStep = -1; _timedEditStep = -1; _stepMetric = StepChangeMetric.Default; } } } } GUILayout.EndHorizontal(); GUILayout.Space(5); if (numSteps > 1 && _timedEditStep < 0) { if (timedLightActive) { if (GUILayout.Button(_timedShowNumbers ? Translation.GetString("Hide_counters") : Translation.GetString("Show_counters"))) { _timedShowNumbers = !_timedShowNumbers; } if (GUILayout.Button(Translation.GetString("Stop"))) { foreach (var nodeId in SelectedNodeIds) { tlsMan.TrafficLightSimulations[nodeId].TimedLight?.Stop(); } } /*bool isInTestMode = false; foreach (var sim in SelectedNodeIndexes.Select(tlsMan.GetNodeSimulation)) { if (sim.TimedLight.IsInTestMode()) { isInTestMode = true; break; } }*/ var curStep = timedNodeMain.CurrentStep; ITimedTrafficLightsStep currentStep = timedNodeMain.GetStep(curStep); _stepMetric = currentStep.ChangeMetric; if (currentStep.MaxTime > currentStep.MinTime) { BuildStepChangeMetricDisplay(false); } _waitFlowBalance = timedNodeMain.GetStep(curStep).WaitFlowBalance; BuildFlowPolicyDisplay(inTestMode); foreach (var nodeId in SelectedNodeIds) { var step = tlsMan.TrafficLightSimulations[nodeId].TimedLight?.GetStep(curStep); if (step != null) { step.WaitFlowBalance = _waitFlowBalance; } } //var mayEnterIfBlocked = GUILayout.Toggle(timedNodeMain.vehiclesMayEnterBlockedJunctions, Translation.GetString("Vehicles_may_enter_blocked_junctions"), new GUILayoutOption[] { }); var testMode = GUILayout.Toggle(inTestMode, Translation.GetString("Enable_test_mode_(stay_in_current_step)"), new GUILayoutOption[] { }); foreach (var nodeId in SelectedNodeIds) { tlsMan.TrafficLightSimulations[nodeId].TimedLight?.SetTestMode(testMode); } } else { if (_timedEditStep < 0 && !_timedPanelAdd) { if (GUILayout.Button(Translation.GetString("Start"))) { _timedPanelAdd = false; nodeSelectionLocked = false; foreach (var nodeId in SelectedNodeIds) { tlsMan.TrafficLightSimulations[nodeId].TimedLight?.Start(); } } } } } if (_timedEditStep >= 0) { DragWindow(ref _windowRect); return; } GUILayout.Space(30); if (SelectedNodeIds.Count == 1 && timedNodeMain.NumSteps() > 0) { GUILayout.BeginHorizontal(); if (GUILayout.Button(Translation.GetString("Rotate_left"))) { timedNodeMain.RotateLeft(); _timedViewedStep = 0; } if (GUILayout.Button(Translation.GetString("Copy"))) { TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_Copy"); nodeIdToCopy = SelectedNodeIds[0]; MainTool.SetToolMode(ToolMode.TimedLightsCopyLights); } if (GUILayout.Button(Translation.GetString("Rotate_right"))) { timedNodeMain.RotateRight(); _timedViewedStep = 0; } GUILayout.EndHorizontal(); } if (!timedLightActive) { GUILayout.Space(30); if (GUILayout.Button(Translation.GetString("Add_junction_to_timed_light"))) { TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_AddJunction"); MainTool.SetToolMode(ToolMode.TimedLightsAddNode); } if (SelectedNodeIds.Count > 1) { if (GUILayout.Button(Translation.GetString("Remove_junction_from_timed_light"))) { TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_RemoveJunction"); MainTool.SetToolMode(ToolMode.TimedLightsRemoveNode); } } GUILayout.Space(30); if (GUILayout.Button(Translation.GetString("Remove_timed_traffic_light"))) { DisableTimed(); ClearSelectedNodes(); MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); } } DragWindow(ref _windowRect); } catch (Exception e) { Log.Error($"TimedTrafficLightsTool._guiTimedControlPanel: {e}"); } } public override void Cleanup() { SelectedNodeId = 0; ClearSelectedNodes(); _timedShowNumbers = false; _timedPanelAdd = false; _timedEditStep = -1; _hoveredNode = 0; _timedShowNumbers = false; _timedViewedStep = -1; timedLightActive = false; nodeIdToCopy = 0; } public override void Initialize() { base.Initialize(); Cleanup(); if (Options.timedLightsOverlay) { RefreshCurrentTimedNodeIds(); } else { currentTimedNodeIds.Clear(); } } private void BuildStepChangeMetricDisplay(bool editable) { GUILayout.BeginVertical(); if (editable) { GUILayout.Label(Translation.GetString("After_min._time_has_elapsed_switch_to_next_step_if") + ":"); if (GUILayout.Toggle(_stepMetric == StepChangeMetric.Default, GetStepChangeMetricDescription(StepChangeMetric.Default))) { _stepMetric = StepChangeMetric.Default; } if (GUILayout.Toggle(_stepMetric == StepChangeMetric.FirstFlow, GetStepChangeMetricDescription(StepChangeMetric.FirstFlow))) { _stepMetric = StepChangeMetric.FirstFlow; } if (GUILayout.Toggle(_stepMetric == StepChangeMetric.FirstWait, GetStepChangeMetricDescription(StepChangeMetric.FirstWait))) { _stepMetric = StepChangeMetric.FirstWait; } if (GUILayout.Toggle(_stepMetric == StepChangeMetric.NoFlow, GetStepChangeMetricDescription(StepChangeMetric.NoFlow))) { _stepMetric = StepChangeMetric.NoFlow; } if (GUILayout.Toggle(_stepMetric == StepChangeMetric.NoWait, GetStepChangeMetricDescription(StepChangeMetric.NoWait))) { _stepMetric = StepChangeMetric.NoWait; } } else { GUILayout.Label(Translation.GetString("Adaptive_step_switching") + ": " + GetStepChangeMetricDescription(_stepMetric)); } GUILayout.EndVertical(); } private void BuildFlowPolicyDisplay(bool editable) { string formatStr; if (_waitFlowBalance < 0.01f) formatStr = "{0:0.###}"; else if (_waitFlowBalance < 0.1f) formatStr = "{0:0.##}"; else formatStr = "{0:0.#}"; GUILayout.BeginHorizontal(); if (editable) { GUILayout.Label(Translation.GetString("Sensitivity") + " (" + String.Format(formatStr, _waitFlowBalance) + ", " + getWaitFlowBalanceInfo() + "):"); if (_waitFlowBalance <= 0.01f) { if (_waitFlowBalance >= 0) { if (GUILayout.Button("-.001")) { _waitFlowBalance -= 0.001f; } } if (_waitFlowBalance < 0.01f) { if (GUILayout.Button("+.001")) { _waitFlowBalance += 0.001f; } } } else if (_waitFlowBalance <= 0.1f) { if (GUILayout.Button("-.01")) { _waitFlowBalance -= 0.01f; } if (_waitFlowBalance < 0.1f) { if (GUILayout.Button("+.01")) { _waitFlowBalance += 0.01f; } } } if (_waitFlowBalance < 0) _waitFlowBalance = 0; if (_waitFlowBalance > 10) _waitFlowBalance = 10; GUILayout.EndHorizontal(); _waitFlowBalance = GUILayout.HorizontalSlider(_waitFlowBalance, 0.001f, 10f); // step snapping if (_waitFlowBalance < 0.001f) { _waitFlowBalance = 0.001f; } else if (_waitFlowBalance < 0.01f) { _waitFlowBalance = Mathf.Round(_waitFlowBalance * 1000f) * 0.001f; } else if (_waitFlowBalance < 0.1f) { _waitFlowBalance = Mathf.Round(_waitFlowBalance * 100f) * 0.01f; } else if (_waitFlowBalance < 10f) { _waitFlowBalance = Mathf.Round(_waitFlowBalance * 10f) * 0.1f; } else { _waitFlowBalance = 10f; } GUILayout.BeginHorizontal(); GUIStyle style = new GUIStyle(); style.normal.textColor = Color.white; style.alignment = TextAnchor.LowerLeft; GUILayout.Label(Translation.GetString("Low"), style, new GUILayoutOption[] { GUILayout.Height(10) }); style.alignment = TextAnchor.LowerRight; GUILayout.Label(Translation.GetString("High"), style, new GUILayoutOption[] { GUILayout.Height(10) }); } else { GUILayout.Label(Translation.GetString("Sensitivity") + ": " + String.Format(formatStr, _waitFlowBalance) + " (" + getWaitFlowBalanceInfo() + ")"); } GUILayout.EndHorizontal(); GUILayout.Space(5); } private string GetStepChangeMetricDescription(StepChangeMetric metric) { switch (metric) { case StepChangeMetric.Default: default: return Translation.GetString("flow_ratio") + " < " + Translation.GetString("wait_ratio") + " (" + Translation.GetString("default") + ")"; case StepChangeMetric.FirstFlow: return Translation.GetString("flow_ratio") + " > 0"; case StepChangeMetric.FirstWait: return Translation.GetString("wait_ratio") + " > 0"; case StepChangeMetric.NoFlow: return Translation.GetString("flow_ratio") + " = 0"; case StepChangeMetric.NoWait: return Translation.GetString("wait_ratio") + " = 0"; } } private void _guiTimedTrafficLightsNode() { _cursorInSecondaryPanel = false; _windowRect2 = GUILayout.Window(252, _windowRect2, _guiTimedTrafficLightsNodeWindow, Translation.GetString("Select_nodes_windowTitle"), WindowStyle); _cursorInSecondaryPanel = _windowRect2.Contains(Event.current.mousePosition); } private void _guiTimedTrafficLights() { TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; TrafficPriorityManager prioMan = TrafficPriorityManager.Instance; _cursorInSecondaryPanel = false; _windowRect = GUILayout.Window(253, _windowRect, _guiTimedControlPanel, Translation.GetString("Timed_traffic_lights_manager"), WindowStyle); _cursorInSecondaryPanel = _windowRect.Contains(Event.current.mousePosition); GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, new Vector3(1, 1, 1)); // revert scaling ShowGUI(); } private void _guiTimedTrafficLightsCopy() { _cursorInSecondaryPanel = false; _windowRect2 = GUILayout.Window(255, _windowRect2, _guiTimedTrafficLightsPasteWindow, Translation.GetString("Paste"), WindowStyle); _cursorInSecondaryPanel = _windowRect2.Contains(Event.current.mousePosition); } private void _guiTimedTrafficLightsPasteWindow(int num) { GUILayout.Label(Translation.GetString("Select_junction")); } private void _guiTimedTrafficLightsNodeWindow(int num) { TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; if (SelectedNodeIds.Count < 1) { GUILayout.Label(Translation.GetString("Select_nodes")); } else { var txt = SelectedNodeIds.Aggregate("", (current, t) => current + (Translation.GetString("Node") + " " + t + "\n")); GUILayout.Label(txt); if (SelectedNodeIds.Count > 0 && GUILayout.Button(Translation.GetString("Deselect_all_nodes"))) { ClearSelectedNodes(); } if (!GUILayout.Button(Translation.GetString("Setup_timed_traffic_light"))) return; _waitFlowBalance = GlobalConfig.Instance.TimedTrafficLights.FlowToWaitRatio; foreach (var nodeId in SelectedNodeIds) { tlsMan.SetUpTimedTrafficLight(nodeId, SelectedNodeIds); RefreshCurrentTimedNodeIds(nodeId); } MainTool.SetToolMode(ToolMode.TimedLightsShowLights); } DragWindow(ref _windowRect2); } private string getWaitFlowBalanceInfo() { if (_waitFlowBalance < 0.1f) { return Translation.GetString("Extreme_long_green/red_phases"); } else if (_waitFlowBalance < 0.5f) { return Translation.GetString("Very_long_green/red_phases"); } else if (_waitFlowBalance < 0.75f) { return Translation.GetString("Long_green/red_phases"); } else if (_waitFlowBalance < 1.25f) { return Translation.GetString("Moderate_green/red_phases"); } else if (_waitFlowBalance < 1.5f) { return Translation.GetString("Short_green/red_phases"); } else if (_waitFlowBalance < 2.5f) { return Translation.GetString("Very_short_green/red_phases"); } else { return Translation.GetString("Extreme_short_green/red_phases"); } } private void DisableTimed() { if (SelectedNodeIds.Count <= 0) return; TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; foreach (var selectedNodeId in SelectedNodeIds) { tlsMan.RemoveNodeFromSimulation(selectedNodeId, true, false); RefreshCurrentTimedNodeIds(selectedNodeId); } } private void AddSelectedNode(ushort node) { SelectedNodeIds.Add(node); } private bool IsNodeSelected(ushort node) { return SelectedNodeIds.Contains(node); } private void RemoveSelectedNode(ushort node) { SelectedNodeIds.Remove(node); } private void ClearSelectedNodes() { SelectedNodeIds.Clear(); } private void drawStraightLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { switch (state) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(rect, TextureResources.GreenLightStraightTexture2D); break; case RoadBaseAI.TrafficLightState.GreenToRed: GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: default: GUI.DrawTexture(rect, TextureResources.RedLightStraightTexture2D); break; case RoadBaseAI.TrafficLightState.RedToGreen: GUI.DrawTexture(rect, TextureResources.YellowLightStraightTexture2D); break; } } private void drawForwardLeftLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { switch (state) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(rect, TextureResources.GreenLightForwardLeftTexture2D); break; case RoadBaseAI.TrafficLightState.GreenToRed: GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: default: GUI.DrawTexture(rect, TextureResources.RedLightForwardLeftTexture2D); break; case RoadBaseAI.TrafficLightState.RedToGreen: GUI.DrawTexture(rect, TextureResources.YellowLightForwardLeftTexture2D); break; } } private void drawForwardRightLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { switch (state) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(rect, TextureResources.GreenLightForwardRightTexture2D); break; case RoadBaseAI.TrafficLightState.GreenToRed: GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: default: GUI.DrawTexture(rect, TextureResources.RedLightForwardRightTexture2D); break; case RoadBaseAI.TrafficLightState.RedToGreen: GUI.DrawTexture(rect, TextureResources.YellowLightForwardRightTexture2D); break; } } private void drawLeftLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { switch (state) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(rect, TextureResources.GreenLightLeftTexture2D); break; case RoadBaseAI.TrafficLightState.GreenToRed: GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: default: GUI.DrawTexture(rect, TextureResources.RedLightLeftTexture2D); break; case RoadBaseAI.TrafficLightState.RedToGreen: GUI.DrawTexture(rect, TextureResources.YellowLightLeftTexture2D); break; } } private void drawRightLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { switch (state) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(rect, TextureResources.GreenLightRightTexture2D); break; case RoadBaseAI.TrafficLightState.GreenToRed: GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: default: GUI.DrawTexture(rect, TextureResources.RedLightRightTexture2D); break; case RoadBaseAI.TrafficLightState.RedToGreen: GUI.DrawTexture(rect, TextureResources.YellowLightRightTexture2D); break; } } private void drawMainLightTexture(RoadBaseAI.TrafficLightState state, Rect rect) { switch (state) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(rect, TextureResources.GreenLightTexture2D); break; case RoadBaseAI.TrafficLightState.GreenToRed: GUI.DrawTexture(rect, TextureResources.YellowLightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: default: GUI.DrawTexture(rect, TextureResources.RedLightTexture2D); break; case RoadBaseAI.TrafficLightState.RedToGreen: GUI.DrawTexture(rect, TextureResources.YellowRedLightTexture2D); break; } } public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { if (! ToolMode.TimedLightsShowLights.Equals(toolMode) && ! ToolMode.TimedLightsSelectNode.Equals(toolMode) && ! ToolMode.TimedLightsAddNode.Equals(toolMode) && ! ToolMode.TimedLightsRemoveNode.Equals(toolMode) && ! ToolMode.TimedLightsCopyLights.Equals(toolMode)) { // TODO refactor timed light related tool modes to sub tool modes return; } if (viewOnly && !Options.timedLightsOverlay) return; Vector3 camPos = Singleton.instance.m_simulationView.m_position; TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; foreach (ushort nodeId in currentTimedNodeIds) { if (!Constants.ServiceFactory.NetService.IsNodeValid((ushort)nodeId)) { continue; } if (SelectedNodeIds.Contains((ushort)nodeId)) { continue; } if (tlsMan.HasTimedSimulation((ushort)nodeId)) { ITimedTrafficLights timedNode = tlsMan.TrafficLightSimulations[nodeId].TimedLight; var nodePos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position; Texture2D tex = timedNode.IsStarted() ? (timedNode.IsInTestMode() ? TextureResources.ClockTestTexture2D : TextureResources.ClockPlayTexture2D) : TextureResources.ClockPauseTexture2D; MainTool.DrawGenericSquareOverlayTexture(tex, camPos, nodePos, 120f, false); } } } private void ShowGUI() { TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; JunctionRestrictionsManager junctionRestrictionsManager = JunctionRestrictionsManager.Instance; var hoveredSegment = false; foreach (var nodeId in SelectedNodeIds) { if (!tlsMan.HasTimedSimulation(nodeId)) { continue; } ITimedTrafficLights timedNode = tlsMan.TrafficLightSimulations[nodeId].TimedLight; var nodePos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position; Vector3 nodeScreenPos; bool nodeVisible = MainTool.WorldToScreenPoint(nodePos, out nodeScreenPos); if (!nodeVisible) continue; var diff = nodePos - Camera.main.transform.position; var zoom = 1.0f / diff.magnitude * 100f * MainTool.GetBaseZoom(); NodeGeometry nodeGeometry = NodeGeometry.Get(nodeId); foreach (SegmentEndGeometry end in nodeGeometry.SegmentEndGeometries) { if (end == null) continue; ushort srcSegmentId = end.SegmentId; // source segment ICustomSegmentLights liveSegmentLights = customTrafficLightsManager.GetSegmentLights(srcSegmentId, end.StartNode, false); if (liveSegmentLights == null) continue; bool showPedLight = liveSegmentLights.PedestrianLightState != null && junctionRestrictionsManager.IsPedestrianCrossingAllowed(liveSegmentLights.SegmentId, liveSegmentLights.StartNode); var timedActive = timedNode.IsStarted(); if (! timedActive) { liveSegmentLights.MakeRedOrGreen(); } var offset = 17f; Vector3 segmentLightPos = nodePos; if (Singleton.instance.m_segments.m_buffer[srcSegmentId].m_startNode == nodeId) { segmentLightPos.x += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_startDirection.x * offset; segmentLightPos.y += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_startDirection.y; segmentLightPos.z += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_startDirection.z * offset; } else { segmentLightPos.x += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_endDirection.x * offset; segmentLightPos.y += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_endDirection.y; segmentLightPos.z += Singleton.instance.m_segments.m_buffer[srcSegmentId].m_endDirection.z * offset; } Vector3 screenPos; bool segmentLightVisible = MainTool.WorldToScreenPoint(segmentLightPos, out screenPos); if (!segmentLightVisible) continue; var guiColor = GUI.color; var manualPedestrianWidth = 36f * zoom; var manualPedestrianHeight = 35f * zoom; var pedestrianWidth = 36f * zoom; var pedestrianHeight = 61f * zoom; // original / 2.5 var lightWidth = 41f * zoom; var lightHeight = 97f * zoom; // SWITCH MODE BUTTON var modeWidth = 41f * zoom; var modeHeight = 38f * zoom; if (showPedLight) { // pedestrian light // SWITCH MANUAL PEDESTRIAN LIGHT BUTTON if (!timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && (_hoveredButton[1] == 1 || _hoveredButton[1] == 2) && _hoveredNode == nodeId); GUI.color = guiColor; var myRect2 = new Rect(screenPos.x - manualPedestrianWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0) + 5f * zoom, screenPos.y - manualPedestrianHeight / 2 - 9f * zoom, manualPedestrianWidth, manualPedestrianHeight); GUI.DrawTexture(myRect2, liveSegmentLights.ManualPedestrianMode ? TextureResources.PedestrianModeManualTexture2D : TextureResources.PedestrianModeAutomaticTexture2D); if (myRect2.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 1; _hoveredNode = nodeId; hoveredSegment = true; if (MainTool.CheckClicked()) { liveSegmentLights.ManualPedestrianMode = !liveSegmentLights.ManualPedestrianMode; } } } // SWITCH PEDESTRIAN LIGHT guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 2 && _hoveredNode == nodeId); GUI.color = guiColor; var myRect3 = new Rect(screenPos.x - pedestrianWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0) + 5f * zoom, screenPos.y - pedestrianHeight / 2 + 22f * zoom, pedestrianWidth, pedestrianHeight); switch (liveSegmentLights.PedestrianLightState) { case RoadBaseAI.TrafficLightState.Green: GUI.DrawTexture(myRect3, TextureResources.PedestrianGreenLightTexture2D); break; case RoadBaseAI.TrafficLightState.Red: default: GUI.DrawTexture(myRect3, TextureResources.PedestrianRedLightTexture2D); break; } if (myRect3.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 2; _hoveredNode = nodeId; hoveredSegment = true; if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { if (!liveSegmentLights.ManualPedestrianMode) { liveSegmentLights.ManualPedestrianMode = true; } else { liveSegmentLights.ChangeLightPedestrian(); } } } } int lightOffset = -1; foreach (ExtVehicleType vehicleType in liveSegmentLights.VehicleTypes) { HashSet laneIndices = new HashSet(); for (byte laneIndex = 0; laneIndex < liveSegmentLights.VehicleTypeByLaneIndex.Length; ++laneIndex) { if (liveSegmentLights.VehicleTypeByLaneIndex[laneIndex] == vehicleType) { laneIndices.Add(laneIndex); } } //Log._Debug($"Traffic light @ seg. {srcSegmentId} node {nodeId}. Lane indices for vehicleType {vehicleType}: {string.Join(",", laneIndices.Select(x => x.ToString()).ToArray())}"); ++lightOffset; ICustomSegmentLight liveSegmentLight = liveSegmentLights.GetCustomLight(vehicleType); Vector3 offsetScreenPos = screenPos; offsetScreenPos.y -= (lightHeight + 10f * zoom) * lightOffset; if (!timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == -1 && _hoveredNode == nodeId); GUI.color = guiColor; var myRect1 = new Rect(offsetScreenPos.x - modeWidth / 2, offsetScreenPos.y - modeHeight / 2 + modeHeight - 7f * zoom, modeWidth, modeHeight); GUI.DrawTexture(myRect1, TextureResources.LightModeTexture2D); if (myRect1.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = -1; _hoveredNode = nodeId; hoveredSegment = true; if (MainTool.CheckClicked()) { liveSegmentLight.ToggleMode(); timedNode.ChangeLightMode(srcSegmentId, vehicleType, liveSegmentLight.CurrentMode); } } } if (vehicleType != ExtVehicleType.None) { // Info sign var infoWidth = 56.125f * zoom; var infoHeight = 51.375f * zoom; int numInfos = 0; for (int k = 0; k < TrafficManagerTool.InfoSignsToDisplay.Length; ++k) { if ((TrafficManagerTool.InfoSignsToDisplay[k] & vehicleType) == ExtVehicleType.None) continue; var infoRect = new Rect(offsetScreenPos.x + modeWidth / 2f + 7f * zoom * (float)(numInfos + 1) + infoWidth * (float)numInfos, offsetScreenPos.y - infoHeight / 2f, infoWidth, infoHeight); guiColor.a = MainTool.GetHandleAlpha(false); GUI.DrawTexture(infoRect, TextureResources.VehicleInfoSignTextures[TrafficManagerTool.InfoSignsToDisplay[k]]); ++numInfos; } } // Draw light index /*if (!timedActive && _timedEditStep < 0 && lightOffset == 0) { var indexSize = 20f * zoom; var yOffset = indexSize + 77f * zoom - modeHeight * 2; //var carNumRect = new Rect(offsetScreenPos.x, offsetScreenPos.y - yOffset, counterSize, counterSize); var segIndexRect = new Rect(offsetScreenPos.x, offsetScreenPos.y - yOffset - indexSize - 2f, indexSize, indexSize); _counterStyle.fontSize = (int)(15f * zoom); _counterStyle.normal.textColor = new Color(0f, 0f, 1f); GUI.Label(segIndexRect, $"#{liveSegmentLight.ClockwiseIndex+1}", _counterStyle); }*/ #if DEBUG if (timedActive /*&& _timedShowNumbers*/) { //var prioSeg = TrafficPriorityManager.Instance.GetPrioritySegment(nodeId, srcSegmentId); var counterSize = 20f * zoom; var yOffset = counterSize + 77f * zoom - modeHeight * 2; //var carNumRect = new Rect(offsetScreenPos.x, offsetScreenPos.y - yOffset, counterSize, counterSize); var segIdRect = new Rect(offsetScreenPos.x, offsetScreenPos.y - yOffset - counterSize - 2f, counterSize, counterSize); _counterStyle.fontSize = (int)(15f * zoom); _counterStyle.normal.textColor = new Color(1f, 0f, 0f); /*String labelStr = "n/a"; if (prioSeg != null) { labelStr = prioSeg.GetRegisteredVehicleCount(laneIndices).ToString() + " " + Translation.GetString("incoming"); } GUI.Label(carNumRect, labelStr, _counterStyle);*/ _counterStyle.normal.textColor = new Color(1f, 0f, 0f); GUI.Label(segIdRect, Translation.GetString("Segment") + " " + srcSegmentId, _counterStyle); } #endif if (lightOffset == 0 && showPedLight) { // PEDESTRIAN COUNTER if (timedActive && _timedShowNumbers) { var counterSize = 20f * zoom; var counter = timedNode.CheckNextChange(srcSegmentId, end.StartNode, vehicleType, 3); float numOffset; if (liveSegmentLights.PedestrianLightState == RoadBaseAI.TrafficLightState.Red) { // TODO check this numOffset = counterSize + 53f * zoom - modeHeight * 2; } else { numOffset = counterSize + 29f * zoom - modeHeight * 2; } var myRectCounterNum = new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 1f) + 24f * zoom - pedestrianWidth / 2, offsetScreenPos.y - numOffset, counterSize, counterSize); _counterStyle.fontSize = (int)(15f * zoom); _counterStyle.normal.textColor = new Color(1f, 1f, 1f); GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 2; _hoveredNode = nodeId; hoveredSegment = true; } } } SegmentGeometry geometry = SegmentGeometry.Get(srcSegmentId); if (geometry == null) { Log.Error($"TimedTrafficLightsTool.ShowGUI: No geometry information available for segment {srcSegmentId}"); continue; } bool startNode = geometry.StartNodeId() == nodeId; if (geometry.IsOutgoingOneWay(startNode)) continue; var hasOutgoingLeftSegment = geometry.HasOutgoingLeftSegment(startNode); var hasOutgoingForwardSegment = geometry.HasOutgoingStraightSegment(startNode); var hasOutgoingRightSegment = geometry.HasOutgoingRightSegment(startNode); /*var hasLeftSegment = geometry.HasLeftSegment(startNode); var hasForwardSegment = geometry.HasStraightSegment(startNode); var hasRightSegment = geometry.HasRightSegment(startNode);*/ bool hasOtherLight = false; switch (liveSegmentLight.CurrentMode) { case LightMode.Simple: { // no arrow light guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); GUI.color = guiColor; var myRect4 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0) - pedestrianWidth + 5f * zoom, offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); drawMainLightTexture(liveSegmentLight.LightMain, myRect4); if (myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 3; _hoveredNode = nodeId; hoveredSegment = true; if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { liveSegmentLight.ChangeMainLight(); } } // COUNTER if (timedActive && _timedShowNumbers) { var counterSize = 20f * zoom; var counter = timedNode.CheckNextChange(srcSegmentId, end.StartNode, vehicleType, 0); float numOffset; if (liveSegmentLight.LightMain == RoadBaseAI.TrafficLightState.Red) { numOffset = counterSize + 96f * zoom - modeHeight * 2; } else { numOffset = counterSize + 40f * zoom - modeHeight * 2; } var myRectCounterNum = new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom, offsetScreenPos.y - numOffset, counterSize, counterSize); _counterStyle.fontSize = (int)(18f * zoom); _counterStyle.normal.textColor = new Color(1f, 1f, 1f); GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 3; _hoveredNode = nodeId; hoveredSegment = true; } } GUI.color = guiColor; } break; case LightMode.SingleLeft: if (hasOutgoingLeftSegment) { // left arrow light guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); GUI.color = guiColor; var myRect4 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth * 2 : lightWidth) - pedestrianWidth + 5f * zoom, offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); drawLeftLightTexture(liveSegmentLight.LightLeft, myRect4); if (myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 3; _hoveredNode = nodeId; hoveredSegment = true; if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { liveSegmentLight.ChangeLeftLight(); } } // COUNTER if (timedActive && _timedShowNumbers) { var counterSize = 20f * zoom; var counter = timedNode.CheckNextChange(srcSegmentId, end.StartNode, vehicleType, 1); float numOffset; if (liveSegmentLight.LightLeft == RoadBaseAI.TrafficLightState.Red) { numOffset = counterSize + 96f * zoom - modeHeight * 2; } else { numOffset = counterSize + 40f * zoom - modeHeight * 2; } var myRectCounterNum = new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth * 2 : lightWidth), offsetScreenPos.y - numOffset, counterSize, counterSize); _counterStyle.fontSize = (int)(18f * zoom); _counterStyle.normal.textColor = new Color(1f, 1f, 1f); GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 3; _hoveredNode = nodeId; hoveredSegment = true; } } } // forward-right arrow light guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 4 && _hoveredNode == nodeId); GUI.color = guiColor; var myRect5 = new Rect(offsetScreenPos.x - lightWidth / 2 - pedestrianWidth - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) + 5f * zoom, offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); if (hasOutgoingForwardSegment && hasOutgoingRightSegment) { drawForwardRightLightTexture(liveSegmentLight.LightMain, myRect5); hasOtherLight = true; } else if (hasOutgoingForwardSegment) { drawStraightLightTexture(liveSegmentLight.LightMain, myRect5); hasOtherLight = true; } else if (hasOutgoingRightSegment) { drawRightLightTexture(liveSegmentLight.LightMain, myRect5); hasOtherLight = true; } if (hasOtherLight && myRect5.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 4; _hoveredNode = nodeId; hoveredSegment = true; if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { liveSegmentLight.ChangeMainLight(); } } // COUNTER if (timedActive && _timedShowNumbers) { var counterSize = 20f * zoom; var counter = timedNode.CheckNextChange(srcSegmentId, end.StartNode, vehicleType, 0); float numOffset; if (liveSegmentLight.LightMain == RoadBaseAI.TrafficLightState.Red) { numOffset = counterSize + 96f * zoom - modeHeight * 2; } else { numOffset = counterSize + 40f * zoom - modeHeight * 2; } var myRectCounterNum = new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f), offsetScreenPos.y - numOffset, counterSize, counterSize); _counterStyle.fontSize = (int)(18f * zoom); _counterStyle.normal.textColor = new Color(1f, 1f, 1f); GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 4; _hoveredNode = nodeId; hoveredSegment = true; } } break; case LightMode.SingleRight: { // forward-left light guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); GUI.color = guiColor; var myRect4 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth * 2 : lightWidth) - pedestrianWidth + 5f * zoom, offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); var lightType = 0; hasOtherLight = false; if (hasOutgoingForwardSegment && hasOutgoingLeftSegment) { hasOtherLight = true; drawForwardLeftLightTexture(liveSegmentLight.LightMain, myRect4); lightType = 1; } else if (hasOutgoingForwardSegment) { hasOtherLight = true; if (!hasOutgoingRightSegment) { myRect4 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); } drawStraightLightTexture(liveSegmentLight.LightMain, myRect4); } else if (hasOutgoingLeftSegment) { hasOtherLight = true; if (!hasOutgoingRightSegment) { myRect4 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); } drawLeftLightTexture(liveSegmentLight.LightMain, myRect4); } if (hasOtherLight && myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 3; _hoveredNode = nodeId; hoveredSegment = true; if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { liveSegmentLight.ChangeMainLight(); } } // COUNTER if (timedActive && _timedShowNumbers) { var counterSize = 20f * zoom; var counter = timedNode.CheckNextChange(srcSegmentId, end.StartNode, vehicleType, lightType); float numOffset; if (liveSegmentLight.LightMain == RoadBaseAI.TrafficLightState.Red) { numOffset = counterSize + 96f * zoom - modeHeight * 2; } else { numOffset = counterSize + 40f * zoom - modeHeight * 2; } var myRectCounterNum = new Rect(offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? (hasOutgoingRightSegment ? lightWidth * 2 : lightWidth) : (hasOutgoingRightSegment ? lightWidth : 0f)), offsetScreenPos.y - numOffset, counterSize, counterSize); _counterStyle.fontSize = (int)(18f * zoom); _counterStyle.normal.textColor = new Color(1f, 1f, 1f); GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 3; _hoveredNode = nodeId; hoveredSegment = true; } } // right arrow light if (hasOutgoingRightSegment) { guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 4 && _hoveredNode == nodeId); GUI.color = guiColor; var rect5 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); drawRightLightTexture(liveSegmentLight.LightRight, rect5); if (rect5.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 4; _hoveredNode = nodeId; hoveredSegment = true; if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { liveSegmentLight.ChangeRightLight(); } } // COUNTER if (timedActive && _timedShowNumbers) { var counterSize = 20f * zoom; var counter = timedNode.CheckNextChange(srcSegmentId, end.StartNode, vehicleType, 2); float numOffset; if (liveSegmentLight.LightRight == RoadBaseAI.TrafficLightState.Red) { numOffset = counterSize + 96f * zoom - modeHeight * 2; } else { numOffset = counterSize + 40f * zoom - modeHeight * 2; } var myRectCounterNum = new Rect( offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f), offsetScreenPos.y - numOffset, counterSize, counterSize); _counterStyle.fontSize = (int)(18f * zoom); _counterStyle.normal.textColor = new Color(1f, 1f, 1f); GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 4; _hoveredNode = nodeId; hoveredSegment = true; } } } } break; default: // left arrow light if (hasOutgoingLeftSegment) { guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); GUI.color = guiColor; var offsetLight = lightWidth; if (hasOutgoingRightSegment) offsetLight += lightWidth; if (hasOutgoingForwardSegment) offsetLight += lightWidth; var myRect4 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? offsetLight : offsetLight - lightWidth) - pedestrianWidth + 5f * zoom, offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); drawLeftLightTexture(liveSegmentLight.LightLeft, myRect4); if (myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 3; _hoveredNode = nodeId; hoveredSegment = true; if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { liveSegmentLight.ChangeLeftLight(); } } // COUNTER if (timedActive && _timedShowNumbers) { var counterSize = 20f * zoom; var counter = timedNode.CheckNextChange(srcSegmentId, end.StartNode, vehicleType, 1); float numOffset; if (liveSegmentLight.LightLeft == RoadBaseAI.TrafficLightState.Red) { numOffset = counterSize + 96f * zoom - modeHeight * 2; } else { numOffset = counterSize + 40f * zoom - modeHeight * 2; } var myRectCounterNum = new Rect( offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? offsetLight : offsetLight - lightWidth), offsetScreenPos.y - numOffset, counterSize, counterSize); _counterStyle.fontSize = (int)(18f * zoom); _counterStyle.normal.textColor = new Color(1f, 1f, 1f); GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 3; _hoveredNode = nodeId; hoveredSegment = true; } } } // forward arrow light if (hasOutgoingForwardSegment) { guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 4 && _hoveredNode == nodeId); GUI.color = guiColor; var offsetLight = lightWidth; if (hasOutgoingRightSegment) offsetLight += lightWidth; var myRect6 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? offsetLight : offsetLight - lightWidth) - pedestrianWidth + 5f * zoom, offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); drawStraightLightTexture(liveSegmentLight.LightMain, myRect6); if (myRect6.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 4; _hoveredNode = nodeId; hoveredSegment = true; if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { liveSegmentLight.ChangeMainLight(); } } // COUNTER if (timedActive && _timedShowNumbers) { var counterSize = 20f * zoom; var counter = timedNode.CheckNextChange(srcSegmentId, end.StartNode, vehicleType, 0); float numOffset; if (liveSegmentLight.LightMain == RoadBaseAI.TrafficLightState.Red) { numOffset = counterSize + 96f * zoom - modeHeight * 2; } else { numOffset = counterSize + 40f * zoom - modeHeight * 2; } var myRectCounterNum = new Rect( offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? offsetLight : offsetLight - lightWidth), offsetScreenPos.y - numOffset, counterSize, counterSize); _counterStyle.fontSize = (int)(18f * zoom); _counterStyle.normal.textColor = new Color(1f, 1f, 1f); GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 4; _hoveredNode = nodeId; hoveredSegment = true; } } } // right arrow light if (hasOutgoingRightSegment) { guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 5 && _hoveredNode == nodeId); GUI.color = guiColor; var rect6 = new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); drawRightLightTexture(liveSegmentLight.LightRight, rect6); if (rect6.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 5; _hoveredNode = nodeId; hoveredSegment = true; if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { liveSegmentLight.ChangeRightLight(); } } // COUNTER if (timedActive && _timedShowNumbers) { var counterSize = 20f * zoom; var counter = timedNode.CheckNextChange(srcSegmentId, end.StartNode, vehicleType, 2); float numOffset; if (liveSegmentLight.LightRight == RoadBaseAI.TrafficLightState.Red) { numOffset = counterSize + 96f * zoom - modeHeight * 2; } else { numOffset = counterSize + 40f * zoom - modeHeight * 2; } var myRectCounterNum = new Rect( offsetScreenPos.x - counterSize + 15f * zoom + (counter >= 10 ? (counter >= 100 ? -10 * zoom : -5 * zoom) : 0f) - pedestrianWidth + 5f * zoom - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f), offsetScreenPos.y - numOffset, counterSize, counterSize); _counterStyle.fontSize = (int)(18f * zoom); _counterStyle.normal.textColor = new Color(1f, 1f, 1f); GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { _hoveredButton[0] = srcSegmentId; _hoveredButton[1] = 5; _hoveredNode = nodeId; hoveredSegment = true; } } } break; } // end switch liveSegmentLight.CurrentMode } // end foreach light } // end foreach segment } // end foreach node if (!hoveredSegment) { _hoveredButton[0] = 0; _hoveredButton[1] = 0; } } } } ================================================ FILE: TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.State; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using UnityEngine; using TrafficManager.Manager; using TrafficManager.Manager.Impl; namespace TrafficManager.UI.SubTools { public class ToggleTrafficLightsTool : SubTool { public ToggleTrafficLightsTool(TrafficManagerTool mainTool) : base(mainTool) { } public override void OnPrimaryClickOverlay() { if (IsCursorInPanel()) return; if (HoveredNodeId == 0) return; Constants.ServiceFactory.NetService.ProcessNode(HoveredNodeId, delegate (ushort nId, ref NetNode node) { ToggleTrafficLight(HoveredNodeId, ref node); return true; }); } public void ToggleTrafficLight(ushort nodeId, ref NetNode node, bool showMessageOnError=true) { UnableReason reason; if (!TrafficLightManager.Instance.IsTrafficLightToggleable(nodeId, !TrafficLightManager.Instance.HasTrafficLight(nodeId, ref node), ref node, out reason)) { if (showMessageOnError) { switch (reason) { case UnableReason.HasTimedLight: MainTool.ShowTooltip(Translation.GetString("NODE_IS_TIMED_LIGHT")); break; case UnableReason.IsLevelCrossing: MainTool.ShowTooltip(Translation.GetString("Node_is_level_crossing")); break; default: break; } } return; } TrafficPriorityManager.Instance.RemovePrioritySignsFromNode(nodeId); TrafficLightManager.Instance.ToggleTrafficLight(nodeId, ref node); } public override void OnToolGUI(Event e) { } public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { if (MainTool.GetToolController().IsInsideUI || !Cursor.visible) { return; } if (HoveredNodeId == 0) return; if (!Flags.mayHaveTrafficLight(HoveredNodeId)) return; MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0), false); } } } ================================================ FILE: TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs ================================================ using ColossalFramework; using ColossalFramework.Math; using ColossalFramework.UI; using GenericGameBridge.Service; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Geometry; using TrafficManager.Manager; using TrafficManager.Manager.Impl; using TrafficManager.State; using TrafficManager.Traffic; using TrafficManager.TrafficLight; using TrafficManager.Util; using UnityEngine; using static TrafficManager.UI.TrafficManagerTool; using static TrafficManager.Util.SegmentLaneTraverser; namespace TrafficManager.UI.SubTools { public class VehicleRestrictionsTool : SubTool { private static ExtVehicleType[] roadVehicleTypes = new ExtVehicleType[] { ExtVehicleType.PassengerCar, ExtVehicleType.Bus, ExtVehicleType.Taxi, ExtVehicleType.CargoTruck, ExtVehicleType.Service, ExtVehicleType.Emergency }; private static ExtVehicleType[] railVehicleTypes = new ExtVehicleType[] { ExtVehicleType.PassengerTrain, ExtVehicleType.CargoTrain }; private readonly float vehicleRestrictionsSignSize = 80f; private bool _cursorInSecondaryPanel; private bool overlayHandleHovered; private Rect windowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 620, 100)); private HashSet currentRestrictedSegmentIds; public VehicleRestrictionsTool(TrafficManagerTool mainTool) : base(mainTool) { currentRestrictedSegmentIds = new HashSet(); } public override void OnActivate() { _cursorInSecondaryPanel = false; RefreshCurrentRestrictedSegmentIds(); } private void RefreshCurrentRestrictedSegmentIds(ushort forceSegmentId=0) { if (forceSegmentId == 0) { currentRestrictedSegmentIds.Clear(); } else { currentRestrictedSegmentIds.Remove(forceSegmentId); } for (uint segmentId = (forceSegmentId == 0 ? 1u : forceSegmentId); segmentId <= (forceSegmentId == 0 ? NetManager.MAX_SEGMENT_COUNT - 1 : forceSegmentId); ++segmentId) { if (!Constants.ServiceFactory.NetService.IsSegmentValid((ushort)segmentId)) { continue; } if (VehicleRestrictionsManager.Instance.HasSegmentRestrictions((ushort)segmentId)) currentRestrictedSegmentIds.Add((ushort)segmentId); } } public override void Cleanup() { } public override void Initialize() { base.Initialize(); Cleanup(); if (Options.vehicleRestrictionsOverlay) { RefreshCurrentRestrictedSegmentIds(); } else { currentRestrictedSegmentIds.Clear(); } } public override bool IsCursorInPanel() { return base.IsCursorInPanel() || _cursorInSecondaryPanel; } public override void OnPrimaryClickOverlay() { //Log._Debug($"Restrictions: {HoveredSegmentId} {overlayHandleHovered}"); if (HoveredSegmentId == 0) return; if (overlayHandleHovered) return; SelectedSegmentId = HoveredSegmentId; currentRestrictedSegmentIds.Add(SelectedSegmentId); MainTool.CheckClicked(); // TODO do we need that? } public override void OnSecondaryClickOverlay() { if (!IsCursorInPanel()) { SelectedSegmentId = 0; } } public override void OnToolGUI(Event e) { base.OnToolGUI(e); if (SelectedSegmentId != 0) { _cursorInSecondaryPanel = false; windowRect = GUILayout.Window(255, windowRect, _guiVehicleRestrictionsWindow, Translation.GetString("Vehicle_restrictions"), WindowStyle); _cursorInSecondaryPanel = windowRect.Contains(Event.current.mousePosition); //overlayHandleHovered = false; } //ShowSigns(false); } public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { //Log._Debug($"Restrictions overlay {_cursorInSecondaryPanel} {HoveredNodeId} {SelectedNodeId} {HoveredSegmentId} {SelectedSegmentId}"); if (SelectedSegmentId != 0) NetTool.RenderOverlay(cameraInfo, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], MainTool.GetToolColor(true, false), MainTool.GetToolColor(true, false)); if (_cursorInSecondaryPanel) return; if (HoveredSegmentId != 0 && HoveredSegmentId != SelectedSegmentId && !overlayHandleHovered) { NetTool.RenderOverlay(cameraInfo, ref Singleton.instance.m_segments.m_buffer[HoveredSegmentId], MainTool.GetToolColor(false, false), MainTool.GetToolColor(false, false)); } } public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { if (viewOnly && !Options.vehicleRestrictionsOverlay) return; ShowSigns(viewOnly); } private void ShowSigns(bool viewOnly) { Vector3 camPos = Camera.main.transform.position; NetManager netManager = Singleton.instance; ushort updatedSegmentId = 0; bool handleHovered = false; foreach (ushort segmentId in currentRestrictedSegmentIds) { if (!Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { continue; } var segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; Vector3 centerPos = netManager.m_segments.m_buffer[segmentId].m_bounds.center; Vector3 screenPos; bool visible = MainTool.WorldToScreenPoint(centerPos, out screenPos); if (!visible) continue; if ((netManager.m_segments.m_buffer[segmentId].m_bounds.center - camPos).magnitude > TrafficManagerTool.MaxOverlayDistance) continue; // do not draw if too distant // draw vehicle restrictions bool update; if (drawVehicleRestrictionHandles(segmentId, ref netManager.m_segments.m_buffer[segmentId], viewOnly || segmentId != SelectedSegmentId, out update)) handleHovered = true; if (update) { updatedSegmentId = segmentId; } } overlayHandleHovered = handleHovered; if (updatedSegmentId != 0) { RefreshCurrentRestrictedSegmentIds(updatedSegmentId); } } private void _guiVehicleRestrictionsWindow(int num) { if (GUILayout.Button(Translation.GetString("Invert"))) { // invert pattern NetInfo selectedSegmentInfo = Singleton.instance.m_segments.m_buffer[SelectedSegmentId].Info; IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(SelectedSegmentId, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], null, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES); // TODO does not need to be sorted, but every lane should be a vehicle lane foreach (LanePos laneData in sortedLanes) { uint laneId = laneData.laneId; byte laneIndex = laneData.laneIndex; NetInfo.Lane laneInfo = selectedSegmentInfo.m_lanes[laneIndex]; ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); if (baseMask == ExtVehicleType.None) continue; ExtVehicleType allowedTypes = VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); allowedTypes = ~(allowedTypes & VehicleRestrictionsManager.EXT_VEHICLE_TYPES) & baseMask; VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, allowedTypes); } RefreshCurrentRestrictedSegmentIds(SelectedSegmentId); } GUILayout.BeginHorizontal(); if (GUILayout.Button(Translation.GetString("Allow_all_vehicles"))) { // allow all vehicle types NetInfo selectedSegmentInfo = Singleton.instance.m_segments.m_buffer[SelectedSegmentId].Info; IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(SelectedSegmentId, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], null, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES); // TODO does not need to be sorted, but every lane should be a vehicle lane foreach (LanePos laneData in sortedLanes) { uint laneId = laneData.laneId; byte laneIndex = laneData.laneIndex; NetInfo.Lane laneInfo = selectedSegmentInfo.m_lanes[laneIndex]; ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); if (baseMask == ExtVehicleType.None) continue; VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, baseMask); } RefreshCurrentRestrictedSegmentIds(SelectedSegmentId); } if (GUILayout.Button(Translation.GetString("Ban_all_vehicles"))) { // ban all vehicle types NetInfo selectedSegmentInfo = Singleton.instance.m_segments.m_buffer[SelectedSegmentId].Info; IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(SelectedSegmentId, ref Singleton.instance.m_segments.m_buffer[SelectedSegmentId], null, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES); // TODO does not need to be sorted, but every lane should be a vehicle lane foreach (LanePos laneData in sortedLanes) { uint laneId = laneData.laneId; byte laneIndex = laneData.laneIndex; NetInfo.Lane laneInfo = selectedSegmentInfo.m_lanes[laneIndex]; ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); if (baseMask == ExtVehicleType.None) continue; VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, ~VehicleRestrictionsManager.EXT_VEHICLE_TYPES & baseMask); } RefreshCurrentRestrictedSegmentIds(SelectedSegmentId); } GUILayout.EndHorizontal(); if (GUILayout.Button(Translation.GetString("Apply_vehicle_restrictions_to_all_road_segments_between_two_junctions"))) { ApplyRestrictionsToAllSegments(); } DragWindow(ref windowRect); } private void ApplyRestrictionsToAllSegments(int? sortedLaneIndex=null) { NetManager netManager = Singleton.instance; NetInfo selectedSegmentInfo = netManager.m_segments.m_buffer[SelectedSegmentId].Info; ushort selectedStartNodeId = netManager.m_segments.m_buffer[SelectedSegmentId].m_startNode; ushort selectedEndNodeId = netManager.m_segments.m_buffer[SelectedSegmentId].m_endNode; SegmentLaneTraverser.Traverse(SelectedSegmentId, SegmentTraverser.TraverseDirection.AnyDirection, SegmentTraverser.TraverseSide.AnySide, SegmentLaneTraverser.LaneStopCriterion.LaneCount, SegmentTraverser.SegmentStopCriterion.Junction, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES, delegate (SegmentLaneVisitData data) { if (data.segVisitData.initial) { return true; } if (sortedLaneIndex != null && data.sortedLaneIndex != sortedLaneIndex) { return true; } ushort segmentId = data.segVisitData.curGeo.SegmentId; NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; uint selectedLaneId = data.initLanePos.laneId; byte selectedLaneIndex = data.initLanePos.laneIndex; NetInfo.Lane selectedLaneInfo = selectedSegmentInfo.m_lanes[selectedLaneIndex]; uint laneId = data.curLanePos.laneId; byte laneIndex = data.curLanePos.laneIndex; NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); if (baseMask == ExtVehicleType.None) { return true; } // apply restrictions of selected segment & lane ExtVehicleType mask = ~VehicleRestrictionsManager.EXT_VEHICLE_TYPES & baseMask; // ban all possible controllable vehicles mask |= VehicleRestrictionsManager.EXT_VEHICLE_TYPES & VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, selectedLaneIndex, selectedLaneInfo, VehicleRestrictionsMode.Configured); // allow all enabled and controllable vehicles VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, laneId, mask); RefreshCurrentRestrictedSegmentIds(segmentId); return true; }); } private bool drawVehicleRestrictionHandles(ushort segmentId, ref NetSegment segment, bool viewOnly, out bool stateUpdated) { stateUpdated = false; if (viewOnly && !Options.vehicleRestrictionsOverlay && MainTool.GetToolMode() != ToolMode.VehicleRestrictions) return false; Vector3 center = segment.m_bounds.center; Vector3 screenPos; bool visible = MainTool.WorldToScreenPoint(center, out screenPos); if (!visible) return false; var camPos = Singleton.instance.m_simulationView.m_position; var diff = center - camPos; if (diff.magnitude > TrafficManagerTool.MaxOverlayDistance) return false; // do not draw if too distant int numDirections; int numLanes = TrafficManagerTool.GetSegmentNumVehicleLanes(segmentId, null, out numDirections, VehicleRestrictionsManager.VEHICLE_TYPES); // draw vehicle restrictions over each lane NetInfo segmentInfo = segment.Info; Vector3 yu = (segment.m_endDirection - segment.m_startDirection).normalized; /*if ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) yu = -yu;*/ Vector3 xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized; float f = viewOnly ? 4f : 7f; // reserved sign size in game coordinates int maxNumSigns = 0; if (VehicleRestrictionsManager.Instance.IsRoadSegment(segmentInfo)) maxNumSigns = roadVehicleTypes.Length; else if (VehicleRestrictionsManager.Instance.IsRailSegment(segmentInfo)) maxNumSigns = railVehicleTypes.Length; //Vector3 zero = center - 0.5f * (float)(numLanes + numDirections - 1) * f * (xu + yu); // "bottom left" Vector3 zero = center - 0.5f * (float)(numLanes - 1 + numDirections - 1) * f * xu - 0.5f * (float)maxNumSigns * f * yu; // "bottom left" /*if (!viewOnly) Log._Debug($"xu: {xu.ToString()} yu: {yu.ToString()} center: {center.ToString()} zero: {zero.ToString()} numLanes: {numLanes} numDirections: {numDirections}");*/ uint x = 0; var guiColor = GUI.color; IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(segmentId, ref segment, null, VehicleRestrictionsManager.LANE_TYPES, VehicleRestrictionsManager.VEHICLE_TYPES); bool hovered = false; HashSet directions = new HashSet(); int sortedLaneIndex = -1; foreach (LanePos laneData in sortedLanes) { ++sortedLaneIndex; uint laneId = laneData.laneId; byte laneIndex = laneData.laneIndex; NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; if (!directions.Contains(laneInfo.m_finalDirection)) { if (directions.Count > 0) ++x; // space between different directions directions.Add(laneInfo.m_finalDirection); } ExtVehicleType[] possibleVehicleTypes = null; if (VehicleRestrictionsManager.Instance.IsRoadLane(laneInfo)) { possibleVehicleTypes = roadVehicleTypes; } else if (VehicleRestrictionsManager.Instance.IsRailLane(laneInfo)) { possibleVehicleTypes = railVehicleTypes; } else { ++x; continue; } ExtVehicleType allowedTypes = VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(segmentId, segmentInfo, laneIndex, laneInfo, VehicleRestrictionsMode.Configured); uint y = 0; #if DEBUGx Vector3 labelCenter = zero + f * (float)x * xu + f * (float)y * yu; // in game coordinates Vector3 labelScreenPos; bool visible = MainTool.WorldToScreenPoint(labelCenter, out labelScreenPos); labelScreenPos.y = Screen.height - labelScreenPos.y; diff = labelCenter - camPos; var labelZoom = 1.0f / diff.magnitude * 100f; _counterStyle.fontSize = (int)(11f * labelZoom); _counterStyle.normal.textColor = new Color(1f, 1f, 0f); string labelStr = $"Idx {laneIndex}"; Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); Rect labelRect = new Rect(labelScreenPos.x - dim.x / 2f, labelScreenPos.y, dim.x, dim.y); GUI.Label(labelRect, labelStr, _counterStyle); ++y; #endif foreach (ExtVehicleType vehicleType in possibleVehicleTypes) { bool allowed = VehicleRestrictionsManager.Instance.IsAllowed(allowedTypes, vehicleType); if (allowed && viewOnly) continue; // do not draw allowed vehicles in view-only mode bool hoveredHandle = MainTool.DrawGenericSquareOverlayGridTexture(TextureResources.VehicleRestrictionTextures[vehicleType][allowed], camPos, zero, f, xu, yu, x, y, vehicleRestrictionsSignSize, !viewOnly); if (hoveredHandle) hovered = true; if (hoveredHandle && MainTool.CheckClicked()) { // toggle vehicle restrictions //Log._Debug($"Setting vehicle restrictions of segment {segmentId}, lane idx {laneIndex}, {vehicleType.ToString()} to {!allowed}"); VehicleRestrictionsManager.Instance.ToggleAllowedType(segmentId, segmentInfo, laneIndex, laneId, laneInfo, vehicleType, !allowed); stateUpdated = true; if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) { ApplyRestrictionsToAllSegments(sortedLaneIndex); } } ++y; } ++x; } guiColor.a = 1f; GUI.color = guiColor; return hovered; } } } ================================================ FILE: TLM/TLM/UI/TextureResources.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using TrafficManager.Geometry; using TrafficManager.Manager; using TrafficManager.Manager.Impl; using TrafficManager.Traffic; using TrafficManager.UI; using TrafficManager.Util; using UnityEngine; using static TrafficManager.Traffic.Data.PrioritySegment; namespace TrafficManager.UI { public class TextureResources { public static readonly Texture2D RedLightTexture2D; public static readonly Texture2D YellowRedLightTexture2D; public static readonly Texture2D YellowLightTexture2D; public static readonly Texture2D GreenLightTexture2D; public static readonly Texture2D RedLightStraightTexture2D; public static readonly Texture2D YellowLightStraightTexture2D; public static readonly Texture2D GreenLightStraightTexture2D; public static readonly Texture2D RedLightRightTexture2D; public static readonly Texture2D YellowLightRightTexture2D; public static readonly Texture2D GreenLightRightTexture2D; public static readonly Texture2D RedLightLeftTexture2D; public static readonly Texture2D YellowLightLeftTexture2D; public static readonly Texture2D GreenLightLeftTexture2D; public static readonly Texture2D RedLightForwardRightTexture2D; public static readonly Texture2D YellowLightForwardRightTexture2D; public static readonly Texture2D GreenLightForwardRightTexture2D; public static readonly Texture2D RedLightForwardLeftTexture2D; public static readonly Texture2D YellowLightForwardLeftTexture2D; public static readonly Texture2D GreenLightForwardLeftTexture2D; public static readonly Texture2D PedestrianRedLightTexture2D; public static readonly Texture2D PedestrianGreenLightTexture2D; public static readonly Texture2D LightModeTexture2D; public static readonly Texture2D LightCounterTexture2D; public static readonly Texture2D PedestrianModeAutomaticTexture2D; public static readonly Texture2D PedestrianModeManualTexture2D; public static readonly IDictionary PrioritySignTextures; public static readonly Texture2D SignRemoveTexture2D; public static readonly Texture2D ClockPlayTexture2D; public static readonly Texture2D ClockPauseTexture2D; public static readonly Texture2D ClockTestTexture2D; public static readonly IDictionary SpeedLimitTextures; public static readonly IDictionary> VehicleRestrictionTextures; public static readonly IDictionary VehicleInfoSignTextures; public static readonly IDictionary ParkingRestrictionTextures; public static readonly Texture2D LaneChangeForbiddenTexture2D; public static readonly Texture2D LaneChangeAllowedTexture2D; public static readonly Texture2D UturnAllowedTexture2D; public static readonly Texture2D UturnForbiddenTexture2D; public static readonly Texture2D RightOnRedForbiddenTexture2D; public static readonly Texture2D RightOnRedAllowedTexture2D; public static readonly Texture2D LeftOnRedForbiddenTexture2D; public static readonly Texture2D LeftOnRedAllowedTexture2D; public static readonly Texture2D EnterBlockedJunctionAllowedTexture2D; public static readonly Texture2D EnterBlockedJunctionForbiddenTexture2D; public static readonly Texture2D PedestrianCrossingAllowedTexture2D; public static readonly Texture2D PedestrianCrossingForbiddenTexture2D; public static readonly Texture2D MainMenuButtonTexture2D; public static readonly Texture2D MainMenuButtonsTexture2D; public static readonly Texture2D NoImageTexture2D; public static readonly Texture2D RemoveButtonTexture2D; public static readonly Texture2D WindowBackgroundTexture2D; static TextureResources() { // missing image NoImageTexture2D = LoadDllResource("noimage.png", 64, 64); // main menu icon MainMenuButtonTexture2D = LoadDllResource("MenuButton.png", 300, 50); MainMenuButtonTexture2D.name = "TMPE_MainMenuButtonIcon"; // main menu buttons MainMenuButtonsTexture2D = LoadDllResource("mainmenu-btns.png", 960, 30); MainMenuButtonsTexture2D.name = "TMPE_MainMenuButtons"; // simple RedLightTexture2D = LoadDllResource("light_1_1.png", 103, 243); YellowRedLightTexture2D = LoadDllResource("light_1_2.png", 103, 243); GreenLightTexture2D = LoadDllResource("light_1_3.png", 103, 243); // forward RedLightStraightTexture2D = LoadDllResource("light_2_1.png", 103, 243); YellowLightStraightTexture2D = LoadDllResource("light_2_2.png", 103, 243); GreenLightStraightTexture2D = LoadDllResource("light_2_3.png", 103, 243); // right RedLightRightTexture2D = LoadDllResource("light_3_1.png", 103, 243); YellowLightRightTexture2D = LoadDllResource("light_3_2.png", 103, 243); GreenLightRightTexture2D = LoadDllResource("light_3_3.png", 103, 243); // left RedLightLeftTexture2D = LoadDllResource("light_4_1.png", 103, 243); YellowLightLeftTexture2D = LoadDllResource("light_4_2.png", 103, 243); GreenLightLeftTexture2D = LoadDllResource("light_4_3.png", 103, 243); // forwardright RedLightForwardRightTexture2D = LoadDllResource("light_5_1.png", 103, 243); YellowLightForwardRightTexture2D = LoadDllResource("light_5_2.png", 103, 243); GreenLightForwardRightTexture2D = LoadDllResource("light_5_3.png", 103, 243); // forwardleft RedLightForwardLeftTexture2D = LoadDllResource("light_6_1.png", 103, 243); YellowLightForwardLeftTexture2D = LoadDllResource("light_6_2.png", 103, 243); GreenLightForwardLeftTexture2D = LoadDllResource("light_6_3.png", 103, 243); // yellow YellowLightTexture2D = LoadDllResource("light_yellow.png", 103, 243); // pedestrian PedestrianRedLightTexture2D = LoadDllResource("pedestrian_light_1.png", 73, 123); PedestrianGreenLightTexture2D = LoadDllResource("pedestrian_light_2.png", 73, 123); // light mode LightModeTexture2D = LoadDllResource(Translation.GetTranslatedFileName("light_mode.png"), 103, 95); LightCounterTexture2D = LoadDllResource(Translation.GetTranslatedFileName("light_counter.png"), 103, 95); // pedestrian mode PedestrianModeAutomaticTexture2D = LoadDllResource("pedestrian_mode_1.png", 73, 70); PedestrianModeManualTexture2D = LoadDllResource("pedestrian_mode_2.png", 73, 73); // priority signs PrioritySignTextures = new TinyDictionary(); PrioritySignTextures[PriorityType.None] = LoadDllResource("sign_none.png", 200, 200); PrioritySignTextures[PriorityType.Main] = LoadDllResource("sign_priority.png", 200, 200); PrioritySignTextures[PriorityType.Stop] = LoadDllResource("sign_stop.png", 200, 200); PrioritySignTextures[PriorityType.Yield] = LoadDllResource("sign_yield.png", 200, 200); // delete priority sign SignRemoveTexture2D = LoadDllResource("remove_signs.png", 256, 256); // timer ClockPlayTexture2D = LoadDllResource("clock_play.png", 512, 512); ClockPauseTexture2D = LoadDllResource("clock_pause.png", 512, 512); ClockTestTexture2D = LoadDllResource("clock_test.png", 512, 512); SpeedLimitTextures = new TinyDictionary(); foreach (ushort speedLimit in SpeedLimitManager.Instance.AvailableSpeedLimits) { SpeedLimitTextures.Add(speedLimit, LoadDllResource(speedLimit.ToString() + ".png", 200, 200)); } VehicleRestrictionTextures = new TinyDictionary>(); VehicleRestrictionTextures[ExtVehicleType.Bus] = new TinyDictionary(); VehicleRestrictionTextures[ExtVehicleType.CargoTrain] = new TinyDictionary(); VehicleRestrictionTextures[ExtVehicleType.CargoTruck] = new TinyDictionary(); VehicleRestrictionTextures[ExtVehicleType.Emergency] = new TinyDictionary(); VehicleRestrictionTextures[ExtVehicleType.PassengerCar] = new TinyDictionary(); VehicleRestrictionTextures[ExtVehicleType.PassengerTrain] = new TinyDictionary(); VehicleRestrictionTextures[ExtVehicleType.Service] = new TinyDictionary(); VehicleRestrictionTextures[ExtVehicleType.Taxi] = new TinyDictionary(); foreach (KeyValuePair> e in VehicleRestrictionTextures) { foreach (bool b in new bool[]{false, true}) { string suffix = b ? "allowed" : "forbidden"; e.Value[b] = LoadDllResource(e.Key.ToString().ToLower() + "_" + suffix + ".png", 200, 200); } } ParkingRestrictionTextures = new TinyDictionary(); ParkingRestrictionTextures[true] = LoadDllResource("parking_allowed.png", 200, 200); ParkingRestrictionTextures[false] = LoadDllResource("parking_disallowed.png", 200, 200); LaneChangeAllowedTexture2D = LoadDllResource("lanechange_allowed.png", 200, 200); LaneChangeForbiddenTexture2D = LoadDllResource("lanechange_forbidden.png", 200, 200); UturnAllowedTexture2D = LoadDllResource("uturn_allowed.png", 200, 200); UturnForbiddenTexture2D = LoadDllResource("uturn_forbidden.png", 200, 200); RightOnRedAllowedTexture2D = LoadDllResource("right_on_red_allowed.png", 200, 200); RightOnRedForbiddenTexture2D = LoadDllResource("right_on_red_forbidden.png", 200, 200); LeftOnRedAllowedTexture2D = LoadDllResource("left_on_red_allowed.png", 200, 200); LeftOnRedForbiddenTexture2D = LoadDllResource("left_on_red_forbidden.png", 200, 200); EnterBlockedJunctionAllowedTexture2D = LoadDllResource("enterblocked_allowed.png", 200, 200); EnterBlockedJunctionForbiddenTexture2D = LoadDllResource("enterblocked_forbidden.png", 200, 200); PedestrianCrossingAllowedTexture2D = LoadDllResource("crossing_allowed.png", 200, 200); PedestrianCrossingForbiddenTexture2D = LoadDllResource("crossing_forbidden.png", 200, 200); VehicleInfoSignTextures = new TinyDictionary(); VehicleInfoSignTextures[ExtVehicleType.Bicycle] = LoadDllResource("bicycle_infosign.png", 449, 411); VehicleInfoSignTextures[ExtVehicleType.Bus] = LoadDllResource("bus_infosign.png", 449, 411); VehicleInfoSignTextures[ExtVehicleType.CargoTrain] = LoadDllResource("cargotrain_infosign.png", 449, 411); VehicleInfoSignTextures[ExtVehicleType.CargoTruck] = LoadDllResource("cargotruck_infosign.png", 449, 411); VehicleInfoSignTextures[ExtVehicleType.Emergency] = LoadDllResource("emergency_infosign.png", 449, 411); VehicleInfoSignTextures[ExtVehicleType.PassengerCar] = LoadDllResource("passengercar_infosign.png", 449, 411); VehicleInfoSignTextures[ExtVehicleType.PassengerTrain] = LoadDllResource("passengertrain_infosign.png", 449, 411); VehicleInfoSignTextures[ExtVehicleType.RailVehicle] = VehicleInfoSignTextures[ExtVehicleType.PassengerTrain]; VehicleInfoSignTextures[ExtVehicleType.Service] = LoadDllResource("service_infosign.png", 449, 411); VehicleInfoSignTextures[ExtVehicleType.Taxi] = LoadDllResource("taxi_infosign.png", 449, 411); VehicleInfoSignTextures[ExtVehicleType.Tram] = LoadDllResource("tram_infosign.png", 449, 411); RemoveButtonTexture2D = LoadDllResource("remove-btn.png", 150, 30); WindowBackgroundTexture2D = LoadDllResource("WindowBackground.png", 16, 60); } private static Texture2D LoadDllResource(string resourceName, int width, int height) { #if DEBUG bool debug = State.GlobalConfig.Instance.Debug.Switches[11]; #endif try { #if DEBUG if (debug) Log._Debug($"Loading DllResource {resourceName}"); #endif var myAssembly = Assembly.GetExecutingAssembly(); var myStream = myAssembly.GetManifestResourceStream("TrafficManager.Resources." + resourceName); var texture = new Texture2D(width, height, TextureFormat.ARGB32, false); texture.LoadImage(ReadToEnd(myStream)); return texture; } catch (Exception e) { Log.Error(e.StackTrace.ToString()); return null; } } static byte[] ReadToEnd(Stream stream) { var originalPosition = stream.Position; stream.Position = 0; try { var readBuffer = new byte[4096]; var totalBytesRead = 0; int bytesRead; while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0) { totalBytesRead += bytesRead; if (totalBytesRead != readBuffer.Length) continue; var nextByte = stream.ReadByte(); if (nextByte == -1) continue; var temp = new byte[readBuffer.Length * 2]; Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length); Buffer.SetByte(temp, totalBytesRead, (byte)nextByte); readBuffer = temp; totalBytesRead++; } var buffer = readBuffer; if (readBuffer.Length == totalBytesRead) return buffer; buffer = new byte[totalBytesRead]; Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead); return buffer; } catch (Exception e) { Log.Error(e.StackTrace.ToString()); return null; } finally { stream.Position = originalPosition; } } } } ================================================ FILE: TLM/TLM/UI/ToolMode.cs ================================================ namespace TrafficManager.UI { public enum ToolMode { None = 0, SwitchTrafficLight = 1, AddPrioritySigns = 2, ManualSwitch = 3, TimedLightsSelectNode = 4, TimedLightsShowLights = 5, LaneChange = 6, TimedLightsAddNode = 7, TimedLightsRemoveNode = 8, TimedLightsCopyLights = 9, SpeedLimits = 10, VehicleRestrictions = 11, LaneConnector = 12, JunctionRestrictions = 13, ParkingRestrictions = 14 } } ================================================ FILE: TLM/TLM/UI/TrafficManagerTool.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using ColossalFramework; using ColossalFramework.Math; using ColossalFramework.UI; using JetBrains.Annotations; using TrafficManager.Custom.AI; using TrafficManager.Geometry; using TrafficManager.UI; using UnityEngine; using TrafficManager.State; using TrafficManager.TrafficLight; using TrafficManager.UI.SubTools; using TrafficManager.Traffic; using TrafficManager.Manager; using TrafficManager.Util; using TrafficManager.UI.MainMenu; using CSUtil.Commons; using TrafficManager.Manager.Impl; using TrafficManager.Traffic.Data; using static TrafficManager.Traffic.Data.ExtCitizenInstance; using System.Collections; namespace TrafficManager.UI { [UsedImplicitly] public class TrafficManagerTool : DefaultTool, IObserver { public struct NodeVisitItem { public ushort nodeId; public bool startNode; public NodeVisitItem(ushort nodeId, bool startNode) { this.nodeId = nodeId; this.startNode = startNode; } } private ToolMode _toolMode; internal static ushort HoveredNodeId; internal static ushort HoveredSegmentId; private static bool mouseClickProcessed; public static readonly float DebugCloseLod = 300f; public static readonly float MaxOverlayDistance = 450f; private IDictionary subTools = new TinyDictionary(); public static ushort SelectedNodeId { get; internal set; } public static ushort SelectedSegmentId { get; internal set; } public static TransportDemandViewMode CurrentTransportDemandViewMode { get; internal set; } = TransportDemandViewMode.Outgoing; internal static ExtVehicleType[] InfoSignsToDisplay = new ExtVehicleType[] { ExtVehicleType.PassengerCar, ExtVehicleType.Bicycle, ExtVehicleType.Bus, ExtVehicleType.Taxi, ExtVehicleType.Tram, ExtVehicleType.CargoTruck, ExtVehicleType.Service, ExtVehicleType.RailVehicle }; private static SubTool activeSubTool = null; private static IDisposable confDisposable; static TrafficManagerTool() { } internal ToolController GetToolController() { return m_toolController; } internal static Rect MoveGUI(Rect rect) { // x := main menu x + rect.x // y := main menu y + main menu height + rect.y return new Rect(MainMenuPanel.DEFAULT_MENU_X + rect.x, MainMenuPanel.DEFAULT_MENU_Y + MainMenuPanel.SIZE_PROFILES[1].MENU_HEIGHT + rect.y, rect.width, rect.height); // TODO use current size profile } internal bool IsNodeWithinViewDistance(ushort nodeId) { bool ret = false; Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { ret = IsPosWithinOverlayDistance(node.m_position); return true; }); return ret; } internal bool IsSegmentWithinViewDistance(ushort segmentId) { bool ret = false; Constants.ServiceFactory.NetService.ProcessSegment(segmentId, delegate (ushort segId, ref NetSegment segment) { Vector3 centerPos = segment.m_bounds.center; ret = IsPosWithinOverlayDistance(centerPos); return true; }); return ret; } internal bool IsPosWithinOverlayDistance(Vector3 position) { return (position - Singleton.instance.m_simulationView.m_position).magnitude <= TrafficManagerTool.MaxOverlayDistance; } internal static float AdaptWidth(float originalWidth) { return originalWidth; //return originalWidth * ((float)Screen.width / 1920f); } internal float GetBaseZoom() { return (float)Screen.height / 1200f; } internal float GetWindowAlpha() { return TransparencyToAlpha(GlobalConfig.Instance.Main.GuiTransparency); } internal float GetHandleAlpha(bool hovered) { byte transparency = GlobalConfig.Instance.Main.OverlayTransparency; if (hovered) { // reduce transparency when handle is hovered transparency = (byte)Math.Min(20, transparency >> 2); } return TransparencyToAlpha(transparency); } private static float TransparencyToAlpha(byte transparency) { return Mathf.Clamp(100 - (int)transparency, 0f, 100f) / 100f; } internal void Initialize() { Log.Info("TrafficManagerTool: Initialization running now."); subTools.Clear(); subTools[ToolMode.SwitchTrafficLight] = new ToggleTrafficLightsTool(this); subTools[ToolMode.AddPrioritySigns] = new PrioritySignsTool(this); subTools[ToolMode.ManualSwitch] = new ManualTrafficLightsTool(this); SubTool timedLightsTool = new TimedTrafficLightsTool(this); subTools[ToolMode.TimedLightsAddNode] = timedLightsTool; subTools[ToolMode.TimedLightsRemoveNode] = timedLightsTool; subTools[ToolMode.TimedLightsSelectNode] = timedLightsTool; subTools[ToolMode.TimedLightsShowLights] = timedLightsTool; subTools[ToolMode.TimedLightsCopyLights] = timedLightsTool; subTools[ToolMode.VehicleRestrictions] = new VehicleRestrictionsTool(this); subTools[ToolMode.SpeedLimits] = new SpeedLimitsTool(this); subTools[ToolMode.LaneChange] = new LaneArrowTool(this); subTools[ToolMode.LaneConnector] = new LaneConnectorTool(this); subTools[ToolMode.JunctionRestrictions] = new JunctionRestrictionsTool(this); subTools[ToolMode.ParkingRestrictions] = new ParkingRestrictionsTool(this); InitializeSubTools(); SetToolMode(ToolMode.None); if (confDisposable != null) { confDisposable.Dispose(); } confDisposable = GlobalConfig.Instance.Subscribe(this); Log.Info("TrafficManagerTool: Initialization completed."); } public void OnUpdate(GlobalConfig config) { InitializeSubTools(); } internal void InitializeSubTools() { foreach (KeyValuePair e in subTools) { e.Value.Initialize(); } } protected override void Awake() { Log._Debug($"TrafficLightTool: Awake {this.GetHashCode()}"); base.Awake(); } public SubTool GetSubTool(ToolMode mode) { SubTool ret; if (subTools.TryGetValue(mode, out ret)) { return ret; } return null; } public ToolMode GetToolMode() { return _toolMode; } public void SetToolMode(ToolMode mode) { Log._Debug($"SetToolMode: {mode}"); bool toolModeChanged = (mode != _toolMode); var oldToolMode = _toolMode; SubTool oldSubTool = null; subTools.TryGetValue(oldToolMode, out oldSubTool); _toolMode = mode; if (!subTools.TryGetValue(_toolMode, out activeSubTool)) { activeSubTool = null; } bool realToolChange = toolModeChanged; if (oldSubTool != null) { if ((oldToolMode == ToolMode.TimedLightsSelectNode || oldToolMode == ToolMode.TimedLightsShowLights || oldToolMode == ToolMode.TimedLightsAddNode || oldToolMode == ToolMode.TimedLightsRemoveNode || oldToolMode == ToolMode.TimedLightsCopyLights)) { // TODO refactor to SubToolMode if (mode != ToolMode.TimedLightsSelectNode && mode != ToolMode.TimedLightsShowLights && mode != ToolMode.TimedLightsAddNode && mode != ToolMode.TimedLightsRemoveNode && mode != ToolMode.TimedLightsCopyLights) { oldSubTool.Cleanup(); } } else { oldSubTool.Cleanup(); } } if (toolModeChanged && activeSubTool != null) { if ((oldToolMode == ToolMode.TimedLightsSelectNode || oldToolMode == ToolMode.TimedLightsShowLights || oldToolMode == ToolMode.TimedLightsAddNode || oldToolMode == ToolMode.TimedLightsRemoveNode || oldToolMode == ToolMode.TimedLightsCopyLights)) { // TODO refactor to SubToolMode if (mode != ToolMode.TimedLightsSelectNode && mode != ToolMode.TimedLightsShowLights && mode != ToolMode.TimedLightsAddNode && mode != ToolMode.TimedLightsRemoveNode && mode != ToolMode.TimedLightsCopyLights) { activeSubTool.Cleanup(); } else { realToolChange = false; } } else { activeSubTool.Cleanup(); } } SelectedNodeId = 0; SelectedSegmentId = 0; //Log._Debug($"Getting activeSubTool for mode {_toolMode} {subTools.Count}"); //subTools.TryGetValue((int)_toolMode, out activeSubTool); //Log._Debug($"activeSubTool is now {activeSubTool}"); if (toolModeChanged && activeSubTool != null) { activeSubTool.OnActivate(); if (realToolChange) { ShowAdvisor(activeSubTool.GetTutorialKey()); } } } // Overridden to disable base class behavior protected override void OnEnable() { Log._Debug($"TrafficManagerTool.OnEnable(): Performing cleanup"); foreach (KeyValuePair e in subTools) { e.Value.Cleanup(); } } // Overridden to disable base class behavior protected override void OnDisable() { } public override void RenderGeometry(RenderManager.CameraInfo cameraInfo) { if (HoveredNodeId != 0) { m_toolController.RenderCollidingNotifications(cameraInfo, 0, 0); } } /// /// Renders overlays (node selection, segment selection, etc.) /// /// public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) { //Log._Debug($"RenderOverlay"); //Log._Debug($"RenderOverlay: {_toolMode} {activeSubTool} {this.GetHashCode()}"); if (!this.isActiveAndEnabled) { return; } if (activeSubTool != null) { //Log._Debug($"Rendering overlay in {_toolMode}"); activeSubTool.RenderOverlay(cameraInfo); } foreach (KeyValuePair e in subTools) { if (e.Key == GetToolMode()) continue; e.Value.RenderInfoOverlay(cameraInfo); } } /// /// Primarily handles click events on hovered nodes/segments /// protected override void OnToolUpdate() { base.OnToolUpdate(); //Log._Debug($"OnToolUpdate"); if (Input.GetKeyUp(KeyCode.PageDown)) { InfoManager.instance.SetCurrentMode(InfoManager.InfoMode.Traffic, InfoManager.SubInfoMode.Default); UIView.library.Hide("TrafficInfoViewPanel"); } else if (Input.GetKeyUp(KeyCode.PageUp)) InfoManager.instance.SetCurrentMode(InfoManager.InfoMode.None, InfoManager.SubInfoMode.Default); bool primaryMouseClicked = Input.GetMouseButtonDown(0); bool secondaryMouseClicked = Input.GetMouseButtonDown(1); // check if clicked if (!primaryMouseClicked && !secondaryMouseClicked) return; // check if mouse is inside panel if (LoadingExtension.BaseUI.GetMenu().containsMouse #if DEBUG || LoadingExtension.BaseUI.GetDebugMenu().containsMouse #endif ) { #if DEBUG Log._Debug($"TrafficManagerTool: OnToolUpdate: Menu contains mouse. Ignoring click."); #endif return; } if (/*!elementsHovered || (*/activeSubTool != null && activeSubTool.IsCursorInPanel()/*)*/) { #if DEBUG Log._Debug($"TrafficManagerTool: OnToolUpdate: Subtool contains mouse. Ignoring click."); #endif //Log.Message("inside ui: " + m_toolController.IsInsideUI + " visible: " + Cursor.visible + " in secondary panel: " + _cursorInSecondaryPanel); return; } /*if (HoveredSegmentId == 0 && HoveredNodeId == 0) { //Log.Message("no hovered segment"); return; }*/ if (activeSubTool != null) { determineHoveredElements(); if (primaryMouseClicked) activeSubTool.OnPrimaryClickOverlay(); if (secondaryMouseClicked) activeSubTool.OnSecondaryClickOverlay(); } } protected override void OnToolGUI(Event e) { try { if (!Input.GetMouseButtonDown(0)) { mouseClickProcessed = false; } if (Options.nodesOverlay) { _guiSegments(); _guiNodes(); } //#if DEBUG if (Options.vehicleOverlay) { _guiVehicles(); } if (Options.citizenOverlay) { _guiCitizens(); } if (Options.buildingOverlay) { _guiBuildings(); } //#endif foreach (KeyValuePair en in subTools) { en.Value.ShowGUIOverlay(en.Key, en.Key != GetToolMode()); } var guiColor = GUI.color; guiColor.a = 1f; GUI.color = guiColor; if (activeSubTool != null) activeSubTool.OnToolGUI(e); else base.OnToolGUI(e); } catch (Exception ex) { Log.Error("GUI Error: " + ex.ToString()); } } public void DrawNodeCircle(RenderManager.CameraInfo cameraInfo, ushort nodeId, bool warning=false, bool alpha=false) { DrawNodeCircle(cameraInfo, nodeId, GetToolColor(warning, false), alpha); } public void DrawNodeCircle(RenderManager.CameraInfo cameraInfo, ushort nodeId, Color color, bool alpha = false) { var segment = Singleton.instance.m_segments.m_buffer[Singleton.instance.m_nodes.m_buffer[nodeId].m_segment0]; Vector3 pos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position; float terrainY = Singleton.instance.SampleDetailHeightSmooth(pos); if (terrainY > pos.y) pos.y = terrainY; Bezier3 bezier; bezier.a = pos; bezier.d = pos; NetSegment.CalculateMiddlePoints(bezier.a, segment.m_startDirection, bezier.d, segment.m_endDirection, false, false, out bezier.b, out bezier.c); DrawOverlayBezier(cameraInfo, bezier, color, alpha); } private void DrawOverlayBezier(RenderManager.CameraInfo cameraInfo, Bezier3 bezier, Color color, bool alpha=false) { const float width = 8f; // 8 - small roads; 16 - big roads Singleton.instance.m_drawCallData.m_overlayCalls++; Singleton.instance.OverlayEffect.DrawBezier(cameraInfo, color, bezier, width * 2f, width, width, -1f, 1280f, false, alpha); } private void DrawOverlayCircle(RenderManager.CameraInfo cameraInfo, Color color, Vector3 position, float width, bool alpha) { Singleton.instance.m_drawCallData.m_overlayCalls++; Singleton.instance.OverlayEffect.DrawCircle(cameraInfo, color, position, width, position.y - 100f, position.y + 100f, false, alpha); } public void DrawStaticSquareOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellSize, Vector3 xu, Vector3 yu, uint x, uint y, float size) { DrawGenericSquareOverlayGridTexture(texture, camPos, gridOrigin, cellSize, xu, yu, x, y, size, false); } public bool DrawHoverableSquareOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellSize, Vector3 xu, Vector3 yu, uint x, uint y, float size) { return DrawGenericSquareOverlayGridTexture(texture, camPos, gridOrigin, cellSize, xu, yu, x, y, size, true); } public bool DrawGenericSquareOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellSize, Vector3 xu, Vector3 yu, uint x, uint y, float size, bool canHover) { return DrawGenericOverlayGridTexture(texture, camPos, gridOrigin, cellSize, cellSize, xu, yu, x, y, size, size, canHover); } public void DrawStaticOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellWidth, float cellHeight, Vector3 xu, Vector3 yu, uint x, uint y, float width, float height) { DrawGenericOverlayGridTexture(texture, camPos, gridOrigin, cellWidth, cellHeight, xu, yu, x, y, width, height, false); } public bool DrawHoverableOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellWidth, float cellHeight, Vector3 xu, Vector3 yu, uint x, uint y, float width, float height) { return DrawGenericOverlayGridTexture(texture, camPos, gridOrigin, cellWidth, cellHeight, xu, yu, x, y, width, height, true); } public bool DrawGenericOverlayGridTexture(Texture2D texture, Vector3 camPos, Vector3 gridOrigin, float cellWidth, float cellHeight, Vector3 xu, Vector3 yu, uint x, uint y, float width, float height, bool canHover) { Vector3 worldPos = gridOrigin + cellWidth * (float)x * xu + cellHeight * (float)y * yu; // grid position in game coordinates return DrawGenericOverlayTexture(texture, camPos, worldPos, width, height, canHover); } public void DrawStaticSquareOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float size) { DrawGenericOverlayTexture(texture, camPos, worldPos, size, size, false); } public bool DrawHoverableSquareOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float size) { return DrawGenericOverlayTexture(texture, camPos, worldPos, size, size, true); } public bool DrawGenericSquareOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float size, bool canHover) { return DrawGenericOverlayTexture(texture, camPos, worldPos, size, size, canHover); } public void DrawStaticOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float width, float height) { DrawGenericOverlayTexture(texture, camPos, worldPos, width, height, false); } public bool DrawHoverableOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float width, float height) { return DrawGenericOverlayTexture(texture, camPos, worldPos, width, height, true); } public bool DrawGenericOverlayTexture(Texture2D texture, Vector3 camPos, Vector3 worldPos, float width, float height, bool canHover) { Vector3 screenPos; if (! WorldToScreenPoint(worldPos, out screenPos)) { return false; } float zoom = 1.0f / (worldPos - camPos).magnitude * 100f * GetBaseZoom(); width *= zoom; height *= zoom; Rect boundingBox = new Rect(screenPos.x - width / 2f, screenPos.y - height / 2f, width, height); Color guiColor = GUI.color; bool hovered = false; if (canHover) { hovered = IsMouseOver(boundingBox); } guiColor.a = GetHandleAlpha(hovered); GUI.color = guiColor; GUI.DrawTexture(boundingBox, texture); return hovered; } /// /// Transforms a world point into a screen point /// /// /// /// public bool WorldToScreenPoint(Vector3 worldPos, out Vector3 screenPos) { screenPos = Camera.main.WorldToScreenPoint(worldPos); screenPos.y = Screen.height - screenPos.y; return screenPos.z >= 0; } /// /// Shows a tutorial message. Must be called by a Unity thread. /// /// public static void ShowAdvisor(string localeKey) { if (! GlobalConfig.Instance.Main.EnableTutorial) { return; } if (! Translation.HasString(Translation.TUTORIAL_BODY_KEY_PREFIX + localeKey)) { return; } Log._Debug($"TrafficManagerTool.ShowAdvisor({localeKey}) called."); TutorialAdvisorPanel tutorialPanel = ToolsModifierControl.advisorPanel; string key = Translation.TUTORIAL_KEY_PREFIX + localeKey; if (GlobalConfig.Instance.Main.DisplayedTutorialMessages.Contains(localeKey)) { tutorialPanel.Refresh(key, "ToolbarIconZoomOutGlobe", string.Empty); } else { tutorialPanel.Show(key, "ToolbarIconZoomOutGlobe", string.Empty, 0f); GlobalConfig.Instance.Main.AddDisplayedTutorialMessage(localeKey); GlobalConfig.WriteConfig(); } } public override void SimulationStep() { base.SimulationStep(); /*currentFrame = Singleton.instance.m_currentFrameIndex >> 2; string displayToolTipText = tooltipText; if (displayToolTipText != null) { if (currentFrame <= tooltipStartFrame + 50) { ShowToolInfo(true, displayToolTipText, (Vector3)tooltipWorldPos); } else { //ShowToolInfo(false, tooltipText, (Vector3)tooltipWorldPos); //ShowToolInfo(false, null, Vector3.zero); tooltipStartFrame = 0; tooltipText = null; tooltipWorldPos = null; } }*/ if (GetToolMode() == ToolMode.None) { ToolCursor = null; } else { bool elementsHovered = determineHoveredElements(); var netTool = ToolsModifierControl.toolController.Tools.OfType().FirstOrDefault(nt => nt.m_prefab != null); if (netTool != null && elementsHovered) { ToolCursor = netTool.m_upgradeCursor; } } } public bool DoRayCast(RaycastInput input, out RaycastOutput output) { return RayCast(input, out output); } private bool determineHoveredElements() { var mouseRayValid = !UIView.IsInsideUI() && Cursor.visible && (activeSubTool == null || !activeSubTool.IsCursorInPanel()); if (mouseRayValid) { ushort oldHoveredSegmentId = HoveredSegmentId; ushort oldHoveredNodeId = HoveredNodeId; HoveredSegmentId = 0; HoveredNodeId = 0; // find currently hovered node var nodeInput = new RaycastInput(this.m_mouseRay, this.m_mouseRayLength); // find road nodes nodeInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; nodeInput.m_netService.m_service = ItemClass.Service.Road; /*nodeInput.m_netService2.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.PublicTransport | ItemClass.Layer.MetroTunnels; nodeInput.m_netService2.m_service = ItemClass.Service.PublicTransport; nodeInput.m_netService2.m_subService = ItemClass.SubService.PublicTransportTrain;*/ nodeInput.m_ignoreTerrain = true; nodeInput.m_ignoreNodeFlags = NetNode.Flags.None; //nodeInput.m_ignoreNodeFlags = NetNode.Flags.Untouchable; RaycastOutput nodeOutput; if (RayCast(nodeInput, out nodeOutput)) { HoveredNodeId = nodeOutput.m_netNode; } else { // find train nodes nodeInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; nodeInput.m_netService.m_service = ItemClass.Service.PublicTransport; nodeInput.m_netService.m_subService = ItemClass.SubService.PublicTransportTrain; nodeInput.m_ignoreTerrain = true; nodeInput.m_ignoreNodeFlags = NetNode.Flags.None; //nodeInput.m_ignoreNodeFlags = NetNode.Flags.Untouchable; if (RayCast(nodeInput, out nodeOutput)) { HoveredNodeId = nodeOutput.m_netNode; } else { // find metro nodes nodeInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; nodeInput.m_netService.m_service = ItemClass.Service.PublicTransport; nodeInput.m_netService.m_subService = ItemClass.SubService.PublicTransportMetro; nodeInput.m_ignoreTerrain = true; nodeInput.m_ignoreNodeFlags = NetNode.Flags.None; //nodeInput.m_ignoreNodeFlags = NetNode.Flags.Untouchable; if (RayCast(nodeInput, out nodeOutput)) { HoveredNodeId = nodeOutput.m_netNode; } } } // find currently hovered segment var segmentInput = new RaycastInput(this.m_mouseRay, this.m_mouseRayLength); // find road segments segmentInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; segmentInput.m_netService.m_service = ItemClass.Service.Road; segmentInput.m_ignoreTerrain = true; segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.None; //segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.Untouchable; RaycastOutput segmentOutput; if (RayCast(segmentInput, out segmentOutput)) { HoveredSegmentId = segmentOutput.m_netSegment; } else { // find train segments segmentInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; segmentInput.m_netService.m_service = ItemClass.Service.PublicTransport; segmentInput.m_netService.m_subService = ItemClass.SubService.PublicTransportTrain; segmentInput.m_ignoreTerrain = true; segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.None; //segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.Untouchable; if (RayCast(segmentInput, out segmentOutput)) { HoveredSegmentId = segmentOutput.m_netSegment; } else { // find metro segments segmentInput.m_netService.m_itemLayers = ItemClass.Layer.Default | ItemClass.Layer.MetroTunnels; segmentInput.m_netService.m_service = ItemClass.Service.PublicTransport; segmentInput.m_netService.m_subService = ItemClass.SubService.PublicTransportMetro; segmentInput.m_ignoreTerrain = true; segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.None; //segmentInput.m_ignoreSegmentFlags = NetSegment.Flags.Untouchable; if (RayCast(segmentInput, out segmentOutput)) { HoveredSegmentId = segmentOutput.m_netSegment; } } } if (HoveredNodeId <= 0 && HoveredSegmentId > 0) { // alternative way to get a node hit: check distance to start and end nodes of the segment ushort startNodeId = Singleton.instance.m_segments.m_buffer[HoveredSegmentId].m_startNode; ushort endNodeId = Singleton.instance.m_segments.m_buffer[HoveredSegmentId].m_endNode; float startDist = (segmentOutput.m_hitPos - Singleton.instance.m_nodes.m_buffer[startNodeId].m_position).magnitude; float endDist = (segmentOutput.m_hitPos - Singleton.instance.m_nodes.m_buffer[endNodeId].m_position).magnitude; if (startDist < endDist && startDist < 75f) HoveredNodeId = startNodeId; else if (endDist < startDist && endDist < 75f) HoveredNodeId = endNodeId; } /*if (oldHoveredNodeId != HoveredNodeId || oldHoveredSegmentId != HoveredSegmentId) { Log._Debug($"*** Mouse ray @ node {HoveredNodeId}, segment {HoveredSegmentId}, toolMode={GetToolMode()}"); }*/ return (HoveredNodeId != 0 || HoveredSegmentId != 0); } else { //Log._Debug($"Mouse ray invalid: {UIView.IsInsideUI()} {Cursor.visible} {activeSubTool == null} {activeSubTool.IsCursorInPanel()}"); } return mouseRayValid; } /// /// Displays lane ids over lanes /// private void _guiLanes(ushort segmentId, ref NetSegment segment, ref NetInfo segmentInfo) { GUIStyle _counterStyle = new GUIStyle(); Vector3 centerPos = segment.m_bounds.center; Vector3 screenPos; bool visible = WorldToScreenPoint(centerPos, out screenPos); if (! visible) { return; } screenPos.y -= 200; if (screenPos.z < 0) return; var camPos = Singleton.instance.m_simulationView.m_position; var diff = centerPos - camPos; if (diff.magnitude > DebugCloseLod) return; // do not draw if too distant var zoom = 1.0f / diff.magnitude * 150f; _counterStyle.fontSize = (int)(11f * zoom); _counterStyle.normal.textColor = new Color(1f, 1f, 0f); /*uint totalDensity = 0u; for (int i = 0; i < segmentInfo.m_lanes.Length; ++i) { if (CustomRoadAI.currentLaneDensities[segmentId] != null && i < CustomRoadAI.currentLaneDensities[segmentId].Length) totalDensity += CustomRoadAI.currentLaneDensities[segmentId][i]; }*/ uint curLaneId = segment.m_lanes; String labelStr = ""; for (int i = 0; i < segmentInfo.m_lanes.Length; ++i) { if (curLaneId == 0) break; TrafficMeasurementManager.LaneTrafficData laneTrafficData; bool laneTrafficDataLoaded = TrafficMeasurementManager.Instance.GetLaneTrafficData(segmentId, (byte)i, out laneTrafficData); NetInfo.Lane laneInfo = segmentInfo.m_lanes[i]; #if PFTRAFFICSTATS uint pfTrafficBuf = TrafficMeasurementManager.Instance.segmentDirTrafficData[TrafficMeasurementManager.Instance.GetDirIndex(segmentId, laneInfo.m_finalDirection)].totalPathFindTrafficBuffer; #endif //TrafficMeasurementManager.Instance.GetTrafficData(segmentId, laneInfo.m_finalDirection, out dirTrafficData); //int dirIndex = laneInfo.m_finalDirection == NetInfo.Direction.Backward ? 1 : 0; labelStr += "L idx " + i + ", id " + curLaneId; #if DEBUG labelStr += ", in: " + RoutingManager.Instance.CalcInnerSimilarLaneIndex(segmentId, i) + ", out: " + RoutingManager.Instance.CalcOuterSimilarLaneIndex(segmentId, i) + ", f: " + ((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[curLaneId].m_flags).ToString() + ", l: " + SpeedLimitManager.Instance.GetCustomSpeedLimit(curLaneId) + " km/h, rst: " + VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(segmentId, segmentInfo, (uint)i, laneInfo, VehicleRestrictionsMode.Configured) + ", dir: " + laneInfo.m_direction + ", fnl: " + laneInfo.m_finalDirection + ", pos: " + String.Format("{0:0.##}", laneInfo.m_position) + ", sim: " + laneInfo.m_similarLaneIndex + " for " + laneInfo.m_vehicleType + "/" + laneInfo.m_laneType; #endif if (laneTrafficDataLoaded) { labelStr += ", sp: " + (TrafficMeasurementManager.Instance.CalcLaneRelativeMeanSpeed(segmentId, (byte)i, curLaneId, laneInfo) / 100) + "%"; #if DEBUG labelStr += ", buf: " + laneTrafficData.trafficBuffer + ", max: " + laneTrafficData.maxTrafficBuffer + ", acc: " + laneTrafficData.accumulatedSpeeds; #if PFTRAFFICSTATS labelStr += ", pfBuf: " + laneTrafficData.pathFindTrafficBuffer + "/" + laneTrafficData.lastPathFindTrafficBuffer + ", (" + (pfTrafficBuf > 0 ? "" + ((laneTrafficData.lastPathFindTrafficBuffer * 100u) / pfTrafficBuf) : "n/a") + " %)"; #endif #endif #if MEASUREDENSITY if (dirTrafficDataLoaded) { labelStr += ", rel. dens.: " + (dirTrafficData.accumulatedDensities > 0 ? "" + Math.Min(laneTrafficData[i].accumulatedDensities * 100 / dirTrafficData.accumulatedDensities, 100) : "?") + "%"; } labelStr += ", acc: " + laneTrafficData[i].accumulatedDensities; #endif } labelStr += ", nd: " + Singleton.instance.m_lanes.m_buffer[curLaneId].m_nodes; #if DEBUG //labelStr += " (" + (CustomRoadAI.currentLaneDensities[segmentId] != null && i < CustomRoadAI.currentLaneDensities[segmentId].Length ? "" + CustomRoadAI.currentLaneDensities[segmentId][i] : "?") + "/" + (CustomRoadAI.maxLaneDensities[segmentId] != null && i < CustomRoadAI.maxLaneDensities[segmentId].Length ? "" + CustomRoadAI.maxLaneDensities[segmentId][i] : "?") + "/" + totalDensity + ")"; //labelStr += " (" + (CustomRoadAI.currentLaneDensities[segmentId] != null && i < CustomRoadAI.currentLaneDensities[segmentId].Length ? "" + CustomRoadAI.currentLaneDensities[segmentId][i] : "?") + "/" + totalDensity + ")"; #endif //labelStr += ", abs. dens.: " + (CustomRoadAI.laneMeanAbsDensities[segmentId] != null && i < CustomRoadAI.laneMeanAbsDensities[segmentId].Length ? "" + CustomRoadAI.laneMeanAbsDensities[segmentId][i] : "?") + " %"; labelStr += "\n"; curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; } Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y, dim.x, dim.y); GUI.Label(labelRect, labelStr, _counterStyle); } /// /// Displays segment ids over segments /// private void _guiSegments() { TrafficMeasurementManager trafficMeasurementManager = TrafficMeasurementManager.Instance; GUIStyle _counterStyle = new GUIStyle(); SegmentEndManager endMan = SegmentEndManager.Instance; Array16 segments = Singleton.instance.m_segments; for (int i = 1; i < segments.m_size; ++i) { if (segments.m_buffer[i].m_flags == NetSegment.Flags.None) // segment is unused continue; ItemClass.Service service = segments.m_buffer[i].Info.GetService(); ItemClass.SubService subService = segments.m_buffer[i].Info.GetSubService(); /*if (service != ItemClass.Service.Road) { if (service != ItemClass.Service.PublicTransport) { continue; } else { if (subService != ItemClass.SubService.PublicTransportBus && subService != ItemClass.SubService.PublicTransportCableCar && subService != ItemClass.SubService.PublicTransportMetro && subService != ItemClass.SubService.PublicTransportMonorail && subService != ItemClass.SubService.PublicTransportTrain) { continue; } } }*/ #if !DEBUG if ((segments.m_buffer[i].m_flags & NetSegment.Flags.Untouchable) != NetSegment.Flags.None) continue; #endif var segmentInfo = segments.m_buffer[i].Info; Vector3 centerPos = segments.m_buffer[i].m_bounds.center; Vector3 screenPos; bool visible = WorldToScreenPoint(centerPos, out screenPos); if (! visible) continue; var camPos = Singleton.instance.m_simulationView.m_position; var diff = centerPos - camPos; if (diff.magnitude > DebugCloseLod) continue; // do not draw if too distant var zoom = 1.0f / diff.magnitude * 150f; _counterStyle.fontSize = (int)(12f * zoom); _counterStyle.normal.textColor = new Color(1f, 0f, 0f); String labelStr = "Segment " + i; #if DEBUG labelStr += ", flags: " + segments.m_buffer[i].m_flags.ToString(); // + ", condition: " + segments.m_buffer[i].m_condition; #endif #if DEBUG labelStr += "\nsvc: " + service + ", sub: " + subService; ISegmentEnd startEnd = endMan.GetSegmentEnd((ushort)i, true); ISegmentEnd endEnd = endMan.GetSegmentEnd((ushort)i, false); labelStr += "\nstart? " + (startEnd != null) + " veh.: " + startEnd?.GetRegisteredVehicleCount() + ", end? " + (endEnd != null) + " veh.: " + endEnd?.GetRegisteredVehicleCount(); #endif labelStr += "\nTraffic: " + segments.m_buffer[i].m_trafficDensity + " %"; #if DEBUG int fwdSegIndex = trafficMeasurementManager.GetDirIndex((ushort)i, NetInfo.Direction.Forward); int backSegIndex = trafficMeasurementManager.GetDirIndex((ushort)i, NetInfo.Direction.Backward); labelStr += "\n"; #if MEASURECONGESTION float fwdCongestionRatio = trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements > 0 ? ((uint)trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongested * 100u) / (uint)trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements : 0; // now in % float backCongestionRatio = trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongestionMeasurements > 0 ? ((uint)trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongested * 100u) / (uint)trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongestionMeasurements : 0; // now in % labelStr += "min speeds: "; labelStr += " " + (trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].minSpeed / 100) + "%/" + (trafficMeasurementManager.segmentDirTrafficData[backSegIndex].minSpeed / 100) + "%"; labelStr += ", "; #endif labelStr += "mean speeds: "; labelStr += " " + (trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].meanSpeed / 100) + "%/" + (trafficMeasurementManager.segmentDirTrafficData[backSegIndex].meanSpeed / 100) + "%"; #if PFTRAFFICSTATS || MEASURECONGESTION labelStr += "\n"; #endif #if PFTRAFFICSTATS labelStr += "pf bufs: "; labelStr += " " + (trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].totalPathFindTrafficBuffer) + "/" + (trafficMeasurementManager.segmentDirTrafficData[backSegIndex].totalPathFindTrafficBuffer); #endif #if PFTRAFFICSTATS && MEASURECONGESTION labelStr += ", "; #endif #if MEASURECONGESTION labelStr += "cong: "; labelStr += " " + fwdCongestionRatio + "% (" + trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongested + "/" + trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements + ")/" + backCongestionRatio + "% (" + trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongested + "/" + trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongestionMeasurements + ")"; #endif labelStr += "\nstart: " + segments.m_buffer[i].m_startNode + ", end: " + segments.m_buffer[i].m_endNode; #endif Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y, dim.x, dim.y); GUI.Label(labelRect, labelStr, _counterStyle); if (Options.showLanes) _guiLanes((ushort)i, ref segments.m_buffer[i], ref segmentInfo); } } /// /// Displays node ids over nodes /// private void _guiNodes() { GUIStyle _counterStyle = new GUIStyle(); Array16 nodes = Singleton.instance.m_nodes; for (int i = 1; i < nodes.m_size; ++i) { if ((nodes.m_buffer[i].m_flags & NetNode.Flags.Created) == NetNode.Flags.None) // node is unused continue; Vector3 pos = nodes.m_buffer[i].m_position; Vector3 screenPos; bool visible = WorldToScreenPoint(pos, out screenPos); if (! visible) continue; var camPos = Singleton.instance.m_simulationView.m_position; var diff = pos - camPos; if (diff.magnitude > DebugCloseLod) continue; // do not draw if too distant var zoom = 1.0f / diff.magnitude * 150f; _counterStyle.fontSize = (int)(15f * zoom); _counterStyle.normal.textColor = new Color(0f, 0f, 1f); String labelStr = "Node " + i; #if DEBUG labelStr += $"\nflags: {nodes.m_buffer[i].m_flags}"; labelStr += $"\nlane: {nodes.m_buffer[i].m_lane}"; #endif Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y, dim.x, dim.y); GUI.Label(labelRect, labelStr, _counterStyle); } } /// /// Displays vehicle ids over vehicles /// private void _guiVehicles() { GUIStyle _counterStyle = new GUIStyle(); Array16 vehicles = Singleton.instance.m_vehicles; LaneConnectionManager connManager = LaneConnectionManager.Instance; SimulationManager simManager = Singleton.instance; NetManager netManager = Singleton.instance; VehicleStateManager vehStateManager = VehicleStateManager.Instance; int startVehicleId = 1; int endVehicleId = (int)(vehicles.m_size - 1); #if DEBUG if (GlobalConfig.Instance.Debug.VehicleId != 0) { startVehicleId = endVehicleId = GlobalConfig.Instance.Debug.VehicleId; } #endif for (int i = startVehicleId; i <= endVehicleId; ++i) { Vehicle vehicle = vehicles.m_buffer[i]; if (vehicle.m_flags == 0) // node is unused continue; Vector3 vehPos = vehicle.GetSmoothPosition((ushort)i); Vector3 screenPos; bool visible = WorldToScreenPoint(vehPos, out screenPos); if (! visible) continue; var camPos = simManager.m_simulationView.m_position; var diff = vehPos - camPos; if (diff.magnitude > DebugCloseLod) continue; // do not draw if too distant var zoom = 1.0f / diff.magnitude * 150f; _counterStyle.fontSize = (int)(10f * zoom); _counterStyle.normal.textColor = new Color(1f, 1f, 1f); //_counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f)); VehicleState vState = vehStateManager.VehicleStates[(ushort)i]; ExtCitizenInstance driverInst = ExtCitizenInstanceManager.Instance.ExtInstances[CustomPassengerCarAI.GetDriverInstanceId((ushort)i, ref Singleton.instance.m_vehicles.m_buffer[i])]; bool startNode = vState.currentStartNode; ushort segmentId = vState.currentSegmentId; ushort vehSpeed = SpeedLimitManager.Instance.VehicleToCustomSpeed(vehicle.GetLastFrameVelocity().magnitude); #if DEBUG if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None && driverInst.pathMode != GlobalConfig.Instance.Debug.ExtPathMode) { continue; } #endif String labelStr = "V #" + i + " is a " + (vState.recklessDriver ? "reckless " : "") + vState.flags + " " + vState.vehicleType + " @ ~" + vehSpeed + " km/h [^2=" + vState.SqrVelocity + "] (len: " + vState.totalLength + ", " + vState.JunctionTransitState + " @ " + vState.currentSegmentId + " (" + vState.currentStartNode + "), l. " + vState.currentLaneIndex + " -> " + vState.nextSegmentId + ", l. " + vState.nextLaneIndex + "), w: " + vState.waitTime + "\n" + "di: " + driverInst.instanceId + " dc: " + driverInst.GetCitizenId() + " m: " + driverInst.pathMode.ToString() + " f: " + driverInst.failedParkingAttempts + " l: " + driverInst.parkingSpaceLocation + " lid: " + driverInst.parkingSpaceLocationId + " ltsu: " + vState.lastTransitStateUpdate + " lpu: " + vState.lastPositionUpdate + " als: " + vState.lastAltLaneSelSegmentId + " srnd: " + Constants.ManagerFactory.VehicleBehaviorManager.GetStaticVehicleRand((ushort)i) + " trnd: " + Constants.ManagerFactory.VehicleBehaviorManager.GetTimedVehicleRand((ushort)i); Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y - dim.y - 50f, dim.x, dim.y); GUI.Box(labelRect, labelStr, _counterStyle); //_counterStyle.normal.background = null; } } private void _guiCitizens() { GUIStyle _counterStyle = new GUIStyle(); Array16 citizenInstances = Singleton.instance.m_instances; for (int i = 1; i < citizenInstances.m_size; ++i) { CitizenInstance citizenInstance = citizenInstances.m_buffer[i]; if (citizenInstance.m_flags == CitizenInstance.Flags.None) continue; if ((citizenInstance.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) continue; #if DEBUG if (GlobalConfig.Instance.Debug.Switches[14]) { #endif if (citizenInstance.m_path != 0) { continue; } #if DEBUG } #endif Vector3 pos = citizenInstance.GetSmoothPosition((ushort)i); Vector3 screenPos; bool visible = WorldToScreenPoint(pos, out screenPos); if (! visible) continue; var camPos = Singleton.instance.m_simulationView.m_position; var diff = pos - camPos; if (diff.magnitude > DebugCloseLod) continue; // do not draw if too distant var zoom = 1.0f / diff.magnitude * 150f; _counterStyle.fontSize = (int)(10f * zoom); _counterStyle.normal.textColor = new Color(1f, 0f, 1f); //_counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f)); #if DEBUG if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None && ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode != GlobalConfig.Instance.Debug.ExtPathMode) { continue; } #endif String labelStr = "Inst. " + i + ", Cit. " + citizenInstance.m_citizen + ",\nm: " + ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode.ToString() + ", tm: " + ExtCitizenManager.Instance.ExtCitizens[citizenInstance.m_citizen].transportMode + ", ltm: " + ExtCitizenManager.Instance.ExtCitizens[citizenInstance.m_citizen].lastTransportMode + ", ll: " + ExtCitizenManager.Instance.ExtCitizens[citizenInstance.m_citizen].lastLocation; if (citizenInstance.m_citizen != 0) { Citizen citizen = Singleton.instance.m_citizens.m_buffer[citizenInstance.m_citizen]; if (citizen.m_parkedVehicle != 0) { labelStr += "\nparked: " + citizen.m_parkedVehicle + " dist: " + (Singleton.instance.m_parkedVehicles.m_buffer[citizen.m_parkedVehicle].m_position - pos).magnitude; } if (citizen.m_vehicle != 0) { labelStr += "\nveh: " + citizen.m_vehicle + " dist: " + (Singleton.instance.m_vehicles.m_buffer[citizen.m_vehicle].GetLastFramePosition() - pos).magnitude; } } Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y - dim.y - 50f, dim.x, dim.y); GUI.Box(labelRect, labelStr, _counterStyle); } } private void _guiBuildings() { GUIStyle _counterStyle = new GUIStyle(); Array16 buildings = Singleton.instance.m_buildings; for (int i = 1; i < buildings.m_size; ++i) { Building building = buildings.m_buffer[i]; if (building.m_flags == Building.Flags.None) continue; Vector3 pos = building.m_position; Vector3 screenPos; bool visible = WorldToScreenPoint(pos, out screenPos); if (! visible) continue; var camPos = Singleton.instance.m_simulationView.m_position; var diff = pos - camPos; if (diff.magnitude > DebugCloseLod) continue; // do not draw if too distant var zoom = 1.0f / diff.magnitude * 150f; _counterStyle.fontSize = (int)(10f * zoom); _counterStyle.normal.textColor = new Color(0f, 1f, 0f); //_counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f)); ExtBuilding extBuilding = ExtBuildingManager.Instance.ExtBuildings[i]; String labelStr = "Building " + i + ", PDemand: " + extBuilding.parkingSpaceDemand + ", IncTDem: " + extBuilding.incomingPublicTransportDemand + ", OutTDem: " + extBuilding.outgoingPublicTransportDemand; Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr)); Rect labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y - dim.y - 50f, dim.x, dim.y); GUI.Box(labelRect, labelStr, _counterStyle); } } new internal Color GetToolColor(bool warning, bool error) { return base.GetToolColor(warning, error); } internal static int GetSegmentNumVehicleLanes(ushort segmentId, ushort? nodeId, out int numDirections, VehicleInfo.VehicleType vehicleTypeFilter) { NetManager netManager = Singleton.instance; var info = netManager.m_segments.m_buffer[segmentId].Info; var curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; var laneIndex = 0; NetInfo.Direction? dir = null; NetInfo.Direction? dir2 = null; //NetInfo.Direction? dir3 = null; numDirections = 0; HashSet directions = new HashSet(); if (nodeId != null) { dir = (netManager.m_segments.m_buffer[segmentId].m_startNode == nodeId) ? NetInfo.Direction.Backward : NetInfo.Direction.Forward; dir2 = ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) ? dir : NetInfo.InvertDirection((NetInfo.Direction)dir); //dir3 = TrafficPriorityManager.IsLeftHandDrive() ? NetInfo.InvertDirection((NetInfo.Direction)dir2) : dir2; } var numLanes = 0; while (laneIndex < info.m_lanes.Length && curLaneId != 0u) { if (((info.m_lanes[laneIndex].m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) != NetInfo.LaneType.None && (info.m_lanes[laneIndex].m_vehicleType & vehicleTypeFilter) != VehicleInfo.VehicleType.None) && (dir2 == null || info.m_lanes[laneIndex].m_finalDirection == dir2)) { if (!directions.Contains(info.m_lanes[laneIndex].m_finalDirection)) { directions.Add(info.m_lanes[laneIndex].m_finalDirection); ++numDirections; } numLanes++; } curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; laneIndex++; } return numLanes; } internal static void CalculateSegmentCenterByDir(ushort segmentId, Dictionary segmentCenterByDir) { segmentCenterByDir.Clear(); NetManager netManager = Singleton.instance; NetInfo segmentInfo = netManager.m_segments.m_buffer[segmentId].Info; uint curLaneId = netManager.m_segments.m_buffer[segmentId].m_lanes; Dictionary numCentersByDir = new Dictionary(); uint laneIndex = 0; while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { if ((segmentInfo.m_lanes[laneIndex].m_laneType & (NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle)) == NetInfo.LaneType.None) goto nextIter; NetInfo.Direction dir = segmentInfo.m_lanes[laneIndex].m_finalDirection; Vector3 bezierCenter = netManager.m_lanes.m_buffer[curLaneId].m_bezier.Position(0.5f); if (!segmentCenterByDir.ContainsKey(dir)) { segmentCenterByDir[dir] = bezierCenter; numCentersByDir[dir] = 1; } else { segmentCenterByDir[dir] += bezierCenter; numCentersByDir[dir]++; } nextIter: curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; laneIndex++; } foreach (KeyValuePair e in numCentersByDir) { segmentCenterByDir[e.Key] /= (float)e.Value; } } public static Texture2D MakeTex(int width, int height, Color col) { var pix = new Color[width * height]; for (var i = 0; i < pix.Length; i++) pix[i] = col; var result = new Texture2D(width, height); result.SetPixels(pix); result.Apply(); return result; } public static Texture2D AdjustAlpha(Texture2D tex, float alpha) { Color[] texColors = tex.GetPixels(); Color[] retPixels = new Color[texColors.Length]; for (int i = 0; i < texColors.Length; ++i) { retPixels[i] = new Color(texColors[i].r, texColors[i].g, texColors[i].b, texColors[i].a * alpha); } Texture2D ret = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false); ret.SetPixels(retPixels); ret.Apply(); return ret; } internal static bool IsMouseOver(Rect boundingBox) { return boundingBox.Contains(Event.current.mousePosition); } internal bool CheckClicked() { if (Input.GetMouseButtonDown(0) && !mouseClickProcessed) { mouseClickProcessed = true; return true; } return false; } public void ShowTooltip(String text) { if (text == null) return; UIView.library.ShowModal("ExceptionPanel").SetMessage("Info", text, false); /*tooltipStartFrame = currentFrame; tooltipText = text; tooltipWorldPos = position;*/ } } } ================================================ FILE: TLM/TLM/UI/Translation.cs ================================================ using ColossalFramework; using ColossalFramework.Globalization; using CSUtil.Commons; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using TrafficManager.State; using TrafficManager.Util; namespace TrafficManager.UI { public class Translation { public static readonly IDictionary LANGUAGE_LABELS = new TinyDictionary(); public static readonly IList AVAILABLE_LANGUAGE_CODES = new List(); public const string DEFAULT_LANGUAGE_CODE = "en"; public const string TUTORIAL_KEY_PREFIX = "TMPE_TUTORIAL_"; public const string TUTORIAL_HEAD_KEY_PREFIX = TUTORIAL_KEY_PREFIX + "HEAD_"; public const string TUTORIAL_BODY_KEY_PREFIX = TUTORIAL_KEY_PREFIX + "BODY_"; static Translation() { AVAILABLE_LANGUAGE_CODES.Clear(); AVAILABLE_LANGUAGE_CODES.Add("de"); AVAILABLE_LANGUAGE_CODES.Add("en"); AVAILABLE_LANGUAGE_CODES.Add("es"); AVAILABLE_LANGUAGE_CODES.Add("fr"); AVAILABLE_LANGUAGE_CODES.Add("it"); AVAILABLE_LANGUAGE_CODES.Add("ja"); AVAILABLE_LANGUAGE_CODES.Add("ko"); AVAILABLE_LANGUAGE_CODES.Add("nl"); AVAILABLE_LANGUAGE_CODES.Add("pl"); AVAILABLE_LANGUAGE_CODES.Add("pt"); AVAILABLE_LANGUAGE_CODES.Add("ru"); AVAILABLE_LANGUAGE_CODES.Add("zh-tw"); AVAILABLE_LANGUAGE_CODES.Add("zh"); LANGUAGE_LABELS.Clear(); LANGUAGE_LABELS["de"] = "Deutsch"; LANGUAGE_LABELS["en"] = "English"; LANGUAGE_LABELS["es"] = "Español"; LANGUAGE_LABELS["fr"] = "Français"; LANGUAGE_LABELS["it"] = "Italiano"; LANGUAGE_LABELS["ja"] = "日本語"; LANGUAGE_LABELS["ko"] = "한국의"; LANGUAGE_LABELS["nl"] = "Nederlands"; LANGUAGE_LABELS["pl"] = "Polski"; LANGUAGE_LABELS["pt"] = "Português"; LANGUAGE_LABELS["ru"] = "русский язык"; LANGUAGE_LABELS["zh-tw"] = "中文 (繁體)"; LANGUAGE_LABELS["zh"] = "中文 (简体)"; } private const string RESOURCES_PREFIX = "TrafficManager.Resources."; private static readonly string DEFAULT_TRANSLATION_FILENAME = "lang.txt"; private static Dictionary translations; private static string loadedLanguage = null; public static string GetString(string key) { LoadTranslations(); string ret = null; if (translations.TryGetValue(key, out ret)) { return ret; } return key; } public static bool HasString(string key) { LoadTranslations(); return translations.ContainsKey(key); } public static string GetTranslatedFileName(string filename) { string language = GetCurrentLanguage(); switch (language) { case "jaex": language = "ja"; break; case "zh-cn": language = "zh"; break; case "kr": language = "ko"; break; } string translatedFilename = filename; if (language != DEFAULT_LANGUAGE_CODE) { int delimiterIndex = filename.Trim().LastIndexOf('.'); // file extension translatedFilename = ""; if (delimiterIndex >= 0) translatedFilename = filename.Substring(0, delimiterIndex); translatedFilename += "_" + language.Trim().ToLower(); if (delimiterIndex >= 0) translatedFilename += filename.Substring(delimiterIndex); } if (Assembly.GetExecutingAssembly().GetManifestResourceNames().Contains(RESOURCES_PREFIX + translatedFilename)) { Log._Debug($"Translated file {translatedFilename} found"); return translatedFilename; } else { if (language != null && !DEFAULT_LANGUAGE_CODE.Equals(language)) Log.Warning($"Translated file {translatedFilename} not found!"); return filename; } } public static string GetCurrentLanguage() { string lang = GlobalConfig.Instance.LanguageCode; if (lang != null) { return lang; } return LocaleManager.instance.language; } public static void SetCurrentLanguage(string lang) { if (lang != null && !LANGUAGE_LABELS.ContainsKey(lang)) { Log.Warning($"Translation.SetCurrentLanguage: Invalid language code: {lang}"); return; } GlobalConfig.Instance.LanguageCode = lang; } private static void LoadTranslations() { string currentLang = GetCurrentLanguage(); if (translations == null || loadedLanguage == null || ! loadedLanguage.Equals(currentLang)) { try { string filename = RESOURCES_PREFIX + GetTranslatedFileName(DEFAULT_TRANSLATION_FILENAME); Log._Debug($"Loading translations from file '{filename}'. Language={currentLang}"); string[] lines; using (Stream st = Assembly.GetExecutingAssembly().GetManifestResourceStream(filename)) { using (StreamReader sr = new StreamReader(st)) { lines = sr.ReadToEnd().Split(new string[] { "\n", "\r\n" }, StringSplitOptions.None); } } translations = new Dictionary(); foreach (string line in lines) { if (line == null || line.Trim().Length == 0) { continue; } int delimiterIndex = line.Trim().IndexOf(' '); if (delimiterIndex > 0) { try { string translationKey = line.Substring(0, delimiterIndex); string translationValue = line.Substring(delimiterIndex + 1).Trim().Replace("\\n", "\n"); translations.Add(translationKey, translationValue); } catch (Exception) { Log.Warning($"Failed to add translation for key {line.Substring(0, delimiterIndex)}, language {currentLang}. Possible duplicate?"); } } } loadedLanguage = currentLang; } catch (Exception e) { Log.Error($"Error while loading translations: {e.ToString()}"); } } } public static void ReloadTutorialTranslations() { Locale locale = (Locale)typeof(LocaleManager).GetField("m_Locale", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(SingletonLite.instance); foreach (KeyValuePair entry in translations) { if (!entry.Key.StartsWith(TUTORIAL_KEY_PREFIX)) { continue; } string identifier; string tutorialKey; if (entry.Key.StartsWith(TUTORIAL_HEAD_KEY_PREFIX)) { identifier = "TUTORIAL_ADVISER_TITLE"; tutorialKey = TUTORIAL_KEY_PREFIX + entry.Key.Substring(TUTORIAL_HEAD_KEY_PREFIX.Length); } else if (entry.Key.StartsWith(TUTORIAL_BODY_KEY_PREFIX)) { identifier = "TUTORIAL_ADVISER"; tutorialKey = TUTORIAL_KEY_PREFIX + entry.Key.Substring(TUTORIAL_BODY_KEY_PREFIX.Length); } else { continue; } //Log._Debug($"Adding tutorial translation for id {identifier}, key={tutorialKey} value={entry.Value}"); Locale.Key key = new Locale.Key() { m_Identifier = identifier, m_Key = tutorialKey }; if (!locale.Exists(key)) { locale.AddLocalizedString(key, entry.Value); } } } internal static int getMenuWidth() { switch (GetCurrentLanguage()) { case null: case "en": case "de": default: return 220; case "ru": case "pl": return 260; case "es": case "fr": case "it": return 240; case "nl": return 270; } } internal static void OnLevelUnloading() { translations = null; } } } ================================================ FILE: TLM/TLM/UI/TransportDemandViewMode.cs ================================================ namespace TrafficManager.UI { public enum TransportDemandViewMode { Incoming, Outgoing } } ================================================ FILE: TLM/TLM/UI/UIBase.cs ================================================ using ColossalFramework.UI; using CSUtil.Commons; using System; using System.Collections.Generic; using TrafficManager.State; using TrafficManager.TrafficLight; using TrafficManager.UI.MainMenu; using TrafficManager.Util; using UnityEngine; namespace TrafficManager.UI { public class UIBase : UICustomControl { public UIMainMenuButton MainMenuButton { get; private set; } public MainMenuPanel MainMenu { get; private set; } #if DEBUG public DebugMenuPanel DebugMenu { get; private set; } #endif public static TrafficManagerTool GetTrafficManagerTool(bool createIfRequired=true) { if (tool == null && createIfRequired) { Log.Info("Initializing traffic manager tool..."); tool = ToolsModifierControl.toolController.gameObject.GetComponent() ?? ToolsModifierControl.toolController.gameObject.AddComponent(); tool.Initialize(); } return tool; } private static TrafficManagerTool tool = null; public static TrafficManagerMode ToolMode { get; set; } private bool _uiShown = false; public UIBase() { Log._Debug("##### Initializing UIBase."); // Get the UIView object. This seems to be the top-level object for most // of the UI. var uiView = UIView.GetAView(); // Add a new button to the view. MainMenuButton = (UIMainMenuButton)uiView.AddUIComponent(typeof(UIMainMenuButton)); // add the menu MainMenu = (MainMenuPanel)uiView.AddUIComponent(typeof(MainMenuPanel)); MainMenu.gameObject.AddComponent(); #if DEBUG DebugMenu = (DebugMenuPanel)uiView.AddUIComponent(typeof(DebugMenuPanel)); #endif ToolMode = TrafficManagerMode.None; } ~UIBase() { UnityEngine.Object.Destroy(MainMenuButton); UnityEngine.Object.Destroy(MainMenu); } public bool IsVisible() { return _uiShown; } public void ToggleMainMenu() { if (IsVisible()) Close(); else Show(); } internal void RebuildMenu() { Close(); if (MainMenu != null) { CustomKeyHandler keyHandler = MainMenu.GetComponent(); if(keyHandler != null) UnityEngine.Object.Destroy(keyHandler); UnityEngine.Object.Destroy(MainMenu); #if DEBUG UnityEngine.Object.Destroy(DebugMenu); #endif } var uiView = UIView.GetAView(); MainMenu = (MainMenuPanel)uiView.AddUIComponent(typeof(MainMenuPanel)); MainMenu.gameObject.AddComponent(); #if DEBUG DebugMenu = (DebugMenuPanel)uiView.AddUIComponent(typeof(DebugMenuPanel)); #endif } public void Show() { try { ToolsModifierControl.mainToolbar.CloseEverything(); } catch (Exception e) { Log.Error("Error on Show(): " + e.ToString()); } foreach (MenuButton button in GetMenu().Buttons) { button.UpdateProperties(); } GetMenu().Show(); Translation.ReloadTutorialTranslations(); TrafficManagerTool.ShowAdvisor("MainMenu"); #if DEBUG GetDebugMenu().Show(); #endif SetToolMode(TrafficManagerMode.Activated); _uiShown = true; MainMenuButton.UpdateSprites(); UIView.SetFocus(MainMenu); } public void Close() { var uiView = UIView.GetAView(); GetMenu().Hide(); #if DEBUG GetDebugMenu().Hide(); #endif TrafficManagerTool tmTool = GetTrafficManagerTool(false); if (tmTool != null) { tmTool.SetToolMode(UI.ToolMode.None); } SetToolMode(TrafficManagerMode.None); _uiShown = false; MainMenuButton.UpdateSprites(); } internal MainMenuPanel GetMenu() { return MainMenu; } #if DEBUG internal DebugMenuPanel GetDebugMenu() { return DebugMenu; } #endif public static void SetToolMode(TrafficManagerMode mode) { if (mode == ToolMode) return; ToolMode = mode; if (mode != TrafficManagerMode.None) { EnableTool(); } else { DisableTool(); } } public static void EnableTool() { Log._Debug("LoadingExtension.EnableTool: called"); TrafficManagerTool tmTool = GetTrafficManagerTool(true); ToolsModifierControl.toolController.CurrentTool = tmTool; ToolsModifierControl.SetTool(); } public static void DisableTool() { Log._Debug("LoadingExtension.DisableTool: called"); ToolsModifierControl.toolController.CurrentTool = ToolsModifierControl.GetTool(); ToolsModifierControl.SetTool(); } internal static void ReleaseTool() { if (ToolMode != TrafficManagerMode.None) { ToolMode = TrafficManagerMode.None; DestroyTool(); } } private static void DestroyTool() { if (ToolsModifierControl.toolController != null) { ToolsModifierControl.toolController.CurrentTool = ToolsModifierControl.GetTool(); ToolsModifierControl.SetTool(); if (tool != null) { UnityEngine.Object.Destroy(tool); tool = null; } } else Log.Warning("LoadingExtensions.DestroyTool: ToolsModifierControl.toolController is null!"); } } } ================================================ FILE: TLM/TLM/UI/UIMainMenuButton.cs ================================================ using ColossalFramework.Math; using ColossalFramework.UI; using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.State; using TrafficManager.Util; using UnityEngine; namespace TrafficManager.UI { public class UIMainMenuButton : UIButton, IObserver { public const string MAIN_MENU_BUTTON_BG_BASE = "TMPE_MainMenuButtonBgBase"; public const string MAIN_MENU_BUTTON_BG_HOVERED = "TMPE_MainMenuButtonBgHovered"; public const string MAIN_MENU_BUTTON_BG_ACTIVE = "TMPE_MainMenuButtonBgActive"; public const string MAIN_MENU_BUTTON_FG_BASE = "TMPE_MainMenuButtonFgBase"; public const string MAIN_MENU_BUTTON_FG_HOVERED = "TMPE_MainMenuButtonFgHovered"; public const string MAIN_MENU_BUTTON_FG_ACTIVE = "TMPE_MainMenuButtonFgActive"; public const int BUTTON_WIDTH = 50; public const int BUTTON_HEIGHT = 50; public UIDragHandle Drag { get; private set; } IDisposable confDisposable; public override void Start() { // Place the button. OnUpdate(GlobalConfig.Instance); confDisposable = GlobalConfig.Instance.Subscribe(this); // Set the atlas and background/foreground atlas = TextureUtil.GenerateLinearAtlas("TMPE_MainMenuButtonAtlas", TextureResources.MainMenuButtonTexture2D, 6, new string[] { MAIN_MENU_BUTTON_BG_BASE, MAIN_MENU_BUTTON_BG_HOVERED, MAIN_MENU_BUTTON_BG_ACTIVE, MAIN_MENU_BUTTON_FG_BASE, MAIN_MENU_BUTTON_FG_HOVERED, MAIN_MENU_BUTTON_FG_ACTIVE }); UpdateSprites(); // Set the button dimensions. width = BUTTON_WIDTH; height = BUTTON_HEIGHT; // Enable button sounds. playAudioEvents = true; var dragHandler = new GameObject("TMPE_MainButton_DragHandler"); dragHandler.transform.parent = transform; dragHandler.transform.localPosition = Vector3.zero; Drag = dragHandler.AddComponent(); Drag.width = width; Drag.height = height; Drag.enabled = !GlobalConfig.Instance.Main.MainMenuButtonPosLocked; } public override void OnDestroy() { if (confDisposable != null) { confDisposable.Dispose(); } } internal void SetPosLock(bool lck) { Drag.enabled = !lck; } protected override void OnClick(UIMouseEventParameter p) { Log._Debug($"Current tool: {ToolManager.instance.m_properties.CurrentTool}"); LoadingExtension.BaseUI.ToggleMainMenu(); UpdateSprites(); } protected override void OnPositionChanged() { GlobalConfig config = GlobalConfig.Instance; bool posChanged = (config.Main.MainMenuButtonX != (int)absolutePosition.x || config.Main.MainMenuButtonY != (int)absolutePosition.y); if (posChanged) { Log._Debug($"Button position changed to {absolutePosition.x}|{absolutePosition.y}"); config.Main.MainMenuButtonX = (int)absolutePosition.x; config.Main.MainMenuButtonY = (int)absolutePosition.y; GlobalConfig.WriteConfig(); } base.OnPositionChanged(); } internal void UpdateSprites() { if (! LoadingExtension.BaseUI.IsVisible()) { m_BackgroundSprites.m_Normal = m_BackgroundSprites.m_Disabled = m_BackgroundSprites.m_Focused = MAIN_MENU_BUTTON_BG_BASE; m_BackgroundSprites.m_Hovered = MAIN_MENU_BUTTON_BG_HOVERED; m_PressedBgSprite = MAIN_MENU_BUTTON_BG_ACTIVE; m_ForegroundSprites.m_Normal = m_ForegroundSprites.m_Disabled = m_ForegroundSprites.m_Focused = MAIN_MENU_BUTTON_FG_BASE; m_ForegroundSprites.m_Hovered = MAIN_MENU_BUTTON_FG_HOVERED; m_PressedFgSprite = MAIN_MENU_BUTTON_FG_ACTIVE; } else { m_BackgroundSprites.m_Normal = m_BackgroundSprites.m_Disabled = m_BackgroundSprites.m_Focused = m_BackgroundSprites.m_Hovered = MAIN_MENU_BUTTON_BG_ACTIVE; m_PressedBgSprite = MAIN_MENU_BUTTON_BG_HOVERED; m_ForegroundSprites.m_Normal = m_ForegroundSprites.m_Disabled = m_ForegroundSprites.m_Focused = m_ForegroundSprites.m_Hovered = MAIN_MENU_BUTTON_FG_ACTIVE; m_PressedFgSprite = MAIN_MENU_BUTTON_FG_HOVERED; } this.Invalidate(); } public void OnUpdate(GlobalConfig config) { UpdatePosition(new Vector2(config.Main.MainMenuButtonX, config.Main.MainMenuButtonY)); } public void UpdatePosition(Vector2 pos) { Rect rect = new Rect(pos.x, pos.y, BUTTON_WIDTH, BUTTON_HEIGHT); Vector2 resolution = UIView.GetAView().GetScreenResolution(); VectorUtil.ClampRectToScreen(ref rect, resolution); Log.Info($"Setting main menu button position to [{pos.x},{pos.y}]"); absolutePosition = rect.position; Invalidate(); } } } ================================================ FILE: TLM/TLM/UI/UITransportDemand.cs ================================================ using System; using System.Linq; using ColossalFramework; using ColossalFramework.UI; using TrafficManager.Geometry; using TrafficManager.TrafficLight; using UnityEngine; using TrafficManager.State; using TrafficManager.Custom.PathFinding; using System.Collections.Generic; using TrafficManager.Manager; using CSUtil.Commons; namespace TrafficManager.UI { public class UITransportDemand : UIPanel { private UIButton switchViewModeButton; private UILabel viewModeLabel; public override void Start() { base.Start(); var transportInfoViewPanel = GameObject.Find("(Library) PublicTransportInfoViewPanel").GetComponent(); if (transportInfoViewPanel != null) { Log._Debug($"Public transport info view panel found."); transportInfoViewPanel.component.eventVisibilityChanged += new PropertyChangedEventHandler(this.ParentVisibilityChanged); } else { Log.Warning($"Public transport info view panel NOT found."); } isInteractive = true; isVisible = false; backgroundSprite = "GenericPanel"; color = new Color32(75, 75, 135, 255); width = 156; height = 48; relativePosition = new Vector3(540f, 10f); viewModeLabel = AddUIComponent(); viewModeLabel.text = Translation.GetString("Outgoing_demand"); viewModeLabel.relativePosition = new Vector3(3f, 33f); viewModeLabel.textScale = 0.75f; switchViewModeButton = _createButton(Translation.GetString("Switch_view"), 3, 3, clickSwitchViewMode); } private UIButton _createButton(string text, int x, int y, MouseEventHandler eventClick) { var button = AddUIComponent(); button.textScale = 0.8f; button.width = 150f; button.height = 30; button.normalBgSprite = "ButtonMenu"; button.disabledBgSprite = "ButtonMenuDisabled"; button.hoveredBgSprite = "ButtonMenuHovered"; button.focusedBgSprite = "ButtonMenu"; button.pressedBgSprite = "ButtonMenuPressed"; button.textColor = new Color32(255, 255, 255, 255); button.playAudioEvents = true; button.text = text; button.relativePosition = new Vector3(x, y); button.eventClick += eventClick; return button; } private void clickSwitchViewMode(UIComponent component, UIMouseEventParameter eventParam) { if (TrafficManagerTool.CurrentTransportDemandViewMode == TransportDemandViewMode.Outgoing) { viewModeLabel.text = Translation.GetString("Incoming_demand"); TrafficManagerTool.CurrentTransportDemandViewMode = TransportDemandViewMode.Incoming; } else { viewModeLabel.text = Translation.GetString("Outgoing_demand"); TrafficManagerTool.CurrentTransportDemandViewMode = TransportDemandViewMode.Outgoing; } } private void ParentVisibilityChanged(UIComponent component, bool value) { Log._Debug($"Public transport info view panel changed visibility: {value}"); if (value && Options.prohibitPocketCars) { TrafficManagerTool.CurrentTransportDemandViewMode = TransportDemandViewMode.Outgoing; if (viewModeLabel != null) viewModeLabel.text = Translation.GetString("Outgoing_demand"); if (switchViewModeButton != null) switchViewModeButton.text = Translation.GetString("Switch_view"); this.Show(); } else this.Hide(); } } } ================================================ FILE: TLM/TLM/Util/GenericObservable.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace TrafficManager.Util { public abstract class GenericObservable : IObservable { /// /// Holds a list of observers which are being notified as soon as the managed node's geometry is updated (but not neccessarily modified) /// protected List> Observers = new List>(); /// /// Lock object. Acquire this before accessing the HashSets. /// protected object ObserverLock = new object(); /// /// Registers an observer. /// /// /// An unsubscriber public IDisposable Subscribe(IObserver observer) { //Log._Debug($"GenericObserable.Subscribe: Subscribing observer {observer} to observable {this}"); try { Monitor.Enter(ObserverLock); Observers.Add(observer); } finally { Monitor.Exit(ObserverLock); } return new GenericUnsubscriber(Observers, observer, ObserverLock); } /// /// Notifies all observers that the observable object' state has changed /// public virtual void NotifyObservers(T subject) { //Log._Debug($"GenericObserable.NotifyObservers: Notifying observers of observable {this}"); List> myObservers = new List>(Observers); // in case somebody unsubscribes while iterating over subscribers foreach (IObserver observer in myObservers) { try { //Log._Debug($"GenericObserable.NotifyObservers: Notifying observer {observer} of observable {this}"); observer.OnUpdate(subject); } catch (Exception e) { Log.Error($"GenericObserable.NotifyObservers: An exception occured while notifying an observer of observable {this}: {e}"); } } } } } ================================================ FILE: TLM/TLM/Util/GenericUnsubscriber.cs ================================================ using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace TrafficManager.Util { public class GenericUnsubscriber : IDisposable { private List> observers; private IObserver observer; public object lck; public GenericUnsubscriber(List> observers, IObserver observer, object lck) { this.observers = observers; this.observer = observer; this.lck = lck; } public void Dispose() { if (observer != null) { try { Monitor.Enter(lck); observers.Remove(observer); } finally { Monitor.Exit(lck); } } } } } ================================================ FILE: TLM/TLM/Util/IObservable.cs ================================================ using System; using System.Collections.Generic; using System.Text; namespace TrafficManager.Util { public interface IObservable { IDisposable Subscribe(IObserver observer); } } ================================================ FILE: TLM/TLM/Util/IObserver.cs ================================================ using System; using System.Collections.Generic; using System.Text; namespace TrafficManager.Util { public interface IObserver { void OnUpdate(T subject); } } ================================================ FILE: TLM/TLM/Util/IVisitor.cs ================================================ using System; using System.Collections.Generic; using System.Text; namespace TrafficManager.Util { public interface IVisitor { bool Visit(Target target); } } ================================================ FILE: TLM/TLM/Util/LoopUtil.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Util { public static class LoopUtil { public delegate bool TwoDimLoopHandler(int x, int y); public static void SpiralLoop(int xCenter, int yCenter, int width, int height, TwoDimLoopHandler handler) { SpiralLoop(width, height, delegate(int x, int y) { return handler(xCenter + x, yCenter + y); }); } public static void SpiralLoop(int width, int height, TwoDimLoopHandler handler) { int x, y, dx, dy; x = y = dx = 0; dy = -1; int t = width > height ? width : height; int maxI = t * t; for (int i = 0; i < maxI; i++) { if ((-width / 2 <= x) && (x <= width / 2) && (-height / 2 <= y) && (y <= height / 2)) { if (! handler(x, y)) break; } if ((x == y) || ((x < 0) && (x == -y)) || ((x > 0) && (x == 1 - y))) { t = dx; dx = -dy; dy = t; } x += dx; y += dy; } } } } ================================================ FILE: TLM/TLM/Util/ModsCompatibilityChecker.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using ColossalFramework.PlatformServices; using ColossalFramework.UI; using CSUtil.Commons; using TrafficManager.UI; using UnityEngine; namespace TrafficManager.Util { public class ModsCompatibilityChecker { //TODO include %APPDATA% mods folder private const string RESOURCES_PREFIX = "TrafficManager.Resources."; private const string DEFAULT_INCOMPATIBLE_MODS_FILENAME = "incompatible_mods.txt"; private readonly ulong[] userModList; private readonly Dictionary incompatibleModList; public ModsCompatibilityChecker() { incompatibleModList = LoadIncompatibleModList(); userModList = GetUserModsList(); } public void PerformModCheck() { Log.Info("Performing incompatible mods check"); Dictionary incompatibleMods = new Dictionary(); for (int i = 0; i < userModList.Length; i++) { string incompatibleModName; if (incompatibleModList.TryGetValue(userModList[i], out incompatibleModName)) { incompatibleMods.Add(userModList[i], incompatibleModName); } } if (incompatibleMods.Count > 0) { Log.Warning("Incompatible mods detected! Count: " + incompatibleMods.Count); IncompatibleModsPanel panel = UIView.GetAView().AddUIComponent(typeof(IncompatibleModsPanel)) as IncompatibleModsPanel; panel.IncompatibleMods = incompatibleMods; panel.Initialize(); UIView.PushModal(panel); UIView.SetFocus(panel); } else { Log.Info("No incompatible mods detected"); } } private Dictionary LoadIncompatibleModList() { Dictionary incompatibleMods = new Dictionary(); string[] lines; using (Stream st = Assembly.GetExecutingAssembly().GetManifestResourceStream(RESOURCES_PREFIX + DEFAULT_INCOMPATIBLE_MODS_FILENAME)) { using (StreamReader sr = new StreamReader(st)) { lines = sr.ReadToEnd().Split(new string[] {"\n", "\r\n"}, StringSplitOptions.None); } } for (int i = 0; i < lines.Length; i++) { string[] strings = lines[i].Split(';'); ulong steamId; if (ulong.TryParse(strings[0], out steamId)) { incompatibleMods.Add(steamId, strings[1]); } } return incompatibleMods; } private ulong[] GetUserModsList() { PublishedFileId[] ids = ContentManagerPanel.subscribedItemsTable.ToArray(); return ids.Select(id => id.AsUInt64).ToArray(); } } } ================================================ FILE: TLM/TLM/Util/README.md ================================================ # TM:PE -- /Util Utility classes. ## Classes - **GenericUnsubscriber**: Allows an observer to unsubscribe from an observed object. - **IObservable**: Describes an object that can be observed. - **IObserver**: Describes an object that can observe observable objects. - **IVisitor**: Generic visitor - **RedirectionHelper**: Helper methods for detouring. - **SegmentLaneTraverser**: Allows a visitor to traverse segment lanes. - **SegmentTraverser**: Allows a visitor to traverse segments. - **TextureUtil**: Texture utilities. - **TinyDictionary**: A dictionary implementation for tiny mappings. Not thread-safe. ================================================ FILE: TLM/TLM/Util/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 CSUtil.Commons; using System; using System.Reflection; namespace TrafficManager.Util { 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(); Log._Debug($"RedirectionHeler.RedirectCalls({from.Name}, {to.Name}) called. IsRedirected1={IsRedirected(fptr1, fptr2)} IsRedirected2={IsRedirected(from.MethodHandle.GetFunctionPointer(), to.MethodHandle.GetFunctionPointer())}"); Log._Debug($"RedirectionHeler.RedirectCalls({from.Name}, {to.Name}): before redir. fptr1_fnc={GetRedir(fptr1).ArrayToString()} fptr1={(ulong)fptr1.ToInt64()} fptr2={(ulong)fptr2.ToInt64()}"); RedirectCallsState ret = PatchJumpTo(fptr1, fptr2); Log._Debug($"RedirectionHeler.RedirectCalls({from.Name}, {to.Name}): after redir. fptr1_fnc={GetRedir(fptr1).ArrayToString()} fptr1={(ulong)fptr1.ToInt64()} fptr2={(ulong)fptr2.ToInt64()}"); Log._Debug($"RedirectionHeler.RedirectCalls({from.Name}, {to.Name}) finished. IsRedirected1={IsRedirected(fptr1, fptr2)} IsRedirected2={IsRedirected(from.MethodHandle.GetFunctionPointer(), to.MethodHandle.GetFunctionPointer())}"); return ret; } public static void RevertRedirect(MethodInfo from, RedirectCallsState state) { var fptr1 = from.MethodHandle.GetFunctionPointer(); RevertJumpTo(fptr1, state); } public static bool IsRedirected(MethodInfo from, MethodInfo to) { var fptr1 = from.MethodHandle.GetFunctionPointer(); var fptr2 = to.MethodHandle.GetFunctionPointer(); return IsRedirected(fptr1, fptr2); } private static byte[] GetRedir(IntPtr ptr) { byte[] ret = new byte[13]; unsafe { byte* pointer = (byte*)ptr.ToPointer(); for (int i = 0; i < 13; ++i) { ret[i] = *(pointer + i); } } return ret; } private static bool IsRedirected(IntPtr site, IntPtr target) { unsafe { byte* sitePtr = (byte*)site.ToPointer(); return *sitePtr == 0x49 && *(sitePtr + 1) == 0xBB && *((ulong*)(sitePtr + 2)) == (ulong)target.ToInt64() && *(sitePtr + 10) == 0x41 && *(sitePtr + 11) == 0xFF && *(sitePtr + 12) == 0xE3; } } /// /// Primitive patching. Inserts a jump to 'target' at 'site'. Works even if both methods' /// callers have already been compiled. /// /// /// private 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; } private 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/TLM/Util/SegmentLaneTraverser.cs ================================================ using CSUtil.Commons; using GenericGameBridge.Service; using System; using System.Collections.Generic; using System.Linq; using System.Text; using TrafficManager.Geometry; using static TrafficManager.Util.SegmentTraverser; namespace TrafficManager.Util { public class SegmentLaneTraverser { public delegate bool SegmentLaneVisitor(SegmentLaneVisitData data); [Flags] public enum LaneStopCriterion { /// /// Traversal stops when the whole network has been visited /// None = 0, /// /// Traversal stops when a segment consists of a different number of filtered lanes than the initial segment /// LaneCount = 1 } public class SegmentLaneVisitData { /// /// Segment visit data /// public SegmentVisitData segVisitData; /// /// Iteration index /// public int sortedLaneIndex; /// /// current traversed lane position /// public LanePos curLanePos; /// /// matching initial lane position /// public LanePos initLanePos; public SegmentLaneVisitData(SegmentVisitData segVisitData, int sortedLaneIndex, LanePos curLanePos, LanePos initLanePos) { this.segVisitData = segVisitData; this.sortedLaneIndex = sortedLaneIndex; this.curLanePos = curLanePos; this.initLanePos = initLanePos; } } public static void Traverse(ushort initialSegmentId, TraverseDirection direction, TraverseSide side, LaneStopCriterion laneStopCrit, SegmentStopCriterion segStopCrit, NetInfo.LaneType? laneTypeFilter, VehicleInfo.VehicleType? vehicleTypeFilter, SegmentLaneVisitor laneVisitor) { IList initialSortedLanes = null; SegmentTraverser.Traverse(initialSegmentId, direction, side, segStopCrit, delegate(SegmentVisitData segData) { bool isInitialSeg = segData.initial; bool reverse = !isInitialSeg && segData.viaStartNode == segData.viaInitialStartNode; bool ret = false; Constants.ServiceFactory.NetService.ProcessSegment(segData.curGeo.SegmentId, delegate (ushort segmentId, ref NetSegment segment) { Log._Debug($"SegmentLaneTraverser: Reached segment {segmentId}: isInitialSeg={isInitialSeg} viaStartNode={segData.viaStartNode} viaInitialStartNode={segData.viaInitialStartNode} reverse={reverse}"); IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(segmentId, ref segment, null, laneTypeFilter, vehicleTypeFilter, reverse); if (isInitialSeg) { initialSortedLanes = sortedLanes; } else if (initialSortedLanes == null) { throw new ApplicationException("Initial list of sorted lanes not set."); } else if (sortedLanes.Count != initialSortedLanes.Count && (laneStopCrit & LaneStopCriterion.LaneCount) != LaneStopCriterion.None) { Log._Debug($"SegmentLaneTraverser: Stop criterion reached @ {segmentId}: {sortedLanes.Count} current vs. {initialSortedLanes.Count} initial lanes"); return false; } for (int i = 0; i < sortedLanes.Count; ++i) { Log._Debug($"SegmentLaneTraverser: Traversing segment lane {sortedLanes[i].laneIndex} @ {segmentId} (id {sortedLanes[i].laneId}, pos {sortedLanes[i].position})"); if (!laneVisitor(new SegmentLaneVisitData(segData, i, sortedLanes[i], initialSortedLanes[i]))) { return false; } } ret = true; return true; }); return ret; }); } } } ================================================ FILE: TLM/TLM/Util/SegmentTraverser.cs ================================================ using CSUtil.Commons; using System; using System.Collections.Generic; using System.Text; using TrafficManager.Custom.AI; using TrafficManager.Geometry; using TrafficManager.Geometry.Impl; namespace TrafficManager.Util { public class SegmentTraverser { [Flags] public enum TraverseDirection { None = 0, Incoming = 1, Outgoing = 1 << 1, AnyDirection = Incoming | Outgoing } [Flags] public enum TraverseSide { None = 0, Left = 1, Straight = 1 << 1, Right = 1 << 2, AnySide = Left | Straight | Right } public delegate bool SegmentVisitor(SegmentVisitData data); [Flags] public enum SegmentStopCriterion { /// /// Traversal stops when the whole network has been visited /// None = 0, /// /// Traversal stops when a node with more than two connected segments has been reached /// Junction = 1, } public class SegmentVisitData { /// /// Previously visited segment geometry /// public SegmentGeometry prevGeo; /// /// Current segment geometry /// public SegmentGeometry curGeo; /// /// If true the current segment geometry has been reached on a path via the initial segment's start node /// public bool viaInitialStartNode; /// /// If true the current segment geometry has been reached on a path via the current segment's start node /// public bool viaStartNode; /// /// If true this is the initial segment /// public bool initial; public SegmentVisitData(SegmentGeometry prevGeo, SegmentGeometry curGeo, bool viaInitialStartNode, bool viaStartNode, bool initial) { this.prevGeo = prevGeo; this.curGeo = curGeo; this.viaInitialStartNode = viaInitialStartNode; this.viaStartNode = viaStartNode; this.initial = initial; } } /// /// Performs a Depth-First traversal over the cached segment geometry structure. At each traversed segment, the given `visitor` is notified. It then can update the current `state`. /// /// Specifies the segment at which the traversal should start. /// Specifies if the next node to traverse is the start node of the initial segment. /// Specifies if traffic should be able to flow towards the initial segment (Incoming) or should be able to flow from the initial segment (Outgoing) or in both directions (Both). /// Specifies the maximum depth to explore. At a depth of 0, no segment will be traversed (event the initial segment will be omitted). /// Specifies the stateful visitor that should be notified as soon as a traversable segment (which has not been traversed before) is found. public static void Traverse(ushort initialSegmentId, TraverseDirection direction, TraverseSide side, SegmentStopCriterion stopCrit, SegmentVisitor visitor) { SegmentGeometry initialSegGeometry = SegmentGeometry.Get(initialSegmentId); if (initialSegGeometry == null) return; Log._Debug($"SegmentTraverser: Traversing initial segment {initialSegGeometry.SegmentId}"); if (visitor(new SegmentVisitData(initialSegGeometry, initialSegGeometry, false, false, true))) { HashSet visitedSegmentIds = new HashSet(); visitedSegmentIds.Add(initialSegmentId); TraverseRec(initialSegGeometry, true, true, direction, side, stopCrit, visitor, visitedSegmentIds); TraverseRec(initialSegGeometry, false, false, direction, side, stopCrit, visitor, visitedSegmentIds); } Log._Debug($"SegmentTraverser: Traversal finished."); } private static void TraverseRec(SegmentGeometry prevSegGeometry, bool exploreStartNode, bool viaInitialStartNode, TraverseDirection direction, TraverseSide side, SegmentStopCriterion stopCrit, SegmentVisitor visitor, HashSet visitedSegmentIds) { Log._Debug($"SegmentTraverser: Traversing segment {prevSegGeometry.SegmentId}"); // collect next segment ids to traverse if (direction == TraverseDirection.None) { throw new ArgumentException($"Invalid direction {direction} given."); } if (side == TraverseSide.None) { throw new ArgumentException($"Invalid side {side} given."); } HashSet nextSegmentIds = new HashSet(); switch (direction) { case TraverseDirection.AnyDirection: default: if (side == TraverseSide.AnySide) { nextSegmentIds.UnionWith(prevSegGeometry.GetConnectedSegments(exploreStartNode)); } else { if ((side & TraverseSide.Left) != TraverseSide.None) { nextSegmentIds.UnionWith(prevSegGeometry.GetLeftSegments(exploreStartNode)); } if ((side & TraverseSide.Straight) != TraverseSide.None) { nextSegmentIds.UnionWith(prevSegGeometry.GetStraightSegments(exploreStartNode)); } if ((side & TraverseSide.Right) != TraverseSide.None) { nextSegmentIds.UnionWith(prevSegGeometry.GetRightSegments(exploreStartNode)); } } break; case TraverseDirection.Incoming: if (side == TraverseSide.AnySide) { nextSegmentIds.UnionWith(prevSegGeometry.GetIncomingSegments(exploreStartNode)); } else { if ((side & TraverseSide.Left) != TraverseSide.None) { nextSegmentIds.UnionWith(prevSegGeometry.GetIncomingLeftSegments(exploreStartNode)); } if ((side & TraverseSide.Straight) != TraverseSide.None) { nextSegmentIds.UnionWith(prevSegGeometry.GetIncomingStraightSegments(exploreStartNode)); } if ((side & TraverseSide.Right) != TraverseSide.None) { nextSegmentIds.UnionWith(prevSegGeometry.GetIncomingRightSegments(exploreStartNode)); } } break; case TraverseDirection.Outgoing: if (side == TraverseSide.AnySide) { nextSegmentIds.UnionWith(prevSegGeometry.GetOutgoingSegments(exploreStartNode)); } else { if ((side & TraverseSide.Left) != TraverseSide.None) { nextSegmentIds.UnionWith(prevSegGeometry.GetOutgoingLeftSegments(exploreStartNode)); } if ((side & TraverseSide.Straight) != TraverseSide.None) { nextSegmentIds.UnionWith(prevSegGeometry.GetOutgoingStraightSegments(exploreStartNode)); } if ((side & TraverseSide.Right) != TraverseSide.None) { nextSegmentIds.UnionWith(prevSegGeometry.GetOutgoingRightSegments(exploreStartNode)); } } break; } nextSegmentIds.Remove(0); Log._Debug($"SegmentTraverser: Fetched next segments to traverse: {nextSegmentIds.CollectionToString()}"); if (nextSegmentIds.Count >= 2 && (stopCrit & SegmentStopCriterion.Junction) != SegmentStopCriterion.None) { Log._Debug($"SegmentTraverser: Stop criterion reached @ {prevSegGeometry.SegmentId}: {nextSegmentIds.Count} connected segments"); return; } ushort prevNodeId = prevSegGeometry.GetNodeId(exploreStartNode); // explore next segments foreach (ushort nextSegmentId in nextSegmentIds) { if (nextSegmentId == 0 || visitedSegmentIds.Contains(nextSegmentId)) continue; visitedSegmentIds.Add(nextSegmentId); SegmentGeometry nextSegGeometry = SegmentGeometry.Get(nextSegmentId); if (nextSegGeometry != null) { Log._Debug($"SegmentTraverser: Traversing segment {nextSegGeometry.SegmentId}"); if (visitor(new SegmentVisitData(prevSegGeometry, nextSegGeometry, viaInitialStartNode, prevNodeId == nextSegGeometry.StartNodeId(), false))) { bool nextNodeIsStartNode = nextSegGeometry.StartNodeId() != prevNodeId; TraverseRec(nextSegGeometry, nextNodeIsStartNode, viaInitialStartNode, direction, side, stopCrit, visitor, visitedSegmentIds); } } } } } } ================================================ FILE: TLM/TLM/Util/TextureUtil.cs ================================================ using ColossalFramework.UI; using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; namespace TrafficManager.Util { public static class TextureUtil { public static UITextureAtlas GenerateLinearAtlas(string name, Texture2D texture, int numSprites, string[] spriteNames) { return Generate2DAtlas(name, texture, numSprites, 1, spriteNames); } public static UITextureAtlas Generate2DAtlas(string name, Texture2D texture, int numX, int numY, string[] spriteNames) { if (spriteNames.Length != numX * numY) { throw new ArgumentException($"Number of sprite name does not match dimensions (expected {numX} x {numY}, was {spriteNames.Length})"); } UITextureAtlas atlas = ScriptableObject.CreateInstance(); atlas.padding = 0; atlas.name = name; var shader = Shader.Find("UI/Default UI Shader"); if (shader != null) atlas.material = new Material(shader); atlas.material.mainTexture = texture; int spriteWidth = Mathf.RoundToInt((float)texture.width / (float)numX); int spriteHeight = Mathf.RoundToInt((float)texture.height / (float)numY); int k = 0; for (int i = 0; i < numX; ++i) { float x = (float)i / (float)numX; for (int j = 0; j < numY; ++j) { float y = (float)j / (float)numY; var sprite = new UITextureAtlas.SpriteInfo { name = spriteNames[k], region = new Rect(x, y, (float)spriteWidth / (float)texture.width, (float)spriteHeight / (float)texture.height) }; var spriteTexture = new Texture2D(spriteWidth, spriteHeight); spriteTexture.SetPixels(texture.GetPixels((int)((float)texture.width * sprite.region.x), (int)((float)texture.height * sprite.region.y), spriteWidth, spriteHeight)); sprite.texture = spriteTexture; atlas.AddSprite(sprite); ++k; } } return atlas; } } } ================================================ FILE: TLM/TLM/Util/TinyDictionary.cs ================================================ using CSUtil.Commons; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; namespace TrafficManager.Util { /// /// Dictionary for use cases with a small number of entries and arbitrary keys /// /// key type /// value type public class TinyDictionary : IDictionary { private TKey[] keys; private TValue[] values; private KeyValuePair[] keyValuePairs; public TinyDictionary() { Clear(); } public ICollection Keys { get { return keys.ToList(); } } public ICollection Values { get { return values.ToList(); } } public int Count { get { return values.Length; } } public bool IsReadOnly { get { return false; } } public override string ToString() { return this.DictionaryToString(); } public TValue this[TKey key] { get { if (key == null) { throw new ArgumentNullException(); } int keyIndex = IndexOfKey(key); if (keyIndex < 0) { throw new KeyNotFoundException($"Key '{key}' not found in dictionary: {string.Join(", ", keyValuePairs.Select(x => x.Key.ToString() + " => " + ToStringExt.ToString(x.Value)).ToArray())}"); } return values[keyIndex]; } set { if (key == null) { throw new ArgumentNullException(); } Add(key, value); } } public bool ContainsKey(TKey key) { return IndexOfKey(key) >= 0; } public void Add(TKey key, TValue value) { int keyIndex = IndexOfKey(key); if (keyIndex >= 0) { values[keyIndex] = value; keyValuePairs[keyIndex] = new KeyValuePair(key, value); } else { int len = Count; int newLen = len + 1; TKey[] newKeys = new TKey[newLen]; TValue[] newValues = new TValue[newLen]; KeyValuePair[] newKeyValuePairs = new KeyValuePair[newLen]; Array.Copy(keys, newKeys, len); Array.Copy(values, newValues, len); Array.Copy(keyValuePairs, newKeyValuePairs, len); newKeys[len] = key; newValues[len] = value; newKeyValuePairs[len] = new KeyValuePair(key, value); keys = newKeys; values = newValues; keyValuePairs = newKeyValuePairs; } } public bool Remove(TKey key) { int keyIndex = IndexOfKey(key); if (keyIndex < 0) { return false; } int len = Count; int newLen = len - 1; TKey[] newKeys = new TKey[newLen]; TValue[] newValues = new TValue[newLen]; KeyValuePair[] newKeyValuePairs = new KeyValuePair[newLen]; if (keyIndex > 0) { // copy 0..keyIndex-1 Array.Copy(keys, 0, newKeys, 0, keyIndex); Array.Copy(values, 0, newValues, 0, keyIndex); Array.Copy(keyValuePairs, 0, newKeyValuePairs, 0, keyIndex); } if (keyIndex < newLen) { // copy keyIndex+1..newLen-1 int remLen = newLen - keyIndex; Array.Copy(keys, keyIndex + 1, newKeys, keyIndex, remLen); Array.Copy(values, keyIndex + 1, newValues, keyIndex, remLen); Array.Copy(keyValuePairs, keyIndex + 1, newKeyValuePairs, keyIndex, remLen); } keys = newKeys; values = newValues; keyValuePairs = newKeyValuePairs; return true; } public bool TryGetValue(TKey key, out TValue value) { int keyIndex = IndexOfKey(key); if (keyIndex < 0) { value = default(TValue); return false; } value = values[keyIndex]; return true; } public void Add(KeyValuePair item) { Add(item.Key, item.Value); } public void Clear() { keys = new TKey[0]; values = new TValue[0]; keyValuePairs = new KeyValuePair[0]; } public bool Contains(KeyValuePair item) { return ContainsKey(item.Key); } public void CopyTo(KeyValuePair[] array, int arrayIndex) { keyValuePairs.CopyTo(array, arrayIndex); } public bool Remove(KeyValuePair item) { return Remove(item.Key); } public IEnumerator> GetEnumerator() { return new TinyDictionaryEnumerator(this); } IEnumerator IEnumerable.GetEnumerator() { return new TinyDictionaryEnumerator(this); } protected int IndexOfKey(TKey key) { if (key == null) { return -1; } for (int i = 0; i < keys.Length; ++i) { if (key.Equals(keys[i])) { return i; } } return -1; } protected class TinyDictionaryEnumerator : IEnumerator> { private int currentIndex = -1; private TinyDictionary dict; public TinyDictionaryEnumerator(TinyDictionary dict) { this.dict = dict; } public KeyValuePair Current { get { return dict.keyValuePairs[currentIndex]; } } object IEnumerator.Current { get { return dict.keyValuePairs[currentIndex]; } } public void Dispose() { dict = null; } public bool MoveNext() { return ++currentIndex < dict.Count; } public void Reset() { currentIndex = -1; } } } } ================================================ FILE: TLM/TLM/packages.config ================================================  ================================================ FILE: TLM/TLM.sln.DotSettings ================================================  AI GUI ================================================ FILE: TLM/TMPE.CitiesGameBridge/Factory/ServiceFactory.cs ================================================ using System; using GenericGameBridge.Factory; using GenericGameBridge.Service; namespace CitiesGameBridge.Factory { public class ServiceFactory : IServiceFactory { public static readonly IServiceFactory Instance = new ServiceFactory(); private ServiceFactory() { } public IBuildingService BuildingService { get { return Service.BuildingService.Instance; } } public ICitizenService CitizenService { get { return Service.CitizenService.Instance; } } public INetService NetService { get { return Service.NetService.Instance; } } public IPathService PathService { get { return Service.PathService.Instance; } } public ISimulationService SimulationService { get { return Service.SimulationService.Instance; } } public IVehicleService VehicleService { get { return Service.VehicleService.Instance; } } } } ================================================ FILE: TLM/TMPE.CitiesGameBridge/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("CitiesGameBridge")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CitiesGameBridge")] [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("3f2f7926-5d51-4880-a2b7-4594a10d7e54")] // 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/TMPE.CitiesGameBridge/Service/BuildingService.cs ================================================ using ColossalFramework; using CSUtil.Commons; using GenericGameBridge.Service; namespace CitiesGameBridge.Service { public class BuildingService : IBuildingService { public static readonly IBuildingService Instance = new BuildingService(); private BuildingService() { } public bool IsBuildingValid(ushort buildingId) { return CheckBuildingFlags(buildingId, Building.Flags.Created | Building.Flags.Deleted, Building.Flags.Created); } public bool CheckBuildingFlags(ushort buildingId, Building.Flags flagMask, Building.Flags? expectedResult = default(Building.Flags?)) { bool ret = false; ProcessBuilding(buildingId, delegate (ushort bId, ref Building building) { ret = LogicUtil.CheckFlags((uint)building.m_flags, (uint)flagMask, (uint?)expectedResult); return true; }); return ret; } public void ProcessBuilding(ushort buildingId, BuildingHandler handler) { ProcessBuilding(buildingId, ref Singleton.instance.m_buildings.m_buffer[buildingId], handler); } public void ProcessBuilding(ushort buildingId, ref Building building, BuildingHandler handler) { handler(buildingId, ref building); } } } ================================================ FILE: TLM/TMPE.CitiesGameBridge/Service/CitizenService.cs ================================================ using ColossalFramework; using CSUtil.Commons; using GenericGameBridge.Service; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace CitiesGameBridge.Service { public class CitizenService : ICitizenService { public static readonly ICitizenService Instance = new CitizenService(); private CitizenService() { } public bool CheckCitizenFlags(uint citizenId, Citizen.Flags flagMask, Citizen.Flags? expectedResult = default(Citizen.Flags?)) { bool ret = false; ProcessCitizen(citizenId, delegate (uint cId, ref Citizen citizen) { ret = LogicUtil.CheckFlags((uint)citizen.m_flags, (uint)flagMask, (uint?)expectedResult); return true; }); return ret; } public bool CheckCitizenInstanceFlags(ushort citizenInstanceId, CitizenInstance.Flags flagMask, CitizenInstance.Flags? expectedResult = default(CitizenInstance.Flags?)) { bool ret = false; ProcessCitizenInstance(citizenInstanceId, delegate (ushort ciId, ref CitizenInstance citizenInstance) { ret = LogicUtil.CheckFlags((uint)citizenInstance.m_flags, (uint)flagMask, (uint?)expectedResult); return true; }); return ret; } public bool IsCitizenInstanceValid(ushort citizenInstanceId) { return CheckCitizenInstanceFlags(citizenInstanceId, CitizenInstance.Flags.Created | CitizenInstance.Flags.Deleted, CitizenInstance.Flags.Created); } public bool IsCitizenValid(uint citizenId) { return CheckCitizenFlags(citizenId, Citizen.Flags.Created); } public void ProcessCitizen(uint citizenId, CitizenHandler handler) { ProcessCitizen(citizenId, ref Singleton.instance.m_citizens.m_buffer[citizenId], handler); } public void ProcessCitizen(uint citizenId, ref Citizen citizen, CitizenHandler handler) { handler(citizenId, ref citizen); } public void ProcessCitizenInstance(ushort citizenInstanceId, CitizenInstanceHandler handler) { ProcessCitizenInstance(citizenInstanceId, ref Singleton.instance.m_instances.m_buffer[citizenInstanceId], handler); } public void ProcessCitizenInstance(ushort citizenInstanceId, ref CitizenInstance citizenInstance, CitizenInstanceHandler handler) { handler(citizenInstanceId, ref citizenInstance); } public void ReleaseCitizenInstance(ushort citizenInstanceId) { Singleton.instance.ReleaseCitizenInstance(citizenInstanceId); } } } ================================================ FILE: TLM/TMPE.CitiesGameBridge/Service/NetService.cs ================================================ using ColossalFramework; using CSUtil.Commons; using GenericGameBridge.Service; using System; using System.Collections.Generic; namespace CitiesGameBridge.Service { public class NetService : INetService { public static readonly INetService Instance = new NetService(); private NetService() { } public bool IsSegmentValid(ushort segmentId) { return CheckSegmentFlags(segmentId, NetSegment.Flags.Created | NetSegment.Flags.Deleted, NetSegment.Flags.Created); } public void ProcessSegment(ushort segmentId, NetSegmentHandler handler) { ProcessSegment(segmentId, ref Singleton.instance.m_segments.m_buffer[segmentId], handler); } public void ProcessSegment(ushort segmentId, ref NetSegment segment, NetSegmentHandler handler) { handler(segmentId, ref segment); } public bool IsNodeValid(ushort nodeId) { return CheckNodeFlags(nodeId, NetNode.Flags.Created | NetNode.Flags.Deleted, NetNode.Flags.Created); } public void ProcessNode(ushort nodeId, NetNodeHandler handler) { ProcessNode(nodeId, ref Singleton.instance.m_nodes.m_buffer[nodeId], handler); } public void ProcessNode(ushort nodeId, ref NetNode node, NetNodeHandler handler) { handler(nodeId, ref node); } [Obsolete] bool IsLaneValid(ref NetLane lane) { if ((lane.m_flags & (uint)(NetLane.Flags.Created | NetLane.Flags.Deleted)) != (uint)NetLane.Flags.Created) { return false; } return IsSegmentValid(lane.m_segment); } public bool IsLaneValid(uint laneId) { if (!CheckLaneFlags(laneId, NetLane.Flags.Created | NetLane.Flags.Deleted, NetLane.Flags.Created)) { return false; } bool ret = false; ProcessLane(laneId, delegate(uint lId, ref NetLane lane) { ret = IsSegmentValid(lane.m_segment); return true; }); return ret; } public void ProcessLane(uint laneId, NetLaneHandler handler) { ProcessLane(laneId, ref Singleton.instance.m_lanes.m_buffer[laneId], handler); } public void ProcessLane(uint laneId, ref NetLane lane, NetLaneHandler handler) { handler(laneId, ref lane); } public ushort GetSegmentNodeId(ushort segmentId, bool startNode) { ushort nodeId = 0; ProcessSegment(segmentId, delegate(ushort segId, ref NetSegment segment) { nodeId = startNode ? segment.m_startNode : segment.m_endNode; return true; }); return nodeId; } public void IterateNodeSegments(ushort nodeId, NetSegmentHandler handler) { IterateNodeSegments(nodeId, ClockDirection.None, handler); } public void IterateNodeSegments(ushort nodeId, ClockDirection dir, NetSegmentHandler handler) { NetManager netManager = Singleton.instance; ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { if (dir == ClockDirection.None) { for (int i = 0; i < 8; ++i) { ushort segmentId = node.GetSegment(i); if (segmentId != 0) { if (!handler(segmentId, ref netManager.m_segments.m_buffer[segmentId])) { break; } } } } else { ushort segmentId = node.GetSegment(0); ushort initSegId = segmentId; while (true) { if (segmentId != 0) { if (!handler(segmentId, ref netManager.m_segments.m_buffer[segmentId])) { break; } } switch (dir) { case ClockDirection.Clockwise: default: segmentId = netManager.m_segments.m_buffer[segmentId].GetLeftSegment(nodeId); break; case ClockDirection.CounterClockwise: segmentId = netManager.m_segments.m_buffer[segmentId].GetRightSegment(nodeId); break; } if (segmentId == initSegId || segmentId == 0) { break; } } } return true; }); } public void IterateSegmentLanes(ushort segmentId, NetSegmentLaneHandler handler) { IterateSegmentLanes(segmentId, ref Singleton.instance.m_segments.m_buffer[segmentId], handler); } public void IterateSegmentLanes(ushort segmentId, ref NetSegment segment, NetSegmentLaneHandler handler) { NetInfo segmentInfo = segment.Info; if (segmentInfo == null) return; byte laneIndex = 0; uint curLaneId = segment.m_lanes; while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; handler(curLaneId, ref Singleton.instance.m_lanes.m_buffer[curLaneId], laneInfo, segmentId, ref segment, laneIndex); curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; ++laneIndex; } } public NetInfo.Direction GetFinalSegmentEndDirection(ushort segmentId, bool startNode) { return GetFinalSegmentEndDirection(segmentId, ref Singleton.instance.m_segments.m_buffer[segmentId], startNode); } public NetInfo.Direction GetFinalSegmentEndDirection(ushort segmentId, ref NetSegment segment, bool startNode) { NetInfo segmentInfo = segment.Info; var dir = startNode ? NetInfo.Direction.Backward : NetInfo.Direction.Forward; if ((segment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None /*^ SimulationService.Instance.LeftHandDrive*/) dir = NetInfo.InvertDirection(dir); return dir; } public bool CheckNodeFlags(ushort nodeId, NetNode.Flags flagMask, NetNode.Flags? expectedResult=null) { bool ret = false; ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { ret = LogicUtil.CheckFlags((uint)node.m_flags, (uint)flagMask, (uint?)expectedResult); return true; }); return ret; } public bool CheckSegmentFlags(ushort segmentId, NetSegment.Flags flagMask, NetSegment.Flags? expectedResult=null) { bool ret = false; ProcessSegment(segmentId, delegate (ushort sId, ref NetSegment segment) { ret = LogicUtil.CheckFlags((uint)segment.m_flags, (uint)flagMask, (uint?)expectedResult); return true; }); return ret; } public bool CheckLaneFlags(uint laneId, NetLane.Flags flagMask, NetLane.Flags? expectedResult=null) { bool ret = false; ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { ret = LogicUtil.CheckFlags((uint)lane.m_flags, (uint)flagMask, (uint?)expectedResult); return true; }); return ret; } /// /// Assembles a geometrically sorted list of lanes for the given segment. /// If the parameter is set only lanes supporting traffic to flow towards the given node are added to the list, otherwise all matched lanes are added. /// /// segment id /// segment data /// reference node (optional) /// lane type filter, lanes must match this filter mask /// vehicle type filter, lanes must match this filter mask /// if true, lanes are ordered from right to left (relative to the segment's start node / the given node), otherwise from left to right /// sorted list of lanes for the given segment public IList GetSortedLanes(ushort segmentId, ref NetSegment segment, bool? startNode, NetInfo.LaneType? laneTypeFilter = null, VehicleInfo.VehicleType? vehicleTypeFilter = null, bool reverse = false) { // TODO refactor together with getSegmentNumVehicleLanes, especially the vehicle type and lane type checks NetManager netManager = Singleton.instance; var laneList = new List(); bool inverted = ((segment.m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None); NetInfo.Direction? filterDir = null; NetInfo.Direction sortDir = NetInfo.Direction.Forward; if (startNode != null) { filterDir = (bool)startNode ? NetInfo.Direction.Backward : NetInfo.Direction.Forward; filterDir = inverted ? NetInfo.InvertDirection((NetInfo.Direction)filterDir) : filterDir; sortDir = NetInfo.InvertDirection((NetInfo.Direction)filterDir); } else if (inverted) { sortDir = NetInfo.Direction.Backward; } if (reverse) { sortDir = NetInfo.InvertDirection(sortDir); } NetInfo segmentInfo = segment.Info; uint curLaneId = segment.m_lanes; byte laneIndex = 0; while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; if ((laneTypeFilter == null || (laneInfo.m_laneType & laneTypeFilter) != NetInfo.LaneType.None) && (vehicleTypeFilter == null || (laneInfo.m_vehicleType & vehicleTypeFilter) != VehicleInfo.VehicleType.None) && (filterDir == null || segmentInfo.m_lanes[laneIndex].m_finalDirection == filterDir)) { laneList.Add(new LanePos(curLaneId, laneIndex, segmentInfo.m_lanes[laneIndex].m_position, laneInfo.m_vehicleType, laneInfo.m_laneType)); } curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; ++laneIndex; } laneList.Sort(delegate (LanePos x, LanePos y) { bool fwd = sortDir == NetInfo.Direction.Forward; if (x.position == y.position) { if (x.position > 0) { // mirror type-bound lanes (e.g. for coherent disply of lane-wise speed limits) fwd = !fwd; } if (x.laneType == y.laneType) { if (x.vehicleType == y.vehicleType) { return 0; } else if ((x.vehicleType < y.vehicleType) == fwd) { return -1; } else { return 1; } } else if ((x.laneType < y.laneType) == fwd) { return -1; } else { return 1; } } if ((x.position < y.position) == fwd) { return -1; } return 1; }); return laneList; } public void PublishSegmentChanges(ushort segmentId) { #if DEBUG Log.Warning($"NetService.PublishSegmentChanges({segmentId}) called."); #endif ISimulationService simService = SimulationService.Instance; ProcessSegment(segmentId, delegate (ushort sId, ref NetSegment segment) { uint currentBuildIndex = simService.CurrentBuildIndex; simService.CurrentBuildIndex = currentBuildIndex + 1; segment.m_modifiedIndex = currentBuildIndex; ++segment.m_buildIndex; return true; }); } } } ================================================ FILE: TLM/TMPE.CitiesGameBridge/Service/PathService.cs ================================================ using ColossalFramework; using CSUtil.Commons; using GenericGameBridge.Service; using System; using System.Collections.Generic; namespace CitiesGameBridge.Service { public class PathService : IPathService { public static readonly IPathService Instance = new PathService(); private PathService() { } public bool CheckUnitFlags(uint unitId, byte flagMask, byte? expectedResult=null) { bool ret = false; ProcessUnit(unitId, delegate (uint uId, ref PathUnit unit) { ret = LogicUtil.CheckFlags((uint)unit.m_pathFindFlags, (uint)flagMask, (uint?)expectedResult); return true; }); return ret; } public void ProcessUnit(uint unitId, PathUnitHandler handler) { ProcessUnit(unitId, ref Singleton.instance.m_pathUnits.m_buffer[unitId], handler); } public void ProcessUnit(uint unitId, ref PathUnit unit, PathUnitHandler handler) { handler(unitId, ref unit); } } } ================================================ FILE: TLM/TMPE.CitiesGameBridge/Service/SimulationService.cs ================================================ using System; using ColossalFramework; using ColossalFramework.Math; using GenericGameBridge.Service; using UnityEngine; namespace CitiesGameBridge.Service { public class SimulationService : ISimulationService { public static readonly ISimulationService Instance = new SimulationService(); private SimulationService() { } public bool LeftHandDrive { get { return Singleton.instance.m_metaData.m_invertTraffic == SimulationMetaData.MetaBool.True; } } public uint CurrentBuildIndex { get { return Singleton.instance.m_currentBuildIndex; } set { Singleton.instance.m_currentBuildIndex = value; } } public uint CurrentFrameIndex { get { return Singleton.instance.m_currentFrameIndex; } } public Vector3 CameraPosition { get { return Singleton.instance.m_simulationView.m_position; } } public Randomizer Randomizer { get { return Singleton.instance.m_randomizer; } } public bool SimulationPaused { get { return Singleton.instance.SimulationPaused; } } public bool ForcedSimulationPaused { get { return Singleton.instance.ForcedSimulationPaused; } } public AsyncAction AddAction(Action action) { return Singleton.instance.AddAction(action); } public void PauseSimulation(bool forced) { if (forced) { Singleton.instance.ForcedSimulationPaused = true; } else { Singleton.instance.SimulationPaused = true; } } public void ResumeSimulation(bool forced) { if (forced) { Singleton.instance.ForcedSimulationPaused = false; } else { Singleton.instance.SimulationPaused = false; } } } } ================================================ FILE: TLM/TMPE.CitiesGameBridge/Service/VehicleService.cs ================================================ using System; using ColossalFramework; using CSUtil.Commons; using GenericGameBridge.Service; namespace CitiesGameBridge.Service { public class VehicleService : IVehicleService { public static readonly IVehicleService Instance = new VehicleService(); private VehicleService() { } public bool CheckVehicleFlags(ushort vehicleId, Vehicle.Flags flagMask, Vehicle.Flags? expectedResult = default(Vehicle.Flags?)) { bool ret = false; ProcessVehicle(vehicleId, delegate (ushort vId, ref Vehicle vehicle) { ret = LogicUtil.CheckFlags((uint)vehicle.m_flags, (uint)flagMask, (uint?)expectedResult); return true; }); return ret; } public bool CheckVehicleFlags2(ushort vehicleId, Vehicle.Flags2 flagMask, Vehicle.Flags2? expectedResult = default(Vehicle.Flags2?)) { bool ret = false; ProcessVehicle(vehicleId, delegate (ushort vId, ref Vehicle vehicle) { ret = LogicUtil.CheckFlags((uint)vehicle.m_flags2, (uint)flagMask, (uint?)expectedResult); return true; }); return ret; } public bool IsVehicleValid(ushort vehicleId) { return CheckVehicleFlags(vehicleId, Vehicle.Flags.Created | Vehicle.Flags.Deleted, Vehicle.Flags.Created); } public void ProcessParkedVehicle(ushort parkedVehicleId, ParkedVehicleHandler handler) { ProcessParkedVehicle(parkedVehicleId, ref Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId], handler); } public void ProcessParkedVehicle(ushort parkedVehicleId, ref VehicleParked parkedVehicle, ParkedVehicleHandler handler) { handler(parkedVehicleId, ref parkedVehicle); } public void ProcessVehicle(ushort vehicleId, VehicleHandler handler) { ProcessVehicle(vehicleId, ref Singleton.instance.m_vehicles.m_buffer[vehicleId], handler); } public void ProcessVehicle(ushort vehicleId, ref Vehicle vehicle, VehicleHandler handler) { handler(vehicleId, ref vehicle); } public void ReleaseParkedVehicle(ushort parkedVehicleId) { Singleton.instance.ReleaseParkedVehicle(parkedVehicleId); } public void ReleaseVehicle(ushort vehicleId) { Singleton.instance.ReleaseVehicle(vehicleId); } } } ================================================ FILE: TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj ================================================  Debug AnyCPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54} Library Properties CitiesGameBridge TMPE.CitiesGameBridge 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\Assembly-CSharp.dll ..\dependencies\ColossalManaged.dll ..\dependencies\UnityEngine.dll {D3ADE06E-F493-4819-865A-3BB44FEEDF01} CSUtil.Commons {663B991F-32A1-46E1-A4D3-540F8EA7F386} TMPE.GenericGameBridge ================================================ FILE: TLM/TMPE.CitiesGameBridge/packages.config ================================================  ================================================ FILE: TLM/TMPE.GenericGameBridge/Factory/IServiceFactory.cs ================================================ using GenericGameBridge.Service; namespace GenericGameBridge.Factory { public interface IServiceFactory { IBuildingService BuildingService { get; } ICitizenService CitizenService { get; } INetService NetService { get; } IPathService PathService { get; } ISimulationService SimulationService { get; } IVehicleService VehicleService { get; } } } ================================================ FILE: TLM/TMPE.GenericGameBridge/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("GenericGameBridge")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("GenericGameBridge")] [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("663b991f-32a1-46e1-a4d3-540f8ea7f386")] // 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/TMPE.GenericGameBridge/Service/IBuildingService.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace GenericGameBridge.Service { public delegate bool BuildingHandler(ushort buildingId, ref Building building); public interface IBuildingService { bool CheckBuildingFlags(ushort buildingId, Building.Flags flagMask, Building.Flags? expectedResult = default(Building.Flags?)); bool IsBuildingValid(ushort buildingId); void ProcessBuilding(ushort buildingId, BuildingHandler handler); void ProcessBuilding(ushort buildingId, ref Building building, BuildingHandler handler); } } ================================================ FILE: TLM/TMPE.GenericGameBridge/Service/ICitizenService.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace GenericGameBridge.Service { public delegate bool CitizenHandler(uint citizenId, ref Citizen citizen); public delegate bool CitizenInstanceHandler(ushort citizenInstanceId, ref CitizenInstance citizenInstance); public interface ICitizenService { bool CheckCitizenFlags(uint citizenId, Citizen.Flags flagMask, Citizen.Flags? expectedResult = default(Citizen.Flags?)); bool IsCitizenValid(uint citizenId); void ProcessCitizen(uint citizenId, CitizenHandler handler); void ProcessCitizen(uint citizenId, ref Citizen citizen, CitizenHandler handler); bool CheckCitizenInstanceFlags(ushort citizenInstanceId, CitizenInstance.Flags flagMask, CitizenInstance.Flags? expectedResult = default(CitizenInstance.Flags?)); bool IsCitizenInstanceValid(ushort citizenInstanceId); void ProcessCitizenInstance(ushort citizenInstanceId, CitizenInstanceHandler handler); void ProcessCitizenInstance(ushort citizenInstanceId, ref CitizenInstance citizenInstance, CitizenInstanceHandler handler); /// /// Despawns and releases the given citizen instance. /// /// Citizen instance id to release void ReleaseCitizenInstance(ushort citizenInstanceId); } } ================================================ FILE: TLM/TMPE.GenericGameBridge/Service/INetService.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace GenericGameBridge.Service { public delegate bool NetSegmentHandler(ushort segmentId, ref NetSegment segment); public delegate bool NetNodeHandler(ushort nodeId, ref NetNode node); public delegate bool NetLaneHandler(uint laneId, ref NetLane lane); public delegate bool NetSegmentLaneHandler(uint laneId, ref NetLane lane, NetInfo.Lane laneInfo, ushort segmentId, ref NetSegment segment, byte laneIndex); public struct LanePos { public uint laneId; public byte laneIndex; public float position; public VehicleInfo.VehicleType vehicleType; public NetInfo.LaneType laneType; public LanePos(uint laneId, byte laneIndex, float position, VehicleInfo.VehicleType vehicleType, NetInfo.LaneType laneType) { this.laneId = laneId; this.laneIndex = laneIndex; this.position = position; this.vehicleType = vehicleType; this.laneType = laneType; } } public enum ClockDirection { None, Clockwise, CounterClockwise } public interface INetService { bool CheckLaneFlags(uint laneId, NetLane.Flags flagMask, NetLane.Flags? expectedResult = default(NetLane.Flags?)); bool CheckNodeFlags(ushort nodeId, NetNode.Flags flagMask, NetNode.Flags? expectedResult = default(NetNode.Flags?)); bool CheckSegmentFlags(ushort segmentId, NetSegment.Flags flagMask, NetSegment.Flags? expectedResult = default(NetSegment.Flags?)); NetInfo.Direction GetFinalSegmentEndDirection(ushort segmentId, bool startNode); NetInfo.Direction GetFinalSegmentEndDirection(ushort segmentId, ref NetSegment segment, bool startNode); ushort GetSegmentNodeId(ushort segmentId, bool startNode); /// /// Assembles a geometrically sorted list of lanes for the given segment. /// If the parameter is set only lanes supporting traffic to flow towards the given node are added to the list, otherwise all matched lanes are added. /// /// segment id /// segment data /// reference node (optional) /// lane type filter, lanes must match this filter mask /// vehicle type filter, lanes must match this filter mask /// if true, lanes are ordered from right to left (relative to the segment's start node / the given node), otherwise from left to right /// sorted list of lanes for the given segment IList GetSortedLanes(ushort segmentId, ref NetSegment segment, bool? startNode, NetInfo.LaneType? laneTypeFilter = default(NetInfo.LaneType?), VehicleInfo.VehicleType? vehicleTypeFilter = default(VehicleInfo.VehicleType?), bool reverse = false); bool IsLaneValid(uint laneId); bool IsNodeValid(ushort nodeId); bool IsSegmentValid(ushort segmentId); void IterateNodeSegments(ushort nodeId, NetSegmentHandler handler); void IterateNodeSegments(ushort nodeId, ClockDirection dir, NetSegmentHandler handler); void IterateSegmentLanes(ushort segmentId, NetSegmentLaneHandler handler); void IterateSegmentLanes(ushort segmentId, ref NetSegment segment, NetSegmentLaneHandler handler); void ProcessLane(uint laneId, NetLaneHandler handler); void ProcessLane(uint laneId, ref NetLane lane, NetLaneHandler handler); void ProcessNode(ushort nodeId, NetNodeHandler handler); void ProcessNode(ushort nodeId, ref NetNode node, NetNodeHandler handler); void ProcessSegment(ushort segmentId, NetSegmentHandler handler); void ProcessSegment(ushort segmentId, ref NetSegment segment, NetSegmentHandler handler); void PublishSegmentChanges(ushort segmentId); } } ================================================ FILE: TLM/TMPE.GenericGameBridge/Service/IPathService.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace GenericGameBridge.Service { public delegate bool PathUnitHandler(uint unitId, ref PathUnit unit); public interface IPathService { bool CheckUnitFlags(uint unitId, byte flagMask, byte? expectedResult = null); void ProcessUnit(uint unitId, PathUnitHandler handler); void ProcessUnit(uint unitId, ref PathUnit unit, PathUnitHandler handler); } } ================================================ FILE: TLM/TMPE.GenericGameBridge/Service/ISimulationService.cs ================================================ using ColossalFramework.Math; using UnityEngine; namespace GenericGameBridge.Service { public interface ISimulationService { bool LeftHandDrive { get; } uint CurrentBuildIndex { get; set; } uint CurrentFrameIndex { get; } Vector3 CameraPosition { get; } Randomizer Randomizer { get; } bool SimulationPaused { get; } bool ForcedSimulationPaused { get; } AsyncAction AddAction(System.Action action); void PauseSimulation(bool forced); void ResumeSimulation(bool forced); } } ================================================ FILE: TLM/TMPE.GenericGameBridge/Service/IVehicleService.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace GenericGameBridge.Service { public delegate bool VehicleHandler(ushort vehicleId, ref Vehicle vehicle); public delegate bool ParkedVehicleHandler(ushort parkedVehicleId, ref VehicleParked parkedVehicle); public interface IVehicleService { bool CheckVehicleFlags(ushort vehicleId, Vehicle.Flags flagMask, Vehicle.Flags? expectedResult = default(Vehicle.Flags?)); bool CheckVehicleFlags2(ushort vehicleId, Vehicle.Flags2 flagMask, Vehicle.Flags2? expectedResult = default(Vehicle.Flags2?)); bool IsVehicleValid(ushort vehicleId); void ProcessVehicle(ushort vehicleId, VehicleHandler handler); void ProcessVehicle(ushort vehicleId, ref Vehicle vehicle, VehicleHandler handler); void ProcessParkedVehicle(ushort parkedVehicleId, ParkedVehicleHandler handler); void ProcessParkedVehicle(ushort parkedVehicleId, ref VehicleParked parkedVehicle, ParkedVehicleHandler handler); void ReleaseVehicle(ushort vehicleId); void ReleaseParkedVehicle(ushort parkedVehicleId); } } ================================================ FILE: TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj ================================================  Debug AnyCPU {663B991F-32A1-46E1-A4D3-540F8EA7F386} Library Properties GenericGameBridge TMPE.GenericGameBridge 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\Assembly-CSharp.dll False ..\dependencies\ColossalManaged.dll False ..\dependencies\UnityEngine.dll ================================================ FILE: TLM/TMPE.GenericGameBridge/packages.config ================================================  ================================================ FILE: TLM/TMPE.GlobalConfigGenerator/App.config ================================================ ================================================ FILE: TLM/TMPE.GlobalConfigGenerator/Generator.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Xml; using System.Xml.Serialization; using TrafficManager.State; namespace GlobalConfigGenerator { class Generator { public const string FILENAME = "TMPE_GlobalConfig.xml"; public static int? RushHourParkingSearchRadius { get; private set; } = null; //private static DateTime? rushHourConfigModifiedTime = null; private const string RUSHHOUR_CONFIG_FILENAME = "RushHourOptions.xml"; //private static uint lastRushHourConfigCheck = 0; public static void Main(string[] args) { /*WriteDefaultConfig(); GlobalConfig conf = LoadConfig();*/ TestRushHourConf(); Console.ReadLine(); } /*public static void WriteDefaultConfig() { GlobalConfig conf = new GlobalConfig(); XmlSerializer serializer = new XmlSerializer(typeof(GlobalConfig)); TextWriter writer = new StreamWriter(FILENAME); serializer.Serialize(writer, conf); writer.Close(); } public static GlobalConfig LoadConfig() { FileStream fs = new FileStream(FILENAME, FileMode.Open); XmlSerializer serializer = new XmlSerializer(typeof(GlobalConfig)); GlobalConfig conf = (GlobalConfig)serializer.Deserialize(fs); Console.WriteLine("OK"); return conf; }*/ private static void TestRushHourConf() { // TODO refactor XmlDocument doc = new XmlDocument(); doc.Load(RUSHHOUR_CONFIG_FILENAME); XmlNode root = doc.DocumentElement; XmlNode betterParkingNode = root.SelectSingleNode("OptionPanel/data/BetterParking"); XmlNode parkingSpaceRadiusNode = root.SelectSingleNode("OptionPanel/data/ParkingSearchRadius"); string s = betterParkingNode.InnerText; if ("True".Equals(s)) { RushHourParkingSearchRadius = int.Parse(parkingSpaceRadiusNode.InnerText); } } } } ================================================ FILE: TLM/TMPE.GlobalConfigGenerator/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // Allgemeine Informationen über eine Assembly werden über die folgenden // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, // die einer Assembly zugeordnet sind. [assembly: AssemblyTitle("GlobalConfigGenerator")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("GlobalConfigGenerator")] [assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. [assembly: ComVisible(false)] // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird [assembly: Guid("48d1868b-ee81-4339-95c9-4c5eadeb9eca")] // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: // // Hauptversion // Nebenversion // Buildnummer // Revision // // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern // übernehmen, indem Sie "*" eingeben: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.*")] ================================================ FILE: TLM/TMPE.GlobalConfigGenerator/TMPE.GlobalConfigGenerator.csproj ================================================  Debug AnyCPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA} Exe Properties GlobalConfigGenerator TMPE.GlobalConfigGenerator v3.5 512 true AnyCPU true full false bin\Debug\ DEBUG;TRACE prompt 4 ..\TMPE.ruleset AnyCPU pdbonly true bin\Release\ TRACE prompt 4 ..\TMPE.ruleset bin\QueuedStats\ TRACE true pdbonly AnyCPU prompt MinimumRecommendedRules.ruleset true ..\TMPE.ruleset true bin\Benchmark\ DEBUG;TRACE full AnyCPU prompt ..\TMPE.ruleset true bin\PF2_Debug\ DEBUG;TRACE full AnyCPU prompt ..\TMPE.ruleset {7422AE58-8B0A-401C-9404-F4A438EFFE10} TLM ================================================ FILE: TLM/TMPE.GlobalConfigGenerator/packages.config ================================================  ================================================ FILE: TLM/TMPE.SpiralLoopTest/App.config ================================================  ================================================ FILE: TLM/TMPE.SpiralLoopTest/Program.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using TrafficManager.Util; namespace SpiralLoopTest { class Program { static void Main(string[] args) { LoopUtil.SpiralLoop(0, 0, 3, 3, delegate(int x, int y) { Console.WriteLine($"x={x} y={y}"); return true; }); Console.ReadLine(); } } } ================================================ FILE: TLM/TMPE.SpiralLoopTest/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // Allgemeine Informationen über eine Assembly werden über die folgenden // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, // die einer Assembly zugeordnet sind. [assembly: AssemblyTitle("SpiralLoopTest")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("SpiralLoopTest")] [assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. [assembly: ComVisible(false)] // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird [assembly: Guid("615ef202-3e13-4a15-a82f-594d90cd0ecd")] // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: // // Hauptversion // Nebenversion // Buildnummer // Revision // // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern // übernehmen, indem Sie "*" eingeben: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] ================================================ FILE: TLM/TMPE.SpiralLoopTest/SpiralLoopTest.csproj ================================================  Debug AnyCPU {615EF202-3E13-4A15-A82F-594D90CD0ECD} Exe Properties SpiralLoopTest SpiralLoopTest v4.5.2 512 true AnyCPU true full false bin\Debug\ DEBUG;TRACE prompt 4 AnyCPU pdbonly true bin\Release\ TRACE prompt 4 bin\QueuedStats\ TRACE true pdbonly AnyCPU prompt MinimumRecommendedRules.ruleset true {7422AE58-8B0A-401C-9404-F4A438EFFE10} TLM ================================================ FILE: TLM/TMPE.TestGameBridge/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("TestGameBridge")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("TestGameBridge")] [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("97dbfe4d-0db1-43b3-aa35-067c06cf125b")] // 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/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj ================================================  Debug AnyCPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B} Library Properties TestGameBridge TMPE.TestGameBridge 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\Assembly-CSharp.dll ================================================ FILE: TLM/TMPE.TestGameBridge/packages.config ================================================  ================================================ FILE: TLM/TMPE.UnitTest/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // Allgemeine Informationen über eine Assembly werden über folgende // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, // die einer Assembly zugeordnet sind. [assembly: AssemblyTitle("TMUnitTest")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("TMUnitTest")] [assembly: AssemblyCopyright("Copyright © 2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Wenn ComVisible auf "false" festgelegt wird, sind die Typen innerhalb dieser Assembly // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. [assembly: ComVisible(false)] // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird [assembly: Guid("d0d1848a-9bae-4121-89a0-66757d16bc73")] // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: // // Hauptversion // Nebenversion // Buildnummer // Revision // // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern // übernehmen, indem Sie "*" eingeben: // [Assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] ================================================ FILE: TLM/TMPE.UnitTest/TMPE.UnitTest.csproj ================================================  Debug AnyCPU {D0D1848A-9BAE-4121-89A0-66757D16BC73} Library Properties TMUnitTest TMPE.UnitTest v3.5 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest 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\Assembly-CSharp.dll ..\dependencies\ColossalManaged.dll ..\dependencies\ICities.dll ..\dependencies\UnityEngine.dll ..\dependencies\UnityEngine.UI.dll False {D3ADE06E-F493-4819-865A-3BB44FEEDF01} CSUtil.Commons {7422AE58-8B0A-401C-9404-F4A438EFFE10} TLM False False False False ================================================ FILE: TLM/TMPE.UnitTest/Util/LogicUtilUnitTest.cs ================================================ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using CSUtil.Commons; namespace TMUnitTest.Util { [TestClass] public class LogicUtilUnitTest { [TestMethod] public void TestCheckFlags1() { Assert.IsTrue(LogicUtil.CheckFlags((uint)(NetSegment.Flags.Created | NetSegment.Flags.Deleted), (uint)NetSegment.Flags.Created)); } [TestMethod] public void TestCheckFlags2() { Assert.IsFalse(LogicUtil.CheckFlags((uint)(NetSegment.Flags.Created | NetSegment.Flags.Deleted), (uint)NetSegment.Flags.Collapsed)); } [TestMethod] public void TestCheckFlags3() { Assert.IsTrue(LogicUtil.CheckFlags((uint)(NetSegment.Flags.Created | NetSegment.Flags.Collapsed), (uint)NetSegment.Flags.Created, (uint)NetSegment.Flags.Created)); } [TestMethod] public void TestCheckFlags4() { Assert.IsTrue(LogicUtil.CheckFlags((uint)(NetSegment.Flags.Created | NetSegment.Flags.Collapsed), (uint)(NetSegment.Flags.Created | NetSegment.Flags.Deleted), (uint)NetSegment.Flags.Created)); } [TestMethod] public void TestCheckFlags5() { Assert.IsFalse(LogicUtil.CheckFlags((uint)(NetSegment.Flags.Created | NetSegment.Flags.Deleted | NetSegment.Flags.Collapsed), (uint)(NetSegment.Flags.Created | NetSegment.Flags.Deleted), (uint)NetSegment.Flags.Created)); } } } ================================================ FILE: TLM/TMPE.UnitTest/Util/TinyDictionaryUnitTest.cs ================================================ using System; using System.Text; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using TrafficManager.Util; using TrafficManager.Traffic; using CSUtil.Commons; namespace TMUnitTest.Util { [TestClass] public class TinyDictionaryUnitTest { private TinyDictionary dict0; private TinyDictionary dict1; private TinyDictionary> dict2; private TinyDictionary, bool> dict3; private TinyDictionary> dict4; private static string alice, bob, cedric, dora; private static IList alicesNicknames, bobsNicknames, cedricsNicknames, dorasNicknames; private static ICollection girls, boys; #region Zusätzliche Testattribute // // Sie können beim Schreiben der Tests folgende zusätzliche Attribute verwenden: // // Verwenden Sie ClassInitialize, um vor Ausführung des ersten Tests in der Klasse Code auszuführen. // [ClassInitialize()] // public static void MyClassInitialize(TestContext testContext) { } // // Verwenden Sie ClassCleanup, um nach Ausführung aller Tests in einer Klasse Code auszuführen. // [ClassCleanup()] // public static void MyClassCleanup() { } // // Mit TestInitialize können Sie vor jedem einzelnen Test Code ausführen. // [TestInitialize()] // public void MyTestInitialize() { } // // Mit TestCleanup können Sie nach jedem Test Code ausführen. // [TestCleanup()] // public void MyTestCleanup() { } // #endregion [ClassInitialize] public static void InitializeClass(TestContext testContext) { alice = "Alice"; bob = "Bob"; cedric = "Cedric"; dora = "Dora"; alicesNicknames = null; bobsNicknames = new List { "Bobby", "Bobbi", "Bobb" }; cedricsNicknames = new List { "Ced" }; dorasNicknames = new List(); girls = new HashSet { alice, dora }; boys = new HashSet { bob, cedric }; } [TestInitialize()] public void InitializeTest() { dict0 = new TinyDictionary(); dict1 = new TinyDictionary(); dict1.Add(alice, 1); dict1.Add(bob, 2); dict1.Add(cedric, 3); dict2 = new TinyDictionary>(); dict2.Add(bob, bobsNicknames); dict2.Add(cedric, cedricsNicknames); dict2.Add(dora, dorasNicknames); dict2.Add(alice, alicesNicknames); dict3 = new TinyDictionary, bool>(); dict3.Add(girls, true); dict3.Add(boys, false); } [TestMethod] public void TestKeys0() { ICollection keys = dict0.Keys; Assert.IsNotNull(keys); Assert.AreEqual(0, keys.Count); } [TestMethod] public void TestKeys1() { ICollection keys = dict1.Keys; Assert.IsNotNull(keys); Assert.AreEqual(3, keys.Count); Assert.IsTrue(keys.Contains(alice)); Assert.IsTrue(keys.Contains(bob)); Assert.IsTrue(keys.Contains(cedric)); } [TestMethod] public void TestKeys2() { ICollection keys = dict2.Keys; Assert.IsNotNull(keys); Assert.AreEqual(4, keys.Count); Assert.IsTrue(keys.Contains(alice)); Assert.IsTrue(keys.Contains(bob)); Assert.IsTrue(keys.Contains(cedric)); Assert.IsTrue(keys.Contains(dora)); } [TestMethod] public void TestKeys3() { ICollection> keys = dict3.Keys; Assert.IsNotNull(keys); Assert.AreEqual(2, keys.Count); Assert.IsTrue(keys.Contains(girls)); Assert.IsTrue(keys.Contains(boys)); } [TestMethod] public void TestValues0() { ICollection values = dict0.Values; Assert.IsNotNull(values); Assert.AreEqual(0, values.Count); } [TestMethod] public void TestValues1() { ICollection values = dict1.Values; Assert.IsNotNull(values); Assert.AreEqual(3, values.Count); Assert.IsTrue(values.Contains(1)); Assert.IsTrue(values.Contains(2)); Assert.IsTrue(values.Contains(3)); } [TestMethod] public void TestValues2() { ICollection> values = dict2.Values; Assert.IsNotNull(values); Assert.AreEqual(4, values.Count); Assert.IsTrue(values.Contains(alicesNicknames)); Assert.IsTrue(values.Contains(bobsNicknames)); Assert.IsTrue(values.Contains(cedricsNicknames)); Assert.IsTrue(values.Contains(dorasNicknames)); } [TestMethod] public void TestValues3() { ICollection values = dict3.Values; Assert.IsNotNull(values); Assert.AreEqual(2, values.Count); Assert.IsTrue(values.Contains(true)); Assert.IsTrue(values.Contains(false)); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void TestGetNull() { int val = dict1[null]; } [TestMethod] [ExpectedException(typeof(KeyNotFoundException))] public void TestGetNotFound() { int val = dict1["Santa Claus"]; } [TestMethod] public void TestGet1() { int val = dict1[cedric]; Assert.AreEqual(3, val); } [TestMethod] public void TestGet2() { IList val = dict2[alice]; Assert.AreEqual(alicesNicknames, val); val = dict2[bob]; Assert.AreEqual(bobsNicknames, val); val = dict2[cedric]; Assert.AreEqual(cedricsNicknames, val); val = dict2[dora]; Assert.AreEqual(dorasNicknames, val); } [TestMethod] public void TestGet3() { bool val = dict3[girls]; Assert.IsTrue(val); } [TestMethod] public void TestSet0() { dict0[1] = 2; Assert.AreEqual(dict0[1], 2); } [TestMethod] public void TestSet1() { dict1["Eugen"] = 42; Assert.AreEqual(dict1["Eugen"], 42); } [TestMethod] public void TestSet2() { IList eugensNicknames = new List { "Eugenius" }; dict2["Eugen"] = eugensNicknames; Assert.AreEqual(dict2["Eugen"], eugensNicknames); } [TestMethod] public void TestSet3() { dict3[girls] = false; Assert.AreEqual(dict3[girls], false); Assert.AreEqual(dict3[boys], false); } [TestMethod] public void TestContainsKey0() { Assert.IsFalse(dict0.ContainsKey(0)); } [TestMethod] public void TestContainsKey1() { Assert.IsTrue(dict1.ContainsKey(alice)); Assert.IsTrue(dict1.ContainsKey(bob)); Assert.IsTrue(dict1.ContainsKey(cedric)); Assert.IsFalse(dict1.ContainsKey(dora)); } [TestMethod] public void TestContainsKey2() { Assert.IsTrue(dict2.ContainsKey(alice)); Assert.IsTrue(dict2.ContainsKey(bob)); Assert.IsTrue(dict2.ContainsKey(cedric)); Assert.IsTrue(dict2.ContainsKey(dora)); Assert.IsFalse(dict2.ContainsKey("Eugen")); } [TestMethod] public void TestContainsKey3() { Assert.IsTrue(dict3.ContainsKey(girls)); Assert.IsTrue(dict3.ContainsKey(boys)); Assert.IsFalse(dict3.ContainsKey(new HashSet { "Chair" })); Assert.IsFalse(dict3.ContainsKey(null)); } [TestMethod] public void TestAddRemove() { dict0.Add(1, 5); dict0.Add(5, 1); dict0.Add(2, 2); Assert.AreEqual(3, dict0.Count); Assert.AreEqual(5, dict0[1]); Assert.AreEqual(1, dict0[5]); Assert.AreEqual(2, dict0[2]); dict0.Remove(1); Assert.AreEqual(2, dict0.Count); Assert.IsFalse(dict0.ContainsKey(1)); dict0.Add(2, 3); Assert.AreEqual(2, dict0.Count); Assert.AreEqual(3, dict0[2]); dict0.Add(3, 0); Assert.AreEqual(3, dict0.Count); Assert.AreEqual(0, dict0[3]); dict0.Add(1, 4); Assert.AreEqual(4, dict0.Count); Assert.AreEqual(4, dict0[1]); dict0.Add(1, 8); Assert.AreEqual(4, dict0.Count); Assert.AreEqual(8, dict0[1]); dict0.Remove(1); Assert.AreEqual(3, dict0.Count); dict0.Add(1, 2); Assert.AreEqual(4, dict0.Count); Assert.AreEqual(2, dict0[1]); Assert.AreEqual(3, dict0[2]); Assert.AreEqual(0, dict0[3]); Assert.AreEqual(1, dict0[5]); } [TestMethod] public void TestTryGetValue1() { int val; Assert.IsTrue(dict1.TryGetValue(bob, out val)); Assert.AreEqual(2, val); Assert.IsFalse(dict1.TryGetValue(dora, out val)); Assert.AreEqual(default(int), val); } [TestMethod] public void TestClear() { dict1.Clear(); Assert.AreEqual(0, dict1.Count); } [TestMethod] public void TestCopyTo() { KeyValuePair>[] a = new KeyValuePair>[4]; dict2.CopyTo(a, 0); bool[] r = new bool[4]; foreach (KeyValuePair> e in a) { if (alice.Equals(e.Key) && e.Value == null) { r[0] = true; } else if (bob.Equals(e.Key) && bobsNicknames.Equals(e.Value)) { r[1] = true; } else if (cedric.Equals(e.Key) && cedricsNicknames.Equals(e.Value)) { r[2] = true; } else if (dora.Equals(e.Key) && dorasNicknames.Equals(e.Value)) { r[3] = true; } } foreach (bool rr in r) { Assert.IsTrue(rr); } } [TestMethod] public void TestEnumeration() { int i = 0; bool[] r = new bool[3]; foreach (KeyValuePair e in dict1) { if (alice.Equals(e.Key)) { Assert.AreEqual(1, e.Value); } else if (bob.Equals(e.Key)) { Assert.AreEqual(2, e.Value); } else if (cedric.Equals(e.Key)) { Assert.AreEqual(3, e.Value); } r[e.Value - 1] = true; ++i; } Assert.AreEqual(3, i); foreach (bool rr in r) { Assert.IsTrue(rr); } } [TestMethod] public void TestEnumerationAfterModification() { dict1[alice] = 5; int i = 0; bool[] r = new bool[3]; foreach (KeyValuePair e in dict1) { if (alice.Equals(e.Key)) { Assert.AreEqual(5, e.Value); r[0] = true; } else if (bob.Equals(e.Key)) { Assert.AreEqual(2, e.Value); r[1] = true; } else if (cedric.Equals(e.Key)) { Assert.AreEqual(3, e.Value); r[2] = true; } ++i; } Assert.AreEqual(3, i); foreach (bool rr in r) { Assert.IsTrue(rr); } } [TestMethod] public void TestEnumerationAfterRemoval() { dict1.Remove(alice); int i = 0; bool[] r = new bool[2]; foreach (KeyValuePair e in dict1) { if (bob.Equals(e.Key)) { Assert.AreEqual(2, e.Value); r[0] = true; } else if (cedric.Equals(e.Key)) { Assert.AreEqual(3, e.Value); r[1] = true; } ++i; } Assert.AreEqual(2, i); foreach (bool rr in r) { Assert.IsTrue(rr); } } [TestMethod] public void TestIncrement() { ++dict1[alice]; Assert.AreEqual(2, dict1[alice]); Assert.AreEqual(2, dict1[bob]); Assert.AreEqual(3, dict1[cedric]); } [TestMethod] public void TestArithmeticAssignment() { dict1[bob] *= 4; Assert.AreEqual(1, dict1[alice]); Assert.AreEqual(8, dict1[bob]); Assert.AreEqual(3, dict1[cedric]); } [TestMethod] [ExpectedException(typeof(KeyNotFoundException))] public void TestArithmeticAssignmentOnNonExistingKey() { dict1[dora]++; } [TestMethod] public void TestNested() { dict4 = new TinyDictionary>(); IDictionary innerDict1 = new TinyDictionary(); dict4.Add(1270, innerDict1); innerDict1.Add(1270, ArrowDirection.Turn); innerDict1.Add(14929, ArrowDirection.Forward); innerDict1.Add(26395, ArrowDirection.Left); IDictionary innerDict2 = new TinyDictionary(); dict4.Add(14929, innerDict2); innerDict2.Add(1270, ArrowDirection.Forward); innerDict2.Add(14929, ArrowDirection.Turn); innerDict2.Add(26395, ArrowDirection.Right); IDictionary innerDict3 = new TinyDictionary(); dict4.Add(26395, innerDict3); innerDict3.Add(1270, ArrowDirection.Right); innerDict3.Add(14929, ArrowDirection.Left); innerDict3.Add(26395, ArrowDirection.Turn); Assert.AreEqual(3, dict4.Count); Assert.IsTrue(dict4.ContainsKey(1270)); Assert.IsTrue(dict4.ContainsKey(14929)); Assert.IsTrue(dict4.ContainsKey(26395)); Assert.AreEqual(3, dict4[1270].Count); Assert.AreEqual(3, dict4[14929].Count); Assert.AreEqual(3, dict4[26395].Count); Assert.IsTrue(dict4[1270].ContainsKey(1270)); Assert.IsTrue(dict4[1270].ContainsKey(14929)); Assert.IsTrue(dict4[1270].ContainsKey(26395)); Assert.IsTrue(dict4[14929].ContainsKey(1270)); Assert.IsTrue(dict4[14929].ContainsKey(14929)); Assert.IsTrue(dict4[14929].ContainsKey(26395)); Assert.IsTrue(dict4[26395].ContainsKey(1270)); Assert.IsTrue(dict4[26395].ContainsKey(14929)); Assert.IsTrue(dict4[26395].ContainsKey(26395)); Assert.AreEqual(ArrowDirection.Turn, dict4[1270][1270]); Assert.AreEqual(ArrowDirection.Forward, dict4[1270][14929]); Assert.AreEqual(ArrowDirection.Left, dict4[1270][26395]); Assert.AreEqual(ArrowDirection.Forward, dict4[14929][1270]); Assert.AreEqual(ArrowDirection.Turn, dict4[14929][14929]); Assert.AreEqual(ArrowDirection.Right, dict4[14929][26395]); Assert.AreEqual(ArrowDirection.Right, dict4[26395][1270]); Assert.AreEqual(ArrowDirection.Left, dict4[26395][14929]); Assert.AreEqual(ArrowDirection.Turn, dict4[26395][26395]); } } } ================================================ FILE: TLM/TMPE.UnitTest/packages.config ================================================  ================================================ FILE: TLM/TMPE.ruleset ================================================ ================================================ FILE: TLM/TMPE.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TLM", "TLM\TLM.csproj", "{7422AE58-8B0A-401C-9404-F4A438EFFE10}" ProjectSection(ProjectDependencies) = postProject {663B991F-32A1-46E1-A4D3-540F8EA7F386} = {663B991F-32A1-46E1-A4D3-540F8EA7F386} {3F2F7926-5D51-4880-A2B7-4594A10D7E54} = {3F2F7926-5D51-4880-A2B7-4594A10D7E54} {D3ADE06E-F493-4819-865A-3BB44FEEDF01} = {D3ADE06E-F493-4819-865A-3BB44FEEDF01} {F8759084-DF5B-4A54-B73C-824640A8FA3F} = {F8759084-DF5B-4A54-B73C-824640A8FA3F} EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1D3D9CF0-947E-4A1A-BCFE-25F48E7908DF}" ProjectSection(SolutionItems) = preProject ..\README.md = ..\README.md EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TMPE.GlobalConfigGenerator", "TMPE.GlobalConfigGenerator\TMPE.GlobalConfigGenerator.csproj", "{48D1868B-EE81-4339-95C9-4C5EADEB9ECA}" ProjectSection(ProjectDependencies) = postProject {7422AE58-8B0A-401C-9404-F4A438EFFE10} = {7422AE58-8B0A-401C-9404-F4A438EFFE10} EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TMPE.UnitTest", "TMPE.UnitTest\TMPE.UnitTest.csproj", "{D0D1848A-9BAE-4121-89A0-66757D16BC73}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TMPE.GenericGameBridge", "TMPE.GenericGameBridge\TMPE.GenericGameBridge.csproj", "{663B991F-32A1-46E1-A4D3-540F8EA7F386}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TMPE.CitiesGameBridge", "TMPE.CitiesGameBridge\TMPE.CitiesGameBridge.csproj", "{3F2F7926-5D51-4880-A2B7-4594A10D7E54}" ProjectSection(ProjectDependencies) = postProject {D3ADE06E-F493-4819-865A-3BB44FEEDF01} = {D3ADE06E-F493-4819-865A-3BB44FEEDF01} EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TMPE.TestGameBridge", "TMPE.TestGameBridge\TMPE.TestGameBridge.csproj", "{97DBFE4D-0DB1-43B3-AA35-067C06CF125B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSUtil.Commons", "CSUtil.Commons\CSUtil.Commons.csproj", "{D3ADE06E-F493-4819-865A-3BB44FEEDF01}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptionsFramework", "OptionsFramework\OptionsFramework.csproj", "{F4BEABA8-E56B-4201-99C8-5E0115E87D1C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSUtil.CameraControl", "CSUtil.CameraControl\CSUtil.CameraControl\CSUtil.CameraControl\CSUtil.CameraControl.csproj", "{F8759084-DF5B-4A54-B73C-824640A8FA3F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Benchmark|Any CPU = Benchmark|Any CPU Debug|Any CPU = Debug|Any CPU FullDebug|Any CPU = FullDebug|Any CPU PF2_Debug|Any CPU = PF2_Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Debug|Any CPU.Build.0 = Debug|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.FullDebug|Any CPU.ActiveCfg = FullDebug|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.FullDebug|Any CPU.Build.0 = FullDebug|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Release|Any CPU.ActiveCfg = Release|Any CPU {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Release|Any CPU.Build.0 = Release|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Debug|Any CPU.Build.0 = Debug|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Release|Any CPU.ActiveCfg = Release|Any CPU {48D1868B-EE81-4339-95C9-4C5EADEB9ECA}.Release|Any CPU.Build.0 = Release|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Debug|Any CPU.Build.0 = Debug|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Release|Any CPU.ActiveCfg = Release|Any CPU {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Release|Any CPU.Build.0 = Release|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.Debug|Any CPU.Build.0 = Debug|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.Release|Any CPU.ActiveCfg = Release|Any CPU {663B991F-32A1-46E1-A4D3-540F8EA7F386}.Release|Any CPU.Build.0 = Release|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.Debug|Any CPU.Build.0 = Debug|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.Release|Any CPU.ActiveCfg = Release|Any CPU {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.Release|Any CPU.Build.0 = Release|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.Debug|Any CPU.Build.0 = Debug|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.Release|Any CPU.ActiveCfg = Release|Any CPU {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.Release|Any CPU.Build.0 = Release|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.Debug|Any CPU.Build.0 = Debug|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.Release|Any CPU.ActiveCfg = Release|Any CPU {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.Release|Any CPU.Build.0 = Release|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.Release|Any CPU.Build.0 = Release|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.Benchmark|Any CPU.ActiveCfg = Release|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.Benchmark|Any CPU.Build.0 = Release|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.Debug|Any CPU.Build.0 = Debug|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.PF2_Debug|Any CPU.ActiveCfg = PF2_Debug|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.PF2_Debug|Any CPU.Build.0 = PF2_Debug|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F8759084-DF5B-4A54-B73C-824640A8FA3F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal