diff --git a/.gitignore b/.gitignore index cf1a6dc28..4c81b0cbf 100644 --- a/.gitignore +++ b/.gitignore @@ -125,7 +125,7 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings +# 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 @@ -183,9 +183,9 @@ UpgradeLog*.htm FakesAssemblies/ /logo/ -# MSVS 2017 artifacts -/.vs/slnx.sqlite -/TLM/.vs/TMPE/* +# MSVS 2017, IntelliJ IDEA/Rider artifacts +.vs/ +.idea/ # Dependecies game dlls -/TLM/dependencies \ No newline at end of file +/TLM/dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bdb514a7..96b255ecb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,897 +1,1793 @@ # Cities: Skylines - Traffic Manager: *President Edition* [![Discord](https://img.shields.io/discord/545065285862948894.svg)](https://discord.gg/faKUnST) # Changelog -10.20, 21/05/2019 -- Updated for game version 1.12.0-f5 -- Updated Korean translation (thanks Twotoolus-FLY-LShst) (#294) -- Updated French translation (thanks PierreTSE) (#311) - -10.19, 20/04/2019 -- Bugfix: Mod options overlapping issue (#250, #266). -- Added: Japanese language (thanks mashitaro) (#258). -- Update: Chinese language (thanks Emphasia) (#285, #286). -- Update: "Vanilla Trees Remover" as incompatible mod (it breaks mod options screen) (#271, #290). -- Update: Moved "Delete" step button on timed traffic lights (#283, #285). -- Update: Mod incompatibility checker can now be disabled, or skip disabled mods (#264, #284, #286). - -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) + +> **Legend:** +> +> * **C:SL** = Cities: Skylines game updates +> * **TM:PE** = Traffic Manager: President Edition +> * **TPP2** = Traffic++ V2 +> * **TMPlus** = Traffic Manager Plus +> * **TPP:AI** - Traffic++ Improved AI +> * **TPP** = Traffic++ +> * **TM** = Traffic Manager + +### TM:PE [10.21.1 hotfix](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.21...10.21.1), 06/07/2019 + +- Fixed: Speed panel tanks fps if train tracks on screen (thanks rlas & DaEgi01!) (#411, #413) +- Meta: Main changelog refactored (#412) + +### TM:PE [10.21](https://github.com/krzychu124/Cities-Skylines-Traffic-Manager-President-Edition/compare/10.20...10.21), 02/07/2019 + +- Added: Cims have individual driving styles to determine lane changes and driving speed (#263 #334) +- Added: Miles Per Hour option for speed limits (thanks kvakvs) (#384) +- Added: Selectable style (US, UK, EU) of speed sign in speed limits UI (thanks kvakvs) (#384) +- Added: Differentiate LABS, STABLE and DEBUG branches in UI (#326, #333) +- Added: Keybinds tab in mod options - choose your own shortcuts! (thanks kvakvs) (#382) +- Added: Show keyboard shortcuts in button tooltips where applicable (thanks kvakvs) (#382) +- Added: Basic support of offline mode for users playing on EA's Origin service (#333, #400) +- Improved:: Avoid setting loss due to duplicate TM:PE subscriptions (#333, #306, #149, #190, #211, #400) +- Fixed: Vehicle limit count; compatibility with More Vehicles mod (thanks Dymanoid) (#362) +- Fixed: Mail trucks ignoring lane arrows (thanks Subaru & eudyptula for feedback) (#307, #338) +- Fixed: Vehicles stop in road trying to find parking (thanks eudyptula for investigating) (#259, #359) +- Fixed: Random parking broken (thanks s2500111 for beta testing) (#259, #359) +- Fixed: Pedestrian crossing restriction affects road-side parking (#259, #359) +- Fixed: 'Vanilla Trees Remover' is now compatible (thanks TPB for fixing) (#331, #332) +- Fixed: Single-lane bunching on DLS higher than 50% (#263 #334) +- Fixed: Lane changes at toll booths (also notified CO of bug in vanilla) (#225, #355) +- Fixed: Minor issues regarding saving/loading junction restrictions (#358) +- Fixed: Changes of default junction restrictions not reflected in UI overlay (#358) +- Fixed: Resetting stuck cims unpauses the simulation (#358, #351) +- Fixed: Treat duplicate TM:PE subscriptions as mod conflicts (#333, #306, #149, #190, #400) +- Fixed: TargetInvocationException in mod compatibility checker (#386, #333) +- Fixed: Issue with Paradox login blurring compatibility checker dialog (#404) +- Updated: Game version 1.12.1-f1 compatible (#403) +- Updated: Chinese translation (thanks Emphasia) (#375, #336) +- Updated: German translation (thanks kvakvs) (#384) +- Updated: Polish translation (thanks krzychu124) (#384, #333) +- Updated: Russian translation (thanks vitalii201 & kvakvs) (#327, #328) +- Updated: Renamed 'Realistic driving speeds' to 'Individual driving styles' (#334) +- Removed: Obsolete `TMPE.GlobalConfigGenerator` module (#367, #374) +- Meta: Separate binaries for Stable and Labs on GitHub release pages (#360) +- Meta: Initial documentation for release process in wiki (see `Contributing` page) (#360) +- Meta: Added GitHub issue templates for bugs, features, translations. (#272) +- Meta: Added `.editorconfig` file for IDE code indenting standardisation (#392, #384) +- Meta: Added entire `.vs/` and `.idea/` folders to `.gitignore` (#395, #382) +- Meta: Updated install guide to include section for EA Origin users (#333) +- Meta: Enable latest C# `LangVersion` in all projects (#398) + +### C:SL 1.12.1-f2, 04/06/2019 + +- Fixed: Numerous bugs in Campus DLC + +### TM:PE 10.20, 21/05/2019 + +- Updated: Compatible with C:SL 1.12.0-f5 +- Updated: Korean translation (thanks Twotoolus-FLY-LShst) (#294) +- Updated: French translation (thanks PierreTSE) (#311) + +### C:SL 1.12.0-f5 (Campus), 21/05/2019 + +- Added: University areas & lots of buildings +- Added: Choose bus to use on bus lines + +### TM:PE 10.19, 20/04/2019 + +- Fixed: Mod options overlapping issue (#250, #266). +- Updated: Moved "Delete" step button on timed traffic lights (#283, #285). +- Updated: "Vanilla Trees Remover" as incompatible mod (it breaks mod options screen) (#271, #290). +- Updated: Mod incompatibility checker can now be disabled, or skip disabled mods (#264, #284, #286). +- Updated: Chinese language (thanks Emphasia) (#285, #286). +- Updated: Japanese language (thanks mashitaro) (#258). + +### TM:PE 10.18, 29/03/2019 + +- Fixed: Parking AI: Cars do not spawn at outside connections (#245) +- Fixed: Trams perform turns on red (#248) +- Updated: Service Radius Adjuster mod by Egi removed from incompatible mods list (#255) + +### TM:PE 10.17, 23/03/2019 + +- Added: 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) +- Improved: Parking AI - Improved public transport (PT) usage patterns, mixed car/PT paths are now possible (#218) +- Fixed: Lane selection for u-turns and at dead ends (also optimised) (#101) +- Fixed: Parking AI - Tourist cars despawn because they assume they are at an outside connection (#218) +- Fixed: Parking AI - Return path calculation did not accept beautification segments (#218) +- Fixed: Parking AI - Cars/Citizens waiting for a path might jump around (#218) +- Fixed: Vanilla lane randomization does not work as intended at highway transitions (#112) +- Fixed: Vehicles change lanes at tollbooths (#225) +- Fixed: Path-finding: Array index is out of range due to a race condition (#221) +- Fixed: Citizen not found errors when using walking tours (#219) +- Fixed: Timed light indicator only visible when any timed light node is selected (#222) +- Updated: Compatible with C:SL 1.11.1-f4 +- Updated: Synchronized code and version with stable version +- Updated: List of incompatible mods (#115) +- Updated: Russian translation (thanks to vitalii201 for translating) (#207) +- Removed: Stable version from list of incompatible mods (#168) +- Meta: Introduced new versioning scheme (10.17 instead of 1.10.17) + +### C:SL 1.11.1-f4, 27/02/2019 + +- Fixed: Remove duplicate map + +### TM:PE 1.10.16, 24/02/2019 + +- Improved: New icons for empty and remove_priority_sign settings (thanks aubergine10 for those icons) (#75, #77) +- Fixed: Problem with vehicle despawn after road upgrade/remove (thanks pcfantasy for implementation suggestion)(#86, #101) +- Fixed: problem with vehicles unable to choose lane when u-turn at dead-end (thanks pcfantasy for implementation and aubergine10 for neccesary tests)(#101) +- 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) +- Fixed: Fixed 'silent error' inside log related with "Esc key handler" (#92) +- Updated: Greatly improved incompatible mod scanner, added dialog to list and unsubscribe incompatible mods (#91) +- Updated: Changed mod name in Content Manager to __TM:PE__ +- Updated: Added missing logic for noise density calculations (thanks to pcfantasy for fix) (#66) +- Meta: Discord server was set up by FireController1847 - link in mod description +- Meta: Added project building instructions and PR review + +### TM:PE 1.10.15, 10/02/2019 + +- Added: (Experimental) Turn on red (thanks to FireController1847 for implementation and to pcfantasy for source code base) +- Added: Notification if user is still subscribed to old original TM:PE +- Improved: Use Escape key to close Traffic Manager without returning to Pause Menu (thanks to aubergine10 for suggestion) (#16) +- Improved: New icons for cargo and passenger train restriction (thanks to aubergine10) (#17) +- Updated: Updated pathfinding with missing vanilla logic +- Updated: Tweaked values in CargoTruckAI path finding (thanks to pcfantasy for improvement suggestion) +- Updated: Tweaked speed multiplier of reckless drivers to get more realistic speed range (thanks to aubergine10 for suggestion) (#23) +- Updated: Simplified Chinese translation updated (thanks to Emphasia for translating) + +### TM:PE 1.10.14, 27/01/2019 + +- Fixed: Added missing Car AI type (postVanAI) - now post vans and post trucks are assigned to service vehicles group +- Fixed: Vehicles doesn't stop when driving through toll booth - fixes toll booth income too +- Fixed: Cargo Airport doesn't work (Cargo planes not spawning and not arriving) +- Fixed: Mod Options layout (text label overlaps slider control if too wide) +- Updated: Compatible with C:SL 1.11.1-f2 +- Updated: Polish translation +- Updated: Korean translation (thanks to Toothless FLY [ROK]LSh.st for translating) + +### C:SL 1.11.1-f2 (Holiday Surprise Patch), 13/12/2018 + +- Fixed: Cargo planes circle and use buildings that do not otherwise have any plane traffic +- Fixed: Vehicles can't cross the center line of Small Industry roads +- Fixed: Various bugs in game and DLCs + +### TM:PE 1.10.13, 31/10/2018 + +- Fixed: Toll booth not working +- Meta: Roads United Core also breaks toll booths + +### TM:PE 1.10.12, 08/12/2018 + +- Added: Allow/disallow vehicles to enter a blocked junction at transition and pedestrian crossing nodes (#195) +- Fixed: Emergency vehicles pass closed barriers at level crossings +- Fixed: Bus lines render u-turn where they should not (#207) +- Fixed: Parking AI - Cims leaving the city despawn their car at public transport stations (#214) +- Fixed: Crossing restrictions do not work at intersection between road and highway (#212) +- Updated: Compatible with C:SL 1.11.0-f3 +- Updated: Bent nodes do not allow for u-turns by default (#170) +- Updated: Russian translation (thanks to vitalii2011 for translating) + +### C:SL 1.11.0-f3 (Industries), 23/10/2018 + +- Added: Toll booths +- Added: Postal service, vans and trucks +- Added: Additional industry vehicles +- Added: Cargo airport and planes +- Added: Warehouses and storage buildings +- Fixed: Bugs in various DLCs + +### TM:PE 1.10.11, 21/07/2018 + +- Updated: U-turn lane connections are represented by appropriate lane arrow (#201) +- Fixed: Heavy vehicles are unable to u-turn at dead ends (#194) +- Fixed: Routing & Priority rules do not work properly for acute (< 30°)/obtuse(> 150°) segment angles (#199) +- Fixed: Buses do not prefer lanes with correct lane arrow (#206) +- Fixed: Race condition in path-finding might cause paths to be assigned to wrong vehicle/citizen (#205) +- Fixed: Vehicles are unable to perform u-turns when setting off on multi-lane roads (#197) + +### TM:PE 1.10.10, 14/07/2018 + +- Improved: Parking AI - Improved park & ride behaviour +- Fixed: Parking AI causes unnecessary path-findings (#183, thanks to Sipke82 for reporting) +- Fixed: Prohibiting cims from crossing the road also affect paths where crossing is unnecessary (#168, thanks to aubergine10 for reporting) +- Updated: Parking AI - Walking paths from parking position to destination building take public transportation into account + +### TM:PE 1.10.9, 13/07/2018 + +- Updated: Compatible with C:SL 1.10.1-f3 +- Updated: Re-implemented path-finding algorithm +- Updated: French translation (thanks to mjm92150 for translating!) + +### C:SL 1.10.1-f3, 04/07/2018 + +- Fixed: Various bugs in game and DLCs + +### TM:PE 1.10.8, 01/07/2018 + +- Added: Button to remove parked vehicles (in options dialog, see maintenance tab) +- Fixed: Parking AI - Cims spawn pocket cars when they originate from an outside connection +- Fixed: Incorrect speed limits returned for pedestrian lanes +- Fixed: Routing is not updated while the game is paused (thanks to Oh My Lawwwd! for reporting) +- Fixed: Vanilla traffic lights are ignored when either the priority signs or timed traffic light features are disabled (thanks to aubergine10 for reporting) +- Fixed: Park maintenance vehicles are not recognized as service vehicles +- Fixed: Cars leaving city state "thinking of a good parking spot" (thanks to aubergine10 for reporting) +- Updated: Parking AI - Removed check for distance between parked vehicle and target building +- Updated: Korean translation (thanks to Toothless FLY [ROK]LSh.st for translating) +- Updated: Polish translation (thanks to Krzychu1245 for translating) + +### TM:PE 1.10.7, 28/05/2018 + +- Fixed: U-turn routing is inconsistent on transport lines vs. bus paths (#137, thanks to Zorgoth for reporting this issue) +- Fixed: 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. +- 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) + +### TM:PE 1.10.6, 24/05/2018 + +- Added: Lane connector can be used on monorail tracks +- Added: Mod option - Main menu size can be controlled +- Added: Mod option - GUI and overlay transparency can be controlled +- Added: Mod option - Penalties for switching between different public transport lines can be toggled +- Added: Cims can now be removed from the game +- Improved: Advanced Vehicle AI - Tuned parameters +- Improved: Randomization for realistic speeds such that vehicles may change their target velocity over time +- Improved: Vehicle position tracking +- Improved: Mod compatibility checks +- Improved: Parking AI - Improved behaviour in situations where vehicles are parked near public transport hubs and road connections are partially unavailable +- Improved: Window design +- Fixed: Parking AI - Not all possible paths are regarded during path-finding +- Fixed: 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) +- Fixed: Parking AI - Cims do not search for parking building when road-side parking spaces are found +- Fixed: Parking AI - Parked vehicles are spawned near the source building even when cims are already en route +- Fixed: Parking AI - Cims sometimes get stuck in an infinite loop while trying to enter their parked car +- Fixed: Lane connector does not work for roads with more than ten lanes +- Fixed: Allowing/Disallowing vehicles to enter a blocked junction does not work for certain junctions +- Updated: Compatible with C:SL 1.9.2-f1 +- Updated: Compatible with C:SL 1.9.3-f1 +- Updated: Compatible with C:SL 1.10.0-f3 +- Updated: Dynamic Lane Selection: Absolute speed measurements are used instead of relative measurements +- Updated: Service vehicles now allowed to ignore lane arrows when leaving their source building; better for dead-end roads with median +- Updated: Korean translation (thanks to Toothless FLY [ROK]LSh.st for translating) + +### C:SL 1.10.0-f3 (Park Life), 24/05/2018 + +- Added: Park maintenance service and vehicle +- Added: Walking tours +- Added: Sightseeing bus tours and depot +- Added: Hot air balloons +- Fixed: Confused ships rotating forever +- Fixed: All traffic use Bus & Taxi lane when Old Town policy is active +- Fixed: Lots of other game and DLC bugs +- Updated: Trees reduce noise pollution + +### C:SL 1.9.3-f1, 23/03/2018 + +- Fixed: Bugs caused by prior patch + +### C:SL 1.9.2-f1, 09/03/2018 + +- Fixed: Minor bugs + +### TM:PE 1.10.5, 06/01/2018 + +- Added: Randomization for lane changing costs +- Added: Randomization for "trucks prefer innermost lanes on highways" costs +- Added: path-finding costs for public transport transitions +- Fixed: Main menu button might be out of view +- Fixed: Division by zero occurs for low speed roads +- Fixed: Automatic pedestrian lights at railroad do not work as expected +- Fixed: Timed traffic lights show up for bicycles (they should not) +- Fixed: Due to a multi-threading issue junction restrictions may cause the game state to become inconsistent +- Fixed: Routing rules prevents vehicles from spawning when starting building lies too close to an intersection/road end +- Fixed: Disabling tutorial message has no effect +- Fixed: "Stay on lane" feature does not work as intended for certain nodes +- Updated: Compatible with C:SL 1.9.1 +- Updated: Busses are allowed to switch multiple lanes after leaving a bus stop +- Updated: Pedestrian traffic lights do not show up if crossing the street is prohibited +- Updated: Simplified Chinese translation updated (thanks to Emphasia for translating) +- Updated: Polish translation updated (thanks to Krzychu1245 for translating) +- Removed: Unnecessary calculations in path-finding +- Removed: UI scaling + +### C:SL 1.9.1, 05/12/2017 + +- Fixed: Various game bugs +- Updated: Updated Unity to 5.6.4p2 + +### TM:PE 1.10.4, 19/10/2017 + +- Added: Possibility to add priority signs at multiple junctions at once (press Shift) +- Added: Tutorials (can be disabled in the options window globally) +- Updated: Compatible with C:SL 1.9.0-f5 + +### C:SL 1.9.0-f5 (Green Cities), 19/10/2017 + +- Added: Biofuel busses and recycling trucks +- Added: Electric cars and parking spaces with chargers +- Fixed: Huge number of game and localisation bugs +- Updated: Noise Pollution overhaul +- Updated: Train track intersection rules +- Updated: Unity version has been updated to 5.6.3p4 + +### TM:PE 1.10.3, 18/08/2017 + +- Fixed: Setting unlimited speed limit causes vehicles to crawl at low speed (thanks to sethisuwan for reporting this issue) +- Fixed: Vehicle-separated traffic lights do not show up for trams & monorails (thanks to thecitiesdork for reporting this issue) + +### TM:PE 1.10.2, 17/08/2017 + +- Improved: performance +- Fixed: Pedestrians sometimes ignore red traffic light signals (thanks to (c)RIKUPI™ for reporting this issue) +- Fixed: Timed traffic lights do not correctly recognize set vehicle restrictions (thanks to alborzka for reporting this issue) +- Updated: Compatible with C:SL 1.8.0-f3 + +### C:SL 1.8.0-f3 (Concerts), 17/08/2017 + +- Added: Festival areas +- Fixed: Pathfinder causing problems with multiple policies +- Fixed: Traffic routes info view does not show bicycles for players who don't own After Dark +- Fixed: Ships can travel on land and through dam +- Fixed: Cargo Train Terminal not working when build next to road with bicycle lanes +- Fixed: Large number of other game bugs + +### TM:PE 1.10.1, 05/08/2017 + +- Fixed: Default routing is disabled if the lane connector is used on a subset of all available lanes only +- Fixed: Parking AI cannot be enabled/disabled +- Fixed: Lane connection points can connected to themselves +- Updated: Polish, Korean, and Simplified Chinese translations + +### TM:PE 1.10.0, 30/07/2017 + +- Added: Dynamic Lane Selection +- Added: Adaptive step switching +- Added: Individual vehicles may be removed from the game +- Added: Mod option - Vehicle restrictions aggression +- Added: Mod option - Vehicles follow priority rules at junctions with timed traffic lights +- Added: Path-find statistics label +- Added: Confirmation dialog for "Clear Traffic" button +- Improved: Path-finding performance +- Improved: Traffic measurement engine performance +- Improved: Currently active timed traffic light step is remembered +- Improved: Disabling the Parking AI triggers graceful clean up procedure +- Improved: Vehicle state tracking +- Improved: Parking AI - Vehicles can now find parking spaces at the opposite road side +- Improved: Parking AI - Included an improved fallback logic for some edge cases +- Improved: Parking AI - Citizens should now be more successful in returning their cars back home +- Improved: Parking AI - Tuned parking radius parameters +- Improved: Parking AI - If the limit for parked vehicles is reached and parking fails due to it, no alternative parking space is queried +- Improved: Vehicle AI - Busses prefer lanes with correct lane arrow over incorrect ones +- Fixed: Workaround for a base game issue that causes trams to get stuck +- Fixed: Using the bulldozer tool might lead to inconsistent road geometry information +- Fixed: Citizens that fail to approach their parked car fly towards their target building +- Fixed: Parking AI: Path-finding fails if cars are parked too far away from a road +- Fixed: Parking AI: Citizens approaching a car start to float away +- Fixed: "Heavy vehicles prefer outer lanes on highways" does not work +- Fixed: The lane connector does not allow connecting all available lane end points at train stations and on bidirectional one-lane train tracks +- Fixed: Vehicles may get stuck in several situations +- Updated: Compatible with C:SL 1.7.2-f1 +- Updated: Upgrading to a road with bus lanes now copies an already existing traffic light state to the new traffic light +- Updated: Adding a vehicle separate light to a timed traffic lights applies the main light configuration +- Updated: Vehicles use queue skipping to prioritize path-finding runs that are caused by road modifications +- Updated: Trains do not longer stop in front of green timed traffic lights +- Updated: Relocated some mod options +- Updated: It is now possible to connect train station tracks and outside connections with the lane connector +- Updated: Trains do not wait for each other anymore near timed traffic lights +- Updated: The option "Road condition has a bigger impact on vehicle speed" is only shown if the Snowfall DLC is owned +- Updated: Reorganized global configuration file (sorry, your main menu and main button positions are reset) +- Updated: The flow/wait calculation mode to be used is now configurable via the global configuration file + +### C:SL 1.7.2-f1, 01/06/2017 + +- Fixed: Multiple public transport stops at the same location causing division by zero / crashing the UI +- Fixed: Double clicking creating multiple stops at the same place +- Fixed: Various other bugs + +### TM:PE 1.9.6, 28/05/2017 + +- Fixed: Vehicles cannot perform u-turns at junctions with only one outgoing segment (thanks to Sunbird for reporting this issue) +- Fixed: Path-finding costs for large distances exceed the maximum allowed value (thanks to Huitsi for reporting this issue) +- Fixed: Under certain circumstances path-finding at railroad crossings allow switching from road to rail tracks. +- Updated: Simplified Chinese translation + +### TM:PE 1.9.5, 24/05/2017 + +- Improved: Language can now be switched without requiring a game restart +- Fixed: 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) +- Fixed: Copying timed traffic lights causes timed traffic lights to be omitted during the save process (thanks to jakeroot and t1a2l for reporting this issue) +- Fixed: In certain situations unnecessary vehicle-separate traffic lights are being created +- Fixed: Upgrading a train track segment next to a timed traffic light causes trains to ignore the traffic light +- Fixed: Hotfix - Cable cars despawn at end-of-line stations +- Updated: Compatible with C:SL 1.7.1-f1 +- Updated: Polish, Korean and Italian translation + +### TM:PE 1.9.4, 23/05/2017 + +- Added: Mod option - Ban private cars and trucks on bus lanes +- Improved: Optimized path-finding +- Fixed: Path-finding is unable to calculate certain paths after modifying the road network +- Updated: Increased path-finding cost for private cars driving on bus lanes +- Updated: Increased path-finding cost for disregarding vehicle restrictions +- Updated: Spanish and French translation + +### C:SL 1.7.1-f1, 23/05/2017 + +- Fixed: Minor bugs + +### TM:PE 1.9.3, 22/05/2017 + +- Improved: Modified junction restrictions come into effect instantaneously +- Fixed: AI: Segment traffic data is not taken into account +- Fixed: Priority rules are not properly obeyed +- Fixed: Under certain circumstances priority signs cannot be removed +- Fixed: Path-finding is unable to calculate certain paths +- Updated: UI - Saving a timed step does not reset the timed traffic light to the first state +- Removed: Default vehicle restrictions from bus lanes +- Removed: Disabled notification of route recalculating because some players report crashes + +### TM:PE 1.9.2, 20/05/2017 + +- Improved: UI - Main menu & UI tools performance improved +- Fixed: Traffic lights can be removed from junctions that are controlled by a timed traffic light program + +### TM:PE 1.9.1, 19/05/2017 + +- Fixed: Using the vanilla traffic light toggling feature crashes the game if TMPE's main menu has not been opened at least once +- Fixed: AI - More car traffic and less public transportation present than in vanilla +- Updated: French, Dutch and Korean translation + +### TM:PE 1.9.0, 18/05/2017 + +- Added: Parking restrictions +- Added: Speed limits can be set up for individual lanes with the Control key +- Added: Added timed traffic light and speed limit support for monorails +- Added: Copy & paste for individual timed traffic lights +- Added: Rotate individual timed traffic lights +- Added: Lane customizations may come into effect instantaneously +- Added: Mod option - Main button position can be locked +- Added: Mod option - Main menu position can be locked +- Added: Mod option - Added language selection in options dialog +- Added: Mod option - Customization of lane arrows, lane connections and vehicle restrictions can now come into effect instantaneously +- Added: Support for custom languages +- Added: Korean translation (thanks to Toothless FLY [ROK]LSh.st for translating) +- Improved: Performance improvements +- Improved: Advanced Vehicle AI - Algorithm updated, performance improved - Possible routing decisions are now being pre-calculated +- Improved: AI - Tuned path-finding parameters +- Fixed: Cars sometimes get stuck forever when the Advanced Parking AI is activated (thanks to cmfcmf for reporting this issue) +- Fixed: Busses do not perform u-turns even if the transport line show u-turns (thanks to dymanoid for reporting this issue) +- Fixed: Timed traffic lights do not work as expected on single-direction train tracks (thanks to DaEgi01 for reporting this issue) +- Fixed: Vehicle restriction and speed limit signs overlay is displayed on the wrong side of inverted road segments +- Fixed: Influx statistics value is zero (thanks to hjo for reporting this issue) +- Updated: Compatible with C:SL 1.7.0-f5 +- Updated: Major code refactorings +- Updated: UI - More compact, movable main menu UI +- Updated: translations: German, Polish, Russian, Portuguese, Traditional Chinese +- Updated: Path-finding cost multiplicator for vehicle restrictions is now configurable in TMPE_GlobalConfig.xml +- Updated: Unified traffic light toggling feature with game code +- Updated: Reworked the way that traffic measurements are performed + +### C:SL 1.7.0-f5 (Mass Transit), 18/05/2017 + +- Added: Ferries +- Added: Cable Cars +- Added: Elevated monorail +- Added: Blimps +- Added: New stations, transport hubs and service buildings +- Added: Named routes +- Added: One-way train tracks +- Added: Emergency vehicles choose free lane if available, otherwise lane with least traffic +- Added: More public transport info views +- Added: Choose if rail stations accept intercity traffic +- Added: Stop signs at intersections +- Added: Toggle traffic lights at intersections +- Added: Vehicles have show / hide routes button +- Added: Automatic public transport vehicle unbunching +- Fixed: Train tracks could be updated after disaster by upgrading +- Fixed: Helicopters still won't visit buildings with no road connection +- Fixed: Lots of errors, particularly localisation +- Updated: Increased emergency vehicles speed +- Updated: Smaller outside connection capacity for smaller roads +- Updated: Upgraded to Unity 5.5.3f1 + +### TM:PE 1.8.16, 20/03/2017 + +- Improved: lane selection for busses if the option "Busses may ignore lane arrows" is activated +- Fixed: The game sometimes freezes when using the timed traffic light tool +- Fixed: Lane connections are not correctly removed after modifying/removing a junction +- Fixed: Selecting a junction for setting up junction restrictions toggles the currently hovered junction restriction icon +- Updated: Lane connections can now also be removed by pressing the backspace key + +### TM:PE 1.8.15, 27/01/2017 + +- Updated: Compatible with C:SL 1.6.3-f1 + +### C:SL 1.6.3-f1, 26/01/2017 + +- Fixed: Helicopter not used if building has no road connection +- Fixed: Various other game bugs + +### TM:PE 1.8.14, 07/01/2017 + +- Added: Tram lanes can now be customized by using the lane connector tool +- Improved: Minor performance optimizations for priority sign simulation +- Fixed: Wait/flow ratio at timed traffic lights is sometimes not correctly calculated +- Fixed: A deadlock situation can arise at junctions with priority signs such that no vehicle enters the junction +- Fixed: When adding a junction to a timed traffic light, sometimes light states given by user input are not correctly stored +- Fixed: Joining two timed traffic lights sets the minimum time to "1" for steps with zero minimum time assigned +- Fixed: Modifications of timed traffic light states are sometimes not visible while editing the light (but they are applied nonetheless) +- Fixed: Button background is not always correctly changed after clicking on a button within the main menu + +### TM:PE 1.8.13, 05/01/2017 + +- Improved: Selection of overlay markers on underground roads (thanks to Padi for reminding me of that issue) +- Improved: Minor performance improvements +- Fixed: Timed traffic light data can become corrupt when upgrading a road segment next to a traffic light, leading to faulty UI behaviour (thanks to Brain for reporting this issue) +- Fixed: The position of the main menu button resets after switching to the free camera mode (thanks to Impact and gravage for reporting this issue) +- Fixed: A division by zero exception can occur when calculating the average number of waiting/floating vehicles + +### TM:PE 1.8.12, 02/01/2017 + +- Fixed: After leaving the "Manual traffic lights" mode the traffic light simulation is not cleaned up correctly (thanks to diezelunderwood for reporting this issue) +- Fixed: Insufficient access rights to log file causes the mod to crash +- Updated: Compatible with C:SL 1.6.2-f1 + +### TM:PE 1.8.11, 02/01/2017 + +- Fixed: Speed limits for elevated/underground road segments are sometimes not correctly loaded (thanks to Pirazel and [P.A.N] Uf0 for reporting this issue) + +### TM:PE 1.8.10, 31/12/2016 + +- Improved: Path-finding performance (a bit) +- Fixed: Check for invalid road thumbnails in the "custom default speed limits" dialog + +### TM:PE 1.8.9, 29/12/2016 + +- Added: It is now possible to set speed limits for metro tracks +- Added: Custom default speed limits may now be defined for train and metro tracks +- Improved: Customizable junctions are now highlighted by the lane connector tool +- Improved: UI behaviour +- Improved: Performance improvements +- Fixed: Selecting a junction to set up priority signs sometimes does not work (thanks to Artemis *Seven* for reporting this issue) +- Fixed: Automatic pedestrian lights do not work as expected at junctions with incoming one-ways and on left-hand traffic maps +- Updated: Junction restrictions may now be controlled at bend road segments + +### TM:PE 1.8.8, 25/12/2016 + +- Fixed: Taxis are not being used +- Fixed: Prohibiting u-turns with the junction restriction tool does not work (thanks to Kisoe for reporting this issue) +- Fixed: Cars are sometimes floating across the map while trying to park (thanks to [Delta ²k5] for reporting this issue) + +### TM:PE 1.8.7, 24/12/2016 + +- Added: Italian translation (thanks to Admix for translating) +- Improved: Advanced AI: Improved lane selection +- Improved: Overall user interface performance +- Improved: Overlay behaviour +- Improved: Traffic measurement +- Improved: Auto pedestrian lights at timed traffic lights behave more intelligently now +- Fixed: 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 +- Fixed: Parking AI - Cims owning a parked car do not consider using other means of transportation +- Fixed: Parking AI - Residents are unable to leave the city through a highway outside connection +- Fixed: Trains/Trams are sometimes not detected at timed traffic lights +- Updated: Compatible with C:SL 1.6.2-f1 +- Updated: The position of the main menu button is now forced inside screen bounds on startup +- Updated: A timed traffic light step with zero minimum time assigned can now be skipped automatically +- Updated: 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) + +### C:SL 1.6.2-f1, 21/12/2016 + +- Fixed: Various errors in game + +### TM:PE 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) + +### TM:PE 1.8.5, 11/12/2016 + +- Fixed: Average speed limits are not correctly calculated for road segments with bicycle lanes (thanks to Toothless FLY [ROK]LSh.st for reporting this issue) +- Removed: "Evacuation busses may only be used to reach a shelter" (CO fixed this issue) +- Updated: Compatible with C:SL 1.6.1-f2 + +### TM:PE 1.8.4, 11/12/2016 + +- Added: "Stay on lane" - Press Shift + S in the Lane Connector tool can cycle through lane directions. +- Fixed: Bicycles cannot change from bicycle lanes to pedestrian lanes +- Fixed: Travel probabilities set in the "Citizen Lifecycle Rebalance v2.1" mod are not obeyed (thanks to informmanuel, shaundoddmusic for reporting this issue) +- Fixed: Number of tourists seems to drop when activating the mod (statistics were not updated, thanks to hpp7117, wjrohn for reporting this issue) +- Fixed: When loading a second savegame a second main menu button is displayed (thanks to Cpt. Whitepaw for reporting this issue) +- Fixed: While path-finding is in progress vehicles do "bungee-jumping" on the current segment (thanks to mxolsenx, Howzitworld for reporting this issue) +- Fixed: Cims leaving the city search for parking spaces near the outside connection which is obviously not required +- Updated: U-turns are now only allowed to be performed from the innermost lane +- Updated: 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. + +### C:SL 1.6.1-f2, 11/12/2016 + +- Added: Missing service enumerators to modding API +- Fixed: Relocating emergency shelter breaks evacuation route +- Fixed: Citizens using evacuation routes like a bus route +- Fixed: Lots of other bug fixes to game and Disasters DLC +- Updated: Chinese localisation added + +### TM:PE 1.8.3, 4/12/2016 + +- Improved: Tweaked u-turn behaviour +- Improved: Info views +- Fixed: Despite having the Parking AI activated, cims sometimes still spawn pocket cars. +- Fixed: When the Parking AI is active, bicycle lanes are not used (thanks to informmanuel for reporting this issue) + +### TM:PE 1.8.2, 3/12/2016 + +- Fixed: Taxis were not used (thanks to [Delta ²k5] for reporting) +- Fixed: Minor UI fix in Default speed limits dialog + +### TM:PE 1.8.1, 1/12/2016 + +- Fixed: Mod crashed when loading a second savegame +- Updated: translations: Polish, Chinese (simplified) + +### TPP2 2.0.12, 10/06/2016 + +- Updated: Compatible with C:SL 1.6.0-f4 +- Meta: This was the final release of TPP2 +- Meta: TM:PE continued as the main traffic mod for the game +- Meta: The TPP/TPP2 can still be found in the Network Extensions 2 project + +### TM:PE 1.8.0, 29/11/2016 + +- Added: Default speed limits +- Added: Parking AI (replaces "Prohibit cims from spawning pocket cars") +- Added: Main menu button is now moveable +- Added: Mod option - Heavy vehicles prefer outer lanes on highways +- Added: Mod option - Realistic speeds +- Added: Mod option - Evacuation busses may ignore traffic rules (Natural Disasters DLC required) +- Added: Mod option - Evacuation busses may only be used to reach a shelter (Natural Disasters DLC required) +- Added: Traffic info view shows parking space demand if Parking AI is activated +- Added: 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 +- Improved: AI - Improved lane selection, especially on busy roads +- Improved: AI - Improved mean lane speed measurement +- Updated: Compatible with C:SL 1.6.0-f4 +- Updated: Extracted internal configuration to XML configuration file +- Updated: Changed main menu button due to changes in the game's user interface +- Updated: Translations for German, Portuguese, Russian, Dutch, Chinese (traditional) +- Removed: Compatibility check for Traffic++ V2 due to excessive workload + +### C:SL 1.6.0-f4 (Natural Disasters), 29/11/2016 + +- Added: Disasters +- Added: Disaster Recovery Service (Van and Helicopter) +- Added: Police / Fire / Ambulance Helicopters +- Added: Pumping Service & Trucks +- Added: Emergency Shelter & Evacuation Bus +- Added: Additional policies (eg. Helicopter Priority) +- Added: Ability for roads to be destroyed by disasters + +### TM:PE 1.7.15, 26/10/2016 + +- Fixed: 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) + +### TM:PE 1.7.14, 18/10/2016 + +- Updated: Compatible with C:SL 1.5.2-f3 + +### TM:PE 1.7.13, 15/09/2016 + +- Added: Button to reset stuck vehicles/cims (see mod settings menu) +- Fixed: Implemented a permanent fix to solve problems with stuck vehicles/cims caused by third party mods +- Fixed: AI: Lane merging was not working as expected +- Fixed: Pedestrian light states were sometimes not being stored correctly (thanks to Filip for pointing out this problem) +- Updated: AI - Improved lane selection algorithm + +### TM:PE 1.7.12, 09/09/2016 + +- Fixed: Timed traffic lights should now correctly detect trains and trams +- Fixed: GUI: Junction restriction icons sometimes disappear +- Updated: AI - Lane changes are reduced on congested road segments +- Updated: Chinese (simplified) translation + +### TM:PE 1.7.11, 01/09/2016 + +- Updated: Compatible with C:SL 1.5.1-f3 + +### TM:PE 1.7.10, 31/08/2016 + +- Added: Players can now disable spawning of pocket cars +- Fixed: Timed traffic lights were flickering +- Fixed: Pedestrian traffic lights were not working as expected +- Fixed: When upgrading/removing/adding a road segment, nearby junction restrictions were removed +- Fixed: Setting up vehicle restrictions affects trams (thanks to chem for reporting) +- Fixed: Manual pedestrian traffic light states were not correctly handled +- Fixed: Junction restrictions overlay did not show all restricted junctions +- Updated: Chinese (simplified) translation + +### TM:PE 1.7.9, 22/08/2016 + +- Improved: Performance improvements +- Fixed: In-game traffic light states are now correctly rendered when showing "yellow" +- Fixed: GUI - Traffic light states do not flicker anymore +- Removed: Negative effects on public transport usage + +### TM:PE 1.7.8, 18/08/2016: + +- Fixed: Cims sometimes got stuck (thanks to all reports and especially to Thilawyn for providing a savegame) +- Improved: GUI - Better traffic light arrow display +- Improved: Performance while saving + +### TM:PE 1.7.7, 16/08/2016: + +- Added: "110" speed limit +- Improved: Performance while saving +- Improved: GUI - Windows are draggable +- Improved: GUI - Improved window scaling on lower resolutions +- Updated: AI - Instead of walking long distances, citizens now use a car +- Updated: AI - Citizens will remember their last used mode of transport (e.g. they will not drive to work and come return by bus anymore) +- Updated: AI - Increased path-finding costs for traversing over restricted road segments + +### TM:PE 1.7.6, 14/08/2016: + +- Added: Players may now prohibit cims from crossing the street +- Added: the possibility to connect train track lanes with the lane connector (as requested by pilot.patrick93) +- Improved: UI - Clicking with the secondary mouse button now deselects the currently selected node/segment for all tools +- Improved: AI - Tuned randomization of lane changing behaviour +- Fixed: AI: At specific junctions, vehicles were not obeying lane connections correctly (thanks to Mariobro14 for pointing out this problem) +- Fixed: AI: Path-finding costs for u-turns were not correctly calculated (thanks to Mariobro14 for pointing out this problem) +- Fixed: Vehicles were endlessly waiting for each other at junctions with certain priority sign configurations +- Fixed: AI: Lane changing costs corrected +- Updated: AI - Introduced path-finding costs for leaving main highway (should reduce amount of detours taken) +- Updated: Moved options from "Change lane arrows" to "Vehicle restrictions" tool +- Updated: Russian translation + +### TM:PE 1.7.5, 07/08/2016: + +- Fixed: AI - Cims were using pocket cars whenever possible +- Fixed: AI - Path-finding failures led to much less vehicles spawning +- Fixed: AI - Lane selection at junctions with custom lane connection was not always working properly (e.g. for Network Extensions roads with middle lane) +- Fixed: While editing a timed traffic light it could happen that the traffic light was deleted + +### TM:PE 1.7.4, 31/07/2016: + +- Added: French translations (thanks to simon.royer007 for translating!) +- Improved: AI - Tuned new parameters +- Improved: Various code improvements +- Fixed: Activated/Disabled features were not loaded correctly +- Fixed: AI - At specific junctions the lane changer did not work as intended +- Fixed: Possible fix for OSX performance issues +- Updated: AI - Switched from relative to absolute traffic density measurement + +### TM:PE 1.7.3, 29/07/2016: + +- Added: Ability to enable/disable mod features (e.g. for performance reasons) +- Improved: Further code improvements +- Fixed: Vehicle type determination was incorrect (fixed u-turning trams/trains, stuck vehicles) +- Fixed: Clicking on a train/tram node with the lane connector tool led to an uncorrectable error (thanks to noaccount for reporting this problem) + +### TM:PE 1.7.2, 26/07/2016: + +- Improved: Optimized UI overlay performance + +### TM:PE 1.7.1, 24/07/2016: +- Fixed: Trains were not despawning if no path could be calculated +- Fixed: Workaround for third-party issue: TM:PE now detects if the calculation of total vehicle length fails +- Removed: "Busses now may only ignore lane arrows if driving on a bus lane" + +### TM:PE 1.7.0, 23/07/2016: + +- Added: Traffic++ lane connector +- Added: Compatibility detection for the Rainfall mod +- Fixed: Busses now may only ignore lane arrows if driving on a bus lane +- Improved: performance of priority sign rules +- Improved: Better UI performance if overlays are deactivated +- Improved: Better fault-tolerance of the load/save system +- Fixed: Taxis were allowed to ignore lane arrows +- Fixed: AI - Highway rules on left-hand traffic maps did not work the same as on right-hand traffic maps +- Fixed: 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) +- Updated: AI - Cims now ignore junctions where pedestrian lights never change to green +- Updated: AI - Removed the need to define a lane changing probability +- Updated: AI - Tweaked lane changing parameters +- Updated: AI - Highway rules are automatically disabled at complex junctions (= more than 1 incoming and more than 1 outgoing roads) +- Updated: Simulation accuracy now also controls time intervals between traffic measurements +- Updated: Default wait-flow balance is set to 0.8 +- Updated: Rewritten and simplified vehicle position tracking near timed traffic lights and priority signs for performance reasons + +### TM:PE 1.6.22, 29/06/2016: + +- Fixed: Traffic measurement at timed traffic lights was incorrect +- Updated: AI - Taxis now may not ignore lane arrows and are using bus lanes whenever possible (thanks to Cochy for pointing out this issue) +- Updated: AI - Busses may only ignore lane arrows while driving on a bus lane + +### TM:PE 1.6.22, 21/06/2016: + +- Improved: Advanced Vehicle AI - Improved lane selection at junctions where bus lanes end +- Improved: Advanced Vehicle AI - Improved lane selection of busses +- Improved: Speed/vehicle restrictions may now be applied to all road segments between two junctions by holding the shift key +- Improved: Automatic pedestrian lights +- Improved: Separate traffic lights: Traffic lights now control traffic lane-wise +- Fixed: Lane selection on maps with left-hand traffic was incorrect +- Fixed: While building in pause mode, changes in the road network were not always recognized causing vehicles to stop/despawn +- Fixed: Police cars off-duty were ignoring lane arrows +- Fixed: 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) +- Fixed: Trains/Trams were sometimes ignoring timed traffic lights (many thanks to Filip for identifying this problem) +- Fixed: Building roads with bus lanes caused garbage, bodies, etc. to pile up +- Updated: Reworked how changes in the road network are recognized +- Updated: Sensitivity slider is only available while adding/editing a step or while in test mode + +### TM:PE 1.6.21, 14/06/2016: + +- Fixed: Too few cargo trains were spawning (thanks to Scratch, toruk_makto1, Mr.Miyagi, mottoh and Syparo for pointing out this problem) +- Fixed: Vehicle restrictions did not work as expected (thanks to nordlaser for pointing out this problem) + +### TM:PE 1.6.20, 11/06/2016: + +- Fixed: Priority signs were not working correctly (thanks to mottoth, Madgemade for pointing out this problem) + +### TM:PE 1.6.19, 11/06/2016 + +- Fixed: Timed traffic lights UI not working as expected (thanks to Madgemade for pointing out this problem) + +### TPP2 2.0.11, 10/06/2016 + +- Updated: Compatible with C:SL 1.5.0-f4 + +### TM:PE 1.6.18, 09/06/2016 + +- Improved: Players can now select elevated rail segments/nodes +- Improved: Trams and trains now follow priority signs +- Improved: performance of priority signs and timed traffic lights +- Improved: UI behaviour when setting up priority signs +- Updated: Compatible with C:SL 1.5.0-f4 + +### C:SL 1.5.0-f4 (Match Day), 09/06/2016 + +- Added: Football stadium (causes heavy traffic in city on match day) +- Fixed: Various bugs in game and DLCs + +### TPP2 2.0.10, 02/06/2016 + +- Fixed: NullPointerException + +### TPP2 2.0.9, 31/05/2016 + +- Update: Compatbile with Network Extensions 2.5 + +### TM:PE 1.6.17, 20/04/2016 + +- Fixed: Hotfix for reported path-finding problems + +### TM:PE 1.6.16, 19/04/2016 + +- Updated: Compatible with C:SL 1.4.1-f2 + +### C:SL 1.4.1-f2, 19/04/2016 + +- Fixed: Busses could enter/exit bus stations at highways with sound barriers +- Fixed: Various other bugs + +### TPP2 2.0.8, 22/03/2016 + +- Updated: Cpde from TM:PE 1.6.10 +- Updated: Compatible with C:SL 1.4.0-f3 +- Removed: Old code from TPP + +### TM:PE 1.6.15, 22/03/2016 + +- Added: Traditional Chinese translation +- Improved: Possible fix for crashes described by cosminel1982 +- Updated: Compatible with C:SL 1.4.0-f3 + +### C:SL 1.4.0-f3, 22/03/2016 + +- Fixed: Lots of bugs in game and DLCs +- Updated: Lots of stuff in game and DLCs + +### TM:PE 1.6.14, 17/03/2016 + +- Fixed: Cargo trucks did not obey vehicle restrictions (thanks to ad.vissers for pointing out this problem) +- Fixed: When Advanced AI was deactivated, u-turns did not have costs assigned + +### TM:PE 1.6.13, 16/03/2016 + +- Added: Dutch translation +- Improved: The pedestrian light mode of a traffic light can now be switched back to automatic +- Improved: Vehicles approaching a different speed limit change their speed more gradually +- Improved: Path-finding performance improvements +- Improved: Fine-tuned path-finding lane changing behaviour +- Fixed: After loading another savegame, timed traffic lights stopped working for a certain time +- Fixed: Lane speed calculation corrected +- Updated: The size of signs and symbols in the overlay is determined by screen resolution height, not by width + +### TM:PE 1.6.12, 03/03/2016 + +- Improved: Reduced memory usage +- Fixed: Adding/removing junctions to/from existing timed traffic lights did not work (thanks to nieksen for pointing out this problem) +- Fixed: Separate timed traffic lights were sometimes not saved (thanks to nieksen for pointing out this problem) +- Fixed: Fixed an initialization error (thanks to GordonDry for pointing out this problem) + +### TM:PE 1.6.11, 03/03/2016 + +- Added: Chinese translation +- Added: By pressing "Page up"/"Page down" you can now switch between traffic and default map view +- Improved: Size of information icons and signs is now based on your screen resolution +- Updated: UI code refactored + +### TM:PE 1.6.10, 02/03/2016 + +- Added: Additional controls for vehicle restrictions added +- Fixed: Clicking on a Traffic Manager overlay resulted in vanilla game components (e.g. houses, vehicles) being activated + +### TM:PE 1.6.9, 02/03/2016 + +- Updated: Compatibility with C:SL 1.3.2-f1 + +### C:SL 1.3.2-f1, 02/03/2016 + +- Fixed: Stuck pedestrian issues +- Fixed: Short roads warning +- Fixed: Cyclists despawn when changing from bike path to bike lane + +### TPP2 2.0.7, 01/03/2016 + +- Improved: Performance +- Updated: Vehicle AIs +- Updated: Code from TM:PE 1.16 merged in to TPP2 + +### TM:PE 1.6.8, 01/03/2016 + +- Added: Spanish translation +- Improved: Major path-finding performance improvements +- Updated: Japanese translation (thanks to Akira Ishizaki for translating!) + +### TPP2 2.0.6, 28/02/2016 + +- Updated: Cargo trucks pathfinder +- Updated: Stations and ports pathfinder +- Updated: Code from TM:PE 1.6.6 & 1.6.7 merged in to TPP2 + +### TM:PE 1.6.7, 27/02/2016 + +- Improved: Tuned AI parameters +- Improved: Traffic density measurements +- Improved: Lane changing near junctions - reintroduced costs for lane changing before junctions +- Improved: Vehicle behaviour near blocked roads (e.g. while a building is burning) +- Fixed: Automatic pedestrian lights for outgoing one-ways fixed +- Fixed: U-turns did not have appropriate costs assigned +- Fixed: The time span between AI traffic measurements was too high + +### TM:PE 1.6.6, 27/02/2016 + +- Improved: Easier to select segment ends in order to change lane arrows. +- Fixed: U-turning vehicles were not obeying the correct directional traffic light (thanks to t1a2l for pointing out this problem) +- Updated: 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!) + +### TPP2 2.0.5, 26/02/2016 + +- Improved: Service vehicle pathfinding +- Fixed: Cars disappearing underground + +### TPP2 2.0.4, 24/02/2016 + +- Improved: Vehicle restrictions +- Updated: Removed typecasting from Car AI +- Updated: Major pathfinder refactor +- Updated: Code from TM:PE 1.6.0 to 1.6.5 merged in to TPP2 + +### TM:PE 1.6.5, 24/02/2016 + +- Added: Despawning setting to options dialog +- Improved: Detection of Traffic++ V2 + +### TPP2 2.0.3, 23/02/2016 + +- Added: Medium pedestrianised road +- Fixed: Busses not using custom pathfinder +- Fixed: Crash bug when Snowfall DLC subscribed +- Fixed: Trams not working +- Fixed: Transport lines not respecting restrictions +- Fixed: Broken bus lanes on roads +- Fixed: Path finder not respecting vehicle types +- Fixed: Car AI broken when using Snowfall DLC +- Fixed: Bug loading options +- Updated: Improved how extended vehicle types are defined +- Updated: Custom path manager refactored +- Updated: Namespace and project refactor & clean-up +- Updated: Separate extensions and AIs +- Updated: Imrpoved options definition and storage +- Removed: Ghost Mode + +### TM:PE 1.6.4, 23/02/2016 + +- Improved: Minor performance improvements +- Fixed: Path-finding calculated erroneous traffic density values +- Fixed: Cims left the bus just to hop on a bus of the same line again (thanks to kamzik911 for pointing out this problem) +- Fixed: Despawn control did not work (thanks to xXHistoricalxDemoXx for pointing out this problem) +- Fixed: State of new settings was not displayed correctly (thanks to Lord_Assaultーさま for pointing out this problem) +- Fixed: Default settings for vehicle restrictions on bus lanes corrected +- Fixed: Pedestrian lights at railway junctions fixed (they are still invisible but are derived from the car traffic light state automatically) + +### C:SL 1.3.1-f1, 23/02/2016 + +- Fixed: Minor bug fixes and updates + +### TM:PE 1.6.3, 22/02/2016 + +- Fixed: Using the "Old Town" policy led to vehicles not spawning +- Fixed: Planes, cargo trains and ship were sometimes not arriving +- Fixed: Trams are not doing u-turns anymore + +### TPP2 2.0.2, 21/02/2016 + +- Fixed: Bus routing & bus stops +- Fixed: European theme incompatible (thanks BloodyPenguin for help!) +- Fixed: Traffic doesn't stop for flooded tunnels +- Updated: Serialise for lane data + +### TM:PE 1.6.2, 20/02/2016 + +- Added: Trams are now obeying speed limits (thanks to Clausewitz for pointing out the issue) +- Fixed: Clear traffic sometimes throwed an error +- Fixed: Vehicle restrictions did not work as expected (thanks to [Delta ²k5] for pointing out this problem) +- Fixed: Transition of automatic pedestrian lights fixed + +### TM:PE 1.6.1, 20/02/2016 + +- Improved: Performance +- Improved: Modifying mod options through the main menu now gives an annoying warning message instead of a blank page. +- Fixed: Various UI issues + +### TPP2 2.0.1, 19/02/2016 + +- Fixed: Default vehicle restrictions always include bus +- Updated: Compatible with C:SL 1.3.0-f4 + +### TPP2 2.0.0, 18/02/2016 + +- Added: Allow restrictions on pedestrianised roads +- Fixed: Bus lines not working +- Fixed: Traffic++ roads +- Fixed: Options screen now saves options properly +- Improved: Better compatibility with TPP +- Improved: Detection of incompatible mods +- Updated: Lots of code clean-up +- Meta: First sable release of TPP2 + +### TM:PE 1.6.0, 18/02/2016 + +- Added: Separate traffic lights for different vehicle types +- Added: Vehicle restrictions +- Added: "Vehicles may enter blocked junctions" may now be defined for each junction separately (again) +- Added: Road conditions (snow, maintenance state) may now have a higher impact on vehicle speed (see "Options" menu) +- Improved: Better handling of vehicle bans +- Improved: Method for calculating lane traffic densities +- Improved: Performance optimizations +- Improved: Advanced Vehicle AI: Improved lane spreading +- Fixed: Reckless drivers now do not enter railroad crossings if the barrier is down +- Fixed: Path-finding costs for crossing a junction fixed +- Fixed: Vehicle detection at timed traffic lights did not work as expected +- Fixed: Not all valid traffic light arrow modes were reachable +- Updated: Compatible with C:SL 1.3.0-f4 +- Updated: Ambulances, fire trucks and police cars on duty are now ignoring lane arrows +- Updated: Timed traffic lights may now be setup at arbitrary nodes on railway tracks +- Updated: Option dialog is disabled if accessed through the main menu +- Updated: Vehicles going straight may now change lanes at junctions +- Updated: Vehicles may now perform u-turns at junctions that have an appropriate lane arrow configuration +- Updated: Emergency vehicles on duty now always aim for the fastest route + +### C:SL 1.3.0-f4 (Snowfall), 18/02/2016 + +- Added: Winter biome +- Added: Trams +- Added: Snow ploughs +- Added: Road maintenance +- Added: Road conditions affect vehicle speed +- Added: Priority routes + +### TPP2 0.0.1, 17/02/2016 + +- Added: Tool - Lane Connector (from TPP) +- Added: Tool - Vehicle Restrictions (from TPP) +- Added: Tool - Speed Restrictions (from TPP) +- Added: No Despawn (from TPP) +- Added: Improved AI (from TPP:AI) +- Meta: Released as version 0.0a + +### TPP2 0.0, 17/02/2016 + +- Meta: Development started 4th October 2015 +- Meta: Was planned to replace TM:PE, TPP, etc +- Meta: Dev team: LinuxFan, Katalyst6, Lazarus*Man +- Meta: GitHub Repository: [Katalyst6/CSL.TransitAddonMod](https://github.com/Katalyst6/CSL.TransitAddonMod) +- Meta: Steam Workshop: [626024868 - Traffic++ V2](https://steamcommunity.com/sharedfiles/filedetails/?id=626024868) + +### TM:PE 1.5.2, 01/02/2016 + +- Added: Traffic lights may now be added to/removed from underground junctions +- Added: 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) +- Added: Display of priority signs, speed limits and timed traffic lights may now be toggled via the options dialog +- Fixed: Reckless driving does not apply for trains (thanks to GordonDry for pointing out this problem) +- Fixed: Manual traffic lights were not working (thanks to Mas71 for pointing out this problem) +- Fixed: Pedestrians were ignoring timed traffic lights (thanks to Hannes8910 for pointing out this problem) +- Fixed: Sometimes speed limits were not saved (thanks to cca_mikeman for pointing out this problem) + +### TM:PE 1.5.1, 31/01/2016 + +- Added: Trains are now following speed limits + +### TM:PE 1.5.0, 30/01/2016 + +- Added: Speed restrictions (as requested by Gfurst) +- Improved: AI - Parameters tuned +- Improved: Code improvements +- Fixed: Flowing/Waiting vehicles count corrected +- Updated: Lane arrow changer window is now positioned near the edited junction (as requested by GordonDry) + +### TM:PE 1.4.9, 27/01/2016 + +- Added: Junctions can be added to/removed from timed traffic lights after they are created +- Improved: When viewing/moving a timed step, the displayed/moved step is now highlighted (thanks to Joe for this idea) +- Improved: Performance improvements +- Fixed: AI - Fixed a division by zero error (thanks to GordonDry for pointing out this problem) +- Fixed: AI - Near highway exits vehicles tended to use the outermost lane (thanks to Zake for pointing out this problem) +- Fixed: Some lane arrows disappeared on maps using left-hand traffic systems (thanks to Mas71 for pointing out this problem) +- Fixed: In lane arrow edit mode, the order of arrows was sometimes incorrect (thanks to Glowstrontium for pointing out this problem) +- Fixed: Lane merging in left-hand traffic systems fixed +- Fixed: Turning priority roads fixed (thanks to GordonDry for pointing out this problem) + +### TM:PE 1.4.8, 25/01/2016 + +- Added: translation to Polish (thanks to Krzychu1245 for working on this!) +- Added: translation to Russian (thanks to FireGames for working on this!) +- Improved: AI - Parameters have been tuned +- Improved: AI - Added traffic density measurements +- Improved: Performance improvements +- Fixed: After removing a timed or manual light the traffic light was deleted (thanks to Mas71 for pointing out this problem) +- Fixed: Segment geometries were not always calculated +- Fixed: In highway rule mode, lane arrows sometimes flickered +- Fixed: Some traffic light arrows were sometimes not selectable + +### TM:PE 1.4.7, 22/01/2016 + +- Added: Translation to Portuguese (thanks to igordeeoliveira for working on this!) +- Improved: Reduced file size (thanks to GordonDry for reporting this problem) +- Fixed: Freight ships/trains were not coming in (thanks to Mas71 and clus for reporting this problem) +- Fixed: The toggle "Vehicles may enter blocked junctions" did not work properly (thanks for exxonic for reporting this problem) +- Fixed: If a timed traffic light is being edited the segment geometry information is not updated (thanks to GordonDry for reporting this problem) + +### TM:PE 1.4.6, 22/01/2016 + +- Added: Running average lane speeds are measured now +- Fixed: Minor bug fixes + +### TM:PE 1.4.5, 22/01/2016 + +- Added: "Vehicles may enter blocked junctions" may now be defined for each junction separately +- Fixed: A deadlock in the path-finding is fixed +- Fixed: Small timed light sensitivity values (< 0.1) were not saved correctly +- Fixed: Timed traffic lights were not working for some players +- Updated: Refactored segment geometry calculation + +### TM:PE 1.4.4, 21/01/2016 + +- Added: Localization support + +### TM:PE 1.4.3, 20/01/2016 + +- Improved: Several performance improvements +- Improved: Calculation of segment geometries +- Improved: Load balancing +- Improved: Police cars, ambulances, fire trucks and hearses are now also controlled by the AI +- Fixed: Vehicles did not always take the shortest path +- Fixed: Vehicles disappeared after deleting/upgrading a road segment +- Fixed: Fixed an error in path-finding cost calculation +- Fixed: Outgoing roads were treated as ingoing roads when highway rules were activated + +### TM:PE 1.4.2, 16/01/2016 + +- Added: Option added to disable highway rules +- Improved: Several major performance improvements (thanks to sci302 for pointing out those issues) +- Improved: Saving/loading of timed traffic lights +- Fixed: AI did not consider speed limits/road types during path calculation (thanks to bhanhart, sa62039 for pointing out this problem) +- Fixed: Vehicles were stopping in front of green traffic lights +- Fixed: Stop/Yield signs were not working properly (thanks to GordonDry, Glowstrontium for pointing out this problem) +- Fixed: Cargo trucks were ignoring the "Heavy ban" policy, they should do now (thanks to Scratch for pointing out this problem) +- Updated: Lane-wise traffic density is only measured if Advanced AI is activated +- Meta: Connecting a city road to a highway road that does not supply enough lanes for merging leads to behaviour people do not understand (see manual). + +### TM:PE 1.4.1, 15/01/2016 + +- Fixed: Path-finding near junctions fixed + +### TM:PE 1.4.0, 15/01/2016 + +- Added: Advanced Vehicle AI (disabled by default! Go to "Options" and enable it if you want to use it.) +- Fixed: Traffic lights were popping up in the middle of roads +- Fixed: Fixed the lane changer for left-hand traffic systems (thanks to Phishie for pointing out this problem) +- Fixed: Traffic lights on invalid nodes are not saved anymore + +### TM:PE 1.3.24, 13/01/2016 + +- Added: Configuration option that allows vehicles to enter blocked junctions +- Improved: Priority signs: After adding two main road signs the next offered sign is a yield sign +- Improved: Priority signs: Vehicles now should notice earlier that they can enter a junction +- Fixed: Invalid (not created) lanes are not saved/loaded anymore +- Fixed: Some priority signs were not saved +- Fixed: Priority signs on deleted segments are now deleted too +- Fixed: Lane arrows on removed lanes are now removed too +- Fixed: 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) +- Fixed: If reckless driving was set to "The Holy City (0 %)", vehicles blocked intersections with traffic light. +- Fixed: Traffic light arrow modes were sometimes not correctly saved +- Removed: Legacy XML file save system + +### TM:PE 1.3.23, 09/01/2016 + +- Added: Option added to forget all toggled traffic lights +- Fixed: 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) + +### TM:PE 1.3.22, 08/01/2016 + +- Added: Option allowing busses to ignore lane arrows +- Added: Option to display nodes and segments + +### TM:PE 1.3.21, 06/01/2016 + +- Added: Traffic Sensitivity Tuning +- Improved: When adding a new step to a timed traffic light the lights are inverted. +- Improved: Timed traffic light status symbols should now be less annoying +- Fixed: Deletion of junctions that were members of a traffic light group is now handled correctly + +### TM:PE 1.3.20, 04/01/2016 + +- Added: Reckless driving +- Improved: User interface +- Fixed: Timed traffic lights are not saved correctly after upgrading a road nearby + +### TM:PE 1.3.19, 04/01/2016 +- Improved: 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 +- Fixed: Upgrading a road resets the traffic light arrow mode +- Updated: Timed traffic lights: Absolute minimum time changed to 1 + +### TM:PE 1.3.18, 03/01/2016 + +- Improved: You can now switch between activated timed traffic lights without clicking on the menu button again +- Fixed: Provided a fix for unconnected junctions caused by other mods +- Removed: Crosswalk feature removed. If you need to add/remove crosswalks please use the "Crossings" mod. + +### TM:PE 1.3.17, 03/01/2016 + +- Fixed: 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) +- Fixed: After using the "Manual traffic lights" option, toggling lights does not work (thanks to Timso113 for pointing out this problem) + +### TM:PE 1.3.16, 03/01/2016 + +- Fixed: 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 + +### TM:PE 1.3.15, 02/01/2016 + +- Added: Simulation accuracy (and thus performance) is now controllable through the game options dialog +- Fixed: Vehicles on a priority road sometimes stop without an obvious reason + +### TM:PE 1.3.14, 01/01/2016 + +- Improved: Performance +- Improved: Non-timed traffic lights are now automatically removed when adding priority signs to a junction +- Improved: Adjusted the adaptive traffic light decision formula (vehicle lengths are considered now) +- Improved: Traffic two road segments in front of a timed traffic light is being measured now + +### TM:PE 1.3.13, 01/01/2016 + +- Fixed: Lane arrows are not correctly translated into path finding decisions (thanks to bvoice360 for pointing out this problem) +- Fixed: Priority signs are sometimes undeletable (thank to Blackwolf for pointing out this problem) +- Fixed: Errors occur when other mods without namespace definitions are loaded (thanks to Arch Angel for pointing out this problem) +- Fixed: Connecting a new road segment to a junction that already has priority signs now allows modification of the new priority sign + +### TM:PE 1.3.12, 30/12/2015 + +- Fixed: Priority signs are not editable (thanks to ningcaohan for pointing out this problem) + +### TM:PE 1.3.11, 30/12/2015 + +- Improved: Road segments next to a timed traffic light may now be deleted/upgraded/added without leading to deletion of the light +- Updated: Priority signs and Timed traffic light state symbols are now visible as soon as the menu is opened + +### TM:PE 1.3.10, 29/12/2015 +- Fixed: an issue where timed traffic light groups were not deleted after deleting an adjacent segment + +### TM:PE 1.3.9, 29/12/2015 + +- Added: Introduced information icons for timed traffic lights +- Updated: Mod is now compatible with "Improved AI" (Lane changer is deactivated if "Improved AI" is active) + +### TM:PE 1.3.8, 29/12/2015 + +- Improved: UI improvements +- Fixed: Articulated busses are now simulated correctly (thanks to nieksen for pointing out this problem) + +### TM:PE 1.3.7, 28/12/2015 + +- Fixed: When setting up a new timed traffic light, yellow lights from the real-world state are not taken over +- Fixed: When loading another save game via the escape menu, Traffic Manager does not crash +- Fixed: When loading another save game via the escape menu, Traffic++ detection works as intended +- Fixed: Lane arrows are saved correctly + +### TM:PE 1.3.6, 28/12/2015 + +- Fixed: wrong flow value taken when comparing flowing vehicles +- Updated: Forced node rendering after modifying a crosswalk + +### TM:PE 1.3.5, 28/12/2015 + +- Improved: Adjusted the comparison between flowing (green light) and waiting (red light) traffic +- Fixed: Pedestrian traffic Lights (thanks to Glowstrontium for pointing out this problem) +- Fixed: Deleting a segment with a timed traffic light does not cause a NullReferenceException + +### TM:PE 1.3.4, 27/12/2015 + +- Improved: Better traffic jam handling + +### TM:PE 1.3.3, 27/12/2015 + +- Improved: Hotfix - Deleting a segment with a timed traffic light does not cause a NullReferenceException +- Updated: If priority signs are located behind the camera they are not rendered anymore + +### TM:PE 1.3.2, 27/12/2015 + +- Fixed: Synchronized traffic light rendering: In-game Traffic lights display the correct color (Thanks to Fabrice for pointing out this problem) +- Fixed: Traffic lights switch between green, yellow and red. Not only between green and red. +- Updated: Priority signs are persistently visible when Traffic Manager is in "Add priority sign" mode +- Updated: UI tool tips are more explanatory and are shown longer. + +### TM:PE 1.3.1, 26/12/2015 + +- Fixed: Timed traffic lights of deleted/modified junctions get properly disposed +- Updated: Minimum time units may be zero now (timed traffic lights) + +### TM:PE 1.3.0, 25/12/2015 + +- Added: Adaptive Timed Traffic Lights (automatically adjusted based on traffic amount) +- Meta: First release in Steam Workshop under name "Traffic Manager: President Edition" +- Meta: Fork of TMPlus 1.2.0 created by LinuxFan (github user: VictorPhilipp) + +### TMPlus 1.2.0, 04/12/2015 + +- Updated: Game version 1.2.2-f2 compatible + +### C:SL 1.2.2-f2, 05/11/2015 + +- Updated: Limits for zoning, buildings, road segments increased +- Fixed: Various bugfixes in game and asset editor + +### TM 1.0.9rc, 12/10/2015 + +- Removed: Tool - Pedestrian crosswalks (too buggy) +- Meta: Released as version 1.09rc +- Meta: Last release of original Traffic Manager mod by CBeTHaX (aka SvetlozarValchev) + +### TM 1.0.9rc, 12/10/2015 + +- Fixed: Minor bugs +- Meta: Released as version 1.09rc + +### TM 1.0.9rc, 12/10/2015 + +- Update: Compatible with C:SL 1.2.1-f1 +- Meta: Released as version 1.09rc + +### C:SL 1.2.1-f1, 01/10/2015 + +- Fixed: Minor bugfixes with game and asset editor + +### TPP 1.6.1, 25/09/2015 + +- Removed: In-game log messages +- Meta: This was the last release of Traffic++ mod +- Meta: It was later continued in Traffic++ V2 (TPP2) +- Meta: It's features have since been merged in to TM:PE (tools & AIs) and NExt2 (roads) + +### TPP 1.6.0, 25/09/2015 + +- Fixed: Rendering of road customiser in underground view +- Updated: Integrated latest Improved Vehicle AI +- Updated: Lamps on pedestrian roads + +### TPP 1.5.6, 24/09/2015 + +- Fixed: Disable custom roads option not working + +### TPP 1.5.5, 24/09/2015 + +- Update: Compatible with C:SL 1.2.0 (thanks javitonino) + +### C:SL 1.2 (After Dark), 24/09/2015 + +- Added: Bicycles +- Added: Bus and bike lanes +- Added: Prison Vans +- Added: Taxis + +### TMPlus 1.1.1, 09/08/2015 + +- Fixed: Load/save system +- Fixed: Saving no longer opens dev console + +### TM 1.0.6, 07/08/2015 + +- Update: Revert to old version with C:S 1.1.1 compatibility +- Meta: Released as version 1.06 + +### TMPlus 1.1.1, 06/08/2015 + +- Fixed: Null reference error when loading old savegames +- Updated: New save system +- Updated: New load system +- Meta: Lots of code refactoring + +### TPP:AI 1.0.0, 02/08/2015 + +- Added: Improved Vehicle AI +- Meta: This was a preview mod by Jfarias, developer of TPP +- Meta: It made vehicles use more lanes, and also change lanes (later becoming "DLS" in TM:PE) +- Meta: Steam Workshop: [492391912 - Improved AI Traffic++](https://steamcommunity.com/sharedfiles/filedetails/?id=492391912) +- Meta: Source code no longer avialable +- Meta: The feature was merged in to later version of TPP and, later, TM:PE (Advanced Vehicle AI and DLS) + +### TM 1.0.5, 28/07/2015 + +- Fixed: Pathfinder bugs +- Fixed: Lane changer and traffic remover features sometimes disabled even without Traffic++ +- Fixed: Turn direction not matching lane marking logic (thanks klparrot!) +- Updated: Code refactor and clean-up +- Updated: Compatible with C:SL 1.1.1 +- Meta: Released as version 1.05 + +### TMPlus 1.1.1, 26/07/2015 + +- Fixed: Stop signs not working + +### TMPlus 1.1.1, 15/07/2015 + +- Fixed: PathManager. Should now work for people without Traffic++ +- Meta: Last confirmed Workshop release of "Traffic Manager Plus" + +### TMPlus 1.1.1, 15/07/2015 + +- Updated: Reverted PathFinder as it's causing Null Reference exceptions. Need to refactor it. + +### TMPlus 1.1.1, 14/07/2015 + +- Added: Support for tunnels +- Fixed: Turn direction not matching lane marking logic (thanks klparrot!) +- Fixed: Compilation errors +- Improved: Custom AI code refactoring and clean-up +- Improved: Tool code refactoring and clean-up +- Improved: UI code refactoring and clean-up +- Updated: Compatible with C:SL 1.1.1 +- Meta: Project fork by sieggy +- Meta: First release in Steam Workshop under name "Traffic Manager Plus" + +### TPP 1.5.4, 06/07/2015 + +- Updated: Compatible with C:SL 1.1.1 + +### C:SL 1.1.1, 01/07/2015 + +- Added: Tunnels for pedestrian paths +- Added: Autosave feature +- Fixed: Lots of stuff +- Improved: Asset editor + +### TPP 1.5.3, 02/06/2015 + +- Fixed: Crash on Linux + +### TPP 1.5.2, 02/06/2015 + +- Fixed: Trucks not allowed on new roads + +### TPP 1.5.1, 01/06/2015 + +- Fixed: Bug preventing options loading if button not shown in content manager +- Fixed: Duplicate prefab bug in Ghost Mode +- Fixed: Duplicate roads in roads panel in Ghost Mode +- Fixed: Vehicles have wrong AI when returning to main menu +- Fixed: Vehicles using restricted lanes +- Fixed: Bug preventing the Scrollable Toolbar mod from working in the entirety of the speed customizer panel +- Updated: Added debug logs to the game's Debug Panel (F7) - thanks to Nefarion for implementing it + +### TM 1.0.4, 29/05/2015 + +- Fixed: Negative timers (thanks XaBBoK!) +- Improved: Pathfinder cleanup (thanks dornathal!) + +### TPP 1.5.0 hotfix, 28/05/2015 + +- Fixed: Vehicles stopping in road +- Fixed: Realistic speeds not stopping without restarting the game +- Fixed: Road costs (thanks Archomeda) + +### TPP 1.5.0, 27/05/2015 + +- Added: More roads +- Added: Option for Improved Vehicle AI +- Fixed: Bug cauing crashes when loading map through pause menu +- Fixed: Tunnels from new roads turning in to normal tunnels +- Fixed: Custom vehicles ignoring lane restricitons + +### TPP 1.4.0 hotfix, 23/05/2015 + +- Fixed: Few more bugs + +### TPP 1.4.0 hotfix, 23/05/2015 + +- Fixed: Wrong lane usage in highways + +### TPP 1.4.0, 22/05/2015 + +- Added: Tool - Speed Limits +- Added: Underground view for customisation tools +- Added: Mod Option - No Despawn (from TM:PE) +- Added: Multi-track station enabler +- Improved: New UI for road customisation tools +- Fixed: Road customisation tools appearing in asset editor +- Fixed: Objects under the map bug +- Fixed: Options button on all resolutions +- Fixed: Train tracks should not be selectable + +### TPP 1.3.2 hotfix, 20/05/2015 + +- Improved: Compatibility with C:SL 1.1.0b + +### TPP 1.3.2 hotfix, 19/05/2015 + +- Update: Compatible with C:SL 1.1.0b + +### TM 1.0.4 hotfix, 19/05/2015 + +- Fixed: Support for tunnels in AIs and pathfinder +- Update: Compatible with C:SL 1.1.0b +- Update: Traffic++ Compatibility - No despawn or lane changer available in compatibility mode + +### C:SL 1.1.0b, 19/05/2015 + +- Added: European theme +- Added: Tunnels for roads and rail +- Improved: Metro tunnels can be built at different heights + +### TPP 1.3.2, 05/05/2015 + +- Added: Ability to set restrictions lane by lane +- Added: Ability to customise multiple lanes at same time +- Fixed: But that prevented settings saving +- Fixed: Bug that prevented short roads being selected +- Fixed: Vehicle restrictions now show correctly on all resolutions +- Updated: Road customiser tool button moved +- Updated: Customisation overlays visible at all times when tool active + +### TPP 1.3.1, 29/04/2015 + +- Improved: Perfomance issues reduced +- Fixed: Strange vehicle behaviours +- Fixed: Prevent road tool from selecting vehicles + +### TPP 1.3.0, 28/04/2015 + +- Added: More roads +- Added: Services overlay (vehicle restrictions) for pedestrian roads +- Added: Tool - Vehicle Restrictions +- Added: Tool - Lane Connections +- Fixed: Left hand traffic issues +- Fixed: Ghost mode caused crash if busway bridges on map +- Fixed: Several crashes caused by mod incompatibilities + +### TM 1.0.4 beta, 22/04/2015 + +- Added: Buttons for timed traffic light states: skip and move up/down +- Fixed: Null reference exceptions when placing a road +- Fixed: Pathfinder issues +- Improved: Lane changes +- Updated: Prevent lane changer being used on non-node segments +- Meta: Released as version 1.04rc + +### TM 1.0.1, 21/04/2015 + +- Fixed: Errors on very close segments added to timed traffic lights +- Fixed: Save/load traffic lights going negative (finally) +- Meta: Released as version 1.01rc + +### TM 1.0.0, 21/04/2015 + +- Fixed: Wrong lane change (finally) +- Fixed: Timed traffic lights should no longer go below zero +- Fixed: UI problems and positioning +- Meta: First release in Steam Workshop under name "Traffic Manager" +- Meta: Released as version 1.0rc + +### TM 0.9.0, 21/04/2015 + +- Fixed: Cars and service stop working +- Fixed: Wrong lane use +- Updated: Manager no longer loads in map editor +- Fixed: Errors on adding additional segment add +- Fixed: Negative numbers for traffic lights +- Added: Thumbnail to workshop page +- Meta: New save id +- Meta: Released as version 0.9b + +### TM 0.8.5, 19/04/2015 + +- Fixed lane merges in Left Hand Traffic maps +- Meta: Versions 0.8.3 and 0.8.4 were skipped + +### TM 0.8.2, 19/04/2015 + +- Updated: Rename 'Manual Control' to 'Manual Traffic Lights' + +### TM 0.8.1, 19/04/2015 + +- Added: Tool - Toggle Traffic Lights +- Updated: No more deleting trains on 'clear traffic' +- Fixed: Null exception timed traffic lights on node upgrade +- Fixed: UI position on different resolutions +- Meta: Released as version 0.8b + +### TM 0.8.0, 19/04/2015 + +- Fixed: Can now load game from pause menu + +### TM 0.7.1, 18/04/2015 + +- Improved: Tuned car wait on non-priority road + +### TM 0.7.0, 18/04/2015 + +- Fixed: Lanes on save/load in Left Hand traffic maps +- Fixed: Lane positions +- Fixed: More save/load fixes +- Fixed: Various timed lights problems +- Fixed: Traffic Manager load on multiple game loads +- Fixed: Multiple saves on one savegame +- Updated: Moved UI button + +### TM 0.6.1, 18/04/2015 + +- Fixed: Cars coming form wrong lane in Left Hand Traffic maps +- Fixed: Not being able to save on new game +- Fixed: Errors when pressing Esc before opening menu +- Meta: Released as version 0.61b + +### TM 0.6.0, 18/04/2015 + +- Added: Tool - Manual Control (traffic lights) +- Added: Tool - Priority Signs +- Added: Tool - Toggle Pedestrian Crossings +- Added: Tool - Clear Traffic +- Added: Tool - Vehicle Restrictions (Transport, Service, Cargo or Car) +- Added: Option - No Despawn (thanks cope) +- Added: Custom Pathfinder +- Added: Settings stored in save game +- Updated: Compatible with C:SL 1.0.7c +- Updated: Custom traffic light steps can be timed +- Updated: Custom Road AI +- Updated: Better traffic light UI +- Meta: Using cities-skylines-detour by cope (sschoener on github) + +### TPP 1.2.0, 14/04/2015 + +- Added: Realistic driving speeds +- Added: More roads +- Fixed: Bug in mod options screen +- Updated: Compatible with C:SL 1.0.7c + +### C:SL 1.0.7c, 07/04/2015 + +- Fixed: Lots of stuff + +### TPP 1.2.1, 06/04/2015 + +- Fixed: Bugs introduced by previous version + +### TPP 1.1.8, 05/04/2015 + +- Added: Support for left-hand traffic maps +- Improved: Compatibility with Fine Road Heights mod +- Fixed: Various bugs +- Updated: Compatible with C:SL 1.0.7 + +### C:SL 1.0.7, 27/03/2015 + +- Fixed: Lots of stuff + +### TPP 1.1.7, 26/03/2015 + +- Fixed: Various bugs + +### TPP 1.1.6, 26/03/2015 + +- Fixed: Various bugs + +### TPP 1.1.5, 26/03/2015 + +- Fixed: Various bugs + +### TPP 1.1.4, 26/03/2015 + +- Fixed: Various bugs + +### TPP 1.1.3, 26/03/2015 + +- Fixed: Various bugs + +### TPP 1.1.2, 26/03/2015 + +- Fixed: Various bugs + +### TPP 1.1.1, 25/03/2015 + +- Added: Option to disable updates from previous version +- Fixed: Bugs from previous version + +### TPP 1.1.0, 25/03/2015 + +- Added: More roads & associated features +- Added: Option to disable pedestrians in middle lane +- Meta: Renamed to "Traffic++" + +### TPP 1.0.5, 24/03/2015 + +- Fixed: Options panel bug + +### TPP 1.0.4, 23/03/2015 + +- Fixed: Various bugs + +### TM 0.5.0, 22/03/2015 + +- Added: Tool - Traffic Lights Editor +- Added: Tool - Lane Changer +- Added: Toolbar for mod features +- Added: Log file +- Updated: Compatible with C:SL 1.0.6 + +### TPP 1.0.3, 20/03/2015 + +- Added: Option to toggle which vehicles can use pedestrian roads +- Added: Ghost mode to disable most of mod but still allow maps to load +- Updated: Compatible with C:SL 1.0.6 +- Meta: First working implementation of vehicle restrictions + +### C:SL 1.0.6, 19/03/2015 + +- Fixed: Lots of stuff + +### TPP 1.0.2, 18/03/2015 + +- Improved: Some interface improvements + +### TPP 1.0.1, 17/03/2015 + +- Fixed: Mod could only be used once per gaming session +- Fixed: Various bugs + +### TPP 1.0.0, 16/03/2015 + +- Added: Pedestrian zoneable road +- Meta: Traffic++ project starts, but under its original name "CSL-Traffic" +- Meta: GitHub Repository: [joaofarias/csl-traffic](https://github.com/joaofarias/csl-traffic) +- Meta: Steam Workshop + +### TM 0.4.0, 14/03/2015 + +- Meta: Traffic Manager project starts +- Meta: Developer CBeTHaX (github user SvetlozarValchev) +- Meta: GitHub Repository: [SvetlozarValchev/Skylines-Traffic-Manager](https://github.com/SvetlozarValchev/Skylines-Traffic-Manager) +- Meta: Steam Workshop: Traffic Manager + +### C:SL 1.0, 10/03/2015 + +- New: Cities: Skylines 1.0 official release diff --git a/TLM/.editorconfig b/TLM/.editorconfig new file mode 100644 index 000000000..9e11c451a --- /dev/null +++ b/TLM/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.{cs,vb}] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.cs] +# csharp_new_line_before_open_brace = methods diff --git a/TLM/.vs/config/applicationhost.config b/TLM/.vs/config/applicationhost.config deleted file mode 100644 index 5a8a09d3f..000000000 --- a/TLM/.vs/config/applicationhost.config +++ /dev/null @@ -1,1028 +0,0 @@ - - - - - - - - -
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
-
-
-
-
-
- -
-
-
-
-
- -
-
-
- -
-
- -
-
- -
-
-
- - -
-
-
-
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/TLM/CSUtil.Commons/CSUtil.Commons.csproj b/TLM/CSUtil.Commons/CSUtil.Commons.csproj index f51ceffd9..6e17d5df9 100644 --- a/TLM/CSUtil.Commons/CSUtil.Commons.csproj +++ b/TLM/CSUtil.Commons/CSUtil.Commons.csproj @@ -12,6 +12,7 @@ v3.5 512 + latest true diff --git a/TLM/TLM/Custom/AI/CustomAmbulanceAI.cs b/TLM/TLM/Custom/AI/CustomAmbulanceAI.cs index a71b85999..c1be31520 100644 --- a/TLM/TLM/Custom/AI/CustomAmbulanceAI.cs +++ b/TLM/TLM/Custom/AI/CustomAmbulanceAI.cs @@ -1,81 +1,75 @@ -using ColossalFramework; -using TrafficManager.RedirectionFramework.Attributes; -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 TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using Manager.Impl; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(AmbulanceAI))] - public class CustomAmbulanceAI : CarAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { - ExtVehicleType vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0 ? ExtVehicleType.Emergency : ExtVehicleType.Service); + [TargetType(typeof(AmbulanceAI))] + public class CustomAmbulanceAI : CarAI { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + ExtVehicleType vehicleType = ExtVehicleManager.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 = 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; + 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 = 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.CustomCreatePath(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; - } - } -} + if (CustomPathManager._instance.CustomCreatePath(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; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomBusAI.cs b/TLM/TLM/Custom/AI/CustomBusAI.cs index 3bcc2ed49..ae897d40a 100644 --- a/TLM/TLM/Custom/AI/CustomBusAI.cs +++ b/TLM/TLM/Custom/AI/CustomBusAI.cs @@ -1,77 +1,71 @@ -using ColossalFramework; -using TrafficManager.RedirectionFramework.Attributes; -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 TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(BusAI))] - public class CustomBusAI : CarAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { - 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 = 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; + [TargetType(typeof(BusAI))] + public class CustomBusAI : CarAI { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + 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 = 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.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - // NON-STOCK CODE END + if (CustomPathManager._instance.CustomCreatePath(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; - } - } -} + 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; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomCarAI.cs b/TLM/TLM/Custom/AI/CustomCarAI.cs index 984623987..4ce38a029 100644 --- a/TLM/TLM/Custom/AI/CustomCarAI.cs +++ b/TLM/TLM/Custom/AI/CustomCarAI.cs @@ -1,609 +1,610 @@ #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; -using TrafficManager.Traffic.Enums; -using TrafficManager.RedirectionFramework.Attributes; - namespace TrafficManager.Custom.AI { - [TargetType(typeof(CarAI))] - 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. - /// - /// - /// - /// - [RedirectMethod] - public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { + using System; + using System.Runtime.CompilerServices; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Math; + using CSUtil.Commons; + using Custom.PathFinding; + using Manager; + using Manager.Impl; + using RedirectionFramework.Attributes; + using State; + using Traffic.Data; + using UnityEngine; + + [TargetType(typeof(CarAI))] + 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. + /// + /// + /// + /// + [RedirectMethod] + 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; + 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; + 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; - } + // 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}"); + if (debug) + Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Path: {vehicleData.m_path}, mainPathState={mainPathState}"); #endif - IExtVehicleManager extVehicleManager = Constants.ManagerFactory.ExtVehicleManager; - ExtSoftPathState finalPathState = ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); - if (Options.parkingAI && extVehicleManager.ExtVehicles[vehicleId].vehicleType == ExtVehicleType.PassengerCar) { - ushort driverInstanceId = extVehicleManager.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); + IExtVehicleManager extVehicleManager = Constants.ManagerFactory.ExtVehicleManager; + ExtSoftPathState finalPathState = ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); + if (Options.parkingAI && extVehicleManager.ExtVehicles[vehicleId].vehicleType == ExtVehicleType.PassengerCar) { + ushort driverInstanceId = extVehicleManager.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}"); + if (debug) + Log._Debug($"CustomCarAI.CustomSimulationStep({vehicleId}): Applied Parking AI logic. Path: {vehicleData.m_path}, mainPathState={mainPathState}, finalPathState={finalPathState}"); #endif - } + } - switch (finalPathState) { - case ExtSoftPathState.Ready: + 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"); + 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: + 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"); + 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: + 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"); + 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: + 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"); + 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: + 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"); + 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 - IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; - extVehicleMan.UpdateVehiclePosition(vehicleId, ref vehicleData); - - if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) { - extVehicleMan.LogTraffic(vehicleId, ref vehicleData); - } - // 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 - if (VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { - // NON-STOCK CODE END - Singleton.instance.ReleaseVehicle(vehicleId); - } // NON-STOCK CODE - } - } - - [RedirectMethod] - public bool CustomTrySpawn(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; - } - - [RedirectMethod] - public void CustomCalculateSegmentPosition(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position nextNextPosition, - PathUnit.Position nextPosition, uint nextLaneId, byte nextOffset, PathUnit.Position curPosition, uint curLaneId, - byte curOffset, int index, out Vector3 pos, out Vector3 dir, out float maxSpeed) { - var netManager = Singleton.instance; - ushort nextSourceNodeId; - ushort nextTargetNodeId; - if (nextOffset < nextPosition.m_offset) { - nextSourceNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_startNode; - nextTargetNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_endNode; - } else { - nextSourceNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_endNode; - nextTargetNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_startNode; - } - - ushort curTargetNodeId; - if (curOffset == 0) { - curTargetNodeId = netManager.m_segments.m_buffer[(int)curPosition.m_segment].m_startNode; - } else { - curTargetNodeId = netManager.m_segments.m_buffer[(int)curPosition.m_segment].m_endNode; - } + // 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 + IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; + extVehicleMan.UpdateVehiclePosition(vehicleId, ref vehicleData); + + if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) { + extVehicleMan.LogTraffic(vehicleId, ref vehicleData); + } + // 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 + if (VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { + // NON-STOCK CODE END + Singleton.instance.ReleaseVehicle(vehicleId); + } // NON-STOCK CODE + } + } + + [RedirectMethod] + public bool CustomTrySpawn(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; + } + + [RedirectMethod] + public void CustomCalculateSegmentPosition(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position nextNextPosition, + PathUnit.Position nextPosition, uint nextLaneId, byte nextOffset, PathUnit.Position curPosition, uint curLaneId, + byte curOffset, int index, out Vector3 pos, out Vector3 dir, out float maxSpeed) { + var netManager = Singleton.instance; + ushort nextSourceNodeId; + ushort nextTargetNodeId; + if (nextOffset < nextPosition.m_offset) { + nextSourceNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_startNode; + nextTargetNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_endNode; + } else { + nextSourceNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_endNode; + nextTargetNodeId = netManager.m_segments.m_buffer[nextPosition.m_segment].m_startNode; + } + + ushort curTargetNodeId; + if (curOffset == 0) { + curTargetNodeId = netManager.m_segments.m_buffer[(int)curPosition.m_segment].m_startNode; + } else { + curTargetNodeId = netManager.m_segments.m_buffer[(int)curPosition.m_segment].m_endNode; + } #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[21] && (GlobalConfig.Instance.Debug.NodeId <= 0 || curTargetNodeId == 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" + - $"\tcurPosition.m_segment={curPosition.m_segment}, curPosition.m_offset={curPosition.m_offset}\n" + - $"\tnextPosition.m_segment={nextPosition.m_segment}, nextPosition.m_offset={nextPosition.m_offset}\n" + - $"\tnextNextPosition.m_segment={nextNextPosition.m_segment}, nextNextPosition.m_offset={nextNextPosition.m_offset}\n" + - $"\tcurLaneId={curLaneId}, curOffset={curOffset}\n" + - $"\tnextLaneId={nextLaneId}, nextOffset={nextOffset}\n" + - $"\tnextSourceNodeId={nextSourceNodeId}, nextTargetNodeId={nextTargetNodeId}\n" + - $"\tcurTargetNodeId={curTargetNodeId}, curTargetNodeId={curTargetNodeId}\n" + - $"\tindex={index}"); - } + bool debug = GlobalConfig.Instance.Debug.Switches[21] + && (GlobalConfig.Instance.Debug.NodeId <= 0 + || curTargetNodeId == GlobalConfig.Instance.Debug.NodeId) + && (GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.None + || GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.RoadVehicle) + && (GlobalConfig.Instance.Debug.VehicleId == 0 + || GlobalConfig.Instance.Debug.VehicleId == vehicleId); + + if (debug) { + Log._Debug($"CustomCarAI.CustomCalculateSegmentPosition({vehicleId}) called.\n" + + $"\tcurPosition.m_segment={curPosition.m_segment}, curPosition.m_offset={curPosition.m_offset}\n" + + $"\tnextPosition.m_segment={nextPosition.m_segment}, nextPosition.m_offset={nextPosition.m_offset}\n" + + $"\tnextNextPosition.m_segment={nextNextPosition.m_segment}, nextNextPosition.m_offset={nextNextPosition.m_offset}\n" + + $"\tcurLaneId={curLaneId}, curOffset={curOffset}\n" + + $"\tnextLaneId={nextLaneId}, nextOffset={nextOffset}\n" + + $"\tnextSourceNodeId={nextSourceNodeId}, nextTargetNodeId={nextTargetNodeId}\n" + + $"\tcurTargetNodeId={curTargetNodeId}, curTargetNodeId={curTargetNodeId}\n" + + $"\tindex={index}"); + } #endif - Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData(); - Vector3 lastFrameVehiclePos = lastFrameData.m_position; - float sqrVelocity = lastFrameData.m_velocity.sqrMagnitude; - var prevSegmentInfo = netManager.m_segments.m_buffer[nextPosition.m_segment].Info; - netManager.m_lanes.m_buffer[nextLaneId].CalculatePositionAndDirection(nextOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, 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[curLaneId].CalculatePosition(curOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - //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 ( - nextSourceNodeId == curTargetNodeId && - withinBrakingDistance - ) { - // NON-STOCK CODE START (stock code replaced) - if ( - !VehicleBehaviorManager.Instance.MayChangeSegment(vehicleId, ref vehicleData, sqrVelocity, ref curPosition, ref netManager.m_segments.m_buffer[curPosition.m_segment], curTargetNodeId, curLaneId, ref nextPosition, nextSourceNodeId, ref netManager.m_nodes.m_buffer[nextSourceNodeId], nextLaneId, ref nextNextPosition, nextTargetNodeId) - ) { // NON-STOCK CODE - maxSpeed = 0; - return; - } else { - ExtVehicleManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/); - } - // NON-STOCK CODE END - } - - if (prevSegmentInfo.m_lanes != null && prevSegmentInfo.m_lanes.Length > nextPosition.m_lane) { - // NON-STOCK CODE START - float laneSpeedLimit = 1f; - - if (!Options.customSpeedLimitsEnabled) { - laneSpeedLimit = prevSegmentInfo.m_lanes[nextPosition.m_lane].m_speedLimit; - } else { - laneSpeedLimit = Constants.ManagerFactory.SpeedLimitManager.GetLockFreeGameSpeedLimit(nextPosition.m_segment, nextPosition.m_lane, nextLaneId, prevSegmentInfo.m_lanes[nextPosition.m_lane]); - } - - // NON-STOCK CODE END - maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, laneSpeedLimit, netManager.m_lanes.m_buffer[nextLaneId].m_curve); - } else { - maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f); - } - - // NON-STOCK CODE START (stock code replaced) - maxSpeed = Constants.ManagerFactory.VehicleBehaviorManager.CalcMaxSpeed(vehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId], this.m_info, nextPosition, ref netManager.m_segments.m_buffer[nextPosition.m_segment], pos, maxSpeed, false); - // NON-STOCK CODE END - } - - [RedirectMethod] - public void CustomCalculateSegmentPosition(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; - var segmentInfo = netManager.m_segments.m_buffer[position.m_segment].Info; - netManager.m_lanes.m_buffer[laneId].CalculatePositionAndDirection(offset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); - - 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 Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId], this.m_info, position, ref netManager.m_segments.m_buffer[position.m_segment], pos, maxSpeed, false); - // NON-STOCK CODE END - } - - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData(); + Vector3 lastFrameVehiclePos = lastFrameData.m_position; + float sqrVelocity = lastFrameData.m_velocity.sqrMagnitude; + var prevSegmentInfo = netManager.m_segments.m_buffer[nextPosition.m_segment].Info; + netManager.m_lanes.m_buffer[nextLaneId].CalculatePositionAndDirection(nextOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, 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[curLaneId].CalculatePosition(curOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + //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 ( + nextSourceNodeId == curTargetNodeId && + withinBrakingDistance + ) { + // NON-STOCK CODE START (stock code replaced) + if ( + !VehicleBehaviorManager.Instance.MayChangeSegment(vehicleId, ref vehicleData, sqrVelocity, ref curPosition, ref netManager.m_segments.m_buffer[curPosition.m_segment], curTargetNodeId, curLaneId, ref nextPosition, nextSourceNodeId, ref netManager.m_nodes.m_buffer[nextSourceNodeId], nextLaneId, ref nextNextPosition, nextTargetNodeId) + ) { // NON-STOCK CODE + maxSpeed = 0; + return; + } else { + ExtVehicleManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/); + } + // NON-STOCK CODE END + } + + if (prevSegmentInfo.m_lanes != null && prevSegmentInfo.m_lanes.Length > nextPosition.m_lane) { + // NON-STOCK CODE START + float laneSpeedLimit = 1f; + + if (!Options.customSpeedLimitsEnabled) { + laneSpeedLimit = prevSegmentInfo.m_lanes[nextPosition.m_lane].m_speedLimit; + } else { + laneSpeedLimit = Constants.ManagerFactory.SpeedLimitManager.GetLockFreeGameSpeedLimit(nextPosition.m_segment, nextPosition.m_lane, nextLaneId, prevSegmentInfo.m_lanes[nextPosition.m_lane]); + } + + // NON-STOCK CODE END + maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, laneSpeedLimit, netManager.m_lanes.m_buffer[nextLaneId].m_curve); + } else { + maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f); + } + + // NON-STOCK CODE START (stock code replaced) + maxSpeed = Constants.ManagerFactory.VehicleBehaviorManager.CalcMaxSpeed(vehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId], this.m_info, nextPosition, ref netManager.m_segments.m_buffer[nextPosition.m_segment], pos, maxSpeed, false); + // NON-STOCK CODE END + } + + [RedirectMethod] + public void CustomCalculateSegmentPosition(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; + var segmentInfo = netManager.m_segments.m_buffer[position.m_segment].Info; + netManager.m_lanes.m_buffer[laneId].CalculatePositionAndDirection(offset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); + + 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 Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId], this.m_info, position, ref netManager.m_segments.m_buffer[position.m_segment], pos, maxSpeed, false); + // NON-STOCK CODE END + } + + [RedirectMethod] + 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; + 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}"); + 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; + ExtVehicleType vehicleType = ExtVehicleType.None; #if BENCHMARK using (var bm = new Benchmark(null, "OnStartPathFind")) { #endif - vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); - if (vehicleType == ExtVehicleType.None) { + vehicleType = ExtVehicleManager.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!"); + Log.Warning($"CustomCarAI.CustomStartPathFind({vehicleID}): Vehicle {vehicleID} does not have a valid vehicle type!"); #endif - vehicleType = ExtVehicleType.RoadVehicle; - } + 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 = 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.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + 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 = 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.CustomCreatePath(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}"); + 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; - } - - [RedirectMethod] - public static ushort CustomCheckOtherVehicle(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) { - if (otherID == vehicleID || vehicleData.m_leadingVehicle == otherID || vehicleData.m_trailingVehicle == otherID) { - return otherData.m_nextGridVehicle; - } - - VehicleInfo info = otherData.Info; - if (info.m_vehicleType == VehicleInfo.VehicleType.Bicycle) { - return otherData.m_nextGridVehicle; - } - - if (((vehicleData.m_flags | otherData.m_flags) & Vehicle.Flags.Transition) == (Vehicle.Flags)0 && (vehicleData.m_flags & Vehicle.Flags.Underground) != (otherData.m_flags & Vehicle.Flags.Underground)) { - return otherData.m_nextGridVehicle; - } + // 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; + } + + [RedirectMethod] + public static ushort CustomCheckOtherVehicle(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) { + if (otherID == vehicleID || vehicleData.m_leadingVehicle == otherID || vehicleData.m_trailingVehicle == otherID) { + return otherData.m_nextGridVehicle; + } + + VehicleInfo info = otherData.Info; + if (info.m_vehicleType == VehicleInfo.VehicleType.Bicycle) { + return otherData.m_nextGridVehicle; + } + + if (((vehicleData.m_flags | otherData.m_flags) & Vehicle.Flags.Transition) == (Vehicle.Flags)0 && (vehicleData.m_flags & Vehicle.Flags.Underground) != (otherData.m_flags & Vehicle.Flags.Underground)) { + return otherData.m_nextGridVehicle; + } #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[24] && - (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.CustomCheckOtherVehicle({vehicleID}, {otherID}) called."); - } + bool debug = GlobalConfig.Instance.Debug.Switches[24] && + (GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.None + || GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.RoadVehicle) + && (GlobalConfig.Instance.Debug.VehicleId == 0 + || GlobalConfig.Instance.Debug.VehicleId == vehicleID); + + if (debug) { + Log._Debug($"CustomCarAI.CustomCheckOtherVehicle({vehicleID}, {otherID}) called."); + } #endif - Vector3 otherSegMin; - Vector3 otherSegMax; - if (lodPhysics >= 2) { - otherSegMin = otherData.m_segment.Min(); - otherSegMax = otherData.m_segment.Max(); - } else { - otherSegMin = Vector3.Min(otherData.m_segment.Min(), otherData.m_targetPos3); - otherSegMax = Vector3.Max(otherData.m_segment.Max(), otherData.m_targetPos3); - } - - if ( - min.x >= otherSegMax.x + 2f && - min.y >= otherSegMax.y + 2f && - min.z >= otherSegMax.z + 2f && - otherSegMin.x >= max.x + 2f && - otherSegMin.y >= max.y + 2f && - otherSegMin.z >= max.z + 2f - ) { - return otherData.m_nextGridVehicle; - } - - Vehicle.Frame otherFrameData = otherData.GetLastFrameData(); - if (lodPhysics < 2) { - float u = default(float); - float v = default(float); - float segSqrDist = vehicleData.m_segment.DistanceSqr(otherData.m_segment, out u, out v); - if (segSqrDist < 4f) { - Vector3 vehPos = vehicleData.m_segment.Position(0.5f); - Vector3 otherPos = otherData.m_segment.Position(0.5f); - Vector3 vehBounds = vehicleData.m_segment.b - vehicleData.m_segment.a; - if (Vector3.Dot(vehBounds, vehPos - otherPos) < 0f) { - collisionPush -= vehBounds.normalized * (0.1f - segSqrDist * 0.025f); - } else { - collisionPush += vehBounds.normalized * (0.1f - segSqrDist * 0.025f); - } - blocked = true; - } - } - - float vehVelocity = frameData.m_velocity.magnitude + 0.01f; - float otherVehVelocity = otherFrameData.m_velocity.magnitude; - float otherBreakingDist = otherVehVelocity * (0.5f + 0.5f * otherVehVelocity / info.m_braking) + Mathf.Min(1f, otherVehVelocity); - otherVehVelocity += 0.01f; - float prevLength = 0f; - Vector3 prevTargetPos = vehicleData.m_segment.b; - Vector3 prevBounds = vehicleData.m_segment.b - vehicleData.m_segment.a; - int startI = (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram) ? 1 : 0; - for (int i = startI; i < 4; i++) { - Vector3 targetPos = vehicleData.GetTargetPos(i); - Vector3 targetPosDiff = targetPos - prevTargetPos; - if (Vector3.Dot(prevBounds, targetPosDiff) > 0f) { - float targetPosDiffLen = targetPosDiff.magnitude; - Segment3 curSegment = new Segment3(prevTargetPos, targetPos); - min = curSegment.Min(); - max = curSegment.Max(); - curSegment.a.y *= 0.5f; - curSegment.b.y *= 0.5f; - - if ( - targetPosDiffLen > 0.01f && - min.x < otherSegMax.x + 2f && - min.y < otherSegMax.y + 2f && - min.z < otherSegMax.z + 2f && - otherSegMin.x < max.x + 2f && - otherSegMin.y < max.y + 2f && - otherSegMin.z < max.z + 2f - ) { - Vector3 otherVehFrontPos = otherData.m_segment.a; - otherVehFrontPos.y *= 0.5f; - float u = default(float); - if (curSegment.DistanceSqr(otherVehFrontPos, out u) < 4f) { - float otherCosAngleToTargetPosDiff = Vector3.Dot(otherFrameData.m_velocity, targetPosDiff) / targetPosDiffLen; - float uDist = prevLength + targetPosDiffLen * u; - if (uDist >= 0.01f) { - uDist -= otherCosAngleToTargetPosDiff + 3f; - float speed = Mathf.Max(0f, CustomCarAI.CalculateMaxSpeed(uDist, otherCosAngleToTargetPosDiff, maxBraking)); - if (speed < 0.01f) { - blocked = true; - } - - Vector3 normOtherDir = Vector3.Normalize((Vector3)otherData.m_targetPos0 - otherData.m_segment.a); - float blockFactor = 1.2f - 1f / ((float)(int)vehicleData.m_blockCounter * 0.02f + 0.5f); - if (Vector3.Dot(targetPosDiff, normOtherDir) > blockFactor * targetPosDiffLen) { - maxSpeed = Mathf.Min(maxSpeed, speed); - } - } - break; - } - - if (lodPhysics < 2) { - float totalDist = 0f; - float otherBreakDist = otherBreakingDist; - Vector3 otherFrontPos = otherData.m_segment.b; - Vector3 otherBounds = otherData.m_segment.b - otherData.m_segment.a; - int startOtherTargetPosIndex = (info.m_vehicleType == VehicleInfo.VehicleType.Tram) ? 1 : 0; - bool exitTargetPosLoop = false; - int otherTargetPosIndex = startOtherTargetPosIndex; - while (otherTargetPosIndex < 4 && otherBreakDist > 0.1f) { - Vector3 otherTargetPos; - if (otherData.m_leadingVehicle == 0) { - otherTargetPos = otherData.GetTargetPos(otherTargetPosIndex); - } else { - if (otherTargetPosIndex != startOtherTargetPosIndex) { - break; - } - otherTargetPos = Singleton.instance.m_vehicles.m_buffer[otherData.m_leadingVehicle].m_segment.b; - } - - Vector3 minBreakPos = Vector3.ClampMagnitude(otherTargetPos - otherFrontPos, otherBreakDist); - if (Vector3.Dot(otherBounds, minBreakPos) > 0f) { - otherTargetPos = otherFrontPos + minBreakPos; - float breakPosDist = minBreakPos.magnitude; - otherBreakDist -= breakPosDist; - Segment3 otherVehNextSegment = new Segment3(otherFrontPos, otherTargetPos); - otherVehNextSegment.a.y *= 0.5f; - otherVehNextSegment.b.y *= 0.5f; - if (breakPosDist > 0.01f) { - float otherVehNextSegU = default(float); - float otherVehNextSegV = default(float); - float otherVehNextSegmentDistToCurSegment = (otherID >= vehicleID) - ? curSegment.DistanceSqr(otherVehNextSegment, out otherVehNextSegU, out otherVehNextSegV) - : otherVehNextSegment.DistanceSqr(curSegment, out otherVehNextSegV, out otherVehNextSegU); - if (otherVehNextSegmentDistToCurSegment < 4f) { - float uDist = prevLength + targetPosDiffLen * otherVehNextSegU; - float vDist = totalDist + breakPosDist * otherVehNextSegV + 0.1f; - if (uDist >= 0.01f && uDist * otherVehVelocity > vDist * vehVelocity) { - float otherCosAngleToTargetPosDiff = Vector3.Dot(otherFrameData.m_velocity, targetPosDiff) / targetPosDiffLen; - if (uDist >= 0.01f) { - uDist -= otherCosAngleToTargetPosDiff + 1f + otherData.Info.m_generatedInfo.m_size.z; - float speed = Mathf.Max(0f, CustomCarAI.CalculateMaxSpeed(uDist, otherCosAngleToTargetPosDiff, maxBraking)); - if (speed < 0.01f) { - blocked = true; - } - maxSpeed = Mathf.Min(maxSpeed, speed); - } - } - exitTargetPosLoop = true; - break; - } - } - otherBounds = minBreakPos; - totalDist += breakPosDist; - otherFrontPos = otherTargetPos; - } - otherTargetPosIndex++; - } - if (exitTargetPosLoop) { - break; - } - } - } - prevBounds = targetPosDiff; - prevLength += targetPosDiffLen; - prevTargetPos = targetPos; - } - } - - return otherData.m_nextGridVehicle; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - [RedirectReverse] - private static bool CheckOverlap(Segment3 segment, ushort ignoreVehicle, float maxVelocity) { - Log.Error("CustomCarAI.CheckOverlap called"); - return false; - } - - /*[MethodImpl(MethodImplOptions.NoInlining)] - [RedirectReverse] - 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)] - [RedirectReverse] - 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; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - [RedirectReverse] - private static float CalculateMaxSpeed(float targetDistance, float targetSpeed, float maxBraking) { - Log.Error("CustomCarAI.CalculateMaxSpeed called"); - return 0f; - } - } + Vector3 otherSegMin; + Vector3 otherSegMax; + if (lodPhysics >= 2) { + otherSegMin = otherData.m_segment.Min(); + otherSegMax = otherData.m_segment.Max(); + } else { + otherSegMin = Vector3.Min(otherData.m_segment.Min(), otherData.m_targetPos3); + otherSegMax = Vector3.Max(otherData.m_segment.Max(), otherData.m_targetPos3); + } + + if ( + min.x >= otherSegMax.x + 2f && + min.y >= otherSegMax.y + 2f && + min.z >= otherSegMax.z + 2f && + otherSegMin.x >= max.x + 2f && + otherSegMin.y >= max.y + 2f && + otherSegMin.z >= max.z + 2f + ) { + return otherData.m_nextGridVehicle; + } + + Vehicle.Frame otherFrameData = otherData.GetLastFrameData(); + if (lodPhysics < 2) { + float u = default(float); + float v = default(float); + float segSqrDist = vehicleData.m_segment.DistanceSqr(otherData.m_segment, out u, out v); + if (segSqrDist < 4f) { + Vector3 vehPos = vehicleData.m_segment.Position(0.5f); + Vector3 otherPos = otherData.m_segment.Position(0.5f); + Vector3 vehBounds = vehicleData.m_segment.b - vehicleData.m_segment.a; + if (Vector3.Dot(vehBounds, vehPos - otherPos) < 0f) { + collisionPush -= vehBounds.normalized * (0.1f - segSqrDist * 0.025f); + } else { + collisionPush += vehBounds.normalized * (0.1f - segSqrDist * 0.025f); + } + blocked = true; + } + } + + float vehVelocity = frameData.m_velocity.magnitude + 0.01f; + float otherVehVelocity = otherFrameData.m_velocity.magnitude; + float otherBreakingDist = otherVehVelocity * (0.5f + 0.5f * otherVehVelocity / info.m_braking) + Mathf.Min(1f, otherVehVelocity); + otherVehVelocity += 0.01f; + float prevLength = 0f; + Vector3 prevTargetPos = vehicleData.m_segment.b; + Vector3 prevBounds = vehicleData.m_segment.b - vehicleData.m_segment.a; + int startI = (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Tram) ? 1 : 0; + for (int i = startI; i < 4; i++) { + Vector3 targetPos = vehicleData.GetTargetPos(i); + Vector3 targetPosDiff = targetPos - prevTargetPos; + if (Vector3.Dot(prevBounds, targetPosDiff) > 0f) { + float targetPosDiffLen = targetPosDiff.magnitude; + Segment3 curSegment = new Segment3(prevTargetPos, targetPos); + min = curSegment.Min(); + max = curSegment.Max(); + curSegment.a.y *= 0.5f; + curSegment.b.y *= 0.5f; + + if ( + targetPosDiffLen > 0.01f && + min.x < otherSegMax.x + 2f && + min.y < otherSegMax.y + 2f && + min.z < otherSegMax.z + 2f && + otherSegMin.x < max.x + 2f && + otherSegMin.y < max.y + 2f && + otherSegMin.z < max.z + 2f + ) { + Vector3 otherVehFrontPos = otherData.m_segment.a; + otherVehFrontPos.y *= 0.5f; + float u = default(float); + if (curSegment.DistanceSqr(otherVehFrontPos, out u) < 4f) { + float otherCosAngleToTargetPosDiff = Vector3.Dot(otherFrameData.m_velocity, targetPosDiff) / targetPosDiffLen; + float uDist = prevLength + targetPosDiffLen * u; + if (uDist >= 0.01f) { + uDist -= otherCosAngleToTargetPosDiff + 3f; + float speed = Mathf.Max(0f, CustomCarAI.CalculateMaxSpeed(uDist, otherCosAngleToTargetPosDiff, maxBraking)); + if (speed < 0.01f) { + blocked = true; + } + + Vector3 normOtherDir = Vector3.Normalize((Vector3)otherData.m_targetPos0 - otherData.m_segment.a); + float blockFactor = 1.2f - 1f / ((float)(int)vehicleData.m_blockCounter * 0.02f + 0.5f); + if (Vector3.Dot(targetPosDiff, normOtherDir) > blockFactor * targetPosDiffLen) { + maxSpeed = Mathf.Min(maxSpeed, speed); + } + } + break; + } + + if (lodPhysics < 2) { + float totalDist = 0f; + float otherBreakDist = otherBreakingDist; + Vector3 otherFrontPos = otherData.m_segment.b; + Vector3 otherBounds = otherData.m_segment.b - otherData.m_segment.a; + int startOtherTargetPosIndex = (info.m_vehicleType == VehicleInfo.VehicleType.Tram) ? 1 : 0; + bool exitTargetPosLoop = false; + int otherTargetPosIndex = startOtherTargetPosIndex; + while (otherTargetPosIndex < 4 && otherBreakDist > 0.1f) { + Vector3 otherTargetPos; + if (otherData.m_leadingVehicle == 0) { + otherTargetPos = otherData.GetTargetPos(otherTargetPosIndex); + } else { + if (otherTargetPosIndex != startOtherTargetPosIndex) { + break; + } + otherTargetPos = Singleton.instance.m_vehicles.m_buffer[otherData.m_leadingVehicle].m_segment.b; + } + + Vector3 minBreakPos = Vector3.ClampMagnitude(otherTargetPos - otherFrontPos, otherBreakDist); + if (Vector3.Dot(otherBounds, minBreakPos) > 0f) { + otherTargetPos = otherFrontPos + minBreakPos; + float breakPosDist = minBreakPos.magnitude; + otherBreakDist -= breakPosDist; + Segment3 otherVehNextSegment = new Segment3(otherFrontPos, otherTargetPos); + otherVehNextSegment.a.y *= 0.5f; + otherVehNextSegment.b.y *= 0.5f; + if (breakPosDist > 0.01f) { + float otherVehNextSegU = default(float); + float otherVehNextSegV = default(float); + float otherVehNextSegmentDistToCurSegment = (otherID >= vehicleID) + ? curSegment.DistanceSqr(otherVehNextSegment, out otherVehNextSegU, out otherVehNextSegV) + : otherVehNextSegment.DistanceSqr(curSegment, out otherVehNextSegV, out otherVehNextSegU); + if (otherVehNextSegmentDistToCurSegment < 4f) { + float uDist = prevLength + targetPosDiffLen * otherVehNextSegU; + float vDist = totalDist + breakPosDist * otherVehNextSegV + 0.1f; + if (uDist >= 0.01f && uDist * otherVehVelocity > vDist * vehVelocity) { + float otherCosAngleToTargetPosDiff = Vector3.Dot(otherFrameData.m_velocity, targetPosDiff) / targetPosDiffLen; + if (uDist >= 0.01f) { + uDist -= otherCosAngleToTargetPosDiff + 1f + otherData.Info.m_generatedInfo.m_size.z; + float speed = Mathf.Max(0f, CustomCarAI.CalculateMaxSpeed(uDist, otherCosAngleToTargetPosDiff, maxBraking)); + if (speed < 0.01f) { + blocked = true; + } + maxSpeed = Mathf.Min(maxSpeed, speed); + } + } + exitTargetPosLoop = true; + break; + } + } + otherBounds = minBreakPos; + totalDist += breakPosDist; + otherFrontPos = otherTargetPos; + } + otherTargetPosIndex++; + } + if (exitTargetPosLoop) { + break; + } + } + } + prevBounds = targetPosDiff; + prevLength += targetPosDiffLen; + prevTargetPos = targetPos; + } + } + + return otherData.m_nextGridVehicle; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [RedirectReverse] + private static bool CheckOverlap(Segment3 segment, ushort ignoreVehicle, float maxVelocity) { + Log.Error("CustomCarAI.CheckOverlap called"); + return false; + } + + /*[MethodImpl(MethodImplOptions.NoInlining)] + [RedirectReverse] + 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)] + [RedirectReverse] + 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; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [RedirectReverse] + private static float CalculateMaxSpeed(float targetDistance, float targetSpeed, float maxBraking) { + Log.Error("CustomCarAI.CalculateMaxSpeed called"); + return 0f; + } + } } \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomCargoTruckAI.cs b/TLM/TLM/Custom/AI/CustomCargoTruckAI.cs index 0e5fa8832..7f8864e07 100644 --- a/TLM/TLM/Custom/AI/CustomCargoTruckAI.cs +++ b/TLM/TLM/Custom/AI/CustomCargoTruckAI.cs @@ -1,149 +1,143 @@ -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; -using TrafficManager.Traffic.Enums; -using TrafficManager.RedirectionFramework.Attributes; -using System.Runtime.CompilerServices; - namespace TrafficManager.Custom.AI { - [TargetType(typeof(CargoTruckAI))] - public class CustomCargoTruckAI : CarAI { - [RedirectMethod] - 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; - } + using System.Runtime.CompilerServices; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using Custom.PathFinding; + using Manager.Impl; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; + + [TargetType(typeof(CargoTruckAI))] + public class CustomCargoTruckAI : CarAI { + [RedirectMethod] + 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); - } - } + 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); - } + base.SimulationStep(vehicleId, ref vehicleData, physicsLodRefPos); + } - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + [RedirectMethod] + 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}"); + //Log._Debug($"CustomCargoTruckAI.CustomStartPathFind called for vehicle {vehicleID}"); #endif - ExtVehicleType vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); - if (vehicleType == ExtVehicleType.None) { + ExtVehicleType vehicleType = ExtVehicleManager.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!"); + Log.Warning($"CustomCargoTruck.CustomStartPathFind: Vehicle {vehicleID} does not have a valid vehicle type!"); #endif - } + } - if ((vehicleData.m_flags & (Vehicle.Flags.TransferToSource | Vehicle.Flags.GoingBack)) != 0) { - return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget); - } + 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 = 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; + 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 = 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.CustomCreatePath(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; - } + if (pathMan.CustomCreatePath(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; + } - [MethodImpl(MethodImplOptions.NoInlining)] - [RedirectReverse] - private void RemoveOffers(ushort vehicleId, ref Vehicle data) { - Log.Error("CustomCargoTruckAI.RemoveOffers called"); - } - } -} + [MethodImpl(MethodImplOptions.NoInlining)] + [RedirectReverse] + private void RemoveOffers(ushort vehicleId, ref Vehicle data) { + Log.Error("CustomCargoTruckAI.RemoveOffers called"); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomFireTruckAI.cs b/TLM/TLM/Custom/AI/CustomFireTruckAI.cs index c32d788f5..c3b444775 100644 --- a/TLM/TLM/Custom/AI/CustomFireTruckAI.cs +++ b/TLM/TLM/Custom/AI/CustomFireTruckAI.cs @@ -1,91 +1,84 @@ -using ColossalFramework; -using CSUtil.Commons.Benchmark; -using TrafficManager.RedirectionFramework.Attributes; -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 TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using Manager.Impl; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(FireTruckAI))] - public class CustomFireTruckAI : CarAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + [TargetType(typeof(FireTruckAI))] + public class CustomFireTruckAI : CarAI { + [RedirectMethod] + 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}"); + //Log._Debug($"CustomFireTruckAI.CustomStartPathFind called for vehicle {vehicleID}"); #endif - ExtVehicleType vehicleType = ExtVehicleType.None; + ExtVehicleType vehicleType = ExtVehicleType.None; #if BENCHMARK using (var bm = new Benchmark(null, "OnStartPathFind")) { #endif - vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0 ? ExtVehicleType.Emergency : ExtVehicleType.Service); + vehicleType = ExtVehicleManager.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 = 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; + 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 = 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.CustomCreatePath(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; - } - } -} + if (CustomPathManager._instance.CustomCreatePath(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; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomHumanAI.cs b/TLM/TLM/Custom/AI/CustomHumanAI.cs index 5b318a197..833a36b2f 100644 --- a/TLM/TLM/Custom/AI/CustomHumanAI.cs +++ b/TLM/TLM/Custom/AI/CustomHumanAI.cs @@ -20,6 +20,9 @@ using TrafficManager.RedirectionFramework.Attributes; namespace TrafficManager.Custom.AI { + using API.Traffic.Enums; + using API.TrafficLight; + [TargetType(typeof(HumanAI))] public class CustomHumanAI : CitizenAI { [RedirectMethod] diff --git a/TLM/TLM/Custom/AI/CustomPoliceCarAI.cs b/TLM/TLM/Custom/AI/CustomPoliceCarAI.cs index 0abcc1fd5..dd6a13dbd 100644 --- a/TLM/TLM/Custom/AI/CustomPoliceCarAI.cs +++ b/TLM/TLM/Custom/AI/CustomPoliceCarAI.cs @@ -1,82 +1,75 @@ -using ColossalFramework; -using CSUtil.Commons.Benchmark; -using TrafficManager.RedirectionFramework.Attributes; -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 TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using Manager.Impl; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(PoliceCarAI))] - public class CustomPoliceCarAI : CarAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { - ExtVehicleType vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0 ? ExtVehicleType.Emergency : ExtVehicleType.Service); + [TargetType(typeof(PoliceCarAI))] + public class CustomPoliceCarAI : CarAI { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + ExtVehicleType vehicleType = ExtVehicleManager.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 = 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; + 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 = 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.CustomCreatePath(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; - } + if (CustomPathManager._instance.CustomCreatePath(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; + } - } -} + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomPostVanAI.cs b/TLM/TLM/Custom/AI/CustomPostVanAI.cs index 89f76c79f..5f7c073e6 100644 --- a/TLM/TLM/Custom/AI/CustomPostVanAI.cs +++ b/TLM/TLM/Custom/AI/CustomPostVanAI.cs @@ -1,119 +1,114 @@ -using ColossalFramework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Custom.PathFinding; -using TrafficManager.RedirectionFramework.Attributes; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(PostVanAI))] - public class CustomPostVanAI : CarAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { - if (vehicleData.m_transferType == (byte)TransferManager.TransferReason.Mail) { - return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget); - } + [TargetType(typeof(PostVanAI))] + public class CustomPostVanAI : CarAI { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + if (vehicleData.m_transferType == (byte)TransferManager.TransferReason.Mail) { + return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget); + } - if ((vehicleData.m_flags & (Vehicle.Flags.TransferToSource | Vehicle.Flags.GoingBack)) != 0) { - return base.StartPathFind(vehicleID, ref vehicleData, startPos, endPos, startBothWays, endBothWays, undergroundTarget); - } + 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)) != (Vehicle.Flags)0; - PathUnit.Position startPosA = default(PathUnit.Position); - PathUnit.Position startPosB = default(PathUnit.Position); - float startDistSqrA = default(float); - float startDistSqrB = default(float); + bool allowUnderground = (vehicleData.m_flags & (Vehicle.Flags.Underground | Vehicle.Flags.Transition)) != (Vehicle.Flags)0; + PathUnit.Position startPosA = default(PathUnit.Position); + PathUnit.Position startPosB = default(PathUnit.Position); + float startDistSqrA = default(float); + float startDistSqrB = default(float); - // try to find road start position - 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); + // try to find road start position + 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); - // try to find other start position (plane, train, ship) - PathUnit.Position altStartPosA = default(PathUnit.Position); - PathUnit.Position altStartPosB = default(PathUnit.Position); - float altStartDistSqrA = default(float); - float altStartDistSqrB = default(float); - if (PathManager.FindPathPosition(startPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, allowUnderground, false, 32f, out altStartPosA, out altStartPosB, out altStartDistSqrA, out altStartDistSqrB)) { - if (!startPosFound || (altStartDistSqrA < startDistSqrA && (Mathf.Abs(startPos.x) > 4800f || Mathf.Abs(startPos.z) > 4800f))) { - startPosA = altStartPosA; - startPosB = altStartPosB; - startDistSqrA = altStartDistSqrA; - startDistSqrB = altStartDistSqrB; - } - startPosFound = true; - } + // try to find other start position (plane, train, ship) + PathUnit.Position altStartPosA = default(PathUnit.Position); + PathUnit.Position altStartPosB = default(PathUnit.Position); + float altStartDistSqrA = default(float); + float altStartDistSqrB = default(float); + if (PathManager.FindPathPosition(startPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, allowUnderground, false, 32f, out altStartPosA, out altStartPosB, out altStartDistSqrA, out altStartDistSqrB)) { + if (!startPosFound || (altStartDistSqrA < startDistSqrA && (Mathf.Abs(startPos.x) > 4800f || Mathf.Abs(startPos.z) > 4800f))) { + startPosA = altStartPosA; + startPosB = altStartPosB; + startDistSqrA = altStartDistSqrA; + startDistSqrB = altStartDistSqrB; + } + startPosFound = true; + } - PathUnit.Position endPosA = default(PathUnit.Position); - PathUnit.Position endPosB = default(PathUnit.Position); - float endDistSqrA = default(float); - float endDistSqrB = default(float); + PathUnit.Position endPosA = default(PathUnit.Position); + PathUnit.Position endPosB = default(PathUnit.Position); + float endDistSqrA = default(float); + float endDistSqrB = default(float); - // try to find road end position - bool endPosFound = PathManager.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); + // try to find road end position + bool endPosFound = PathManager.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); - // try to find other end position (plane, train, ship) - PathUnit.Position altEndPosA = default(PathUnit.Position); - PathUnit.Position altEndPosB = default(PathUnit.Position); - float altEndDistSqrA = default(float); - float altEndDistSqrB = default(float); - if (PathManager.FindPathPosition(endPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, undergroundTarget, false, 32f, out altEndPosA, out altEndPosB, out altEndDistSqrA, out altEndDistSqrB)) { - if (!endPosFound || (altEndDistSqrA < endDistSqrA && (Mathf.Abs(endPos.x) > 4800f || Mathf.Abs(endPos.z) > 4800f))) { - endPosA = altEndPosA; - endPosB = altEndPosB; - endDistSqrA = altEndDistSqrA; - endDistSqrB = altEndDistSqrB; - } - endPosFound = true; - } + // try to find other end position (plane, train, ship) + PathUnit.Position altEndPosA = default(PathUnit.Position); + PathUnit.Position altEndPosB = default(PathUnit.Position); + float altEndDistSqrA = default(float); + float altEndDistSqrB = default(float); + if (PathManager.FindPathPosition(endPos, ItemClass.Service.PublicTransport, NetInfo.LaneType.Vehicle, VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane, undergroundTarget, false, 32f, out altEndPosA, out altEndPosB, out altEndDistSqrA, out altEndDistSqrB)) { + if (!endPosFound || (altEndDistSqrA < endDistSqrA && (Mathf.Abs(endPos.x) > 4800f || Mathf.Abs(endPos.z) > 4800f))) { + endPosA = altEndPosA; + endPosB = altEndPosB; + endDistSqrA = altEndDistSqrA; + endDistSqrB = altEndDistSqrB; + } + endPosFound = true; + } - if (startPosFound && endPosFound) { - CustomPathManager pathManager = CustomPathManager._instance; - if (!startBothWays || startDistSqrA < 10f) { - startPosB = default(PathUnit.Position); - } - if (!endBothWays || endDistSqrA < 10f) { - endPosB = default(PathUnit.Position); - } - uint path; + if (startPosFound && endPosFound) { + CustomPathManager pathManager = CustomPathManager._instance; + if (!startBothWays || startDistSqrA < 10f) { + startPosB = default(PathUnit.Position); + } + if (!endBothWays || endDistSqrA < 10f) { + endPosB = default(PathUnit.Position); + } + uint path; - PathCreationArgs args; - args.extPathType = ExtPathType.None; - args.extVehicleType = ExtVehicleType.Service; - 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.CargoVehicle; - args.vehicleTypes = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane; - 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; + PathCreationArgs args; + args.extPathType = ExtPathType.None; + args.extVehicleType = ExtVehicleType.Service; + 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.CargoVehicle; + args.vehicleTypes = VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Ship | VehicleInfo.VehicleType.Plane; + 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 (pathManager.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - if (vehicleData.m_path != 0) { - pathManager.ReleasePath(vehicleData.m_path); - } - vehicleData.m_path = path; - vehicleData.m_flags |= Vehicle.Flags.WaitingPath; - return true; - } - } - return false; - } - } -} + if (pathManager.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { + if (vehicleData.m_path != 0) { + pathManager.ReleasePath(vehicleData.m_path); + } + vehicleData.m_path = path; + vehicleData.m_flags |= Vehicle.Flags.WaitingPath; + return true; + } + } + return false; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomShipAI.cs b/TLM/TLM/Custom/AI/CustomShipAI.cs index ef1b0f0f6..a75397113 100644 --- a/TLM/TLM/Custom/AI/CustomShipAI.cs +++ b/TLM/TLM/Custom/AI/CustomShipAI.cs @@ -1,87 +1,79 @@ -using ColossalFramework; -using CSUtil.Commons; -using CSUtil.Commons.Benchmark; -using TrafficManager.RedirectionFramework.Attributes; -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 TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(ShipAI))] - public class CustomShipAI : ShipAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) { + [TargetType(typeof(ShipAI))] + public class CustomShipAI : ShipAI { + [RedirectMethod] + 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}"); + //Log._Debug($"CustomShipAI.CustomStartPathFind called for vehicle {vehicleID}"); #endif - /// NON-STOCK CODE START /// - ExtVehicleType vehicleType = vehicleData.Info.m_vehicleAI is PassengerShipAI ? ExtVehicleType.PassengerShip : ExtVehicleType.CargoVehicle; - /// NON-STOCK CODE END /// + /// NON-STOCK CODE START /// + ExtVehicleType vehicleType = vehicleData.Info.m_vehicleAI is PassengerShipAI ? ExtVehicleType.PassengerShip : ExtVehicleType.CargoVehicle; + /// 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 = 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; + 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 = 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.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args)) { - // NON-STOCK CODE END + if (CustomPathManager._instance.CustomCreatePath(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; - } + 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; + } - } -} + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomTaxiAI.cs b/TLM/TLM/Custom/AI/CustomTaxiAI.cs index 114c1b885..838e8d6a8 100644 --- a/TLM/TLM/Custom/AI/CustomTaxiAI.cs +++ b/TLM/TLM/Custom/AI/CustomTaxiAI.cs @@ -1,89 +1,83 @@ -using ColossalFramework; -using TrafficManager.RedirectionFramework.Attributes; -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 TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; +namespace TrafficManager.Custom.AI { + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using Custom.PathFinding; + using RedirectionFramework.Attributes; + using Traffic.Data; + using UnityEngine; -namespace TrafficManager.Custom.AI { - [TargetType(typeof(TaxiAI))] - public class CustomTaxiAI : CarAI { - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { - CitizenManager instance = Singleton.instance; - ushort passengerInstanceId = Constants.ManagerFactory.ExtVehicleManager.GetDriverInstanceId(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) && - Constants.ManagerFactory.ExtCitizenInstanceManager.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; + [TargetType(typeof(TaxiAI))] + public class CustomTaxiAI : CarAI { + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget) { + CitizenManager instance = Singleton.instance; + ushort passengerInstanceId = Constants.ManagerFactory.ExtVehicleManager.GetDriverInstanceId(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) && + Constants.ManagerFactory.ExtCitizenInstanceManager.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 = 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; + 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 = 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.CustomCreatePath(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; - } + if (CustomPathManager._instance.CustomCreatePath(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; + } - } -} + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomTrainAI.cs b/TLM/TLM/Custom/AI/CustomTrainAI.cs index 0a730751a..5ed5e0188 100644 --- a/TLM/TLM/Custom/AI/CustomTrainAI.cs +++ b/TLM/TLM/Custom/AI/CustomTrainAI.cs @@ -1,882 +1,883 @@ -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; -using TrafficManager.Traffic.Enums; -using TrafficManager.RedirectionFramework.Attributes; - -namespace TrafficManager.Custom.AI { - [TargetType(typeof(TrainAI))] - public class CustomTrainAI : TrainAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`) - [RedirectMethod] - public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { - IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; - - 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 - extVehicleMan.UpdateVehiclePosition(vehicleId, ref vehicleData); - - if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) { - // Advanced AI traffic measurement - extVehicleMan.LogTraffic(vehicleId, ref vehicleData); - } - // 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 - if (VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { - // NON-STOCK CODE END - Singleton.instance.ReleaseVehicle(vehicleId); - } // NON-STOCK CODE - } - } - - [RedirectMethod] - 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; - UpdatePathTargetPositions(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) { - UpdatePathTargetPositions(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; - } - - if (VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, curPathPos, nextPathPos)) { - // 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; - UpdatePathTargetPositions(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) { - UpdatePathTargetPositions(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; - } - - if (VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, curPathPos, nextPathPos)) { - // 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; - UpdatePathTargetPositions(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; - UpdatePathTargetPositions(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); - } - - [RedirectMethod] - public void CustomCalculateSegmentPosition(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 * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, 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 = 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; - maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, laneSpeedLimit, instance.m_lanes.m_buffer[laneID].m_curve); - } else { - maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1f, 0f); - } - } - - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) { - /// NON-STOCK CODE START /// - ExtVehicleType vehicleType = ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); - if (vehicleType == ExtVehicleType.None) { +namespace TrafficManager.Custom.AI { + using System; + using System.Runtime.CompilerServices; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Math; + using CSUtil.Commons; + using Custom.PathFinding; + using Manager; + using Manager.Impl; + using RedirectionFramework.Attributes; + using State; + using Traffic.Data; + using UnityEngine; + + [TargetType(typeof(TrainAI))] + public class CustomTrainAI : TrainAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`) + [RedirectMethod] + public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { + IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; + + 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 + extVehicleMan.UpdateVehiclePosition(vehicleId, ref vehicleData); + + if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) { + // Advanced AI traffic measurement + extVehicleMan.LogTraffic(vehicleId, ref vehicleData); + } + // 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 + if (VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { + // NON-STOCK CODE END + Singleton.instance.ReleaseVehicle(vehicleId); + } // NON-STOCK CODE + } + } + + [RedirectMethod] + 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; + UpdatePathTargetPositions(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) { + UpdatePathTargetPositions(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; + } + + if (VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, curPathPos, nextPathPos)) { + // 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; + UpdatePathTargetPositions(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) { + UpdatePathTargetPositions(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; + } + + if (VehicleBehaviorManager.Instance.IsSpaceReservationAllowed(transitNodeId, curPathPos, nextPathPos)) { + // 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; + UpdatePathTargetPositions(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; + UpdatePathTargetPositions(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); + } + + [RedirectMethod] + public void CustomCalculateSegmentPosition(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 * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, 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 = 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; + maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, laneSpeedLimit, instance.m_lanes.m_buffer[laneID].m_curve); + } else { + maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1f, 0f); + } + } + + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) { + /// NON-STOCK CODE START /// + ExtVehicleType vehicleType = ExtVehicleManager.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!"); + 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; - } - /// 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 = 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.CustomCreatePath(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; - } - - [RedirectMethod] - 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; - } + vehicleType = ExtVehicleType.RailVehicle; + } else if (vehicleType == ExtVehicleType.CargoTrain) { + vehicleType = ExtVehicleType.CargoVehicle; + } + /// 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 = 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.CustomCreatePath(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; + } + + [RedirectMethod] + 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}"); - } + bool debug = GlobalConfig.Instance.Debug.Switches[21] + && (GlobalConfig.Instance.Debug.NodeId <= 0 + || refTargetNodeId == GlobalConfig.Instance.Debug.NodeId) + && (GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.None + || GlobalConfig.Instance.Debug.ApiExtVehicleType == 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; + 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); + 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}"); + 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 (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}."); + 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 (!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."); + 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); - } + 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}"); + 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 (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."); + 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); + 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}"); + 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 (segment.LengthSqr() >= 1f && CustomTrainAI.CheckOverlap(vehicleId, ref vehicleData, segment, vehicleId)) { #if DEBUG - if (debug) - Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Overlap detected (2). ABORT."); + 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) { + 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."); + if (debug) + Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Checking if vehicle is allowed to change segment."); #endif - float oldMaxSpeed = maxSpeed; - if (! 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)) { + float oldMaxSpeed = maxSpeed; + if (! 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)) { #if DEBUG - if (debug) - Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Vehicle is NOT allowed to change segment. ABORT."); + if (debug) + Log._Debug($"CustomTrainAI.CustomCheckNextLane({vehicleId}): Vehicle is NOT allowed to change segment. ABORT."); #endif - maxSpeed = 0; - return; - } else { - ExtVehicleManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/); - } - maxSpeed = oldMaxSpeed; - } - //} // NON-STOCK CODE - } - } - - [RedirectMethod] - 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 (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; - } - } - } - // 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 simGroup = (uint)transitNodeId >> 7; - uint rand = frame - simGroup & 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 - simGroup, 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 - simGroup, vehicleLightState, pedestrianLightState, vehicles, pedestrians); - } - } - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - protected static bool CheckOverlap(ushort vehicleID, ref Vehicle vehicleData, Segment3 segment, ushort ignoreVehicle) { - Log.Error("CustomTrainAI.CheckOverlap (1) called."); - return false; - } - - [RedirectReverse] - [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; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static void InitializePath(ushort vehicleID, ref Vehicle vehicleData) { - Log.Error("CustomTrainAI.InitializePath called"); - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - public static void UpdatePathTargetPositions(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}"); - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static void Reverse(ushort leaderID, ref Vehicle leaderData) { - Log.Error("CustomTrainAI.Reverse called"); - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static float GetMaxSpeed(ushort leaderID, ref Vehicle leaderData) { - Log.Error("CustomTrainAI.GetMaxSpeed called"); - return 0f; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static float CalculateMaxSpeed(float targetDist, float targetSpeed, float maxBraking) { - Log.Error("CustomTrainAI.CalculateMaxSpeed called"); - return 0f; - } - } + maxSpeed = 0; + return; + } else { + ExtVehicleManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/); + } + maxSpeed = oldMaxSpeed; + } + //} // NON-STOCK CODE + } + } + + [RedirectMethod] + 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 (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; + } + } + } + // 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 simGroup = (uint)transitNodeId >> 7; + uint rand = frame - simGroup & 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 - simGroup, 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 - simGroup, vehicleLightState, pedestrianLightState, vehicles, pedestrians); + } + } + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + protected static bool CheckOverlap(ushort vehicleID, ref Vehicle vehicleData, Segment3 segment, ushort ignoreVehicle) { + Log.Error("CustomTrainAI.CheckOverlap (1) called."); + return false; + } + + [RedirectReverse] + [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; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void InitializePath(ushort vehicleID, ref Vehicle vehicleData) { + Log.Error("CustomTrainAI.InitializePath called"); + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void UpdatePathTargetPositions(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}"); + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void Reverse(ushort leaderID, ref Vehicle leaderData) { + Log.Error("CustomTrainAI.Reverse called"); + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static float GetMaxSpeed(ushort leaderID, ref Vehicle leaderData) { + Log.Error("CustomTrainAI.GetMaxSpeed called"); + return 0f; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static float CalculateMaxSpeed(float targetDist, float targetSpeed, float maxBraking) { + Log.Error("CustomTrainAI.CalculateMaxSpeed called"); + return 0f; + } + } } \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomTramBaseAI.cs b/TLM/TLM/Custom/AI/CustomTramBaseAI.cs index 030408db2..06be41a72 100644 --- a/TLM/TLM/Custom/AI/CustomTramBaseAI.cs +++ b/TLM/TLM/Custom/AI/CustomTramBaseAI.cs @@ -1,774 +1,774 @@ -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; -using TrafficManager.Traffic.Enums; -using TrafficManager.RedirectionFramework.Attributes; - -namespace TrafficManager.Custom.AI { - [TargetType(typeof(TramBaseAI))] - public class CustomTramBaseAI : TramBaseAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`) - [RedirectMethod] - public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { - IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; - - 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 - extVehicleMan.UpdateVehiclePosition(vehicleId, ref vehicleData); - - if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) { - // Advanced AI traffic measurement - extVehicleMan.LogTraffic(vehicleId, ref vehicleData); - } - // 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 - if (VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { - // NON-STOCK CODE END - Singleton.instance.ReleaseVehicle(vehicleId); - } // NON-STOCK CODE - } - } - - [RedirectMethod] - public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) { - ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); // NON-STOCK CODE - - 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 = 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.CustomCreatePath(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; - } - - [RedirectMethod] - 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; - } +namespace TrafficManager.Custom.AI { + using System; + using System.Runtime.CompilerServices; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Math; + using CSUtil.Commons; + using Custom.PathFinding; + using Manager; + using Manager.Impl; + using RedirectionFramework.Attributes; + using State; + using UnityEngine; + + [TargetType(typeof(TramBaseAI))] + public class CustomTramBaseAI : TramBaseAI { // TODO inherit from VehicleAI (in order to keep the correct references to `base`) + [RedirectMethod] + public void CustomSimulationStep(ushort vehicleId, ref Vehicle vehicleData, Vector3 physicsLodRefPos) { + IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; + + 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 + extVehicleMan.UpdateVehiclePosition(vehicleId, ref vehicleData); + + if (!Options.isStockLaneChangerUsed() && (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0) { + // Advanced AI traffic measurement + extVehicleMan.LogTraffic(vehicleId, ref vehicleData); + } + // 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 + if (VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) { + // NON-STOCK CODE END + Singleton.instance.ReleaseVehicle(vehicleId); + } // NON-STOCK CODE + } + } + + [RedirectMethod] + public bool CustomStartPathFind(ushort vehicleID, ref Vehicle vehicleData, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays) { + ExtVehicleManager.Instance.OnStartPathFind(vehicleID, ref vehicleData, null); // NON-STOCK CODE + + 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 = 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.CustomCreatePath(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; + } + + [RedirectMethod] + 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}"); - } + bool debug = GlobalConfig.Instance.Debug.Switches[21] + && (GlobalConfig.Instance.Debug.NodeId <= 0 + || refTargetNodeId == GlobalConfig.Instance.Debug.NodeId) + && (GlobalConfig.Instance.Debug.ApiExtVehicleType == ExtVehicleType.None + || GlobalConfig.Instance.Debug.ApiExtVehicleType == 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 * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); - Vector3 b = netManager.m_lanes.m_buffer[refLaneId].CalculatePosition((float)refOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); - 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 (!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)) { - maxSpeed = 0; - return; - } else { - ExtVehicleManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/); - } - } - } - - 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 = 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; // NON-STOCK CODE - maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, speedLimit, netManager.m_lanes.m_buffer[prevLaneId].m_curve); - } else { - maxSpeed = this.CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f); - } - } - - [RedirectMethod] - public void CustomCalculateSegmentPosition(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 * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, 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 = 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; // NON-STOCK CODE - maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, speedLimit, instance.m_lanes.m_buffer[laneID].m_curve); - } else { - maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1f, 0f); - } - } - - [RedirectMethod] - public void CustomSimulationStep(ushort vehicleID, ref Vehicle vehicleData, ref Vehicle.Frame frameData, ushort leaderID, ref Vehicle leaderData, int lodPhysics) { + Vehicle.Frame lastFrameData = vehicleData.GetLastFrameData(); + float sqrVelocity = lastFrameData.m_velocity.sqrMagnitude; + + netManager.m_lanes.m_buffer[prevLaneId].CalculatePositionAndDirection((float)prevOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, out pos, out dir); + Vector3 b = netManager.m_lanes.m_buffer[refLaneId].CalculatePosition((float)refOffset * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR); + 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 (!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)) { + maxSpeed = 0; + return; + } else { + ExtVehicleManager.Instance.UpdateVehiclePosition(vehicleId, ref vehicleData/*, lastFrameData.m_velocity.magnitude*/); + } + } + } + + 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 = 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; // NON-STOCK CODE + maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, speedLimit, netManager.m_lanes.m_buffer[prevLaneId].m_curve); + } else { + maxSpeed = this.CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f); + } + } + + [RedirectMethod] + public void CustomCalculateSegmentPosition(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 * Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR, 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 = 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; // NON-STOCK CODE + maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, speedLimit, instance.m_lanes.m_buffer[laneID].m_curve); + } else { + maxSpeed = this.CalculateTargetSpeed(vehicleID, ref vehicleData, 1f, 0f); + } + } + + [RedirectMethod] + 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; + 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; + 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}"); - } + 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; - UpdatePathTargetPositions(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) { - UpdatePathTargetPositions(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; - UpdatePathTargetPositions(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; + 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; + UpdatePathTargetPositions(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) { + UpdatePathTargetPositions(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; + UpdatePathTargetPositions(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"); - } + 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}"); - } + 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) { - UpdatePathTargetPositions(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; - } + 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) { + UpdatePathTargetPositions(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}"); - } + 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 (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}"); - } + 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; + // 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}"); - } + 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)); + } 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}"); - } + 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}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Start of second part. curSpeed={curSpeed} curInvRot={curInvRot}"); + } #endif - afterRotToTargetPos1Diff = curInvRot * afterRotToTargetPos1Diff; + afterRotToTargetPos1Diff = curInvRot * afterRotToTargetPos1Diff; #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1Diff={afterRotToTargetPos1Diff} (old afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag})"); - } + 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); + 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}"); - } + 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 (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}"); - } + 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 (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}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1DiffSqrMag > maxSpeedAddSqr. afterRotToTargetPos1DiffSqrMag={afterRotToTargetPos1DiffSqrMag} maxSpeedAddSqr={maxSpeedAddSqr} fwdLimiter={fwdLimiter} fwd={fwd}"); + } #endif - } + } - if (fwd.z < -1f) { // !!! + if (fwd.z < -1f) { // !!! #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): fwd.z < -1f. fwd={fwd}"); - } + 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 (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}"); - } + 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 (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}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): afterRotToTargetPos1Diff.z < Mathf.Abs(afterRotToTargetPos1Diff.x) * -10f. fwd={fwd} targetPos0TargetPos1Diff={targetPos0TargetPos1Diff} afterRotToTargetPos1Diff={afterRotToTargetPos1Diff}"); + } #endif - // Fix: Trams get stuck - /*fwd.z = 0f; - afterRotToTargetPos1Diff = Vector3.zero;*/ - maxSpeed = 0.5f; // NON-STOCK CODE + // Fix: Trams get stuck + /*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}"); - } + 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; - UpdatePathTargetPositions(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); + } else { + posAfterWheelRot = posBeforeWheelRot + Vector3.Normalize(vehicleData.m_targetPos1 - vehicleData.m_targetPos0) * this.m_info.m_generatedInfo.m_wheelBase; + posIndex = -1; + UpdatePathTargetPositions(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}"); - } + 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; - UpdatePathTargetPositions(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; + } + } else { + posIndex = -1; + UpdatePathTargetPositions(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}"); - } + 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; + } + } + motionFactor = 0f; #if DEBUG - if (debug) { - Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): Reset motion factor. motionFactor={motionFactor}"); - } + 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; + } + + 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}"); - } + 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 (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}"); - } + if (debug) { + Log._Debug($"CustomTramBaseAI.SimulationStep({vehicleID}): [1] maxSpeed={maxSpeed}"); + } #endif - maxSpeed = Mathf.Min(maxSpeed, CalculateMaxSpeed(targetDist, vehicleData.m_targetPos2.w, braking * 0.9f)); + 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}"); - } + 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)); + 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}"); - } + 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)); + 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}"); - } + 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); + 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}"); - } + 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 (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}"); - } + 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); + } 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}"); - } + 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) { + } + } + } 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}"); - } + 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); - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - public static void UpdatePathTargetPositions(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}"); - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static float GetMaxSpeed(ushort leaderID, ref Vehicle leaderData) { - Log.Error("CustomTrainAI.GetMaxSpeed called"); - return 0f; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static float CalculateMaxSpeed(float targetDist, float targetSpeed, float maxBraking) { - Log.Error("CustomTrainAI.CalculateMaxSpeed called"); - return 0f; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static void InitializePath(ushort vehicleID, ref Vehicle vehicleData) { - Log.Error("CustomTrainAI.InitializePath called"); - } - } -} + 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); + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void UpdatePathTargetPositions(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}"); + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static float GetMaxSpeed(ushort leaderID, ref Vehicle leaderData) { + Log.Error("CustomTrainAI.GetMaxSpeed called"); + return 0f; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static float CalculateMaxSpeed(float targetDist, float targetSpeed, float maxBraking) { + Log.Error("CustomTrainAI.CalculateMaxSpeed called"); + return 0f; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void InitializePath(ushort vehicleID, ref Vehicle vehicleData) { + Log.Error("CustomTrainAI.InitializePath called"); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomTransportLineAI.cs b/TLM/TLM/Custom/AI/CustomTransportLineAI.cs index 44101a753..9fc7794b2 100644 --- a/TLM/TLM/Custom/AI/CustomTransportLineAI.cs +++ b/TLM/TLM/Custom/AI/CustomTransportLineAI.cs @@ -1,196 +1,189 @@ -using ColossalFramework; -using CSUtil.Commons; -using CSUtil.Commons.Benchmark; -using TrafficManager.RedirectionFramework.Attributes; -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 TrafficManager.Traffic.Enums; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; - -namespace TrafficManager.Custom.AI { - [TargetType(typeof(TransportLineAI))] - public class CustomTransportLineAI : TransportLineAI { // TODO inherit from NetAI (in order to keep the correct references to `base`) - [RedirectMethod] - 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; +namespace TrafficManager.Custom.AI { + using System.Runtime.CompilerServices; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using Custom.PathFinding; + using RedirectionFramework.Attributes; + using State; + using UnityEngine; + + [TargetType(typeof(TransportLineAI))] + public class CustomTransportLineAI : TransportLineAI { // TODO inherit from NetAI (in order to keep the correct references to `base`) + [RedirectMethod] + 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}"); + 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 ((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; - - //Log._Debug($"Transport line. extVehicleType={extVehicleType}"); - uint path; - // NON-STOCK CODE START - PathCreationArgs args; - args.extPathType = 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.CustomCreatePath(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; + 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 ((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; + + //Log._Debug($"Transport line. extVehicleType={extVehicleType}"); + uint path; + // NON-STOCK CODE START + PathCreationArgs args; + args.extPathType = 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.CustomCreatePath(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}]"); + 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; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static bool GetStopLane(ref PathUnit.Position pos, VehicleInfo.VehicleType vehicleType) { - Log.Error($"CustomTransportLineAI.GetStopLane called."); - return false; - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static void CheckSegmentProblems(ushort segmentID, ref NetSegment data) { - Log.Error($"CustomTransportLineAI.CheckSegmentProblems called."); - } - - [RedirectReverse] - [MethodImpl(MethodImplOptions.NoInlining)] - private static void CheckNodeProblems(ushort nodeID, ref NetNode data) { - Log.Error($"CustomTransportLineAI.CheckNodeProblems called."); - } - } -} + return false; + } + + CustomTransportLineAI.CheckSegmentProblems(segmentID, ref data); + return true; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool GetStopLane(ref PathUnit.Position pos, VehicleInfo.VehicleType vehicleType) { + Log.Error($"CustomTransportLineAI.GetStopLane called."); + return false; + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void CheckSegmentProblems(ushort segmentID, ref NetSegment data) { + Log.Error($"CustomTransportLineAI.CheckSegmentProblems called."); + } + + [RedirectReverse] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void CheckNodeProblems(ushort nodeID, ref NetNode data) { + Log.Error($"CustomTransportLineAI.CheckNodeProblems called."); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/AI/CustomVehicleAI.cs b/TLM/TLM/Custom/AI/CustomVehicleAI.cs index be791f55e..ddae01a9f 100644 --- a/TLM/TLM/Custom/AI/CustomVehicleAI.cs +++ b/TLM/TLM/Custom/AI/CustomVehicleAI.cs @@ -57,7 +57,7 @@ protected void CustomUpdatePathTargetPositions(ushort vehicleID, ref Vehicle veh $"\ttargetPosIndex={targetPosIndex}\n" + $"\tmaxTargetPosIndex={maxTargetPosIndex}\n" + $"\tminSqrDistanceA={minSqrDistanceA}\n" + - $"\tminSqrDistanceB={minSqrDistanceB}\n" + + $"\tminSqrDistanceB={minSqrDistanceB}\n" + $"\tvehicleData.m_path={vehicleData.m_path}\n" + $"\tvehicleData.m_pathPositionIndex={vehicleData.m_pathPositionIndex}\n" + $"\tvehicleData.m_lastPathOffset={vehicleData.m_lastPathOffset}" @@ -381,14 +381,18 @@ protected void CustomUpdatePathTargetPositions(ushort vehicleID, ref Vehicle veh Log._Debug($"CustomVehicle.CustomUpdatePathTargetPositions({vehicleID}): Finding best lane for emergency vehicles. Before: bestLaneIndex={bestLaneIndex}"); } #endif - bestLaneIndex = FindBestLane(vehicleID, ref vehicleData, nextPosition); - +#if ROUTING + bestLaneIndex = VehicleBehaviorManager.Instance.FindBestEmergencyLane(vehicleID, ref vehicleData, ref ExtVehicleManager.Instance.ExtVehicles[vehicleID], curLaneId, currentPosition, curSegmentInfo, nextPosition, nextSegmentInfo); +#else + // stock procedure for emergency vehicles on duty + bestLaneIndex = FindBestLane(vehicleID, ref vehicleData, nextPathPos); +#endif #if DEBUG if (debug) { Log._Debug($"CustomVehicle.CustomUpdatePathTargetPositions({vehicleID}): Found best lane for emergency vehicles. After: bestLaneIndex={bestLaneIndex}"); } #endif - } else { + } else if (VehicleBehaviorManager.Instance.MayFindBestLane(vehicleID, ref vehicleData, ref ExtVehicleManager.Instance.ExtVehicles[vehicleID])) { // NON-STOCK CODE START if (firstIter && this.m_info.m_vehicleType == VehicleInfo.VehicleType.Car diff --git a/TLM/TLM/Custom/Data/CustomVehicle.cs b/TLM/TLM/Custom/Data/CustomVehicle.cs index 6047dac74..9e18a8147 100644 --- a/TLM/TLM/Custom/Data/CustomVehicle.cs +++ b/TLM/TLM/Custom/Data/CustomVehicle.cs @@ -29,7 +29,7 @@ public static void CustomSpawn(ref Vehicle vehicleData, ushort vehicleId) { 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) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } diff --git a/TLM/TLM/Custom/PathFinding/CustomPathFind.cs b/TLM/TLM/Custom/PathFinding/CustomPathFind.cs index a615d4edc..ba86aafb0 100644 --- a/TLM/TLM/Custom/PathFinding/CustomPathFind.cs +++ b/TLM/TLM/Custom/PathFinding/CustomPathFind.cs @@ -1,670 +1,675 @@ #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; -using TrafficManager.Traffic.Enums; -using TrafficManager.RedirectionFramework.Attributes; - namespace TrafficManager.Custom.PathFinding { + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Threading; + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using ColossalFramework.Math; + using CSUtil.Commons; + using Manager; + using Manager.Impl; + using State; + using Traffic.Data; + using Traffic.Enums; + using UnityEngine; + #if !PF2 [TargetType(typeof(PathFind))] #endif - 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; + 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 = Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; - - //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; + } + + 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 = Constants.BYTE_TO_FLOAT_OFFSET_CONVERSION_FACTOR; + + //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; + 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; + 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 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; + 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; + public bool IsMasterPathFind = false; - protected virtual void Awake() { + protected virtual void Awake() { #if DEBUG - Log._Debug($"CustomPathFind.Awake called."); + Log._Debug($"CustomPathFind.Awake called."); #endif - var stockPathFindType = typeof(PathFind); - const BindingFlags fieldFlags = BindingFlags.NonPublic | BindingFlags.Instance; + 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); + _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; + 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."); - } + 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!"); - } + 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() { + 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); - } - } + try { + Monitor.Enter(QueueLock); + Terminated = true; + Monitor.PulseAll(QueueLock); + } catch (Exception e) { + Log.Error("CustomPathFind.OnDestroy Error: " + e.ToString()); + } finally { + Monitor.Exit(QueueLock); + } + } #if !PF2 [RedirectMethod] #endif - 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; + 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; + bool debug = + this.m_debug = + m_conf.Debug.Switches[0] && + ((m_conf.Debug.ApiExtVehicleType == ExtVehicleType.None + && queueItem.vehicleType == ExtVehicleType.None) + || (queueItem.vehicleType & m_conf.Debug.ApiExtVehicleType) != 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; + } 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 + } 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 + } 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; - } + } 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 (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 >= NetManager.MAX_NODE_COUNT) { - 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_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 >= NetManager.MAX_NODE_COUNT) { + 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 + ++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 (_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."); + 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; - } - } - } + 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}"); + //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 + PathUnits.m_buffer[(int)unit].m_pathFindFlags |= PathUnit.FLAG_READY; // Path found #if DEBUG - ++m_succeededPathFinds; + ++m_succeededPathFinds; #if DEBUGNEWPF - if (debug) - Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Path-find succeeded for unit {unit}"); + if (debug) + Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this.m_pathFindIndex}: Path-find succeeded for unit {unit}"); #endif #endif - //CustomPathManager._instance.ResetQueueItem(unit); + //CustomPathManager._instance.ResetQueueItem(unit); - return; - } + 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; + // 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); + ++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 @@ -682,971 +687,971 @@ protected void PathFindImplementation(uint unit, ref PathUnit data) { } // NON-STOCK CODE END #endif - currentPosition = this.m_laneTarget[laneID]; - } - PathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; + currentPosition = this.m_laneTarget[laneID]; + } + PathUnits.m_buffer[unit].m_pathFindFlags |= PathUnit.FLAG_FAILED; #if DEBUG - ++m_failedPathFinds; + ++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"); + 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); + //CustomPathManager._instance.ResetQueueItem(unit); #if DEBUG - //Log._Debug($"THREAD #{Thread.CurrentThread.ManagedThreadId} PF {this._pathFindIndex}: Cannot find path (pfCurrentState={pfCurrentState}) for unit {unit}"); + //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. + // 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) { + // 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(); - } - } + 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); + //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.parkingAI) { - 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.parkingAI && - queueItem.vehicleType == ExtVehicleType.PassengerCar && - (queueItem.pathType == ExtPathType.WalkingOnly || (queueItem.pathType == 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 /*&& + //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.parkingAI) { + 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.parkingAI && + queueItem.vehicleType == ExtVehicleType.PassengerCar && + (queueItem.pathType == ExtPathType.WalkingOnly || (queueItem.pathType == 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 + 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 - !( + && !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); - 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.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); + 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.parkingAI || 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; - } + // 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; @@ -1659,60 +1664,60 @@ private void ProcessItemPublicTransport(bool debug, BufferItem item, ushort next } #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; + 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; + int wIter = 0; #endif - while (laneIndex < nextNumLanes && curLaneId != 0u) { + 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; + ++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) { @@ -1722,323 +1727,323 @@ private void ProcessItemPublicTransport(bool debug, BufferItem item, ushort next } // 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) { + 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]; + debug = debug && m_conf.Debug.Switches[1]; #else debug = false; #endif #if DEBUGNEWPF && DEBUG - List logBuf = null; - if (debug) - logBuf = new List(); + List logBuf = null; + if (debug) + logBuf = new List(); #endif - foundForced = false; - bool blocked = false; - if ((nextSegment.m_flags & m_disableMask) != NetSegment.Flags.None) { + 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); - } + 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; + 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; + 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}"); + 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; + 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; + /*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; + } + } - 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 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($"allowedVehicleTypes={allowedVehicleTypes} allowedLaneTypes={allowedLaneTypes}"); + if (debug) + logBuf.Add($"forceLaneIndex break! laneIndex={laneIndex}"); #endif + break; + } + // NON-STOCK CODE END // + NetInfo.Lane nextLaneInfo = nextSegmentInfo.m_lanes[laneIndex]; - // 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 ((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) { @@ -2048,200 +2053,200 @@ private bool ProcessItemCosts(bool debug, bool obeyStockLaneArrows, LaneChanging } // 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; - } + 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})"); + 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) { + 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 + // 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}"); - } + 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); + } + nextItem.m_duration = prevDuration + transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f); - // account for public tranport transition costs on non-PT paths - if ( + // account for public tranport transition costs on non-PT paths + if ( #if DEBUG - !m_conf.Debug.Switches[20] && + !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); + 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}"); + 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_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)) { + 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 (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 @@ -2261,245 +2266,245 @@ private bool ProcessItemCosts(bool debug, bool obeyStockLaneArrows, LaneChanging // 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) { + 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; + 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 (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; + // 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) { @@ -2509,315 +2514,315 @@ private void ProcessItemPedBicycle(bool debug, BufferItem item, ushort nextNodeI } // 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); + 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; + ++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"); + 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); + //CustomPathManager._instance.ResetQueueItem(Calculating); - PathUnits.m_buffer[Calculating].m_pathFindFlags |= PathUnit.FLAG_FAILED; - } finally { - } - //tCurrentState = 10; + 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; - } - } -} + 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; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/PathFinding/CustomPathFind2.cs b/TLM/TLM/Custom/PathFinding/CustomPathFind2.cs index 04b8d486f..2b6d75223 100644 --- a/TLM/TLM/Custom/PathFinding/CustomPathFind2.cs +++ b/TLM/TLM/Custom/PathFinding/CustomPathFind2.cs @@ -1,3296 +1,3310 @@ -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.RedirectionFramework.Attributes; -using TrafficManager.State; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using TrafficManager.TrafficLight; -using UnityEngine; -using static TrafficManager.Custom.PathFinding.CustomPathManager; - -namespace TrafficManager.Custom.PathFinding { +namespace TrafficManager.Custom.PathFinding { + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Threading; + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using ColossalFramework.Math; + using ColossalFramework.UI; + using CSUtil.Commons; + using Manager; + using Manager.Impl; + using RedirectionFramework.Attributes; + using State; + using Traffic.Data; + using Traffic.Enums; + using UnityEngine; + #if PF2 - [TargetType(typeof(PathFind))] + [TargetType(typeof(PathFind))] #endif - 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; + 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; + private readonly RoutingManager m_routingManager = RoutingManager.Instance; #endif #if JUNCTIONRESTRICTIONS - private readonly JunctionRestrictionsManager m_junctionManager = JunctionRestrictionsManager.Instance; + private readonly JunctionRestrictionsManager m_junctionManager = JunctionRestrictionsManager.Instance; #endif #if VEHICLERESTRICTIONS - private readonly VehicleRestrictionsManager m_vehicleRestrictionsManager = VehicleRestrictionsManager.Instance; + private readonly VehicleRestrictionsManager m_vehicleRestrictionsManager = VehicleRestrictionsManager.Instance; #endif #if SPEEDLIMITS - private readonly SpeedLimitManager m_speedLimitManager = SpeedLimitManager.Instance; + private readonly SpeedLimitManager m_speedLimitManager = SpeedLimitManager.Instance; #endif #if CUSTOMTRAFFICLIGHTS - private readonly CustomSegmentLightsManager m_customTrafficLightsManager = CustomSegmentLightsManager.Instance; + 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; + 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; + 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" + + 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" + + "\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; + "\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; + 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); - } - } + 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); + } + } #if PF2 - [RedirectMethod] -#endif - 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; - } + [RedirectMethod] +#endif + 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_isRoadVehicle = + (m_queueItem.vehicleType & ExtVehicleType.RoadVehicle) != ExtVehicleType.None; - m_isLaneArrowObeyingEntity = + m_isLaneArrowObeyingEntity = #if DEBUG - ! Options.allRelaxed && // debug option: all vehicle may ignore lane arrows + ! 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 - ; + (! 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>(); - } + m_debug = m_conf.Debug.Switches[0] + && (m_conf.Debug.ApiExtVehicleType == ExtVehicleType.None + || m_queueItem.vehicleType == m_conf.Debug.ApiExtVehicleType) + && (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) { + 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 + 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 + 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 + , out bufferItemStartA.m_vehiclesUsed #endif - ); - bufferItemStartA.m_comparisonValue = 0f; - bufferItemStartA.m_duration = 0f; - } else { + ); + bufferItemStartA.m_comparisonValue = 0f; + bufferItemStartA.m_duration = 0f; + } else { #if PARKINGAI || JUNCTIONRESTRICTIONS - m_startSegmentA = 0; // NON-STOCK CODE + m_startSegmentA = 0; // NON-STOCK CODE #endif - m_startLaneA = 0u; - m_startOffsetA = 0; - } + m_startLaneA = 0u; + m_startOffsetA = 0; + } - BufferItem bufferItemStartB = default(BufferItem); - if (data.m_position02.m_segment != 0 && posCount >= 3) { + 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 + 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 + 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 + , out bufferItemStartB.m_vehiclesUsed #endif - ); - bufferItemStartB.m_comparisonValue = 0f; - bufferItemStartB.m_duration = 0f; - } else { + ); + 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 + 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 + , 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 - bool detourMissing = (m_vehicleTypes & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail | VehicleInfo.VehicleType.Metro)) != VehicleInfo.VehicleType.None && !m_queueItem.queued; - if (detourMissing) { - Log.Warning($"Path-finding for unhandled vehicle requested!"); - } - - if (m_debug || detourMissing) { - 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" + + , 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 + bool detourMissing = (m_vehicleTypes & (VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Tram | VehicleInfo.VehicleType.Monorail | VehicleInfo.VehicleType.Metro)) != VehicleInfo.VehicleType.None && !m_queueItem.queued; + if (detourMissing) { + Log.Warning($"Path-finding for unhandled vehicle requested!"); + } + + if (m_debug || detourMissing) { + 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" + + "\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); + "\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; + 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; + 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; + // 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 + // 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 + // NON-STOCK CODE START #if SPEEDLIMITS - prevMaxSpeed = m_speedLimitManager.GetLockFreeGameSpeedLimit(prevSegmentId, prevLaneIndex, item.m_laneID, prevLaneInfo); + 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 { + 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 { + // 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; - } + // 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; - } - } - } + 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}" - ); - } + 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( + ProcessItemPedBicycle( #if DEBUG - debug, unitId, + 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); - } + 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 (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}" - ); - } + 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( + ProcessItemPedBicycle( #if DEBUG - debug, unitId, + 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); - } + 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)) { + // 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}" - ); - } + 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( + ProcessItemPedBicycle( #if DEBUG - debug, unitId, + 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 { + 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}" - ); - } + 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) { + // 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}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: beautification -> ped: Exploring next segment behind beautification node\n" + + "\t" + $"nextSegmentId={nextSegmentId}" + ); + } #endif - ProcessItemCosts( + ProcessItemCosts( #if DEBUG - debug, unitId, + 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); - } - } - } + 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); - } + // 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}" - ); - } + 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; + // NON-STOCK CODE START + bool parkingAllowed = true; #if PARKINGAI - // Parking AI: Determine if parking is allowed - if (Options.parkingAI) { -#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 + // Parking AI: Determine if parking is allowed + if (Options.parkingAI) { +#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.parkingAI - ) { + if ( + Options.parkingAI + ) { #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}" - ); - } + 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 == ExtPathType.WalkingOnly && prevIsCarLane) || - ( - m_queueItem.pathType == 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 ( + (m_queueItem.pathType == ExtPathType.WalkingOnly && prevIsCarLane) || + ( + m_queueItem.pathType == 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"); - } + 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); + } 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}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: ped -> vehicle: Parking AI: Spawning pocket cars is allowed here\n" + + "\t" + $"switchConnectOffset={switchConnectOffset}" + ); + } #endif - } - } else { + } + } else { #endif - switchConnectOffset = (byte)m_pathRandomizer.UInt32(1u, 254u); + 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}" - ); - } + 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 + ushort nextSegmentId = 0; + if ((m_vehicleTypes & (VehicleInfo.VehicleType.Ferry #if !ROUTING | VehicleInfo.VehicleType.Monorail #endif - )) != VehicleInfo.VehicleType.None) { - // ferry (/ monorail) + )) != VehicleInfo.VehicleType.None) { + // ferry (/ monorail) #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring ferry routes"); - } + 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) { + 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}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring ferry route\n" + + "\t" + $"nextSegmentId={nextSegmentId}" + ); + } #endif - ProcessItemCosts( + ProcessItemCosts( #if DEBUG - debug, unitId, + 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); - } - } + 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 (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"); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring ferry u-turn"); + } #endif - nextSegmentId = prevSegmentId; - ProcessItemCosts( + nextSegmentId = prevSegmentId; + ProcessItemCosts( #if DEBUG - debug, unitId, + 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. + 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"); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring vehicle routes"); + } #endif #if ROUTING - bool exploreUturn = false; + 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; + 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}"); - } + 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 (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}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: bicycle -> vehicle / stock vehicle routing\n" + + "\t" + $"prevIsRouted={prevIsRouted}\n" + + "\t" + $"allowBicycle={allowBicycle}" + ); + } #endif - // NON-STOCK CODE END + // NON-STOCK CODE END #endif - nextSegmentId = prevSegment.GetRightSegment(nextNodeId); - for (int l = 0; l < 8; l++) { - if (nextSegmentId == 0) { - break; - } + nextSegmentId = prevSegment.GetRightSegment(nextNodeId); + for (int l = 0; l < 8; l++) { + if (nextSegmentId == 0) { + break; + } - if (nextSegmentId == prevSegmentId) { - 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}" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: bicycle -> vehicle / stock vehicle routing: exploring next segment\n" + + "\t" + $"nextSegmentId={nextSegmentId}" + ); + } #endif - if (ProcessItemCosts( + if (ProcessItemCosts( #if DEBUG - debug, unitId, + 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, + 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 + !prevIsRouted // NON-STOCK CODE #else true #endif - , allowBicycle) - ) { - exploreUturn = true; // allow exceptional u-turns + , 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}" - ); - } + 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); - } + nextSegmentId = netManager.m_segments.m_buffer[nextSegmentId].GetRightSegment(nextNodeId); + } #if ROUTING - } // NON-STOCK CODE + } // 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}" - ); - } + 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 + // 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 - } - } + /* + * ======================================================================================================= + * 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 (prevIsRouted) { #if DEBUG - if (debug) { - Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing: Exploring custom routes"); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Custom routing: Exploring custom routes"); + } #endif - exploreUturn = false; // custom routing processes regular u-turns - if (ProcessItemRouted( + exploreUturn = false; // custom routing processes regular u-turns + if (ProcessItemRouted( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed #if ADVANCEDAI - , enableAdvancedAI, laneChangingCost, + , 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 { + 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"); - } + 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 (!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" - ); - } + 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 (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" - ); - } + if (debug) { + Debug(unitId, item, $"ProcessItemMain: vehicle -> vehicle: Exploring stock u-turn\n" + + "\t" + $"exploreUturn={exploreUturn}\n" + ); + } #endif - ProcessItemCosts( + ProcessItemCosts( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, #if ADVANCEDAI && ROUTING - false, 0f, + false, 0f, #endif - nextNodeId, ref nextNode, false, prevSegmentId, ref prevSegment, + nextNodeId, ref nextNode, false, prevSegmentId, ref prevSegment, #if ROUTING - segmentSelectionCost, laneSelectionCost, null, + segmentSelectionCost, laneSelectionCost, null, #endif - ref prevRelSimilarLaneIndex, connectOffset, true, false - ); - } - } + 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 (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 + // 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); + 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 ((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 (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}" - ); - } + 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; - } + 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; + 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; - } + 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 (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}" - ); - } + 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; - } + 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; + 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_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); + nextItem.m_laneID = nextLaneId; + nextItem.m_lanesUsed = (item.m_lanesUsed | nextLaneInfo.m_laneType); #if PARKINGAI - nextItem.m_vehiclesUsed = (item.m_vehiclesUsed | nextLaneInfo.m_vehicleType); + 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 + // 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}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, curLaneId, $"ProcessItemPublicTransport: Adding next item\n" + + "\t" + $"nextItem={nextItem}" + ); + } #endif - AddBufferItem( + AddBufferItem( #if DEBUG - debug, + debug, #endif - nextItem, item.m_position - ); - } - } + nextItem, item.m_position + ); + } + } #if ADVANCEDAI && ROUTING - // 3a (non-routed, no adv. AI) - private bool ProcessItemCosts( + // 3a (non-routed, no adv. AI) + private bool ProcessItemCosts( #if DEBUG - bool debug, uint unitId, + 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( + 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, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, + item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, #if ADVANCEDAI && ROUTING - false, 0f, + false, 0f, #endif - nextNodeId, ref nextNode, isMiddle, nextSegmentId, ref nextSegment, + nextNodeId, ref nextNode, isMiddle, nextSegmentId, ref nextSegment, #if ROUTING - 1f, 1f, null, + 1f, 1f, null, #endif - ref laneIndexFromInner, connectOffset, enableVehicle, enablePedestrian); - } + ref laneIndexFromInner, connectOffset, enableVehicle, enablePedestrian); + } #endif - // 3b - private bool ProcessItemCosts( + // 3b + private bool ProcessItemCosts( #if DEBUG - bool debug, uint unitId, + bool debug, uint unitId, #endif - BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, + BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, #if ADVANCEDAI && ROUTING - bool enableAdvancedAI, float laneChangingCost, + bool enableAdvancedAI, float laneChangingCost, #endif - ushort nextNodeId, ref NetNode nextNode, bool isMiddle, ushort nextSegmentId, ref NetSegment nextSegment, + ushort nextNodeId, ref NetNode nextNode, bool isMiddle, ushort nextSegmentId, ref NetSegment nextSegment, #if ROUTING - float segmentSelectionCost, float laneSelectionCost, LaneTransitionData? transition, + float segmentSelectionCost, float laneSelectionCost, LaneTransitionData? transition, #endif - ref int laneIndexFromInner, byte connectOffset, bool enableVehicle, bool enablePedestrian - ) { + 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 (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" + + "\t" + $"enableAdvancedAI={enableAdvancedAI}\n" + + "\t" + $"laneChangingCost={laneChangingCost}\n" #endif - + "\t" + $"nextNodeId={nextNodeId}\n" - + "\t" + $"isMiddle={isMiddle}\n" - + "\t" + $"nextSegmentId={nextSegmentId}\n" + + "\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) { + + "\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) { + 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; + 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}" - ); - } + 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 ((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}" - ); - } + 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 (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}" - ); - } + 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" - ); - } + 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; + offsetLength *= segmentSelectionCost; + offsetLength *= laneSelectionCost; #if DEBUG - if (debug) { - Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Applied custom selection cost factors\n" - + "\t" + $"offsetLength={offsetLength}" - ); - } + 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 + 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}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, $"ProcessItemCosts: Calculated base length\n" + + "\t" + $"baseLength={baseLength}" + ); + } #endif - if ( + 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; + !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; + 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 (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 + // 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); + float transitionCostOverMeanMaxSpeed = transitionCost / ((prevMaxSpeed + nextMaxSpeed) * 0.5f * m_maxLength); #if ADVANCEDAI && ROUTING - if (!enableAdvancedAI) { + 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 (!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); + } +#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); + 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 + // 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 (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) { + 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); - } + 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}" - ); - } + 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 ( + !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}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemCosts: Adding next item\n" + + "\t" + $"nextItem={nextItem}" + ); + } #endif - AddBufferItem( + AddBufferItem( #if DEBUG - debug, + debug, #endif - nextItem, item.m_position - ); - } else { + 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}" - ); - } + 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 { + } + } 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}" - ); - } + 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++; - } - } + 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; - } + nextLaneId = netManager.m_lanes.m_buffer[nextLaneId].m_nextLane; + continue; + } + laneIndexFromInner = newLaneIndexFromInner; + return blocked; + } - // 4 - private void ProcessItemPedBicycle( + // 4 + private void ProcessItemPedBicycle( #if DEBUG - bool debug, uint unitId, + 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) { + 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}" - ); - } + 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 ((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"); - } + 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; - } + return; + } - // NON-STOCK CODE START + // NON-STOCK CODE START + bool mayCross = true; #if JUNCTIONRESTRICTIONS || CUSTOMTRAFFICLIGHTS - if (Options.junctionRestrictionsEnabled || Options.timedLightsEnabled) { - bool nextIsStartNode = nextNodeId == nextSegment.m_startNode; - if (nextIsStartNode || nextNodeId == nextSegment.m_endNode) { + 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 (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"); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Pedestrian crossing prohibited"); + } #endif - return; - } - } + mayCross = false; + } + } #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 (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; + + // NON-STOCK CODE START + if (!mayCross && nextLaneInfo.m_laneType == NetInfo.LaneType.Pedestrian) { +#if DEBUG + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Aborting: Crossing prohibited"); + } +#endif + return; + } + // NON-STOCK CODE END + + 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 + // 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); + 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); + 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 + // 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}" - ); - } + if (debug) { + Debug(unitId, item, nextSegmentId, nextLaneIndex, nextLaneId, $"ProcessItemPedBicycle: Adding next item\n" + + "\t" + $"nextItem={nextItem}" + ); + } #endif - AddBufferItem( + AddBufferItem( #if DEBUG - debug, + debug, #endif - nextItem, item.m_position - ); - } - } + nextItem, item.m_position + ); + } + } #if ROUTING - // 5 (custom: process routed vehicle paths) - private bool ProcessItemRouted( + // 5 (custom: process routed vehicle paths) + private bool ProcessItemRouted( #if DEBUG - bool debug, uint unitId, + bool debug, uint unitId, #endif - BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, + BufferItem item, ref NetSegment prevSegment, ref NetLane prevLane, float prevMaxSpeed, float prevLaneSpeed, #if ADVANCEDAI - bool enableAdvancedAI, float laneChangingCost, + bool enableAdvancedAI, float laneChangingCost, #endif - float segmentSelectionCost, float laneSelectionCost, ushort nextNodeId, ref NetNode nextNode, bool isMiddle, SegmentRoutingData prevSegmentRouting, LaneEndRoutingData prevLaneEndRouting, byte connectOffset, int prevInnerSimilarLaneIndex - ) { + 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 (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]; + + "\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; + /* + * ======================================================================================================= + * 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}"); - } + 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}" - ); - } + /* + * ======================================================================================================= + * 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 ( + !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}" - ); - } + 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; - } + continue; + } #if DEBUG - if (debug) { - Debug(unitId, item, laneTransitions[k].segmentId, laneTransitions[k].laneIndex, laneTransitions[k].laneId, $"ProcessItemRouted: Exploring lane transition now\n" + 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" + + "\t" + $"enableAdvancedAI={enableAdvancedAI}\n" + + "\t" + $"laneChangingCost={laneChangingCost}\n" #endif - + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" - + "\t" + $"laneSelectionCost={laneSelectionCost}" - ); - } + + "\t" + $"segmentSelectionCost={segmentSelectionCost}\n" + + "\t" + $"laneSelectionCost={laneSelectionCost}" + ); + } #endif - if ( - ProcessItemCosts( + if ( + ProcessItemCosts( #if DEBUG - debug, unitId, + debug, unitId, #endif - item, ref prevSegment, ref prevLane, prevMaxSpeed, prevLaneSpeed, + 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 + 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; + , 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; + 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; + 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; - } + 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); + ExtVehicleType allowedTypes = m_vehicleRestrictionsManager.GetAllowedVehicleTypes(segmentId, segmentInfo, (uint)laneIndex, laneInfo, VehicleRestrictionsMode.Configured); - return ((allowedTypes & m_queueItem.vehicleType) != ExtVehicleType.None); - } + 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); - - float segmentTraffic = Mathf.Clamp(1f - (float)m_trafficMeasurementManager.SegmentDirTrafficData[m_trafficMeasurementManager.GetDirIndex(item.m_position.m_segment, prevFinalDir)].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); - } - } - } - } -} + 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); + + float segmentTraffic = Mathf.Clamp(1f - (float)m_trafficMeasurementManager.SegmentDirTrafficData[m_trafficMeasurementManager.GetDirIndex(item.m_position.m_segment, prevFinalDir)].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); + } + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Custom/PathFinding/CustomPathManager.cs b/TLM/TLM/Custom/PathFinding/CustomPathManager.cs index 6940f30dc..7089a3c96 100644 --- a/TLM/TLM/Custom/PathFinding/CustomPathManager.cs +++ b/TLM/TLM/Custom/PathFinding/CustomPathManager.cs @@ -13,6 +13,7 @@ using TrafficManager.Traffic; using TrafficManager.Util; using CSUtil.Commons; +using TrafficManager.Manager.Impl; using static TrafficManager.Traffic.Data.ExtCitizenInstance; using TrafficManager.Traffic.Data; using static TrafficManager.Manager.Impl.ExtPathManager; @@ -21,6 +22,8 @@ // ReSharper disable InconsistentNaming namespace TrafficManager.Custom.PathFinding { + using API.Traffic.Data; + [TargetType(typeof(PathManager))] public class CustomPathManager : PathManager { /// @@ -202,22 +205,25 @@ public bool CustomCreatePath(out uint unit, ref Randomizer randomizer, PathCreat unit = pathUnitId; if (args.isHeavyVehicle) { - this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 16; + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_IS_HEAVY; } if (args.ignoreBlocked || args.ignoreFlooded) { - this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 32; + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_IGNORE_BLOCKED; } if (args.stablePath) { - this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 64; + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_STABLE_PATH; } if (args.randomParking) { - this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 2; + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_RANDOM_PARKING; + } + if (args.ignoreFlooded) { + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_IGNORE_FLOODED; } if (args.hasCombustionEngine) { - this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 4; + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_COMBUSTION; } if (args.ignoreCosts) { - this.m_pathUnits.m_buffer[unit].m_simulationFlags |= 8; + this.m_pathUnits.m_buffer[unit].m_simulationFlags |= PathUnit.FLAG_IGNORE_COST; } this.m_pathUnits.m_buffer[unit].m_pathFindFlags = 0; this.m_pathUnits.m_buffer[unit].m_buildIndex = args.buildIndex; @@ -270,7 +276,7 @@ public bool CustomCreatePath(out uint unit, ref Randomizer randomizer, PathCreat // NON-STOCK CODE START try { Monitor.Enter(this.m_bufferLock); - + queueItems[pathUnitId].queued = false; // NON-STOCK CODE END this.ReleasePath(unit); @@ -284,19 +290,22 @@ public bool CustomCreatePath(out uint unit, ref Randomizer randomizer, PathCreat return false; } - + /// /// Finds a suitable path position for a walking citizen with the given world position. + /// If secondary lane constraints are given also checks whether there exists another lane that matches those constraints. /// /// world position /// allowed lane types /// allowed vehicle types + /// allowed lane types for secondary lane + /// other vehicle types for secondary lane /// 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) { + public static bool FindCitizenPathPosition(Vector3 pos, NetInfo.LaneType laneTypes, VehicleInfo.VehicleType vehicleTypes, NetInfo.LaneType otherLaneTypes, VehicleInfo.VehicleType otherVehicleTypes, bool allowTransport, bool allowUnderground, out PathUnit.Position position) { // TODO move to ExtPathManager after harmony upgrade position = default(PathUnit.Position); float minDist = 1E+10f; @@ -304,15 +313,15 @@ public static bool FindCitizenPathPosition(Vector3 pos, NetInfo.LaneType laneTyp PathUnit.Position posB; float distA; float distB; - if (PathManager.FindPathPosition(pos, ItemClass.Service.Road, laneTypes, vehicleTypes, allowUnderground, false, Options.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { + if (ExtPathManager.Instance.FindPathPositionWithSpiralLoop(pos, ItemClass.Service.Road, laneTypes, vehicleTypes, otherLaneTypes, otherVehicleTypes, allowUnderground, false, Options.parkingAI ? 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.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { + if (ExtPathManager.Instance.FindPathPositionWithSpiralLoop(pos, ItemClass.Service.Beautification, laneTypes, vehicleTypes, otherLaneTypes, otherVehicleTypes, allowUnderground, false, Options.parkingAI ? 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.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { + if (allowTransport && ExtPathManager.Instance.FindPathPositionWithSpiralLoop(pos, ItemClass.Service.PublicTransport, laneTypes, vehicleTypes, otherLaneTypes, otherVehicleTypes, allowUnderground, false, Options.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { minDist = distA; position = posA; } diff --git a/TLM/TLM/Manager/AbstractGeometryObservingManager.cs b/TLM/TLM/Manager/AbstractGeometryObservingManager.cs index 3bf8768e2..5e08db5bf 100644 --- a/TLM/TLM/Manager/AbstractGeometryObservingManager.cs +++ b/TLM/TLM/Manager/AbstractGeometryObservingManager.cs @@ -1,120 +1,115 @@ -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.Data; -using TrafficManager.Util; +namespace TrafficManager.Manager { + using System; + using CSUtil.Commons; + using Geometry; + using State; + using Traffic.Data; + using Util; -namespace TrafficManager.Manager { - public abstract class AbstractGeometryObservingManager : AbstractCustomManager, IObserver { - private IDisposable geoUpdateUnsubscriber = null; + public abstract class AbstractGeometryObservingManager : AbstractCustomManager, IObserver { + private IDisposable geoUpdateUnsubscriber = null; - private object geoLock = new object(); + private object geoLock = new object(); - /// - /// Handles an invalid segment - /// - /// segment geometry - protected virtual void HandleInvalidSegment(ref ExtSegment seg) { } + /// + /// Handles an invalid segment + /// + /// segment geometry + protected virtual void HandleInvalidSegment(ref ExtSegment seg) { } - /// - /// Handles a valid segment - /// - /// segment geometry - protected virtual void HandleValidSegment(ref ExtSegment seg) { } + /// + /// Handles a valid segment + /// + /// segment geometry + protected virtual void HandleValidSegment(ref ExtSegment seg) { } - /// - /// Handles an invalid node - /// - /// node geometry - protected virtual void HandleInvalidNode(ushort nodeId, ref NetNode node) { } + /// + /// Handles an invalid node + /// + /// node geometry + protected virtual void HandleInvalidNode(ushort nodeId, ref NetNode node) { } - /// - /// Handles a valid node - /// - /// node geometry - protected virtual void HandleValidNode(ushort nodeId, ref NetNode node) { } + /// + /// Handles a valid node + /// + /// node geometry + protected virtual void HandleValidNode(ushort nodeId, ref NetNode node) { } - /// - /// Handles a segment replacement - /// - /// segment replacement - /// new segment end geometry - protected virtual void HandleSegmentEndReplacement(SegmentEndReplacement replacement, ref ExtSegmentEnd segEnd) { } + /// + /// Handles a segment replacement + /// + /// segment replacement + /// new segment end geometry + protected virtual void HandleSegmentEndReplacement(SegmentEndReplacement replacement, ref ExtSegmentEnd segEnd) { } - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - } + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + } - public override void OnLevelLoading() { - base.OnLevelLoading(); - geoUpdateUnsubscriber = Constants.ManagerFactory.GeometryManager.Subscribe(this); - } + public override void OnLevelLoading() { + base.OnLevelLoading(); + geoUpdateUnsubscriber = Constants.ManagerFactory.GeometryManager.Subscribe(this); + } - public override void OnLevelUnloading() { - base.OnLevelUnloading(); - if (geoUpdateUnsubscriber != null) { - geoUpdateUnsubscriber.Dispose(); - } - } + public override void OnLevelUnloading() { + base.OnLevelUnloading(); + if (geoUpdateUnsubscriber != null) { + geoUpdateUnsubscriber.Dispose(); + } + } - public void OnUpdate(GeometryUpdate update) { - if (update.segment != null) { - // Handle a segment update - ExtSegment seg = (ExtSegment)update.segment; - if (!seg.valid) { + public void OnUpdate(GeometryUpdate update) { + if (update.segment != null) { + // Handle a segment update + ExtSegment seg = (ExtSegment)update.segment; + if (!seg.valid) { #if DEBUGGEO - if (GlobalConfig.Instance.Debug.Switches[5]) - Log._Debug($"{this.GetType().Name}.HandleInvalidSegment({seg.segmentId})"); + if (GlobalConfig.Instance.Debug.Switches[5]) + Log._Debug($"{this.GetType().Name}.HandleInvalidSegment({seg.segmentId})"); #endif - HandleInvalidSegment(ref seg); - } else { + HandleInvalidSegment(ref seg); + } else { #if DEBUGGEO - if (GlobalConfig.Instance.Debug.Switches[5]) - Log._Debug($"{this.GetType().Name}.HandleValidSegment({seg.segmentId})"); + if (GlobalConfig.Instance.Debug.Switches[5]) + Log._Debug($"{this.GetType().Name}.HandleValidSegment({seg.segmentId})"); #endif - HandleValidSegment(ref seg); - } - } else if (update.nodeId != null) { - // Handle a node update - ushort nodeId = (ushort)update.nodeId; - Services.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { - if ((node.m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) == NetNode.Flags.Created) { + HandleValidSegment(ref seg); + } + } else if (update.nodeId != null) { + // Handle a node update + ushort nodeId = (ushort)update.nodeId; + Services.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { + if ((node.m_flags & (NetNode.Flags.Created | NetNode.Flags.Deleted)) == NetNode.Flags.Created) { #if DEBUGGEO - if (GlobalConfig.Instance.Debug.Switches[5]) - Log._Debug($"{this.GetType().Name}.HandleValidNode({nodeId})"); + if (GlobalConfig.Instance.Debug.Switches[5]) + Log._Debug($"{this.GetType().Name}.HandleValidNode({nodeId})"); #endif - HandleValidNode(nodeId, ref node); - } else { + HandleValidNode(nodeId, ref node); + } else { #if DEBUGGEO - if (GlobalConfig.Instance.Debug.Switches[5]) - Log._Debug($"{this.GetType().Name}.HandleInvalidNode({nodeId})"); + if (GlobalConfig.Instance.Debug.Switches[5]) + Log._Debug($"{this.GetType().Name}.HandleInvalidNode({nodeId})"); #endif - HandleInvalidNode(nodeId, ref node); - } - return true; - }); - } else { - // Handle a segment end replacement - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + HandleInvalidNode(nodeId, ref node); + } + return true; + }); + } else { + // Handle a segment end replacement + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; #if DEBUGGEO - if (GlobalConfig.Instance.Debug.Switches[5]) - Log._Debug($"{this.GetType().Name}.HandleSegmentReplacement({update.replacement.oldSegmentEndId} -> {update.replacement.newSegmentEndId})"); + if (GlobalConfig.Instance.Debug.Switches[5]) + Log._Debug($"{this.GetType().Name}.HandleSegmentReplacement({update.replacement.oldSegmentEndId} -> {update.replacement.newSegmentEndId})"); #endif - HandleSegmentEndReplacement(update.replacement, ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(update.replacement.newSegmentEndId.SegmentId, update.replacement.newSegmentEndId.StartNode)]); - } - } + HandleSegmentEndReplacement(update.replacement, ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(update.replacement.newSegmentEndId.SegmentId, update.replacement.newSegmentEndId.StartNode)]); + } + } - ~AbstractGeometryObservingManager() { - if (geoUpdateUnsubscriber != null) { - geoUpdateUnsubscriber.Dispose(); - } - } - } -} + ~AbstractGeometryObservingManager() { + if (geoUpdateUnsubscriber != null) { + geoUpdateUnsubscriber.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/AdvancedParkingManager.cs b/TLM/TLM/Manager/Impl/AdvancedParkingManager.cs index 8100d9fcd..f548c10dc 100644 --- a/TLM/TLM/Manager/Impl/AdvancedParkingManager.cs +++ b/TLM/TLM/Manager/Impl/AdvancedParkingManager.cs @@ -1,195 +1,191 @@ -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.Traffic.Enums; -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 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); +namespace TrafficManager.Manager.Impl { + using System; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Globalization; + using ColossalFramework.Math; + using CSUtil.Commons; + using Custom.AI; + using Custom.PathFinding; + using State; + using Traffic.Data; + using Traffic.Enums; + using UI; + using UnityEngine; + using Util; + + 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 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); - // 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; + // 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); - 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 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; - // update vehicle target position - vehManager.m_vehicles.m_buffer[vehicleId].m_targetPos0 = new Vector4(vehLanePos.x, vehLanePos.y, vehLanePos.z, 2f); + // unspawn citizen instance + instanceData.Unspawn(instanceID); - // 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}!"); + 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 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); + return true; + } else { + // failed to find a road position #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})"); + if (debug) + Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Could not find a road position for citizen instance {instanceID} near parked vehicle {parkedVehicleId}!"); #endif + return false; + } + } - return true; - } else { - // failed to find a road position + public ExtSoftPathState UpdateCitizenPathState(ushort citizenInstanceId, ref CitizenInstance citizenInstance, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, ref Citizen citizen, ExtPathState mainPathState) { #if DEBUG - if (debug) - Log._Debug($"CustomHumanAI.EnterParkedCar({instanceID}): Could not find a road position for citizen instance {instanceID} near parked vehicle {parkedVehicleId}!"); + 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 - return false; - } - } - - public ExtSoftPathState UpdateCitizenPathState(ushort citizenInstanceId, ref CitizenInstance citizenInstance, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, ref Citizen citizen, ExtPathState mainPathState) { + if (mainPathState == ExtPathState.Calculating) { + // main path is still calculating, do not check return path #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."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): still calculating main path. returning CALCULATING."); #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); - } + return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); + } - IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; // if (!Constants.ManagerFactory.ExtCitizenInstanceManager.IsValid(citizenInstanceId)) { // // no citizen @@ -200,102 +196,102 @@ public ExtSoftPathState UpdateCitizenPathState(ushort citizenInstanceId, ref Cit // return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); // } - if (mainPathState == ExtPathState.None || mainPathState == ExtPathState.Failed) { - // main path failed or non-existing + 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}."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): mainPathSate is {mainPathState}."); #endif - if (mainPathState == ExtPathState.Failed) { + if (mainPathState == ExtPathState.Failed) { #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Checking if path-finding may be repeated."); + 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 { + 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."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Resetting instance and returning FAILED."); #endif - extCitInstMan.Reset(ref extInstance); - return ExtSoftPathState.FailedHard; - } - } + extCitInstMan.Reset(ref extInstance); + return ExtSoftPathState.FailedHard; + } + } - // main path state is READY + // main path state is READY - // main path calculation succeeded: update return path state and check its state if necessary - extCitInstMan.UpdateReturnPathState(ref extInstance); + // main path calculation succeeded: update return path state and check its state if necessary + extCitInstMan.UpdateReturnPathState(ref extInstance); - bool success = true; - switch (extInstance.returnPathState) { - case ExtPathState.None: - default: - // no return path calculated: ignore + 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."); + 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 + 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."); + 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'. + 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."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Return path FAILED."); #endif - success = false; - break; - case ExtPathState.Ready: - // handle valid return path + success = false; + break; + case ExtPathState.Ready: + // handle valid return path #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Path is READY."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.UpdateCitizenPathState({citizenInstanceId}, ..., {mainPathState}): Path is READY."); #endif - break; - } + break; + } - extCitInstMan.ReleaseReturnPath(ref extInstance); + extCitInstMan.ReleaseReturnPath(ref extInstance); - 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); - } - } + 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) { - IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + public ExtSoftPathState UpdateCarPathState(ushort vehicleId, ref Vehicle vehicleData, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, ExtPathState mainPathState) { + IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; #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."); + 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 (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."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): still calculating main path. returning CALCULATING."); #endif - return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); - } + return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); + } // if (!driverExtInstance.IsValid()) { // // no driver @@ -306,346 +302,346 @@ public ExtSoftPathState UpdateCarPathState(ushort vehicleId, ref Vehicle vehicle // return mainPathState; // } - //ExtCitizenInstance driverExtInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(CustomPassengerCarAI.GetDriverInstance(vehicleId, ref vehicleData)); + //ExtCitizenInstance driverExtInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(CustomPassengerCarAI.GetDriverInstance(vehicleId, ref vehicleData)); - if (!extCitInstMan.IsValid(driverExtInstance.instanceId)) { - // no driver + if (!extCitInstMan.IsValid(driverExtInstance.instanceId)) { + // no driver #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): no driver found!"); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): no driver found!"); #endif - return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); - } + return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); + } - if (Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].vehicleType != ExtVehicleType.PassengerCar) { - // non-passenger cars are not handled + if (Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].vehicleType != ExtVehicleType.PassengerCar) { + // non-passenger cars are not handled #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): not a passenger car!"); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): not a passenger car!"); #endif - extCitInstMan.Reset(ref driverExtInstance); - return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); - } + extCitInstMan.Reset(ref driverExtInstance); + return ExtCitizenInstance.ConvertPathStateToSoftPathState(mainPathState); + } - if (mainPathState == ExtPathState.None || mainPathState == ExtPathState.Failed) { - // main path failed or non-existing: reset return path + 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}."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): mainPathSate is {mainPathState}."); #endif - if (mainPathState == ExtPathState.Failed) { + if (mainPathState == ExtPathState.Failed) { #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Checking if path-finding may be repeated."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Checking if path-finding may be repeated."); #endif - extCitInstMan.ReleaseReturnPath(ref driverExtInstance); - return OnCarPathFindFailure(vehicleId, ref vehicleData, ref driverInstance, ref driverExtInstance); - } else { + extCitInstMan.ReleaseReturnPath(ref driverExtInstance); + 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."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Resetting instance and returning FAILED."); #endif - extCitInstMan.Reset(ref driverExtInstance); - return ExtSoftPathState.FailedHard; - } - } + extCitInstMan.Reset(ref driverExtInstance); + return ExtSoftPathState.FailedHard; + } + } - // main path state is READY + // main path state is READY - // main path calculation succeeded: update return path state and check its state - extCitInstMan.UpdateReturnPathState(ref driverExtInstance); + // main path calculation succeeded: update return path state and check its state + extCitInstMan.UpdateReturnPathState(ref driverExtInstance); - switch (driverExtInstance.returnPathState) { - case ExtPathState.None: - default: - // no return path calculated: ignore + 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."); + 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 + 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."); + 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'. + 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."); + if (debug) + Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Return path {driverExtInstance.returnPathId} FAILED. Forcing path-finding to fail."); #endif - extCitInstMan.Reset(ref driverExtInstance); - return ExtSoftPathState.FailedHard; - case ExtPathState.Ready: - // handle valid return path - extCitInstMan.ReleaseReturnPath(ref driverExtInstance); + extCitInstMan.Reset(ref driverExtInstance); + return ExtSoftPathState.FailedHard; + case ExtPathState.Ready: + // handle valid return path + extCitInstMan.ReleaseReturnPath(ref driverExtInstance); #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.UpdateCarPathState({vehicleId}, ..., {mainPathState}): Path is ready for vehicle {vehicleId}, citizen instance {driverExtInstance.instanceId}! CurrentPathMode={driverExtInstance.pathMode}"); + 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; + 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 (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 (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}"); + 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; + } 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}"); + 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; + } 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}"); + 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; - } - } + } + return ExtSoftPathState.Ready; + } + } - public ParkedCarApproachState CitizenApproachingParkedCarSimulationStep(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, Vector3 physicsLodRefPos, ref VehicleParked parkedCar) { + 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; + 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 ((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."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.CheckCitizenReachedParkedCar({instanceId}): citizen instance {instanceId} is waiting for path-finding to complete."); #endif - return ParkedCarApproachState.None; - } + return ParkedCarApproachState.None; + } - //ExtCitizenInstance extInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(instanceId); + //ExtCitizenInstance extInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(instanceId); - if (extInstance.pathMode != ExtPathMode.ApproachingParkedCar && extInstance.pathMode != ExtPathMode.WalkingToParkedCar) { + 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})"); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): citizen instance {instanceId} is not reaching a parked car ({extInstance.pathMode})"); #endif - return ParkedCarApproachState.None; - } + return ParkedCarApproachState.None; + } - if ((instanceData.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.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!");*/ + /*if (fineDebug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingParkedCarSimulationStep({instanceId}): citizen instance {instanceId} is not spawned!");*/ #endif - return ParkedCarApproachState.None; - } + return ParkedCarApproachState.None; + } - Vector3 lastFramePos = instanceData.GetLastFramePosition(); - Vector3 doorPosition = parkedCar.GetClosestDoorPosition(parkedCar.m_position, VehicleInfo.DoorType.Enter); + 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 (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}."); + 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); + 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); + 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 + 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 (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; - } + 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); + CitizenInstance.Frame frameData = instanceData.GetLastFrameData(); + frameData.m_position = doorPosition; + instanceData.SetLastFrameData(frameData); - extInstance.pathMode = ExtPathMode.RequiresCarPath; + extInstance.pathMode = ExtPathMode.RequiresCarPath; - return ParkedCarApproachState.Approached; - } else if (doorSqrDist < extInstance.lastDistanceToParkedCar) { - extInstance.lastDistanceToParkedCar = doorSqrDist; - } + 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}"); + 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 = ExtPathMode.RequiresCarPath; + return ParkedCarApproachState.Approaching; + } else { + extInstance.pathMode = 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}"); + 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.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); - } - } - } + return ParkedCarApproachState.None; + } - 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); + 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); + } + } + } - Vector3 targetDiff = (Vector3)instanceData.m_targetPos - frameData.m_position; - Vector3 targetVelDiff = targetDiff - frameData.m_velocity; - float targetVelDiffMag = targetVelDiff.magnitude; + 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); - 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); - } - } + Vector3 targetDiff = (Vector3)instanceData.m_targetPos - frameData.m_position; + Vector3 targetVelDiff = targetDiff - frameData.m_velocity; + float targetVelDiffMag = targetVelDiff.magnitude; - public bool CitizenApproachingTargetSimulationStep(ushort instanceId, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance) { - IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + 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) { + IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; #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; + 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 ((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."); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is waiting for path-finding to complete."); #endif - return false; - } + return false; + } - //ExtCitizenInstance extInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(instanceId); + //ExtCitizenInstance extInstance = ExtCitizenInstanceManager.Instance.GetExtInstance(instanceId); - if (extInstance.pathMode != ExtPathMode.WalkingToTarget && - extInstance.pathMode != ExtPathMode.TaxiToTarget) { + if (extInstance.pathMode != ExtPathMode.WalkingToTarget && + extInstance.pathMode != ExtPathMode.TaxiToTarget) { #if DEBUG - if (fineDebug) - Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is not reaching target ({extInstance.pathMode})"); + if (fineDebug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is not reaching target ({extInstance.pathMode})"); #endif - return false; - } + return false; + } - if ((instanceData.m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) { + 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!");*/ + /*if (fineDebug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): citizen instance {instanceId} is not spawned!");*/ #endif - return false; - } + 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))) { - extCitInstMan.Reset(ref extInstance); + // 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))) { + extCitInstMan.Reset(ref extInstance); #if DEBUG - if (debug) - Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): Citizen instance {instanceId} reached target. CurrentDepartureMode={extInstance.pathMode}"); + if (debug) + Log._Debug($"AdvancedParkingManager.CitizenApproachingTargetSimulationStep({instanceId}): Citizen instance {instanceId} reached target. CurrentDepartureMode={extInstance.pathMode}"); #endif - return true; - } + return true; + } + + return false; + } - 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) { - IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; - IExtBuildingManager extBuildingMan = Constants.ManagerFactory.ExtBuildingManager; -#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}"); + /// + /// 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) { + IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + IExtBuildingManager extBuildingMan = Constants.ManagerFactory.ExtBuildingManager; +#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 // if (!extInstance.IsValid()) { @@ -656,1308 +652,1308 @@ protected ExtSoftPathState OnCitizenPathFindSuccess(ushort instanceId, ref Citiz // return ExtSoftPathState.FailedHard; // } - if (citizenData.m_vehicle == 0) { - // citizen does not already have a vehicle assigned + if (citizenData.m_vehicle == 0) { + // citizen does not already have a vehicle assigned - if (extInstance.pathMode == ExtPathMode.TaxiToTarget) { + 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."); + 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) { - extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); - } + // cim uses taxi + if (instanceData.m_sourceBuilding != 0) { + extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); + } - if (instanceData.m_targetBuilding != 0) { - extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); - } + if (instanceData.m_targetBuilding != 0) { + extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); + } - extCitizen.transportMode |= ExtTransportMode.PublicTransport; - return ExtSoftPathState.Ready; - } + extCitizen.transportMode |= 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; - } + 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; + 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 (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."); + 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; - } + 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) { + 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}"); + if (debug) + Log.Warning($"AdvancedParkingManager.OnCitizenPathFindSuccess({instanceId}): Unexpected path mode {extInstance.pathMode}! {extInstance}"); #endif - } + } - if (usesCar) { + 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."); + 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; + ushort sourceBuildingId = instanceData.m_sourceBuilding; + ushort homeId = Singleton.instance.m_citizens.m_buffer[instanceData.m_citizen].m_homeBuilding; - if (parkedVehicleId == 0) { + 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}"); + 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); - } + // 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 (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}"); + 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; + // 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}"); + 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(); + } 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}"); + 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; + // 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})."); + 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) { - extBuildingMan.ModifyParkingSpaceDemand(ref extBuildingMan.ExtBuildings[currentBuildingId], parkPos, GlobalConfig.Instance.ParkingAI.MinSpawnedCarParkingSpaceDemandDelta, GlobalConfig.Instance.ParkingAI.MaxSpawnedCarParkingSpaceDemandDelta); - } - } else { + if (currentBuildingId != 0) { + extBuildingMan.ModifyParkingSpaceDemand(ref extBuildingMan.ExtBuildings[currentBuildingId], 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}"); - } + 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) { - extBuildingMan.AddParkingSpaceDemand(ref extBuildingMan.ExtBuildings[currentBuildingId], GlobalConfig.Instance.ParkingAI.FailedSpawnParkingSpaceDemandIncrement); - } - } - } else { + if (parkReason == ParkingUnableReason.NoSpaceFound && currentBuildingId != 0) { + extBuildingMan.AddParkingSpaceDemand(ref extBuildingMan.ExtBuildings[currentBuildingId], 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."); - } + 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 (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()}"); + 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 + 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"); + 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 + 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."); + 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) { - extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); - } - if (instanceData.m_targetBuilding != 0) { - extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); - } - extCitizen.transportMode |= ExtTransportMode.PublicTransport; - } + if (usesPublicTransport) { + // decrease public tranport demand + if (instanceData.m_sourceBuilding != 0) { + extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); + } + if (instanceData.m_targetBuilding != 0) { + extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); + } + extCitizen.transportMode |= 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 + 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."); + 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 - - extCitInstMan.Reset(ref extInstance); - 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 (EnterParkedCar(instanceId, ref instanceData, parkedVehicleId, out vehicleId)) { - extInstance.pathMode = extInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget ? ExtPathMode.DrivingToTarget : ExtPathMode.DrivingToKnownParkPos; - extCitizen.transportMode |= 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 - - extCitInstMan.Reset(ref extInstance); - 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 - extCitInstMan.Reset(ref extInstance); - - if (usesPublicTransport) { - // decrease public tranport demand - if (instanceData.m_sourceBuilding != 0) { - extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); - } - if (instanceData.m_targetBuilding != 0) { - extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); - } - extCitizen.transportMode |= 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 - extCitInstMan.Reset(ref extInstance); - 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 - extCitInstMan.Reset(ref extInstance); - 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 - extCitInstMan.Reset(ref extInstance); - 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) { - IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; - IExtBuildingManager extBuildingMan = Constants.ManagerFactory.ExtBuildingManager; - -#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) { - extBuildingMan.AddPublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandIncrement, false); - } - if (instanceData.m_sourceBuilding != 0) { - extBuildingMan.AddPublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (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); - } - } - } + 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 + + extCitInstMan.Reset(ref extInstance); + 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 (EnterParkedCar(instanceId, ref instanceData, parkedVehicleId, out vehicleId)) { + extInstance.pathMode = extInstance.pathMode == ExtPathMode.CalculatingCarPathToTarget ? ExtPathMode.DrivingToTarget : ExtPathMode.DrivingToKnownParkPos; + extCitizen.transportMode |= 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 + + extCitInstMan.Reset(ref extInstance); + 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 + extCitInstMan.Reset(ref extInstance); + + if (usesPublicTransport) { + // decrease public tranport demand + if (instanceData.m_sourceBuilding != 0) { + extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, true); + } + if (instanceData.m_targetBuilding != 0) { + extBuildingMan.RemovePublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandUsageDecrement, false); + } + extCitizen.transportMode |= 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 + extCitInstMan.Reset(ref extInstance); + 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 + extCitInstMan.Reset(ref extInstance); + 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 + extCitInstMan.Reset(ref extInstance); + 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) { + IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + IExtBuildingManager extBuildingMan = Constants.ManagerFactory.ExtBuildingManager; + +#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) { + extBuildingMan.AddPublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_targetBuilding], (uint)GlobalConfig.Instance.ParkingAI.PublicTransportDemandIncrement, false); + } + if (instanceData.m_sourceBuilding != 0) { + extBuildingMan.AddPublicTransportDemand(ref extBuildingMan.ExtBuildings[instanceData.m_sourceBuilding], (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 + extCitInstMan.Reset(ref extInstance); + 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 = 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) { + IExtCitizenInstanceManager extCitizenInstanceManager = Constants.ManagerFactory.ExtCitizenInstanceManager; +#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) { + IExtBuildingManager extBuildingManager = Constants.ManagerFactory.ExtBuildingManager; + extBuildingManager.AddParkingSpaceDemand(ref extBuildingManager.ExtBuildings[driverInstanceData.m_targetBuilding], (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 + extCitizenInstanceManager.Reset(ref driverExtInstance); + 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; - // 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 - extCitInstMan.Reset(ref extInstance); - 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 = 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) { - IExtCitizenInstanceManager extCitizenInstanceManager = Constants.ManagerFactory.ExtCitizenInstanceManager; -#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) { - IExtBuildingManager extBuildingManager = Constants.ManagerFactory.ExtBuildingManager; - extBuildingManager.AddParkingSpaceDemand(ref extBuildingManager.ExtBuildings[driverInstanceData.m_targetBuilding], (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 - extCitizenInstanceManager.Reset(ref driverExtInstance); - 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; + bool found = false; #if BENCHMARK using (var bm = new Benchmark(null, "FindParkingSpaceInVicinity")) { #endif - found = AdvancedParkingManager.Instance.FindParkingSpaceInVicinity(refPos, Vector3.zero, parkedVehicle.Info, homeId, 0, maxDistance, out parkingSpaceLocation, out parkingSpaceLocationId, out parkPos, out parkRot, out parkOffset); + found = AdvancedParkingManager.Instance.FindParkingSpaceInVicinity(refPos, Vector3.zero, 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); - } + if (found) { + Singleton.instance.RemoveFromGrid(parkedVehicleId, ref parkedVehicle); + parkedVehicle.m_position = parkPos; + parkedVehicle.m_rotation = parkRot; + Singleton.instance.AddToGrid(parkedVehicleId, ref parkedVehicle); + } - return found; - } + 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) { - IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + 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) { + IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; #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 == extCitInstMan.GetCitizenId(extDriverInstance.instanceId)) && - (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; + 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 == extCitInstMan.GetCitizenId(extDriverInstance.instanceId)) && + (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); + calculateEndPos = true; + parkPos = default(Vector3); - if (!allowTourists) { - // TODO remove this from this method - uint citizenId = extCitInstMan.GetCitizenId(extDriverInstance.instanceId); - if (citizenId == 0 || - (Singleton.instance.m_citizens.m_buffer[citizenId].m_flags & Citizen.Flags.Tourist) != Citizen.Flags.None) - return false; - } + if (!allowTourists) { + // TODO remove this from this method + uint citizenId = extCitInstMan.GetCitizenId(extDriverInstance.instanceId); + 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."); + 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; + ExtParkingSpaceLocation knownParkingSpaceLocation; + ushort knownParkingSpaceLocationId; + Quaternion parkRot; + float parkOffset; - // find a free parking space - bool success = FindParkingSpaceInVicinity(endPos, Vector3.zero, vehicleInfo, homeId, vehicleId, goingHome ? GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToHome : GlobalConfig.Instance.ParkingAI.MaxParkedCarDistanceToBuilding, out knownParkingSpaceLocation, out knownParkingSpaceLocationId, out parkPos, out parkRot, out parkOffset); + // find a free parking space + bool success = FindParkingSpaceInVicinity(endPos, Vector3.zero, 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; + extDriverInstance.parkingSpaceLocation = knownParkingSpaceLocation; + extDriverInstance.parkingSpaceLocationId = knownParkingSpaceLocationId; - if (success) { + 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}"); + 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 (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}!"); + 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; + // 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; + //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}"); + 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 { + 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}"); + 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 (Constants.ManagerFactory.ExtPathManager.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; - } + return false; + } + } else if (knownParkingSpaceLocation == ExtParkingSpaceLocation.Building) { + // found a building with parking space + if (Constants.ManagerFactory.ExtPathManager.FindPathPositionWithSpiralLoop(parkPos, endPos, ItemClass.Service.Road, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car, false, false, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out endPathPos)) { + calculateEndPos = false; + } - //endPos = parkPos; + //endPos = parkPos; - //extDriverInstance.CurrentPathMode = successMode;// ExtCitizenInstance.PathMode.CalculatingKnownCarPath; + //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}"); + 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; - } + return true; + } + } + return false; + } + + public bool TrySpawnParkedPassengerCar(uint citizenId, ushort homeId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason) { #if DEBUG - if (debug) - Log._Debug($"[FAIL] Failed to spawn parked passenger car at road side for citizen {citizenId}"); + 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 - return false; - } - public bool TrySpawnParkedPassengerCarBuilding(uint citizenId, ushort homeId, Vector3 refPos, VehicleInfo vehicleInfo, out Vector3 parkPos, out ParkingUnableReason reason) { + 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; + 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}"); + 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; + 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, Vector3 searchDir, 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; + + Vector3 refPos = targetPos + searchDir * 16f; + + // TODO depending on simulation accuracy, disable searching for both road-side and building parking spaces + ushort parkingSpaceSegmentId = FindParkingSpaceAtRoadSide(0, refPos, 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, refPos, maxDist, maxDist, true, out buildingParkPos, out buildingParkRot, out buildingParkOffset); + + if (parkingSpaceSegmentId != 0) { + if (parkingBuildingId != 0) { + Randomizer rng = Services.SimulationService.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 = ExtParkingSpaceLocation.RoadSide; + parkingSpaceLocationId = parkingSpaceSegmentId; + return true; + } else { + // choose building parking space +#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 = 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 = 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 = ExtParkingSpaceLocation.Building; + parkingSpaceLocationId = parkingBuildingId; + return true; + } else { + //driverExtInstance.CurrentPathMode = ExtCitizenInstance.PathMode.AltParkFailed; + parkingSpaceLocation = 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; + } + } - 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); + return true; + }); + + if (foundSegmentId == 0) { #if DEBUG - if (fineDebug && homeId != 0) - Log._Debug($"[SUCCESS] Spawned parked passenger car next to building for citizen {citizenId}: {parkedVehicleId} @ {parkPos}"); + if (debug) + Log._Debug($"FindParkingSpaceRoadSide: Could not find a parking space for refPos {refPos}!"); #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, Vector3 searchDir, 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; - - Vector3 refPos = targetPos + searchDir * 16f; - - // TODO depending on simulation accuracy, disable searching for both road-side and building parking spaces - ushort parkingSpaceSegmentId = FindParkingSpaceAtRoadSide(0, refPos, 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, refPos, maxDist, maxDist, true, out buildingParkPos, out buildingParkRot, out buildingParkOffset); - - if (parkingSpaceSegmentId != 0) { - if (parkingBuildingId != 0) { - Randomizer rng = Services.SimulationService.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 = ExtParkingSpaceLocation.RoadSide; - parkingSpaceLocationId = parkingSpaceSegmentId; - return true; - } else { - // choose building parking space -#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 = 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 = 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 = ExtParkingSpaceLocation.Building; - parkingSpaceLocationId = parkingBuildingId; - return true; - } else { - //driverExtInstance.CurrentPathMode = ExtCitizenInstance.PathMode.AltParkFailed; - parkingSpaceLocation = 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; - } + return 0; + } - parkPos = myParkPos; - parkRot = myParkRot; - parkOffset = myParkOffset; + parkPos = myParkPos; + parkRot = myParkRot; + parkOffset = myParkOffset; - return foundSegmentId; - } + 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) { + 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]; + bool debug = GlobalConfig.Instance.Debug.Switches[22]; #endif - parkPos = Vector3.zero; - parkRot = Quaternion.identity; - parkOffset = -1f; + 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); + 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; + BuildingManager buildingMan = Singleton.instance; + Randomizer rng = Singleton.instance.m_randomizer; - ushort foundBuildingId = 0; - Vector3 myParkPos = parkPos; - Quaternion myParkRot = parkRot; - float myParkOffset = parkOffset; + 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; + 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}"); - } + 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; + 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}."); - } + 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 (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}!"); - */ + /*/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; + 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; - } - } + 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; - }); + return true; + }); - if (foundBuildingId == 0) { + if (foundBuildingId == 0) { #if DEBUG - if (debug && homeID != 0) - Log._Debug($"FindParkingSpaceBuilding: Could not find a parking space for homeID {homeID}!"); + if (debug && homeID != 0) + Log._Debug($"FindParkingSpaceBuilding: Could not find a parking space for homeID {homeID}!"); #endif - return 0; - } + return 0; + } - parkPos = myParkPos; - parkRot = myParkRot; - parkOffset = myParkOffset; + parkPos = myParkPos; + parkRot = myParkRot; + parkOffset = myParkOffset; - return foundBuildingId; - } + 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) { + 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]; + bool debug = GlobalConfig.Instance.Debug.Switches[22]; #endif - int buildingWidth = building.Width; - int buildingLength = building.Length; + 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); + // 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 ((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."); + if (debug) + Log._Debug($"Refusing to find parking space at building {buildingID}! Building is not created."); #endif - return false; - } + return false; + } - if ((building.m_problems & Notification.Problem.TurnedOff) != Notification.Problem.None) { + 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."); + if (debug) + Log._Debug($"Refusing to find parking space at building {buildingID}! Building is not active."); #endif - return false; - } + return false; + } - if ((building.m_flags & Building.Flags.Collapsed) != Building.Flags.None) { + 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."); + if (debug) + Log._Debug($"Refusing to find parking space at building {buildingID}! Building is collapsed."); #endif - return false; - } + return false; + } - Randomizer rng = Singleton.instance.m_randomizer; // 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 + 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}.");*/ + /*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; - } + 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; - } - } - } - } - } - } + 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 (result && propMinDistance <= maxDistance) { + maxDistance = propMinDistance; // NON-STOCK CODE #if DEBUG - if (debug) - Log._Debug($"Found parking space prop in range ({maxDistance}) at building {buildingID}."); + 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 (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}."); + 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; - } + Vector3 unspawnPos; + Vector3 unspawnTargetPos; + building.Info.m_buildingAI.CalculateUnspawnPosition(buildingID, ref building, ref Singleton.instance.m_randomizer, vehicleInfo, out unspawnPos, out unspawnTargetPos); - return false; - } - - public string EnrichLocalizedCitizenStatus(string ret, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen) { - //IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; - //if (extCitInstMan.IsValid(extInstance.instanceId)) { - 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 & ExtTransportMode.PublicTransport) != 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) { - //IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; - //if (extCitInstMan.IsValid(driverExtInstance.instanceId)) { - 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; - } - } -} + 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) { + //IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + //if (extCitInstMan.IsValid(extInstance.instanceId)) { + 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 & ExtTransportMode.PublicTransport) != 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) { + //IExtCitizenInstanceManager extCitInstMan = Constants.ManagerFactory.ExtCitizenInstanceManager; + //if (extCitInstMan.IsValid(driverExtInstance.instanceId)) { + 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; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/CustomSegmentLightsManager.cs b/TLM/TLM/Manager/Impl/CustomSegmentLightsManager.cs index 89c062e31..944761478 100644 --- a/TLM/TLM/Manager/Impl/CustomSegmentLightsManager.cs +++ b/TLM/TLM/Manager/Impl/CustomSegmentLightsManager.cs @@ -1,292 +1,290 @@ -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; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - 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) { - if (! Services.NetService.IsSegmentValid(segmentId)) { - return null; - } - - ushort nodeId = Services.NetService.GetSegmentNodeId(segmentId, startNode); - uint currentFrameIndex = Services.SimulationService.CurrentFrameIndex; - - RoadBaseAI.TrafficLightState vehicleLightState; - RoadBaseAI.TrafficLightState pedestrianLightState; - bool vehicles; - bool pedestrians; - - RoadBaseAI.GetTrafficLightState(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) { + using System.Collections.Generic; + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using CSUtil.Commons; + using Traffic.Data; + using Traffic.Enums; + using TrafficLight; + using TrafficLight.Impl; + using ExtVehicleType = global::TrafficManager.Traffic.ExtVehicleType; + + /// + /// 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) { + if (! Services.NetService.IsSegmentValid(segmentId)) { + return null; + } + + ushort nodeId = Services.NetService.GetSegmentNodeId(segmentId, startNode); + uint currentFrameIndex = Services.SimulationService.CurrentFrameIndex; + + RoadBaseAI.TrafficLightState vehicleLightState; + RoadBaseAI.TrafficLightState pedestrianLightState; + bool vehicles; + bool pedestrians; + + RoadBaseAI.GetTrafficLightState(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._Trace($"CustomTrafficLights.AddSegmentLights: Adding segment light: {segmentId} @ startNode={startNode}"); + Log._Trace($"CustomTrafficLights.AddSegmentLights: Adding segment light: {segmentId} @ startNode={startNode}"); #endif - if (!Services.NetService.IsSegmentValid(segmentId)) { - 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) { - bool? startNode = Services.NetService.IsStartNode(segmentId, nodeId); - if (startNode == null) { - return false; - } - - CustomSegment customSegment = CustomSegments[segmentId]; - if (customSegment == null) { - customSegment = new CustomSegment(); - CustomSegments[segmentId] = customSegment; - } - - if ((bool)startNode) { - customSegment.StartNodeLights = lights; - } else { - customSegment.EndNodeLights = lights; - } - return true; - } - - /// - /// Add custom traffic lights at the given node - /// - /// - public void AddNodeLights(ushort nodeId) { - if (! Services.NetService.IsNodeValid(nodeId)) { - return; - } - - Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { - AddSegmentLights(segmentId, segment.m_startNode == nodeId); - return true; - }); - } - - /// - /// Removes custom traffic lights at the given node - /// - /// - public void RemoveNodeLights(ushort nodeId) { - Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { - RemoveSegmentLight(segmentId, segment.m_startNode == nodeId); - return true; - }); - } - - /// - /// 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 (!Services.NetService.IsSegmentValid(segmentId)) { + 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) { + bool? startNode = Services.NetService.IsStartNode(segmentId, nodeId); + if (startNode == null) { + return false; + } + + CustomSegment customSegment = CustomSegments[segmentId]; + if (customSegment == null) { + customSegment = new CustomSegment(); + CustomSegments[segmentId] = customSegment; + } + + if ((bool)startNode) { + customSegment.StartNodeLights = lights; + } else { + customSegment.EndNodeLights = lights; + } + return true; + } + + /// + /// Add custom traffic lights at the given node + /// + /// + public void AddNodeLights(ushort nodeId) { + if (! Services.NetService.IsNodeValid(nodeId)) { + return; + } + + Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { + AddSegmentLights(segmentId, segment.m_startNode == nodeId); + return true; + }); + } + + /// + /// Removes custom traffic lights at the given node + /// + /// + public void RemoveNodeLights(ushort nodeId) { + Services.NetService.IterateNodeSegments(nodeId, delegate (ushort segmentId, ref NetSegment segment) { + RemoveSegmentLight(segmentId, segment.m_startNode == nodeId); + return true; + }); + } + + /// + /// 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._Trace($"Removing segment light: {segmentId} @ startNode={startNode}"); + Log._Trace($"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) { - bool? startNode = Services.NetService.IsStartNode(segmentId, nodeId); - if (startNode == null) { - return null; - } - return GetSegmentLights(segmentId, (bool)startNode, false); - } - - protected override void HandleInvalidSegment(ref ExtSegment seg) { - RemoveSegmentLights(seg.segmentId); - } - - public override void OnLevelUnloading() { - base.OnLevelUnloading(); - CustomSegments = new CustomSegment[NetManager.MAX_SEGMENT_COUNT]; - } - } -} + 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, + API.Traffic.Enums.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 (var e in sourceLights.CustomLights) { + var vehicleType = e.Key; + var targetLight = e.Value; + + if (otherLights.CustomLights.TryGetValue(vehicleType, out var sourceLight)) { + targetLight.CurrentMode = sourceLight.CurrentMode; + } + } + return true; + } + + public ICustomSegmentLights GetSegmentLights(ushort nodeId, ushort segmentId) { + bool? startNode = Services.NetService.IsStartNode(segmentId, nodeId); + if (startNode == null) { + return null; + } + return GetSegmentLights(segmentId, (bool)startNode, false); + } + + protected override void HandleInvalidSegment(ref ExtSegment seg) { + RemoveSegmentLights(seg.segmentId); + } + + public override void OnLevelUnloading() { + base.OnLevelUnloading(); + CustomSegments = new CustomSegment[NetManager.MAX_SEGMENT_COUNT]; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/ExtCitizenInstanceManager.cs b/TLM/TLM/Manager/Impl/ExtCitizenInstanceManager.cs index c20f82926..274d61ab8 100644 --- a/TLM/TLM/Manager/Impl/ExtCitizenInstanceManager.cs +++ b/TLM/TLM/Manager/Impl/ExtCitizenInstanceManager.cs @@ -1,1133 +1,1129 @@ -using ColossalFramework; -using ColossalFramework.Globalization; -using CSUtil.Commons; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using TrafficManager.Custom.AI; -using TrafficManager.Custom.PathFinding; -using TrafficManager.State; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -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 { get; private set; } - - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - Log._Debug($"Extended citizen instance data:"); - for (int i = 0; i < ExtInstances.Length; ++i) { - if (!IsValid((ushort)i)) { - continue; - } - Log._Debug($"Citizen instance {i}: {ExtInstances[i]}"); - } - } - - public void OnReleaseInstance(ushort instanceId) { - Reset(ref ExtInstances[instanceId]); - } - - public void ResetInstance(ushort instanceId) { - Reset(ref ExtInstances[instanceId]); - } - - 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) { - Reset(ref ExtInstances[i]); - } - } - - public String GetTouristLocalizedStatus(ushort instanceID, ref CitizenInstance data, out bool mayAddCustomStatus, out InstanceID target) { - if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != CitizenInstance.Flags.None) { - target = InstanceID.Empty; - mayAddCustomStatus = 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; - mayAddCustomStatus = 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; - mayAddCustomStatus = 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; - mayAddCustomStatus = 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; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); - } - } - } - - if ((data.m_flags & CitizenInstance.Flags.OnTour) != 0) { - target = InstanceID.Empty; - target.NetNode = targetBuilding; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING"); - } - - target = InstanceID.Empty; - target.NetNode = targetBuilding; - mayAddCustomStatus = 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; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_DRIVINGTO_OUTSIDE"); - } - - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = 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; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_OUTSIDE"); - } - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); - } - } - if (isOutsideConnection) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_GOINGTO_OUTSIDE"); - } - - if (hangsAround) { - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = false; - return Locale.Get("CITIZEN_STATUS_VISITING"); - } - - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_GOINGTO"); - } - - public String GetResidentLocalizedStatus(ushort instanceID, ref CitizenInstance data, out bool mayAddCustomStatus, out InstanceID target) { - if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != CitizenInstance.Flags.None) { - target = InstanceID.Empty; - mayAddCustomStatus = 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; - mayAddCustomStatus = 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; - mayAddCustomStatus = 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; - mayAddCustomStatus = 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; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); - } - } - } - - if ((data.m_flags & CitizenInstance.Flags.OnTour) != 0) { - target = InstanceID.Empty; - target.NetNode = targetBuilding; - mayAddCustomStatus = true; - return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING"); - } - - target = InstanceID.Empty; - target.NetNode = targetBuilding; - mayAddCustomStatus = 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; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_DRIVINGTO_OUTSIDE"); - } - - if (targetBuilding == homeId) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_DRIVINGTO_HOME"); - } else if (targetBuilding == workId) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get((!isStudent) ? "CITIZEN_STATUS_DRIVINGTO_WORK" : "CITIZEN_STATUS_DRIVINGTO_SCHOOL"); - } else { - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = 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; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_WAITING_TAXI"); - } - if (isOutsideConnection) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_OUTSIDE"); - } - if (targetBuilding == homeId) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_HOME"); - } - if (targetBuilding == workId) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get((!isStudent) ? "CITIZEN_STATUS_TRAVELLINGTO_WORK" : "CITIZEN_STATUS_TRAVELLINGTO_SCHOOL"); - } - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); - } - } - - if (isOutsideConnection) { - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_GOINGTO_OUTSIDE"); - } - - if (targetBuilding == homeId) { - if (hangsAround) { - target = InstanceID.Empty; - mayAddCustomStatus = false; - return Locale.Get("CITIZEN_STATUS_AT_HOME"); - } - - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_GOINGTO_HOME"); - } else if (targetBuilding == workId) { - if (hangsAround) { - target = InstanceID.Empty; - mayAddCustomStatus = false; - return Locale.Get((!isStudent) ? "CITIZEN_STATUS_AT_WORK" : "CITIZEN_STATUS_AT_SCHOOL"); - } - target = InstanceID.Empty; - mayAddCustomStatus = true; - return Locale.Get((!isStudent) ? "CITIZEN_STATUS_GOINGTO_WORK" : "CITIZEN_STATUS_GOINGTO_SCHOOL"); - } else { - if (hangsAround) { - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = false; - return Locale.Get("CITIZEN_STATUS_VISITING"); - } - target = InstanceID.Empty; - target.Building = targetBuilding; - mayAddCustomStatus = true; - return Locale.Get("CITIZEN_STATUS_GOINGTO"); - } - } - - public bool StartPathFind(ushort instanceID, ref CitizenInstance instanceData, ref ExtCitizenInstance extInstance, ref ExtCitizen extCitizen, Vector3 startPos, Vector3 endPos, VehicleInfo vehicleInfo, bool enableTransport, bool ignoreCost) { +namespace TrafficManager.Manager.Impl { + using System; + using System.Collections.Generic; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Globalization; + using CSUtil.Commons; + using Custom.PathFinding; + using State; + using Traffic.Data; + using Traffic.Enums; + using UnityEngine; + + 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 { get; private set; } + + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"Extended citizen instance data:"); + for (int i = 0; i < ExtInstances.Length; ++i) { + if (!IsValid((ushort)i)) { + continue; + } + Log._Debug($"Citizen instance {i}: {ExtInstances[i]}"); + } + } + + public void OnReleaseInstance(ushort instanceId) { + Reset(ref ExtInstances[instanceId]); + } + + public void ResetInstance(ushort instanceId) { + Reset(ref ExtInstances[instanceId]); + } + + 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) { + Reset(ref ExtInstances[i]); + } + } + + public String GetTouristLocalizedStatus(ushort instanceID, ref CitizenInstance data, out bool mayAddCustomStatus, out InstanceID target) { + if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != CitizenInstance.Flags.None) { + target = InstanceID.Empty; + mayAddCustomStatus = 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; + mayAddCustomStatus = 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; + mayAddCustomStatus = 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; + mayAddCustomStatus = 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; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); + } + } + } + + if ((data.m_flags & CitizenInstance.Flags.OnTour) != 0) { + target = InstanceID.Empty; + target.NetNode = targetBuilding; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING"); + } + + target = InstanceID.Empty; + target.NetNode = targetBuilding; + mayAddCustomStatus = 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; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_DRIVINGTO_OUTSIDE"); + } + + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = 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; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_OUTSIDE"); + } + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); + } + } + if (isOutsideConnection) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_GOINGTO_OUTSIDE"); + } + + if (hangsAround) { + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = false; + return Locale.Get("CITIZEN_STATUS_VISITING"); + } + + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_GOINGTO"); + } + + public String GetResidentLocalizedStatus(ushort instanceID, ref CitizenInstance data, out bool mayAddCustomStatus, out InstanceID target) { + if ((data.m_flags & (CitizenInstance.Flags.Blown | CitizenInstance.Flags.Floating)) != CitizenInstance.Flags.None) { + target = InstanceID.Empty; + mayAddCustomStatus = 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; + mayAddCustomStatus = 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; + mayAddCustomStatus = 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; + mayAddCustomStatus = 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; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); + } + } + } + + if ((data.m_flags & CitizenInstance.Flags.OnTour) != 0) { + target = InstanceID.Empty; + target.NetNode = targetBuilding; + mayAddCustomStatus = true; + return ColossalFramework.Globalization.Locale.Get("CITIZEN_STATUS_VISITING"); + } + + target = InstanceID.Empty; + target.NetNode = targetBuilding; + mayAddCustomStatus = 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; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_DRIVINGTO_OUTSIDE"); + } + + if (targetBuilding == homeId) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_DRIVINGTO_HOME"); + } else if (targetBuilding == workId) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get((!isStudent) ? "CITIZEN_STATUS_DRIVINGTO_WORK" : "CITIZEN_STATUS_DRIVINGTO_SCHOOL"); + } else { + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = 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; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_WAITING_TAXI"); + } + if (isOutsideConnection) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_OUTSIDE"); + } + if (targetBuilding == homeId) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO_HOME"); + } + if (targetBuilding == workId) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get((!isStudent) ? "CITIZEN_STATUS_TRAVELLINGTO_WORK" : "CITIZEN_STATUS_TRAVELLINGTO_SCHOOL"); + } + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_TRAVELLINGTO"); + } + } + + if (isOutsideConnection) { + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_GOINGTO_OUTSIDE"); + } + + if (targetBuilding == homeId) { + if (hangsAround) { + target = InstanceID.Empty; + mayAddCustomStatus = false; + return Locale.Get("CITIZEN_STATUS_AT_HOME"); + } + + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_GOINGTO_HOME"); + } else if (targetBuilding == workId) { + if (hangsAround) { + target = InstanceID.Empty; + mayAddCustomStatus = false; + return Locale.Get((!isStudent) ? "CITIZEN_STATUS_AT_WORK" : "CITIZEN_STATUS_AT_SCHOOL"); + } + target = InstanceID.Empty; + mayAddCustomStatus = true; + return Locale.Get((!isStudent) ? "CITIZEN_STATUS_GOINGTO_WORK" : "CITIZEN_STATUS_GOINGTO_SCHOOL"); + } else { + if (hangsAround) { + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = false; + return Locale.Get("CITIZEN_STATUS_VISITING"); + } + target = InstanceID.Empty; + target.Building = targetBuilding; + mayAddCustomStatus = true; + return Locale.Get("CITIZEN_STATUS_GOINGTO"); + } + } + + public bool StartPathFind(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}"); + 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; + // 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.parkingAI) { - 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 - */ + bool startsAtOutsideConnection = false; + if (Options.parkingAI) { + 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'."); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode} but no parked vehicle present. Change to 'None'."); #endif - Reset(ref extInstance); - } else { - /* - * Parked vehicle is present and citizen wants to reach it - * -> Prohibit car usage - */ + Reset(ref extInstance); + } 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'."); + 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 - */ + 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'."); + 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 - */ + 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'."); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode} but no parked vehicle present. Change to 'None'."); #endif - Reset(ref extInstance); - } else { - /* - * Citizen wants to drive to target and parked vehicle is present - * -> Force parked car usage - */ + Reset(ref extInstance); + } 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'."); + 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: + 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'."); + if (debug) + Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Citizen has CurrentPathMode={extInstance.pathMode}. Change to 'None'."); #endif - Reset(ref extInstance); - 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 - */ + Reset(ref extInstance); + 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'"); + 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 - */ + 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'"); + 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 - */ + 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"); + 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 - Reset(ref extInstance); - return false; - } - } else { - /* - * Citizen is located at a non-road outside connection - * -> Prohibit car usage - */ + Reset(ref extInstance); + 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'"); + 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 & ExtTransportMode.Car) != ExtTransportMode.None) { - /* - * citizen travelled by car - * -> return car back home - */ - extInstance.pathMode = ExtPathMode.CalculatingWalkingPathToParkedCar; - carUsageMode = CarUsagePolicy.Forbidden; + 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 & ExtTransportMode.Car) != ExtTransportMode.None) { + /* + * citizen travelled by car + * -> return car back home + */ + extInstance.pathMode = 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."); + 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 = ExtPathMode.CalculatingWalkingPathToParkedCar; - carUsageMode = CarUsagePolicy.Forbidden; + } 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 = 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."); + 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 == ExtPathMode.CalculatingWalkingPathToParkedCar) { - /* - * walk to parked car - * -> end position is parked car - */ - endPos = Singleton.instance.m_parkedVehicles.m_buffer[parkedVehicleId].m_position; + } + } + } + } + + /* + * 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 == 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}"); + 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}"); + 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.parkingAI) { - 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; + // 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.parkingAI) { + 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.parkingAI) { - // Parking AI + if (Options.parkingAI) { + // Parking AI - if (extInstance.pathMode == ExtPathMode.RequiresCarPath) { + if (extInstance.pathMode == ExtPathMode.RequiresCarPath) { #if DEBUG - if (debug) - Log._Debug($"CustomCitizenAI.ExtStartPathFind({instanceID}): Setting startPos={startPos} for citizen instance {instanceID}. CurrentDepartureMode={extInstance.pathMode}"); + 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 ( + 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}"); + 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) && - CalculateReturnPath(ref extInstance, parkPos, endPos) - ) { - // success - extInstance.pathMode = 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 + // 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) && + CalculateReturnPath(ref extInstance, parkPos, endPos) + ) { + // success + extInstance.pathMode = 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}"); + 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 (! 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}"); + 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 = 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. - */ - } + extInstance.pathMode = 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; - Constants.ManagerFactory.ExtPathManager.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; + /* + * 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; + Constants.ManagerFactory.ExtPathManager.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}"); + 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.parkingAI && (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.parkingAI && (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.parkingAI) { // 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) { + /* + * determine start & end path positions + */ + bool foundEndPos = !calculateEndPos || FindPathPosition(instanceID, ref instanceData, endPos, Options.parkingAI && (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.parkingAI && (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.parkingAI) { // 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."); + 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 - IExtBuildingManager extBuildingManager = Constants.ManagerFactory.ExtBuildingManager; - extBuildingManager.AddPublicTransportDemand(ref extBuildingManager.ExtBuildings[instanceData.m_sourceBuilding], 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.CustomCreatePath(out path, ref Singleton.instance.m_randomizer, args); - // NON-STOCK CODE END - - if (res) { + IExtBuildingManager extBuildingManager = Constants.ManagerFactory.ExtBuildingManager; + extBuildingManager.AddPublicTransportDemand(ref extBuildingManager.ExtBuildings[instanceData.m_sourceBuilding], 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.CustomCreatePath(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}"); + 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 (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.parkingAI) { - 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}"); - Reset(ref extInstance); - } + if (Options.parkingAI) { + 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}"); + Reset(ref extInstance); + } #endif - return false; - } - - public bool FindPathPosition(ushort instanceID, ref CitizenInstance instanceData, Vector3 pos, NetInfo.LaneType laneTypes, VehicleInfo.VehicleType vehicleTypes, bool allowUnderground, out PathUnit.Position position) { - 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.parkingAI ? 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.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { - minDist = distA; - position = posA; - } - if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None && PathManager.FindPathPosition(pos, ItemClass.Service.PublicTransport, laneTypes, vehicleTypes, allowUnderground, false, Options.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { - minDist = distA; - position = posA; - } - return position.m_segment != 0; - } - - public bool IsValid(ushort instanceId) { - return Constants.ServiceFactory.CitizenService.IsCitizenInstanceValid(instanceId); - } - - public uint GetCitizenId(ushort instanceId) { - uint ret = 0; - Constants.ServiceFactory.CitizenService.ProcessCitizenInstance(instanceId, delegate (ushort citInstId, ref CitizenInstance citizenInst) { - ret = citizenInst.m_citizen; - return true; - }); - return ret; - } - - /// - /// Releases the return path - /// - public void ReleaseReturnPath(ref ExtCitizenInstance extInstance) { + return false; + } + + public bool FindPathPosition(ushort instanceID, ref CitizenInstance instanceData, Vector3 pos, NetInfo.LaneType laneTypes, VehicleInfo.VehicleType vehicleTypes, bool allowUnderground, out PathUnit.Position position) { + 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.parkingAI ? 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.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { + minDist = distA; + position = posA; + } + if ((instanceData.m_flags & CitizenInstance.Flags.CannotUseTransport) == CitizenInstance.Flags.None && PathManager.FindPathPosition(pos, ItemClass.Service.PublicTransport, laneTypes, vehicleTypes, allowUnderground, false, Options.parkingAI ? GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance : 32f, out posA, out posB, out distA, out distB) && distA < minDist) { + minDist = distA; + position = posA; + } + return position.m_segment != 0; + } + + public bool IsValid(ushort instanceId) { + return Constants.ServiceFactory.CitizenService.IsCitizenInstanceValid(instanceId); + } + + public uint GetCitizenId(ushort instanceId) { + uint ret = 0; + Constants.ServiceFactory.CitizenService.ProcessCitizenInstance(instanceId, delegate (ushort citInstId, ref CitizenInstance citizenInst) { + ret = citizenInst.m_citizen; + return true; + }); + return ret; + } + + /// + /// Releases the return path + /// + public void ReleaseReturnPath(ref ExtCitizenInstance extInstance) { #if DEBUG - bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(extInstance.instanceId); - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(extInstance.instanceId); + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; #endif - if (extInstance.returnPathId != 0) { + if (extInstance.returnPathId != 0) { #if DEBUG - if (debug) - Log._Debug($"Releasing return path {extInstance.returnPathId} of citizen instance {extInstance.instanceId}. ReturnPathState={extInstance.returnPathState}"); + if (debug) + Log._Debug($"Releasing return path {extInstance.returnPathId} of citizen instance {extInstance.instanceId}. ReturnPathState={extInstance.returnPathState}"); #endif - Singleton.instance.ReleasePath(extInstance.returnPathId); - extInstance.returnPathId = 0; - } - extInstance.returnPathState = ExtPathState.None; - } + Singleton.instance.ReleasePath(extInstance.returnPathId); + extInstance.returnPathId = 0; + } + extInstance.returnPathState = ExtPathState.None; + } - /// - /// - /// - public void UpdateReturnPathState(ref ExtCitizenInstance extInstance) { + /// + /// + /// + public void UpdateReturnPathState(ref ExtCitizenInstance extInstance) { #if DEBUG - bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(extInstance.instanceId); - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(extInstance.instanceId); + 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 {extInstance.instanceId}"); + if (fineDebug) + Log._Debug($"ExtCitizenInstance.UpdateReturnPathState() called for citizen instance {extInstance.instanceId}"); #endif - if (extInstance.returnPathId != 0 && extInstance.returnPathState == ExtPathState.Calculating) { - byte returnPathFlags = CustomPathManager._instance.m_pathUnits.m_buffer[extInstance.returnPathId].m_pathFindFlags; - if ((returnPathFlags & PathUnit.FLAG_READY) != 0) { - extInstance.returnPathState = ExtPathState.Ready; + if (extInstance.returnPathId != 0 && extInstance.returnPathState == ExtPathState.Calculating) { + byte returnPathFlags = CustomPathManager._instance.m_pathUnits.m_buffer[extInstance.returnPathId].m_pathFindFlags; + if ((returnPathFlags & PathUnit.FLAG_READY) != 0) { + extInstance.returnPathState = ExtPathState.Ready; #if DEBUG - if (fineDebug) - Log._Debug($"CustomHumanAI.CustomSimulationStep: Return path {extInstance.returnPathId} SUCCEEDED. Flags={returnPathFlags}. Setting ReturnPathState={extInstance.returnPathState}"); + if (fineDebug) + Log._Debug($"CustomHumanAI.CustomSimulationStep: Return path {extInstance.returnPathId} SUCCEEDED. Flags={returnPathFlags}. Setting ReturnPathState={extInstance.returnPathState}"); #endif - } else if ((returnPathFlags & PathUnit.FLAG_FAILED) != 0) { - extInstance.returnPathState = ExtPathState.Failed; + } else if ((returnPathFlags & PathUnit.FLAG_FAILED) != 0) { + extInstance.returnPathState = ExtPathState.Failed; #if DEBUG - if (debug) - Log._Debug($"CustomHumanAI.CustomSimulationStep: Return path {extInstance.returnPathId} FAILED. Flags={returnPathFlags}. Setting ReturnPathState={extInstance.returnPathState}"); + if (debug) + Log._Debug($"CustomHumanAI.CustomSimulationStep: Return path {extInstance.returnPathId} FAILED. Flags={returnPathFlags}. Setting ReturnPathState={extInstance.returnPathState}"); #endif - } - } - } + } + } + } - public bool CalculateReturnPath(ref ExtCitizenInstance extInstance, Vector3 parkPos, Vector3 targetPos) { + public bool CalculateReturnPath(ref ExtCitizenInstance extInstance, Vector3 parkPos, Vector3 targetPos) { #if DEBUG - bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(extInstance.instanceId); - bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; - bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; + bool citDebug = GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(extInstance.instanceId); + bool debug = GlobalConfig.Instance.Debug.Switches[2] && citDebug; + bool fineDebug = GlobalConfig.Instance.Debug.Switches[4] && citDebug; #endif - ReleaseReturnPath(ref extInstance); - - 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 = 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.CustomCreatePath(out pathId, ref Singleton.instance.m_randomizer, args)) { + ReleaseReturnPath(ref extInstance); + + PathUnit.Position parkPathPos; + PathUnit.Position targetPathPos = default(PathUnit.Position); + bool foundParkPathPos = CustomPathManager.FindCitizenPathPosition(parkPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.None, VehicleInfo.VehicleType.None, false, false, out parkPathPos); + bool foundTargetPathPos = foundParkPathPos && CustomPathManager.FindCitizenPathPosition(targetPos, NetInfo.LaneType.Pedestrian, VehicleInfo.VehicleType.None, NetInfo.LaneType.None, VehicleInfo.VehicleType.None, false, false, out targetPathPos); + if (foundParkPathPos && foundTargetPathPos) { + + PathUnit.Position dummyPathPos = default(PathUnit.Position); + uint pathId; + PathCreationArgs args; + args.extPathType = 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.CustomCreatePath(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 {extInstance.instanceId}, path={pathId}, parkPathPos.segment={parkPathPos.m_segment}, parkPathPos.lane={parkPathPos.m_lane}, targetPathPos.segment={targetPathPos.m_segment}, targetPathPos.lane={targetPathPos.m_lane}"); + if (debug) + Log._Debug($"ExtCitizenInstance.CalculateReturnPath: Path-finding starts for return path of citizen instance {extInstance.instanceId}, path={pathId}, parkPathPos.segment={parkPathPos.m_segment}, parkPathPos.lane={parkPathPos.m_lane}, targetPathPos.segment={targetPathPos.m_segment}, targetPathPos.lane={targetPathPos.m_lane}"); #endif - extInstance.returnPathId = pathId; - extInstance.returnPathState = ExtPathState.Calculating; - return true; - } - } + extInstance.returnPathId = pathId; + extInstance.returnPathState = ExtPathState.Calculating; + return true; + } + } #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 {extInstance.instanceId}."); + if (debug) + Log._Debug($"ExtCitizenInstance.CalculateReturnPath: Could not find path position(s) for either the parking position or target position of citizen instance {extInstance.instanceId}."); #endif - return false; - } - - public void Reset(ref ExtCitizenInstance extInstance) { - //Flags = ExtFlags.None; - extInstance.pathMode = ExtPathMode.None; - extInstance.failedParkingAttempts = 0; - extInstance.parkingSpaceLocation = ExtParkingSpaceLocation.None; - extInstance.parkingSpaceLocationId = 0; - extInstance.lastDistanceToParkedCar = float.MaxValue; - extInstance.atOutsideConnection = false; - //extInstance.ParkedVehiclePosition = default(Vector3); - ReleaseReturnPath(ref extInstance); - } - - 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) { + return false; + } + + public void Reset(ref ExtCitizenInstance extInstance) { + //Flags = ExtFlags.None; + extInstance.pathMode = ExtPathMode.None; + extInstance.failedParkingAttempts = 0; + extInstance.parkingSpaceLocation = ExtParkingSpaceLocation.None; + extInstance.parkingSpaceLocationId = 0; + extInstance.lastDistanceToParkedCar = float.MaxValue; + extInstance.atOutsideConnection = false; + //extInstance.ParkedVehiclePosition = default(Vector3); + ReleaseReturnPath(ref extInstance); + } + + 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 == GetCitizenId(instanceId)) && - (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}"); + bool citDebug = + (GlobalConfig.Instance.Debug.CitizenId == 0 || GlobalConfig.Instance.Debug.CitizenId == GetCitizenId(instanceId)) && + (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; + 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}"); + if (debug) + Log._Debug($"ExtCitizenInstanceManager.IsAtOutsideConnection({instanceId}): ret={ret}"); #endif - return ret; - } - } -} + return ret; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/ExtCitizenManager.cs b/TLM/TLM/Manager/Impl/ExtCitizenManager.cs index 296c5d002..70610e22f 100644 --- a/TLM/TLM/Manager/Impl/ExtCitizenManager.cs +++ b/TLM/TLM/Manager/Impl/ExtCitizenManager.cs @@ -15,6 +15,8 @@ using static TrafficManager.Traffic.Data.ExtCitizenInstance; namespace TrafficManager.Manager.Impl { + using API.Traffic.Enums; + public class ExtCitizenManager : AbstractCustomManager, ICustomDataManager>, IExtCitizenManager { public static ExtCitizenManager Instance = new ExtCitizenManager(); diff --git a/TLM/TLM/Manager/Impl/ExtSegmentEndManager.cs b/TLM/TLM/Manager/Impl/ExtSegmentEndManager.cs index badb06356..aad7661f1 100644 --- a/TLM/TLM/Manager/Impl/ExtSegmentEndManager.cs +++ b/TLM/TLM/Manager/Impl/ExtSegmentEndManager.cs @@ -20,7 +20,7 @@ public class ExtSegmentEndManager : AbstractCustomManager, IExtSegmentEndManager static ExtSegmentEndManager() { Instance = new ExtSegmentEndManager(); } - + /// /// All additional data for segment ends /// @@ -44,7 +44,7 @@ public string GenerateVehicleChainDebugInfo(ushort segmentId, bool startNode) { ret += $" -> {vehicleId} (seg: {Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].currentSegmentId}@{Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].currentStartNode} , adj: {Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].previousVehicleIdOnSegment}..{Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].nextVehicleIdOnSegment})"; vehicleId = Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[vehicleId].nextVehicleIdOnSegment; - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } @@ -64,7 +64,7 @@ protected void Reset(ref ExtSegmentEnd extSegmentEnd) { while (extSegmentEnd.firstVehicleId != 0) { extVehicleMan.Unlink(ref extVehicleMan.ExtVehicles[extSegmentEnd.firstVehicleId]); - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } @@ -111,7 +111,7 @@ public uint GetRegisteredVehicleCount(ref ExtSegmentEnd end) { ++ret; vehicleId = vehStateManager.ExtVehicles[vehicleId].nextVehicleIdOnSegment; - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } @@ -150,7 +150,7 @@ public ArrowDirection GetDirection(ref ExtSegmentEnd sourceEnd, ushort targetSeg protected ArrowDirection CalculateArrowDirection(Vector3 sourceDir, Vector3 targetDir) { sourceDir.y = 0; sourceDir.Normalize(); - + targetDir.y = 0; targetDir.Normalize(); float c = Vector3.Cross(sourceDir, targetDir).y; diff --git a/TLM/TLM/Manager/Impl/ExtVehicleManager.cs b/TLM/TLM/Manager/Impl/ExtVehicleManager.cs index 213699760..12e6e9e3f 100644 --- a/TLM/TLM/Manager/Impl/ExtVehicleManager.cs +++ b/TLM/TLM/Manager/Impl/ExtVehicleManager.cs @@ -1,94 +1,90 @@ -using ColossalFramework; -using ColossalFramework.Math; -using CSUtil.Commons; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using TrafficManager.Custom.AI; -using TrafficManager.State; -using TrafficManager.State.ConfigData; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Data; -using TrafficManager.Traffic.Enums; -using UnityEngine; - -namespace TrafficManager.Manager.Impl { - public class ExtVehicleManager : AbstractCustomManager, IExtVehicleManager { - public static readonly ExtVehicleManager Instance = new ExtVehicleManager(); - - public const int STATE_UPDATE_SHIFT = 6; - public const int JUNCTION_RECHECK_SHIFT = 4; - public const uint MAX_TIMED_RAND = 100; - 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 ExtVehicle[] ExtVehicles { get; private set; } = null; - - static ExtVehicleManager() { - Instance = new ExtVehicleManager(); - } - - protected override void InternalPrintDebugInfo() { - base.InternalPrintDebugInfo(); - Log._Debug($"Ext. vehicles:"); - for (int i = 0; i < ExtVehicles.Length; ++i) { - if ((ExtVehicles[i].flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { - continue; - } - Log._Debug($"Vehicle {i}: {ExtVehicles[i]}"); - } - } - - private ExtVehicleManager() { - ExtVehicles = new ExtVehicle[VehicleManager.MAX_VEHICLE_COUNT]; - for (uint i = 0; i < VehicleManager.MAX_VEHICLE_COUNT; ++i) { - ExtVehicles[i] = new ExtVehicle((ushort)i); - } - } - - public void SetJunctionTransitState(ref ExtVehicle extVehicle, VehicleJunctionTransitState transitState) { - if (transitState != extVehicle.junctionTransitState) { - extVehicle.junctionTransitState = transitState; - extVehicle.lastTransitStateUpdate = Now(); - } - } - - public ushort GetDriverInstanceId(ushort vehicleId, ref Vehicle data) { - // (stock code from PassengerCarAI.GetDriverInstance) - CitizenManager citizenManager = Singleton.instance; - uint citizenUnitId = data.m_citizenUnits; - int numIter = 0; - while (citizenUnitId != 0) { - uint nextCitizenUnitId = citizenManager.m_units.m_buffer[citizenUnitId].m_nextUnit; - for (int i = 0; i < 5; i++) { - uint citizenId = citizenManager.m_units.m_buffer[citizenUnitId].GetCitizen(i); - if (citizenId != 0) { - ushort citizenInstanceId = citizenManager.m_citizens.m_buffer[citizenId].m_instance; - if (citizenInstanceId != 0) { - return citizenInstanceId; - } - } - } - citizenUnitId = nextCitizenUnitId; - if (++numIter > CitizenManager.MAX_UNIT_COUNT) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - return 0; - } - - public void LogTraffic(ushort vehicleId, ref Vehicle vehicle) { - LogTraffic(vehicleId, ref vehicle, ref ExtVehicles[vehicleId]); - } - - protected void LogTraffic(ushort vehicleId, ref Vehicle vehicle, ref ExtVehicle extVehicle) { - if (extVehicle.currentSegmentId == 0) { - return; - } +namespace TrafficManager.Manager.Impl { + using System; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Math; + using CSUtil.Commons; + using State; + using State.ConfigData; + using Traffic.Data; + using UnityEngine; + + public class ExtVehicleManager : AbstractCustomManager, IExtVehicleManager { + public static readonly ExtVehicleManager Instance = new ExtVehicleManager(); + + public const int STATE_UPDATE_SHIFT = 6; + public const int JUNCTION_RECHECK_SHIFT = 4; + public const uint MAX_TIMED_RAND = 100; + 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 ExtVehicle[] ExtVehicles { get; private set; } = null; + + static ExtVehicleManager() { + Instance = new ExtVehicleManager(); + } + + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"Ext. vehicles:"); + for (int i = 0; i < ExtVehicles.Length; ++i) { + if ((ExtVehicles[i].flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { + continue; + } + Log._Debug($"Vehicle {i}: {ExtVehicles[i]}"); + } + } + + private ExtVehicleManager() { + ExtVehicles = new ExtVehicle[Constants.ServiceFactory.VehicleService.MaxVehicleCount]; + for (uint i = 0; i < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++i) { + ExtVehicles[i] = new ExtVehicle((ushort)i); + } + } + + public void SetJunctionTransitState(ref ExtVehicle extVehicle, VehicleJunctionTransitState transitState) { + if (transitState != extVehicle.junctionTransitState) { + extVehicle.junctionTransitState = transitState; + extVehicle.lastTransitStateUpdate = Now(); + } + } + + public ushort GetDriverInstanceId(ushort vehicleId, ref Vehicle data) { + // (stock code from PassengerCarAI.GetDriverInstance) + CitizenManager citizenManager = Singleton.instance; + uint citizenUnitId = data.m_citizenUnits; + int numIter = 0; + while (citizenUnitId != 0) { + uint nextCitizenUnitId = citizenManager.m_units.m_buffer[citizenUnitId].m_nextUnit; + for (int i = 0; i < 5; i++) { + uint citizenId = citizenManager.m_units.m_buffer[citizenUnitId].GetCitizen(i); + if (citizenId != 0) { + ushort citizenInstanceId = citizenManager.m_citizens.m_buffer[citizenId].m_instance; + if (citizenInstanceId != 0) { + return citizenInstanceId; + } + } + } + citizenUnitId = nextCitizenUnitId; + if (++numIter > CitizenManager.MAX_UNIT_COUNT) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + return 0; + } + + public void LogTraffic(ushort vehicleId, ref Vehicle vehicle) { + LogTraffic(vehicleId, ref vehicle, ref ExtVehicles[vehicleId]); + } + + protected void LogTraffic(ushort vehicleId, ref Vehicle vehicle, ref ExtVehicle extVehicle) { + if (extVehicle.currentSegmentId == 0) { + return; + } #if MEASUREDENSITY ushort length = (ushort)state.totalLength; if (length == 0) { @@ -96,695 +92,698 @@ protected void LogTraffic(ushort vehicleId, ref Vehicle vehicle, ref ExtVehicle } #endif - if (Options.advancedAI) { - TrafficMeasurementManager.Instance.AddTraffic(extVehicle.currentSegmentId, extVehicle.currentLaneIndex + if (Options.advancedAI) { + TrafficMeasurementManager.Instance.AddTraffic(extVehicle.currentSegmentId, extVehicle.currentLaneIndex #if MEASUREDENSITY , length #endif - , (ushort)vehicle.GetLastFrameVelocity().magnitude); - } - } + , (ushort)vehicle.GetLastFrameVelocity().magnitude); + } + } - public void OnCreateVehicle(ushort vehicleId, ref Vehicle vehicleData) { - OnReleaseVehicle(vehicleId, ref vehicleData); - if ((vehicleData.m_flags & (Vehicle.Flags.Created | Vehicle.Flags.Deleted)) != Vehicle.Flags.Created || - (vehicleData.Info.m_vehicleType & VEHICLE_TYPES) == VehicleInfo.VehicleType.None) { + public void OnCreateVehicle(ushort vehicleId, ref Vehicle vehicleData) { + OnReleaseVehicle(vehicleId, ref 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($"ExtVehicleManager.OnCreateVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnCreateVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}"); #endif - return; - } + return; + } #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnCreateVehicle({vehicleId}): calling OnCreate for vehicle {vehicleId}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnCreateVehicle({vehicleId}): calling OnCreate for vehicle {vehicleId}"); #endif - OnCreate(ref ExtVehicles[vehicleId], ref vehicleData); - } + OnCreate(ref ExtVehicles[vehicleId], ref vehicleData); + } - public 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) { + public 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($"ExtVehicleManager.OnStartPathFind({vehicleId}, {vehicleType}): unhandled vehicle! type: {vehicleData.Info.m_vehicleType}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnStartPathFind({vehicleId}, {vehicleType}): unhandled vehicle! type: {vehicleData.Info.m_vehicleType}"); #endif - return ExtVehicleType.None; - } + return ExtVehicleType.None; + } - ExtVehicleType ret = OnStartPathFind(ref ExtVehicles[vehicleId], ref vehicleData, vehicleType); + ExtVehicleType ret = OnStartPathFind(ref ExtVehicles[vehicleId], ref vehicleData, vehicleType); - ushort connectedVehicleId = vehicleId; - while (true) { - connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; + ushort connectedVehicleId = vehicleId; + while (true) { + connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; - if (connectedVehicleId == 0) { - break; - } + if (connectedVehicleId == 0) { + break; + } #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnStartPathFind({vehicleId}, {vehicleType}): overriding vehicle type for connected vehicle {connectedVehicleId} of vehicle {vehicleId} (trailing)"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnStartPathFind({vehicleId}, {vehicleType}): overriding vehicle type for connected vehicle {connectedVehicleId} of vehicle {vehicleId} (trailing)"); #endif - OnStartPathFind(ref ExtVehicles[connectedVehicleId], ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId], vehicleType); - } + OnStartPathFind(ref ExtVehicles[connectedVehicleId], ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId], vehicleType); + } - return ret; - } + return ret; + } - public 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) { + public 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($"ExtVehicleManager.OnSpawnVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}, path: {vehicleData.m_path}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnSpawnVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}, path: {vehicleData.m_path}"); #endif - return; - } + return; + } #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnSpawnVehicle({vehicleId}): calling OnSpawn for vehicle {vehicleId}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnSpawnVehicle({vehicleId}): calling OnSpawn for vehicle {vehicleId}"); #endif - ushort connectedVehicleId = vehicleId; - while (connectedVehicleId != 0) { - OnSpawn(ref ExtVehicles[connectedVehicleId], ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId]); - connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; - } - } + ushort connectedVehicleId = vehicleId; + while (connectedVehicleId != 0) { + OnSpawn(ref ExtVehicles[connectedVehicleId], ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId]); + connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; + } + } - public void UpdateVehiclePosition(ushort vehicleId, ref Vehicle vehicleData) { - ushort connectedVehicleId = vehicleId; - while (connectedVehicleId != 0) { - UpdateVehiclePosition(ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId], ref ExtVehicles[connectedVehicleId]); - connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; - } - } + public void UpdateVehiclePosition(ushort vehicleId, ref Vehicle vehicleData) { + ushort connectedVehicleId = vehicleId; + while (connectedVehicleId != 0) { + UpdateVehiclePosition(ref Singleton.instance.m_vehicles.m_buffer[connectedVehicleId], ref ExtVehicles[connectedVehicleId]); + connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; + } + } - protected void UpdateVehiclePosition(ref Vehicle vehicleData, ref ExtVehicle extVehicle) { + protected void UpdateVehiclePosition(ref Vehicle vehicleData, ref ExtVehicle extVehicle) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdateVehiclePosition({extVehicle.vehicleId}) called"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdateVehiclePosition({extVehicle.vehicleId}) called"); #endif - if (vehicleData.m_path == 0 || (vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0 || - (extVehicle.lastPathId == vehicleData.m_path && extVehicle.lastPathPositionIndex == vehicleData.m_pathPositionIndex) - ) { - return; - } + if (vehicleData.m_path == 0 || (vehicleData.m_flags & Vehicle.Flags.WaitingPath) != 0 || + (extVehicle.lastPathId == vehicleData.m_path && extVehicle.lastPathPositionIndex == vehicleData.m_pathPositionIndex) + ) { + return; + } - PathManager pathManager = Singleton.instance; - IExtSegmentEndManager segmentEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + PathManager pathManager = Singleton.instance; + IExtSegmentEndManager segmentEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - // update vehicle position for timed traffic lights and priority signs - int coarsePathPosIndex = vehicleData.m_pathPositionIndex >> 1; - PathUnit.Position curPathPos = pathManager.m_pathUnits.m_buffer[vehicleData.m_path].GetPosition(coarsePathPosIndex); - PathUnit.Position nextPathPos = default(PathUnit.Position); - pathManager.m_pathUnits.m_buffer[vehicleData.m_path].GetNextPosition(coarsePathPosIndex, out nextPathPos); - bool startNode = IsTransitNodeCurStartNode(ref curPathPos, ref nextPathPos); - UpdatePosition(ref extVehicle, ref vehicleData, ref segmentEndMan.ExtSegmentEnds[segmentEndMan.GetIndex(curPathPos.m_segment, startNode)], ref curPathPos, ref nextPathPos); - } + // update vehicle position for timed traffic lights and priority signs + int coarsePathPosIndex = vehicleData.m_pathPositionIndex >> 1; + PathUnit.Position curPathPos = pathManager.m_pathUnits.m_buffer[vehicleData.m_path].GetPosition(coarsePathPosIndex); + PathUnit.Position nextPathPos = default(PathUnit.Position); + pathManager.m_pathUnits.m_buffer[vehicleData.m_path].GetNextPosition(coarsePathPosIndex, out nextPathPos); + bool startNode = IsTransitNodeCurStartNode(ref curPathPos, ref nextPathPos); + UpdatePosition(ref extVehicle, ref vehicleData, ref segmentEndMan.ExtSegmentEnds[segmentEndMan.GetIndex(curPathPos.m_segment, startNode)], ref curPathPos, ref nextPathPos); + } - public 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) { + public 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($"ExtVehicleManager.OnDespawnVehicle({vehicleId}): unhandled vehicle! type: {vehicleData.Info.m_vehicleType}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnDespawnVehicle({vehicleId}): unhandled vehicle! type: {vehicleData.Info.m_vehicleType}"); #endif - return; - } + return; + } - ushort connectedVehicleId = vehicleId; - while (connectedVehicleId != 0) { + ushort connectedVehicleId = vehicleId; + while (connectedVehicleId != 0) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnDespawnVehicle({vehicleId}): calling OnDespawn for connected vehicle {connectedVehicleId} of vehicle {vehicleId} (trailing)"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnDespawnVehicle({vehicleId}): calling OnDespawn for connected vehicle {connectedVehicleId} of vehicle {vehicleId} (trailing)"); #endif - OnDespawn(ref ExtVehicles[connectedVehicleId]); - connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; - } - } + OnDespawn(ref ExtVehicles[connectedVehicleId]); + connectedVehicleId = Singleton.instance.m_vehicles.m_buffer[connectedVehicleId].m_trailingVehicle; + } + } - public void OnReleaseVehicle(ushort vehicleId, ref Vehicle vehicleData) { + public void OnReleaseVehicle(ushort vehicleId, ref Vehicle vehicleData) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnReleaseVehicle({vehicleId}) called."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.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 ((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($"ExtVehicleManager.OnReleaseVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnReleaseVehicle({vehicleId}): unhandled vehicle! flags: {vehicleData.m_flags}, type: {vehicleData.Info.m_vehicleType}"); #endif - return; - } + return; + } #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnReleaseVehicle({vehicleId}): calling OnRelease for vehicle {vehicleId}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnReleaseVehicle({vehicleId}): calling OnRelease for vehicle {vehicleId}"); #endif - OnRelease(ref ExtVehicles[vehicleId], ref vehicleData); - } + OnRelease(ref ExtVehicles[vehicleId], ref vehicleData); + } - public void Unlink(ref ExtVehicle extVehicle) { + public void Unlink(ref ExtVehicle extVehicle) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.Unlink({extVehicle.vehicleId}) called: Unlinking vehicle from all segment ends\nstate:{extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.Unlink({extVehicle.vehicleId}) called: Unlinking vehicle from all segment ends\nstate:{extVehicle}"); - ushort prevSegmentId = extVehicle.currentSegmentId; - bool prevStartNode = extVehicle.currentStartNode; + ushort prevSegmentId = extVehicle.currentSegmentId; + bool prevStartNode = extVehicle.currentStartNode; #endif - extVehicle.lastPositionUpdate = Now(); + extVehicle.lastPositionUpdate = Now(); - if (extVehicle.previousVehicleIdOnSegment != 0) { - ExtVehicles[extVehicle.previousVehicleIdOnSegment].nextVehicleIdOnSegment = extVehicle.nextVehicleIdOnSegment; - } else if (extVehicle.currentSegmentId != 0) { - IExtSegmentEndManager segmentEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - int endIndex = segmentEndMan.GetIndex(extVehicle.currentSegmentId, extVehicle.currentStartNode); - if (segmentEndMan.ExtSegmentEnds[endIndex].firstVehicleId == extVehicle.vehicleId) { - segmentEndMan.ExtSegmentEnds[endIndex].firstVehicleId = extVehicle.nextVehicleIdOnSegment; - } else { - Log.Error($"ExtVehicleManager.Unlink({extVehicle.vehicleId}): Unexpected first vehicle on segment {extVehicle.currentSegmentId}: {segmentEndMan.ExtSegmentEnds[endIndex].firstVehicleId}"); - } - } + if (extVehicle.previousVehicleIdOnSegment != 0) { + ExtVehicles[extVehicle.previousVehicleIdOnSegment].nextVehicleIdOnSegment = extVehicle.nextVehicleIdOnSegment; + } else if (extVehicle.currentSegmentId != 0) { + IExtSegmentEndManager segmentEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + int endIndex = segmentEndMan.GetIndex(extVehicle.currentSegmentId, extVehicle.currentStartNode); + if (segmentEndMan.ExtSegmentEnds[endIndex].firstVehicleId == extVehicle.vehicleId) { + segmentEndMan.ExtSegmentEnds[endIndex].firstVehicleId = extVehicle.nextVehicleIdOnSegment; + } else { + Log.Error($"ExtVehicleManager.Unlink({extVehicle.vehicleId}): Unexpected first vehicle on segment {extVehicle.currentSegmentId}: {segmentEndMan.ExtSegmentEnds[endIndex].firstVehicleId}"); + } + } - if (extVehicle.nextVehicleIdOnSegment != 0) { - ExtVehicles[extVehicle.nextVehicleIdOnSegment].previousVehicleIdOnSegment = extVehicle.previousVehicleIdOnSegment; - } + if (extVehicle.nextVehicleIdOnSegment != 0) { + ExtVehicles[extVehicle.nextVehicleIdOnSegment].previousVehicleIdOnSegment = extVehicle.previousVehicleIdOnSegment; + } - extVehicle.nextVehicleIdOnSegment = 0; - extVehicle.previousVehicleIdOnSegment = 0; + extVehicle.nextVehicleIdOnSegment = 0; + extVehicle.previousVehicleIdOnSegment = 0; - extVehicle.currentSegmentId = 0; - extVehicle.currentStartNode = false; - extVehicle.currentLaneIndex = 0; + extVehicle.currentSegmentId = 0; + extVehicle.currentStartNode = false; + extVehicle.currentLaneIndex = 0; - extVehicle.lastPathId = 0; - extVehicle.lastPathPositionIndex = 0; + extVehicle.lastPathId = 0; + extVehicle.lastPathPositionIndex = 0; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[12]) { - IExtSegmentEndManager segmentEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - Log._Debug($"ExtVehicleManager.Unlink({extVehicle.vehicleId}) finished: Unlinked vehicle from all segment ends\nstate:{extVehicle}\nold segment end vehicle chain: {ExtSegmentEndManager.Instance.GenerateVehicleChainDebugInfo(prevSegmentId, prevStartNode)}"); - } + if (GlobalConfig.Instance.Debug.Switches[12]) { + IExtSegmentEndManager segmentEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + Log._Debug($"ExtVehicleManager.Unlink({extVehicle.vehicleId}) finished: Unlinked vehicle from all segment ends\nstate:{extVehicle}\nold segment end vehicle chain: {ExtSegmentEndManager.Instance.GenerateVehicleChainDebugInfo(prevSegmentId, prevStartNode)}"); + } #endif - } + } - /// - /// Links the given vehicle to the given segment end. - /// - /// vehicle - /// ext. segment end - /// lane index - private void Link(ref ExtVehicle extVehicle, ref ExtSegmentEnd end, byte laneIndex) { + /// + /// Links the given vehicle to the given segment end. + /// + /// vehicle + /// ext. segment end + /// lane index + private void Link(ref ExtVehicle extVehicle, ref ExtSegmentEnd end, byte laneIndex) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.Link({extVehicle.vehicleId}) called: Linking vehicle to segment end {end}\nstate:{extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.Link({extVehicle.vehicleId}) called: Linking vehicle to segment end {end}\nstate:{extVehicle}"); #endif - extVehicle.currentSegmentId = end.segmentId; - extVehicle.currentStartNode = end.startNode; - extVehicle.currentLaneIndex = laneIndex; + extVehicle.currentSegmentId = end.segmentId; + extVehicle.currentStartNode = end.startNode; + extVehicle.currentLaneIndex = laneIndex; - ushort oldFirstRegVehicleId = end.firstVehicleId; - if (oldFirstRegVehicleId != 0) { - ExtVehicles[oldFirstRegVehicleId].previousVehicleIdOnSegment = extVehicle.vehicleId; - extVehicle.nextVehicleIdOnSegment = oldFirstRegVehicleId; - } - end.firstVehicleId = extVehicle.vehicleId; + ushort oldFirstRegVehicleId = end.firstVehicleId; + if (oldFirstRegVehicleId != 0) { + ExtVehicles[oldFirstRegVehicleId].previousVehicleIdOnSegment = extVehicle.vehicleId; + extVehicle.nextVehicleIdOnSegment = oldFirstRegVehicleId; + } + end.firstVehicleId = extVehicle.vehicleId; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[12]) - Log._Debug($"ExtVehicleManager.Link({extVehicle.vehicleId}) finished: Linked vehicle to segment end: {end}\nstate:{extVehicle}\nsegment end vehicle chain: {ExtSegmentEndManager.Instance.GenerateVehicleChainDebugInfo(extVehicle.currentSegmentId, extVehicle.currentStartNode)}"); + if (GlobalConfig.Instance.Debug.Switches[12]) + Log._Debug($"ExtVehicleManager.Link({extVehicle.vehicleId}) finished: Linked vehicle to segment end: {end}\nstate:{extVehicle}\nsegment end vehicle chain: {ExtSegmentEndManager.Instance.GenerateVehicleChainDebugInfo(extVehicle.currentSegmentId, extVehicle.currentStartNode)}"); #endif - } + } - public void OnCreate(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { + public void OnCreate(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnCreate({extVehicle.vehicleId}) called: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnCreate({extVehicle.vehicleId}) called: {extVehicle}"); #endif - if ((extVehicle.flags & ExtVehicleFlags.Created) != ExtVehicleFlags.None) { + if ((extVehicle.flags & ExtVehicleFlags.Created) != ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnCreate({extVehicle.vehicleId}): Vehicle is already created."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnCreate({extVehicle.vehicleId}): Vehicle is already created."); #endif - OnRelease(ref extVehicle, ref vehicleData); - } + OnRelease(ref extVehicle, ref vehicleData); + } - DetermineVehicleType(ref extVehicle, ref vehicleData); - extVehicle.recklessDriver = false; - extVehicle.flags = ExtVehicleFlags.Created; + DetermineVehicleType(ref extVehicle, ref vehicleData); + extVehicle.recklessDriver = false; + extVehicle.flags = ExtVehicleFlags.Created; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnCreate({extVehicle.vehicleId}) finished: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnCreate({extVehicle.vehicleId}) finished: {extVehicle}"); #endif - } + } - public ExtVehicleType OnStartPathFind(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ExtVehicleType? vehicleType) { + public API.Traffic.Enums.ExtVehicleType OnStartPathFind( + ref ExtVehicle extVehicle, + ref Vehicle vehicleData, + API.Traffic.Enums.ExtVehicleType? vehicleType) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnStartPathFind({extVehicle.vehicleId}, {vehicleType}) called: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnStartPathFind({extVehicle.vehicleId}, {vehicleType}) called: {extVehicle}"); #endif - if ((extVehicle.flags & ExtVehicleFlags.Created) == ExtVehicleFlags.None) { + if ((extVehicle.flags & ExtVehicleFlags.Created) == ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnStartPathFind({extVehicle.vehicleId}, {vehicleType}): Vehicle has not yet been created."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnStartPathFind({extVehicle.vehicleId}, {vehicleType}): Vehicle has not yet been created."); #endif - OnCreate(ref extVehicle, ref vehicleData); - } + OnCreate(ref extVehicle, ref vehicleData); + } - if (vehicleType != null) { - extVehicle.vehicleType = (ExtVehicleType)vehicleType; - } + if (vehicleType != null) { + extVehicle.vehicleType = (API.Traffic.Enums.ExtVehicleType)vehicleType; + } - extVehicle.recklessDriver = Constants.ManagerFactory.VehicleBehaviorManager.IsRecklessDriver(extVehicle.vehicleId, ref vehicleData); + extVehicle.recklessDriver = Constants.ManagerFactory.VehicleBehaviorManager.IsRecklessDriver(extVehicle.vehicleId, ref vehicleData); #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnStartPathFind({extVehicle.vehicleId}, {vehicleType}) finished: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnStartPathFind({extVehicle.vehicleId}, {vehicleType}) finished: {extVehicle}"); #endif - StepRand(ref extVehicle, true); - UpdateDynamicLaneSelectionParameters(ref extVehicle); + StepRand(ref extVehicle, true); + UpdateDynamicLaneSelectionParameters(ref extVehicle); - return extVehicle.vehicleType; - } + return extVehicle.vehicleType; + } - public void OnSpawn(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { + public void OnSpawn(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}) called: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}) called: {extVehicle}"); #endif - if ((extVehicle.flags & ExtVehicleFlags.Created) == ExtVehicleFlags.None) { + if ((extVehicle.flags & ExtVehicleFlags.Created) == ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}): Vehicle has not yet been created."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}): Vehicle has not yet been created."); #endif - OnCreate(ref extVehicle, ref vehicleData); - } + OnCreate(ref extVehicle, ref vehicleData); + } - Unlink(ref extVehicle); + Unlink(ref extVehicle); - extVehicle.lastPathId = 0; - extVehicle.lastPathPositionIndex = 0; - extVehicle.lastAltLaneSelSegmentId = 0; - extVehicle.recklessDriver = Constants.ManagerFactory.VehicleBehaviorManager.IsRecklessDriver(extVehicle.vehicleId, ref vehicleData); - StepRand(ref extVehicle, true); - UpdateDynamicLaneSelectionParameters(ref extVehicle); + extVehicle.lastPathId = 0; + extVehicle.lastPathPositionIndex = 0; + extVehicle.lastAltLaneSelSegmentId = 0; + extVehicle.recklessDriver = Constants.ManagerFactory.VehicleBehaviorManager.IsRecklessDriver(extVehicle.vehicleId, ref vehicleData); + StepRand(ref extVehicle, true); + UpdateDynamicLaneSelectionParameters(ref extVehicle); - try { - extVehicle.totalLength = vehicleData.CalculateTotalLength(extVehicle.vehicleId); - } catch (Exception + try { + extVehicle.totalLength = vehicleData.CalculateTotalLength(extVehicle.vehicleId); + } catch (Exception #if DEBUG - e + e #endif - ) { - extVehicle.totalLength = 0; + ) { + extVehicle.totalLength = 0; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}): Error occurred while calculating total length: {e}\nstate: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}): Error occurred while calculating total length: {e}\nstate: {extVehicle}"); #endif - return; - } + return; + } - extVehicle.flags |= ExtVehicleFlags.Spawned; + extVehicle.flags |= ExtVehicleFlags.Spawned; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}) finished: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnSpawn({extVehicle.vehicleId}) finished: {extVehicle}"); #endif - } + } - public void UpdatePosition(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ref ExtSegmentEnd segEnd, ref PathUnit.Position curPos, ref PathUnit.Position nextPos) { + public void UpdatePosition(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ref ExtSegmentEnd segEnd, ref PathUnit.Position curPos, ref PathUnit.Position nextPos) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}) called: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}) called: {extVehicle}"); #endif - if ((extVehicle.flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { + if ((extVehicle.flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Vehicle is not yet spawned."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Vehicle is not yet spawned."); #endif - OnSpawn(ref extVehicle, ref vehicleData); - } + OnSpawn(ref extVehicle, ref vehicleData); + } - if (extVehicle.nextSegmentId != nextPos.m_segment || extVehicle.nextLaneIndex != nextPos.m_lane) { - extVehicle.nextSegmentId = nextPos.m_segment; - extVehicle.nextLaneIndex = nextPos.m_lane; - } + if (extVehicle.nextSegmentId != nextPos.m_segment || extVehicle.nextLaneIndex != nextPos.m_lane) { + extVehicle.nextSegmentId = nextPos.m_segment; + extVehicle.nextLaneIndex = nextPos.m_lane; + } - bool startNode = IsTransitNodeCurStartNode(ref curPos, ref nextPos); - //ISegmentEnd end = Constants.ManagerFactory.SegmentEndManager.GetSegmentEnd(curPos.m_segment, startNode); + bool startNode = IsTransitNodeCurStartNode(ref curPos, ref nextPos); + //ISegmentEnd end = Constants.ManagerFactory.SegmentEndManager.GetSegmentEnd(curPos.m_segment, startNode); - if (extVehicle.currentSegmentId != segEnd.segmentId || extVehicle.currentStartNode != segEnd.startNode || extVehicle.currentLaneIndex != curPos.m_lane) { + if (extVehicle.currentSegmentId != segEnd.segmentId || extVehicle.currentStartNode != segEnd.startNode || extVehicle.currentLaneIndex != curPos.m_lane) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Current segment end changed. seg. {extVehicle.currentSegmentId}, start {extVehicle.currentStartNode}, lane {extVehicle.currentLaneIndex} -> seg. {segEnd.segmentId}, start {segEnd.startNode}, lane {curPos.m_lane}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Current segment end changed. seg. {extVehicle.currentSegmentId}, start {extVehicle.currentStartNode}, lane {extVehicle.currentLaneIndex} -> seg. {segEnd.segmentId}, start {segEnd.startNode}, lane {curPos.m_lane}"); #endif - if (extVehicle.currentSegmentId != 0) { + if (extVehicle.currentSegmentId != 0) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Unlinking from current segment end"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Unlinking from current segment end"); #endif - Unlink(ref extVehicle); - } + Unlink(ref extVehicle); + } - extVehicle.lastPathId = vehicleData.m_path; - extVehicle.lastPathPositionIndex = vehicleData.m_pathPositionIndex; + extVehicle.lastPathId = vehicleData.m_path; + extVehicle.lastPathPositionIndex = vehicleData.m_pathPositionIndex; - extVehicle.waitTime = 0; + extVehicle.waitTime = 0; #if DEBUGVSTATE - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Linking vehicle to segment end {segEnd.segmentId} @ {segEnd.startNode} ({segEnd.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}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}): Linking vehicle to segment end {segEnd.segmentId} @ {segEnd.startNode} ({segEnd.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 - if (segEnd.segmentId != 0) { - Link(ref extVehicle, ref segEnd, curPos.m_lane); - } - SetJunctionTransitState(ref extVehicle, VehicleJunctionTransitState.Approach); - } + if (segEnd.segmentId != 0) { + Link(ref extVehicle, ref segEnd, curPos.m_lane); + } + SetJunctionTransitState(ref extVehicle, VehicleJunctionTransitState.Approach); + } #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}) finshed: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.UpdatePosition({extVehicle.vehicleId}) finshed: {extVehicle}"); #endif - } + } - public void OnDespawn(ref ExtVehicle extVehicle) { + public void OnDespawn(ref ExtVehicle extVehicle) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnDespawn({extVehicle.vehicleId} called: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnDespawn({extVehicle.vehicleId} called: {extVehicle}"); #endif - if ((extVehicle.flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { + if ((extVehicle.flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnDespawn({extVehicle.vehicleId}): Vehicle is not spawned."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnDespawn({extVehicle.vehicleId}): Vehicle is not spawned."); #endif - return; - } + return; + } - Constants.ManagerFactory.ExtCitizenInstanceManager.ResetInstance(GetDriverInstanceId(extVehicle.vehicleId, ref Singleton.instance.m_vehicles.m_buffer[extVehicle.vehicleId])); + Constants.ManagerFactory.ExtCitizenInstanceManager.ResetInstance(GetDriverInstanceId(extVehicle.vehicleId, ref Singleton.instance.m_vehicles.m_buffer[extVehicle.vehicleId])); - Unlink(ref extVehicle); + Unlink(ref extVehicle); - extVehicle.lastAltLaneSelSegmentId = 0; - extVehicle.recklessDriver = false; - extVehicle.nextSegmentId = 0; - extVehicle.nextLaneIndex = 0; - extVehicle.totalLength = 0; - extVehicle.flags &= ExtVehicleFlags.Created; + extVehicle.lastAltLaneSelSegmentId = 0; + extVehicle.recklessDriver = false; + extVehicle.nextSegmentId = 0; + extVehicle.nextLaneIndex = 0; + extVehicle.totalLength = 0; + extVehicle.flags &= ExtVehicleFlags.Created; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnDespawn({extVehicle.vehicleId}) finished: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnDespawn({extVehicle.vehicleId}) finished: {extVehicle}"); #endif - } + } - public void OnRelease(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { + public void OnRelease(ref ExtVehicle extVehicle, ref Vehicle vehicleData) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}) called: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}) called: {extVehicle}"); #endif - if ((extVehicle.flags & ExtVehicleFlags.Created) == ExtVehicleFlags.None) { + if ((extVehicle.flags & ExtVehicleFlags.Created) == ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}): Vehicle is not created."); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}): Vehicle is not created."); #endif - return; - } - - if ((extVehicle.flags & ExtVehicleFlags.Spawned) != ExtVehicleFlags.None) { -#if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}): Vehicle is spawned."); -#endif - OnDespawn(ref extVehicle); - } else { - Unlink(ref extVehicle); - } - - extVehicle.lastTransitStateUpdate = 0; - extVehicle.lastPositionUpdate = 0; - extVehicle.waitTime = 0; - extVehicle.flags = ExtVehicleFlags.None; - extVehicle.vehicleType = ExtVehicleType.None; - extVehicle.heavyVehicle = false; - extVehicle.lastAltLaneSelSegmentId = 0; - extVehicle.junctionTransitState = VehicleJunctionTransitState.None; - extVehicle.recklessDriver = false; + return; + } + if ((extVehicle.flags & ExtVehicleFlags.Spawned) != ExtVehicleFlags.None) { #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}) finished: {extVehicle}"); + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}): Vehicle is spawned."); #endif - } - - public bool IsJunctionTransitStateNew(ref ExtVehicle extVehicle) { - uint frame = Constants.ServiceFactory.SimulationService.CurrentFrameIndex; - return (extVehicle.lastTransitStateUpdate >> STATE_UPDATE_SHIFT) >= (frame >> STATE_UPDATE_SHIFT); - } - - public uint GetStaticVehicleRand(ushort vehicleId) { - return vehicleId % 100u; - } - - public uint GetTimedVehicleRand(ushort vehicleId) { - return (uint)((vehicleId % 2) * 50u + (ExtVehicles[vehicleId].timedRand >> 1)); - } - - public void StepRand(ref ExtVehicle extVehicle, bool force) { - Randomizer rand = Constants.ServiceFactory.SimulationService.Randomizer; - if (force || (rand.UInt32(GlobalConfig.Instance.Gameplay.VehicleTimedRandModulo) == 0)) { - extVehicle.timedRand = Options.individualDrivingStyle ? (byte)rand.UInt32(100) : (byte)50; - } - } + OnDespawn(ref extVehicle); + } else { + Unlink(ref extVehicle); + } - public void UpdateDynamicLaneSelectionParameters(ref ExtVehicle extVehicle) { -#if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"VehicleState.UpdateDynamicLaneSelectionParameters({extVehicle.vehicleId}) called."); -#endif - - if (!Options.IsDynamicLaneSelectionActive()) { - extVehicle.dlsReady = false; - return; - } - - if (extVehicle.dlsReady) { - return; - } - - float egoism = (float)extVehicle.timedRand / 100f; - float altruism = 1f - egoism; - DynamicLaneSelection dls = GlobalConfig.Instance.DynamicLaneSelection; - - if (Options.individualDrivingStyle) { - extVehicle.maxReservedSpace = extVehicle.recklessDriver - ? Mathf.Lerp(dls.MinMaxRecklessReservedSpace, dls.MaxMaxRecklessReservedSpace, altruism) - : Mathf.Lerp(dls.MinMaxReservedSpace, dls.MaxMaxReservedSpace, altruism); - extVehicle.laneSpeedRandInterval = Mathf.Lerp(dls.MinLaneSpeedRandInterval, dls.MaxLaneSpeedRandInterval, egoism); - extVehicle.maxOptLaneChanges = (int)Math.Round(Mathf.Lerp(dls.MinMaxOptLaneChanges, dls.MaxMaxOptLaneChanges + 1, egoism)); - extVehicle.maxUnsafeSpeedDiff = Mathf.Lerp(dls.MinMaxUnsafeSpeedDiff, dls.MaxMaxOptLaneChanges, egoism); - extVehicle.minSafeSpeedImprovement = Mathf.Lerp(dls.MinMinSafeSpeedImprovement, dls.MaxMinSafeSpeedImprovement, altruism); - extVehicle.minSafeTrafficImprovement = Mathf.Lerp(dls.MinMinSafeTrafficImprovement, dls.MaxMinSafeTrafficImprovement, altruism); - } else { - extVehicle.maxReservedSpace = extVehicle.recklessDriver ? dls.MaxRecklessReservedSpace : dls.MaxReservedSpace; - extVehicle.laneSpeedRandInterval = dls.LaneSpeedRandInterval; - extVehicle.maxOptLaneChanges = dls.MaxOptLaneChanges; - extVehicle.maxUnsafeSpeedDiff = dls.MaxUnsafeSpeedDiff; - extVehicle.minSafeSpeedImprovement = dls.MinSafeSpeedImprovement; - extVehicle.minSafeTrafficImprovement = dls.MinSafeTrafficImprovement; - } - extVehicle.dlsReady = true; - } - - 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 ExtVehicle extVehicle, ref Vehicle vehicleData) { - VehicleAI ai = vehicleData.Info.m_vehicleAI; - - if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0) { - extVehicle.vehicleType = ExtVehicleType.Emergency; - } else { - ExtVehicleType? type = DetermineVehicleTypeFromAIType(extVehicle.vehicleId, ai, false); - if (type != null) { - extVehicle.vehicleType = (ExtVehicleType)type; - } else { - extVehicle.vehicleType = ExtVehicleType.None; - } - } - - if (extVehicle.vehicleType == ExtVehicleType.CargoTruck) { - extVehicle.heavyVehicle = ((CargoTruckAI)ai).m_isHeavyVehicle; - } else { - extVehicle.heavyVehicle = false; - } - -#if DEBUG - if (GlobalConfig.Instance.Debug.Switches[9]) - Log._Debug($"ExtVehicleManager.DetermineVehicleType({extVehicle.vehicleId}): vehicleType={extVehicle.vehicleType}, heavyVehicle={extVehicle.heavyVehicle}. Info={vehicleData.Info?.name}"); -#endif - } - - private ExtVehicleType? DetermineVehicleTypeFromAIType(ushort vehicleId, 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; - } + extVehicle.lastTransitStateUpdate = 0; + extVehicle.lastPositionUpdate = 0; + extVehicle.waitTime = 0; + extVehicle.flags = ExtVehicleFlags.None; + extVehicle.vehicleType = API.Traffic.Enums.ExtVehicleType.None; + extVehicle.heavyVehicle = false; + extVehicle.lastAltLaneSelSegmentId = 0; + extVehicle.junctionTransitState = VehicleJunctionTransitState.None; + extVehicle.recklessDriver = false; + +#if DEBUG + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.OnRelease({extVehicle.vehicleId}) finished: {extVehicle}"); +#endif + } + + public bool IsJunctionTransitStateNew(ref ExtVehicle extVehicle) { + uint frame = Constants.ServiceFactory.SimulationService.CurrentFrameIndex; + return (extVehicle.lastTransitStateUpdate >> STATE_UPDATE_SHIFT) >= (frame >> STATE_UPDATE_SHIFT); + } + + public uint GetStaticVehicleRand(ushort vehicleId) { + return vehicleId % 100u; + } + + public uint GetTimedVehicleRand(ushort vehicleId) { + return (uint)((vehicleId % 2) * 50u + (ExtVehicles[vehicleId].timedRand >> 1)); + } + + public void StepRand(ref ExtVehicle extVehicle, bool force) { + Randomizer rand = Constants.ServiceFactory.SimulationService.Randomizer; + if (force || (rand.UInt32(GlobalConfig.Instance.Gameplay.VehicleTimedRandModulo) == 0)) { + extVehicle.timedRand = Options.individualDrivingStyle ? (byte)rand.UInt32(100) : (byte)50; + } + } + + public void UpdateDynamicLaneSelectionParameters(ref ExtVehicle extVehicle) { +#if DEBUG + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"VehicleState.UpdateDynamicLaneSelectionParameters({extVehicle.vehicleId}) called."); +#endif + + if (!Options.IsDynamicLaneSelectionActive()) { + extVehicle.dlsReady = false; + return; + } + + if (extVehicle.dlsReady) { + return; + } + + float egoism = (float)extVehicle.timedRand / 100f; + float altruism = 1f - egoism; + DynamicLaneSelection dls = GlobalConfig.Instance.DynamicLaneSelection; + + if (Options.individualDrivingStyle) { + extVehicle.maxReservedSpace = extVehicle.recklessDriver + ? Mathf.Lerp(dls.MinMaxRecklessReservedSpace, dls.MaxMaxRecklessReservedSpace, altruism) + : Mathf.Lerp(dls.MinMaxReservedSpace, dls.MaxMaxReservedSpace, altruism); + extVehicle.laneSpeedRandInterval = Mathf.Lerp(dls.MinLaneSpeedRandInterval, dls.MaxLaneSpeedRandInterval, egoism); + extVehicle.maxOptLaneChanges = (int)Math.Round(Mathf.Lerp(dls.MinMaxOptLaneChanges, dls.MaxMaxOptLaneChanges + 1, egoism)); + extVehicle.maxUnsafeSpeedDiff = Mathf.Lerp(dls.MinMaxUnsafeSpeedDiff, dls.MaxMaxOptLaneChanges, egoism); + extVehicle.minSafeSpeedImprovement = Mathf.Lerp(dls.MinMinSafeSpeedImprovement, dls.MaxMinSafeSpeedImprovement, altruism); + extVehicle.minSafeTrafficImprovement = Mathf.Lerp(dls.MinMinSafeTrafficImprovement, dls.MaxMinSafeTrafficImprovement, altruism); + } else { + extVehicle.maxReservedSpace = extVehicle.recklessDriver ? dls.MaxRecklessReservedSpace : dls.MaxReservedSpace; + extVehicle.laneSpeedRandInterval = dls.LaneSpeedRandInterval; + extVehicle.maxOptLaneChanges = dls.MaxOptLaneChanges; + extVehicle.maxUnsafeSpeedDiff = dls.MaxUnsafeSpeedDiff; + extVehicle.minSafeSpeedImprovement = dls.MinSafeSpeedImprovement; + extVehicle.minSafeTrafficImprovement = dls.MinSafeTrafficImprovement; + } + extVehicle.dlsReady = true; + } + + 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 ExtVehicle extVehicle, ref Vehicle vehicleData) { + VehicleAI ai = vehicleData.Info.m_vehicleAI; + + if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0) { + extVehicle.vehicleType = API.Traffic.Enums.ExtVehicleType.Emergency; + } else { + ExtVehicleType? type = DetermineVehicleTypeFromAIType(extVehicle.vehicleId, ai, false); + if (type != null) { + extVehicle.vehicleType = (API.Traffic.Enums.ExtVehicleType)type; + } else { + extVehicle.vehicleType = API.Traffic.Enums.ExtVehicleType.None; + } + } + + if (extVehicle.vehicleType == API.Traffic.Enums.ExtVehicleType.CargoTruck) { + extVehicle.heavyVehicle = ((CargoTruckAI)ai).m_isHeavyVehicle; + } else { + extVehicle.heavyVehicle = false; + } + +#if DEBUG + if (GlobalConfig.Instance.Debug.Switches[9]) + Log._Debug($"ExtVehicleManager.DetermineVehicleType({extVehicle.vehicleId}): vehicleType={extVehicle.vehicleType}, heavyVehicle={extVehicle.heavyVehicle}. Info={vehicleData.Info?.name}"); +#endif + } + + private ExtVehicleType? DetermineVehicleTypeFromAIType(ushort vehicleId, 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($"ExtVehicleManager.DetermineVehicleType({vehicleId}): Could not determine vehicle type from ai type: {ai.GetType().ToString()}"); -#endif - return null; - } - - public void InitAllVehicles() { - Log._Debug("ExtVehicleManager: 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 < ExtVehicles.Length; ++i) { - Services.VehicleService.ProcessVehicle((ushort)i, (ushort vehId, ref Vehicle veh) => { - OnRelease(ref ExtVehicles[i], ref veh); - return true; - }); - } - } - - public override void OnAfterLoadData() { - base.OnAfterLoadData(); - InitAllVehicles(); - } - } -} + Log._Debug($"ExtVehicleManager.DetermineVehicleType({vehicleId}): Could not determine vehicle type from ai type: {ai.GetType().ToString()}"); +#endif + return null; + } + + public void InitAllVehicles() { + Log._Debug("ExtVehicleManager: InitAllVehicles()"); + VehicleManager vehicleManager = Singleton.instance; + + for (uint vehicleId = 0; vehicleId < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++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 < ExtVehicles.Length; ++i) { + Services.VehicleService.ProcessVehicle((ushort)i, (ushort vehId, ref Vehicle veh) => { + OnRelease(ref ExtVehicles[i], ref veh); + return true; + }); + } + } + + public override void OnAfterLoadData() { + base.OnAfterLoadData(); + InitAllVehicles(); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs b/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs index 4e2c56aa7..027de9dc0 100644 --- a/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs +++ b/TLM/TLM/Manager/Impl/JunctionRestrictionsManager.cs @@ -187,7 +187,7 @@ protected void UpdateDefaults(ref ExtSegment seg) { UpdateDefaults(ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, true)], ref SegmentFlags[segmentId].startNodeFlags, ref node); return true; }); - + ushort endNodeId = Services.NetService.GetSegmentNodeId(seg.segmentId, false); Services.NetService.ProcessNode(endNodeId, delegate (ushort nId, ref NetNode node) { UpdateDefaults(ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, false)], ref SegmentFlags[segmentId].endNodeFlags, ref node); @@ -334,7 +334,7 @@ public bool GetDefaultTurnOnRedAllowed(bool near, ushort segmentId, bool startNo #endif return false; } - + bool ret = near ? Options.allowNearTurnOnRed : Options.allowFarTurnOnRed; #if DEBUG if (debug) @@ -368,7 +368,7 @@ public bool IsLaneChangingAllowedWhenGoingStraightConfigurable(ushort segmentId, IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - + bool ret = (node.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Transition)) != NetNode.Flags.None && node.Info?.m_class?.m_service != ItemClass.Service.Beautification && @@ -499,7 +499,8 @@ public bool GetDefaultPedestrianCrossingAllowed(ushort segmentId, bool startNode return true; } - bool ret = (node.m_flags & NetNode.Flags.Junction) != NetNode.Flags.None; + // crossing is allowed at junctions and at untouchable nodes (for example: spiral underground parking) + bool ret = (node.m_flags & (NetNode.Flags.Junction | NetNode.Flags.Untouchable)) != NetNode.Flags.None; #if DEBUG if (debug) Log._Debug($"JunctionRestrictionsManager.GetDefaultPedestrianCrossingAllowed({segmentId}, {startNode}): Setting is configurable. ret={ret}, flags={node.m_flags}"); @@ -709,7 +710,7 @@ public bool LoadData(List data) { SetNearTurnOnRedAllowed(segNodeConf.segmentId, true, (bool)flags.turnOnRedAllowed); } - if (flags.farTurnOnRedAllowed != null && IsNearTurnOnRedAllowedConfigurable(segNodeConf.segmentId, true, ref node)) { + if (flags.farTurnOnRedAllowed != null && IsFarTurnOnRedAllowedConfigurable(segNodeConf.segmentId, true, ref node)) { SetFarTurnOnRedAllowed(segNodeConf.segmentId, true, (bool)flags.farTurnOnRedAllowed); } @@ -741,14 +742,6 @@ public bool LoadData(List data) { SetUturnAllowed(segNodeConf.segmentId, false, (bool)flags.uturnAllowed); } - if (flags.turnOnRedAllowed != null) { - SetNearTurnOnRedAllowed(segNodeConf.segmentId, false, (bool)flags.turnOnRedAllowed); - } - - if (flags.farTurnOnRedAllowed != null) { - SetFarTurnOnRedAllowed(segNodeConf.segmentId, false, (bool)flags.farTurnOnRedAllowed); - } - if (flags.straightLaneChangingAllowed != null && IsLaneChangingAllowedWhenGoingStraightConfigurable(segNodeConf.segmentId, false, ref node)) { SetLaneChangingAllowedWhenGoingStraight(segNodeConf.segmentId, false, (bool)flags.straightLaneChangingAllowed); } @@ -760,6 +753,14 @@ public bool LoadData(List data) { if (flags.pedestrianCrossingAllowed != null && IsPedestrianCrossingAllowedConfigurable(segNodeConf.segmentId, false, ref node)) { SetPedestrianCrossingAllowed(segNodeConf.segmentId, false, (bool)flags.pedestrianCrossingAllowed); } + + if (flags.turnOnRedAllowed != null && IsNearTurnOnRedAllowedConfigurable(segNodeConf.segmentId, false, ref node)) { + SetNearTurnOnRedAllowed(segNodeConf.segmentId, false, (bool)flags.turnOnRedAllowed); + } + + if (flags.farTurnOnRedAllowed != null && IsFarTurnOnRedAllowedConfigurable(segNodeConf.segmentId, false, ref node)) { + SetFarTurnOnRedAllowed(segNodeConf.segmentId, false, (bool)flags.farTurnOnRedAllowed); + } return true; }); } else { diff --git a/TLM/TLM/Manager/Impl/LaneArrowManager.cs b/TLM/TLM/Manager/Impl/LaneArrowManager.cs index 3a26a6f19..f161f9d95 100644 --- a/TLM/TLM/Manager/Impl/LaneArrowManager.cs +++ b/TLM/TLM/Manager/Impl/LaneArrowManager.cs @@ -1,174 +1,173 @@ -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 CSUtil.Commons; -using TrafficManager.Geometry.Impl; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - -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 SetLaneArrowUnableReason 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(ref ExtSegment seg) { - Flags.resetSegmentArrowFlags(seg.segmentId); - } - - protected override void HandleValidSegment(ref ExtSegment seg) { - - } - - 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)"); +namespace TrafficManager.Manager.Impl { + using System; + using System.Collections.Generic; + using System.Linq; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using State; + using Traffic.Data; + using Traffic.Enums; + + 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 SetLaneArrowUnableReason 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(ref ExtSegment seg) { + Flags.resetSegmentArrowFlags(seg.segmentId); + } + + protected override void HandleValidSegment(ref ExtSegment seg) { + + } + + 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 DEBUGLOAD Log._Debug($"LaneFlags: {data}"); #endif - var lanes = data.Split(','); + var lanes = data.Split(','); - if (lanes.Length > 1) { - foreach (var split in lanes.Select(lane => lane.Split(':')).Where(split => split.Length > 1)) { - try { + if (lanes.Length > 1) { + foreach (var split in lanes.Select(lane => lane.Split(':')).Where(split => split.Length > 1)) { + try { #if DEBUGLOAD Log._Debug($"Split Data: {split[0]} , {split[1]}"); #endif - var laneId = Convert.ToUInt32(split[0]); - uint flags = Convert.ToUInt32(split[1]); + var laneId = Convert.ToUInt32(split[0]); + uint flags = Convert.ToUInt32(split[1]); - if (!Services.NetService.IsLaneValid(laneId)) - continue; + 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 (flags > ushort.MaxValue) + continue; + + uint laneArrowFlags = flags & Flags.lfr; + uint origFlags = (Singleton.instance.m_lanes.m_buffer[laneId].m_flags & Flags.lfr); #if DEBUGLOAD 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, (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, (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 { - LaneArrows? laneArrows = Flags.getLaneArrowFlags(i); - - if (laneArrows == null) - continue; - - uint laneArrowInt = (uint)laneArrows; + SetLaneArrows(laneId, (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, (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 { + LaneArrows? laneArrows = Flags.getLaneArrowFlags(i); + + if (laneArrows == null) + continue; + + uint laneArrowInt = (uint)laneArrows; #if DEBUGSAVE Log._Debug($"Saving lane arrows for lane {i}, setting to {laneArrows.ToString()} ({laneArrowInt})"); #endif - 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; - } - } -} + 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; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/LaneConnectionManager.cs b/TLM/TLM/Manager/Impl/LaneConnectionManager.cs index 9bb00a03a..7117f8066 100644 --- a/TLM/TLM/Manager/Impl/LaneConnectionManager.cs +++ b/TLM/TLM/Manager/Impl/LaneConnectionManager.cs @@ -1,668 +1,669 @@ -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; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - -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) { +namespace TrafficManager.Manager.Impl { + using System; + using System.Collections.Generic; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using State; + using Traffic.Data; + using UnityEngine; + + 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."); + 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); + bool ret = Flags.RemoveLaneConnection(laneId1, laneId2, startNode1); #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RemoveLaneConnection({laneId1}, {laneId2}, {startNode1}): ret={ret}"); + 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 (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."); + 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) { + 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."); + 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) { + 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}."); + 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) { + 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."); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"LaneConnectionManager.RemoveLaneConnections({laneId}, {startNode}) called."); #endif - if (Flags.laneConnections[laneId] == null) - return; + if (Flags.laneConnections[laneId] == null) + return; - int nodeArrayIndex = startNode ? 0 : 1; + int nodeArrayIndex = startNode ? 0 : 1; - if (Flags.laneConnections[laneId][nodeArrayIndex] == null) - return; + if (Flags.laneConnections[laneId][nodeArrayIndex] == null) + return; - NetManager netManager = Singleton.instance; + 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) { + /*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); - } - } - }*/ + ushort otherSegmentId = netManager.m_lanes.m_buffer[otherLaneId].m_segment; + UnsubscribeFromSegmentGeometry(otherSegmentId); + } + } + }*/ - Flags.RemoveLaneConnections(laneId, startNode); + Flags.RemoveLaneConnections(laneId, startNode); - Services.NetService.ProcessLane(laneId, delegate (uint lId, ref NetLane lane) { - if (recalcAndPublish) { - RoutingManager.Instance.RequestRecalculation(lane.m_segment); + 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; - }); - } + 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; - } + /// + /// 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); + 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}"); + 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); + 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; + NetManager netManager = Singleton.instance; - ushort sourceSegmentId = netManager.m_lanes.m_buffer[sourceLaneId].m_segment; - ushort targetSegmentId = netManager.m_lanes.m_buffer[targetLaneId].m_segment; + 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); - } + if (sourceSegmentId == targetSegmentId) { + JunctionRestrictionsManager.Instance.SetUturnAllowed(sourceSegmentId, sourceStartNode, true); + } - RoutingManager.Instance.RequestRecalculation(sourceSegmentId, false); - RoutingManager.Instance.RequestRecalculation(targetSegmentId, false); + RoutingManager.Instance.RequestRecalculation(sourceSegmentId, false); + RoutingManager.Instance.RequestRecalculation(targetSegmentId, false); - if (OptionsManager.Instance.MayPublishSegmentChanges()) { - Services.NetService.PublishSegmentChanges(sourceSegmentId); - Services.NetService.PublishSegmentChanges(targetSegmentId); - } - } + if (OptionsManager.Instance.MayPublishSegmentChanges()) { + Services.NetService.PublishSegmentChanges(sourceSegmentId); + Services.NetService.PublishSegmentChanges(targetSegmentId); + } + } - return ret; - } + return ret; + } - protected override void HandleInvalidSegment(ref ExtSegment seg) { + protected override void HandleInvalidSegment(ref ExtSegment seg) { #if DEBUGCONN - bool debug = GlobalConfig.Instance.Debug.Switches[23]; - if (debug) - Log._Debug($"LaneConnectionManager.HandleInvalidSegment({seg.segmentId}): Segment has become invalid. Removing lane connections."); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"LaneConnectionManager.HandleInvalidSegment({seg.segmentId}): Segment has become invalid. Removing lane connections."); #endif - RemoveLaneConnectionsFromSegment(seg.segmentId, false, false); - RemoveLaneConnectionsFromSegment(seg.segmentId, true); - } - - protected override void HandleValidSegment(ref ExtSegment seg) { - - } - - /// - /// 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) { + RemoveLaneConnectionsFromSegment(seg.segmentId, false, false); + RemoveLaneConnectionsFromSegment(seg.segmentId, true); + } + + protected override void HandleValidSegment(ref ExtSegment seg) { + + } + + /// + /// 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"); + bool debug = GlobalConfig.Instance.Debug.Switches[23]; + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}) called"); #endif - if (!Options.laneConnectorEnabled) { - return; - } + if (!Options.laneConnectorEnabled) { + return; + } - if (!Flags.mayHaveLaneArrows(laneId, startNode)) { + if (!Flags.mayHaveLaneArrows(laneId, startNode)) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): lane {laneId}, startNode? {startNode} must not have lane arrows"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): lane {laneId}, startNode? {startNode} must not have lane arrows"); #endif - return; - } + return; + } - if (!HasConnections(laneId, startNode)) { + if (!HasConnections(laneId, startNode)) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): lane {laneId} does not have outgoing connections"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): lane {laneId} does not have outgoing connections"); #endif - return; - } + return; + } - if (nodeId == 0) { + if (nodeId == 0) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid node"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid node"); #endif - return; - } + return; + } - LaneArrows arrows = LaneArrows.None; + LaneArrows arrows = LaneArrows.None; - NetManager netManager = Singleton.instance; - ushort segmentId = netManager.m_lanes.m_buffer[laneId].m_segment; + NetManager netManager = Singleton.instance; + ushort segmentId = netManager.m_lanes.m_buffer[laneId].m_segment; - if (segmentId == 0) { + if (segmentId == 0) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid segment"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): invalid segment"); #endif - return; - } + return; + } #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): startNode? {startNode}"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): startNode? {startNode}"); #endif - if (! Services.NetService.IsNodeValid(nodeId)) { + if (! Services.NetService.IsNodeValid(nodeId)) { #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): Node is invalid"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): Node is invalid"); #endif - return; - } + return; + } - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)]; + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)]; - Services.NetService.IterateNodeSegments(nodeId, delegate (ushort otherSegmentId, ref NetSegment otherSeg) { - ArrowDirection dir = segEndMan.GetDirection(ref segEnd, otherSegmentId); + Services.NetService.IterateNodeSegments(nodeId, delegate (ushort otherSegmentId, ref NetSegment otherSeg) { + ArrowDirection dir = segEndMan.GetDirection(ref segEnd, otherSegmentId); #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}. dir={dir}"); + 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 & LaneArrows.Right) != LaneArrows.None) - return true; - } else { - if ((arrows & LaneArrows.Left) != LaneArrows.None) - return true; - } - break; - case ArrowDirection.Forward: - if ((arrows & LaneArrows.Forward) != LaneArrows.None) - return true; - break; - case ArrowDirection.Left: - if ((arrows & LaneArrows.Left) != LaneArrows.None) - return true; - break; - case ArrowDirection.Right: - if ((arrows & LaneArrows.Right) != LaneArrows.None) - return true; - break; - default: - return true; - } + // check if arrow has already been set for this direction + switch (dir) { + case ArrowDirection.Turn: + if (Constants.ServiceFactory.SimulationService.LeftHandDrive) { + if ((arrows & LaneArrows.Right) != LaneArrows.None) + return true; + } else { + if ((arrows & LaneArrows.Left) != LaneArrows.None) + return true; + } + break; + case ArrowDirection.Forward: + if ((arrows & LaneArrows.Forward) != LaneArrows.None) + return true; + break; + case ArrowDirection.Left: + if ((arrows & LaneArrows.Left) != LaneArrows.None) + return true; + break; + case ArrowDirection.Right: + if ((arrows & LaneArrows.Right) != LaneArrows.None) + return true; + break; + default: + return true; + } #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: need to determine arrows"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: need to determine arrows"); #endif - bool addArrow = false; + bool addArrow = false; - uint curLaneId = netManager.m_segments.m_buffer[otherSegmentId].m_lanes; - while (curLaneId != 0) { + 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}"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: checking lane {curLaneId}"); #endif - if (AreLanesConnected(laneId, curLaneId, startNode)) { + 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"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: checking lane {curLaneId}: lanes are connected"); #endif - addArrow = true; - break; - } + addArrow = true; + break; + } - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - } + 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}"); + 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 |= LaneArrows.Right; - } else { - arrows |= LaneArrows.Left; - } - break; - case ArrowDirection.Forward: - arrows |= LaneArrows.Forward; - break; - case ArrowDirection.Left: - arrows |= LaneArrows.Left; - break; - case ArrowDirection.Right: - arrows |= LaneArrows.Right; - break; - default: - return true; - } + if (addArrow) { + switch (dir) { + case ArrowDirection.Turn: + if (Constants.ServiceFactory.SimulationService.LeftHandDrive) { + arrows |= LaneArrows.Right; + } else { + arrows |= LaneArrows.Left; + } + break; + case ArrowDirection.Forward: + arrows |= LaneArrows.Forward; + break; + case ArrowDirection.Left: + arrows |= LaneArrows.Left; + break; + case ArrowDirection.Right: + arrows |= LaneArrows.Right; + break; + default: + return true; + } #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: arrows={arrows}"); + if (debug) + Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): processing connected segment {otherSegmentId}: arrows={arrows}"); #endif - } + } - return true; - }); + return true; + }); #if DEBUGCONN - if (debug) - Log._Debug($"LaneConnectionManager.RecalculateLaneArrows({laneId}, {nodeId}): setting lane arrows to {arrows}"); + 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; - } + 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; + } #if DEBUGLOAD Log._Debug($"Loading lane connection: lane {conn.lowerLaneId} -> {conn.higherLaneId}"); #endif - 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; + 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; #if DEBUGSAVE Log._Debug($"Saving lane connection: lane {i} -> {otherHigherLaneId}"); #endif - 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; - } - } -} + 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; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/ManagerFactory.cs b/TLM/TLM/Manager/Impl/ManagerFactory.cs index e99b3ffed..a865bd08d 100644 --- a/TLM/TLM/Manager/Impl/ManagerFactory.cs +++ b/TLM/TLM/Manager/Impl/ManagerFactory.cs @@ -5,6 +5,8 @@ using TrafficManager.Manager; namespace TrafficManager.Manager.Impl { + using API.Manager; + public class ManagerFactory : IManagerFactory { public static IManagerFactory Instance = new ManagerFactory(); diff --git a/TLM/TLM/Manager/Impl/OptionsManager.cs b/TLM/TLM/Manager/Impl/OptionsManager.cs index 3b3098b76..edaecbc34 100644 --- a/TLM/TLM/Manager/Impl/OptionsManager.cs +++ b/TLM/TLM/Manager/Impl/OptionsManager.cs @@ -7,6 +7,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.Manager.Impl { + using API.Traffic.Enums; + public class OptionsManager : AbstractCustomManager, IOptionsManager { // TODO I contain ugly code public static OptionsManager Instance = new OptionsManager(); diff --git a/TLM/TLM/Manager/Impl/RoutingManager.cs b/TLM/TLM/Manager/Impl/RoutingManager.cs index 6a0198851..85f624d46 100644 --- a/TLM/TLM/Manager/Impl/RoutingManager.cs +++ b/TLM/TLM/Manager/Impl/RoutingManager.cs @@ -18,6 +18,8 @@ using static TrafficManager.State.Flags; namespace TrafficManager.Manager.Impl { + using API.Traffic.Enums; + public class RoutingManager : AbstractGeometryObservingManager, IRoutingManager { public static readonly RoutingManager Instance = new RoutingManager(); @@ -303,8 +305,6 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui 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) { @@ -342,15 +342,25 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui bool nextHasTrafficLights = false; bool nextHasPrioritySigns = Constants.ManagerFactory.TrafficPriorityManager.HasNodePrioritySign(nextNodeId); bool nextIsRealJunction = false; + ushort buildingId = 0; 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; nextIsRealJunction = node.CountSegments() >= 3; + buildingId = NetNode.FindOwnerBuilding(nextNodeId, 32f); return true; }); + bool isTollBooth = false; + if (buildingId != 0) { + Constants.ServiceFactory.BuildingService.ProcessBuilding(buildingId, (ushort bId, ref Building building) => { + isTollBooth = building.Info.m_buildingAI is TollBoothAI; + return true; + }); + } + bool nextIsSimpleJunction = false; bool nextIsSplitJunction = false; if (Options.highwayRules && !nextHasTrafficLights && !nextHasPrioritySigns) { @@ -384,6 +394,7 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui nextIsSimpleJunction = numOutgoing == 1 || numIncoming == 1; nextIsSplitJunction = numOutgoing > 1; } +// bool isNextRealJunction = prevSegGeo.CountOtherSegments(startNode) > 1; bool nextAreOnlyOneWayHighways = Constants.ManagerFactory.ExtSegmentEndManager.CalculateOnlyHighways(prevEnd.segmentId, prevEnd.startNode); // determine if highway rules should be applied @@ -399,6 +410,7 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui 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={nextIsRealJunction}"); + Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextNodeId={nextNodeId} buildingId={buildingId} isTollBooth={isTollBooth}"); } #endif @@ -565,15 +577,15 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui } } - if (prevIsMergeLane && Constants.ServiceFactory.NetService.CheckLaneFlags(nextLaneId, NetLane.Flags.Merge)) { + if (isTollBooth) { #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."); + Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextNodeId={nextNodeId}, buildingId={buildingId} is a toll booth. Preventing lane changes."); #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."); + Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): nextLaneId={nextLaneId}, idx={nextLaneIndex} is associated with a toll booth (buildingId={buildingId}). adding as Default."); #endif isCompatibleLane = true; transitionType = LaneEndTransitionType.Default; @@ -601,7 +613,7 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui (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 + (nextIncomingDir == ArrowDirection.Turn && (!nextIsRealJunction || 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."); @@ -1054,7 +1066,7 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui } } } else { - // at lane splits: distribute traffic evenly (1-to-n, n-to-n) + // at lane splits: distribute traffic evenly (1-to-n, n-to-n) // prevOuterSimilarIndex is always > nextCompatibleLaneCount #if DEBUGROUTING if (debugFine) @@ -1069,7 +1081,7 @@ protected void RecalculateLaneEndRoutingData(ushort segmentId, int laneIndex, ui Log._Debug($"RoutingManager.RecalculateLaneEndRoutingData({segmentId}, {laneIndex}, {laneId}, {startNode}): split outer lanes: minNextCompatibleOuterSimilarIndex={minNextCompatibleOuterSimilarIndex}, maxNextCompatibleOuterSimilarIndex={maxNextCompatibleOuterSimilarIndex}"); #endif } else { - // split outer lanes, criss-cross inner lanes + // 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; diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs index f85bc4c75..5b23db887 100644 --- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs +++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs @@ -15,48 +15,32 @@ public class SpeedLimitManager : AbstractGeometryObservingManager, ICustomDataMa 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; + /// Ingame speed units, max possible speed 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 CustomLaneSpeedLimitByNetInfoName; // For each NetInfo (by name) and lane index: custom speed limit 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(); + CustomLaneSpeedLimitByNetInfoName = new Dictionary(); customizableNetInfos = new List(); childNetInfoNamesByCustomizableNetInfoName = new Dictionary>(); NetInfoByName = new Dictionary(); } + protected override void InternalPrintDebugInfo() { + base.InternalPrintDebugInfo(); + Log._Debug($"- Not implemented -"); + // TODO implement + } + /// /// Determines if custom speed limits may be assigned to the given segment. /// @@ -87,42 +71,47 @@ public bool MayHaveCustomSpeedLimits(NetInfo.Lane laneInfo) { /// /// /// - /// - public ushort GetCustomSpeedLimit(ushort segmentId, NetInfo.Direction finalDir) { + /// Mean speed limit, average for custom and default lane speeds + public float 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; + return 0.0f; } 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; + var curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + var laneIndex = 0; + var 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) + var laneInfo = segmentInfo.m_lanes[laneIndex]; + var d = laneInfo.m_finalDirection; + if (d != finalDir) { goto nextIter; - if (!MayHaveCustomSpeedLimits(laneInfo)) + } + if (!MayHaveCustomSpeedLimits(laneInfo)) { goto nextIter; + } - ushort? setSpeedLimit = Flags.getLaneSpeedLimit(curLaneId); - if (setSpeedLimit != null) - meanSpeedLimit += ToGameSpeedLimit((ushort)setSpeedLimit); // custom speed limit - else + var setSpeedLimit = Flags.getLaneSpeedLimit(curLaneId); + if (setSpeedLimit != null) { + meanSpeedLimit += ToGameSpeedLimit(setSpeedLimit.Value); // custom speed limit + } else { meanSpeedLimit += laneInfo.m_speedLimit; // game default + } + ++validLanes; - nextIter: + nextIter: curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane; laneIndex++; } - if (validLanes > 0) - meanSpeedLimit /= (float)validLanes; - ushort ret = LaneToCustomSpeedLimit(meanSpeedLimit); - return ret; + if (validLanes > 0) { + meanSpeedLimit /= validLanes; + } + + return meanSpeedLimit; } /// @@ -132,25 +121,26 @@ public ushort GetCustomSpeedLimit(ushort segmentId, NetInfo.Direction finalDir) /// /// /// - public ushort GetAverageDefaultCustomSpeedLimit(NetInfo segmentInfo, NetInfo.Direction? finalDir=null) { - float meanSpeedLimit = 0f; + public float GetAverageDefaultCustomSpeedLimit(NetInfo segmentInfo, NetInfo.Direction? finalDir=null) { + var 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) + foreach (var laneInfo in segmentInfo.m_lanes) { + var d = laneInfo.m_finalDirection; + if (finalDir != null && d != finalDir) { continue; - if (!MayHaveCustomSpeedLimits(laneInfo)) + } + if (!MayHaveCustomSpeedLimits(laneInfo)) { continue; - + } + meanSpeedLimit += laneInfo.m_speedLimit; ++validLanes; } - if (validLanes > 0) - meanSpeedLimit /= (float)validLanes; - ushort ret = LaneToCustomSpeedLimit(meanSpeedLimit); - return ret; + if (validLanes > 0) { + meanSpeedLimit /= validLanes; + } + return meanSpeedLimit; } /// @@ -189,11 +179,11 @@ public ushort GetAverageCustomSpeedLimit(ushort segmentId, ref NetSegment segmen /// /// /// - public ushort GetCustomSpeedLimit(uint laneId) { + public float GetCustomSpeedLimit(uint laneId) { // check custom speed limit - ushort? setSpeedLimit = Flags.getLaneSpeedLimit(laneId); + var setSpeedLimit = Flags.getLaneSpeedLimit(laneId); if (setSpeedLimit != null) { - return (ushort)setSpeedLimit; + return setSpeedLimit.Value; } // check default speed limit @@ -201,7 +191,7 @@ public ushort GetCustomSpeedLimit(uint laneId) { 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; @@ -212,8 +202,7 @@ public ushort GetCustomSpeedLimit(uint laneId) { if (!MayHaveCustomSpeedLimits(laneInfo)) return 0; - ushort ret = LaneToCustomSpeedLimit(laneInfo.m_speedLimit); - return ret; + return laneInfo.m_speedLimit; } laneIndex++; @@ -239,9 +228,9 @@ public float GetLockFreeGameSpeedLimit(ushort segmentId, byte laneIndex, uint la } float speedLimit = 0; - ushort?[] fastArray = Flags.laneSpeedLimitArray[segmentId]; + float?[] fastArray = Flags.laneSpeedLimitArray[segmentId]; if (fastArray != null && fastArray.Length > laneIndex && fastArray[laneIndex] != null) { - speedLimit = ToGameSpeedLimit((ushort)fastArray[laneIndex]); + speedLimit = ToGameSpeedLimit((float)fastArray[laneIndex]); } else { speedLimit = laneInfo.m_speedLimit; } @@ -253,32 +242,10 @@ public float GetLockFreeGameSpeedLimit(ushort segmentId, byte laneIndex, uint la /// /// /// - 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; + public float ToGameSpeedLimit(float customSpeedLimit) { + return SpeedLimit.IsZero(customSpeedLimit) + ? MAX_SPEED + : customSpeedLimit; } /// @@ -299,19 +266,20 @@ public void FixCurrentSpeedLimits(NetInfo info) { #endif return; } - - if (!customizableNetInfos.Contains(info)) + if (!customizableNetInfos.Contains(info)) { return; - + } for (uint laneId = 1; laneId < NetManager.MAX_LANE_COUNT; ++laneId) { - if (!Services.NetService.IsLaneValid(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))) + } + var segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; + var 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)); } } @@ -356,7 +324,7 @@ public void ClearCurrentSpeedLimits(NetInfo info) { /// 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) { + public float GetVanillaNetInfoSpeedLimit(NetInfo info, bool roundToSignLimits = true) { if (info == null) { #if DEBUG Log.Warning($"SpeedLimitManager.GetVanillaNetInfoSpeedLimit: info is null!"); @@ -378,10 +346,6 @@ public ushort GetVanillaNetInfoSpeedLimit(NetInfo info, bool roundToSignLimits = 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)) { @@ -395,18 +359,15 @@ public ushort GetVanillaNetInfoSpeedLimit(NetInfo info, bool roundToSignLimits = } } - if (maxSpeedLimit == null) - return 0; - - return LaneToCustomSpeedLimit((float)maxSpeedLimit, roundToSignLimits); + return maxSpeedLimit ?? 0; } /// /// 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) { + /// -1 if no custom speed limit was set + public float GetCustomNetInfoSpeedLimit(NetInfo info) { if (info == null) { #if DEBUG Log.Warning($"SpeedLimitManager.SetCustomNetInfoSpeedLimitIndex: info is null!"); @@ -421,25 +382,19 @@ public int GetCustomNetInfoSpeedLimitIndex(NetInfo info) { 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; + var infoName = info.name; + float speedLimit; + return !CustomLaneSpeedLimitByNetInfoName.TryGetValue(infoName, out speedLimit) + ? GetVanillaNetInfoSpeedLimit(info, true) + : speedLimit; } /// /// 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) { + /// The speed value to set in game speed units + public void SetCustomNetInfoSpeedLimit(NetInfo info, float customSpeedLimit) { if (info == null) { #if DEBUG Log.Warning($"SetCustomNetInfoSpeedLimitIndex: info is null!"); @@ -454,15 +409,10 @@ public void SetCustomNetInfoSpeedLimitIndex(NetInfo info, int customSpeedLimitIn 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; + CustomLaneSpeedLimitByNetInfoName[infoName] = customSpeedLimit; - float gameSpeedLimit = ToGameSpeedLimit(AvailableSpeedLimits[customSpeedLimitIndex]); + float gameSpeedLimit = ToGameSpeedLimit(customSpeedLimit); // save speed limit in all NetInfos #if DEBUGLOAD @@ -472,13 +422,13 @@ public void SetCustomNetInfoSpeedLimitIndex(NetInfo info, int customSpeedLimitIn List childNetInfoNames; if (childNetInfoNamesByCustomizableNetInfoName.TryGetValue(infoName, out childNetInfoNames)) { - foreach (string childNetInfoName in childNetInfoNames) { + foreach (var childNetInfoName in childNetInfoNames) { NetInfo childNetInfo; if (NetInfoByName.TryGetValue(childNetInfoName, out childNetInfo)) { #if DEBUGLOAD Log._Debug($"Updating child NetInfo {childNetInfoName}: Setting speed limit to {gameSpeedLimit}"); #endif - CustomLaneSpeedLimitIndexByNetInfoName[childNetInfoName] = customSpeedLimitIndex; + CustomLaneSpeedLimitByNetInfoName[childNetInfoName] = customSpeedLimit; UpdateNetInfoGameSpeedLimit(childNetInfo, gameSpeedLimit); } } @@ -517,29 +467,18 @@ private void UpdateNetInfoGameSpeedLimit(NetInfo info, float 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. - /// + /// Sets the speed limit of a given lane. /// /// /// /// - /// + /// Game speed units, 0=unlimited /// - public bool SetSpeedLimit(ushort segmentId, uint laneIndex, NetInfo.Lane laneInfo, uint laneId, ushort speedLimit) { + public bool SetSpeedLimit(ushort segmentId, uint laneIndex, NetInfo.Lane laneInfo, uint laneId, float speedLimit) { if (!MayHaveCustomSpeedLimits(laneInfo)) { return false; } - if (!AvailableSpeedLimits.Contains(speedLimit)) { + if (!SpeedLimit.IsValidRange(speedLimit)) { return false; } if (!Services.NetService.IsLaneValid(laneId)) { @@ -557,15 +496,15 @@ public bool SetSpeedLimit(ushort segmentId, uint laneIndex, NetInfo.Lane laneInf /// /// /// - public bool SetSpeedLimit(ushort segmentId, NetInfo.Direction finalDir, ushort speedLimit) { + public bool SetSpeedLimit(ushort segmentId, NetInfo.Direction finalDir, float speedLimit) { if (!MayHaveCustomSpeedLimits(segmentId, ref Singleton.instance.m_segments.m_buffer[segmentId])) { return false; } - if (!AvailableSpeedLimits.Contains(speedLimit)) { + if (!SpeedLimit.IsValidRange(speedLimit)) { return false; } - NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; if (segmentInfo == null) { #if DEBUG @@ -586,13 +525,15 @@ public bool SetSpeedLimit(ushort segmentId, NetInfo.Direction finalDir, ushort s while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) { NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; NetInfo.Direction d = laneInfo.m_finalDirection; - if (d != finalDir) + if (d != finalDir) { goto nextIter; - if (!MayHaveCustomSpeedLimits(laneInfo)) + } + if (!MayHaveCustomSpeedLimits(laneInfo)) { goto nextIter; - + } #if DEBUG - Log._Debug($"SpeedLimitManager: Setting speed limit of lane {curLaneId} to {speedLimit}"); + Log._Debug($"SpeedLimitManager: Setting speed limit of lane {curLaneId} " + + $"to {speedLimit * SpeedLimit.SPEED_TO_KMPH}"); #endif Flags.setLaneSpeedLimit(curLaneId, speedLimit); @@ -618,7 +559,7 @@ public override void OnBeforeLoadData() { vanillaLaneSpeedLimitsByNetInfoName.Clear(); customizableNetInfos.Clear(); - CustomLaneSpeedLimitIndexByNetInfoName.Clear(); + CustomLaneSpeedLimitByNetInfoName.Clear(); childNetInfoNamesByCustomizableNetInfoName.Clear(); NetInfoByName.Clear(); @@ -761,8 +702,8 @@ protected override void HandleInvalidSegment(ref ExtSegment seg) { uint curLaneId = Singleton.instance.m_segments.m_buffer[seg.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); + // NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex]; + // float? setSpeedLimit = Flags.getLaneSpeedLimit(curLaneId); Flags.setLaneSpeedLimit(curLaneId, null); @@ -789,19 +730,28 @@ public bool LoadData(List data) { 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); + var customSpeedLimit = GetCustomNetInfoSpeedLimit(info); #if DEBUGLOAD - 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}"); + Log._Debug($"SpeedLimitManager.LoadData: Handling lane {laneSpeedLimit.laneId}: " + + $"Custom speed limit of segment {segmentId} info ({info}, name={info?.name}, " + + $"lanes={info?.m_lanes} is {customSpeedLimit}"); #endif - if (customSpeedLimitIndex < 0 || AvailableSpeedLimits[customSpeedLimitIndex] != laneSpeedLimit.speedLimit) { + + if (SpeedLimit.IsValidRange(customSpeedLimit)) { // lane speed limit differs from default speed limit #if DEBUGLOAD Log._Debug($"SpeedLimitManager.LoadData: Loading lane speed limit: lane {laneSpeedLimit.laneId} = {laneSpeedLimit.speedLimit}"); #endif Flags.setLaneSpeedLimit(laneSpeedLimit.laneId, laneSpeedLimit.speedLimit); + Log._Debug($"SpeedLimitManager.LoadData: Loading lane speed limit: " + + $"lane {laneSpeedLimit.laneId} = {laneSpeedLimit.speedLimit} km/h"); + var kmph = laneSpeedLimit.speedLimit / SpeedLimit.SPEED_TO_KMPH; // convert to game units + Flags.setLaneSpeedLimit(laneSpeedLimit.laneId, kmph); } else { #if DEBUGLOAD - Log._Debug($"SpeedLimitManager.LoadData: Skipping lane speed limit of lane {laneSpeedLimit.laneId} ({laneSpeedLimit.speedLimit})"); + Log._Debug($"SpeedLimitManager.LoadData: " + + $"Skipping lane speed limit of lane {laneSpeedLimit.laneId} " + + $"({laneSpeedLimit.speedLimit} km/h)"); #endif } } catch (Exception e) { @@ -814,16 +764,17 @@ public bool LoadData(List data) { } List ICustomDataManager>.SaveData(ref bool success) { - List ret = new List(); - foreach (KeyValuePair e in Flags.getAllLaneSpeedLimits()) { + var ret = new List(); + foreach (var e in Flags.getAllLaneSpeedLimits()) { try { - Configuration.LaneSpeedLimit laneSpeedLimit = new Configuration.LaneSpeedLimit(e.Key, e.Value); + var laneSpeedLimit = new Configuration.LaneSpeedLimit(e.Key, e.Value); #if DEBUGSAVE - Log._Debug($"Saving speed limit of lane {laneSpeedLimit.laneId}: {laneSpeedLimit.speedLimit}"); + Log._Debug($"Saving speed limit of lane {laneSpeedLimit.laneId}: " + + $"{laneSpeedLimit.speedLimit*SpeedLimit.SPEED_TO_KMPH} km/h"); #endif ret.Add(laneSpeedLimit); } catch (Exception ex) { - Log.Error($"Exception occurred while saving lane speed limit @ {e.Key}: {ex.ToString()}"); + Log.Error($"Exception occurred while saving lane speed limit @ {e.Key}: {ex}"); success = false; } } @@ -831,28 +782,25 @@ public bool LoadData(List data) { } 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)) + foreach (var e in data) { + NetInfo netInfo; + 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); + if (e.Value >= 0f) { + SetCustomNetInfoSpeedLimit(netInfo, e.Value); } } - return success; + return true; // true = success } Dictionary ICustomDataManager>.SaveData(ref bool success) { - Dictionary ret = new Dictionary(); - foreach (KeyValuePair e in CustomLaneSpeedLimitIndexByNetInfoName) { + var ret = new Dictionary(); + foreach (var e in CustomLaneSpeedLimitByNetInfoName) { try { - ushort customSpeedLimit = AvailableSpeedLimits[e.Value]; - float gameSpeedLimit = ToGameSpeedLimit(customSpeedLimit); + float gameSpeedLimit = ToGameSpeedLimit(e.Value); ret.Add(e.Key, gameSpeedLimit); } catch (Exception ex) { diff --git a/TLM/TLM/Manager/Impl/TrafficLightManager.cs b/TLM/TLM/Manager/Impl/TrafficLightManager.cs index 8faf4f69a..83cd7abb0 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightManager.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightManager.cs @@ -11,6 +11,8 @@ using TrafficManager.Util; namespace TrafficManager.Manager.Impl { + using API.Traffic.Enums; + /// /// Manages traffic light toggling /// diff --git a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs index a6135d5d5..f0210a772 100644 --- a/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs +++ b/TLM/TLM/Manager/Impl/TrafficLightSimulationManager.cs @@ -1,710 +1,705 @@ -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; -using TrafficManager.Traffic.Enums; -using static RoadBaseAI; - 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 { get; private set; } = null; - //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 GetTrafficLightState( + using System; + using System.Collections.Generic; + using System.Linq; + using API.Traffic.Enums; + using API.TrafficLight; + using CSUtil.Commons; + using State; + using Traffic; + using TrafficLight; + using TrafficLight.Data; + using TrafficLight.Impl; + using static RoadBaseAI; + + 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 { get; private set; } = null; + //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 GetTrafficLightState( #if DEBUG - ushort vehicleId, ref Vehicle vehicleData, + 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) { + ushort nodeId, ushort fromSegmentId, byte fromLaneIndex, ushort toSegmentId, ref NetSegment segmentData, uint frame, out RoadBaseAI.TrafficLightState vehicleLightState, out RoadBaseAI.TrafficLightState pedestrianLightState) { - bool callStockMethod = true; + bool callStockMethod = true; #if BENCHMARK using (var bm = new Benchmark(null, "callStockMethod")) { #endif - callStockMethod = !Options.timedLightsEnabled || !TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId].IsSimulationRunning(); + 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 (callStockMethod) { + RoadBaseAI.GetTrafficLightState(nodeId, ref segmentData, frame, out vehicleLightState, out pedestrianLightState); + } else { #if BENCHMARK using (var bm = new Benchmark(null, "GetCustomTrafficLightState")) { #endif - GetCustomTrafficLightState( + GetCustomTrafficLightState( #if DEBUG - vehicleId, ref vehicleData, + vehicleId, ref vehicleData, #endif - nodeId, fromSegmentId, fromLaneIndex, toSegmentId, out vehicleLightState, out pedestrianLightState, ref TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId]); + nodeId, fromSegmentId, fromLaneIndex, toSegmentId, out vehicleLightState, out pedestrianLightState, ref TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId]); #if BENCHMARK } #endif - } - } + } + } - public void GetTrafficLightState( + public void GetTrafficLightState( #if DEBUG - ushort vehicleId, ref Vehicle vehicleData, + 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) { + 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; + bool callStockMethod = true; #if BENCHMARK using (var bm = new Benchmark(null, "callStockMethod")) { #endif - callStockMethod = !Options.timedLightsEnabled || !TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId].IsSimulationRunning(); + 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 (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( + GetCustomTrafficLightState( #if DEBUG - vehicleId, ref vehicleData, + vehicleId, ref vehicleData, #endif - nodeId, fromSegmentId, fromLaneIndex, toSegmentId, out vehicleLightState, out pedestrianLightState, ref TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId]); + nodeId, fromSegmentId, fromLaneIndex, toSegmentId, out vehicleLightState, out pedestrianLightState, ref TrafficLightSimulationManager.Instance.TrafficLightSimulations[nodeId]); #if BENCHMARK } #endif - vehicles = false; - pedestrians = false; - } - } + vehicles = false; + pedestrians = false; + } + } - // TODO this should be optimized - protected void GetCustomTrafficLightState( + // TODO this should be optimized + protected 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; - bool? isStartNode = Services.NetService.IsStartNode(fromSegmentId, nodeId); - if (isStartNode == null) { - Log.Error($"GetTrafficLightState: Invalid node {nodeId} for segment {fromSegmentId}."); - vehicleLightState = TrafficLightState.Green; - pedestrianLightState = TrafficLightState.Green; - return; - } - - ICustomSegmentLights lights = CustomSegmentLightsManager.Instance.GetSegmentLights(fromSegmentId, (bool)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 - vehicleLightState = light.GetLightState(toSegmentId); + 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; + bool? isStartNode = Services.NetService.IsStartNode(fromSegmentId, nodeId); + if (isStartNode == null) { + Log.Error($"GetTrafficLightState: Invalid node {nodeId} for segment {fromSegmentId}."); + vehicleLightState = TrafficLightState.Green; + pedestrianLightState = TrafficLightState.Green; + return; + } + + ICustomSegmentLights lights = CustomSegmentLightsManager.Instance.GetSegmentLights(fromSegmentId, (bool)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 + vehicleLightState = light.GetLightState(toSegmentId); #if DEBUG - //Log._Debug($"GetTrafficLightState: Getting light for vehicle {vehicleId} @ node {nodeId}, segment {fromSegmentId}, lane {fromLaneIndex}. vehicleLightState={vehicleLightState}, pedestrianLightState={pedestrianLightState}"); -#endif - } - - public void SetVisualState(ushort nodeId, ref NetSegment segmentData, uint frame, RoadBaseAI.TrafficLightState vehicleLightState, RoadBaseAI.TrafficLightState pedestrianLightState, bool vehicles, bool pedestrians) { - // stock code from RoadBaseAI.SetTrafficLightState - - 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; - } - } - } - - 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 SetUpManualTrafficLight(ref TrafficLightSimulations[nodeId]); - } - - /// - /// Adds a timed traffic light simulation to the node with the given id - /// - /// - public bool SetUpTimedTrafficLight(ushort nodeId, IList nodeGroup) { // TODO improve signature - if (! SetUpTimedTrafficLight(ref TrafficLightSimulations[nodeId], nodeGroup)) { - return false; - } - - return true; - } - - /// - /// Destroys the traffic light and removes it - /// - /// - /// - public void RemoveNodeFromSimulation(ushort nodeId, bool destroyGroup, bool removeTrafficLight) { + //Log._Debug($"GetTrafficLightState: Getting light for vehicle {vehicleId} @ node {nodeId}, segment {fromSegmentId}, lane {fromLaneIndex}. vehicleLightState={vehicleLightState}, pedestrianLightState={pedestrianLightState}"); +#endif + } + + public void SetVisualState(ushort nodeId, ref NetSegment segmentData, uint frame, RoadBaseAI.TrafficLightState vehicleLightState, RoadBaseAI.TrafficLightState pedestrianLightState, bool vehicles, bool pedestrians) { + // stock code from RoadBaseAI.SetTrafficLightState + + 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; + } + } + } + + 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 SetUpManualTrafficLight(ref TrafficLightSimulations[nodeId]); + } + + /// + /// Adds a timed traffic light simulation to the node with the given id + /// + /// + public bool SetUpTimedTrafficLight(ushort nodeId, IList nodeGroup) { // TODO improve signature + if (! SetUpTimedTrafficLight(ref TrafficLightSimulations[nodeId], 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[nodeId].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) { + 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[nodeId].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 - - Destroy(ref TrafficLightSimulations[nodeId]); - } - - public override void OnLevelUnloading() { - base.OnLevelUnloading(); - for (uint nodeId = 0; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { - Destroy(ref TrafficLightSimulations[nodeId]); - } - } - - public bool SetUpManualTrafficLight(ref TrafficLightSimulation sim) { - if (sim.IsTimedLight()) { - return false; - } - - Constants.ServiceFactory.NetService.ProcessNode(sim.nodeId, delegate (ushort nId, ref NetNode node) { - Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nId, ref node); - return true; - }); - - Constants.ManagerFactory.CustomSegmentLightsManager.AddNodeLights(sim.nodeId); - sim.type = TrafficLightSimulationType.Manual; - return true; - } - - public bool DestroyManualTrafficLight(ref TrafficLightSimulation sim) { - if (sim.IsTimedLight()) { - return false; - } - if (!sim.IsManualLight()) { - return false; - } - - sim.type = TrafficLightSimulationType.None; - Constants.ManagerFactory.CustomSegmentLightsManager.RemoveNodeLights(sim.nodeId); - return true; - } - - public bool SetUpTimedTrafficLight(ref TrafficLightSimulation sim, IList nodeGroup) { - if (sim.IsManualLight()) { - DestroyManualTrafficLight(ref sim); - } - - if (sim.IsTimedLight()) { - return false; - } - - Constants.ServiceFactory.NetService.ProcessNode(sim.nodeId, delegate (ushort nId, ref NetNode node) { - Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nId, ref node); - return true; - }); - - Constants.ManagerFactory.CustomSegmentLightsManager.AddNodeLights(sim.nodeId); - sim.timedLight = new TimedTrafficLights(sim.nodeId, nodeGroup); - sim.type = TrafficLightSimulationType.Timed; - return true; - } - - public bool DestroyTimedTrafficLight(ref TrafficLightSimulation sim) { - if (!sim.IsTimedLight()) { - return false; - } - - sim.type = TrafficLightSimulationType.None; - var timedLight = sim.timedLight; - sim.timedLight = null; - - if (timedLight != null) { - timedLight.Destroy(); - } - return true; - } - - public void Destroy(ref TrafficLightSimulation sim) { - DestroyTimedTrafficLight(ref sim); - DestroyManualTrafficLight(ref sim); - } - - protected override void HandleInvalidNode(ushort nodeId, ref NetNode node) { - RemoveNodeFromSimulation(nodeId, false, true); - } - - protected override void HandleValidNode(ushort nodeId, ref NetNode node) { + Log._Debug($"TrafficLightSimulationManager.RemoveNodeFromSimulation({nodeId}) called."); +#endif + + Destroy(ref TrafficLightSimulations[nodeId]); + } + + public override void OnLevelUnloading() { + base.OnLevelUnloading(); + for (uint nodeId = 0; nodeId < NetManager.MAX_NODE_COUNT; ++nodeId) { + Destroy(ref TrafficLightSimulations[nodeId]); + } + } + + public bool SetUpManualTrafficLight(ref TrafficLightSimulation sim) { + if (sim.IsTimedLight()) { + return false; + } + + Constants.ServiceFactory.NetService.ProcessNode(sim.nodeId, delegate (ushort nId, ref NetNode node) { + Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nId, ref node); + return true; + }); + + Constants.ManagerFactory.CustomSegmentLightsManager.AddNodeLights(sim.nodeId); + sim.type = TrafficLightSimulationType.Manual; + return true; + } + + public bool DestroyManualTrafficLight(ref TrafficLightSimulation sim) { + if (sim.IsTimedLight()) { + return false; + } + if (!sim.IsManualLight()) { + return false; + } + + sim.type = TrafficLightSimulationType.None; + Constants.ManagerFactory.CustomSegmentLightsManager.RemoveNodeLights(sim.nodeId); + return true; + } + + public bool SetUpTimedTrafficLight(ref TrafficLightSimulation sim, IList nodeGroup) { + if (sim.IsManualLight()) { + DestroyManualTrafficLight(ref sim); + } + + if (sim.IsTimedLight()) { + return false; + } + + Constants.ServiceFactory.NetService.ProcessNode(sim.nodeId, delegate (ushort nId, ref NetNode node) { + Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nId, ref node); + return true; + }); + + Constants.ManagerFactory.CustomSegmentLightsManager.AddNodeLights(sim.nodeId); + sim.timedLight = new TimedTrafficLights(sim.nodeId, nodeGroup); + sim.type = TrafficLightSimulationType.Timed; + return true; + } + + public bool DestroyTimedTrafficLight(ref TrafficLightSimulation sim) { + if (!sim.IsTimedLight()) { + return false; + } + + sim.type = TrafficLightSimulationType.None; + var timedLight = sim.timedLight; + sim.timedLight = null; + + if (timedLight != null) { + timedLight.Destroy(); + } + return true; + } + + public void Destroy(ref TrafficLightSimulation sim) { + DestroyTimedTrafficLight(ref sim); + DestroyManualTrafficLight(ref sim); + } + + protected override void HandleInvalidNode(ushort nodeId, ref NetNode node) { + RemoveNodeFromSimulation(nodeId, false, true); + } + + protected override void HandleValidNode(ushort nodeId, ref NetNode node) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[7] && (GlobalConfig.Instance.Debug.NodeId == 0 || GlobalConfig.Instance.Debug.NodeId == nodeId); + bool debug = GlobalConfig.Instance.Debug.Switches[7] && (GlobalConfig.Instance.Debug.NodeId == 0 || GlobalConfig.Instance.Debug.NodeId == nodeId); #endif - if (!TrafficLightSimulations[nodeId].HasSimulation()) { + if (!TrafficLightSimulations[nodeId].HasSimulation()) { #if DEBUG - if (debug) - Log._Debug($"TrafficLightSimulationManager.HandleValidNode({nodeId}): Node is not controlled by a custom traffic light simulation."); + if (debug) + Log._Debug($"TrafficLightSimulationManager.HandleValidNode({nodeId}): Node is not controlled by a custom traffic light simulation."); #endif - return; - } + return; + } - if (! Flags.mayHaveTrafficLight(nodeId)) { + if (! Flags.mayHaveTrafficLight(nodeId)) { #if DEBUG - if (debug) - Log._Debug($"TrafficLightSimulationManager.HandleValidNode({nodeId}): Node must not have a traffic light: Removing traffic light simulation."); + if (debug) + Log._Debug($"TrafficLightSimulationManager.HandleValidNode({nodeId}): Node must not have a traffic light: Removing traffic light simulation."); #endif - RemoveNodeFromSimulation(nodeId, false, true); - return; - } + RemoveNodeFromSimulation(nodeId, false, true); + return; + } - for (int i = 0; i < 8; ++i) { - ushort segmentId = node.GetSegment(i); - if (segmentId == 0) { - continue; - } + for (int i = 0; i < 8; ++i) { + ushort segmentId = node.GetSegment(i); + if (segmentId == 0) { + continue; + } - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, nodeId); + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, nodeId); #if DEBUG - if (debug) - Log._Debug($"TrafficLightSimulationManager.HandleValidNode({nodeId}): Adding live traffic lights to segment {segmentId}"); -#endif - // housekeep timed light - CustomSegmentLightsManager.Instance.GetSegmentLights(segmentId, startNode).Housekeeping(true, true); - } - - // ensure there is a physical traffic light - Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nodeId, ref node); - - TrafficLightSimulations[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]; + if (debug) + Log._Debug($"TrafficLightSimulationManager.HandleValidNode({nodeId}): Adding live traffic lights to segment {segmentId}"); +#endif + // housekeep timed light + CustomSegmentLightsManager.Instance.GetSegmentLights(segmentId, startNode).Housekeeping(true, true); + } + + // ensure there is a physical traffic light + Constants.ManagerFactory.TrafficLightManager.AddTrafficLight(nodeId, ref node); + + TrafficLightSimulations[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]; #if DEBUGLOAD Log._Debug($"Adding timed light at node {cnfTimedLights.nodeId}. NodeGroup: {string.Join(", ", nodeGroup.Select(x => x.ToString()).ToArray())}"); #endif - SetUpTimedTrafficLight(cnfTimedLights.nodeId, nodeGroup); + SetUpTimedTrafficLight(cnfTimedLights.nodeId, nodeGroup); - int j = 0; - foreach (Configuration.TimedTrafficLightsStep cnfTimedStep in cnfTimedLights.timedSteps) { + int j = 0; + foreach (Configuration.TimedTrafficLightsStep cnfTimedStep in cnfTimedLights.timedSteps) { #if DEBUGLOAD Log._Debug($"Loading timed step {j} at node {cnfTimedLights.nodeId}"); #endif - ITimedTrafficLightsStep step = TrafficLightSimulations[cnfTimedLights.nodeId].timedLight.AddStep(cnfTimedStep.minTime, cnfTimedStep.maxTime, (StepChangeMetric)cnfTimedStep.changeMetric, cnfTimedStep.waitFlowBalance); + ITimedTrafficLightsStep step = TrafficLightSimulations[cnfTimedLights.nodeId].timedLight.AddStep(cnfTimedStep.minTime, cnfTimedStep.maxTime, (StepChangeMetric)cnfTimedStep.changeMetric, cnfTimedStep.waitFlowBalance); - foreach (KeyValuePair e in cnfTimedStep.segmentLights) { - if (!Services.NetService.IsSegmentValid(e.Key)) - continue; - e.Value.nodeId = cnfTimedLights.nodeId; + foreach (KeyValuePair e in cnfTimedStep.segmentLights) { + if (!Services.NetService.IsSegmentValid(e.Key)) + continue; + e.Value.nodeId = cnfTimedLights.nodeId; #if DEBUGLOAD Log._Debug($"Loading timed step {j}, segment {e.Key} at node {cnfTimedLights.nodeId}"); #endif - ICustomSegmentLights lights = null; - if (!step.CustomSegmentLights.TryGetValue(e.Key, out lights)) { + ICustomSegmentLights lights = null; + if (!step.CustomSegmentLights.TryGetValue(e.Key, out lights)) { #if DEBUGLOAD Log._Debug($"No segment lights found at timed step {j} for segment {e.Key}, node {cnfTimedLights.nodeId}"); #endif - continue; - } - Configuration.CustomSegmentLights cnfLights = e.Value; + continue; + } + Configuration.CustomSegmentLights cnfLights = e.Value; #if DEBUGLOAD Log._Debug($"Loading pedestrian light @ seg. {e.Key}, step {j}: {cnfLights.pedestrianLightState} {cnfLights.manualPedestrianMode}"); #endif - lights.ManualPedestrianMode = cnfLights.manualPedestrianMode; - lights.PedestrianLightState = cnfLights.pedestrianLightState; + lights.ManualPedestrianMode = cnfLights.manualPedestrianMode; + lights.PedestrianLightState = cnfLights.pedestrianLightState; - bool first = true; // v1.10.2 transitional code - foreach (KeyValuePair e2 in cnfLights.customLights) { + bool first = true; // v1.10.2 transitional code + foreach (var e2 in cnfLights.customLights) { #if DEBUGLOAD Log._Debug($"Loading timed step {j}, segment {e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}"); #endif - ICustomSegmentLight light = null; - if (!lights.CustomLights.TryGetValue(e2.Key, out light)) { + ICustomSegmentLight light = null; + if (!lights.CustomLights.TryGetValue(LegacyExtVehicleType.ToNew(e2.Key), out light)) { #if DEBUGLOAD Log._Debug($"No segment light found for timed step {j}, segment {e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}"); #endif - // v1.10.2 transitional code START - if (first) { - first = false; - if (!lights.CustomLights.TryGetValue(CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE, out light)) { + // v1.10.2 transitional code START + if (first) { + first = false; + if (!lights.CustomLights.TryGetValue(CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE, out light)) { #if DEBUGLOAD Log._Debug($"No segment light found for timed step {j}, segment {e.Key}, DEFAULT vehicleType {CustomSegmentLights.DEFAULT_MAIN_VEHICLETYPE} at node {cnfTimedLights.nodeId}"); #endif - 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 = (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; - } + 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 = (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; + } #if DEBUGSAVE Log._Debug($"Going to save timed light at node {nodeId}."); #endif - var timedNode = TrafficLightSimulations[nodeId].timedLight; - timedNode.OnGeometryUpdate(); + var timedNode = TrafficLightSimulations[nodeId].timedLight; + timedNode.OnGeometryUpdate(); - Configuration.TimedTrafficLights cnfTimedLights = new Configuration.TimedTrafficLights(); - ret.Add(cnfTimedLights); + 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(); + 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++) { + for (var j = 0; j < timedNode.NumSteps(); j++) { #if DEBUGSAVE Log._Debug($"Saving timed light step {j} at node {nodeId}."); #endif - ITimedTrafficLightsStep timedStep = timedNode.GetStep(j); - Configuration.TimedTrafficLightsStep cnfTimedStep = new Configuration.TimedTrafficLightsStep(); - cnfTimedLights.timedSteps.Add(cnfTimedStep); + 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) { + 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) { #if DEBUGSAVE Log._Debug($"Saving timed light step {j}, segment {e.Key} at node {nodeId}."); #endif - ICustomSegmentLights segLights = e.Value; - Configuration.CustomSegmentLights cnfSegLights = new Configuration.CustomSegmentLights(); + 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; - } + 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; + 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); + cnfTimedStep.segmentLights.Add(e.Key, cnfSegLights); #if DEBUGSAVE Log._Debug($"Saving pedestrian light @ seg. {e.Key}, step {j}: {cnfSegLights.pedestrianLightState} {cnfSegLights.manualPedestrianMode}"); #endif - foreach (KeyValuePair e2 in segLights.CustomLights) { + foreach (var e2 in segLights.CustomLights) { #if DEBUGSAVE Log._Debug($"Saving timed light step {j}, segment {e.Key}, vehicleType {e2.Key} at node {nodeId}."); #endif - 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; - } - } -} + ICustomSegmentLight segLight = e2.Value; + Configuration.CustomSegmentLight cnfSegLight = new Configuration.CustomSegmentLight(); + cnfSegLights.customLights.Add(LegacyExtVehicleType.ToOld(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; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/TrafficPriorityManager.cs b/TLM/TLM/Manager/Impl/TrafficPriorityManager.cs index 808775a0d..a47e1c2bb 100644 --- a/TLM/TLM/Manager/Impl/TrafficPriorityManager.cs +++ b/TLM/TLM/Manager/Impl/TrafficPriorityManager.cs @@ -1,308 +1,307 @@ -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; -using TrafficManager.Traffic.Enums; - namespace TrafficManager.Manager.Impl { - public class TrafficPriorityManager : AbstractGeometryObservingManager, ICustomDataManager>, ICustomDataManager>, ITrafficPriorityManager { - public static readonly TrafficPriorityManager Instance = new TrafficPriorityManager(); - - /// - /// 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]}"); - } - } + using System; + using System.Collections.Generic; + using API.Traffic.Data; + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using CSUtil.Commons; + using Geometry; + using State; + using Traffic; + using Traffic.Data; + using TrafficLight; + using UnityEngine; + + public class TrafficPriorityManager : AbstractGeometryObservingManager, + ICustomDataManager>, ICustomDataManager>, + ITrafficPriorityManager { + public static readonly TrafficPriorityManager Instance = new TrafficPriorityManager(); + + /// + /// 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; - } + protected void AddInvalidPrioritySegment(ushort segmentId, ref PrioritySegment prioritySegment) { + invalidPrioritySegments[segmentId] = prioritySegment; + } - public bool MayNodeHavePrioritySigns(ushort nodeId) { - SetPrioritySignUnableReason reason; - return MayNodeHavePrioritySigns(nodeId, out reason); - } + public bool MayNodeHavePrioritySigns(ushort nodeId) { + SetPrioritySignUnableReason reason; + return MayNodeHavePrioritySigns(nodeId, out reason); + } - public bool MayNodeHavePrioritySigns(ushort nodeId, out SetPrioritySignUnableReason reason) { + public bool MayNodeHavePrioritySigns(ushort nodeId, out SetPrioritySignUnableReason reason) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); + 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 = SetPrioritySignUnableReason.NoJunction; + if (!Services.NetService.CheckNodeFlags(nodeId, NetNode.Flags.Created | NetNode.Flags.Deleted | NetNode.Flags.Junction, NetNode.Flags.Created | NetNode.Flags.Junction)) { + reason = SetPrioritySignUnableReason.NoJunction; #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=false, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=false, reason={reason}"); + } #endif - return false; - } + return false; + } - if (TrafficLightSimulationManager.Instance.HasTimedSimulation(nodeId)) { - reason = SetPrioritySignUnableReason.HasTimedLight; + if (TrafficLightSimulationManager.Instance.HasTimedSimulation(nodeId)) { + reason = SetPrioritySignUnableReason.HasTimedLight; #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=false, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=false, reason={reason}"); + } #endif - return false; - } + return false; + } - //Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=true"); - reason = SetPrioritySignUnableReason.None; - return true; - } + //Log._Debug($"TrafficPriorityManager.MayNodeHavePrioritySigns: nodeId={nodeId}, result=true"); + reason = SetPrioritySignUnableReason.None; + return true; + } - public bool MaySegmentHavePrioritySign(ushort segmentId, bool startNode) { - SetPrioritySignUnableReason reason; - return MaySegmentHavePrioritySign(segmentId, startNode, out reason); - } + public bool MaySegmentHavePrioritySign(ushort segmentId, bool startNode) { + SetPrioritySignUnableReason reason; + return MaySegmentHavePrioritySign(segmentId, startNode, out reason); + } - public bool MaySegmentHavePrioritySign(ushort segmentId, bool startNode, out SetPrioritySignUnableReason reason) { + public bool MaySegmentHavePrioritySign(ushort segmentId, bool startNode, out SetPrioritySignUnableReason reason) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); #endif - if (! Services.NetService.IsSegmentValid(segmentId)) { - reason = SetPrioritySignUnableReason.InvalidSegment; + if (! Services.NetService.IsSegmentValid(segmentId)) { + reason = SetPrioritySignUnableReason.InvalidSegment; #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); + } #endif - return false; - } + return false; + } - if (! MayNodeHavePrioritySigns(Services.NetService.GetSegmentNodeId(segmentId, startNode), out reason)) { + 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}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); + } #endif - return false; - } + return false; + } - IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - if (segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)].outgoing && segMan.ExtSegments[segmentId].oneWay) { - reason = SetPrioritySignUnableReason.NotIncoming; + IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + if (segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)].outgoing && segMan.ExtSegments[segmentId].oneWay) { + reason = SetPrioritySignUnableReason.NotIncoming; #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=false, reason={reason}"); + } #endif - return false; - } + return false; + } #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=true"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, startNode={startNode}, result=true"); + } #endif - reason = SetPrioritySignUnableReason.None; - return true; - } + reason = SetPrioritySignUnableReason.None; + return true; + } - public bool MaySegmentHavePrioritySign(ushort segmentId) { - SetPrioritySignUnableReason reason; - return MaySegmentHavePrioritySign(segmentId, out reason); - } + public bool MaySegmentHavePrioritySign(ushort segmentId) { + SetPrioritySignUnableReason reason; + return MaySegmentHavePrioritySign(segmentId, out reason); + } - public bool MaySegmentHavePrioritySign(ushort segmentId, out SetPrioritySignUnableReason reason) { + public bool MaySegmentHavePrioritySign(ushort segmentId, out SetPrioritySignUnableReason reason) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); #endif - if (!Services.NetService.IsSegmentValid(segmentId)) { - reason = SetPrioritySignUnableReason.InvalidSegment; + if (!Services.NetService.IsSegmentValid(segmentId)) { + reason = SetPrioritySignUnableReason.InvalidSegment; #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, result=false, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, result=false, reason={reason}"); + } #endif - return false; - } + return false; + } - bool ret = - (MaySegmentHavePrioritySign(segmentId, true, out reason) || - MaySegmentHavePrioritySign(segmentId, false, out reason)); + 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}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.MaySegmentHavePrioritySign: segmentId={segmentId}, result={ret}, reason={reason}"); + } #endif - return ret; - } + return ret; + } - public bool HasSegmentPrioritySign(ushort segmentId) { - return !PrioritySegments[segmentId].IsDefault(); - } + public bool HasSegmentPrioritySign(ushort segmentId) { + return !PrioritySegments[segmentId].IsDefault(); + } - public bool HasSegmentPrioritySign(ushort segmentId, bool startNode) { - return PrioritySegments[segmentId].HasPrioritySignAtNode(startNode); - } + public bool HasSegmentPrioritySign(ushort segmentId, bool startNode) { + return PrioritySegments[segmentId].HasPrioritySignAtNode(startNode); + } - public bool HasNodePrioritySign(ushort nodeId) { + public bool HasNodePrioritySign(ushort nodeId) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.NodeId <= 0 || nodeId == GlobalConfig.Instance.Debug.NodeId); + 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; - }); + 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}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.HasNodePrioritySign: nodeId={nodeId}, result={ret}"); + } #endif - return ret; - } + return ret; + } - public bool SetPrioritySign(ushort segmentId, bool startNode, PriorityType type) { - SetPrioritySignUnableReason reason; - return SetPrioritySign(segmentId, startNode, type, out reason); - } + public bool SetPrioritySign(ushort segmentId, bool startNode, PriorityType type) { + SetPrioritySignUnableReason reason; + return SetPrioritySign(segmentId, startNode, type, out reason); + } - public bool SetPrioritySign(ushort segmentId, bool startNode, PriorityType type, out SetPrioritySignUnableReason reason) { + public bool SetPrioritySign(ushort segmentId, bool startNode, PriorityType type, out SetPrioritySignUnableReason reason) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); + bool debug = GlobalConfig.Instance.Debug.Switches[13] && (GlobalConfig.Instance.Debug.SegmentId <= 0 || segmentId == GlobalConfig.Instance.Debug.SegmentId); #endif - bool ret = true; - reason = SetPrioritySignUnableReason.None; + bool ret = true; + reason = SetPrioritySignUnableReason.None; - if (type != PriorityType.None && - ! MaySegmentHavePrioritySign(segmentId, startNode, out reason)) { + 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}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.SetPrioritySign: Segment {segmentId} @ {startNode} may not have a priority sign: {reason}"); + } #endif - ret = false; - type = PriorityType.None; - } + ret = false; + type = PriorityType.None; + } - if (type != PriorityType.None) { - ushort nodeId = Services.NetService.GetSegmentNodeId(segmentId, startNode); - Services.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { - TrafficLightManager.Instance.RemoveTrafficLight(nodeId, ref node); - return true; - }); - } + if (type != PriorityType.None) { + ushort nodeId = Services.NetService.GetSegmentNodeId(segmentId, 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; - } + if (startNode) { + PrioritySegments[segmentId].startType = type; + } else { + PrioritySegments[segmentId].endType = type; + } - SegmentEndManager.Instance.UpdateSegmentEnd(segmentId, startNode); + SegmentEndManager.Instance.UpdateSegmentEnd(segmentId, startNode); #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.SetPrioritySign: segmentId={segmentId}, startNode={startNode}, type={type}, result={ret}, reason={reason}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.SetPrioritySign: segmentId={segmentId}, startNode={startNode}, type={type}, result={ret}, reason={reason}"); + } #endif - return ret; - } + return ret; + } - public void RemovePrioritySignsFromNode(ushort nodeId) { + 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}"); - } + 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; - }); - } + Services.NetService.IterateNodeSegments(nodeId, delegate(ushort segmentId, ref NetSegment segment) { + RemovePrioritySignFromSegmentEnd(segmentId, nodeId == segment.m_startNode); + return true; + }); + } - public void RemovePrioritySignsFromSegment(ushort segmentId) { + 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}"); - } + 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); - } + RemovePrioritySignFromSegmentEnd(segmentId, true); + RemovePrioritySignFromSegmentEnd(segmentId, false); + } - public void RemovePrioritySignFromSegmentEnd(ushort segmentId, bool startNode) { + 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}"); - } + 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; - } + if (startNode) { + PrioritySegments[segmentId].startType = PriorityType.None; + } else { + PrioritySegments[segmentId].endType = PriorityType.None; + } - SegmentEndManager.Instance.UpdateSegmentEnd(segmentId, startNode); - } + SegmentEndManager.Instance.UpdateSegmentEnd(segmentId, startNode); + } - public PriorityType GetPrioritySign(ushort segmentId, bool startNode) { - return startNode ? PrioritySegments[segmentId].startType : PrioritySegments[segmentId].endType; - } + public PriorityType GetPrioritySign(ushort segmentId, bool startNode) { + return startNode ? PrioritySegments[segmentId].startType : PrioritySegments[segmentId].endType; + } - public byte CountPrioritySignsAtNode(ushort nodeId, PriorityType sign) { + 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); + 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; - }); + 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}"); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.CountPrioritySignsAtNode: nodeId={nodeId}, sign={sign}, result={ret}"); + } #endif - return ret; - } + return ret; + } - public bool HasPriority(ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Position curPos, ref ExtSegmentEnd curEnd, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, ref NetNode transitNode) { - IExtSegmentEndManager extSegEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + public bool HasPriority(ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Position curPos, ref ExtSegmentEnd curEnd, ushort transitNodeId, bool startNode, ref PathUnit.Position nextPos, ref NetNode transitNode) { + IExtSegmentEndManager extSegEndMan = Constants.ManagerFactory.ExtSegmentEndManager; // ISegmentEndGeometry endGeo = SegmentGeometry.Get(curPos.m_segment)?.GetEnd(startNode); // if (Constants.ManagerFactory.ExtSegmentManager == null) { @@ -312,897 +311,897 @@ public bool HasPriority(ushort vehicleId, ref Vehicle vehicle, ref PathUnit.Posi //#endif // } - /*SegmentEnd end = SegmentEndManager.Instance.GetSegmentEnd(curPos.m_segment, startNode); - if (end == null) { + /*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; + Log.Warning($"TrafficPriorityManager.HasPriority({vehicleId}): No segment end found for segment {curPos.m_segment} @ {startNode}"); + return true; #endif - } - ushort transitNodeId = end.NodeId;*/ + } + 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}"); - } + 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 ((vehicle.m_flags & Vehicle.Flags.Spawned) == 0) { #if DEBUG - if (debug) - Log.Warning($"TrafficPriorityManager.HasPriority({vehicleId}): Vehicle is not spawned."); + if (debug) + Log.Warning($"TrafficPriorityManager.HasPriority({vehicleId}): Vehicle is not spawned."); #endif - return true; - } + return true; + } - if ((vehicle.m_flags & Vehicle.Flags.Emergency2) != 0) { - // target vehicle is on emergency + 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."); + if (debug) + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): Vehicle is on emergency."); #endif - return true; - } + return true; + } - if (vehicle.Info.m_vehicleType == VehicleInfo.VehicleType.Monorail) { - // monorails do not obey priority signs + 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."); + 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) { + 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"); + 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; + curSign = PriorityType.Main; + } + bool onMain = curSign == PriorityType.Main; - /*if (! Services.VehicleService.IsVehicleValid(vehicleId)) { - curEnd.RequestCleanup(); - return true; - }*/ + /*if (! Services.VehicleService.IsVehicleValid(vehicleId)) { + curEnd.RequestCleanup(); + return true; + }*/ - // calculate approx. time after which the transit node will be reached - Vector3 targetToNode = transitNode.m_position - vehicle.GetLastFramePosition(); - Vector3 targetVel = vehicle.GetLastFrameVelocity(); - float targetSpeed = targetVel.magnitude; - float targetDistanceToTransitNode = targetToNode.magnitude; + // calculate approx. time after which the transit node will be reached + Vector3 targetToNode = transitNode.m_position - vehicle.GetLastFramePosition(); + Vector3 targetVel = vehicle.GetLastFrameVelocity(); + float targetSpeed = targetVel.magnitude; + float targetDistanceToTransitNode = targetToNode.magnitude; - float targetTimeToTransitNode = Single.NaN; - if (targetSpeed > 0) - targetTimeToTransitNode = targetDistanceToTransitNode / targetSpeed; - else - targetTimeToTransitNode = 0; + float targetTimeToTransitNode = Single.NaN; + 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}"); + if (debug) + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): estimated target time to transit node {transitNodeId} is {targetTimeToTransitNode} for vehicle {vehicleId}"); #endif - ArrowDirection targetToDir = extSegEndMan.GetDirection(ref curEnd, nextPos.m_segment); // absolute target direction of target vehicle + ArrowDirection targetToDir = extSegEndMan.GetDirection(ref curEnd, 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 - ExtVehicleManager vehStateManager = ExtVehicleManager.Instance; - CustomSegmentLightsManager segLightsManager = CustomSegmentLightsManager.Instance; + // iterate over all cars approaching the transit node and check if the target vehicle should be prioritized + ExtVehicleManager vehStateManager = ExtVehicleManager.Instance; + CustomSegmentLightsManager segLightsManager = CustomSegmentLightsManager.Instance; - for (int i = 0; i < 8; ++i) { - ushort otherSegmentId = transitNode.GetSegment(i); - if (otherSegmentId == curEnd.segmentId || otherSegmentId == 0) { - continue; - } + for (int i = 0; i < 8; ++i) { + ushort otherSegmentId = transitNode.GetSegment(i); + if (otherSegmentId == curEnd.segmentId || otherSegmentId == 0) { + continue; + } - bool otherStartNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(otherSegmentId, transitNodeId); - /*ISegmentEnd incomingEnd = SegmentEndManager.Instance.GetSegmentEnd(otherSegmentId, otherStartNode); - if (incomingEnd == null) { + bool otherStartNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(otherSegmentId, transitNodeId); + /*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}"); + if (debug) + Log.Error($"TrafficPriorityManager.HasPriority({vehicleId}): No segment end found for other segment {otherSegmentId} @ {otherStartNode}"); #endif - return true; - }*/ + return true; + }*/ - ICustomSegmentLights otherLights = null; - if (Options.trafficLightPriorityRules) { - otherLights = segLightsManager.GetSegmentLights(otherSegmentId, otherStartNode, false); - } + 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; + PriorityType otherSign = GetPrioritySign(otherSegmentId, otherStartNode); + if (otherSign == PriorityType.None) { + otherSign = PriorityType.Main; + //continue; + } + bool incomingOnMain = otherSign == PriorityType.Main; - ArrowDirection incomingFromDir = extSegEndMan.GetDirection(ref curEnd, otherSegmentId); // absolute incoming direction of incoming vehicle + ArrowDirection incomingFromDir = extSegEndMan.GetDirection(ref curEnd, otherSegmentId); // absolute incoming direction of incoming vehicle #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): checking other segment {otherSegmentId} @ {transitNodeId}"); + if (debug) + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): checking other segment {otherSegmentId} @ {transitNodeId}"); #endif - int otherEndIndex = extSegEndMan.GetIndex(otherSegmentId, otherStartNode); - ushort incomingVehicleId = extSegEndMan.ExtSegmentEnds[otherEndIndex].firstVehicleId; - int numIter = 0; - while (incomingVehicleId != 0) { + int otherEndIndex = extSegEndMan.GetIndex(otherSegmentId, otherStartNode); + ushort incomingVehicleId = extSegEndMan.ExtSegmentEnds[otherEndIndex].firstVehicleId; + int numIter = 0; + while (incomingVehicleId != 0) { #if DEBUG - if (debug) { - Log._Debug(""); - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): checking other vehicle {incomingVehicleId} @ seg. {otherSegmentId}"); - } + 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, ref curEnd, targetToDir, incomingVehicleId, ref Singleton.instance.m_vehicles.m_buffer[incomingVehicleId], ref vehStateManager.ExtVehicles[incomingVehicleId], incomingOnMain, ref extSegEndMan.ExtSegmentEnds[otherEndIndex], otherLights, incomingFromDir)) { + if (IsConflictingVehicle(debug, transitNode.m_position, targetTimeToTransitNode, vehicleId, ref vehicle, ref curPos, transitNodeId, startNode, ref nextPos, onMain, ref curEnd, targetToDir, incomingVehicleId, ref Singleton.instance.m_vehicles.m_buffer[incomingVehicleId], ref vehStateManager.ExtVehicles[incomingVehicleId], incomingOnMain, ref extSegEndMan.ExtSegmentEnds[otherEndIndex], otherLights, incomingFromDir)) { #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): incoming vehicle {incomingVehicleId} is conflicting."); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): incoming vehicle {incomingVehicleId} is conflicting."); + } #endif - return false; - } + return false; + } - // check next incoming vehicle - incomingVehicleId = vehStateManager.ExtVehicles[incomingVehicleId].nextVehicleIdOnSegment; + // check next incoming vehicle + incomingVehicleId = vehStateManager.ExtVehicles[incomingVehicleId].nextVehicleIdOnSegment; - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { - CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); - break; - } - } - } + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { + CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); + break; + } + } + } #if DEBUG - if (debug) { - Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): No conflicting incoming vehicles found."); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.HasPriority({vehicleId}): No conflicting incoming vehicles found."); + } #endif - return true; - } + 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, ref ExtSegmentEnd curEnd, - ArrowDirection targetToDir, ushort incomingVehicleId, ref Vehicle incomingVehicle, ref ExtVehicle incomingState, bool incomingOnMain, - ref ExtSegmentEnd incomingEnd, ICustomSegmentLights incomingLights, ArrowDirection incomingFromDir) { + 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, ref ExtSegmentEnd curEnd, + ArrowDirection targetToDir, ushort incomingVehicleId, ref Vehicle incomingVehicle, ref ExtVehicle incomingState, bool incomingOnMain, + ref ExtSegmentEnd incomingEnd, 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}"); - } + 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 & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { + if ((incomingState.flags & ExtVehicleFlags.Spawned) == ExtVehicleFlags.None) { #if DEBUG - if (debug) - Log.Warning($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming vehicle is not spawned."); + if (debug) + Log.Warning($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming vehicle is not spawned."); #endif - return false; - } + return false; + } - if (incomingVehicle.Info.m_vehicleType == VehicleInfo.VehicleType.Monorail) { - // monorails and cars do not collide + 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."); - } + if (debug) { + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming vehicle is a monorail."); + } #endif - return false; - } + return false; + } - ArrowDirection incomingToRelDir = Constants.ManagerFactory.ExtSegmentEndManager.GetDirection(ref incomingEnd, incomingState.nextSegmentId); // relative target direction of incoming vehicle + ArrowDirection incomingToRelDir = Constants.ManagerFactory.ExtSegmentEndManager.GetDirection(ref incomingEnd, incomingState.nextSegmentId); // relative target direction of incoming vehicle - if (incomingLights != null) { - ICustomSegmentLight incomingLight = incomingLights.GetCustomLight(incomingState.currentLaneIndex); + 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)}"); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Detected traffic light. Incoming state ({incomingToRelDir}): {incomingLight.GetLightState(incomingToRelDir)}"); #endif - if (incomingLight.IsRed(incomingToRelDir)) { + if (incomingLight.IsRed(incomingToRelDir)) { #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming traffic light is red."); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming traffic light is red."); #endif - return false; - } - } + return false; + } + } - if (incomingState.junctionTransitState != VehicleJunctionTransitState.None) { - Vector3 incomingVel = incomingVehicle.GetLastFrameVelocity(); - bool incomingStateChangedRecently = Constants.ManagerFactory.ExtVehicleManager.IsJunctionTransitStateNew(ref incomingState); - 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 (incomingState.junctionTransitState != VehicleJunctionTransitState.None) { + Vector3 incomingVel = incomingVehicle.GetLastFrameVelocity(); + bool incomingStateChangedRecently = Constants.ManagerFactory.ExtVehicleManager.IsJunctionTransitStateNew(ref incomingState); + if (incomingState.junctionTransitState == VehicleJunctionTransitState.Approach || + incomingState.junctionTransitState == VehicleJunctionTransitState.Leave + ) { + if ((incomingState.vehicleType & API.Traffic.Enums.ExtVehicleType.RoadVehicle) != API.Traffic.Enums.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"); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is LEAVING or APPROACHING but not moving. -> BLOCKED"); #endif - Constants.ManagerFactory.ExtVehicleManager.SetJunctionTransitState(ref incomingState, VehicleJunctionTransitState.Blocked); - incomingStateChangedRecently = true; - return false; - } - } + Constants.ManagerFactory.ExtVehicleManager.SetJunctionTransitState(ref incomingState, VehicleJunctionTransitState.Blocked); + incomingStateChangedRecently = true; + return false; + } + } - // incoming vehicle is (1) entering the junction or (2) leaving - Vector3 incomingPos = incomingVehicle.GetLastFramePosition(); - Vector3 incomingToNode = transitNodePos - incomingPos; + // 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) { + // 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*"); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is moving away from the transit node ({dot}). *IGNORING*"); #endif - return false; - } + return false; + } #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is moving towards the transit node ({dot}). Distance: {incomingToNode.magnitude}"); + 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 (!Single.IsInfinity(targetTimeToTransitNode) && - !Single.IsNaN(targetTimeToTransitNode) && - incomingToNode.sqrMagnitude > GlobalConfig.Instance.PriorityRules.MaxPriorityCheckSqrDist - ) { + // 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 (!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; + // check speeds + float incomingSpeed = incomingVel.magnitude; + float incomingDistanceToTransitNode = incomingToNode.magnitude; + float incomingTimeToTransitNode = Single.NaN; - if (incomingSpeed > 0) - incomingTimeToTransitNode = incomingDistanceToTransitNode / incomingSpeed; - else - incomingTimeToTransitNode = Single.PositiveInfinity; + if (incomingSpeed > 0) + incomingTimeToTransitNode = incomingDistanceToTransitNode / incomingSpeed; + else + incomingTimeToTransitNode = Single.PositiveInfinity; - float timeDiff = Mathf.Abs(incomingTimeToTransitNode - targetTimeToTransitNode); - if (timeDiff > GlobalConfig.Instance.PriorityRules.MaxPriorityApproachTime) { + 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*"); + 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 { + 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."); + 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 { + } + } else { #if DEBUG - if (debug) - Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming is stopped."); + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming is stopped."); #endif - } - } + } + } - if (!incomingStateChangedRecently && - (incomingState.junctionTransitState == VehicleJunctionTransitState.Blocked/* || + 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 ExtVehicle 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})."); - } + ) { +#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 - if (targetToDir == ArrowDirection.None || incomingFromDir == ArrowDirection.None || incomingToRelDir == ArrowDirection.None) { + // 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.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Invalid directions given: targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}"); - } + if (debug) + Log._Debug($"TrafficPriorityManager.IsConflictingVehicle({vehicleId}, {incomingVehicleId}): Incoming {incomingVehicleId} is not conflicting."); #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. + 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.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Both vehicles come from the same segment. *IGNORING*"); - } + 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 ExtVehicle 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 - return true; - } - /* FORWARD - * | - * | - * LEFT --- + --- RIGHT - * | - * | - * TURN + 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; + } - /* - * - 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 (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}): targetToDir: {targetToDir.ToString()}, incomingFromDir: {incomingFromDir.ToString()}, incomingToRelDir: {incomingToRelDir.ToString()}"); + 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 (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()}"); - } + 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}"); - } + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): targetToDir={targetToDir}, incomingFromDir={incomingFromDir}, incomingToRelDir={incomingToRelDir}"); + } #endif - /* - * (1) COLLISION DETECTION - */ + /* + * (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); + 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 (!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."); - } + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} would not collide. NO CONFLICT."); + } #endif - return true; - } + return true; + } - // -> vehicles would collide + // -> vehicles would collide #if DEBUG - if (debug) { - Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} would collide. Checking priority rules."); - } + if (debug) { + Log._Debug($" TrafficPriorityManager.HasVehiclePriority({vehicleId}, {incomingVehicleId}): Cars {vehicleId} and {incomingVehicleId} would collide. Checking priority rules."); + } #endif - /* - * (2) CHECK PRIORITY RULES - */ + /* + * (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); + 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}"); - } + 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; + } 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}"); - } + 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 (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}"); - } + 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 + 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."); - } + 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 ExtVehicle 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 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 ExtVehicle 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(ref ExtSegment seg) { - if (!PrioritySegments[seg.segmentId].IsDefault()) { - AddInvalidPrioritySegment(seg.segmentId, ref PrioritySegments[seg.segmentId]); - } - RemovePrioritySignsFromSegment(seg.segmentId); - } - - protected override void HandleValidSegment(ref ExtSegment seg) { - if (! MaySegmentHavePrioritySign(seg.segmentId, true)) { - RemovePrioritySignFromSegmentEnd(seg.segmentId, true); - } else { - UpdateNode(Services.NetService.GetSegmentNodeId(seg.segmentId, true)); - } - - if (!MaySegmentHavePrioritySign(seg.segmentId, false)) { - RemovePrioritySignFromSegmentEnd(seg.segmentId, false); - } else { - UpdateNode(Services.NetService.GetSegmentNodeId(seg.segmentId, false)); - } - } - - protected override void HandleSegmentEndReplacement(SegmentEndReplacement replacement, ref ExtSegmentEnd segEnd) { - 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) { - SetPrioritySignUnableReason 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; - } - - bool? startNode = Services.NetService.IsStartNode(segmentId, nodeId); - if (startNode == null) { - Log.Error($"TrafficPriorityManager.LoadData: No node found for node id {nodeId} @ seg. {segmentId}"); - continue; - } - - SetPrioritySign(segmentId, (bool)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; - } - bool? startNode = Services.NetService.IsStartNode(prioSegData.segmentId, prioSegData.nodeId); - if (startNode == null) { - Log.Error($"TrafficPriorityManager.LoadData: No node found for node id {prioSegData.nodeId} @ seg. {prioSegData.segmentId}"); - continue; - } + + 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(ref ExtSegment seg) { + if (!PrioritySegments[seg.segmentId].IsDefault()) { + AddInvalidPrioritySegment(seg.segmentId, ref PrioritySegments[seg.segmentId]); + } + RemovePrioritySignsFromSegment(seg.segmentId); + } + + protected override void HandleValidSegment(ref ExtSegment seg) { + if (! MaySegmentHavePrioritySign(seg.segmentId, true)) { + RemovePrioritySignFromSegmentEnd(seg.segmentId, true); + } else { + UpdateNode(Services.NetService.GetSegmentNodeId(seg.segmentId, true)); + } + + if (!MaySegmentHavePrioritySign(seg.segmentId, false)) { + RemovePrioritySignFromSegmentEnd(seg.segmentId, false); + } else { + UpdateNode(Services.NetService.GetSegmentNodeId(seg.segmentId, false)); + } + } + + protected override void HandleSegmentEndReplacement(SegmentEndReplacement replacement, ref ExtSegmentEnd segEnd) { + 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) { + SetPrioritySignUnableReason 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; + } + + bool? startNode = Services.NetService.IsStartNode(segmentId, nodeId); + if (startNode == null) { + Log.Error($"TrafficPriorityManager.LoadData: No node found for node id {nodeId} @ seg. {segmentId}"); + continue; + } + + SetPrioritySign(segmentId, (bool)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; + } + bool? startNode = Services.NetService.IsStartNode(prioSegData.segmentId, prioSegData.nodeId); + if (startNode == null) { + Log.Error($"TrafficPriorityManager.LoadData: No node found for node id {prioSegData.nodeId} @ seg. {prioSegData.segmentId}"); + continue; + } #if DEBUGLOAD Log._Debug($"Loading priority sign {(PriorityType)prioSegData.priorityType} @ seg. {prioSegData.segmentId}, start node? {startNode}"); #endif - SetPrioritySign(prioSegData.segmentId, (bool)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; - } - - PriorityType startSign = GetPrioritySign((ushort)segmentId, true); - if (startSign != PriorityType.None) { - ushort startNodeId = Services.NetService.GetSegmentNodeId((ushort)segmentId, true); - if (Services.NetService.IsNodeValid(startNodeId)) { + SetPrioritySign(prioSegData.segmentId, (bool)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; + } + + PriorityType startSign = GetPrioritySign((ushort)segmentId, true); + if (startSign != PriorityType.None) { + ushort startNodeId = Services.NetService.GetSegmentNodeId((ushort)segmentId, true); + if (Services.NetService.IsNodeValid(startNodeId)) { #if DEBUGSAVE Log._Debug($"Saving priority sign of type {startSign} @ start node {startNodeId} of segment {segmentId}"); #endif - ret.Add(new Configuration.PrioritySegment((ushort)segmentId, startNodeId, (int)startSign)); - } - } + ret.Add(new Configuration.PrioritySegment((ushort)segmentId, startNodeId, (int)startSign)); + } + } - PriorityType endSign = GetPrioritySign((ushort)segmentId, false); - if (endSign != PriorityType.None) { - ushort endNodeId = Services.NetService.GetSegmentNodeId((ushort)segmentId, false); - if (Services.NetService.IsNodeValid(endNodeId)) { + PriorityType endSign = GetPrioritySign((ushort)segmentId, false); + if (endSign != PriorityType.None) { + ushort endNodeId = Services.NetService.GetSegmentNodeId((ushort)segmentId, false); + if (Services.NetService.IsNodeValid(endNodeId)) { #if DEBUGSAVE Log._Debug($"Saving priority sign of type {endSign} @ end node {endNodeId} of segment {segmentId}"); #endif - 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; - } - } -} + 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; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/UtilityManager.cs b/TLM/TLM/Manager/Impl/UtilityManager.cs index 9c9ddd9cb..c508fafe3 100644 --- a/TLM/TLM/Manager/Impl/UtilityManager.cs +++ b/TLM/TLM/Manager/Impl/UtilityManager.cs @@ -79,8 +79,12 @@ public void PrintAllDebugInfo() { public void ResetStuckEntities() { Log.Info($"UtilityManager.RemoveStuckEntities() called."); - Log.Info($"UtilityManager.RemoveStuckEntities(): Pausing simulation."); - Singleton.instance.ForcedSimulationPaused = true; + bool wasPaused = Singleton.instance.ForcedSimulationPaused; + + if (!wasPaused) { + Log.Info($"UtilityManager.RemoveStuckEntities(): Pausing simulation."); + Singleton.instance.ForcedSimulationPaused = true; + } Log.Info($"UtilityManager.RemoveStuckEntities(): Waiting for all paths."); Singleton.instance.WaitForAllPaths(); @@ -109,7 +113,7 @@ public void ResetStuckEntities() { } Log.Info($"UtilityManager.RemoveStuckEntities(): Resetting vehicles that are waiting for a path."); - for (uint vehicleId = 1; vehicleId < VehicleManager.MAX_VEHICLE_COUNT; ++vehicleId) { + for (uint vehicleId = 1; vehicleId < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++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) { @@ -123,7 +127,7 @@ public void ResetStuckEntities() { } 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) { + for (uint vehicleId = 1; vehicleId < Constants.ServiceFactory.VehicleService.MaxVehicleCount; ++vehicleId) { //Log._Debug($"UtilityManager.RemoveStuckEntities(): Processing vehicle {vehicleId}."); if ((Singleton.instance.m_vehicles.m_buffer[vehicleId].m_flags & Vehicle.Flags.Parking) != 0) { ushort driverInstanceId = Constants.ManagerFactory.ExtVehicleManager.GetDriverInstanceId((ushort)vehicleId, ref Singleton.instance.m_vehicles.m_buffer[vehicleId]); @@ -136,8 +140,10 @@ public void ResetStuckEntities() { } } - Log.Info($"UtilityManager.RemoveStuckEntities(): Unpausing simulation."); - Singleton.instance.ForcedSimulationPaused = false; + if (!wasPaused) { + Log.Info($"UtilityManager.RemoveStuckEntities(): Unpausing simulation."); + Singleton.instance.ForcedSimulationPaused = false; + } } } } diff --git a/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs b/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs index 8187b69e9..dfb4f0826 100644 --- a/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs +++ b/TLM/TLM/Manager/Impl/VehicleBehaviorManager.cs @@ -1,876 +1,866 @@ -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.Traffic.Enums; -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 MAX_EVASION_SPEED = 8f * 1f; // 50 km/h - public const float EVASION_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 ParkPassengerCar(ushort vehicleID, ref Vehicle vehicleData, VehicleInfo vehicleInfo, 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 - IExtCitizenInstanceManager extCitizenInstanceManager = Constants.ManagerFactory.ExtCitizenInstanceManager; - 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.parkingAI && 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; - bool searchedParkingSpace = 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 == ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == 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 - - searchedParkingSpace = true; - switch (driverExtInstance.parkingSpaceLocation) { - case 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(vehicleInfo, 0, driverExtInstance.parkingSpaceLocationId, refPos, out parkPos, out parkRot, out parkOffset, out parkLaneID, out parkLaneIndex); - break; - case ExtParkingSpaceLocation.Building: - float maxDist = 9999f; -#if DEBUG - if (debug) - Log._Debug($"Vehicle {vehicleID} wants to park @ building {driverExtInstance.parkingSpaceLocationId}"); -#endif - foundParkingSpace = AdvancedParkingManager.Instance.FindParkingSpacePropAtBuilding(vehicleInfo, 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 - ExtParkingSpaceLocation parkLoc; - ushort parkId; - foundParkingSpace = Constants.ManagerFactory.AdvancedParkingManager.FindParkingSpaceInVicinity(refPos, searchDir, vehicleInfo, homeID, vehicleID, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out parkLoc, out parkId, out parkPos, out parkRot, out parkOffset); - break; - } - } - } - - if (!searchedParkingSpace) { - ExtParkingSpaceLocation parkLoc; - ushort parkId; - foundParkingSpace = Constants.ManagerFactory.AdvancedParkingManager.FindParkingSpaceInVicinity(refPos, searchDir, vehicleInfo, homeID, vehicleID, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out parkLoc, out parkId, 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, vehicleInfo, parkPos, parkRot, driverCitizenId); -#if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parked car created? {parkedCarCreated}"); -#endif - - IExtBuildingManager extBuildingManager = Constants.ManagerFactory.ExtBuildingManager; - 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 == ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos) && targetBuildingId != 0) { - // decrease parking space demand of target building - Constants.ManagerFactory.ExtBuildingManager.ModifyParkingSpaceDemand(ref extBuildingManager.ExtBuildings[targetBuildingId], 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 = ExtPathMode.RequiresWalkingPathToTarget; - driverExtInstance.failedParkingAttempts = 0; - driverExtInstance.parkingSpaceLocation = 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 == ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos) && targetBuildingId != 0) { - // increase parking space demand of target building - if (driverExtInstance.failedParkingAttempts > 1) { - extBuildingManager.AddParkingSpaceDemand(ref extBuildingManager.ExtBuildings[targetBuildingId], 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 = 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.Reset(ref extCitizenInstanceManager.ExtInstances[citizenInstanceId]); - } - - 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 == 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.ExtInstances[citizenInstanceId].pathMode = 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 == 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; - } - - public bool StartPassengerCarPathFind(ushort vehicleID, ref Vehicle vehicleData, VehicleInfo vehicleInfo, ushort driverInstanceId, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget, bool isHeavyVehicle, bool hasCombustionEngine, bool ignoreBlocked) { - IExtCitizenInstanceManager extCitizenInstanceManager = Constants.ManagerFactory.ExtCitizenInstanceManager; -#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; +namespace TrafficManager.Manager.Impl { + using System; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using Custom.PathFinding; + using State; + using Traffic.Data; + using Traffic.Enums; + using UnityEngine; + + public class VehicleBehaviorManager : AbstractCustomManager, IVehicleBehaviorManager { + public const float MIN_SPEED = 8f * 0.2f; // 10 km/h + public const float MAX_EVASION_SPEED = 8f * 1f; // 50 km/h + public const float EVASION_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 = { + 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 ParkPassengerCar(ushort vehicleID, ref Vehicle vehicleData, VehicleInfo vehicleInfo, 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 + IExtCitizenInstanceManager extCitizenInstanceManager = Constants.ManagerFactory.ExtCitizenInstanceManager; + 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.parkingAI && 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; + bool searchedParkingSpace = 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 == ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == 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 + + searchedParkingSpace = true; + switch (driverExtInstance.parkingSpaceLocation) { + case 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(vehicleInfo, 0, driverExtInstance.parkingSpaceLocationId, refPos, out parkPos, out parkRot, out parkOffset, out parkLaneID, out parkLaneIndex); + break; + case ExtParkingSpaceLocation.Building: + float maxDist = 9999f; +#if DEBUG + if (debug) + Log._Debug($"Vehicle {vehicleID} wants to park @ building {driverExtInstance.parkingSpaceLocationId}"); +#endif + foundParkingSpace = AdvancedParkingManager.Instance.FindParkingSpacePropAtBuilding(vehicleInfo, 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 + ExtParkingSpaceLocation parkLoc; + ushort parkId; + foundParkingSpace = Constants.ManagerFactory.AdvancedParkingManager.FindParkingSpaceInVicinity(refPos, searchDir, vehicleInfo, homeID, vehicleID, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out parkLoc, out parkId, out parkPos, out parkRot, out parkOffset); + break; + } + } + } + + if (!searchedParkingSpace) { + ExtParkingSpaceLocation parkLoc; + ushort parkId; + foundParkingSpace = Constants.ManagerFactory.AdvancedParkingManager.FindParkingSpaceInVicinity(refPos, searchDir, vehicleInfo, homeID, vehicleID, GlobalConfig.Instance.ParkingAI.MaxBuildingToPedestrianLaneDistance, out parkLoc, out parkId, 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, vehicleInfo, parkPos, parkRot, driverCitizenId); +#if DEBUG + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtParkVehicle({vehicleID}): Parked car created? {parkedCarCreated}"); +#endif + + IExtBuildingManager extBuildingManager = Constants.ManagerFactory.ExtBuildingManager; + 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 == ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos) && targetBuildingId != 0) { + // decrease parking space demand of target building + Constants.ManagerFactory.ExtBuildingManager.ModifyParkingSpaceDemand(ref extBuildingManager.ExtBuildings[targetBuildingId], 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 = ExtPathMode.RequiresWalkingPathToTarget; + driverExtInstance.failedParkingAttempts = 0; + driverExtInstance.parkingSpaceLocation = 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 == ExtPathMode.DrivingToAltParkPos || driverExtInstance.pathMode == ExtPathMode.DrivingToKnownParkPos) && targetBuildingId != 0) { + // increase parking space demand of target building + if (driverExtInstance.failedParkingAttempts > 1) { + extBuildingManager.AddParkingSpaceDemand(ref extBuildingManager.ExtBuildings[targetBuildingId], 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 = 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.Reset(ref extCitizenInstanceManager.ExtInstances[citizenInstanceId]); + } + + 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 == 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.ExtInstances[citizenInstanceId].pathMode = 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 == 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; + } + + public bool StartPassengerCarPathFind(ushort vehicleID, ref Vehicle vehicleData, VehicleInfo vehicleInfo, ushort driverInstanceId, ref CitizenInstance driverInstance, ref ExtCitizenInstance driverExtInstance, Vector3 startPos, Vector3 endPos, bool startBothWays, bool endBothWays, bool undergroundTarget, bool isHeavyVehicle, bool hasCombustionEngine, bool ignoreBlocked) { + IExtCitizenInstanceManager extCitizenInstanceManager = Constants.ManagerFactory.ExtCitizenInstanceManager; +#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.parkingAI) { - //if (driverExtInstance != null) { + if (Options.parkingAI) { + //if (driverExtInstance != null) { #if DEBUG - if (debug) - Log.Warning($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): PathMode={driverExtInstance.pathMode} for vehicle {vehicleID}, driver citizen instance {driverExtInstance.instanceId}!"); + 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 (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}"); + 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; + } 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}"); + 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) { + } 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}"); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Skipping queue. pathMode={driverExtInstance.pathMode}"); #endif - skipQueue = true; - } + 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; + 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}"); + 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 (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}"); + 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; + } + startBothWays = false; - if (driverExtInstance.failedParkingAttempts > GlobalConfig.Instance.ParkingAI.MaxParkingAttempts) { - // maximum number of parking attempts reached + 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."); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Reached maximum number of parking attempts for vehicle {vehicleID}! GIVING UP."); #endif - extCitizenInstanceManager.Reset(ref driverExtInstance); + extCitizenInstanceManager.Reset(ref driverExtInstance); - // pocket car fallback - //vehicleData.m_flags |= Vehicle.Flags.Parking; - return false; - } else { + // 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}"); + 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; + } + } else { + driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToKnownParkPos; #if DEBUG - if (debug) - Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): No parking involved: Setting pathMode={driverExtInstance.pathMode}"); + 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; + 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; + 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 (!extCitizenInstanceManager.CalculateReturnPath(ref driverExtInstance, parkPos, returnPos)) { + if (!extCitizenInstanceManager.CalculateReturnPath(ref driverExtInstance, 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."); + if (debug) + Log._Debug($"CustomPassengerCarAI.ExtStartPathFind({vehicleID}): Could not calculate return path for citizen instance {driverExtInstance.instanceId}, vehicle {vehicleID}. Resetting instance."); #endif - extCitizenInstanceManager.Reset(ref driverExtInstance); - return false; - } - } else if (driverExtInstance.pathMode == ExtPathMode.CalculatingCarPathToAltParkPos) { - // no alternative parking spot found: abort + extCitizenInstanceManager.Reset(ref driverExtInstance); + 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."); + 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 - extCitizenInstanceManager.Reset(ref driverExtInstance); - return false; - } else { - // calculate a direct path to target + extCitizenInstanceManager.Reset(ref driverExtInstance); + 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'."); + 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; - } - } + driverExtInstance.pathMode = ExtPathMode.CalculatingCarPathToTarget; + } + } - extPathType = driverExtInstance.GetPathType(); - driverExtInstance.atOutsideConnection = Constants.ManagerFactory.ExtCitizenInstanceManager.IsAtOutsideConnection(driverInstanceId, ref driverInstance, ref driverExtInstance, startPos); - } + 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.parkingAI && (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 = vehicleInfo.m_vehicleType; - bool allowUnderground = (vehicleData.m_flags & Vehicle.Flags.Underground) != 0; - bool randomParking = false; - bool combustionEngine = vehicleInfo.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.parkingAI && (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 = isHeavyVehicle; - args.hasCombustionEngine = hasCombustionEngine; - args.ignoreBlocked = ignoreBlocked; - args.ignoreFlooded = false; - args.ignoreCosts = false; - args.randomParking = randomParking; - args.stablePath = false; - args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; - - if (CustomPathManager._instance.CustomCreatePath(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.parkingAI) { - extCitizenInstanceManager.Reset(ref driverExtInstance); - } - return false; - } - - 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 - Constants.ManagerFactory.TrafficLightSimulationManager.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 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 - /// 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) { - VehicleJunctionTransitState transitState = MayChangeSegment(frontVehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], ref vehicleData, sqrVelocity, ref prevPos, ref prevSegment, prevTargetNodeId, prevLaneID, ref position, targetNodeId, ref targetNode, laneID, ref DUMMY_POS, 0); - Constants.ManagerFactory.ExtVehicleManager.SetJunctionTransitState(ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], transitState); - return transitState == VehicleJunctionTransitState.Leave /* || transitState == VehicleJunctionTransitState.Blocked*/; - } - - /// - /// 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 - /// 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) { - VehicleJunctionTransitState transitState = MayChangeSegment(frontVehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], ref vehicleData, sqrVelocity, ref prevPos, ref prevSegment, prevTargetNodeId, prevLaneID, ref position, targetNodeId, ref targetNode, laneID, ref nextPosition, nextTargetNodeId); - Constants.ManagerFactory.ExtVehicleManager.SetJunctionTransitState(ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], transitState); - return transitState == VehicleJunctionTransitState.Leave /* || transitState == VehicleJunctionTransitState.Blocked*/; - } - - protected VehicleJunctionTransitState MayChangeSegment(ushort frontVehicleId, ref ExtVehicle extVehicle, 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) { - //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 (prevTargetNodeId != targetNodeId - || (vehicleData.m_blockCounter == 255 && !VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) // NON-STOCK CODE - ) { - // method should only be called if targetNodeId == prevTargetNode - return VehicleJunctionTransitState.Leave; - } - - if (extVehicle.junctionTransitState == VehicleJunctionTransitState.Leave) { - // vehicle was already allowed to leave the junction - if ( - sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity * GlobalConfig.Instance.PriorityRules.MaxStopVelocity && - (extVehicle.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 - return VehicleJunctionTransitState.Blocked; - } else { - return VehicleJunctionTransitState.Leave; - } - } - - uint currentFrameIndex = Constants.ServiceFactory.SimulationService.CurrentFrameIndex; - if ((extVehicle.junctionTransitState == VehicleJunctionTransitState.Stop || extVehicle.junctionTransitState == VehicleJunctionTransitState.Blocked) && - extVehicle.lastTransitStateUpdate >> ExtVehicleManager.JUNCTION_RECHECK_SHIFT >= currentFrameIndex >> ExtVehicleManager.JUNCTION_RECHECK_SHIFT) { - // reuse recent result - return extVehicle.junctionTransitState; - } - - bool isRecklessDriver = extVehicle.recklessDriver; - - var netManager = Singleton.instance; - IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; - - 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; - } - return VehicleJunctionTransitState.Stop; - } else { - vehicleData.m_waitCounter = (byte)Mathf.Min((int)(vehicleData.m_waitCounter + 1), 4); - if (vehicleData.m_waitCounter < 4) { - return VehicleJunctionTransitState.Stop; - } - vehicleData.m_flags2 &= ~Vehicle.Flags2.Yielding; - vehicleData.m_waitCounter = 0; - } - } else if (sqrVelocity > 0.01f) { - return VehicleJunctionTransitState.Stop; - } - } - - // entering blocked junctions - if (MustCheckSpace(prevPos.m_segment, isTargetStartNode, ref targetNode, isRecklessDriver)) { - // check if there is enough space - var len = extVehicle.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) { -#if DEBUG - if (debug) - Log._Debug($"Vehicle {frontVehicleId}: Setting JunctionTransitState to BLOCKED"); -#endif - - return VehicleJunctionTransitState.Blocked; - } - } - } - - 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; - } - } - - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - VehicleJunctionTransitState transitState = extVehicle.junctionTransitState; - if (extVehicle.junctionTransitState == VehicleJunctionTransitState.Blocked) { -#if DEBUG - if (debug) - Log._Debug($"Vehicle {frontVehicleId}: Setting JunctionTransitState from BLOCKED to APPROACH"); -#endif - transitState = 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 - bool stopCar = false; - uint simGroup = (uint)targetNodeId >> 7; - - RoadBaseAI.TrafficLightState vehicleLightState; - RoadBaseAI.TrafficLightState pedestrianLightState; - bool vehicles; - bool pedestrians; - Constants.ManagerFactory.TrafficLightSimulationManager.GetTrafficLightState( -#if DEBUG - frontVehicleId, ref vehicleData, -#endif - targetNodeId, prevPos.m_segment, prevPos.m_lane, position.m_segment, ref prevSegment, currentFrameIndex - simGroup, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians); // TODO current frame index or reference frame index? - - 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} (recklessDriver={isRecklessDriver})"); -#endif - - uint random = currentFrameIndex - simGroup & 255u; - if (!vehicles && random >= 196u) { - vehicles = true; - RoadBaseAI.SetTrafficLightState(targetNodeId, ref prevSegment, currentFrameIndex - simGroup, 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; - } + NetInfo.LaneType laneTypes = NetInfo.LaneType.Vehicle; + if (!movingToParkingPos) { + laneTypes |= NetInfo.LaneType.Pedestrian; + + if (Options.parkingAI && (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 = vehicleInfo.m_vehicleType; + bool allowUnderground = (vehicleData.m_flags & Vehicle.Flags.Underground) != 0; + bool randomParking = false; + bool combustionEngine = vehicleInfo.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.parkingAI && (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 = isHeavyVehicle; + args.hasCombustionEngine = hasCombustionEngine; + args.ignoreBlocked = ignoreBlocked; + args.ignoreFlooded = false; + args.ignoreCosts = false; + args.randomParking = randomParking; + args.stablePath = false; + args.skipQueue = (vehicleData.m_flags & Vehicle.Flags.Spawned) != 0; + + if (CustomPathManager._instance.CustomCreatePath(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.parkingAI) { + extCitizenInstanceManager.Reset(ref driverExtInstance); + } + return false; + } + + 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 + Constants.ManagerFactory.TrafficLightSimulationManager.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 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 + /// 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) { + VehicleJunctionTransitState transitState = MayChangeSegment(frontVehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], ref vehicleData, sqrVelocity, ref prevPos, ref prevSegment, prevTargetNodeId, prevLaneID, ref position, targetNodeId, ref targetNode, laneID, ref DUMMY_POS, 0); + Constants.ManagerFactory.ExtVehicleManager.SetJunctionTransitState(ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], transitState); + return transitState == VehicleJunctionTransitState.Leave /* || transitState == VehicleJunctionTransitState.Blocked*/; + } + + /// + /// 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 + /// 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) { + VehicleJunctionTransitState transitState = MayChangeSegment(frontVehicleId, ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], ref vehicleData, sqrVelocity, ref prevPos, ref prevSegment, prevTargetNodeId, prevLaneID, ref position, targetNodeId, ref targetNode, laneID, ref nextPosition, nextTargetNodeId); + Constants.ManagerFactory.ExtVehicleManager.SetJunctionTransitState(ref Constants.ManagerFactory.ExtVehicleManager.ExtVehicles[frontVehicleId], transitState); + return transitState == VehicleJunctionTransitState.Leave /* || transitState == VehicleJunctionTransitState.Blocked*/; + } + + protected VehicleJunctionTransitState MayChangeSegment(ushort frontVehicleId, ref ExtVehicle extVehicle, 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) { + //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 (prevTargetNodeId != targetNodeId + || (vehicleData.m_blockCounter == 255 && !VehicleBehaviorManager.Instance.MayDespawn(ref vehicleData)) // NON-STOCK CODE + ) { + // method should only be called if targetNodeId == prevTargetNode + return VehicleJunctionTransitState.Leave; + } + + if (extVehicle.junctionTransitState == VehicleJunctionTransitState.Leave) { + // vehicle was already allowed to leave the junction + if ( + sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity * GlobalConfig.Instance.PriorityRules.MaxStopVelocity && + (extVehicle.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 + return VehicleJunctionTransitState.Blocked; + } else { + return VehicleJunctionTransitState.Leave; + } + } + + uint currentFrameIndex = Constants.ServiceFactory.SimulationService.CurrentFrameIndex; + if ((extVehicle.junctionTransitState == VehicleJunctionTransitState.Stop || extVehicle.junctionTransitState == VehicleJunctionTransitState.Blocked) && + extVehicle.lastTransitStateUpdate >> ExtVehicleManager.JUNCTION_RECHECK_SHIFT >= currentFrameIndex >> ExtVehicleManager.JUNCTION_RECHECK_SHIFT) { + // reuse recent result + return extVehicle.junctionTransitState; + } + + bool isRecklessDriver = extVehicle.recklessDriver; + + var netManager = Singleton.instance; + IExtVehicleManager extVehicleMan = Constants.ManagerFactory.ExtVehicleManager; + + 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; + } + return VehicleJunctionTransitState.Stop; + } else { + vehicleData.m_waitCounter = (byte)Mathf.Min((int)(vehicleData.m_waitCounter + 1), 4); + if (vehicleData.m_waitCounter < 4) { + return VehicleJunctionTransitState.Stop; + } + vehicleData.m_flags2 &= ~Vehicle.Flags2.Yielding; + vehicleData.m_waitCounter = 0; + } + } else if (sqrVelocity > 0.01f) { + return VehicleJunctionTransitState.Stop; + } + } + + // entering blocked junctions + if (MustCheckSpace(prevPos.m_segment, isTargetStartNode, ref targetNode, isRecklessDriver)) { + // check if there is enough space + var len = extVehicle.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) { +#if DEBUG + if (debug) + Log._Debug($"Vehicle {frontVehicleId}: Setting JunctionTransitState to BLOCKED"); +#endif + + return VehicleJunctionTransitState.Blocked; + } + } + } + + 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; + } + } + + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + VehicleJunctionTransitState transitState = extVehicle.junctionTransitState; + if (extVehicle.junctionTransitState == VehicleJunctionTransitState.Blocked) { +#if DEBUG + if (debug) + Log._Debug($"Vehicle {frontVehicleId}: Setting JunctionTransitState from BLOCKED to APPROACH"); +#endif + transitState = 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 + bool stopCar = false; + uint simGroup = (uint)targetNodeId >> 7; + + RoadBaseAI.TrafficLightState vehicleLightState; + RoadBaseAI.TrafficLightState pedestrianLightState; + bool vehicles; + bool pedestrians; + Constants.ManagerFactory.TrafficLightSimulationManager.GetTrafficLightState( +#if DEBUG + frontVehicleId, ref vehicleData, +#endif + targetNodeId, prevPos.m_segment, prevPos.m_lane, position.m_segment, ref prevSegment, currentFrameIndex - simGroup, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians); // TODO current frame index or reference frame index? + + 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} (recklessDriver={isRecklessDriver})"); +#endif + + uint random = currentFrameIndex - simGroup & 255u; + if (!vehicles && random >= 196u) { + vehicles = true; + RoadBaseAI.SetTrafficLightState(targetNodeId, ref prevSegment, currentFrameIndex - simGroup, 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 TURNONRED // Check if turning in the preferred direction, and if turning while it's red is allowed @@ -889,1049 +879,1179 @@ protected VehicleJunctionTransitState MayChangeSegment(ushort frontVehicleId, re } #endif - // Turn-on-red: Check if turning in the preferred direction, and if turning while it's red is allowed - if ( - Options.turnOnRedEnabled && - stopCar && - (extVehicle.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)) - ) { + // Turn-on-red: Check if turning in the preferred direction, and if turning while it's red is allowed + if ( + Options.turnOnRedEnabled && + stopCar && + (extVehicle.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}"); + 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; - } - } + stopCar = false; + } + } - // check priority rules at unprotected traffic lights - if (!stopCar && Options.prioritySignsEnabled && Options.trafficLightPriorityRules && segLightsMan.IsSegmentLight(prevPos.m_segment, isTargetStartNode)) { - bool hasPriority = prioMan.HasPriority(frontVehicleId, ref vehicleData, ref prevPos, ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(prevPos.m_segment, isTargetStartNode)], targetNodeId, isTargetStartNode, ref position, ref targetNode); + // check priority rules at unprotected traffic lights + if (!stopCar && Options.prioritySignsEnabled && Options.trafficLightPriorityRules && segLightsMan.IsSegmentLight(prevPos.m_segment, isTargetStartNode)) { + bool hasPriority = prioMan.HasPriority(frontVehicleId, ref vehicleData, ref prevPos, ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(prevPos.m_segment, isTargetStartNode)], targetNodeId, isTargetStartNode, ref position, ref targetNode); - if (!hasPriority) { - // green light but other cars are incoming and they have priority: stop + 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."); + 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; - } - } + stopCar = true; + } + } - if (stopCar) { + if (stopCar) { #if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to STOP"); + 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; - } + 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; + } - vehicleData.m_blockCounter = 0; - return VehicleJunctionTransitState.Stop; - } else { + vehicleData.m_blockCounter = 0; + return VehicleJunctionTransitState.Stop; + } else { #if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE ({vehicleLightState})"); + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE ({vehicleLightState})"); #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; - } + 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; + } - return VehicleJunctionTransitState.Leave; - } - } else if (Options.prioritySignsEnabled && vehicleData.Info.m_vehicleType != VehicleInfo.VehicleType.Monorail) { + return VehicleJunctionTransitState.Leave; + } + } else if (Options.prioritySignsEnabled && vehicleData.Info.m_vehicleType != VehicleInfo.VehicleType.Monorail) { #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."); + 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 != PriorityType.None) { + var sign = prioMan.GetPrioritySign(prevPos.m_segment, isTargetStartNode); + if (sign != 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."); - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): JunctionTransitState={transitState.ToString()}"); - } + 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."); + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): JunctionTransitState={transitState.ToString()}"); + } #endif - if (transitState == VehicleJunctionTransitState.None) { + if (transitState == VehicleJunctionTransitState.None) { #if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to APPROACH (prio)"); + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to APPROACH (prio)"); #endif - transitState = VehicleJunctionTransitState.Approach; - } + transitState = VehicleJunctionTransitState.Approach; + } - if (sign == PriorityType.Stop) { - if (transitState == VehicleJunctionTransitState.Approach) { - extVehicle.waitTime = 0; - } + if (sign == PriorityType.Stop) { + if (transitState == VehicleJunctionTransitState.Approach) { + extVehicle.waitTime = 0; + } - if (sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity * GlobalConfig.Instance.PriorityRules.MaxStopVelocity) { - ++extVehicle.waitTime; + if (sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxStopVelocity * GlobalConfig.Instance.PriorityRules.MaxStopVelocity) { + ++extVehicle.waitTime; - if (extVehicle.waitTime < 2) { - vehicleData.m_blockCounter = 0; - return VehicleJunctionTransitState.Stop; - } - } else { -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle has come to a full stop."); + if (extVehicle.waitTime < 2) { + vehicleData.m_blockCounter = 0; + return VehicleJunctionTransitState.Stop; + } + } else { +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle has come to a full stop."); +#endif + vehicleData.m_blockCounter = 0; + return VehicleJunctionTransitState.Stop; + } + } + + if (sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxYieldVelocity * GlobalConfig.Instance.PriorityRules.MaxYieldVelocity) { +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): {sign} sign. waittime={extVehicle.waitTime}"); +#endif + if (extVehicle.waitTime < GlobalConfig.Instance.PriorityRules.MaxPriorityWaitTime) { + extVehicle.waitTime++; +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to STOP (wait)"); +#endif + bool hasPriority = prioMan.HasPriority(frontVehicleId, ref vehicleData, ref prevPos, ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(prevPos.m_segment, isTargetStartNode)], 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 VehicleJunctionTransitState.Stop; + } else { +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE (no conflicting cars)"); +#endif + return VehicleJunctionTransitState.Leave; + } + } else { +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE (max wait timeout)"); +#endif + return VehicleJunctionTransitState.Leave; + } + } else { +#if DEBUG + if (debug) + Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle has not yet reached yield speed (sqrVelocity={sqrVelocity})"); +#endif + + // vehicle has not yet reached yield speed + return VehicleJunctionTransitState.Stop; + } + } else { + return VehicleJunctionTransitState.Leave; + } + } else { + return VehicleJunctionTransitState.Leave; + } + } else { + return VehicleJunctionTransitState.Leave; + } + } + + /// + /// 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 ExtVehicle extVehicle, VehicleInfo vehicleInfo, PathUnit.Position position, ref NetSegment segment, Vector3 pos, float maxSpeed, bool emergency) { + 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 extVehicle, vehicleInfo); + maxSpeed = Math.Max(MIN_SPEED, maxSpeed); // at least 10 km/h + + return maxSpeed; + } + + public float ApplyRealisticSpeeds(float speed, ushort vehicleId, ref ExtVehicle extVehicle, VehicleInfo vehicleInfo) { + if (Options.individualDrivingStyle) { + float vehicleRand = 0.01f * (float)Constants.ManagerFactory.ExtVehicleManager.GetTimedVehicleRand(vehicleId); + if (vehicleInfo.m_isLargeVehicle) { + speed *= 0.75f + vehicleRand * 0.25f; // a little variance, 0.75 .. 1 + } else if (extVehicle.recklessDriver) { + speed *= 1.3f + vehicleRand * 1.7f; // woohooo, 1.3 .. 3 + } else { + speed *= 0.8f + vehicleRand * 0.5f; // a little variance, 0.8 .. 1.3 + } + } else if (extVehicle.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 ExtVehicle 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.dlsReady) { + Constants.ManagerFactory.ExtVehicleManager.UpdateDynamicLaneSelectionParameters(ref vehicleState); + } + + 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; + float maxReservedSpace = vehicleState.maxReservedSpace; + + // 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 - vehicleData.m_blockCounter = 0; - return VehicleJunctionTransitState.Stop; - } - } + return next1PathPos.m_lane; + } - if (sqrVelocity <= GlobalConfig.Instance.PriorityRules.MaxYieldVelocity * GlobalConfig.Instance.PriorityRules.MaxYieldVelocity) { -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): {sign} sign. waittime={extVehicle.waitTime}"); -#endif - if (extVehicle.waitTime < GlobalConfig.Instance.PriorityRules.MaxPriorityWaitTime) { - extVehicle.waitTime++; -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to STOP (wait)"); -#endif - bool hasPriority = prioMan.HasPriority(frontVehicleId, ref vehicleData, ref prevPos, ref segEndMan.ExtSegmentEnds[segEndMan.GetIndex(prevPos.m_segment, isTargetStartNode)], targetNodeId, isTargetStartNode, ref position, ref targetNode); -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): hasPriority: {hasPriority}"); -#endif + LaneTransitionData[] currentFwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].transitions; - if (!hasPriority) { - vehicleData.m_blockCounter = 0; - return VehicleJunctionTransitState.Stop; - } else { + if (currentFwdTransitions == null) { #if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE (no conflicting cars)"); -#endif - return VehicleJunctionTransitState.Leave; - } - } else { -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Setting JunctionTransitState to LEAVE (max wait timeout)"); -#endif - return VehicleJunctionTransitState.Leave; - } - } else { -#if DEBUG - if (debug) - Log._Debug($"VehicleBehaviorManager.MayChangeSegment({frontVehicleId}): Vehicle has not yet reached yield speed (sqrVelocity={sqrVelocity})"); + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): No forward transitions found for current lane {currentLaneId} at startNode {startNode}."); + } #endif + return next1PathPos.m_lane; + } - // vehicle has not yet reached yield speed - return VehicleJunctionTransitState.Stop; - } - } else { - return VehicleJunctionTransitState.Leave; - } - } else { - return VehicleJunctionTransitState.Leave; - } - } else { - return VehicleJunctionTransitState.Leave; - } - } - - /// - /// 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; - } - } + VehicleInfo vehicleInfo = vehicleData.Info; + float vehicleMaxSpeed = vehicleInfo.m_maxSpeed / 8f; + float vehicleCurSpeed = vehicleData.GetLastFrameVelocity().magnitude / 8f; - 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 ExtVehicle extVehicle, VehicleInfo vehicleInfo, PathUnit.Position position, ref NetSegment segment, Vector3 pos, float maxSpeed, bool emergency) { - 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% - } - } + float bestStayMeanSpeed = 0f; + float bestStaySpeedDiff = float.PositiveInfinity; // best speed difference on next continuous lane + int bestStayTotalLaneDist = int.MaxValue; + byte bestStayNext1LaneIndex = next1PathPos.m_lane; - 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 extVehicle, vehicleInfo); - maxSpeed = Math.Max(MIN_SPEED, maxSpeed); // at least 10 km/h - - return maxSpeed; - } - - public float ApplyRealisticSpeeds(float speed, ushort vehicleId, ref ExtVehicle extVehicle, VehicleInfo vehicleInfo) { - if (Options.individualDrivingStyle) { - float vehicleRand = 0.01f * (float)Constants.ManagerFactory.ExtVehicleManager.GetTimedVehicleRand(vehicleId); - if (vehicleInfo.m_isLargeVehicle) { - speed *= 0.75f + vehicleRand * 0.25f; // a little variance, 0.75 .. 1 - } else if (extVehicle.recklessDriver) { - speed *= 1.3f + vehicleRand * 1.7f; // woohooo, 1.3 .. 3 - } else { - speed *= 0.8f + vehicleRand * 0.5f; // a little variance, 0.8 .. 1.3 - } - } else if (extVehicle.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; - } + float bestOptMeanSpeed = 0f; + float bestOptSpeedDiff = float.PositiveInfinity; // best speed difference on all next lanes + int bestOptTotalLaneDist = int.MaxValue; + byte bestOptNext1LaneIndex = next1PathPos.m_lane; - return (uint)vehicleId % Options.getRecklessDriverModulo() == 0; - } + bool foundSafeLaneChange = false; + //bool foundClearBackLane = false; + //bool foundClearFwdLane = false; - public int FindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref ExtVehicle 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); - } + //ushort reachableNext1LanesMask = 0; + uint reachableNext2LanesMask = 0; + uint reachableNext3LanesMask = 0; - 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.dlsReady) { - Constants.ManagerFactory.ExtVehicleManager.UpdateDynamicLaneSelectionParameters(ref vehicleState); - } + //int numReachableNext1Lanes = 0; + int numReachableNext2Lanes = 0; + int numReachableNext3Lanes = 0; - if (vehicleState.lastAltLaneSelSegmentId == currentPathPos.m_segment) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping alternative lane selection: Already calculated."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Starting lane-finding algorithm now. vehicleMaxSpeed={vehicleMaxSpeed}, vehicleCurSpeed={vehicleCurSpeed} vehicleLength={vehicleLength}"); + } #endif - return next1PathPos.m_lane; - } - vehicleState.lastAltLaneSelSegmentId = currentPathPos.m_segment; - bool recklessDriver = vehicleState.recklessDriver; - float maxReservedSpace = vehicleState.maxReservedSpace; + uint mask; + for (int i = 0; i < currentFwdTransitions.Length; ++i) { + if (currentFwdTransitions[i].segmentId != next1PathPos.m_segment) { + continue; + } - // cur -> next1 - float vehicleLength = 1f + vehicleState.totalLength; - bool startNode = currentPathPos.m_offset < 128; - uint currentFwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentLaneId, startNode); + 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 (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})"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (distance too large)"); + } #endif + continue; + } - if (!RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].routed) { + if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, next1PathPos.m_segment, currentFwdTransitions[i].laneIndex, next1SegInfo)) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): No forward routing for next path position available."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (vehicle restrictions)"); + } #endif - return next1PathPos.m_lane; - } + continue; + } - LaneTransitionData[] currentFwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].transitions; + int minTotalLaneDist = int.MaxValue; - if (currentFwdTransitions == null) { + if (next2PathPos.m_segment != 0) { + // next1 -> next2 + uint next1FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentFwdTransitions[i].laneId, !currentFwdTransitions[i].startNode); #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): No forward transitions found for current lane {currentLaneId} at startNode {startNode}."); - } + 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 - 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}"); - } + 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; - uint mask; - for (int i = 0; i < currentFwdTransitions.Length; ++i) { - if (currentFwdTransitions[i].segmentId != next1PathPos.m_segment) { - continue; - } + if (next1FwdTransitions == null) { + continue; + } - if (!(currentFwdTransitions[i].type == LaneEndTransitionType.Default || - currentFwdTransitions[i].type == LaneEndTransitionType.LaneConnection || - (recklessDriver && currentFwdTransitions[i].type == LaneEndTransitionType.Relaxed)) - ) { - continue; - } + bool foundNext1Next2 = false; + for (int j = 0; j < next1FwdTransitions.Length; ++j) { + if (next1FwdTransitions[j].segmentId != next2PathPos.m_segment) { + continue; + } - if (currentFwdTransitions[i].distance > 1) { + 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 current transition {currentFwdTransitions[i]} (distance too large)"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 transition {next1FwdTransitions[j]} (distance too large)"); + } #endif - continue; - } + continue; + } - if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, next1PathPos.m_segment, currentFwdTransitions[i].laneIndex, next1SegInfo)) { + if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, next2PathPos.m_segment, next1FwdTransitions[j].laneIndex, next2SegInfo)) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (vehicle restrictions)"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 transition {next1FwdTransitions[j]} (vehicle restrictions)"); + } #endif - continue; - } - - int minTotalLaneDist = int.MaxValue; + continue; + } - if (next2PathPos.m_segment != 0) { - // next1 -> next2 - uint next1FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentFwdTransitions[i].laneId, !currentFwdTransitions[i].startNode); + if (next3PathPos.m_segment != 0) { + // next2 -> next3 + uint next2FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(next1FwdTransitions[j].laneId, !next1FwdTransitions[j].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})"); - } + 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 next1 lane id={currentFwdTransitions[i].laneId}, seg.={currentFwdTransitions[i].segmentId}, index={currentFwdTransitions[i].laneIndex}, startNode={!currentFwdTransitions[i].startNode}: {RoutingManager.Instance.LaneEndForwardRoutings[next1FwdRoutingIndex]}"); - } + 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[next1FwdRoutingIndex].routed) { - continue; - } - LaneTransitionData[] next1FwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[next1FwdRoutingIndex].transitions; + if (!RoutingManager.Instance.LaneEndForwardRoutings[next2FwdRoutingIndex].routed) { + continue; + } + LaneTransitionData[] next2FwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[next2FwdRoutingIndex].transitions; - if (next1FwdTransitions == null) { - continue; - } + if (next2FwdTransitions == null) { + continue; + } - bool foundNext1Next2 = false; - for (int j = 0; j < next1FwdTransitions.Length; ++j) { - if (next1FwdTransitions[j].segmentId != next2PathPos.m_segment) { - continue; - } + bool foundNext2Next3 = false; + for (int k = 0; k < next2FwdTransitions.Length; ++k) { + if (next2FwdTransitions[k].segmentId != next3PathPos.m_segment) { + continue; + } - if (!(next1FwdTransitions[j].type == LaneEndTransitionType.Default || - next1FwdTransitions[j].type == LaneEndTransitionType.LaneConnection || - (recklessDriver && next1FwdTransitions[j].type == LaneEndTransitionType.Relaxed)) - ) { - continue; - } + if (!(next2FwdTransitions[k].type == LaneEndTransitionType.Default || + next2FwdTransitions[k].type == LaneEndTransitionType.LaneConnection || + (recklessDriver && next2FwdTransitions[k].type == LaneEndTransitionType.Relaxed)) + ) { + continue; + } - if (next1FwdTransitions[j].distance > 1) { + if (next2FwdTransitions[k].distance > 1) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 transition {next1FwdTransitions[j]} (distance too large)"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next2 transition {next2FwdTransitions[k]} (distance too large)"); + } #endif - continue; - } + 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 (!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)"); - } + 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 - continue; - } - if (next3PathPos.m_segment != 0) { - // next2 -> next3 - uint next2FwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(next1FwdTransitions[j].laneId, !next1FwdTransitions[j].startNode); + Services.NetService.ProcessLane(next1BackTransitions[j].laneId, delegate (uint prevLaneId, ref NetLane prevLane) { + prevLanesClear = prevLane.GetReservedSpace() <= maxReservedSpace; + return true; + }); + + if (!prevLanesClear) { #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})"); - } + 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}): 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]}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Back lane {next1BackTransitions[j].laneId} is clear!"); + } #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}."); - } +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Checking for coming up traffic in front of next1 lane. prevLanesClear={prevLanesClear}"); + } #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 (/*foundClearBackLane*/foundSafeLaneChange && !prevLanesClear) { + continue; + } - 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); + // calculate lane metric #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})"); - } + 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 - if (!RoutingManager.Instance.LaneEndBackwardRoutings[next1BackRoutingIndex].routed) { - continue; - } - LaneTransitionData[] next1BackTransitions = RoutingManager.Instance.LaneEndBackwardRoutings[next1BackRoutingIndex].transitions; + 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)); - if (next1BackTransitions == null) { - continue; - } + ushort meanSpeed = TrafficMeasurementManager.Instance.CalcLaneRelativeMeanSpeed(currentFwdTransitions[i].segmentId, currentFwdTransitions[i].laneIndex, currentFwdTransitions[i].laneId, next1LaneInfo); - for (int j = 0; j < next1BackTransitions.Length; ++j) { - if (next1BackTransitions[j].segmentId != currentPathPos.m_segment || - next1BackTransitions[j].laneIndex == currentPathPos.m_lane) { - continue; - } + float relMeanSpeedInPercent = meanSpeed / (TrafficMeasurementManager.REF_REL_SPEED / TrafficMeasurementManager.REF_REL_SPEED_PERCENT_DENOMINATOR); + float randSpeed = 0f; + if (vehicleState.laneSpeedRandInterval > 0) { + randSpeed = Services.SimulationService.Randomizer.Int32((uint)vehicleState.laneSpeedRandInterval + 1u) - vehicleState.laneSpeedRandInterval / 2f; + relMeanSpeedInPercent += randSpeed; + } - if (!(next1BackTransitions[j].type == LaneEndTransitionType.Default || - next1BackTransitions[j].type == LaneEndTransitionType.LaneConnection || - (recklessDriver && next1BackTransitions[j].type == LaneEndTransitionType.Relaxed)) - ) { - continue; - } + float relMeanSpeed = relMeanSpeedInPercent / (float)TrafficMeasurementManager.REF_REL_SPEED_PERCENT_DENOMINATOR; + float next1MeanSpeed = relMeanSpeed * next1MaxSpeed; - if (next1BackTransitions[j].distance > 1) { + /*if ( #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Skipping next1 backward transition {next1BackTransitions[j]} (distance too large)"); - } + conf.Debug.Switches[19] && #endif - continue; - } - + 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}): Checking for upcoming traffic in front of next1 lane id={currentFwdTransitions[i].laneId}. Checking back transition {next1BackTransitions[j]}"); - } + 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); + }*/ - Services.NetService.ProcessLane(next1BackTransitions[j].laneId, delegate (uint prevLaneId, ref NetLane prevLane) { - prevLanesClear = prevLane.GetReservedSpace() <= maxReservedSpace; - return true; - }); + float speedDiff = next1MeanSpeed - targetSpeed; // > 0: lane is faster than vehicle would go. < 0: vehicle could go faster than this lane allows - if (!prevLanesClear) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): Back lane {next1BackTransitions[j].laneId} is not clear!"); - } + 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 - break; - } else { + 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}): Back lane {next1BackTransitions[j].laneId} is clear!"); - } + 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}): Checking for coming up traffic in front of next1 lane. prevLanesClear={prevLanesClear}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> no continuous lane found -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); + } #endif + return bestOptNext1LaneIndex; + } - if (/*foundClearBackLane*/foundSafeLaneChange && !prevLanesClear) { - continue; - } - - // calculate lane metric + if (float.IsInfinity(bestOptSpeedDiff)) { + // no lane change found #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}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> no lane change found -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); + } #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)); + return bestStayNext1LaneIndex; + } - ushort meanSpeed = TrafficMeasurementManager.Instance.CalcLaneRelativeMeanSpeed(currentFwdTransitions[i].segmentId, currentFwdTransitions[i].laneIndex, currentFwdTransitions[i].laneId, next1LaneInfo); + // decide if vehicle should stay or change - float relMeanSpeedInPercent = meanSpeed / (TrafficMeasurementManager.REF_REL_SPEED / TrafficMeasurementManager.REF_REL_SPEED_PERCENT_DENOMINATOR); - float randSpeed = 0f; - if (vehicleState.laneSpeedRandInterval > 0) { - randSpeed = Services.SimulationService.Randomizer.Int32((uint)vehicleState.laneSpeedRandInterval + 1u) - vehicleState.laneSpeedRandInterval / 2f; - relMeanSpeedInPercent += randSpeed; - } + // vanishing lane change opportunity detection + int vehSel = vehicleId % 12; +#if DEBUG + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): vehMod4={vehSel} numReachableNext2Lanes={numReachableNext2Lanes} numReachableNext3Lanes={numReachableNext3Lanes}"); + } +#endif + if ((numReachableNext3Lanes == 1 && vehSel <= 5) || // 50% of all vehicles will change lanes 3 segments in front + (numReachableNext2Lanes == 1 && vehSel <= 9) // 33% of all vehicles will change lanes 2 segments in front, 16.67% will change at the last opportunity + ) { + // vehicle must reach a certain lane since lane changing opportunities will vanish - float relMeanSpeed = relMeanSpeedInPercent / (float)TrafficMeasurementManager.REF_REL_SPEED_PERCENT_DENOMINATOR; - float next1MeanSpeed = relMeanSpeed * next1MaxSpeed; +#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 ( + if (bestOptTotalLaneDist < bestStayTotalLaneDist) { #if DEBUG - conf.Debug.Switches[19] && + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> vanishing lane change opportunities -- selecting bestOptTotalLaneDist={bestOptTotalLaneDist}"); + } #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; + return bestOptNext1LaneIndex; + } else { #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)}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> vanishing lane change opportunities -- selecting bestStayTotalLaneDist={bestStayTotalLaneDist}"); + } #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 + 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}): Calculated metric for next1 lane {currentFwdTransitions[i].laneId}: next1MaxSpeed={next1MaxSpeed} next1MeanSpeed={next1MeanSpeed} targetSpeed={targetSpeed} speedDiff={speedDiff} bestSpeedDiff={bestOptSpeedDiff} bestStaySpeedDiff={bestStaySpeedDiff}"); - } + 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 - 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 + return bestStayNext1LaneIndex; + } + if (bestStayTotalLaneDist != bestOptTotalLaneDist && Math.Max(bestStayTotalLaneDist, bestOptTotalLaneDist) > vehicleState.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}): 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}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): maximum best total lane distance = {Math.Max(bestStayTotalLaneDist, bestOptTotalLaneDist)} > AltLaneSelectionMaxOptLaneChanges"); + } #endif - if (float.IsInfinity(bestStaySpeedDiff)) { - // no continuous lane found + if (bestOptTotalLaneDist < bestStayTotalLaneDist) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> no continuous lane found -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> selecting lane change option for minimizing number of future lane changes -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); + } #endif - return bestOptNext1LaneIndex; - } - - if (float.IsInfinity(bestOptSpeedDiff)) { - // no lane change found + return bestOptNext1LaneIndex; + } else { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> no lane change found -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> selecting stay option for minimizing number of future lane changes -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); + } #endif - return bestStayNext1LaneIndex; - } + return bestStayNext1LaneIndex; + } + } - // decide if vehicle should stay or change - - // vanishing lane change opportunity detection - int vehSel = vehicleId % 12; + if (bestStaySpeedDiff < 0 && bestOptSpeedDiff > bestStaySpeedDiff) { + // found a lane change that improves vehicle speed + //float improvement = 100f * ((bestOptSpeedDiff - bestStaySpeedDiff) / ((bestStayMeanSpeed + bestOptMeanSpeed) / 2f)); + var speedDiff = Mathf.Abs(bestOptMeanSpeed - vehicleCurSpeed); + var optImprovementSpeed = SpeedLimit.ToKmphRounded(bestOptSpeedDiff - bestStaySpeedDiff); #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): vehMod4={vehSel} numReachableNext2Lanes={numReachableNext2Lanes} numReachableNext3Lanes={numReachableNext3Lanes}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): " + + $"a lane change for speed improvement is possible. " + + $"optImprovementInKmH={optImprovementSpeed} km/h speedDiff={speedDiff} " + + $"(bestOptMeanSpeed={bestOptMeanSpeed}, vehicleCurVelocity={vehicleCurSpeed}, " + + $"foundSafeLaneChange={foundSafeLaneChange})"); + } #endif - if ((numReachableNext3Lanes == 1 && vehSel <= 5) || // 50% of all vehicles will change lanes 3 segments in front - (numReachableNext2Lanes == 1 && vehSel <= 9) // 33% of all vehicles will change lanes 2 segments in front, 16.67% will change at the last opportunity - ) { - // vehicle must reach a certain lane since lane changing opportunities will vanish - + if (optImprovementSpeed >= vehicleState.minSafeSpeedImprovement && + (foundSafeLaneChange || (speedDiff <= vehicleState.maxUnsafeSpeedDiff)) + ) { + // speed improvement is significant #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): vanishing lane change opportunities detected: numReachableNext2Lanes={numReachableNext2Lanes} numReachableNext3Lanes={numReachableNext3Lanes}, vehSel={vehSel}, bestOptTotalLaneDist={bestOptTotalLaneDist}, bestStayTotalLaneDist={bestStayTotalLaneDist}"); - } + 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; + } - if (bestOptTotalLaneDist < bestStayTotalLaneDist) { + // insufficient improvement #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> vanishing lane change opportunities -- selecting bestOptTotalLaneDist={bestOptTotalLaneDist}"); - } + 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 bestOptNext1LaneIndex; - } else { + 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}): ===> vanishing lane change opportunities -- selecting bestStayTotalLaneDist={bestStayTotalLaneDist}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): found a lane change that optimizes overall traffic. optimization={optimization}%"); + } #endif - return bestStayNext1LaneIndex; - } - } + if (optimization >= vehicleState.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; + } - if (bestStaySpeedDiff == 0 || bestOptMeanSpeed < 0.1f) { - /* - * edge cases: - * (1) continuous lane is super optimal - * (2) best mean speed is near zero - */ + // insufficient optimization #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}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a lane that optimizes overall traffic but optimization is NOT significant -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); + } #endif - return bestStayNext1LaneIndex; - } + return bestOptNext1LaneIndex; + } - if (bestStayTotalLaneDist != bestOptTotalLaneDist && Math.Max(bestStayTotalLaneDist, bestOptTotalLaneDist) > vehicleState.maxOptLaneChanges) { - /* - * best route contains more lane changes than allowed: choose lane with the least number of future lane changes - */ + // suboptimal safe lane change #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): maximum best total lane distance = {Math.Max(bestStayTotalLaneDist, bestOptTotalLaneDist)} > AltLaneSelectionMaxOptLaneChanges"); - } + 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; + } - if (bestOptTotalLaneDist < bestStayTotalLaneDist) { + public int FindBestEmergencyLane(ushort vehicleId, ref Vehicle vehicleData, ref ExtVehicle vehicleState, uint currentLaneId, PathUnit.Position currentPathPos, NetInfo currentSegInfo, PathUnit.Position nextPathPos, NetInfo nextSegInfo) { + try { + GlobalConfig conf = GlobalConfig.Instance; #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> selecting lane change option for minimizing number of future lane changes -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); - } + 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.FindBestEmergencyLane({vehicleId}): currentLaneId={currentLaneId}, currentPathPos=[seg={currentPathPos.m_segment}, lane={currentPathPos.m_lane}, off={currentPathPos.m_offset}] nextPathPos=[seg={nextPathPos.m_segment}, lane={nextPathPos.m_lane}, off={nextPathPos.m_offset}]"); + } #endif - return bestOptNext1LaneIndex; - } else { + + // cur -> next + float curPosition = 0f; + if (currentPathPos.m_lane < currentSegInfo.m_lanes.Length) { + curPosition = currentSegInfo.m_lanes[currentPathPos.m_lane].m_position; + } + float vehicleLength = 1f + vehicleState.totalLength; + bool startNode = currentPathPos.m_offset < 128; + uint currentFwdRoutingIndex = RoutingManager.Instance.GetLaneEndRoutingIndex(currentLaneId, startNode); + #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> selecting stay option for minimizing number of future lane changes -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); - } + if (currentFwdRoutingIndex >= RoutingManager.Instance.LaneEndForwardRoutings.Length) { + Log.Error($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Invalid array index: currentFwdRoutingIndex={currentFwdRoutingIndex}, RoutingManager.Instance.laneEndForwardRoutings.Length={RoutingManager.Instance.LaneEndForwardRoutings.Length} (currentLaneId={currentLaneId}, startNode={startNode})"); + } #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 (!RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].routed) { #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})"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): No forward routing for next path position available."); + } #endif - if (optImprovementInKmH >= vehicleState.minSafeSpeedImprovement && - (foundSafeLaneChange || (speedDiff <= vehicleState.maxUnsafeSpeedDiff)) - ) { - // speed improvement is significant + return nextPathPos.m_lane; + } + + LaneTransitionData[] currentFwdTransitions = RoutingManager.Instance.LaneEndForwardRoutings[currentFwdRoutingIndex].transitions; + + if (currentFwdTransitions == null) { #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})"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): No forward transitions found for current lane {currentLaneId} at startNode {startNode}."); + } #endif - return bestOptNext1LaneIndex; - } + return nextPathPos.m_lane; + } - // 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})"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Starting lane-finding algorithm now. vehicleLength={vehicleLength}"); + } #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)); + + float minCost = float.MaxValue; + byte bestNextLaneIndex = nextPathPos.m_lane; + for (int i = 0; i < currentFwdTransitions.Length; ++i) { + if (currentFwdTransitions[i].segmentId != nextPathPos.m_segment) { + continue; + } + + if (!( + currentFwdTransitions[i].type == LaneEndTransitionType.Default || + currentFwdTransitions[i].type == LaneEndTransitionType.LaneConnection || + currentFwdTransitions[i].type == LaneEndTransitionType.Relaxed + )) { + continue; + } + + if (!VehicleRestrictionsManager.Instance.MayUseLane(vehicleState.vehicleType, nextPathPos.m_segment, currentFwdTransitions[i].laneIndex, nextSegInfo)) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): found a lane change that optimizes overall traffic. optimization={optimization}%"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Skipping current transition {currentFwdTransitions[i]} (vehicle restrictions)"); + } #endif - if (optimization >= vehicleState.minSafeTrafficImprovement) { - // traffic optimization is significant + continue; + } + + NetInfo.Lane nextLaneInfo = nextSegInfo.m_lanes[currentFwdTransitions[i].laneIndex]; + + /* + * Check reserved space on next lane + */ #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a lane that optimizes overall traffic and traffic optimization is significant -- selecting bestOptNext1LaneIndex={bestOptNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Checking for traffic on next lane id={currentFwdTransitions[i].laneId}."); + } #endif - return bestOptNext1LaneIndex; - } - // insufficient optimization + Services.NetService.ProcessLane(currentFwdTransitions[i].laneId, (uint nextLaneId, ref NetLane nextLane) => { + // similar to stock code in VehicleAI.FindBestLane + float cost = nextLane.GetReservedSpace(); + if (currentFwdTransitions[i].laneIndex == nextPathPos.m_lane) { + cost -= vehicleLength; + } + + cost += Mathf.Abs(curPosition - nextLaneInfo.m_position) * 0.1f; + + if (cost < minCost) { + minCost = cost; + bestNextLaneIndex = currentFwdTransitions[i].laneIndex; + #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> found a lane that optimizes overall traffic but optimization is NOT significant -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Found better lane: bestNextLaneIndex={bestNextLaneIndex}, minCost={minCost}"); + } #endif - return bestOptNext1LaneIndex; - } + } + return true; + }); + } // for each forward transition - // suboptimal safe lane change #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.FindBestLane({vehicleId}): ===> suboptimal safe lane change detected -- selecting bestStayNext1LaneIndex={bestStayNext1LaneIndex}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Best lane identified: bestNextLaneIndex={bestNextLaneIndex}, minCost={minCost}"); + } #endif - return bestStayNext1LaneIndex; - } catch (Exception e) { - Log.Error($"VehicleBehaviorManager.FindBestLane({vehicleId}): Exception occurred: {e}"); - } - return next1PathPos.m_lane; - } + return bestNextLaneIndex; + } catch (Exception e) { + Log.Error($"VehicleBehaviorManager.FindBestEmergencyLane({vehicleId}): Exception occurred: {e}"); + } + return nextPathPos.m_lane; + } - public bool MayFindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref ExtVehicle vehicleState) { - GlobalConfig conf = GlobalConfig.Instance; + public bool MayFindBestLane(ushort vehicleId, ref Vehicle vehicleData, ref ExtVehicle 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."); - } + 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 (!Options.advancedAI) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. Advanced Vehicle AI is disabled."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. Advanced Vehicle AI is disabled."); + } #endif - return false; - } + return false; + } - if (vehicleState.heavyVehicle) { + if (vehicleState.heavyVehicle) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. Vehicle is heavy."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. Vehicle is heavy."); + } #endif - return false; - } + return false; + } - if ((vehicleState.vehicleType & (ExtVehicleType.RoadVehicle & ~ExtVehicleType.Bus)) == ExtVehicleType.None) { + if ((vehicleState.vehicleType & (ExtVehicleType.RoadVehicle & ~ExtVehicleType.Bus)) == ExtVehicleType.None) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. vehicleType={vehicleState.vehicleType}"); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking. vehicleType={vehicleState.vehicleType}"); + } #endif - return false; - } + return false; + } - uint vehicleRand = Constants.ManagerFactory.ExtVehicleManager.GetStaticVehicleRand(vehicleId); + uint vehicleRand = Constants.ManagerFactory.ExtVehicleManager.GetStaticVehicleRand(vehicleId); - if (vehicleRand < 100 - (int)Options.altLaneSelectionRatio) { + if (vehicleRand < 100 - (int)Options.altLaneSelectionRatio) { #if DEBUG - if (debug) { - Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking (randomization)."); - } + if (debug) { + Log._Debug($"VehicleBehaviorManager.MayFindBestLane({vehicleId}): Skipping lane checking (randomization)."); + } #endif - return false; - } + return false; + } - return true; - } - } -} + return true; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs b/TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs index a45982ff5..2de95b611 100644 --- a/TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs +++ b/TLM/TLM/Manager/Impl/VehicleRestrictionsManager.cs @@ -1,572 +1,582 @@ -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.Enums; -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(startNodeId); - } - if (endNodeId != 0) { - Constants.ManagerFactory.GeometryManager.MarkAsUpdated(endNodeId); - } - } - - protected override void HandleInvalidSegment(ref ExtSegment seg) { - Flags.resetSegmentVehicleRestrictions(seg.segmentId); - ClearCache(seg.segmentId); - } - - protected override void HandleValidSegment(ref ExtSegment seg) { - - } - - 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; +namespace TrafficManager.Manager.Impl { + using System; + using System.Collections.Generic; + using API.Manager; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using State; + using Traffic; + using Traffic.Data; + using Util; + using ExtVehicleType = global::TrafficManager.API.Traffic.Enums.ExtVehicleType; + + 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 = { 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) { + var 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(startNodeId); + } + if (endNodeId != 0) { + Constants.ManagerFactory.GeometryManager.MarkAsUpdated(endNodeId); + } + } + + protected override void HandleInvalidSegment(ref ExtSegment seg) { + Flags.resetSegmentVehicleRestrictions(seg.segmentId); + ClearCache(seg.segmentId); + } + + protected override void HandleValidSegment(ref ExtSegment seg) { + + } + + 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; + + var baseMask = GetBaseMask(laneVehicleTypes.laneId, + VehicleRestrictionsMode.Configured); + var maskedType = laneVehicleTypes.ApiVehicleTypes & baseMask; #if DEBUGLOAD Log._Debug($"Loading lane vehicle restriction: lane {laneVehicleTypes.laneId} = {laneVehicleTypes.vehicleTypes}, masked = {maskedType}"); #endif - if (maskedType != baseMask) { - Flags.setLaneAllowedVehicleTypes(laneVehicleTypes.laneId, maskedType); - } else { + if (maskedType != baseMask) { + Flags.setLaneAllowedVehicleTypes(laneVehicleTypes.laneId, maskedType); + } else { #if DEBUGLOAD Log._Debug($"Masked type does not differ from base type. Ignoring."); #endif - } - } 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)); - Log._Trace($"Saving lane vehicle restriction: laneid={e.Key} vehicleType={e.Value}"); - } catch (Exception ex) { - Log.Error($"Exception occurred while saving lane vehicle restrictions @ {e.Key}: {ex.ToString()}"); - success = false; - } - } - return ret; - } - } -} + } + } 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, LegacyExtVehicleType.ToOld(e.Value))); + Log._Trace($"Saving lane vehicle restriction: laneid={e.Key} vehicleType={e.Value}"); + } catch (Exception ex) { + Log.Error($"Exception occurred while saving lane vehicle restrictions @ {e.Key}: {ex.ToString()}"); + success = false; + } + } + return ret; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs b/TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs index b1d8e3afc..d0a7b4f37 100644 --- a/TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs +++ b/TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs @@ -15,7 +15,7 @@ public static class CreateVehiclePatch { /// [HarmonyPrefix] public static bool Prefix(VehicleManager __instance, ref ushort vehicle, VehicleInfo info) { - if (__instance.m_vehicleCount > VehicleManager.MAX_VEHICLE_COUNT - 5) { + if (__instance.m_vehicleCount > Constants.ServiceFactory.VehicleService.MaxVehicleCount - 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) { diff --git a/TLM/TLM/Properties/AssemblyInfo.cs b/TLM/TLM/Properties/AssemblyInfo.cs index 5b22dc1bc..98ef1c155 100644 --- a/TLM/TLM/Properties/AssemblyInfo.cs +++ b/TLM/TLM/Properties/AssemblyInfo.cs @@ -35,4 +35,4 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.*")] -[assembly:TypeForwardedTo(typeof(ExtVehicleType))] \ No newline at end of file +// [assembly:TypeForwardedTo(typeof(ExtVehicleType))] \ No newline at end of file diff --git a/TLM/TLM/Resources/110.png b/TLM/TLM/Resources/110.png deleted file mode 100644 index fdf3a16c7..000000000 Binary files a/TLM/TLM/Resources/110.png and /dev/null differ diff --git a/TLM/TLM/Resources/0.png b/TLM/TLM/Resources/SpeedLimits/Kmh/0.png similarity index 100% rename from TLM/TLM/Resources/0.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/0.png diff --git a/TLM/TLM/Resources/10.png b/TLM/TLM/Resources/SpeedLimits/Kmh/10.png similarity index 100% rename from TLM/TLM/Resources/10.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/10.png diff --git a/TLM/TLM/Resources/100.png b/TLM/TLM/Resources/SpeedLimits/Kmh/100.png similarity index 100% rename from TLM/TLM/Resources/100.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/100.png diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/105.png b/TLM/TLM/Resources/SpeedLimits/Kmh/105.png new file mode 100644 index 000000000..fb0ca66fa Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/105.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/110.png b/TLM/TLM/Resources/SpeedLimits/Kmh/110.png new file mode 100644 index 000000000..28f2b0f93 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/110.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/115.png b/TLM/TLM/Resources/SpeedLimits/Kmh/115.png new file mode 100644 index 000000000..f61ec322f Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/115.png differ diff --git a/TLM/TLM/Resources/120.png b/TLM/TLM/Resources/SpeedLimits/Kmh/120.png similarity index 100% rename from TLM/TLM/Resources/120.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/120.png diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/125.png b/TLM/TLM/Resources/SpeedLimits/Kmh/125.png new file mode 100644 index 000000000..004225955 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/125.png differ diff --git a/TLM/TLM/Resources/130.png b/TLM/TLM/Resources/SpeedLimits/Kmh/130.png similarity index 100% rename from TLM/TLM/Resources/130.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/130.png diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/135.png b/TLM/TLM/Resources/SpeedLimits/Kmh/135.png new file mode 100644 index 000000000..253ad482f Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/135.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/140.png b/TLM/TLM/Resources/SpeedLimits/Kmh/140.png new file mode 100644 index 000000000..5cf5aabb7 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/140.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/15.png b/TLM/TLM/Resources/SpeedLimits/Kmh/15.png new file mode 100644 index 000000000..120fed7b7 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/15.png differ diff --git a/TLM/TLM/Resources/20.png b/TLM/TLM/Resources/SpeedLimits/Kmh/20.png similarity index 100% rename from TLM/TLM/Resources/20.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/20.png diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/25.png b/TLM/TLM/Resources/SpeedLimits/Kmh/25.png new file mode 100644 index 000000000..108f2ad9e Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/25.png differ diff --git a/TLM/TLM/Resources/30.png b/TLM/TLM/Resources/SpeedLimits/Kmh/30.png similarity index 100% rename from TLM/TLM/Resources/30.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/30.png diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/35.png b/TLM/TLM/Resources/SpeedLimits/Kmh/35.png new file mode 100644 index 000000000..54b56e4f6 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/35.png differ diff --git a/TLM/TLM/Resources/40.png b/TLM/TLM/Resources/SpeedLimits/Kmh/40.png similarity index 100% rename from TLM/TLM/Resources/40.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/40.png diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/45.png b/TLM/TLM/Resources/SpeedLimits/Kmh/45.png new file mode 100644 index 000000000..df1406954 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/45.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/5.png b/TLM/TLM/Resources/SpeedLimits/Kmh/5.png new file mode 100644 index 000000000..c6ae24a07 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/5.png differ diff --git a/TLM/TLM/Resources/50.png b/TLM/TLM/Resources/SpeedLimits/Kmh/50.png similarity index 100% rename from TLM/TLM/Resources/50.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/50.png diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/55.png b/TLM/TLM/Resources/SpeedLimits/Kmh/55.png new file mode 100644 index 000000000..656194e74 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/55.png differ diff --git a/TLM/TLM/Resources/60.png b/TLM/TLM/Resources/SpeedLimits/Kmh/60.png similarity index 100% rename from TLM/TLM/Resources/60.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/60.png diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/65.png b/TLM/TLM/Resources/SpeedLimits/Kmh/65.png new file mode 100644 index 000000000..99e967802 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/65.png differ diff --git a/TLM/TLM/Resources/70.png b/TLM/TLM/Resources/SpeedLimits/Kmh/70.png similarity index 100% rename from TLM/TLM/Resources/70.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/70.png diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/75.png b/TLM/TLM/Resources/SpeedLimits/Kmh/75.png new file mode 100644 index 000000000..d85c6fff1 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/75.png differ diff --git a/TLM/TLM/Resources/80.png b/TLM/TLM/Resources/SpeedLimits/Kmh/80.png similarity index 100% rename from TLM/TLM/Resources/80.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/80.png diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/85.png b/TLM/TLM/Resources/SpeedLimits/Kmh/85.png new file mode 100644 index 000000000..63615a3ea Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/85.png differ diff --git a/TLM/TLM/Resources/90.png b/TLM/TLM/Resources/SpeedLimits/Kmh/90.png similarity index 100% rename from TLM/TLM/Resources/90.png rename to TLM/TLM/Resources/SpeedLimits/Kmh/90.png diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/95.png b/TLM/TLM/Resources/SpeedLimits/Kmh/95.png new file mode 100644 index 000000000..2c4022279 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Kmh/95.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/0.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/0.png new file mode 100644 index 000000000..f90cdb1c0 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/0.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/10.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/10.png new file mode 100644 index 000000000..a2f9c445a Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/10.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/15.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/15.png new file mode 100644 index 000000000..f656eb7fa Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/15.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/20.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/20.png new file mode 100644 index 000000000..52170aa4b Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/20.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/25.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/25.png new file mode 100644 index 000000000..d5d083205 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/25.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/30.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/30.png new file mode 100644 index 000000000..384c47b95 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/30.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/35.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/35.png new file mode 100644 index 000000000..4080dddcd Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/35.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/40.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/40.png new file mode 100644 index 000000000..a094b8230 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/40.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/45.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/45.png new file mode 100644 index 000000000..a4e07c695 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/45.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/5.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/5.png new file mode 100644 index 000000000..0dba1538d Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/5.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/50.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/50.png new file mode 100644 index 000000000..28401cc0a Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/50.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/55.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/55.png new file mode 100644 index 000000000..ed446b3b6 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/55.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/60.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/60.png new file mode 100644 index 000000000..c90bfc758 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/60.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/65.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/65.png new file mode 100644 index 000000000..f7d208139 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/65.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/70.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/70.png new file mode 100644 index 000000000..4fa829f16 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/70.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/75.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/75.png new file mode 100644 index 000000000..fc70b5c34 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/75.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/80.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/80.png new file mode 100644 index 000000000..bdb8eabcf Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/80.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/85.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/85.png new file mode 100644 index 000000000..04957300b Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/85.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_UK/90.png b/TLM/TLM/Resources/SpeedLimits/Mph_UK/90.png new file mode 100644 index 000000000..322368f8d Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_UK/90.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/0.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/0.png new file mode 100644 index 000000000..28f5a16e0 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/0.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/10.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/10.png new file mode 100644 index 000000000..99328d125 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/10.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/15.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/15.png new file mode 100644 index 000000000..f1c8d1320 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/15.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/20.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/20.png new file mode 100644 index 000000000..b7c78cced Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/20.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/25.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/25.png new file mode 100644 index 000000000..d4f601ff1 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/25.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/30.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/30.png new file mode 100644 index 000000000..fe2bc3c28 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/30.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/35.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/35.png new file mode 100644 index 000000000..1792175b8 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/35.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/40.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/40.png new file mode 100644 index 000000000..6402f08c6 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/40.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/45.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/45.png new file mode 100644 index 000000000..d86f3404f Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/45.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/5.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/5.png new file mode 100644 index 000000000..a1906baa3 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/5.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/50.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/50.png new file mode 100644 index 000000000..c5044d0c4 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/50.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/55.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/55.png new file mode 100644 index 000000000..6f07df67e Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/55.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/60.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/60.png new file mode 100644 index 000000000..59a7bfbcf Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/60.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/65.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/65.png new file mode 100644 index 000000000..2c6d5e3a0 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/65.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/70.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/70.png new file mode 100644 index 000000000..1d7416e15 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/70.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/75.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/75.png new file mode 100644 index 000000000..8d081e91a Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/75.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/80.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/80.png new file mode 100644 index 000000000..008d1cc24 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/80.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/85.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/85.png new file mode 100644 index 000000000..54cfdff62 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/85.png differ diff --git a/TLM/TLM/Resources/SpeedLimits/Mph_US/90.png b/TLM/TLM/Resources/SpeedLimits/Mph_US/90.png new file mode 100644 index 000000000..b842d07f8 Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/Mph_US/90.png differ diff --git a/TLM/TLM/Resources/lang.txt b/TLM/TLM/Resources/lang.txt index 68534b7ca..32ee3af2f 100644 --- a/TLM/TLM/Resources/lang.txt +++ b/TLM/TLM/Resources/lang.txt @@ -59,7 +59,7 @@ 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 +Settings_are_defined_for_each_savegame_separately Settings can only be changed whilst in-game, and are unique to each save game. 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) @@ -123,7 +123,7 @@ 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) +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 @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles 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 +Policies_&_Restrictions Policies At_junctions At junctions In_case_of_emergency In case of emergency Show_lane-wise_speed_limits Show lane-wise speed limits @@ -208,11 +208,11 @@ 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_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_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. @@ -230,5 +230,28 @@ 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 Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Traffic_Manager_detected_incompatible_mods detected incompatible mods +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Unsubscribe Unsubscribe +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Stay in lane +Keybind_lane_connector_delete Clear lane connections +Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys +Speed_limit_unlimited No limit +Display_speed_limits_mph Display speed limits as MPH instead of km/h +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_de.txt b/TLM/TLM/Resources/lang_de.txt index 63b2c749a..168d6cb7f 100644 --- a/TLM/TLM/Resources/lang_de.txt +++ b/TLM/TLM/Resources/lang_de.txt @@ -109,7 +109,7 @@ Rotate_left Links rotieren Rotate_right Rechts rotieren Name Name Apply Anwenden -Select_a_timed_traffic_light_program Wähle ein zeitgesteuertes Ampelprogramm aus +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++). @@ -154,7 +154,7 @@ Individual_driving_styles Individuelle Fahrstile 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 +Policies_&_Restrictions Richtlinien At_junctions An Kreuzungen In_case_of_emergency Im Notfall Show_lane-wise_speed_limits Zeige spurweise Geschwindigkeitsbeschränkungen @@ -164,7 +164,7 @@ 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 +Lock_main_menu_position Position des Hauptmenüs sperren Recalculating_lane_routing Berechne Routenführung neu Please_wait Bitte warten Parking_restrictions Parkverbote @@ -189,11 +189,11 @@ TMPE_TUTORIAL_BODY_MainMenu Willkommen bei TM:PE!\n\nBenutzerhandbuch: http://ww 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_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_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 @@ -202,17 +202,17 @@ 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_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_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_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. @@ -230,5 +230,27 @@ 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 Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Zeige Fehlermeldung, wenn eine Mod-Inkompatibilität erkannt wurde \ No newline at end of file +Traffic_Manager_detected_incompatible_mods detected incompatible mods +Notify_me_if_there_is_an_unexpected_mod_conflict Zeige Fehlermeldung, wenn eine Mod-Inkompatibilität erkannt wurde +Unsubscribe Unsubscribe +Keybind_toggle_TMPE_main_menu TM:PE-Hauptmenü umschalten +Keybind_toggle_traffic_lights_tool Werkzeug 'Ampeln setzen' verwenden +Keybind_use_lane_arrow_tool Werkzeug 'Richtungspfeile' verwenden +Keybind_use_lane_connections_tool Werkzeug 'Fahrspurverbinder' verwenden +Keybind_use_priority_signs_tool Werkzeug 'Vorfahrtsschilder' verwenden +Keybind_use_junction_restrictions_tool Werkzeug 'Kreuzungsbeschränkungen' verwenden +Keybind_use_speed_limits_tool Werkzeug 'Geschwindigkeitsbeschränkungen' verwenden +Keybind_lane_connector_stay_in_lane Fahrspurverbinder: Bleib auf der Fahrspur +Keybind_lane_connector_delete Fahrspurverbindungen löschen +Keybinds Tastenkombinationen +Keybind_Exit_subtool Werkzeug und TM:PE Menü schließen +Keybind_conflict Die ausgewählte Taste steht in Konflikt mit einer anderen Tastenkombination. Bitte wähle etwas anderes. +Keybind_category_Global Allgemeine Tasten +Keybind_category_LaneConnector Tasten für 'Fahrspurverbinder' Werkzeug +Display_speed_limits_mph Geschwindigkeitsbegrenzungen anzeigen als MPH statt km/h +Miles_per_hour Meilen pro Stunde +Kilometers_per_hour Kilometer pro Stunde +Road_signs_theme_mph Thema für MPH Verkehrszeichen +theme_Square_US US-Verkehrszeichen +theme_Round_UK Britische Verkehrszeichen +theme_Round_German Deutsche Verkehrszeichen diff --git a/TLM/TLM/Resources/lang_es.txt b/TLM/TLM/Resources/lang_es.txt index 21457ec8a..4960a8723 100644 --- a/TLM/TLM/Resources/lang_es.txt +++ b/TLM/TLM/Resources/lang_es.txt @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles 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 +Policies_&_Restrictions Normas 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 @@ -208,11 +208,11 @@ 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_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_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. @@ -230,5 +230,27 @@ 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 Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Traffic_Manager_detected_incompatible_mods detected incompatible mods +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Unsubscribe Unsubscribe +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections +Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys +Display_speed_limits_mph Display speed limits as MPH instead of km/h +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_fr.txt b/TLM/TLM/Resources/lang_fr.txt index b40634d4b..88f517a97 100644 --- a/TLM/TLM/Resources/lang_fr.txt +++ b/TLM/TLM/Resources/lang_fr.txt @@ -23,8 +23,8 @@ 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 : +Min._Time: Temps Min : +Max._Time: Temps Max : Save Sauvegarder Add Ajouter Sensitivity Sensibilité @@ -62,7 +62,7 @@ TMPE_Title Gestionnaire de trafic : Édition 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) : +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 @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles Evacuation_busses_may_ignore_traffic_rules Les bus d'évacuation peuvent ignorer les règles de trafic Evacuation_busses_may_only_be_used_to_reach_a_shelter Les bus d'évacuation ne peuvent être utilisés que pour atteindre un abri Vehicle_behavior Comportement des véhicules -Policies_&_Restrictions Restrictions et politiques +Policies_&_Restrictions Politiques At_junctions Aux intersections In_case_of_emergency En cas d'urgence Show_lane-wise_speed_limits Afficher les limites de vitesse par voie @@ -212,7 +212,7 @@ TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddStep Ajouter une étape chronométr 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 sont en train de rouler qu'en attente.\n4. En option, ajustez la sensibilité du feu. Par exemple, déplacez le curseur vers la gauche pour le rendre moins sensible aux véhicules en attente. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy Copier un feu de circulation chronométré TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy Cliquez sur un autre carrefour pour coller le feu de circulation chronométré.\n\nCliquez n'importe où avec le bouton secondaire de la souris pour annuler l'opération. -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Ajouter une intersection à un feu de circulation chronométré +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction Ajouter une intersection à un feu de circulation chronométré TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Cliquez sur une autre intersection pour l'ajouter. Les deux feux seront associés de sorte que le programme chronométré contrôlera ensuite les deux intersections à la fois.\n\nCliquez n'importe où avec le bouton secondaire de la souris pour annuler l'opération. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Retirer une intersection d'un feu de circulation chronométré TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Cliquez sur l'une des intersections contrôlées par ce programme chronométré. Le feu sélectionné sera supprimé de telle sorte que le programme chronométré ne le gérera plus.\n\nCliquez n'importe où avec le bouton secondaire de la souris pour annuler l'opération. @@ -230,5 +230,27 @@ Vehicles_may_turn_on_red Les véhicules peuvent tourner aux feux rouges Also_apply_to_left/right_turns_between_one-way_streets Appliquer de même aux virages gauche/droite entre des rues à sens unique Scan_for_known_incompatible_mods_on_startup Rechercher les mods incompatibles au lancement Ignore_disabled_mods Ignorer les mods désactivés -Traffic_Manager_detected_incompatible_mods Le gestionnaire de trafic a détecté des mods incompatibles +Traffic_Manager_detected_incompatible_mods a détecté des mods incompatibles Notify_me_if_there_is_an_unexpected_mod_conflict Afficher un message d'erreur si un mod incompatible est détecté +Unsubscribe Unsubscribe +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections +Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys +Display_speed_limits_mph Display speed limits as MPH instead of km/h +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_it.txt b/TLM/TLM/Resources/lang_it.txt index a4f86a030..620265e55 100644 --- a/TLM/TLM/Resources/lang_it.txt +++ b/TLM/TLM/Resources/lang_it.txt @@ -123,7 +123,7 @@ 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) +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" @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles 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 +Policies_&_Restrictions Policies At_junctions Agli incroci In_case_of_emergency In caso di emergenza Show_lane-wise_speed_limits Mostra limite velocità per ogni corsia @@ -176,11 +176,11 @@ 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 +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 +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 @@ -208,11 +208,11 @@ 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_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_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. @@ -230,5 +230,27 @@ 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 Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Traffic_Manager_detected_incompatible_mods detected incompatible mods +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Unsubscribe Unsubscribe +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections +Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys +Display_speed_limits_mph Display speed limits as MPH instead of km/h +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_ja.txt b/TLM/TLM/Resources/lang_ja.txt index fdbd694c5..e5ababc51 100644 --- a/TLM/TLM/Resources/lang_ja.txt +++ b/TLM/TLM/Resources/lang_ja.txt @@ -85,9 +85,9 @@ Add_zoning 区間の追加 Remove_zoning 区間の削除 Lane_Arrow_Changer_Disabled_Highway 高速道路ルールシステムを適用しているため、この車線の矢印は変更できません Add_junction_to_timed_light この信号に交差点を追加 -Remove_junction_from_timed_light この信号から交差点を削除 -Select_junction 交差点を選択してください -Cancel キャンセル +Remove_junction_from_timed_light この信号から交差点を削除 +Select_junction 交差点を選択してください +Cancel キャンセル Speed_limits 速度制限 Persistently_visible_overlays オーバーレイ表示し続ける情報 Priority_signs 優先関係標識 @@ -209,10 +209,10 @@ TMPE_TUTORIAL_BODY_VehicleRestrictionsTool 特定の道路区間の車両を禁 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_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_HEAD_TimedTrafficLightsTool_AddJunction 時間設定付き信号機に交差点を追加する TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction 追加するには、他の交差点をクリックします。 両方の信号は、時間設定プログラムが両方の交差点を一度に制御するように結合されます。\n\n操作をキャンセルするには、マウスの右ボタンで任意の場所をクリックします。 TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction 時間設定付き信号機から交差点を削除する TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction この時間設定プログラムによって制御されている交差点の1つをクリックします。 選択した信号機は削除され、時間設定プログラムによって管理されなくなります。\n\n操作をキャンセルするには、マウスの右ボタンで任意の場所をクリックします。 @@ -230,5 +230,27 @@ Vehicles_may_turn_on_red 車両が赤信号で曲がることがある Also_apply_to_left/right_turns_between_one-way_streets 一方通行路の左右の曲がり角にも適用 Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict modの非互換性が検出された場合にエラーメッセージを表示す \ No newline at end of file +Traffic_Manager_detected_incompatible_mods detected incompatible mods +Notify_me_if_there_is_an_unexpected_mod_conflict modの非互換性が検出された場合にエラーメッセージを表示す +Unsubscribe Unsubscribe +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections +Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys +Display_speed_limits_mph Display speed limits as MPH instead of km/h +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_ko.txt b/TLM/TLM/Resources/lang_ko.txt index 1bc09409f..b899a7c56 100644 --- a/TLM/TLM/Resources/lang_ko.txt +++ b/TLM/TLM/Resources/lang_ko.txt @@ -230,5 +230,27 @@ Vehicles_may_turn_on_red 빨간 신호등에서 차량이 진입 할 수 있습 Also_apply_to_left/right_turns_between_one-way_streets 빨간 신호등에서 우회전을 허용 Scan_for_known_incompatible_mods_on_startup 게임 실행시 비호환되는 모드 스캔 Ignore_disabled_mods 비활성화된 모드 무시하기 -Traffic_Manager_detected_incompatible_mods Traffic Manager와 비호환되는 모드 감지 +Traffic_Manager_detected_incompatible_mods 와 비호환되는 모드 감지 Notify_me_if_there_is_an_unexpected_mod_conflict 모드와 비 호환되는 모드 발견 시 에러 보여주기 +Unsubscribe Unsubscribe +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections +Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys +Display_speed_limits_mph Display speed limits as MPH instead of km/h +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_nl.txt b/TLM/TLM/Resources/lang_nl.txt index 5f748af47..07e61dc66 100644 --- a/TLM/TLM/Resources/lang_nl.txt +++ b/TLM/TLM/Resources/lang_nl.txt @@ -155,7 +155,7 @@ Individual_driving_styles Individual driving styles 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 +Policies_&_Restrictions Beleidsregels At_junctions Bij splitsingen In_case_of_emergency In geval van nood Show_lane-wise_speed_limits Toon rijstrookspecifieke snelheidslimieten @@ -208,11 +208,11 @@ 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_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_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. @@ -230,5 +230,27 @@ 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 Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Traffic_Manager_detected_incompatible_mods detected incompatible mods +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Unsubscribe Unsubscribe +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections +Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys +Display_speed_limits_mph Display speed limits as MPH instead of km/h +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_pl.txt b/TLM/TLM/Resources/lang_pl.txt index 9200d8b58..f7eea10d4 100644 --- a/TLM/TLM/Resources/lang_pl.txt +++ b/TLM/TLM/Resources/lang_pl.txt @@ -1,4 +1,4 @@ -Switch_traffic_lights Włącznik sygnalizacji +Switch_traffic_lights Włącznik sygnalizacji Add_priority_signs Dodaj znaki pierwszeństwa Manual_traffic_lights Sygnalizacja Ręczna Timed_traffic_lights Sygnalizacja Czasowa @@ -63,7 +63,7 @@ Settings_are_defined_for_each_savegame_separately ustawienia są definiowane odd 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) +Maintenance Konserwacja Very_often Bardzo często Often Często Sometimes Czasami @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles 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 +Policies_&_Restrictions Zasady 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 @@ -208,11 +208,11 @@ 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_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_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ę. @@ -230,5 +230,27 @@ 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 Scan_for_known_incompatible_mods_on_startup Uruchamiaj sprawdzanie przy starcie gry Ignore_disabled_mods Ignoruj wyłączone mody -Traffic_Manager_detected_incompatible_mods Traffic Manager wykrył niekompatybilne mody -Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów \ No newline at end of file +Traffic_Manager_detected_incompatible_mods wykrył niekompatybilne mody +Notify_me_if_there_is_an_unexpected_mod_conflict Powiadom mnie w razie nieoczekiwanej niezgodność modów +Unsubscribe Odsubskrybuj +Keybind_toggle_TMPE_main_menu Menu TM:PE +Keybind_toggle_traffic_lights_tool Narzędzie 'Przełącznik Sygnalizacji świetlnej' +Keybind_use_lane_arrow_tool Narzędzie 'Strzałki pasów ruchu' +Keybind_use_lane_connections_tool Narzędzie 'Połącz pasy ruchu' +Keybind_use_priority_signs_tool Narzędzie 'Znaki pierwszeństwa' +Keybind_use_junction_restrictions_tool Narzędzie 'Ograniczenia na skrzyżowaniu' +Keybind_use_speed_limits_tool Narzędzie 'Limity prędkości' +Keybind_lane_connector_stay_in_lane Połącz pasy ruchu: Pozostań na pasie +Keybind_lane_connector_delete Usuń połączenia pasów ruchu +Keybinds Skróty klawiszowe +Keybind_Exit_subtool Wyłącz narzędzie i zamknij TM:PE menu +Keybind_conflict Wybrany skrót klawiszowy jest już używany. Wybierz inną kombinację. +Keybind_category_Global Główne +Keybind_category_LaneConnector Narzędzie: Połącz pasy ruchu +Display_speed_limits_mph Wyświetlaj limity prędkości w mph zamiast km/h +Miles_per_hour Mile na godzinę +Kilometers_per_hour Kilometry na godzinę +Road_signs_theme_mph Motyw dla znaków MPH +theme_Square_US Stany zjednoczone +theme_Round_UK Wielka Brytania +theme_Round_German Niemcy diff --git a/TLM/TLM/Resources/lang_pt.txt b/TLM/TLM/Resources/lang_pt.txt index 1993850e4..dd6555f7a 100644 --- a/TLM/TLM/Resources/lang_pt.txt +++ b/TLM/TLM/Resources/lang_pt.txt @@ -1,13 +1,13 @@ Switch_traffic_lights Interruptor de Semáforos -Add_priority_signs Placas de preferência +Add_priority_signs Placas de preferência Manual_traffic_lights Semáforo manual -Timed_traffic_lights Semáforos cronometrados +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. +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 @@ -50,10 +50,10 @@ avg._wait Espera média min/max min/max Lane Pista Set_Speed Setar velocidade {0} -Max_speed Velocidade Máxima +Max_speed Velocidade Máxima Segment Segmento incoming entrando -Enable_Advanced_Vehicle_AI Ativar IA avançada de veículo +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 @@ -74,9 +74,9 @@ 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 %) +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 +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 @@ -90,7 +90,7 @@ 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 +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 @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles 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 +Policies_&_Restrictions Políticas 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. @@ -208,11 +208,11 @@ 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_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_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. @@ -230,5 +230,27 @@ 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 Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Traffic_Manager_detected_incompatible_mods detected incompatible mods +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Unsubscribe Unsubscribe +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections +Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys +Display_speed_limits_mph Display speed limits as MPH instead of km/h +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_ru.txt b/TLM/TLM/Resources/lang_ru.txt index 4193e0a8c..7efe04686 100644 --- a/TLM/TLM/Resources/lang_ru.txt +++ b/TLM/TLM/Resources/lang_ru.txt @@ -154,7 +154,7 @@ Individual_driving_styles Individual driving styles Evacuation_busses_may_ignore_traffic_rules Автобусы эвакуации игнорируют правила движения Evacuation_busses_may_only_be_used_to_reach_a_shelter Эвакуационные автобусы, только чтобы добраться до аварийного убежища Vehicle_behavior Поведение транспорта -Policies_&_Restrictions Политики и Ограничения +Policies_&_Restrictions Политики At_junctions На перекрёстках In_case_of_emergency В случае крайней необходимости Show_lane-wise_speed_limits Показывать ограничения скорости по полосам @@ -208,11 +208,11 @@ 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_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_HEAD_TimedTrafficLightsTool_AddJunction Добавить другой перекрёсток в шаблон светофора TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction Нажмите на другой перекрёсток, чтобы добавить его. Программа с таймером будет контролировать светофоры на обоих перекрёстках одновременно.\n\nЩёлкните в любом месте дополнительной кнопкой мышки, чтобы отменить операцию. TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction Удалите перекрёсток из схемы настраиваемого светофора TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction Нажмите на один из перекрёстков, которые контролируются этой программой времени. Выбранный светофор будет удален таким образом, что программа с таймером больше не будет управлять им.\n\nЩёлкните в любом месте дополнительной кнопкой мышки, чтобы отменить операцию. @@ -230,5 +230,27 @@ Vehicles_may_turn_on_red Разрешить поворот направо на Also_apply_to_left/right_turns_between_one-way_streets Разрешить направо/налево между улицами с односторонним движением Scan_for_known_incompatible_mods_on_startup Сканирование известных несовместимых модов при запуске Ignore_disabled_mods Игнорировать отключённые моды -Traffic_Manager_detected_incompatible_mods Traffic Manager обнаружил несовместимые моды -Notify_me_if_there_is_an_unexpected_mod_conflict Показывать сообщение об ошибке при несовместимости модов \ No newline at end of file +Traffic_Manager_detected_incompatible_mods обнаружил несовместимые моды +Notify_me_if_there_is_an_unexpected_mod_conflict Показывать сообщение об ошибке при несовместимости модов +Unsubscribe Unsubscribe +Keybind_toggle_TMPE_main_menu Показать или скрыть меню TM:PE +Keybind_toggle_traffic_lights_tool Инструмент "Установка светофора" +Keybind_use_lane_arrow_tool Инструмент "Стрелки движения" +Keybind_use_lane_connections_tool Инструмент "Линии движения" +Keybind_use_priority_signs_tool Инструмент "Знаки приоритета" +Keybind_use_junction_restrictions_tool Инструмент "Ограничения на перекрёстках" +Keybind_use_speed_limits_tool Инструмент "Ограничения скорости" +Keybind_lane_connector_stay_in_lane Линии движения: Оставаться в своей полосе +Keybind_lane_connector_delete Очистить соединения линий движения +Keybinds Горячие клавиши +Keybind_Exit_subtool Закрыть инструмент и меню TM:PE +Keybind_conflict Выбранная комбинация клавиш уже используется в другом месте. Пожалуйста, выберите другую комбинацию. +Keybind_category_Global Общие комбинации клавиш +Keybind_category_LaneConnector Клавиши для инструмента "Линии Движения" +Display_speed_limits_mph Показывать скорости в милях в час вместо км/ч +Miles_per_hour Мили в час +Kilometers_per_hour Километры в час +Road_signs_theme_mph Стиль знаков, когда показываются мили в час +theme_Square_US Американские знаки +theme_Round_UK Британские знаки +theme_Round_German Немецкие знаки diff --git a/TLM/TLM/Resources/lang_template.txt b/TLM/TLM/Resources/lang_template.txt index 3e1e48396..ab9738a4b 100644 --- a/TLM/TLM/Resources/lang_template.txt +++ b/TLM/TLM/Resources/lang_template.txt @@ -208,11 +208,11 @@ 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_AddStep +TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddStep TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_Copy TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_Copy -TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction +TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_AddJunction TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_AddJunction TMPE_TUTORIAL_HEAD_TimedTrafficLightsTool_RemoveJunction TMPE_TUTORIAL_BODY_TimedTrafficLightsTool_RemoveJunction @@ -231,4 +231,27 @@ Also_apply_to_left/right_turns_between_one-way_streets Scan_for_known_incompatible_mods_on_startup Ignore_disabled_mods Traffic_Manager_detected_incompatible_mods -Notify_me_if_there_is_an_unexpected_mod_conflict \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict +Unsubscribe +Keybind_toggle_TMPE_main_menu +Keybind_toggle_traffic_lights_tool +Keybind_use_lane_arrow_tool +Keybind_use_lane_connections_tool +Keybind_use_priority_signs_tool +Keybind_use_junction_restrictions_tool +Keybind_use_speed_limits_tool +Keybind_lane_connector_stay_in_lane +Keybind_lane_connector_delete +Keybinds +Keybind_Exit_subtool +Keybind_conflict +Keybind_category_Global +Keybind_category_LaneConnector +Speed_limit_unlimited +Display_speed_limits_mph +Miles_per_hour +Kilometers_per_hour +Road_signs_theme_mph +theme_Square_US +theme_Round_UK +theme_Round_German \ No newline at end of file diff --git a/TLM/TLM/Resources/lang_zh-tw.txt b/TLM/TLM/Resources/lang_zh-tw.txt index ff86907fa..285c5b6b1 100644 --- a/TLM/TLM/Resources/lang_zh-tw.txt +++ b/TLM/TLM/Resources/lang_zh-tw.txt @@ -208,11 +208,11 @@ 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_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_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. @@ -230,5 +230,27 @@ 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 Scan_for_known_incompatible_mods_on_startup Scan for known incompatible mods on startup Ignore_disabled_mods Ignore disabled mods -Traffic_Manager_detected_incompatible_mods Traffic Manager detected incompatible mods -Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict \ No newline at end of file +Traffic_Manager_detected_incompatible_mods detected incompatible mods +Notify_me_if_there_is_an_unexpected_mod_conflict Notify me if there is an unexpected mod conflict +Unsubscribe Unsubscribe +Keybind_toggle_TMPE_main_menu Toggle TM:PE Main Menu +Keybind_toggle_traffic_lights_tool Use 'Toggle Traffic Lights' Tool +Keybind_use_lane_arrow_tool Use 'Lane Arrow' Tool +Keybind_use_lane_connections_tool Use 'Lane Connections' Tool +Keybind_use_priority_signs_tool Use 'Priority Signs' Tool +Keybind_use_junction_restrictions_tool Use 'Junction Restrictions' Tool +Keybind_use_speed_limits_tool Use 'Speed Limits' Tool +Keybind_lane_connector_stay_in_lane Lane connector: Stay in lane +Keybind_lane_connector_delete Clear lane connections +Keybinds Keybinds +Keybind_Exit_subtool Exit tool and close TM:PE Menu +Keybind_conflict The key you've selected is conflicting with another shortcut key. Please choose something else. +Keybind_category_Global Global keys +Keybind_category_LaneConnector Lane Connector Tool keys +Display_speed_limits_mph Display speed limits as MPH instead of km/h +Miles_per_hour Miles per Hour +Kilometers_per_hour Kilometers per Hour +Road_signs_theme_mph Theme for MPH road signs +theme_Square_US US signs +theme_Round_UK British signs +theme_Round_German German signs diff --git a/TLM/TLM/Resources/lang_zh.txt b/TLM/TLM/Resources/lang_zh.txt index fc8cd7f9f..2329fccb7 100644 --- a/TLM/TLM/Resources/lang_zh.txt +++ b/TLM/TLM/Resources/lang_zh.txt @@ -123,7 +123,7 @@ Default_speed_limit 默认道路限速 Unit_system 单元制度 Metric 公制 Imperial 英制 -Use_more_CPU_cores_for_route_calculation_if_available 使用更多的CPU内核来计算路线(如果可用) +Use_more_CPU_cores_for_route_calculation_if_available 使用更多的CPU核心来计算路线 Activated_features 启用功能 Junction_restrictions 路口管制 Prohibit_spawning_of_pocket_cars 禁止袖珍车产生 @@ -150,7 +150,7 @@ Reset_global_configuration 重置所有设定 General 常规 Gameplay 游戏 Overlays 覆盖 -Individual_driving_styles Individual driving styles +Individual_driving_styles 个人驾驶风格 Evacuation_busses_may_ignore_traffic_rules 应急疏散巴士可忽略交通规则 Evacuation_busses_may_only_be_used_to_reach_a_shelter 应急疏散巴士仅可用于到达避难所 Vehicle_behavior 车辆行为 @@ -191,7 +191,7 @@ TMPE_TUTORIAL_BODY_JunctionRestrictionsTool 控制车辆和行人在路口的行 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_BODY_LaneConnectorTool 连接2+条车道来告知车辆可用的车道。\n\n1. 单击一个车道变更点(灰色圆圈)\n2. 单击一个来源车道\n3. 单击目标车道来建立连接\n4. 任意位置单击右键返回来源车道选择\n\n可用热键:\n\n- Delete 或 Backspace:移除所有车道连接\n- Shift + S:(循环)查看所有"stay on lane"模式 TMPE_TUTORIAL_HEAD_ManualTrafficLightsTool 手动控制红绿灯 TMPE_TUTORIAL_BODY_ManualTrafficLightsTool 尝试自定时的红绿灯。\n\n1. 单击一个路口\n2. 使用此工具来控制红绿灯 TMPE_TUTORIAL_HEAD_ParkingRestrictionsTool 路边停车限制 @@ -231,4 +231,26 @@ Also_apply_to_left/right_turns_between_one-way_streets 单行道左右转也适 Scan_for_known_incompatible_mods_on_startup 启动时检测是否存在已知不兼容MOD Ignore_disabled_mods 忽略未启用MOD Traffic_Manager_detected_incompatible_mods 检测到的不兼容MOD -Notify_me_if_there_is_an_unexpected_mod_conflict 检测到不兼容MOD时提示 \ No newline at end of file +Notify_me_if_there_is_an_unexpected_mod_conflict 检测到不兼容MOD时提示 +Unsubscribe 取消订阅 +Keybind_toggle_TMPE_main_menu TM:PE主菜单 +Keybind_toggle_traffic_lights_tool 「红绿灯增删」工具 +Keybind_use_lane_arrow_tool 「变更车道方向」工具 +Keybind_use_lane_connections_tool 「车道连接」工具 +Keybind_use_priority_signs_tool 「优先通行权设置」工具 +Keybind_use_junction_restrictions_tool 「路口管制」工具 +Keybind_use_speed_limits_tool 「道路限速设置」工具 +Keybind_lane_connector_stay_in_lane 车道连接:stay in lane +Keybind_lane_connector_delete 清除车道连接设置 +Keybinds 热键 +Keybind_Exit_subtool 关闭工具与TM:PE主菜单 +Keybind_conflict 所选快捷键有冲突,请重新选择。 +Keybind_category_Global 全局热键 +Keybind_category_LaneConnector 车道连接工具热键 +Display_speed_limits_mph 速度以MPH而不是km/h显示 +Miles_per_hour 英里/小时 +Kilometers_per_hour 千米/小时 +Road_signs_theme_mph MPH路标风格 +theme_Square_US 美国风格 +theme_Round_UK 英国风格 +theme_Round_German 德国风格 diff --git a/TLM/TLM/State/ConfigData/AdvancedVehicleAI.cs b/TLM/TLM/State/ConfigData/AdvancedVehicleAI.cs index cb7f7bdfc..6e961ec6a 100644 --- a/TLM/TLM/State/ConfigData/AdvancedVehicleAI.cs +++ b/TLM/TLM/State/ConfigData/AdvancedVehicleAI.cs @@ -1,58 +1,53 @@ -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; - } -} +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; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/Debug.cs b/TLM/TLM/State/ConfigData/Debug.cs index da63f10fc..9460b0d66 100644 --- a/TLM/TLM/State/ConfigData/Debug.cs +++ b/TLM/TLM/State/ConfigData/Debug.cs @@ -1,54 +1,62 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; -using static TrafficManager.Traffic.Data.ExtCitizenInstance; +namespace TrafficManager.State.ConfigData { + using System; + using Traffic; + using Traffic.Enums; -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 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; - } + 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; + + [Obsolete] + public ExtVehicleType ExtVehicleType = ExtVehicleType.None; + + /// + /// This adds access to the new moved type from the compatible old field + /// + // Property will not be serialized, permit use of obsolete symbol +#pragma warning disable 612 + public API.Traffic.Enums.ExtVehicleType ApiExtVehicleType + => LegacyExtVehicleType.ToNew(ExtVehicleType); +#pragma warning restore 612 + + public ExtPathMode ExtPathMode = ExtPathMode.None; + } #endif -} +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/DynamicLaneSelection.cs b/TLM/TLM/State/ConfigData/DynamicLaneSelection.cs index 8c19815d1..282000192 100644 --- a/TLM/TLM/State/ConfigData/DynamicLaneSelection.cs +++ b/TLM/TLM/State/ConfigData/DynamicLaneSelection.cs @@ -1,156 +1,151 @@ -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; - - // --- - - /* - * max. reserved space: - * low = egoistic - * high = altruistic - */ - - /// - /// Minimum maximum allowed reserved space on previous vehicle lane (for regular drivers) - /// - public float MinMaxReservedSpace = 0f; - - /// - /// Maximum value for Maximum allowed reserved space on previous vehicle lane (for regular drivers) - /// - public float MaxMaxReservedSpace = 5f; - - /// - /// Minimum maximum allowed reserved space on previous vehicle lane (for reckless drivers) - /// - public float MinMaxRecklessReservedSpace = 10f; - - /// - /// Maximum maximum allowed reserved space on previous vehicle lane (for reckless drivers) - /// - public float MaxMaxRecklessReservedSpace = 50f; - - /* - * lane speed randomization interval: - * low = altruistic (driver sees the true lane speed) - * high = egoistic (driver imagines to be in the slowest queue, http://www.bbc.com/future/story/20130827-why-other-queues-move-faster) - */ - - /// - /// Minimum lane speed randomization interval - /// - public float MinLaneSpeedRandInterval = 0f; - - /// - /// Maximum lane speed randomization interval - /// - public float MaxLaneSpeedRandInterval = 25f; - - /* - * max. considered lane changes: - * low = altruistic - * high = egoistic - */ - - /// - /// Maximum number of considered lane changes - /// - public int MinMaxOptLaneChanges = 1; - - /// - /// Maximum number of considered lane changes - /// - public int MaxMaxOptLaneChanges = 3; - - /* - * max. allowed speed difference for unsafe lane changes - * low = altruistic - * high = egoistic - */ - - /// - /// Minimum maximum allowed speed difference for unsafe lane changes (in game units) - /// - public float MinMaxUnsafeSpeedDiff = 0.1f; - - /// - /// Maximum maximum allowed speed difference for unsafe lane changes (in game units) - /// - public float MaxMaxUnsafeSpeedDiff = 1f; - - /* - * min. required speed improvement for safe lane changes - * low = egoistic - * high = altruistic - */ - - /// - /// Minimum minimum required speed improvement for safe lane changes (in km/h) - /// - public float MinMinSafeSpeedImprovement = 5f; - - /// - /// Maximum minimum required speed improvement for safe lane changes (in km/h) - /// - public float MaxMinSafeSpeedImprovement = 30f; - - /* - * min. required traffic flow improvement for safe lane changes - * low = egoistic - * high = altruistic - */ - - /// - /// Minimum minimum required traffic flow improvement for safe lane changes (in %) - /// - public float MinMinSafeTrafficImprovement = 5f; - - /// - /// Maximum minimum required traffic flow improvement for safe lane changes (in %) - /// - public float MaxMinSafeTrafficImprovement = 30f; - } -} +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; + + // --- + + /* + * max. reserved space: + * low = egoistic + * high = altruistic + */ + + /// + /// Minimum maximum allowed reserved space on previous vehicle lane (for regular drivers) + /// + public float MinMaxReservedSpace = 0f; + + /// + /// Maximum value for Maximum allowed reserved space on previous vehicle lane (for regular drivers) + /// + public float MaxMaxReservedSpace = 5f; + + /// + /// Minimum maximum allowed reserved space on previous vehicle lane (for reckless drivers) + /// + public float MinMaxRecklessReservedSpace = 10f; + + /// + /// Maximum maximum allowed reserved space on previous vehicle lane (for reckless drivers) + /// + public float MaxMaxRecklessReservedSpace = 50f; + + /* + * lane speed randomization interval: + * low = altruistic (driver sees the true lane speed) + * high = egoistic (driver imagines to be in the slowest queue, http://www.bbc.com/future/story/20130827-why-other-queues-move-faster) + */ + + /// + /// Minimum lane speed randomization interval + /// + public float MinLaneSpeedRandInterval = 0f; + + /// + /// Maximum lane speed randomization interval + /// + public float MaxLaneSpeedRandInterval = 25f; + + /* + * max. considered lane changes: + * low = altruistic + * high = egoistic + */ + + /// + /// Maximum number of considered lane changes + /// + public int MinMaxOptLaneChanges = 1; + + /// + /// Maximum number of considered lane changes + /// + public int MaxMaxOptLaneChanges = 3; + + /* + * max. allowed speed difference for unsafe lane changes + * low = altruistic + * high = egoistic + */ + + /// + /// Minimum maximum allowed speed difference for unsafe lane changes (in game units) + /// + public float MinMaxUnsafeSpeedDiff = 0.1f; + + /// + /// Maximum maximum allowed speed difference for unsafe lane changes (in game units) + /// + public float MaxMaxUnsafeSpeedDiff = 1f; + + /* + * min. required speed improvement for safe lane changes + * low = egoistic + * high = altruistic + */ + + /// + /// Minimum minimum required speed improvement for safe lane changes (in km/h) + /// + public float MinMinSafeSpeedImprovement = 5f; + + /// + /// Maximum minimum required speed improvement for safe lane changes (in km/h) + /// + public float MaxMinSafeSpeedImprovement = 30f; + + /* + * min. required traffic flow improvement for safe lane changes + * low = egoistic + * high = altruistic + */ + + /// + /// Minimum minimum required traffic flow improvement for safe lane changes (in %) + /// + public float MinMinSafeTrafficImprovement = 5f; + + /// + /// Maximum minimum required traffic flow improvement for safe lane changes (in %) + /// + public float MaxMinSafeTrafficImprovement = 30f; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/Gameplay.cs b/TLM/TLM/State/ConfigData/Gameplay.cs index 48e778e80..0c845c0cf 100644 --- a/TLM/TLM/State/ConfigData/Gameplay.cs +++ b/TLM/TLM/State/ConfigData/Gameplay.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.State.ConfigData { - public class Gameplay { - /// - /// Modulo value for time-varying vehicle behavior randomization - /// - public uint VehicleTimedRandModulo = 10; - } -} +namespace TrafficManager.State.ConfigData { + public class Gameplay { + /// + /// Modulo value for time-varying vehicle behavior randomization + /// + public uint VehicleTimedRandModulo = 10; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/Main.cs b/TLM/TLM/State/ConfigData/Main.cs index ca2ad9d3e..de4152520 100644 --- a/TLM/TLM/State/ConfigData/Main.cs +++ b/TLM/TLM/State/ConfigData/Main.cs @@ -1,54 +1,53 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.UI.MainMenu; +namespace TrafficManager.State.ConfigData { + using System.Collections.Generic; + using System.Linq; + using Traffic.Data; + using 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; + 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; + /// + /// 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]; + /// + /// Already displayed tutorial messages + /// + public string[] DisplayedTutorialMessages = new string[0]; - /// - /// Determines if tutorial messages shall show up - /// - public bool EnableTutorial = true; + /// + /// 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; + /// + /// Determines if the main menu shall be displayed in a tiny format + /// + public bool TinyMainMenu = true; - /// - /// User interface transparency - /// - public byte GuiTransparency = 30; + /// + /// User interface transparency + /// + public byte GuiTransparency = 30; - /// - /// Overlay transparency - /// - public byte OverlayTransparency = 40; + /// + /// Overlay transparency + /// + public byte OverlayTransparency = 40; - /// - /// Extended mod compatibility check - /// - public bool ShowCompatibilityCheckErrorMessage = false; + /// + /// Extended mod compatibility check + /// + public bool ShowCompatibilityCheckErrorMessage = false; /// /// Shows warning dialog if any incompatible mods detected @@ -60,10 +59,23 @@ public class Main { /// public bool IgnoreDisabledMods = true; - public void AddDisplayedTutorialMessage(string messageKey) { - HashSet newMessages = DisplayedTutorialMessages != null ? new HashSet(DisplayedTutorialMessages) : new HashSet(); - newMessages.Add(messageKey); - DisplayedTutorialMessages = newMessages.ToArray(); - } - } -} + /// + /// Prefer Miles per hour instead of Kmph (affects speed limits display + /// but internally Kmph are still used). + /// + public bool DisplaySpeedLimitsMph = false; + + /// + /// Selected theme for road signs when MPH is active. + /// + public MphSignStyle MphRoadSignStyle = MphSignStyle.SquareUS; + + public void AddDisplayedTutorialMessage(string messageKey) { + HashSet newMessages = DisplayedTutorialMessages != null + ? new HashSet(DisplayedTutorialMessages) + : new HashSet(); + newMessages.Add(messageKey); + DisplayedTutorialMessages = newMessages.ToArray(); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/ParkingAI.cs b/TLM/TLM/State/ConfigData/ParkingAI.cs index 094052c87..89ffaba58 100644 --- a/TLM/TLM/State/ConfigData/ParkingAI.cs +++ b/TLM/TLM/State/ConfigData/ParkingAI.cs @@ -1,103 +1,98 @@ -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; - } -} +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; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/PathFinding.cs b/TLM/TLM/State/ConfigData/PathFinding.cs index 181786797..2dd176dfb 100644 --- a/TLM/TLM/State/ConfigData/PathFinding.cs +++ b/TLM/TLM/State/ConfigData/PathFinding.cs @@ -1,53 +1,48 @@ -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; - } -} +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; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/PriorityRules.cs b/TLM/TLM/State/ConfigData/PriorityRules.cs index 3a478c91f..fca762803 100644 --- a/TLM/TLM/State/ConfigData/PriorityRules.cs +++ b/TLM/TLM/State/ConfigData/PriorityRules.cs @@ -1,33 +1,28 @@ -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; -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 junction approach time for priority signs - /// - public float MaxPriorityApproachTime = 15f; + /// + /// maximum waiting time at priority signs + /// + public uint MaxPriorityWaitTime = 100; - /// - /// maximum waiting time at priority signs - /// - public uint MaxPriorityWaitTime = 100; + /// + /// Maximum yield velocity + /// + public float MaxYieldVelocity = 2.5f; - /// - /// Maximum yield velocity - /// - public float MaxYieldVelocity = 2.5f; - - /// - /// Maximum stop velocity - /// - public float MaxStopVelocity = 0.1f; - } -} + /// + /// Maximum stop velocity + /// + public float MaxStopVelocity = 0.1f; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/ConfigData/TimedTrafficLights.cs b/TLM/TLM/State/ConfigData/TimedTrafficLights.cs index 67238fcad..2d5a8bdf3 100644 --- a/TLM/TLM/State/ConfigData/TimedTrafficLights.cs +++ b/TLM/TLM/State/ConfigData/TimedTrafficLights.cs @@ -1,24 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.TrafficLight; +namespace TrafficManager.State.ConfigData { + using API.Traffic.Enums; -namespace TrafficManager.State.ConfigData { - public class TimedTrafficLights { - /// - /// TTL wait/flow calculation mode - /// - public FlowWaitCalcMode FlowWaitCalcMode = FlowWaitCalcMode.Mean; + public class TimedTrafficLights { + /// + /// TTL wait/flow calculation mode + /// + public FlowWaitCalcMode FlowWaitCalcMode = FlowWaitCalcMode.Mean; - /// - /// Default TTL flow-to-wait ratio - /// - public float FlowToWaitRatio = 0.8f; + /// + /// Default TTL flow-to-wait ratio + /// + public float FlowToWaitRatio = 0.8f; - /// - /// TTL smoothing factor for flowing/waiting vehicles - /// - public float SmoothingFactor = 0.1f; - } -} + /// + /// TTL smoothing factor for flowing/waiting vehicles + /// + public float SmoothingFactor = 0.1f; + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Configuration.cs b/TLM/TLM/State/Configuration.cs index d3ca7bfe0..30100795f 100644 --- a/TLM/TLM/State/Configuration.cs +++ b/TLM/TLM/State/Configuration.cs @@ -1,294 +1,314 @@ -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; + using System; + using System.Collections.Generic; + using State; + using Traffic; + using Traffic.Data; + + [Serializable] + public class Configuration { + [Serializable] + public class LaneSpeedLimit { + public uint laneId; + public ushort speedLimit; + + public LaneSpeedLimit(uint laneId, float speedLimit) { + this.laneId = laneId; + this.speedLimit = (ushort)(speedLimit * SpeedLimit.SPEED_TO_KMPH); + } + } + + [Serializable] + public class LaneVehicleTypes { + public uint laneId; + + /// + /// Do not use this, for save compatibility only. + /// + [Obsolete] + public Traffic.ExtVehicleType vehicleTypes; + + /// + /// Use this to access new ExtVehicleType, from TMPE.API + /// + // Property will not be serialized, permit use of obsolete symbol +#pragma warning disable 612 + public API.Traffic.Enums.ExtVehicleType ApiVehicleTypes + => LegacyExtVehicleType.ToNew(vehicleTypes); +#pragma warning restore 612 + + 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; + + /// + /// This is using old type for save compatibility. + /// Use LegacyExtVehicleType helper class to convert between old/new + /// + [Obsolete] + 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() { - // TODO v1.11.0: check this - bool uturnIsDefault = uturnAllowed == null || (bool)uturnAllowed == Options.allowUTurns; + public bool? farTurnOnRedAllowed = null; + public bool? straightLaneChangingAllowed = null; + public bool? enterWhenBlockedAllowed = null; + public bool? pedestrianCrossingAllowed = null; + + public bool IsDefault() { + // TODO v1.11.0: check this + 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(); - } -} + 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(); + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Flags.cs b/TLM/TLM/State/Flags.cs index 87a0b3e3e..2811a2595 100644 --- a/TLM/TLM/State/Flags.cs +++ b/TLM/TLM/State/Flags.cs @@ -1,972 +1,977 @@ #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.Traffic.Enums; -using TrafficManager.Util; - namespace TrafficManager.State { - [Obsolete] - public class Flags { - 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; + using System; + using System.Collections.Generic; + using System.Threading; + using API.Traffic.Enums; + using ColossalFramework; + using CSUtil.Commons; + using Manager.Impl; + using Traffic.Enums; + + [Obsolete] + public class Flags { + 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 float?[][] 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; - } + 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) { + 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 { + 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) { + 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) { + 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."); + 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; - } + 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}"); + 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) { + 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]}"); + 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; + if (laneConnections[laneId] == null) + return; - bool laneValid = CheckLane(laneId); - bool clearBothSides = startNode == null || !laneValid; + bool laneValid = CheckLane(laneId); + bool clearBothSides = startNode == null || !laneValid; #if DEBUGCONN - if (debug) - Log._Debug($"Flags.RemoveLaneConnections({laneId}, {startNode}): laneValid={laneValid}, clearBothSides={clearBothSides}"); + 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); + 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, float? speedLimit) { + if (!CheckLane(laneId)) { + return; + } + + var segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment; + + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + var 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, float 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; + } + var segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info; + if (laneIndex >= segmentInfo.m_lanes.Length) { + return; + } + + // find the lane id + var laneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes; + for (var 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, float? 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; + } + var 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 (speedLimit == null) { + laneSpeedLimit.Remove(laneId); + + if (laneSpeedLimitArray[segmentId] == null) { + return; + } + if (laneIndex >= laneSpeedLimitArray[segmentId].Length) { + return; + } + laneSpeedLimitArray[segmentId][laneIndex] = null; + } else { + laneSpeedLimit[laneId] = speedLimit.Value; + + // 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 float?[segmentInfo.m_lanes.Length]; + } else if (laneSpeedLimitArray[segmentId].Length < segmentInfo.m_lanes.Length) { + var oldArray = laneSpeedLimitArray[segmentId]; + laneSpeedLimitArray[segmentId] = new float?[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, API.Traffic.Enums.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; + // 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; - } + laneAllowedVehicleTypesArray[segmentId] = null; + } - public static void resetSegmentArrowFlags(ushort segmentId) { - if (segmentId <= 0) - return; + 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; + 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) { + 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; + laneArrowFlags[curLaneId] = null; - curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; - ++laneIndex; - } - } + curLaneId = netManager.m_lanes.m_buffer[curLaneId].m_nextLane; + ++laneIndex; + } + } - public static bool setLaneArrowFlags(uint laneId, LaneArrows flags, bool overrideHighwayArrows=false) { + 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 (!mayHaveLaneArrows(laneId)) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): lane must not have lane arrows"); #endif - removeLaneArrowFlags(laneId); - return false; - } + removeLaneArrowFlags(laneId); + return false; + } - if (!overrideHighwayArrows && highwayLaneArrowFlags[laneId] != null) { + 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 - } + return false; // disallow custom lane arrows in highway rule mode + } - if (overrideHighwayArrows) { + if (overrideHighwayArrows) { #if DEBUGFLAGS Log._Debug($"Flags.setLaneArrowFlags({laneId}, {flags}, {overrideHighwayArrows}): overriding highway arrows"); #endif - highwayLaneArrowFlags[laneId] = null; - } + 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; + 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 SetLaneArrowUnableReason res) { - if (!mayHaveLaneArrows(laneId)) { - removeLaneArrowFlags(laneId); - res = SetLaneArrowUnableReason.Invalid; - return false; - } - - if (highwayLaneArrowFlags[laneId] != null) { - res = SetLaneArrowUnableReason.HighwayArrows; - return false; // disallow custom lane arrows in highway rule mode - } - - if (LaneConnectionManager.Instance.HasConnections(laneId, startNode)) { // TODO refactor - res = SetLaneArrowUnableReason.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 = SetLaneArrowUnableReason.Success; - return true; - } else { - res = SetLaneArrowUnableReason.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) { + applyLaneArrowFlags(laneId, false); + } + + public static bool toggleLaneArrowFlags(uint laneId, bool startNode, LaneArrows flags, out SetLaneArrowUnableReason res) { + if (!mayHaveLaneArrows(laneId)) { + removeLaneArrowFlags(laneId); + res = SetLaneArrowUnableReason.Invalid; + return false; + } + + if (highwayLaneArrowFlags[laneId] != null) { + res = SetLaneArrowUnableReason.HighwayArrows; + return false; // disallow custom lane arrows in highway rule mode + } + + if (LaneConnectionManager.Instance.HasConnections(laneId, startNode)) { // TODO refactor + res = SetLaneArrowUnableReason.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 = SetLaneArrowUnableReason.Success; + return true; + } else { + res = SetLaneArrowUnableReason.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 float? getLaneSpeedLimit(uint laneId) { + try { + Monitor.Enter(laneSpeedLimitLock); + + float 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 (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 (laneId <= 0) + return true; - if (check && !mayHaveLaneArrows(laneId)) { - removeLaneArrowFlags(laneId); - return false; - } + 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; + 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 (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; - } + 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)) { + 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() { - - } - } -} + 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 float?[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() { + + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/GlobalConfig.cs b/TLM/TLM/State/GlobalConfig.cs index 10e85dc87..1efa3150c 100644 --- a/TLM/TLM/State/GlobalConfig.cs +++ b/TLM/TLM/State/GlobalConfig.cs @@ -1,237 +1,227 @@ -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; - - 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() { - } - - 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; +namespace TrafficManager.State { + using System; + using System.IO; + using System.Xml.Serialization; + using CSUtil.Commons; + using State.ConfigData; + using Util; + + [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; + + public static GlobalConfig Instance { + get => 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() { + } + + 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(); + public Debug Debug = new Debug(); #endif - public AdvancedVehicleAI AdvancedVehicleAI = new AdvancedVehicleAI(); + public AdvancedVehicleAI AdvancedVehicleAI = new AdvancedVehicleAI(); - public DynamicLaneSelection DynamicLaneSelection = new DynamicLaneSelection(); + public DynamicLaneSelection DynamicLaneSelection = new DynamicLaneSelection(); - public Gameplay Gameplay = new Gameplay(); + public Gameplay Gameplay = new Gameplay(); - public Main Main = new Main(); + public Main Main = new Main(); - public ParkingAI ParkingAI = new ParkingAI(); + public ParkingAI ParkingAI = new ParkingAI(); - public PathFinding PathFinding = new PathFinding(); + public PathFinding PathFinding = new PathFinding(); - public PriorityRules PriorityRules = new PriorityRules(); + public PriorityRules PriorityRules = new PriorityRules(); - public TimedTrafficLights TimedTrafficLights = new TimedTrafficLights(); + public TimedTrafficLights TimedTrafficLights = new TimedTrafficLights(); - internal static void WriteConfig() { - ModifiedTime = WriteConfig(Instance); - } + 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; + 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.MainMenuX = oldConfig.Main.MainMenuX; + conf.Main.MainMenuY = oldConfig.Main.MainMenuY; - conf.Main.MainMenuButtonPosLocked = oldConfig.Main.MainMenuButtonPosLocked; - conf.Main.MainMenuPosLocked = oldConfig.Main.MainMenuPosLocked; + 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.GuiTransparency = oldConfig.Main.GuiTransparency; + conf.Main.OverlayTransparency = oldConfig.Main.OverlayTransparency; - conf.Main.TinyMainMenu = oldConfig.Main.TinyMainMenu; + conf.Main.TinyMainMenu = oldConfig.Main.TinyMainMenu; - conf.Main.EnableTutorial = oldConfig.Main.EnableTutorial; - conf.Main.DisplayedTutorialMessages = oldConfig.Main.DisplayedTutorialMessages; - } - modifiedTime = WriteConfig(conf); - return conf; - } + 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()}"); - } + 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; - } - } + 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); + 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 + 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] + && !conf.Debug.Switches[10] #endif - ) { - Constants.ManagerFactory.RoutingManager.RequestFullRecalculation(); - } + ) { + Constants.ManagerFactory.RoutingManager.RequestFullRecalculation(); + } #if DEBUG - if (conf.Debug == null) { - conf.Debug = new 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.Gameplay == null) { - conf.Gameplay = new Gameplay(); - } - - 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 (conf.AdvancedVehicleAI == null) { + conf.AdvancedVehicleAI = new AdvancedVehicleAI(); + } + + if (conf.DynamicLaneSelection == null) { + conf.DynamicLaneSelection = new DynamicLaneSelection(); + } + + if (conf.Gameplay == null) { + conf.Gameplay = new Gameplay(); + } + + 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."); + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/Keybind.cs b/TLM/TLM/State/Keybinds/Keybind.cs new file mode 100644 index 000000000..dbf494c7c --- /dev/null +++ b/TLM/TLM/State/Keybinds/Keybind.cs @@ -0,0 +1,78 @@ +namespace TrafficManager.State.Keybinds { + using ColossalFramework; + using ColossalFramework.UI; + using UnityEngine; + + /// + /// General input key handling functions, checking for empty, converting to string etc. + /// + public class Keybind { + public static bool IsEmpty(InputKey sample) { + var noKey = SavedInputKey.Encode(KeyCode.None, false, false, false); + return sample == SavedInputKey.Empty || sample == noKey; + } + + /// + /// Returns shortcut as a string in user's language. Modify for special handling. + /// + /// The key + /// The shortcut, example: "Ctrl + Alt + H" + public static string ToLocalizedString(SavedInputKey k) { + return k.ToLocalizedString("KEYNAME"); + } + + public static bool IsModifierKey(KeyCode code) { + return code == KeyCode.LeftControl || code == KeyCode.RightControl || + code == KeyCode.LeftShift || code == KeyCode.RightShift || + code == KeyCode.LeftAlt || code == KeyCode.RightAlt; + } + + public static bool IsControlDown() { + return Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl); + } + + public static bool IsShiftDown() { + return Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); + } + + public static bool IsAltDown() { + return Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt); + } + + public static bool IsUnbindableMouseButton(UIMouseButton code) { + return code == UIMouseButton.Left || code == UIMouseButton.Right; + } + + public static KeyCode ButtonToKeycode(UIMouseButton button) { + if (button == UIMouseButton.Left) { + return KeyCode.Mouse0; + } + + if (button == UIMouseButton.Right) { + return KeyCode.Mouse1; + } + + if (button == UIMouseButton.Middle) { + return KeyCode.Mouse2; + } + + if (button == UIMouseButton.Special0) { + return KeyCode.Mouse3; + } + + if (button == UIMouseButton.Special1) { + return KeyCode.Mouse4; + } + + if (button == UIMouseButton.Special2) { + return KeyCode.Mouse5; + } + + if (button == UIMouseButton.Special3) { + return KeyCode.Mouse6; + } + + return KeyCode.None; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeybindSetting.cs b/TLM/TLM/State/Keybinds/KeybindSetting.cs new file mode 100644 index 000000000..e25bbae84 --- /dev/null +++ b/TLM/TLM/State/Keybinds/KeybindSetting.cs @@ -0,0 +1,113 @@ +namespace TrafficManager.State.Keybinds { + using ColossalFramework; + using JetBrains.Annotations; + using UnityEngine; + + /// + /// Contains one or two SavedInputKeys, and event handler when the key is changed. + /// + public class KeybindSetting { + /// + /// Used by the GUI to tell the button event handler which key is being edited + /// + public struct Editable { + public KeybindSetting Target; + public SavedInputKey TargetKey; + } + + /// + /// Groups input keys by categories, also helps to know the usage for conflict search. + /// + public string Category; + + /// + /// The key itself, bound to a config file value + /// + public SavedInputKey Key { get; } + + /// + /// A second key, which can possibly be used or kept null + /// + [CanBeNull] + public SavedInputKey AlternateKey { get; } + + private OnKeyChangedHandler onKeyChanged_; + + public delegate void OnKeyChangedHandler(); + + public KeybindSetting(string cat, + string configFileKey, + InputKey? defaultKey1 = null) { + Category = cat; + Key = new SavedInputKey( + configFileKey, + KeybindSettingsBase.KEYBOARD_SHORTCUTS_FILENAME, + defaultKey1 ?? SavedInputKey.Empty, + true); + } + + public void OnKeyChanged(OnKeyChangedHandler onChanged) { + onKeyChanged_ = onChanged; + } + + public void NotifyKeyChanged() { + onKeyChanged_?.Invoke(); + } + + public KeybindSetting(string cat, + string configFileKey, + InputKey? defaultKey1, + InputKey? defaultKey2) { + Category = cat; + Key = new SavedInputKey( + configFileKey, + KeybindSettingsBase.KEYBOARD_SHORTCUTS_FILENAME, + defaultKey1 ?? SavedInputKey.Empty, + true); + AlternateKey = new SavedInputKey( + configFileKey + "_Alternate", + KeybindSettingsBase.KEYBOARD_SHORTCUTS_FILENAME, + defaultKey2 ?? SavedInputKey.Empty, + true); + } + + /// + /// Produce a keybind tooltip text, or two if alternate key is set. Prefixed if not empty. + /// + /// Prefix will be added if any key is not empty + /// String tooltip with the key shortcut or two + public string ToLocalizedString(string prefix = "") { + var result = default(string); + if (!Keybind.IsEmpty(Key)) { + result += prefix + Keybind.ToLocalizedString(Key); + } + + if (AlternateKey == null || Keybind.IsEmpty(AlternateKey)) { + return result; + } + + if (result.IsNullOrWhiteSpace()) { + result += prefix; + } else { + result += " | "; + } + + return result + Keybind.ToLocalizedString(AlternateKey); + } + + public bool IsPressed(Event e) { + return Key.IsPressed(e) + || (AlternateKey != null && AlternateKey.IsPressed(e)); + } + + /// + /// Check whether main or alt key are the same as k + /// + /// Find key + /// We have the key + public bool HasKey(InputKey k) { + return Key.value == k + || (AlternateKey != null && AlternateKey.value == k); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs b/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs new file mode 100644 index 000000000..cfc27661c --- /dev/null +++ b/TLM/TLM/State/Keybinds/KeybindSettingsBase.cs @@ -0,0 +1,174 @@ +// Based on keymapping module from CS-MoveIt mod +// Thanks to https://github.com/Quboid/CS-MoveIt +namespace TrafficManager.State.Keybinds { + using System; + using ColossalFramework; + using ColossalFramework.Globalization; + using ColossalFramework.UI; + using CSUtil.Commons; + using UnityEngine; + + public class KeybindSettingsBase : UICustomControl { + protected static readonly string KeyBindingTemplate = "KeyBindingTemplate"; + public const string KEYBOARD_SHORTCUTS_FILENAME = "TMPE_Keybinds"; + + // NOTE: Do not change fields to properties, otherwise also fix the conflict + // detection code in KeybindUI.FindConflict(inTmpe) which expects these below + // to be fields of type KeybindSetting. + + /// + /// This input key can not be changed and is not checked, instead it is display only + /// + protected static KeybindSetting ToolCancelViewOnly = new KeybindSetting( + "Global", + "Key_ExitSubtool", + SavedInputKey.Encode(KeyCode.Escape, false, false, false)); + + public static KeybindSetting ToggleMainMenu = new KeybindSetting( + "Global", + "Key_ToggleTMPEMainMenu", + SavedInputKey.Encode(KeyCode.Semicolon, false, true, false)); + + public static KeybindSetting ToggleTrafficLightTool = + new KeybindSetting("Global", "Key_ToggleTrafficLightTool"); + + public static KeybindSetting LaneArrowTool = + new KeybindSetting("Global", "Key_LaneArrowTool"); + + public static KeybindSetting LaneConnectionsTool = + new KeybindSetting("Global", "Key_LaneConnectionsTool"); + + public static KeybindSetting PrioritySignsTool = + new KeybindSetting("Global", "Key_PrioritySignsTool"); + + public static KeybindSetting JunctionRestrictionsTool = + new KeybindSetting("Global", "Key_JunctionRestrictionsTool"); + + public static KeybindSetting SpeedLimitsTool = + new KeybindSetting("Global", "Key_SpeedLimitsTool"); + + public static KeybindSetting LaneConnectorStayInLane = new KeybindSetting( + "LaneConnector", + "Key_LaneConnector_StayInLane", + SavedInputKey.Encode(KeyCode.S, false, true, false)); + + public static KeybindSetting LaneConnectorDelete = new KeybindSetting( + "LaneConnector", + "Key_LaneConnector_Delete", + SavedInputKey.Encode(KeyCode.Delete, false, false, false), + SavedInputKey.Encode(KeyCode.Backspace, false, false, false)); + + protected KeybindUI keybindUi_ = new KeybindUI(); + + /// + /// Counter to produce alternating UI row colors (dark and light). + /// + private int uiRowCount_; + + protected static void TryCreateConfig() { + try { + // Creating setting file + if (GameSettings.FindSettingsFileByName(KEYBOARD_SHORTCUTS_FILENAME) == null) { + GameSettings.AddSettingsFile( + new SettingsFile {fileName = KEYBOARD_SHORTCUTS_FILENAME}); + } + } + catch (Exception) { + Log._Debug("Could not load/create the keyboard shortcuts file."); + } + } + + /// + /// Creates a row in the current panel with the label and the button + /// which will prompt user to press a new key. + /// + /// Localized label + /// The setting to edit + protected void AddKeybindRowUI(string label, KeybindSetting keybind) { + var settingsRow = keybindUi_.CreateRowPanel(); + if (uiRowCount_++ % 2 == 1) { + settingsRow.backgroundSprite = null; + } + + keybindUi_.CreateLabel(settingsRow, label, 0.6f); + keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.Key, 0.3f); + } + + /// + /// Add a second key under the first key, using same row background as the + /// previous key editor. + /// + /// + /// Whether main key binding is editable or readonly + /// Whether alt key binding is editable or readonly + protected void AddAlternateKeybindUI(string title, KeybindSetting keybind, + bool editable1, bool editable2) { + var settingsRow = keybindUi_.CreateRowPanel(); + if (uiRowCount_ % 2 == 1) { + // color the panel but do not increment uiRowCount + settingsRow.backgroundSprite = null; + } + + keybindUi_.CreateLabel(settingsRow, title, 0.45f); + if (editable1) { + keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.Key, 0.2f); + } else { + keybindUi_.CreateKeybindText(settingsRow, keybind.Key, 0.25f); + } + + if (editable2) { + keybindUi_.CreateKeybindButton(settingsRow, keybind, keybind.AlternateKey, 0.2f); + } else { + keybindUi_.CreateKeybindText(settingsRow, keybind.AlternateKey, 0.25f); + } + } + + /// + /// Creates a line of key mapping but does not allow changing it. + /// Used to improve awareness. + /// + /// Localized label + /// The setting to edit + protected void AddReadOnlyKeybind(string label, KeybindSetting keybind) { + var settingsRow = keybindUi_.CreateRowPanel(); + if (uiRowCount_++ % 2 == 1) { + settingsRow.backgroundSprite = null; + } + + keybindUi_.CreateLabel(settingsRow, label, 0.6f); + keybindUi_.CreateKeybindText(settingsRow, keybind.Key, 0.3f); + } + + protected void OnEnable() { + LocaleManager.eventLocaleChanged += OnLocaleChanged; + } + + protected void OnDisable() { + LocaleManager.eventLocaleChanged -= OnLocaleChanged; + } + + private void OnLocaleChanged() { + // RefreshBindableInputs(); + } + +// /// +// /// Called on locale change, resets keys to the new language +// /// +// private void RefreshBindableInputs() { +// foreach (var current in component.GetComponentsInChildren()) { +// var uITextComponent = current.Find("Binding"); +// if (uITextComponent != null) { +// var savedInputKey = uITextComponent.objectUserData as SavedInputKey; +// if (savedInputKey != null) { +// uITextComponent.text = Keybind.Str(savedInputKey); +// } +// } +// +// var uILabel = current.Find("Name"); +// if (uILabel != null) { +// uILabel.text = Locale.Get("KEYMAPPING", uILabel.stringUserData); +// } +// } +// } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs new file mode 100644 index 000000000..ee3bbeeb0 --- /dev/null +++ b/TLM/TLM/State/Keybinds/KeybindSettingsPage.cs @@ -0,0 +1,61 @@ +namespace TrafficManager.State.Keybinds { + using UI; + + public class KeybindSettingsPage : KeybindSettingsBase { + private void Awake() { + TryCreateConfig(); + + keybindUi_.BeginForm(component); + + // Section: Global + keybindUi_.AddGroup(Translation.GetString("Keybind_category_Global"), + CreateUI_Global); + + // Section: Lane Connector Tool + keybindUi_.AddGroup(Translation.GetString("Keybind_category_LaneConnector"), + CreateUI_LaneConnector); + } + + /// + /// Fill Global keybinds section + /// + private void CreateUI_Global() { + AddReadOnlyKeybind(Translation.GetString("Keybind_Exit_subtool"), + ToolCancelViewOnly); + + AddKeybindRowUI(Translation.GetString("Keybind_toggle_TMPE_main_menu"), + ToggleMainMenu); + ToggleMainMenu.OnKeyChanged(() => { + if (LoadingExtension.BaseUI != null && + LoadingExtension.BaseUI.MainMenuButton != null) { + LoadingExtension.BaseUI.MainMenuButton.UpdateTooltip(); + } + }); + + AddKeybindRowUI(Translation.GetString("Keybind_toggle_traffic_lights_tool"), + ToggleTrafficLightTool); + AddKeybindRowUI(Translation.GetString("Keybind_use_lane_arrow_tool"), + LaneArrowTool); + AddKeybindRowUI(Translation.GetString("Keybind_use_lane_connections_tool"), + LaneConnectionsTool); + AddKeybindRowUI(Translation.GetString("Keybind_use_priority_signs_tool"), + PrioritySignsTool); + AddKeybindRowUI(Translation.GetString("Keybind_use_junction_restrictions_tool"), + JunctionRestrictionsTool); + AddKeybindRowUI(Translation.GetString("Keybind_use_speed_limits_tool"), + SpeedLimitsTool); + } + + /// + /// Fill Lane Connector keybinds section + /// + private void CreateUI_LaneConnector() { + AddKeybindRowUI(Translation.GetString("Keybind_lane_connector_stay_in_lane"), + LaneConnectorStayInLane); + + // First key binding is readonly (editable1=false) + AddAlternateKeybindUI(Translation.GetString("Keybind_lane_connector_delete"), + LaneConnectorDelete, false, true); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Keybinds/KeybindUI.cs b/TLM/TLM/State/Keybinds/KeybindUI.cs new file mode 100644 index 000000000..0b771c76c --- /dev/null +++ b/TLM/TLM/State/Keybinds/KeybindUI.cs @@ -0,0 +1,416 @@ +namespace TrafficManager.State.Keybinds { + using System; + using System.Linq; + using System.Reflection; + using System.Text.RegularExpressions; + using ColossalFramework; + using ColossalFramework.UI; + using CSUtil.Commons; + using UI; + using UnityEngine; + + /// + /// Helper for creating keyboard bindings Settings page. + /// + public class KeybindUI { + private const float ROW_WIDTH = 744f - 15f; + private const float ROW_HEIGHT = 34f; + + private KeybindSetting.Editable? currentlyEditedBinding_; + + /// + /// Scrollable panel, first created on Unity Awake call + /// + private UIComponent scrollPanel_; + + /// + /// Group panel with text title for adding controls in it + /// + private UIComponent currentGroup_; + + /// + /// Creates a row for keyboard bindings editor. The row will contain a text + /// label, a button to edit the key, and X button to delete the key. + /// + /// The component where the UI is attached + /// The new scrollable panel + public static UIComponent CreateScrollablePanel(UIComponent root) { + var scrollablePanel = root.AddUIComponent(); + scrollablePanel.backgroundSprite = string.Empty; + scrollablePanel.size = root.size; + scrollablePanel.relativePosition = Vector3.zero; + + scrollablePanel.clipChildren = true; + scrollablePanel.autoLayoutStart = LayoutStart.TopLeft; + scrollablePanel.autoLayoutDirection = LayoutDirection.Vertical; + scrollablePanel.autoLayout = true; + + scrollablePanel.FitTo(root); + scrollablePanel.scrollWheelDirection = UIOrientation.Vertical; + scrollablePanel.builtinKeyNavigation = true; + + var verticalScroll = root.AddUIComponent(); + verticalScroll.stepSize = 1; + verticalScroll.relativePosition = new Vector2(root.width - 15, 0); + verticalScroll.orientation = UIOrientation.Vertical; + verticalScroll.size = new Vector2(20, root.height); + verticalScroll.incrementAmount = 25; + verticalScroll.scrollEasingType = EasingType.BackEaseOut; + + scrollablePanel.verticalScrollbar = verticalScroll; + + var track = verticalScroll.AddUIComponent(); + track.spriteName = "ScrollbarTrack"; + track.relativePosition = Vector3.zero; + track.size = new Vector2(16, 320); + + verticalScroll.trackObject = track; + + var thumb = track.AddUIComponent(); + thumb.spriteName = "ScrollbarThumb"; + thumb.autoSize = true; + thumb.relativePosition = Vector3.zero; + verticalScroll.thumbObject = thumb; + + return scrollablePanel; + } + + public void BeginForm(UIComponent component) { + scrollPanel_ = CreateScrollablePanel(component); + } + + /// + /// Create an empty row of ROW_HEIGHT pixels, with left-to-right layout + /// + /// The row panel + public UIPanel CreateRowPanel() { + var rowPanel = currentGroup_.AddUIComponent(); + rowPanel.size = new Vector2(ROW_WIDTH, ROW_HEIGHT); + rowPanel.autoLayoutStart = LayoutStart.TopLeft; + rowPanel.autoLayoutDirection = LayoutDirection.Horizontal; + rowPanel.autoLayout = true; + + return rowPanel; + } + + /// + /// Create a box with title + /// + /// Title + private void BeginGroup(string text) { + const string K_GROUP_TEMPLATE = "OptionsGroupTemplate"; + var groupPanel = scrollPanel_.AttachUIComponent( + UITemplateManager.GetAsGameObject(K_GROUP_TEMPLATE)) as UIPanel; + groupPanel.autoLayoutStart = LayoutStart.TopLeft; + groupPanel.autoLayoutDirection = LayoutDirection.Vertical; + groupPanel.autoLayout = true; + + groupPanel.Find("Label").text = text; + + currentGroup_ = groupPanel.Find("Content"); + } + + /// + /// Close the group and expand the scroll panel to include it + /// + private void EndGroup() { + currentGroup_ = null; + } + + public UILabel CreateLabel(UIPanel parent, string text, float widthFraction) { + var label = parent.AddUIComponent(); + label.wordWrap = true; + label.autoSize = false; + label.size = new Vector2(ROW_WIDTH * widthFraction, ROW_HEIGHT); + label.text = text; + label.verticalAlignment = UIVerticalAlignment.Middle; + label.textAlignment = UIHorizontalAlignment.Left; + return label; + } + + public void CreateKeybindButton(UIPanel parent, KeybindSetting setting, SavedInputKey editKey, + float widthFraction) { + var btn = parent.AddUIComponent(); + btn.size = new Vector2(ROW_WIDTH * widthFraction, ROW_HEIGHT); + btn.text = Keybind.ToLocalizedString(editKey); + btn.hoveredTextColor = new Color32(128, 128, 255, 255); // darker blue + btn.pressedTextColor = new Color32(192, 192, 255, 255); // lighter blue + btn.normalBgSprite = "ButtonMenu"; + + btn.eventKeyDown += OnBindingKeyDown; + btn.eventMouseDown += OnBindingMouseDown; + btn.objectUserData + = new KeybindSetting.Editable {Target = setting, TargetKey = editKey}; + + AddXButton(parent, editKey, btn); + } + + /// + /// Add X button to the right of another button + /// + /// The panel to host the new button + /// The key to be cleared on click + /// Align X button to the right of this + private static void AddXButton(UIPanel parent, SavedInputKey editKey, UIButton alignTo) { + var btnX = parent.AddUIComponent(); + btnX.autoSize = false; + btnX.size = new Vector2(ROW_HEIGHT, ROW_HEIGHT); + btnX.normalBgSprite = "buttonclose"; + btnX.hoveredBgSprite = "buttonclosehover"; + btnX.pressedBgSprite = "buttonclosepressed"; + btnX.eventClicked += (component, eventParam) => { + editKey.value = SavedInputKey.Empty; + alignTo.text = Keybind.ToLocalizedString(editKey); + }; + } + + /// + /// Create read-only display of a key binding + /// + /// The panel to host it + /// The key to display + public void CreateKeybindText(UIPanel parent, SavedInputKey showKey, float widthFraction) { + var label = parent.AddUIComponent(); + label.autoSize = false; + label.size = new Vector2(ROW_WIDTH * widthFraction, ROW_HEIGHT); + label.text = Keybind.ToLocalizedString(showKey); + label.verticalAlignment = UIVerticalAlignment.Middle; + label.textAlignment = UIHorizontalAlignment.Center; + label.textColor = new Color32(128, 128, 128, 255); // grey + } + + /// + /// Performs group creation sequence: BeginGroup, add keybinds UI rows, EndGroup + /// + /// Translated title + /// Function which adds keybind rows + public void AddGroup(string title, Action code) { + BeginGroup(title); + code.Invoke(); + EndGroup(); + } + + private void OnBindingKeyDown(UIComponent comp, UIKeyEventParameter evParam) { + try { + // This will only work if the user clicked the modify button + // otherwise no effect + if (!currentlyEditedBinding_.HasValue || Keybind.IsModifierKey(evParam.keycode)) { + return; + } + + evParam.Use(); // Consume the event + var editedBinding = currentlyEditedBinding_; // will be nulled by closing modal + UIView.PopModal(); + + var keybindButton = evParam.source as UIButton; + var inputKey = SavedInputKey.Encode(evParam.keycode, evParam.control, evParam.shift, evParam.alt); + var editable = (KeybindSetting.Editable) evParam.source.objectUserData; + var category = editable.Target.Category; + + if (evParam.keycode != KeyCode.Escape) { + // Check the key conflict + var maybeConflict = FindConflict(editedBinding.Value, inputKey, category); + if (maybeConflict != string.Empty) { + var message = Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict; + Log.Info($"Keybind conflict: {message}"); + UIView.library + .ShowModal("ExceptionPanel") + .SetMessage("Key Conflict", message, false); + } else { + editedBinding.Value.TargetKey.value = inputKey; + editedBinding.Value.Target.NotifyKeyChanged(); + } + } + + keybindButton.text = Keybind.ToLocalizedString(editedBinding.Value.TargetKey); + currentlyEditedBinding_ = null; + } catch (Exception e) {Log.Error($"{e}");} + } + + private void OnBindingMouseDown(UIComponent comp, UIMouseEventParameter evParam) { + var editable = (KeybindSetting.Editable) evParam.source.objectUserData; + var keybindButton = evParam.source as UIButton; + + // This will only work if the user is not in the process of changing the shortcut + if (currentlyEditedBinding_ == null) { + evParam.Use(); + StartKeybindEditMode(editable, keybindButton); + } else if (!Keybind.IsUnbindableMouseButton(evParam.buttons)) { + // This will work if the user clicks while the shortcut change is in progress + evParam.Use(); + var editedBinding = currentlyEditedBinding_; // will be nulled by closing modal + UIView.PopModal(); + + var inputKey = SavedInputKey.Encode(Keybind.ButtonToKeycode(evParam.buttons), + Keybind.IsControlDown(), + Keybind.IsShiftDown(), + Keybind.IsAltDown()); + var category = editable.Target.Category; + var maybeConflict = FindConflict(editedBinding.Value, inputKey, category); + if (maybeConflict != string.Empty) { + var message = Translation.GetString("Keybind_conflict") + "\n\n" + maybeConflict; + Log.Info($"Keybind conflict: {message}"); + UIView.library + .ShowModal("ExceptionPanel") + .SetMessage("Key Conflict", message, false); + } else { + editedBinding.Value.TargetKey.value = inputKey; + editedBinding.Value.Target.NotifyKeyChanged(); + } + + keybindButton.buttonsMask = UIMouseButton.Left; + keybindButton.text = Keybind.ToLocalizedString(editedBinding.Value.TargetKey); + currentlyEditedBinding_ = null; + } + } + + /// + /// Set the button text to welcoming message. Push the button as modal blocking + /// everything else on screen and capturing the input. + /// + /// The keysetting and inputkey inside it, to edit + /// The button to become modal + private void StartKeybindEditMode(KeybindSetting.Editable editable, UIButton keybindButton) { + currentlyEditedBinding_ = editable; + + keybindButton.buttonsMask = + UIMouseButton.Left | UIMouseButton.Right | UIMouseButton.Middle | + UIMouseButton.Special0 | UIMouseButton.Special1 | UIMouseButton.Special2 | + UIMouseButton.Special3; + keybindButton.text = "Press key (or Esc)"; + keybindButton.Focus(); + UIView.PushModal(keybindButton, OnKeybindModalPopped); + } + + /// + /// Called by the UIView when modal was popped without us knowing + /// + /// The button which temporarily was modal + private void OnKeybindModalPopped(UIComponent component) { + var keybindButton = component as UIButton; + if (keybindButton != null && currentlyEditedBinding_ != null) { + keybindButton.text = Keybind.ToLocalizedString(currentlyEditedBinding_.Value.TargetKey); + currentlyEditedBinding_ = null; + } + } + + /// + /// For an inputkey, try find where possibly it is already used. + /// This covers game Settings class, and self (OptionsKeymapping class). + /// + /// Key to search for the conflicts + /// Check the same category keys if possible + /// Empty string for no conflict, or the conflicting key name + private string FindConflict(KeybindSetting.Editable editedKeybind, + InputKey sample, + string sampleCategory) { + if (Keybind.IsEmpty(sample)) { + // empty key never conflicts + return string.Empty; + } + + var inGameSettings = FindConflictInGameSettings(sample); + if (!string.IsNullOrEmpty(inGameSettings)) { + return inGameSettings; + } + + // Saves and null 'self.editingBinding_' to allow rebinding the key to itself. + var saveEditingBinding = editedKeybind.TargetKey.value; + editedKeybind.TargetKey.value = SavedInputKey.Empty; + + // Check in TMPE settings + var tmpeSettingsType = typeof(KeybindSettingsBase); + var tmpeFields = tmpeSettingsType.GetFields(BindingFlags.Static | BindingFlags.Public); + + var inTmpe = FindConflictInTmpe(sample, sampleCategory, tmpeFields); + editedKeybind.TargetKey.value = saveEditingBinding; + return inTmpe; + } + + private static string FindConflictInGameSettings(InputKey sample) { + var fieldList = typeof(Settings).GetFields(BindingFlags.Static | BindingFlags.Public); + foreach (var field in fieldList) { + var customAttributes = + field.GetCustomAttributes(typeof(RebindableKeyAttribute), false) as RebindableKeyAttribute[]; + if (customAttributes != null && customAttributes.Length > 0) { + var category = customAttributes[0].category; + if (category != string.Empty && category != "Game") { + // Ignore other categories: MapEditor, Decoration, ThemeEditor, ScenarioEditor + continue; + } + + var str = field.GetValue(null) as string; + + var savedInputKey = new SavedInputKey(str, + Settings.gameSettingsFile, + GetDefaultEntryInGameSettings(str), + true); + if (savedInputKey.value == sample) { + return (category == string.Empty ? string.Empty : (category + " -- ")) + + CamelCaseSplit(field.Name); + } + } + } + + return string.Empty; + } + + private static InputKey GetDefaultEntryInGameSettings(string entryName) { + var field = typeof(DefaultSettings).GetField(entryName, BindingFlags.Static | BindingFlags.Public); + if (field == null) { + return 0; + } + + var obj = field.GetValue(null); + if (obj is InputKey) { + return (InputKey) obj; + } + + return 0; + } + + /// + /// For given key and category check TM:PE settings for the Global category + /// and the same category if it is not Global. This will allow reusing key in other tool + /// categories without conflicting. + /// + /// The key to search for + /// The category Global or some tool name + /// Fields of the key settings class + /// Empty string if no conflicts otherwise the key name to print an error + private static string FindConflictInTmpe(InputKey testSample, + string testSampleCategory, + FieldInfo[] fields) { + foreach (var field in fields) { + // This will match inputkeys of TMPE key settings + if (field.FieldType != typeof(KeybindSetting)) { + continue; + } + + var tmpeSetting = field.GetValue(null) as KeybindSetting; + + // Check category + // settingCategory=Global will check against any other test samples + // category= will check Global and its own only + if (tmpeSetting.Category == "Global" + || testSampleCategory == tmpeSetting.Category) { + if (tmpeSetting.HasKey(testSample)) { + return "TM:PE, " + + Translation.GetString("Keybind_category_" + tmpeSetting.Category) + + " -- " + CamelCaseSplit(field.Name); + } + } + } + + return string.Empty; + } + + private static string CamelCaseSplit(string s) { + var words = Regex.Matches(s, @"([A-Z][a-z]+)") + .Cast() + .Select(m => m.Value); + + return string.Join(" ", words.ToArray()); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/State/Options.cs b/TLM/TLM/State/Options.cs index 8b1b546eb..15e86f6cb 100644 --- a/TLM/TLM/State/Options.cs +++ b/TLM/TLM/State/Options.cs @@ -1,489 +1,546 @@ 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; using TrafficManager.Traffic.Enums; +using TrafficManager.State.Keybinds; +using TrafficManager.Traffic.Data; 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 scanForKnownIncompatibleModsToggle = null; - private static UICheckBox ignoreDisabledModsToggle = null; - private static UICheckBox individualDrivingStyleToggle = 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; + using API.Traffic.Enums; + + public class Options : MonoBehaviour { + private static UIDropDown languageDropdown = 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 scanForKnownIncompatibleModsToggle = null; + private static UICheckBox ignoreDisabledModsToggle = null; + private static UICheckBox displayMphToggle = null; + private static UIDropDown roadSignsMphThemeDropdown = null; + private static UICheckBox individualDrivingStyleToggle = 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; + 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; + 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; + 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; + 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; + 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 laneChangingRandomization = 2; - public static bool individualDrivingStyle = 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; + private static UIButton reloadGlobalConfBtn = null; + private static UIButton resetGlobalConfBtn = null; + + public static int roadSignMphStyleInt; + public static bool instantEffects = true; + //public static int laneChangingRandomization = 2; + public static bool individualDrivingStyle = 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 = + public static bool showPathFindStats = #if DEBUG - true; + true; #else - false; + 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; + 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; + 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; + 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; + public static bool showLanes = true; #else - public static bool showLanes = false; + public static bool showLanes = false; #endif - public static bool strongerRoadConditionEffects = false; - public static bool parkingAI = 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 bool strongerRoadConditionEffects = false; + public static bool parkingAI = 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 bool scanForKnownIncompatibleModsEnabled = true; public static bool ignoreDisabledModsEnabled = false; - 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; + public static VehicleRestrictionsAggression vehicleRestrictionsAggression = VehicleRestrictionsAggression.Medium; - int tabIndex = 0; - // GENERAL + public static bool MenuRebuildRequired { + get { return false; } + internal set { + if (value) { + if (LoadingExtension.BaseUI != null) { + LoadingExtension.BaseUI.RebuildMenu(); + } + } + } + } - AddOptionTab(tabStrip, Translation.GetString("General"));// tabStrip.AddTab(Translation.GetString("General"), tabTemplate, true); - tabStrip.selectedIndex = tabIndex; + public static void MakeSettings(UIHelperBase helper) { + // tabbing code is borrowed from RushHour mod + // https://github.com/PropaneDragon/RushHour/blob/release/RushHour/Options/OptionHandler.cs - 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 actualHelper = helper as UIHelper; + UIComponent container = actualHelper.self as UIComponent; - UIHelper panelHelper = new UIHelper(currentPanel); + UITabstrip tabStrip = container.AddUIComponent(); + tabStrip.relativePosition = new Vector3(0, 0); + tabStrip.size = new Vector2(container.width - 20, 40); - var generalGroup = panelHelper.AddGroup(Translation.GetString("General")); + 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; - 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; - } - } + int tabIndex = 0; - 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("Notify_me_if_there_is_an_unexpected_mod_conflict"), GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage, onShowCompatibilityCheckErrorChanged) as UICheckBox; - scanForKnownIncompatibleModsToggle = generalGroup.AddCheckbox(Translation.GetString("Scan_for_known_incompatible_mods_on_startup"), GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup, onScanForKnownIncompatibleModsChanged) as UICheckBox; - ignoreDisabledModsToggle = generalGroup.AddCheckbox(Translation.GetString("Ignore_disabled_mods"), GlobalConfig.Instance.Main.IgnoreDisabledMods, onIgnoreDisabledModsChanged) as UICheckBox; - Indent(ignoreDisabledModsToggle); + // GENERAL + UIPanel currentPanel; + UIHelper panelHelper; + MakeSettings_General(tabStrip, tabIndex); - var simGroup = panelHelper.AddGroup(Translation.GetString("Simulation")); - instantEffectsToggle = simGroup.AddCheckbox(Translation.GetString("Customizations_come_into_effect_instantaneously"), instantEffects, onInstantEffectsChanged) as UICheckBox; + // GAMEPLAY + ++tabIndex; + MakeSettings_Gameplay(tabStrip, tabIndex); - // GAMEPLAY - ++tabIndex; + // VEHICLE RESTRICTIONS + ++tabIndex; + MakeSettings_VehicleRestrictions(tabStrip, tabIndex); - AddOptionTab(tabStrip, Translation.GetString("Gameplay")); - tabStrip.selectedIndex = tabIndex; + // OVERLAYS + ++tabIndex; + MakeSettings_Overlays(tabStrip, 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; + // MAINTENANCE + ++tabIndex; + MakeSettings_Maintenance(tabStrip, tabIndex); - panelHelper = new UIHelper(currentPanel); + // KEYBOARD + ++tabIndex; + MakeSettings_Keybinds(tabStrip, tabIndex); - var vehBehaviorGroup = panelHelper.AddGroup(Translation.GetString("Vehicle_behavior")); +#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); + } - 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; - individualDrivingStyleToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Individual_driving_styles"), individualDrivingStyle, onIndividualDrivingStyleChanged) 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; + } - 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; + private static void MakeSettings_Keybinds(UITabstrip tabStrip, int tabIndex) { + UIPanel currentPanel; + UIHelper panelHelper; + AddOptionTab(tabStrip, Translation.GetString("Keybinds")); + tabStrip.selectedIndex = tabIndex; - 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; + 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; - var parkAiGroup = panelHelper.AddGroup(Translation.GetString("Parking_AI")); - prohibitPocketCarsToggle = parkAiGroup.AddCheckbox(Translation.GetString("Enable_more_realistic_parking"), parkingAI, onProhibitPocketCarsChanged) as UICheckBox; + panelHelper = new UIHelper(currentPanel); - var ptGroup = panelHelper.AddGroup(Translation.GetString("Public_transport")); - realisticPublicTransportToggle = ptGroup.AddCheckbox(Translation.GetString("Prevent_excessive_transfers_at_public_transport_stations"), realisticPublicTransport, onRealisticPublicTransportChanged) as UICheckBox; + var keyboardGroup = panelHelper.AddGroup(Translation.GetString("Keybinds")); + ((UIPanel) ((UIHelper) keyboardGroup).self).gameObject.AddComponent(); + } - // VEHICLE RESTRICTIONS - ++tabIndex; + private static void MakeSettings_Maintenance(UITabstrip tabStrip, int tabIndex) { + UIPanel currentPanel; + UIHelper panelHelper; + AddOptionTab(tabStrip, Translation.GetString("Maintenance")); + tabStrip.selectedIndex = 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; - 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); - panelHelper = new UIHelper(currentPanel); + var maintenanceGroup = panelHelper.AddGroup(Translation.GetString("Maintenance")); - var atJunctionsGroup = panelHelper.AddGroup(Translation.GetString("At_junctions")); + 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 - allRelaxedToggle = atJunctionsGroup.AddCheckbox(Translation.GetString("All_vehicles_may_ignore_lane_arrows"), allRelaxed, onAllRelaxedChanged) as UICheckBox; + resetSpeedLimitsBtn = maintenanceGroup.AddButton(Translation.GetString("Reset_custom_speed_limits"), onClickResetSpeedLimits) as UIButton; #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; - } + reloadGlobalConfBtn = maintenanceGroup.AddButton(Translation.GetString("Reload_global_configuration"), onClickReloadGlobalConf) as UIButton; + resetGlobalConfBtn = maintenanceGroup.AddButton(Translation.GetString("Reset_global_configuration"), onClickResetGlobalConf) as UIButton; - // 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; +#if QUEUEDSTATS + showPathFindStatsToggle = maintenanceGroup.AddCheckbox(Translation.GetString("Show_path-find_stats"), showPathFindStats, onShowPathFindStatsChanged) as UICheckBox; #endif - // MAINTENANCE - ++tabIndex; + 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); + } - AddOptionTab(tabStrip, Translation.GetString("Maintenance")); - tabStrip.selectedIndex = tabIndex; + private static void MakeSettings_Overlays(UITabstrip tabStrip, int tabIndex) { + UIPanel currentPanel; + UIHelper panelHelper; + 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 + } - 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; + private static void MakeSettings_VehicleRestrictions(UITabstrip tabStrip, int tabIndex) { + UIPanel currentPanel; + UIHelper panelHelper; + AddOptionTab(tabStrip, Translation.GetString("Policies_&_Restrictions")); + tabStrip.selectedIndex = tabIndex; - panelHelper = new UIHelper(currentPanel); + 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; - var maintenanceGroup = panelHelper.AddGroup(Translation.GetString("Maintenance")); + panelHelper = new UIHelper(currentPanel); - resetStuckEntitiesBtn = maintenanceGroup.AddButton(Translation.GetString("Reset_stuck_cims_and_vehicles"), onClickResetStuckEntities) as UIButton; - removeParkedVehiclesBtn = maintenanceGroup.AddButton(Translation.GetString("Remove_parked_vehicles"), onClickRemoveParkedVehicles) as UIButton; + var atJunctionsGroup = panelHelper.AddGroup(Translation.GetString("At_junctions")); #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; + 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; + } + } - 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 + private static void MakeSettings_Gameplay(UITabstrip tabStrip, int tabIndex) { + UIPanel currentPanel; + UIHelper panelHelper; + 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; + individualDrivingStyleToggle = vehBehaviorGroup.AddCheckbox(Translation.GetString("Individual_driving_styles"), individualDrivingStyle, onIndividualDrivingStyleChanged) 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"), parkingAI, 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; + } + private static void MakeSettings_General(UITabstrip tabStrip, int tabIndex) { + 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("Notify_me_if_there_is_an_unexpected_mod_conflict"), GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage, onShowCompatibilityCheckErrorChanged) as UICheckBox; + scanForKnownIncompatibleModsToggle = generalGroup.AddCheckbox(Translation.GetString("Scan_for_known_incompatible_mods_on_startup"), GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup, onScanForKnownIncompatibleModsChanged) as UICheckBox; + ignoreDisabledModsToggle = generalGroup.AddCheckbox(Translation.GetString("Ignore_disabled_mods"), GlobalConfig.Instance.Main.IgnoreDisabledMods, onIgnoreDisabledModsChanged) as UICheckBox; + Indent(ignoreDisabledModsToggle); - // 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); - } + // General: Speed Limits + setupSpeedLimitsPanel(panelHelper, generalGroup); - 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 + // General: Simulation + var simGroup = panelHelper.AddGroup(Translation.GetString("Simulation")); + instantEffectsToggle = simGroup.AddCheckbox(Translation.GetString("Customizations_come_into_effect_instantaneously"), instantEffects, onInstantEffectsChanged) as UICheckBox; + } - tabStrip.selectedIndex = 0; - } + private static void setupSpeedLimitsPanel(UIHelper panelHelper, UIHelperBase generalGroup) { + displayMphToggle = generalGroup.AddCheckbox( + Translation.GetString("Display_speed_limits_mph"), + GlobalConfig.Instance.Main.DisplaySpeedLimitsMph, + onDisplayMphChanged) as UICheckBox; + var mphThemeOptions = new[] { + Translation.GetString("theme_Square_US"), + Translation.GetString("theme_Round_UK"), + Translation.GetString("theme_Round_German"), + }; + roadSignMphStyleInt = (int)GlobalConfig.Instance.Main.MphRoadSignStyle; + roadSignsMphThemeDropdown = generalGroup.AddDropdown( + Translation.GetString("Road_signs_theme_mph") + ":", + mphThemeOptions, roadSignMphStyleInt, + onRoadSignsMphThemeChanged) as UIDropDown; + roadSignsMphThemeDropdown.width = 400; + } - private static void Indent(T component) where T : UIComponent { + private static void Indent(T component) where T : UIComponent { UILabel label = component.Find("Label"); if (label != null) { label.padding = new RectOffset(22, 0, 0, 0); @@ -492,199 +549,199 @@ private static void Indent(T component) where T : UIComponent { if (check != null) { check.relativePosition += new Vector3(22.0f, 0); } - } + } - private static UIButton AddOptionTab(UITabstrip tabStrip, string caption) { - UIButton tabButton = tabStrip.AddTab(caption); + 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.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; + tabButton.textPadding = new RectOffset(10, 10, 10, 10); + tabButton.autoSize = true; + tabButton.tooltip = caption; - return tabButton; - } + return tabButton; + } - private static bool IsGameLoaded(bool warn=true) { - if (!SerializableDataExtension.StateLoading && !LoadingExtension.IsGameLoaded) { - if (warn) { - 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 bool IsGameLoaded(bool warn = true) { + if (!SerializableDataExtension.StateLoading && !LoadingExtension.IsGameLoaded) { + if (warn) { + 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 (!IsGameLoaded()) - return; + private static void onGuiTransparencyChanged(float newVal) { + if (!IsGameLoaded()) + return; - setGuiTransparency((byte)Mathf.RoundToInt(newVal)); - guiTransparencySlider.tooltip = Translation.GetString("Window_transparency") + ": " + GlobalConfig.Instance.Main.GuiTransparency + " %"; + setGuiTransparency((byte) Mathf.RoundToInt(newVal)); + guiTransparencySlider.tooltip = Translation.GetString("Window_transparency") + ": " + GlobalConfig.Instance.Main.GuiTransparency + " %"; - GlobalConfig.WriteConfig(); + GlobalConfig.WriteConfig(); - Log._Debug($"GuiTransparency changed to {GlobalConfig.Instance.Main.GuiTransparency}"); - } + Log._Debug($"GuiTransparency changed to {GlobalConfig.Instance.Main.GuiTransparency}"); + } - private static void onOverlayTransparencyChanged(float newVal) { - if (!IsGameLoaded()) - return; + private static void onOverlayTransparencyChanged(float newVal) { + if (!IsGameLoaded()) + return; - setOverlayTransparency((byte)Mathf.RoundToInt(newVal)); - overlayTransparencySlider.tooltip = Translation.GetString("Overlay_transparency") + ": " + GlobalConfig.Instance.Main.OverlayTransparency + " %"; + setOverlayTransparency((byte) Mathf.RoundToInt(newVal)); + overlayTransparencySlider.tooltip = Translation.GetString("Overlay_transparency") + ": " + GlobalConfig.Instance.Main.OverlayTransparency + " %"; - GlobalConfig.WriteConfig(); + GlobalConfig.WriteConfig(); - Log._Debug($"OverlayTransparency changed to {GlobalConfig.Instance.Main.OverlayTransparency}"); - } + Log._Debug($"OverlayTransparency changed to {GlobalConfig.Instance.Main.OverlayTransparency}"); + } - private static void onAltLaneSelectionRatioChanged(float newVal) { - if (!IsGameLoaded()) - return; + private static void onAltLaneSelectionRatioChanged(float newVal) { + if (!IsGameLoaded()) + return; - setAltLaneSelectionRatio((byte)Mathf.RoundToInt(newVal)); - altLaneSelectionRatioSlider.tooltip = Translation.GetString("Percentage_of_vehicles_performing_dynamic_lane_section") + ": " + altLaneSelectionRatio + " %"; + setAltLaneSelectionRatio((byte) Mathf.RoundToInt(newVal)); + altLaneSelectionRatioSlider.tooltip = Translation.GetString("Percentage_of_vehicles_performing_dynamic_lane_section") + ": " + altLaneSelectionRatio + " %"; - Log._Debug($"altLaneSelectionRatio changed to {altLaneSelectionRatio}"); - } + Log._Debug($"altLaneSelectionRatio changed to {altLaneSelectionRatio}"); + } - private static void onPrioritySignsOverlayChanged(bool newPrioritySignsOverlay) { - if (!IsGameLoaded()) - return; + private static void onPrioritySignsOverlayChanged(bool newPrioritySignsOverlay) { + if (!IsGameLoaded()) + return; - Log._Debug($"prioritySignsOverlay changed to {newPrioritySignsOverlay}"); - prioritySignsOverlay = newPrioritySignsOverlay; + Log._Debug($"prioritySignsOverlay changed to {newPrioritySignsOverlay}"); + prioritySignsOverlay = newPrioritySignsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onTimedLightsOverlayChanged(bool newTimedLightsOverlay) { - if (!IsGameLoaded()) - return; + private static void onTimedLightsOverlayChanged(bool newTimedLightsOverlay) { + if (!IsGameLoaded()) + return; - Log._Debug($"timedLightsOverlay changed to {newTimedLightsOverlay}"); - timedLightsOverlay = newTimedLightsOverlay; + Log._Debug($"timedLightsOverlay changed to {newTimedLightsOverlay}"); + timedLightsOverlay = newTimedLightsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onSpeedLimitsOverlayChanged(bool newSpeedLimitsOverlay) { - if (!IsGameLoaded()) - return; + private static void onSpeedLimitsOverlayChanged(bool newSpeedLimitsOverlay) { + if (!IsGameLoaded()) + return; - Log._Debug($"speedLimitsOverlay changed to {newSpeedLimitsOverlay}"); - speedLimitsOverlay = newSpeedLimitsOverlay; + Log._Debug($"speedLimitsOverlay changed to {newSpeedLimitsOverlay}"); + speedLimitsOverlay = newSpeedLimitsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onVehicleRestrictionsOverlayChanged(bool newVehicleRestrictionsOverlay) { - if (!IsGameLoaded()) - return; + private static void onVehicleRestrictionsOverlayChanged(bool newVehicleRestrictionsOverlay) { + if (!IsGameLoaded()) + return; - Log._Debug($"vehicleRestrictionsOverlay changed to {newVehicleRestrictionsOverlay}"); - vehicleRestrictionsOverlay = newVehicleRestrictionsOverlay; + Log._Debug($"vehicleRestrictionsOverlay changed to {newVehicleRestrictionsOverlay}"); + vehicleRestrictionsOverlay = newVehicleRestrictionsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onParkingRestrictionsOverlayChanged(bool newParkingRestrictionsOverlay) { - if (!IsGameLoaded()) - return; + private static void onParkingRestrictionsOverlayChanged(bool newParkingRestrictionsOverlay) { + if (!IsGameLoaded()) + return; - Log._Debug($"parkingRestrictionsOverlay changed to {newParkingRestrictionsOverlay}"); - parkingRestrictionsOverlay = newParkingRestrictionsOverlay; + Log._Debug($"parkingRestrictionsOverlay changed to {newParkingRestrictionsOverlay}"); + parkingRestrictionsOverlay = newParkingRestrictionsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onJunctionRestrictionsOverlayChanged(bool newValue) { - if (!IsGameLoaded()) - return; + private static void onJunctionRestrictionsOverlayChanged(bool newValue) { + if (!IsGameLoaded()) + return; - Log._Debug($"junctionRestrictionsOverlay changed to {newValue}"); - junctionRestrictionsOverlay = newValue; + Log._Debug($"junctionRestrictionsOverlay changed to {newValue}"); + junctionRestrictionsOverlay = newValue; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onConnectedLanesOverlayChanged(bool newValue) { - if (!IsGameLoaded()) - return; + private static void onConnectedLanesOverlayChanged(bool newValue) { + if (!IsGameLoaded()) + return; - Log._Debug($"connectedLanesOverlay changed to {newValue}"); - connectedLanesOverlay = newValue; + Log._Debug($"connectedLanesOverlay changed to {newValue}"); + connectedLanesOverlay = newValue; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + 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}"); - } + private static void onLanguageChanged(int newLanguageIndex) { + bool localeChanged = false; - if (localeChanged) { - MethodInfo onChangedHandler = typeof(OptionsMainPanel).GetMethod("OnLocaleChanged", BindingFlags.Instance | BindingFlags.NonPublic); - if (onChangedHandler != null) { - onChangedHandler.Invoke(UIView.library.Get("OptionsPanel"), new object[0] { }); - } - } - } + 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}"); + } - private static void onLockButtonChanged(bool newValue) { - Log._Debug($"Button lock changed to {newValue}"); - if (IsGameLoaded(false)) { - LoadingExtension.BaseUI.MainMenuButton.SetPosLock(newValue); - } - GlobalConfig.Instance.Main.MainMenuButtonPosLocked = newValue; - GlobalConfig.WriteConfig(); - } + 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 onLockMenuChanged(bool newValue) { - Log._Debug($"Menu lock changed to {newValue}"); - if (IsGameLoaded(false)) { - LoadingExtension.BaseUI.MainMenu.SetPosLock(newValue); - } - GlobalConfig.Instance.Main.MainMenuPosLocked = newValue; - GlobalConfig.WriteConfig(); - } + private static void onLockButtonChanged(bool newValue) { + Log._Debug($"Button lock changed to {newValue}"); + if (IsGameLoaded(false)) { + LoadingExtension.BaseUI.MainMenuButton.SetPosLock(newValue); + } + GlobalConfig.Instance.Main.MainMenuButtonPosLocked = 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 onLockMenuChanged(bool newValue) { + Log._Debug($"Menu lock changed to {newValue}"); + if (IsGameLoaded(false)) { + LoadingExtension.BaseUI.MainMenu.SetPosLock(newValue); + } + GlobalConfig.Instance.Main.MainMenuPosLocked = newValue; + 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 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 onShowCompatibilityCheckErrorChanged(bool newValue) { - Log._Debug($"Show mod compatibility error changed to {newValue}"); - GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage = newValue; - 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 onScanForKnownIncompatibleModsChanged(bool newValue) { Log._Debug($"Show incompatible mod checker warnings changed to {newValue}"); @@ -703,567 +760,589 @@ private static void onIgnoreDisabledModsChanged(bool newValue) { GlobalConfig.WriteConfig(); } - private static void onInstantEffectsChanged(bool newValue) { - if (!IsGameLoaded()) - return; + private static void onDisplayMphChanged(bool newValue) { + Log._Debug($"Display MPH changed to {newValue}"); + GlobalConfig.Instance.Main.DisplaySpeedLimitsMph = newValue; + GlobalConfig.WriteConfig(); + } - Log._Debug($"Instant effects changed to {newValue}"); - instantEffects = newValue; - } + public static void setDisplayInMPH(bool value) { + if (displayMphToggle != null) { + displayMphToggle.isChecked = value; + } + } - private static void onVehicleRestrictionsAggressionChanged(int newValue) { - if (!IsGameLoaded()) - return; + private static void onRoadSignsMphThemeChanged(int newRoadSignStyle) { + if (!IsGameLoaded()) { + return; + } - Log._Debug($"vehicleRestrictionsAggression changed to {newValue}"); - setVehicleRestrictionsAggression((VehicleRestrictionsAggression)newValue); - } + // The UI order is: US, UK, German + var newStyle = MphSignStyle.RoundGerman; + switch (newRoadSignStyle) { + case 1: + newStyle = MphSignStyle.RoundUK; + break; + case 0: + newStyle = MphSignStyle.SquareUS; + break; + } - /*private static void onLaneChangingRandomizationChanged(int newLaneChangingRandomization) { - if (!checkGameLoaded()) - return; + Log._Debug($"Road Sign theme changed to {newStyle}"); + GlobalConfig.Instance.Main.MphRoadSignStyle = newStyle; + } - Log._Debug($"Lane changing frequency changed to {newLaneChangingRandomization}"); - laneChangingRandomization = newLaneChangingRandomization; - }*/ + private static void onInstantEffectsChanged(bool newValue) { + if (!IsGameLoaded()) + return; - private static void onRecklessDriversChanged(int newRecklessDrivers) { - if (!IsGameLoaded()) - return; + Log._Debug($"Instant effects changed to {newValue}"); + instantEffects = newValue; + } - Log._Debug($"Reckless driver amount changed to {newRecklessDrivers}"); - recklessDrivers = newRecklessDrivers; - } + private static void onVehicleRestrictionsAggressionChanged(int newValue) { + if (!IsGameLoaded()) + return; - private static void onRelaxedBussesChanged(bool newRelaxedBusses) { - if (!IsGameLoaded()) - return; + Log._Debug($"vehicleRestrictionsAggression changed to {newValue}"); + setVehicleRestrictionsAggression((VehicleRestrictionsAggression)newValue); + } - Log._Debug($"Relaxed busses changed to {newRelaxedBusses}"); - relaxedBusses = newRelaxedBusses; - } + /*private static void onLaneChangingRandomizationChanged(int newLaneChangingRandomization) { + if (!checkGameLoaded()) + return; - private static void onAllRelaxedChanged(bool newAllRelaxed) { - if (!IsGameLoaded()) - return; + Log._Debug($"Lane changing frequency changed to {newLaneChangingRandomization}"); + laneChangingRandomization = newLaneChangingRandomization; + }*/ - Log._Debug($"All relaxed changed to {newAllRelaxed}"); - allRelaxed = newAllRelaxed; - } + private static void onRecklessDriversChanged(int newRecklessDrivers) { + if (!IsGameLoaded()) + return; - private static void onAdvancedAIChanged(bool newAdvancedAI) { - if (!IsGameLoaded()) - return; + Log._Debug($"Reckless driver amount changed to {newRecklessDrivers}"); + recklessDrivers = newRecklessDrivers; + } - Log._Debug($"advancedAI changed to {newAdvancedAI}"); - setAdvancedAI(newAdvancedAI); - } + private static void onRelaxedBussesChanged(bool newRelaxedBusses) { + if (!IsGameLoaded()) + return; - private static void onHighwayRulesChanged(bool newHighwayRules) { - if (!IsGameLoaded()) - return; + Log._Debug($"Relaxed busses changed to {newRelaxedBusses}"); + relaxedBusses = newRelaxedBusses; + } - bool changed = newHighwayRules != highwayRules; - if (!changed) { - return; - } + private static void onAllRelaxedChanged(bool newAllRelaxed) { + if (!IsGameLoaded()) + return; - Log._Debug($"Highway rules changed to {newHighwayRules}"); - highwayRules = newHighwayRules; - Flags.clearHighwayLaneArrows(); - Flags.applyAllFlags(); - RoutingManager.Instance.RequestFullRecalculation(); - } + Log._Debug($"All relaxed changed to {newAllRelaxed}"); + allRelaxed = newAllRelaxed; + } - private static void onPreferOuterLaneChanged(bool val) { - if (!IsGameLoaded()) - return; + private static void onAdvancedAIChanged(bool newAdvancedAI) { + if (!IsGameLoaded()) + return; - preferOuterLane = val; - } + Log._Debug($"advancedAI changed to {newAdvancedAI}"); + setAdvancedAI(newAdvancedAI); + } - private static void onPrioritySignsEnabledChanged(bool val) { - if (!IsGameLoaded()) - return; + private static void onHighwayRulesChanged(bool newHighwayRules) { + if (!IsGameLoaded()) + return; - MenuRebuildRequired = true; - prioritySignsEnabled = val; - if (!val) { - setPrioritySignsOverlay(false); - setTrafficLightPriorityRules(false); - } - } + bool changed = newHighwayRules != highwayRules; + if (!changed) { + return; + } - private static void onTimedLightsEnabledChanged(bool val) { - if (!IsGameLoaded()) - return; + Log._Debug($"Highway rules changed to {newHighwayRules}"); + highwayRules = newHighwayRules; + Flags.clearHighwayLaneArrows(); + Flags.applyAllFlags(); + RoutingManager.Instance.RequestFullRecalculation(); + } - MenuRebuildRequired = true; - timedLightsEnabled = val; - if (!val) { - setTimedLightsOverlay(false); - setTrafficLightPriorityRules(false); - } - } + private static void onPreferOuterLaneChanged(bool val) { + if (!IsGameLoaded()) + return; - private static void onCustomSpeedLimitsEnabledChanged(bool val) { - if (!IsGameLoaded()) - return; + preferOuterLane = val; + } - MenuRebuildRequired = true; - customSpeedLimitsEnabled = val; - if (!val) - setSpeedLimitsOverlay(false); - } + private static void onPrioritySignsEnabledChanged(bool val) { + if (!IsGameLoaded()) + return; - private static void onVehicleRestrictionsEnabledChanged(bool val) { - if (!IsGameLoaded()) - return; + MenuRebuildRequired = true; + prioritySignsEnabled = val; + if (!val) { + setPrioritySignsOverlay(false); + setTrafficLightPriorityRules(false); + } + } - MenuRebuildRequired = true; - vehicleRestrictionsEnabled = val; - if (!val) - setVehicleRestrictionsOverlay(false); - } + private static void onTimedLightsEnabledChanged(bool val) { + if (!IsGameLoaded()) + return; - private static void onParkingRestrictionsEnabledChanged(bool val) { - if (!IsGameLoaded()) - return; + MenuRebuildRequired = true; + timedLightsEnabled = val; + if (!val) { + setTimedLightsOverlay(false); + setTrafficLightPriorityRules(false); + } + } - MenuRebuildRequired = true; - parkingRestrictionsEnabled = val; - if (!val) - setParkingRestrictionsOverlay(false); - } + private static void onCustomSpeedLimitsEnabledChanged(bool val) { + if (!IsGameLoaded()) + return; - private static void onJunctionRestrictionsEnabledChanged(bool val) { - if (!IsGameLoaded()) - return; + MenuRebuildRequired = true; + customSpeedLimitsEnabled = val; + if (!val) + setSpeedLimitsOverlay(false); + } - MenuRebuildRequired = true; - junctionRestrictionsEnabled = val; - if (!val) { - setAllowUTurns(false); - setAllowEnterBlockedJunctions(false); - setAllowLaneChangesWhileGoingStraight(false); - setTurnOnRedEnabled(false); - setJunctionRestrictionsOverlay(false); - } - } + private static void onVehicleRestrictionsEnabledChanged(bool val) { + if (!IsGameLoaded()) + return; - private static void onTurnOnRedEnabledChanged(bool val) { - if (!IsGameLoaded()) - return; + MenuRebuildRequired = true; + vehicleRestrictionsEnabled = val; + if (!val) + setVehicleRestrictionsOverlay(false); + } - setTurnOnRedEnabled(val); - } + private static void onParkingRestrictionsEnabledChanged(bool val) { + if (!IsGameLoaded()) + return; - private static void onLaneConnectorEnabledChanged(bool val) { - if (!IsGameLoaded()) - return; + MenuRebuildRequired = true; + parkingRestrictionsEnabled = val; + if (!val) + setParkingRestrictionsOverlay(false); + } - bool changed = val != laneConnectorEnabled; - if (!changed) { - return; - } + private static void onJunctionRestrictionsEnabledChanged(bool val) { + if (!IsGameLoaded()) + return; + + MenuRebuildRequired = true; + junctionRestrictionsEnabled = val; + if (!val) { + setAllowUTurns(false); + setAllowEnterBlockedJunctions(false); + setAllowLaneChangesWhileGoingStraight(false); + setTurnOnRedEnabled(false); + setJunctionRestrictionsOverlay(false); + } + } - MenuRebuildRequired = true; - laneConnectorEnabled = val; - RoutingManager.Instance.RequestFullRecalculation(); - if (!val) - setConnectedLanesOverlay(false); - } + private static void onTurnOnRedEnabledChanged(bool val) { + if (!IsGameLoaded()) + return; - private static void onEvacBussesMayIgnoreRulesChanged(bool value) { - if (!IsGameLoaded()) - return; + setTurnOnRedEnabled(val); + } - Log._Debug($"evacBussesMayIgnoreRules changed to {value}"); - evacBussesMayIgnoreRules = value; - } + private static void onLaneConnectorEnabledChanged(bool val) { + if (!IsGameLoaded()) + return; - private static void onAllowEnterBlockedJunctionsChanged(bool newValue) { - if (!IsGameLoaded()) - return; - if (newValue && !junctionRestrictionsEnabled) { - setAllowEnterBlockedJunctions(false); - return; - } + bool changed = val != laneConnectorEnabled; + if (!changed) { + return; + } - Log._Debug($"allowEnterBlockedJunctions changed to {newValue}"); - setAllowEnterBlockedJunctions(newValue); - } + MenuRebuildRequired = true; + laneConnectorEnabled = val; + RoutingManager.Instance.RequestFullRecalculation(); + if (!val) + setConnectedLanesOverlay(false); + } - private static void onAllowUTurnsChanged(bool newValue) { - if (!IsGameLoaded()) - return; - if (newValue && !junctionRestrictionsEnabled) { - setAllowUTurns(false); - return; - } + private static void onEvacBussesMayIgnoreRulesChanged(bool value) { + if (!IsGameLoaded()) + return; - Log._Debug($"allowUTurns changed to {newValue}"); - setAllowUTurns(newValue); - } + Log._Debug($"evacBussesMayIgnoreRules changed to {value}"); + evacBussesMayIgnoreRules = value; + } - private static void onAllowNearTurnOnRedChanged(bool newValue) { - if (!IsGameLoaded()) - return; - if (newValue && !turnOnRedEnabled) { - setAllowNearTurnOnRed(false); - setAllowFarTurnOnRed(false); - return; - } + private static void onAllowEnterBlockedJunctionsChanged(bool newValue) { + if (!IsGameLoaded()) + return; + if (newValue && !junctionRestrictionsEnabled) { + setAllowEnterBlockedJunctions(false); + return; + } - Log._Debug($"allowNearTurnOnRed changed to {newValue}"); - setAllowNearTurnOnRed(newValue); + Log._Debug($"allowEnterBlockedJunctions changed to {newValue}"); + setAllowEnterBlockedJunctions(newValue); + } - if (! newValue) { - setAllowFarTurnOnRed(false); - } - } + private static void onAllowUTurnsChanged(bool newValue) { + if (!IsGameLoaded()) + return; + if (newValue && !junctionRestrictionsEnabled) { + setAllowUTurns(false); + return; + } - private static void onAllowFarTurnOnRedChanged(bool newValue) { - if (!IsGameLoaded()) - return; - if (newValue && (!turnOnRedEnabled || !allowNearTurnOnRed)) { - setAllowFarTurnOnRed(false); - return; - } + Log._Debug($"allowUTurns changed to {newValue}"); + setAllowUTurns(newValue); + } - Log._Debug($"allowFarTurnOnRed changed to {newValue}"); - setAllowFarTurnOnRed(newValue); - } + private static void onAllowNearTurnOnRedChanged(bool newValue) { + if (!IsGameLoaded()) + return; + if (newValue && !turnOnRedEnabled) { + setAllowNearTurnOnRed(false); + setAllowFarTurnOnRed(false); + return; + } -#if TURNONRED - private static void onAllowTurnOnRedChanged(bool value) { - if (!checkGameLoaded()) - return; + Log._Debug($"allowNearTurnOnRed changed to {newValue}"); + setAllowNearTurnOnRed(newValue); - Log._Debug($"allowTurnOnRed changed to {value}"); - allowTurnOnRed = value; - } -#endif + if (!newValue) { + setAllowFarTurnOnRed(false); + } + } - private static void onAllowLaneChangesWhileGoingStraightChanged(bool newValue) { - if (!IsGameLoaded()) - return; - if (newValue && !junctionRestrictionsEnabled) { - setAllowLaneChangesWhileGoingStraight(false); - return; - } + private static void onAllowFarTurnOnRedChanged(bool newValue) { + if (!IsGameLoaded()) + return; + if (newValue && (!turnOnRedEnabled || !allowNearTurnOnRed)) { + setAllowFarTurnOnRed(false); + return; + } - Log._Debug($"allowLaneChangesWhileGoingStraight changed to {newValue}"); - allowLaneChangesWhileGoingStraight = newValue; - } + Log._Debug($"allowFarTurnOnRed changed to {newValue}"); + setAllowFarTurnOnRed(newValue); + } - private static void onTrafficLightPriorityRulesChanged(bool newValue) { - if (!IsGameLoaded()) - return; - if (newValue && !prioritySignsEnabled) { - setTrafficLightPriorityRules(false); - return; - } + private static void onAllowLaneChangesWhileGoingStraightChanged(bool newValue) { + if (!IsGameLoaded()) + return; + if (newValue && !junctionRestrictionsEnabled) { + setAllowLaneChangesWhileGoingStraight(false); + return; + } - Log._Debug($"trafficLightPriorityRules changed to {newValue}"); - trafficLightPriorityRules = newValue; - if (newValue) { - setPrioritySignsEnabled(true); - setTimedLightsEnabled(true); - } - } + Log._Debug($"allowLaneChangesWhileGoingStraight changed to {newValue}"); + setAllowLaneChangesWhileGoingStraight(newValue); + } - private static void onBanRegularTrafficOnBusLanesChanged(bool newValue) { - if (!IsGameLoaded()) - return; + private static void onTrafficLightPriorityRulesChanged(bool newValue) { + if (!IsGameLoaded()) + return; + if (newValue && !prioritySignsEnabled) { + setTrafficLightPriorityRules(false); + return; + } - Log._Debug($"banRegularTrafficOnBusLanes changed to {newValue}"); - banRegularTrafficOnBusLanes = newValue; - VehicleRestrictionsManager.Instance.ClearCache(); - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + Log._Debug($"trafficLightPriorityRules changed to {newValue}"); + trafficLightPriorityRules = newValue; + if (newValue) { + setPrioritySignsEnabled(true); + setTimedLightsEnabled(true); + } + } - private static void onStrongerRoadConditionEffectsChanged(bool newStrongerRoadConditionEffects) { - if (!IsGameLoaded()) - return; + private static void onBanRegularTrafficOnBusLanesChanged(bool newValue) { + if (!IsGameLoaded()) + return; - Log._Debug($"strongerRoadConditionEffects changed to {newStrongerRoadConditionEffects}"); - strongerRoadConditionEffects = newStrongerRoadConditionEffects; - } + Log._Debug($"banRegularTrafficOnBusLanes changed to {newValue}"); + banRegularTrafficOnBusLanes = newValue; + VehicleRestrictionsManager.Instance.ClearCache(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - private static void onProhibitPocketCarsChanged(bool newValue) { - if (!IsGameLoaded()) - return; + private static void onStrongerRoadConditionEffectsChanged(bool newStrongerRoadConditionEffects) { + if (!IsGameLoaded()) + return; - Log._Debug($"prohibitPocketCars changed to {newValue}"); + Log._Debug($"strongerRoadConditionEffects changed to {newStrongerRoadConditionEffects}"); + strongerRoadConditionEffects = newStrongerRoadConditionEffects; + } - parkingAI = newValue; - if (parkingAI) { - AdvancedParkingManager.Instance.OnEnableFeature(); - } else { - AdvancedParkingManager.Instance.OnDisableFeature(); - } - } + private static void onProhibitPocketCarsChanged(bool newValue) { + if (!IsGameLoaded()) + return; + + Log._Debug($"prohibitPocketCars changed to {newValue}"); + + parkingAI = newValue; + if (parkingAI) { + AdvancedParkingManager.Instance.OnEnableFeature(); + } else { + AdvancedParkingManager.Instance.OnDisableFeature(); + } + } private static void onRealisticPublicTransportChanged(bool newValue) { if (!IsGameLoaded()) return; - Log._Debug($"realisticPublicTransport changed to {newValue}"); - realisticPublicTransport = newValue; - } + Log._Debug($"realisticPublicTransport changed to {newValue}"); + realisticPublicTransport = newValue; + } - private static void onIndividualDrivingStyleChanged(bool value) { - if (!IsGameLoaded()) - return; + private static void onIndividualDrivingStyleChanged(bool value) { + if (!IsGameLoaded()) + return; - Log._Debug($"individualDrivingStyle changed to {value}"); - setIndividualDrivingStyle(value); - } + Log._Debug($"individualDrivingStyle changed to {value}"); + setIndividualDrivingStyle(value); + } - private static void onDisableDespawningChanged(bool value) { - if (!IsGameLoaded()) - return; + private static void onDisableDespawningChanged(bool value) { + if (!IsGameLoaded()) + return; - Log._Debug($"disableDespawning changed to {value}"); - disableDespawning = value; - } + Log._Debug($"disableDespawning changed to {value}"); + disableDespawning = value; + } - private static void onNodesOverlayChanged(bool newNodesOverlay) { - if (!IsGameLoaded()) - return; + private static void onNodesOverlayChanged(bool newNodesOverlay) { + if (!IsGameLoaded()) + return; - Log._Debug($"Nodes overlay changed to {newNodesOverlay}"); - nodesOverlay = newNodesOverlay; - } + Log._Debug($"Nodes overlay changed to {newNodesOverlay}"); + nodesOverlay = newNodesOverlay; + } - private static void onShowLanesChanged(bool newShowLanes) { - if (!IsGameLoaded()) - return; + private static void onShowLanesChanged(bool newShowLanes) { + if (!IsGameLoaded()) + return; - Log._Debug($"Show lanes changed to {newShowLanes}"); - showLanes = newShowLanes; - } + Log._Debug($"Show lanes changed to {newShowLanes}"); + showLanes = newShowLanes; + } - private static void onVehicleOverlayChanged(bool newVal) { - if (!IsGameLoaded()) - return; + private static void onVehicleOverlayChanged(bool newVal) { + if (!IsGameLoaded()) + return; - Log._Debug($"Vehicle overlay changed to {newVal}"); - vehicleOverlay = newVal; - } + Log._Debug($"Vehicle overlay changed to {newVal}"); + vehicleOverlay = newVal; + } - private static void onCitizenOverlayChanged(bool newVal) { - if (!IsGameLoaded()) - return; + private static void onCitizenOverlayChanged(bool newVal) { + if (!IsGameLoaded()) + return; - Log._Debug($"Citizen overlay changed to {newVal}"); - citizenOverlay = newVal; - } + Log._Debug($"Citizen overlay changed to {newVal}"); + citizenOverlay = newVal; + } - private static void onBuildingOverlayChanged(bool newVal) { - if (!IsGameLoaded()) - return; + private static void onBuildingOverlayChanged(bool newVal) { + if (!IsGameLoaded()) + return; - Log._Debug($"Building overlay changed to {newVal}"); - buildingOverlay = newVal; - } + Log._Debug($"Building overlay changed to {newVal}"); + buildingOverlay = newVal; + } #if QUEUEDSTATS - private static void onShowPathFindStatsChanged(bool newVal) { - if (!IsGameLoaded()) - return; + private static void onShowPathFindStatsChanged(bool newVal) { + if (!IsGameLoaded()) + return; - Log._Debug($"Show path-find stats changed to {newVal}"); - showPathFindStats = newVal; - } + Log._Debug($"Show path-find stats changed to {newVal}"); + showPathFindStats = newVal; + } #endif - private static void onFloatValueChanged(string varName, string newValueStr, ref float var) { - if (!IsGameLoaded()) - 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 onFloatValueChanged(string varName, string newValueStr, ref float var) { + if (!IsGameLoaded()) + 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 (!IsGameLoaded()) - return; + private static void onBoolValueChanged(string varName, bool newVal, ref bool var) { + if (!IsGameLoaded()) + return; - var = newVal; - Log._Debug($"{varName} changed to {newVal}"); - } + var = newVal; + Log._Debug($"{varName} changed to {newVal}"); + } - private static void onClickResetStuckEntities() { - if (!IsGameLoaded()) - return; + private static void onClickResetStuckEntities() { + if (!IsGameLoaded()) + return; - Constants.ServiceFactory.SimulationService.AddAction(() => { - UtilityManager.Instance.ResetStuckEntities(); - }); - } + Constants.ServiceFactory.SimulationService.AddAction(() => { + UtilityManager.Instance.ResetStuckEntities(); + }); + } - private static void onClickRemoveParkedVehicles() { - if (!IsGameLoaded()) - return; + private static void onClickRemoveParkedVehicles() { + if (!IsGameLoaded()) + return; - Constants.ServiceFactory.SimulationService.AddAction(() => { - UtilityManager.Instance.RemoveParkedVehicles(); - }); - } + Constants.ServiceFactory.SimulationService.AddAction(() => { + UtilityManager.Instance.RemoveParkedVehicles(); + }); + } - private static void onClickResetSpeedLimits() { - if (!IsGameLoaded()) - return; + private static void onClickResetSpeedLimits() { + if (!IsGameLoaded()) + return; - Flags.resetSpeedLimits(); - } + Flags.resetSpeedLimits(); + } - private static void onClickReloadGlobalConf() { - GlobalConfig.Reload(); - } + private static void onClickReloadGlobalConf() { + GlobalConfig.Reload(); + } - private static void onClickResetGlobalConf() { - GlobalConfig.Reset(null, true); - } + private static void onClickResetGlobalConf() { + GlobalConfig.Reset(null, true); + } - public static void setVehicleRestrictionsAggression(VehicleRestrictionsAggression val) { - bool changed = vehicleRestrictionsAggression != val; - vehicleRestrictionsAggression = val; - if (changed && vehicleRestrictionsAggressionDropdown != null) { - vehicleRestrictionsAggressionDropdown.selectedIndex = (int)val; - } - } + 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 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; - } + public static void setRecklessDrivers(int newRecklessDrivers) { + recklessDrivers = newRecklessDrivers; + if (recklessDriversDropdown != null) + recklessDriversDropdown.selectedIndex = newRecklessDrivers; + } - internal static bool isStockLaneChangerUsed() { - return !advancedAI; - } + internal static bool isStockLaneChangerUsed() { + return !advancedAI; + } - public static void setRelaxedBusses(bool newRelaxedBusses) { - relaxedBusses = newRelaxedBusses; - if (relaxedBussesToggle != null) - relaxedBussesToggle.isChecked = newRelaxedBusses; - } + 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 setAllRelaxed(bool newAllRelaxed) { + allRelaxed = newAllRelaxed; + if (allRelaxedToggle != null) + allRelaxedToggle.isChecked = newAllRelaxed; + } - public static void setHighwayRules(bool newHighwayRules) { - highwayRules = newHighwayRules; + public static void setHighwayRules(bool newHighwayRules) { + highwayRules = newHighwayRules; - if (highwayRulesToggle != null) - highwayRulesToggle.isChecked = highwayRules; - } + if (highwayRulesToggle != null) + highwayRulesToggle.isChecked = highwayRules; + } - public static void setPreferOuterLane(bool val) { - preferOuterLane = val; + public static void setPreferOuterLane(bool val) { + preferOuterLane = val; - if (preferOuterLaneToggle != null) - preferOuterLaneToggle.isChecked = preferOuterLane; - } + if (preferOuterLaneToggle != null) + preferOuterLaneToggle.isChecked = preferOuterLane; + } - public static void setShowLanes(bool newShowLanes) { - showLanes = newShowLanes; - if (showLanesToggle != null) - showLanesToggle.isChecked = newShowLanes; - } + 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; + public static void setAdvancedAI(bool newAdvancedAI) { + bool changed = newAdvancedAI != advancedAI; + advancedAI = newAdvancedAI; - if (changed && advancedAIToggle != null) { - advancedAIToggle.isChecked = newAdvancedAI; - } + if (changed && advancedAIToggle != null) { + advancedAIToggle.isChecked = newAdvancedAI; + } - if (changed && !newAdvancedAI) { - setAltLaneSelectionRatio(0); - } - } + if (changed && !newAdvancedAI) { + setAltLaneSelectionRatio(0); + } + } - public static void setGuiTransparency(byte val) { - bool changed = val != GlobalConfig.Instance.Main.GuiTransparency; - GlobalConfig.Instance.Main.GuiTransparency = val; + 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; - } - } + 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; + 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; - } - } + if (changed && overlayTransparencySlider != null) { + overlayTransparencySlider.value = val; + } + } - public static void setAltLaneSelectionRatio(byte val) { - bool changed = val != altLaneSelectionRatio; - altLaneSelectionRatio = val; + public static void setAltLaneSelectionRatio(byte val) { + bool changed = val != altLaneSelectionRatio; + altLaneSelectionRatio = val; - if (changed && altLaneSelectionRatioSlider != null) { - altLaneSelectionRatioSlider.value = val; - } + if (changed && altLaneSelectionRatioSlider != null) { + altLaneSelectionRatioSlider.value = val; + } - if (changed && altLaneSelectionRatio > 0) { - setAdvancedAI(true); - } - } + if (changed && altLaneSelectionRatio > 0) { + setAdvancedAI(true); + } + } - public static void setEvacBussesMayIgnoreRules(bool value) { - if (! SteamHelper.IsDLCOwned(SteamHelper.DLC.NaturalDisastersDLC)) - value = false; + public static void setEvacBussesMayIgnoreRules(bool value) { + if (! SteamHelper.IsDLCOwned(SteamHelper.DLC.NaturalDisastersDLC)) + value = false; - evacBussesMayIgnoreRules = value; - if (evacBussesMayIgnoreRulesToggle != null) - evacBussesMayIgnoreRulesToggle.isChecked = value; - } + 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 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 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; - } + public static void setStrongerRoadConditionEffects(bool newStrongerRoadConditionEffects) { + if (!SteamHelper.IsDLCOwned(SteamHelper.DLC.SnowFallDLC)) { + newStrongerRoadConditionEffects = false; + } - strongerRoadConditionEffects = newStrongerRoadConditionEffects; - if (strongerRoadConditionEffectsToggle != null) - strongerRoadConditionEffectsToggle.isChecked = newStrongerRoadConditionEffects; - } + strongerRoadConditionEffects = newStrongerRoadConditionEffects; + if (strongerRoadConditionEffectsToggle != null) + strongerRoadConditionEffectsToggle.isChecked = newStrongerRoadConditionEffects; + } public static void setProhibitPocketCars(bool newValue) { bool valueChanged = newValue != parkingAI; @@ -1272,227 +1351,232 @@ public static void setProhibitPocketCars(bool newValue) { prohibitPocketCarsToggle.isChecked = newValue; } - public static void setRealisticPublicTransport(bool newValue) { - bool valueChanged = newValue != realisticPublicTransport; - realisticPublicTransport = newValue; - if (realisticPublicTransportToggle != null) - realisticPublicTransportToggle.isChecked = newValue; - } + public static void setRealisticPublicTransport(bool newValue) { + bool valueChanged = newValue != realisticPublicTransport; + realisticPublicTransport = newValue; + if (realisticPublicTransportToggle != null) + realisticPublicTransportToggle.isChecked = newValue; + } - public static void setIndividualDrivingStyle(bool newValue) { - individualDrivingStyle = newValue; + public static void setIndividualDrivingStyle(bool newValue) { + individualDrivingStyle = newValue; - if (individualDrivingStyleToggle != null) { - individualDrivingStyleToggle.isChecked = newValue; - } - } + if (individualDrivingStyleToggle != null) { + individualDrivingStyleToggle.isChecked = newValue; + } + } - public static void setDisableDespawning(bool value) { - disableDespawning = value; + public static void setDisableDespawning(bool value) { + disableDespawning = value; - if (disableDespawningToggle != null) - disableDespawningToggle.isChecked = 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 setAllowUTurns(bool value) { + allowUTurns = value; + if (allowUTurnsToggle != null) + allowUTurnsToggle.isChecked = value; + Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setAllowNearTurnOnRed(bool newValue) { - allowNearTurnOnRed = newValue; - if (allowNearTurnOnRedToggle != null) - allowNearTurnOnRedToggle.isChecked = newValue; - Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); - } + public static void setAllowNearTurnOnRed(bool newValue) { + allowNearTurnOnRed = newValue; + if (allowNearTurnOnRedToggle != null) + allowNearTurnOnRedToggle.isChecked = newValue; + Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setAllowFarTurnOnRed(bool newValue) { - allowFarTurnOnRed = newValue; - if (allowFarTurnOnRedToggle != null) - allowFarTurnOnRedToggle.isChecked = newValue; - Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); - } + public static void setAllowFarTurnOnRed(bool newValue) { + allowFarTurnOnRed = newValue; + if (allowFarTurnOnRedToggle != null) + allowFarTurnOnRedToggle.isChecked = newValue; + Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setAllowLaneChangesWhileGoingStraight(bool value) { - allowLaneChangesWhileGoingStraight = value; - if (allowLaneChangesWhileGoingStraightToggle != null) - allowLaneChangesWhileGoingStraightToggle.isChecked = value; - Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); - } + public static void setAllowLaneChangesWhileGoingStraight(bool value) { + allowLaneChangesWhileGoingStraight = value; + if (allowLaneChangesWhileGoingStraightToggle != null) + allowLaneChangesWhileGoingStraightToggle.isChecked = value; + Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setAllowEnterBlockedJunctions(bool value) { - allowEnterBlockedJunctions = value; - if (allowEnterBlockedJunctionsToggle != null) - allowEnterBlockedJunctionsToggle.isChecked = value; - Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); - } + public static void setAllowEnterBlockedJunctions(bool value) { + allowEnterBlockedJunctions = value; + if (allowEnterBlockedJunctionsToggle != null) + allowEnterBlockedJunctionsToggle.isChecked = value; + Constants.ManagerFactory.JunctionRestrictionsManager.UpdateAllDefaults(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setTrafficLightPriorityRules(bool value) { - trafficLightPriorityRules = value; - if (trafficLightPriorityRulesToggle != null) - trafficLightPriorityRulesToggle.isChecked = value; - } + 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; + public static void setBanRegularTrafficOnBusLanes(bool value) { + banRegularTrafficOnBusLanes = value; + if (banRegularTrafficOnBusLanesToggle != null) + banRegularTrafficOnBusLanesToggle.isChecked = value; - VehicleRestrictionsManager.Instance.ClearCache(); - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + VehicleRestrictionsManager.Instance.ClearCache(); + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setPrioritySignsOverlay(bool newPrioritySignsOverlay) { - prioritySignsOverlay = newPrioritySignsOverlay; - if (prioritySignsOverlayToggle != null) - prioritySignsOverlayToggle.isChecked = newPrioritySignsOverlay; + public static void setPrioritySignsOverlay(bool newPrioritySignsOverlay) { + prioritySignsOverlay = newPrioritySignsOverlay; + if (prioritySignsOverlayToggle != null) + prioritySignsOverlayToggle.isChecked = newPrioritySignsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setTimedLightsOverlay(bool newTimedLightsOverlay) { - timedLightsOverlay = newTimedLightsOverlay; - if (timedLightsOverlayToggle != null) - timedLightsOverlayToggle.isChecked = newTimedLightsOverlay; + public static void setTimedLightsOverlay(bool newTimedLightsOverlay) { + timedLightsOverlay = newTimedLightsOverlay; + if (timedLightsOverlayToggle != null) + timedLightsOverlayToggle.isChecked = newTimedLightsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setSpeedLimitsOverlay(bool newSpeedLimitsOverlay) { - speedLimitsOverlay = newSpeedLimitsOverlay; - if (speedLimitsOverlayToggle != null) - speedLimitsOverlayToggle.isChecked = newSpeedLimitsOverlay; + public static void setSpeedLimitsOverlay(bool newSpeedLimitsOverlay) { + speedLimitsOverlay = newSpeedLimitsOverlay; + if (speedLimitsOverlayToggle != null) + speedLimitsOverlayToggle.isChecked = newSpeedLimitsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setVehicleRestrictionsOverlay(bool newVehicleRestrictionsOverlay) { - vehicleRestrictionsOverlay = newVehicleRestrictionsOverlay; - if (vehicleRestrictionsOverlayToggle != null) - vehicleRestrictionsOverlayToggle.isChecked = newVehicleRestrictionsOverlay; + public static void setVehicleRestrictionsOverlay(bool newVehicleRestrictionsOverlay) { + vehicleRestrictionsOverlay = newVehicleRestrictionsOverlay; + if (vehicleRestrictionsOverlayToggle != null) + vehicleRestrictionsOverlayToggle.isChecked = newVehicleRestrictionsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setParkingRestrictionsOverlay(bool newParkingRestrictionsOverlay) { - parkingRestrictionsOverlay = newParkingRestrictionsOverlay; - if (parkingRestrictionsOverlayToggle != null) - parkingRestrictionsOverlayToggle.isChecked = newParkingRestrictionsOverlay; + public static void setParkingRestrictionsOverlay(bool newParkingRestrictionsOverlay) { + parkingRestrictionsOverlay = newParkingRestrictionsOverlay; + if (parkingRestrictionsOverlayToggle != null) + parkingRestrictionsOverlayToggle.isChecked = newParkingRestrictionsOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setJunctionRestrictionsOverlay(bool newValue) { - junctionRestrictionsOverlay = newValue; - if (junctionRestrictionsOverlayToggle != null) - junctionRestrictionsOverlayToggle.isChecked = newValue; + public static void setJunctionRestrictionsOverlay(bool newValue) { + junctionRestrictionsOverlay = newValue; + if (junctionRestrictionsOverlayToggle != null) + junctionRestrictionsOverlayToggle.isChecked = newValue; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setConnectedLanesOverlay(bool newValue) { - connectedLanesOverlay = newValue; - if (connectedLanesOverlayToggle != null) - connectedLanesOverlayToggle.isChecked = newValue; - } + 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; + public static void setNodesOverlay(bool newNodesOverlay) { + nodesOverlay = newNodesOverlay; + if (nodesOverlayToggle != null) + nodesOverlayToggle.isChecked = newNodesOverlay; - UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); - } + UIBase.GetTrafficManagerTool(false)?.InitializeSubTools(); + } - public static void setVehicleOverlay(bool newVal) { - vehicleOverlay = newVal; - if (vehicleOverlayToggle != null) - vehicleOverlayToggle.isChecked = newVal; - } + 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 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 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 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 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 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 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 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); - } + 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; - } + public static void setShowPathFindStats(bool value) { + showPathFindStats = value; + if (showPathFindStatsToggle != null) + showPathFindStatsToggle.isChecked = value; + } #endif - + public static void setScanForKnownIncompatibleMods(bool value) { scanForKnownIncompatibleModsEnabled = value; if (scanForKnownIncompatibleModsToggle != null) { @@ -1548,25 +1632,25 @@ public static void setIgnoreDisabledMods(bool value) { }*/ internal static int getRecklessDriverModulo() { - switch (recklessDrivers) { - case 0: - return 10; - case 1: - return 20; - case 2: - return 50; - case 3: - return 10000; - } - return 10000; - } + switch (recklessDrivers) { + case 0: + return 10; + case 1: + return 20; + case 2: + return 50; + case 3: + return 10000; + } + return 10000; + } - /// - /// Determines whether Dynamic Lane Selection (DLS) is enabled. - /// - /// - public static bool IsDynamicLaneSelectionActive() { - return advancedAI && altLaneSelectionRatio > 0; - } - } -} + /// + /// Determines whether Dynamic Lane Selection (DLS) is enabled. + /// + /// + public static bool IsDynamicLaneSelectionActive() { + return advancedAI && altLaneSelectionRatio > 0; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index 1fa999340..264878c0e 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -12,6 +12,7 @@ v3.5 512 + latest true @@ -67,6 +68,16 @@ prompt ..\TMPE.ruleset + + bin\Release LABS\ + PF2;QUEUEDSTATS;PARKINGAI;SPEEDLIMITS;ROUTING;JUNCTIONRESTRICTIONS;VEHICLERESTRICTIONS;ADVANCEDAI;CUSTOMTRAFFICLIGHTS;LABS + true + true + pdbonly + AnyCPU + prompt + ..\TMPE.ruleset + RedirectionFramework\lib\0TMPE.Harmony.dll @@ -183,7 +194,13 @@ + + + + + + @@ -196,6 +213,7 @@ + @@ -340,19 +358,6 @@ - - - - - - - - - - - - - @@ -369,9 +374,6 @@ - - - @@ -438,6 +440,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rem set "DEPLOYDIR=D:\Games\Steam\steamapps\workshop\content\255710\1637663252\" diff --git a/TLM/TLM/Traffic/Data/SpeedLimit.cs b/TLM/TLM/Traffic/Data/SpeedLimit.cs new file mode 100644 index 000000000..eb9a949aa --- /dev/null +++ b/TLM/TLM/Traffic/Data/SpeedLimit.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using TrafficManager.State; +using TrafficManager.UI; +using UnityEngine; + +namespace TrafficManager.Traffic.Data { + public enum SpeedUnit { + CurrentlyConfigured, // Currently selected in the options menu + Kmph, + Mph + } + + public enum MphSignStyle { + SquareUS = 0, + RoundUK = 1, + RoundGerman = 2, + } + + /// + /// Defines a speed limit value with default Kmph and display value of Mph + /// for when the option is set to display Mph. The engine still uses kmph. + /// + public struct SpeedLimit { + public const float SPEED_TO_KMPH = 50.0f; // 1.0f equals 50 km/h + private const ushort LOWER_KMPH = 10; + public const ushort UPPER_KMPH = 140; + private const ushort KMPH_STEP = 10; + public const int BREAK_PALETTE_COLUMN_KMPH = 8; // palette shows N in a row, then break and another row + + private const float SPEED_TO_MPH = 32.06f; // 50 km/h converted to mph + private const ushort LOWER_MPH = 5; + public const ushort UPPER_MPH = 90; + private const ushort MPH_STEP = 5; + public const int BREAK_PALETTE_COLUMN_MPH = 10; // palette shows M in a row, then break and another row + + private const float LOWER_SPEED = 0.1f; + private const float UPPER_SPEED = 2 * 10.0f; // 1000 km/h + + /// + /// Produces list of speed limits to offer user in the palette + /// + /// What kind of speed limit list is required + /// List from smallest to largest speed with the given unit. Zero (no limit) is not added to the list. + /// The values are in-game speeds as float. + public static List EnumerateSpeedLimits(SpeedUnit unit) { + var result = new List(); + switch (unit) { + case SpeedUnit.Kmph: + for (var km = LOWER_KMPH; km <= UPPER_KMPH; km += KMPH_STEP) { + result.Add(km / SPEED_TO_KMPH); + } + break; + case SpeedUnit.Mph: + for (var mi = LOWER_MPH; mi <= UPPER_MPH; mi += MPH_STEP) { + result.Add(mi / SPEED_TO_MPH); + } + break; + case SpeedUnit.CurrentlyConfigured: + // Automatically choose from the config + return GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? EnumerateSpeedLimits(SpeedUnit.Mph) + : EnumerateSpeedLimits(SpeedUnit.Kmph); + } + + return result; + } + + public static string ToMphPreciseString(float speed) { + if (IsZero(speed)) { + return Translation.GetString("Speed_limit_unlimited"); + } + + return ToMphPrecise(speed) + " MPH"; + } + + public static string ToKmphPreciseString(float speed) { + if (IsZero(speed)) { + return Translation.GetString("Speed_limit_unlimited"); + } + + return ToKmphPrecise(speed) + " km/h"; + } + + public static bool NearlyEqual(float a, float b) { + return Mathf.Abs(a - b) < 0.001f; + } + + public static bool IsZero(float speed) { + return speed < 0.001f; + } + + public static bool IsValidRange(float speed) { + return IsZero(speed) || (speed >= LOWER_SPEED && speed <= UPPER_SPEED); + } + + /// + /// Convert float game speed to mph and round to nearest STEP + /// + /// + /// + public static ushort ToMphRounded(float speed) { + var mph = speed * SPEED_TO_MPH; + return (ushort)(Mathf.Round(mph / MPH_STEP) * MPH_STEP); + } + + public static ushort ToMphPrecise(float speed) { + return (ushort)Mathf.Round(speed * SPEED_TO_MPH); + } + + /// + /// Convert float game speed to km/h and round to nearest STEP + /// + /// + /// + public static ushort ToKmphRounded(float speed) { + var kmph = speed * SPEED_TO_KMPH; + return (ushort)(Mathf.Round(kmph / KMPH_STEP) * KMPH_STEP); + } + + public static ushort ToKmphPrecise(float speed) { + return (ushort)Mathf.Round(speed * SPEED_TO_KMPH); + } + + /// + /// Based on the MPH/KMPH settings round the current speed to the nearest STEP and + /// then decrease by STEP. + /// + /// Ingame speed + /// Ingame speed decreased by the increment for MPH or KMPH + public static float GetPrevious(float speed) { + if (speed < 0f) { + return -1f; + } + if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph) { + var rounded = ToMphRounded(speed); + return (rounded > LOWER_MPH ? rounded - MPH_STEP : LOWER_MPH) / SPEED_TO_MPH; + } else { + var rounded = ToKmphRounded(speed); + return (rounded > LOWER_KMPH ? rounded - KMPH_STEP : LOWER_KMPH) / SPEED_TO_KMPH; + } + } + + /// + /// Based on the MPH/KMPH settings round the current speed to the nearest STEP and + /// then increase by STEP. + /// + /// Ingame speed + /// Ingame speed increased by the increment for MPH or KMPH + public static float GetNext(float speed) { + if (speed < 0f) { + return -1f; + } + if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph) { + var rounded = ToMphRounded(speed); + return (rounded < UPPER_MPH ? rounded + MPH_STEP : UPPER_MPH) / SPEED_TO_MPH; + } + else { + var rounded = ToKmphRounded(speed); + return (rounded < UPPER_KMPH ? rounded + KMPH_STEP : UPPER_KMPH) / SPEED_TO_KMPH; + } + } + + /// + /// For US signs and MPH enabled, scale textures vertically by 1.25f. + /// Other signs are round. + /// + /// Multiplier for horizontal sign size + public static float GetVerticalTextureScale() { + return (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph && + GlobalConfig.Instance.Main.MphRoadSignStyle == MphSignStyle.SquareUS) + ? 1.25f + : 1.0f; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Traffic/ExtVehicleType.cs b/TLM/TLM/Traffic/ExtVehicleType.cs new file mode 100644 index 000000000..5feda1659 --- /dev/null +++ b/TLM/TLM/Traffic/ExtVehicleType.cs @@ -0,0 +1,53 @@ +namespace TrafficManager.Traffic { + using System; + + /// + /// This Enum is kept for save compatibility. + /// DO NOT USE. + /// Please use TMPE.API.Traffic.Enums.ExtVehicleType + /// + [Flags] + [Obsolete] + 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 + } + + public static class LegacyExtVehicleType { + public static API.Traffic.Enums.ExtVehicleType ToNew(ExtVehicleType old) { + return (API.Traffic.Enums.ExtVehicleType)(int)old; + } + + public static ExtVehicleType ToOld(API.Traffic.Enums.ExtVehicleType new_) { + return (ExtVehicleType)(int)new_; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Traffic/Impl/SegmentEnd.cs b/TLM/TLM/Traffic/Impl/SegmentEnd.cs index 507cf6775..64ecf6ab7 100644 --- a/TLM/TLM/Traffic/Impl/SegmentEnd.cs +++ b/TLM/TLM/Traffic/Impl/SegmentEnd.cs @@ -26,6 +26,9 @@ /// (having custom traffic lights or priority signs). /// namespace TrafficManager.Traffic.Impl { + using API.Traffic.Data; + using API.Traffic.Enums; + [Obsolete("should be removed when implementing issue #240")] public class SegmentEnd : SegmentEndId, ISegmentEnd { // TODO convert to struct @@ -71,7 +74,7 @@ public override string ToString() { public SegmentEnd(ushort segmentId, bool startNode) : base(segmentId, startNode) { Update(); } - + ~SegmentEnd() { //Destroy(); } @@ -119,7 +122,7 @@ public IDictionary[] MeasureOutgoingVehicles(bool includeStopped=t }); vehicleId = vehStateManager.ExtVehicles[vehicleId].nextVehicleIdOnSegment; - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } @@ -180,7 +183,7 @@ protected void MeasureOutgoingVehicle(bool debug, IDictionary[] re 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?) @@ -219,7 +222,7 @@ private void UnregisterAllVehicles() { int numIter = 0; while (segEndMan.ExtSegmentEnds[endIndex].firstVehicleId != 0) { extVehicleMan.Unlink(ref extVehicleMan.ExtVehicles[segEndMan.ExtSegmentEnds[endIndex].firstVehicleId]); - if (++numIter > VehicleManager.MAX_VEHICLE_COUNT) { + if (++numIter > Constants.ServiceFactory.VehicleService.MaxVehicleCount) { CODebugBase.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } @@ -269,7 +272,7 @@ private void RebuildVehicleNumDicts(ref NetNode node) { if (!segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segId, (bool)Constants.ServiceFactory.NetService.IsStartNode(segId, NodeId))].outgoing) { continue; } - + foreach (TinyDictionary numVehiclesMovingToSegId in numVehiclesMovingToSegmentId) { numVehiclesMovingToSegId[segId] = 0; } diff --git a/TLM/TLM/TrafficLight/Impl/CustomSegment.cs b/TLM/TLM/TrafficLight/Impl/CustomSegment.cs index 6b9cc5dee..8883a1e04 100644 --- a/TLM/TLM/TrafficLight/Impl/CustomSegment.cs +++ b/TLM/TLM/TrafficLight/Impl/CustomSegment.cs @@ -2,7 +2,9 @@ using TrafficManager.Geometry; namespace TrafficManager.TrafficLight.Impl { - class CustomSegment { + using API.TrafficLight; + + class CustomSegment { public ICustomSegmentLights StartNodeLights; public ICustomSegmentLights EndNodeLights; diff --git a/TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs b/TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs index c9a4b3fee..1175cf767 100644 --- a/TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs +++ b/TLM/TLM/TrafficLight/Impl/CustomSegmentLight.cs @@ -14,6 +14,8 @@ using TrafficManager.Traffic.Data; namespace TrafficManager.TrafficLight.Impl { + using API.Traffic.Enums; + /// /// Represents the traffic light (left, forward, right) at a specific segment end /// diff --git a/TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs b/TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs index bea223aa6..f0088360c 100644 --- a/TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs +++ b/TLM/TLM/TrafficLight/Impl/CustomSegmentLights.cs @@ -1,681 +1,680 @@ #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; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - 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 { - return Constants.ServiceFactory.NetService.GetSegmentNodeId(SegmentId, StartNode); - } - } - - 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) { + using System; + using System.Collections.Generic; + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using CSUtil.Commons; + using Geometry.Impl; + using Manager; + using State; + using Traffic.Data; + using Util; + + /// + /// 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 { + return Constants.ServiceFactory.NetService.GetSegmentNodeId(SegmentId, StartNode); + } + } + + 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}"); + 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 == Constants.ServiceFactory.NetService.GetSegmentNodeId(segmentId, true), 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) { + 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 == Constants.ServiceFactory.NetService.GetSegmentNodeId(segmentId, true), 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; - } + return MainSegmentLight; + } - ExtVehicleType? vehicleType = VehicleTypeByLaneIndex[laneIndex]; + ExtVehicleType? vehicleType = VehicleTypeByLaneIndex[laneIndex]; - if (vehicleType == null) { + if (vehicleType == null) { #if DEBUGGET Log._Debug($"CustomSegmentLights.GetCustomLight({laneIndex}): No vehicle type found for lane index: lane is invalid"); #endif - return MainSegmentLight; - } + return MainSegmentLight; + } #if DEBUGGET Log._Debug($"CustomSegmentLights.GetCustomLight({laneIndex}): Vehicle type is {vehicleType}"); #endif - ICustomSegmentLight light; - if (!CustomLights.TryGetValue((ExtVehicleType)vehicleType, out light)) { + 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; - } + 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); - } - - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { - CalculateAutoPedestrianLightState(ref node); - return true; - }); - } - - 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) { - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { - CalculateAutoPedestrianLightState(ref node); - return true; - }); - } - } - - public void CalculateAutoPedestrianLightState(ref NetNode node, bool propagate=true) { + 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); + } + + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { + CalculateAutoPedestrianLightState(ref node); + return true; + }); + } + + 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) { + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { + CalculateAutoPedestrianLightState(ref node); + return true; + }); + } + } + + public void CalculateAutoPedestrianLightState(ref NetNode node, bool propagate=true) { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + 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}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Calculating pedestrian light state of seg. {SegmentId} @ node {NodeId}"); #endif - IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - ExtSegment seg = segMan.ExtSegments[SegmentId]; - ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(SegmentId, StartNode)]; - - ushort nodeId = segEnd.nodeId; - if (nodeId != NodeId) { - Log.Warning($"CustomSegmentLights.CalculateAutoPedestrianLightState: Node id mismatch! segment end node is {nodeId} but we are node {NodeId}. segEnd={segEnd} this={this}"); - return; - } - - if (propagate) { - for (int i = 0; i < 8; ++i) { - ushort otherSegmentId = node.GetSegment(i); - if (otherSegmentId == 0 || otherSegmentId == SegmentId) - continue; - - ICustomSegmentLights otherLights = LightsManager.GetSegmentLights(nodeId, otherSegmentId); - if (otherLights == null) { + IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + ExtSegment seg = segMan.ExtSegments[SegmentId]; + ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(SegmentId, StartNode)]; + + ushort nodeId = segEnd.nodeId; + if (nodeId != NodeId) { + Log.Warning($"CustomSegmentLights.CalculateAutoPedestrianLightState: Node id mismatch! segment end node is {nodeId} but we are node {NodeId}. segEnd={segEnd} this={this}"); + return; + } + + if (propagate) { + for (int i = 0; i < 8; ++i) { + ushort otherSegmentId = node.GetSegment(i); + if (otherSegmentId == 0 || otherSegmentId == SegmentId) + 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}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Expected other (propagate) CustomSegmentLights at segment {otherSegmentId} @ {NodeId} but there was none. Original segment id: {SegmentId}"); #endif - continue; - } + continue; + } - otherLights.CalculateAutoPedestrianLightState(ref node, false); - } - } + otherLights.CalculateAutoPedestrianLightState(ref node, false); + } + } - if (IsAnyGreen()) { + if (IsAnyGreen()) { #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Any green at seg. {SegmentId} @ {NodeId}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Any green at seg. {SegmentId} @ {NodeId}"); #endif - AutoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; - return; - } + AutoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; + return; + } #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Querying incoming segments at seg. {SegmentId} @ {NodeId}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Querying incoming segments at seg. {SegmentId} @ {NodeId}"); #endif - ItemClass prevConnectionClass = null; - Constants.ServiceFactory.NetService.ProcessSegment(SegmentId, delegate (ushort prevSegId, ref NetSegment segment) { - prevConnectionClass = segment.Info.GetConnectionClass(); - return true; - }); + ItemClass prevConnectionClass = null; + Constants.ServiceFactory.NetService.ProcessSegment(SegmentId, delegate (ushort prevSegId, ref NetSegment segment) { + prevConnectionClass = segment.Info.GetConnectionClass(); + return true; + }); - RoadBaseAI.TrafficLightState autoPedestrianLightState = RoadBaseAI.TrafficLightState.Green; - bool lhd = Constants.ServiceFactory.SimulationService.LeftHandDrive; - if (!(segEnd.incoming && seg.oneWay)) { - for (int i = 0; i < 8; ++i) { - ushort otherSegmentId = node.GetSegment(i); - if (otherSegmentId == 0 || otherSegmentId == SegmentId) - continue; + RoadBaseAI.TrafficLightState autoPedestrianLightState = RoadBaseAI.TrafficLightState.Green; + bool lhd = Constants.ServiceFactory.SimulationService.LeftHandDrive; + if (!(segEnd.incoming && seg.oneWay)) { + for (int i = 0; i < 8; ++i) { + ushort otherSegmentId = node.GetSegment(i); + if (otherSegmentId == 0 || otherSegmentId == SegmentId) + continue; - //ExtSegment otherSeg = segMan.ExtSegments[otherSegmentId]; + //ExtSegment otherSeg = segMan.ExtSegments[otherSegmentId]; - if (!segEndMan.ExtSegmentEnds[segEndMan.GetIndex(otherSegmentId, (bool)Constants.ServiceFactory.NetService.IsStartNode(otherSegmentId, NodeId))].incoming) { - continue; - } + if (!segEndMan.ExtSegmentEnds[segEndMan.GetIndex(otherSegmentId, (bool)Constants.ServiceFactory.NetService.IsStartNode(otherSegmentId, NodeId))].incoming) { + continue; + } #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Checking incoming straight segment {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); + 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) { + 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}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Expected other (straight) CustomSegmentLights at segment {otherSegmentId} @ {NodeId} but there was none. Original segment id: {SegmentId}"); #endif - continue; - } + continue; + } - ItemClass nextConnectionClass = null; - Constants.ServiceFactory.NetService.ProcessSegment(otherSegmentId, delegate (ushort otherSegId, ref NetSegment segment) { - nextConnectionClass = segment.Info.GetConnectionClass(); - return true; - }); + 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 (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."); + 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; - } + continue; + } - ArrowDirection dir = segEndMan.GetDirection(ref segEnd, otherSegmentId); - if (dir == ArrowDirection.Forward) { - if (!otherLights.IsAllMainRed()) { + ArrowDirection dir = segEndMan.GetDirection(ref segEnd, otherSegmentId); + if (dir == ArrowDirection.Forward) { + if (!otherLights.IsAllMainRed()) { #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Not all main red at {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Not all main red at {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); #endif - autoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; - break; - } - } else if ((dir == ArrowDirection.Left && lhd) || (dir == ArrowDirection.Right && !lhd)) { - if ((lhd && !otherLights.IsAllRightRed()) || (!lhd && !otherLights.IsAllLeftRed())) { + autoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; + break; + } + } else if ((dir == ArrowDirection.Left && lhd) || (dir == ArrowDirection.Right && !lhd)) { + 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}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Not all left red at {otherSegmentId} at seg. {SegmentId} @ {NodeId}"); #endif - autoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; - break; - } - } - } - } - - AutoPedestrianLightState = autoPedestrianLightState; + autoPedestrianLightState = RoadBaseAI.TrafficLightState.Red; + break; + } + } + } + } + + AutoPedestrianLightState = autoPedestrianLightState; #if DEBUGTTL - if (debug) - Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Calculated AutoPedestrianLightState for segment {SegmentId} @ {NodeId}: {AutoPedestrianLightState}"); + if (debug) + Log._Debug($"CustomSegmentLights.CalculateAutoPedestrianLightState: Calculated AutoPedestrianLightState for segment {SegmentId} @ {NodeId}: {AutoPedestrianLightState}"); #endif - } + } - // TODO improve & remove - public void Housekeeping(bool mayDelete, bool calculateAutoPedLight) { + // TODO improve & remove + public void Housekeeping(bool mayDelete, bool calculateAutoPedLight) { #if DEBUGHK - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + 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) + // 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; + 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}"); + 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); + //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}"); + 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 (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."); + 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; - } + // no vehicle restrictions applied, generic lights are handled further below + ++defaultLanes; + continue; + } - ExtVehicleType mask = allowedTypes & ~ExtVehicleType.Emergency; + 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"); + 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); - } + 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})"); + 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); - } + CustomLights.Add(mask, segmentLight); + VehicleTypes.AddFirst(mask); + } - mainVehicleType = mask; - VehicleTypeByLaneIndex[laneIndex] = mask; - laneIndicesWithoutSeparateLights.Remove(laneIndex); - ++separateLanes; - //addPedestrianLight = true; - setupLights.Add(mask); - SeparateVehicleTypes |= 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}"); + 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 (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}"); + 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; - } + // 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}"); + 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; - } + //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()}"); + 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 (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()}"); + 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); - } - } + 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 (CustomLights.ContainsKey(DEFAULT_MAIN_VEHICLETYPE) && VehicleTypes.First.Value != DEFAULT_MAIN_VEHICLETYPE) { + VehicleTypes.Remove(DEFAULT_MAIN_VEHICLETYPE); + VehicleTypes.AddFirst(DEFAULT_MAIN_VEHICLETYPE); + } - //if (addPedestrianLight) { + //if (addPedestrianLight) { #if DEBUGHK - if (debug) - Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: adding pedestrian light"); + 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 (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()}"); + if (debug) + Log._Debug($"CustomSegmentLights.Housekeeping({mayDelete}, {calculateAutoPedLight}): housekeeping @ seg. {SegmentId}, node {nodeId}: Housekeeping complete. VehicleTypeByLaneIndex={VehicleTypeByLaneIndex.ArrayToString()} CustomLights={CustomLights.DictionaryToString()}"); #endif - } - } -} + } + } +} \ No newline at end of file diff --git a/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs b/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs index 0bcb59de0..f0517d42b 100644 --- a/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs +++ b/TLM/TLM/TrafficLight/Impl/TimedTrafficLights.cs @@ -1,498 +1,494 @@ #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; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - 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]; - - Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { - UpdateDirections(ref node); - UpdateSegmentEnds(ref node); - return true; - }); - - 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]; - - bool targetStartNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(targetSegmentId, 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]; - - 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, (bool)Constants.ServiceFactory.NetService.IsStartNode(targetSegmentId, 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(ref NetNode node) { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using API.Traffic.Enums; + using API.TrafficLight; + using CSUtil.Commons; + using GenericGameBridge.Service; + using Geometry.Impl; + using Manager; + using Manager.Impl; + using State; + using Traffic; + using Util; + + // 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]; + + Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { + UpdateDirections(ref node); + UpdateSegmentEnds(ref node); + return true; + }); + + 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]; + + bool targetStartNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(targetSegmentId, 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]; + + 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, (bool)Constants.ServiceFactory.NetService.IsStartNode(targetSegmentId, 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(ref NetNode node) { #if DEBUG - bool debug = GlobalConfig.Instance.Debug.Switches[7] && (GlobalConfig.Instance.Debug.NodeId == 0 || GlobalConfig.Instance.Debug.NodeId == NodeId); - if (debug) - Log._Debug($">>>>> TimedTrafficLights.UpdateDirections: called for node {NodeId}"); + bool debug = GlobalConfig.Instance.Debug.Switches[7] && (GlobalConfig.Instance.Debug.NodeId == 0 || GlobalConfig.Instance.Debug.NodeId == NodeId); + if (debug) + Log._Debug($">>>>> TimedTrafficLights.UpdateDirections: called for node {NodeId}"); #endif - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - Directions = new TinyDictionary>(); - for (int i = 0; i < 8; ++i) { - ushort sourceSegmentId = node.GetSegment(i); + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + Directions = new TinyDictionary>(); + for (int i = 0; i < 8; ++i) { + ushort sourceSegmentId = node.GetSegment(i); - if (sourceSegmentId == 0) - continue; + if (sourceSegmentId == 0) + continue; #if DEBUG - if (debug) - Log._Debug($"TimedTrafficLights.UpdateDirections: Processing source segment {sourceSegmentId}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateDirections: Processing source segment {sourceSegmentId}"); #endif - IDictionary dirs = new TinyDictionary(); - Directions.Add(sourceSegmentId, dirs); - int endIndex = segEndMan.GetIndex(sourceSegmentId, (bool)Constants.ServiceFactory.NetService.IsStartNode(sourceSegmentId, NodeId)); + IDictionary dirs = new TinyDictionary(); + Directions.Add(sourceSegmentId, dirs); + int endIndex = segEndMan.GetIndex(sourceSegmentId, (bool)Constants.ServiceFactory.NetService.IsStartNode(sourceSegmentId, NodeId)); - for (int k = 0; k < 8; ++k) { - ushort targetSegmentId = node.GetSegment(k); + for (int k = 0; k < 8; ++k) { + ushort targetSegmentId = node.GetSegment(k); - if (targetSegmentId == 0) - continue; + if (targetSegmentId == 0) + continue; - ArrowDirection dir = segEndMan.GetDirection(ref segEndMan.ExtSegmentEnds[endIndex], targetSegmentId); - dirs.Add(targetSegmentId, dir); + ArrowDirection dir = segEndMan.GetDirection(ref segEndMan.ExtSegmentEnds[endIndex], targetSegmentId); + dirs.Add(targetSegmentId, dir); #if DEBUG - if (debug) - Log._Debug($"TimedTrafficLights.UpdateDirections: Processing source segment {sourceSegmentId}, target segment {targetSegmentId}: adding dir {dir}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateDirections: Processing source segment {sourceSegmentId}, target segment {targetSegmentId}: adding dir {dir}"); #endif - } - } + } + } #if DEBUG - if (debug) - Log._Debug($"<<<<< TimedTrafficLights.UpdateDirections: finished for node {NodeId}: {Directions.DictionaryToString()}"); + if (debug) + Log._Debug($"<<<<< TimedTrafficLights.UpdateDirections: finished for node {NodeId}: {Directions.DictionaryToString()}"); #endif - } - - 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; - - //Log._Debug($"Checking for invalid pedestrian lights @ {NodeId}."); - for (int i = 0; i < 8; ++i) { - ushort segmentId = 0; - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { - segmentId = node.GetSegment(i); - return true; - }); - - if (segmentId == 0) { - continue; - } - - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); - - ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(segmentId, startNode); - if (lights == null) { - Log.Warning($"TimedTrafficLights.CheckInvalidPedestrianLights() @ node {NodeId}: Could not retrieve segment lights for segment {segmentId} @ start {startNode}."); - continue; - } - - //Log._Debug($"Checking seg. {segmentId} @ {NodeId}."); - bool needsAlwaysGreenPedestrian = true; - int s = 0; - foreach (TimedTrafficLightsStep step in Steps) { - //Log._Debug($"Checking step {s}, seg. {segmentId} @ {NodeId}."); - if (!step.CustomSegmentLights.ContainsKey(segmentId)) { - //Log._Debug($"Step {s} @ {NodeId} does not contain a segment light for seg. {segmentId}."); - ++s; - continue; - } - //Log._Debug($"Checking step {s}, 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[segmentId].PedestrianLightState == RoadBaseAI.TrafficLightState.Green) { - //Log._Debug($"Step {s} @ {NodeId} has a green ped. light @ seg. {segmentId}."); - needsAlwaysGreenPedestrian = false; - break; - } - ++s; - } - //Log._Debug($"Setting InvalidPedestrianLight of seg. {segmentId} @ {NodeId} to {needsAlwaysGreenPedestrian}."); - lights.InvalidPedestrianLight = needsAlwaysGreenPedestrian; - } - } - - private void ClearInvalidPedestrianLights() { - ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; - - for (int i = 0; i < 8; ++i) { - ushort segmentId = 0; - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { - segmentId = node.GetSegment(i); - return true; - }); - - if (segmentId == 0) { - continue; - } - - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); - - ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(segmentId, startNode); - if (lights == null) { - Log.Warning($"TimedTrafficLights.ClearInvalidPedestrianLights() @ node {NodeId}: Could not retrieve segment lights for segment {segmentId} @ start {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 + } + + 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; + + //Log._Debug($"Checking for invalid pedestrian lights @ {NodeId}."); + for (int i = 0; i < 8; ++i) { + ushort segmentId = 0; + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { + segmentId = node.GetSegment(i); + return true; + }); + + if (segmentId == 0) { + continue; + } + + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); + + ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(segmentId, startNode); + if (lights == null) { + Log.Warning($"TimedTrafficLights.CheckInvalidPedestrianLights() @ node {NodeId}: Could not retrieve segment lights for segment {segmentId} @ start {startNode}."); + continue; + } + + //Log._Debug($"Checking seg. {segmentId} @ {NodeId}."); + bool needsAlwaysGreenPedestrian = true; + int s = 0; + foreach (TimedTrafficLightsStep step in Steps) { + //Log._Debug($"Checking step {s}, seg. {segmentId} @ {NodeId}."); + if (!step.CustomSegmentLights.ContainsKey(segmentId)) { + //Log._Debug($"Step {s} @ {NodeId} does not contain a segment light for seg. {segmentId}."); + ++s; + continue; + } + //Log._Debug($"Checking step {s}, 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[segmentId].PedestrianLightState == RoadBaseAI.TrafficLightState.Green) { + //Log._Debug($"Step {s} @ {NodeId} has a green ped. light @ seg. {segmentId}."); + needsAlwaysGreenPedestrian = false; + break; + } + ++s; + } + //Log._Debug($"Setting InvalidPedestrianLight of seg. {segmentId} @ {NodeId} to {needsAlwaysGreenPedestrian}."); + lights.InvalidPedestrianLight = needsAlwaysGreenPedestrian; + } + } + + private void ClearInvalidPedestrianLights() { + ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; + + for (int i = 0; i < 8; ++i) { + ushort segmentId = 0; + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { + segmentId = node.GetSegment(i); + return true; + }); + + if (segmentId == 0) { + continue; + } + + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); + + ICustomSegmentLights lights = customTrafficLightsManager.GetSegmentLights(segmentId, startNode); + if (lights == null) { + Log.Warning($"TimedTrafficLights.ClearInvalidPedestrianLights() @ node {NodeId}: Could not retrieve segment lights for segment {segmentId} @ start {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; - } + 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 + 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 + 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; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif - if (!IsMasterNode() || !IsStarted()) { + if (!IsMasterNode() || !IsStarted()) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} isMasterNode={IsMasterNode()} IsStarted={IsStarted()}"); + if (debug) + Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} isMasterNode={IsMasterNode()} IsStarted={IsStarted()}"); #endif - return; - } - // we are the master node + return; + } + // we are the master node - /*if (!housekeeping()) { + /*if (!housekeeping()) { #if DEBUGTTL - Log.Warning($"TTL SimStep: *STOP* NodeId={NodeId} Housekeeping detected that this timed traffic light has become invalid: {NodeId}."); + Log.Warning($"TTL SimStep: *STOP* NodeId={NodeId} Housekeeping detected that this timed traffic light has become invalid: {NodeId}."); #endif - Stop(); - return; - }*/ + Stop(); + return; + }*/ #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={NodeId} Setting lights (1)"); + 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(); + SetLights(); #if BENCHMARK //} #endif @@ -500,135 +496,135 @@ public void SimulationStep() { #if BENCHMARK //using (var bm = new Benchmark(null, "StepDone")) { #endif - if (!Steps[CurrentStep].StepDone(true)) { + if (!Steps[CurrentStep].StepDone(true)) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} current step ({CurrentStep}) is not done."); + if (debug) + Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} current step ({CurrentStep}) is not done."); #endif - return; - } + return; + } #if BENCHMARK //} #endif - // step is done + // step is done #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={NodeId} Setting lights (2)"); + if (debug) + Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: NodeId={NodeId} Setting lights (2)"); #endif - TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; - if (Steps[CurrentStep].NextStepRefIndex < 0) { + 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."); - } + 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) { + // 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; + // 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; + // 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})."); - } + 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); + 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; - } + //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}"); - } + 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; - } + 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(); + nextStepIndex = (nextStepIndex + 1) % NumSteps(); - if (Steps[nextStepIndex].ChangeMetric != Steps[CurrentStep].ChangeMetric) { - break; - } - } + if (Steps[nextStepIndex].ChangeMetric != Steps[CurrentStep].ChangeMetric) { + break; + } + } - if (bestNextStepIndex == CurrentStep) { + if (bestNextStepIndex == CurrentStep) { #if DEBUGTTL - if (debug) { - Log._Debug($"TimedTrafficLights.SimulationStep(): Best next step {bestNextStepIndex} (wait/flow diff = {maxWaitFlowDiff}) equals CurrentStep @ node {NodeId}."); - } + 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 { + // 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}."); - } + 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; - } + // 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; - } - } + ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; + slaveTTL.GetStep(CurrentStep).NextStepRefIndex = bestNextStepIndex; + } + } #if BENCHMARK //} #endif - } else { - Steps[CurrentStep].NextStepRefIndex = nextStepIndex; - } - } + } else { + Steps[CurrentStep].NextStepRefIndex = nextStepIndex; + } + } #if BENCHMARK //using (var bm = new Benchmark(null, "SetLights.2")) { #endif - SetLights(); // check if this is needed + SetLights(); // check if this is needed #if BENCHMARK //} #endif @@ -636,554 +632,559 @@ public void SimulationStep() { #if BENCHMARK //using (var bm = new Benchmark(null, "IsEndTransitionDone")) { #endif - if (!Steps[CurrentStep].IsEndTransitionDone()) { + 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."); + if (debug) + Log._Debug($"TimedTrafficLights.SimulationStep(): TTL SimStep: *STOP* NodeId={NodeId} current step ({CurrentStep}): end transition is not done."); #endif - return; - } + return; + } #if BENCHMARK //} #endif - // ending transition (yellow) finished + // 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()}"); + 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; + // change step + int newStepIndex = Steps[CurrentStep].NextStepRefIndex; + int oldStepIndex = CurrentStep; - foreach (ushort slaveNodeId in NodeGroup) { - if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { - continue; - } + foreach (ushort slaveNodeId in NodeGroup) { + if (!tlsMan.TrafficLightSimulations[slaveNodeId].IsTimedLight()) { + continue; + } - ITimedTrafficLights slaveTTL = tlsMan.TrafficLightSimulations[slaveNodeId].timedLight; - slaveTTL.CurrentStep = newStepIndex; + 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}"); + 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(); - } + 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}"); + } + + 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, + API.Traffic.Enums.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; - } - 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() { - Log._Trace($"TimedTrafficLights.OnGeometryUpdate: called for timed traffic light @ {NodeId}."); - - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { - UpdateDirections(ref node); - UpdateSegmentEnds(ref node); - return true; - }); - - if (NumSteps() <= 0) { - Log._Debug($"TimedTrafficLights.OnGeometryUpdate: no steps @ {NodeId}"); - return; - } - - Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { - BackUpInvalidStepSegments(ref node); - HandleNewSegments(ref node); - return true; - }); - } - - /// - /// Moves all custom segment lights that are associated with an invalid segment to a special container for later reuse - /// - private void BackUpInvalidStepSegments(ref NetNode node) { + } + + 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() { + Log._Trace($"TimedTrafficLights.OnGeometryUpdate: called for timed traffic light @ {NodeId}."); + + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { + UpdateDirections(ref node); + UpdateSegmentEnds(ref node); + return true; + }); + + if (NumSteps() <= 0) { + Log._Debug($"TimedTrafficLights.OnGeometryUpdate: no steps @ {NodeId}"); + return; + } + + Constants.ServiceFactory.NetService.ProcessNode(NodeId, delegate (ushort nId, ref NetNode node) { + BackUpInvalidStepSegments(ref node); + HandleNewSegments(ref node); + return true; + }); + } + + /// + /// Moves all custom segment lights that are associated with an invalid segment to a special container for later reuse + /// + private void BackUpInvalidStepSegments(ref NetNode node) { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; - if (debug) - Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: called for timed traffic light @ {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: called for timed traffic light @ {NodeId}"); #endif - ICollection validSegments = new HashSet(); - for (int k = 0; k < 8; ++k) { - ushort segmentId = node.GetSegment(k); - - if (segmentId == 0) { - continue; - } + ICollection validSegments = new HashSet(); + for (int k = 0; k < 8; ++k) { + ushort segmentId = node.GetSegment(k); + + if (segmentId == 0) { + continue; + } - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); - validSegments.Add(segmentId); - } + validSegments.Add(segmentId); + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: valid segments @ {NodeId}: {validSegments.CollectionToString()}"); + 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); + 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}"); + if (debug) + Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: Detected invalid segment @ step {i}, node {NodeId}: {e.Key}"); #endif - } - } + } + } - foreach (ushort invalidSegmentId in invalidSegmentIds) { + foreach (ushort invalidSegmentId in invalidSegmentIds) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: Removing invalid segment {invalidSegmentId} from step {i} @ node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.BackUpInvalidStepSegments: Removing invalid segment {invalidSegmentId} from step {i} @ node {NodeId}"); #endif - step.CustomSegmentLights.Remove(invalidSegmentId); - } + 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()}"); + 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; - } - } + ++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(ref NetNode node) { + /// + /// 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(ref NetNode node) { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif - ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; - ITrafficPriorityManager prioMan = Constants.ManagerFactory.TrafficPriorityManager; + ICustomSegmentLightsManager customTrafficLightsManager = Constants.ManagerFactory.CustomSegmentLightsManager; + ITrafficPriorityManager prioMan = Constants.ManagerFactory.TrafficPriorityManager; - //Log._Debug($"Checking for invalid pedestrian lights @ {NodeId}."); - for (int k = 0; k < 8; ++k) { - ushort segmentId = node.GetSegment(k); + //Log._Debug($"Checking for invalid pedestrian lights @ {NodeId}."); + for (int k = 0; k < 8; ++k) { + ushort segmentId = node.GetSegment(k); - if (segmentId == 0) { - continue; - } + if (segmentId == 0) { + continue; + } - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.HandleNewSegments: handling existing seg. {segmentId} @ {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.HandleNewSegments: handling existing seg. {segmentId} @ {NodeId}"); #endif - if (Steps[0].CustomSegmentLights.ContainsKey(segmentId)) { - continue; - } + if (Steps[0].CustomSegmentLights.ContainsKey(segmentId)) { + continue; + } - // segment was created - RotationOffset = 0; + // segment was created + RotationOffset = 0; #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.HandleNewSegments: New segment detected: {segmentId} @ {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.HandleNewSegments: New segment detected: {segmentId} @ {NodeId}"); #endif - int stepIndex = -1; - foreach (TimedTrafficLightsStep step in Steps) { - ++stepIndex; + 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 + 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 {segmentId} to node {NodeId} without reusing old segment"); + if (debug) + Log._Debug($"TimedTrafficLights.HandleNewSegments: Adding new segment {segmentId} to node {NodeId} without reusing old segment"); #endif - if (! step.AddSegment(segmentId, startNode, true)) { + if (! step.AddSegment(segmentId, startNode, true)) { #if DEBUGTTL - if (debug) - Log.Warning($"TimedTrafficLights.HandleNewSegments: Failed to add segment {segmentId} @ start {startNode} to node {NodeId}"); + if (debug) + Log.Warning($"TimedTrafficLights.HandleNewSegments: Failed to add segment {segmentId} @ start {startNode} to node {NodeId}"); #endif - } - } else { - // reuse old lights - step.InvalidSegmentLights.RemoveFirst(); - ICustomSegmentLights lightsToReuse = lightsToReuseNode.Value; + } + } else { + // reuse old lights + step.InvalidSegmentLights.RemoveFirst(); + ICustomSegmentLights lightsToReuse = lightsToReuseNode.Value; #if DEBUGTTL - if (debug) - Log._Debug($"Replacing old segment @ {NodeId} with new segment {segmentId}"); + if (debug) + Log._Debug($"Replacing old segment @ {NodeId} with new segment {segmentId}"); #endif - lightsToReuse.Relocate(segmentId, startNode); - step.SetSegmentLights(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) { + lightsToReuse.Relocate(segmentId, startNode); + step.SetSegmentLights(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, + API.Traffic.Enums.ExtVehicleType vehicleType, + LightMode mode) { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; #endif - if (! Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { + if (! Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { #if DEBUGTTL - if (debug) - Log.Error($"TimedTrafficLights.ChangeLightMode: Segment {segmentId} is invalid"); + if (debug) + Log.Error($"TimedTrafficLights.ChangeLightMode: Segment {segmentId} is invalid"); #endif - return; - } - - bool? startNode = Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); - if (startNode == null) { - return; - } - - foreach (TimedTrafficLightsStep step in Steps) { - step.ChangeLightMode(segmentId, vehicleType, mode); - } - Constants.ManagerFactory.CustomSegmentLightsManager.SetLightMode(segmentId, (bool)startNode, 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(ref NetNode node) { + return; + } + + bool? startNode = Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); + if (startNode == null) { + return; + } + + foreach (TimedTrafficLightsStep step in Steps) { + step.ChangeLightMode(segmentId, vehicleType, mode); + } + Constants.ManagerFactory.CustomSegmentLightsManager.SetLightMode(segmentId, (bool)startNode, 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(ref NetNode node) { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: called for node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: called for node {NodeId}"); #endif - ISegmentEndManager segEndMan = Constants.ManagerFactory.SegmentEndManager; + ISegmentEndManager segEndMan = Constants.ManagerFactory.SegmentEndManager; - ICollection segmentEndsToDelete = new HashSet(); - // update currently set segment ends - foreach (ISegmentEndId endId in segmentEndIds) { + ICollection segmentEndsToDelete = new HashSet(); + // update currently set segment ends + foreach (ISegmentEndId endId in segmentEndIds) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: updating existing segment end {endId} for node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: updating existing segment end {endId} for node {NodeId}"); #endif - if (!segEndMan.UpdateSegmentEnd(endId)) { + if (!segEndMan.UpdateSegmentEnd(endId)) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: segment end {endId} @ node {NodeId} is invalid"); + 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) { + 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."); + 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 { + segmentEndsToDelete.Add(endId); + } else { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: segment end {endId} @ node {NodeId} is valid"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: segment end {endId} @ node {NodeId} is valid"); #endif - } - } - } + } + } + } - // remove all invalid segment ends - foreach (ISegmentEndId endId in segmentEndsToDelete) { + // remove all invalid segment ends + foreach (ISegmentEndId endId in segmentEndsToDelete) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Removing invalid segment end {endId} @ node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Removing invalid segment end {endId} @ node {NodeId}"); #endif - segmentEndIds.Remove(endId); - } + segmentEndIds.Remove(endId); + } - // set up new segment ends + // set up new segment ends #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Setting up new segment ends @ node {NodeId}."); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Setting up new segment ends @ node {NodeId}."); #endif - for (int i = 0; i < 8; ++i) { - ushort segmentId = node.GetSegment(i); + for (int i = 0; i < 8; ++i) { + ushort segmentId = node.GetSegment(i); - if (segmentId == 0) { - continue; - } + if (segmentId == 0) { + continue; + } - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); - ISegmentEndId endId = new SegmentEndId(segmentId, startNode); + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, NodeId); + ISegmentEndId endId = new SegmentEndId(segmentId, startNode); - if (segmentEndIds.Contains(endId)) { + if (segmentEndIds.Contains(endId)) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Node {NodeId} already knows segment {segmentId}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Node {NodeId} already knows segment {segmentId}"); #endif - continue; - } + continue; + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Adding segment {segmentId} to node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: Adding segment {segmentId} to node {NodeId}"); #endif - ISegmentEnd end = segEndMan.GetOrAddSegmentEnd(segmentId, startNode); - if (end != null) { - segmentEndIds.Add(end); - } else { + ISegmentEnd end = segEndMan.GetOrAddSegmentEnd(segmentId, startNode); + if (end != null) { + segmentEndIds.Add(end); + } else { #if DEBUGTTL - if (debug) - Log.Warning($"TimedTrafficLights.UpdateSegmentEnds: Failed to add segment end {segmentId} @ {startNode} to node {NodeId}: GetOrAddSegmentEnd returned null."); + if (debug) + Log.Warning($"TimedTrafficLights.UpdateSegmentEnds: Failed to add segment end {segmentId} @ {startNode} to node {NodeId}: GetOrAddSegmentEnd returned null."); #endif - } - } + } + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: finished for node {NodeId}: {segmentEndIds.CollectionToString()}"); + if (debug) + Log._Debug($"TimedTrafficLights.UpdateSegmentEnds: finished for node {NodeId}: {segmentEndIds.CollectionToString()}"); #endif - } + } - private void DestroySegmentEnds() { + private void DestroySegmentEnds() { #if DEBUGTTL - bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; + bool debug = GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == NodeId; - if (debug) - Log._Debug($"TimedTrafficLights.DestroySegmentEnds: Destroying segment ends @ node {NodeId}"); + if (debug) + Log._Debug($"TimedTrafficLights.DestroySegmentEnds: Destroying segment ends @ node {NodeId}"); #endif - foreach (ISegmentEndId endId in segmentEndIds) { + foreach (ISegmentEndId endId in segmentEndIds) { #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLights.DestroySegmentEnds: Destroying segment end {endId} @ node {NodeId}"); + 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(); + // 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}"); + if (debug) + Log._Debug($"TimedTrafficLights.DestroySegmentEnds: finished for node {NodeId}"); #endif - } - } -} + } + } +} \ No newline at end of file diff --git a/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs b/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs index d812b107e..9f1ed9946 100644 --- a/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs +++ b/TLM/TLM/TrafficLight/Impl/TimedTrafficLightsStep.cs @@ -2,1097 +2,1100 @@ #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; -using TrafficManager.Traffic.Enums; - 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; - - for (int i = 0; i < 8; ++i) { - ushort segmentId = 0; - Constants.ServiceFactory.NetService.ProcessNode(timedNode.NodeId, delegate (ushort nId, ref NetNode node) { - segmentId = node.GetSegment(i); - return true; - }); - - if (segmentId == 0) { - continue; - } - - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, timedNode.NodeId); - if (!AddSegment(segmentId, startNode, makeRed)) { - Log.Warning($"TimedTrafficLightsStep.ctor: Failed to add segment {segmentId} @ start {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); + using System; + using System.Collections.Generic; + using System.Linq; + using API.Manager; + using API.Traffic.Enums; + using API.TrafficLight; + using CSUtil.Commons; + using Manager; + using Manager.Impl; + using State; + using Traffic; + using Util; + + // 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; + + for (int i = 0; i < 8; ++i) { + ushort segmentId = 0; + Constants.ServiceFactory.NetService.ProcessNode(timedNode.NodeId, delegate (ushort nId, ref NetNode node) { + segmentId = node.GetSegment(i); + return true; + }); + + if (segmentId == 0) { + continue; + } + + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, timedNode.NodeId); + if (!AddSegment(segmentId, startNode, makeRed)) { + Log.Warning($"TimedTrafficLightsStep.ctor: Failed to add segment {segmentId} @ start {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}"); + 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); + 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}"); + 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; - } + return ret; + } - public bool IsInStartTransition() { - if (!timedNode.IsMasterNode()) { - ITimedTrafficLights masterLights = timedNode.MasterLights(); - return masterLights.GetStep(masterLights.CurrentStep).IsInStartTransition(); - } + public bool IsInStartTransition() { + if (!timedNode.IsMasterNode()) { + ITimedTrafficLights masterLights = timedNode.MasterLights(); + return masterLights.GetStep(masterLights.CurrentStep).IsInStartTransition(); + } - bool ret = getCurrentFrame() == startFrame && !stepDone; //!StepDone(false); + 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}"); + 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) { + return ret; + } + + public RoadBaseAI.TrafficLightState GetLightState(ushort segmentId, + API.Traffic.Enums.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}"); + 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; + 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()}"); - } - } - }*/ + /*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 + } + + 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; - } + 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 (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; - } + 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}"); + //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; + 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}"); + //Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) -> segmentId={segmentId} @ NodeId={timedNode.NodeId}"); #endif - ICustomSegmentLights prevStepSegmentLights = null; - if (!previousStep.CustomSegmentLights.TryGetValue(segmentId, out prevStepSegmentLights)) { + 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}!"); + 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; - } + continue; + } - ICustomSegmentLights nextStepSegmentLights = null; - if (!nextStep.CustomSegmentLights.TryGetValue(segmentId, out nextStepSegmentLights)) { + 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}!"); + 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; - } + continue; + } - //segLightState.makeRedOrGreen(); // TODO temporary fix + //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; - } + 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}"); + 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()}"); + 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; - } + 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) { + foreach (var vehicleType in curStepSegmentLights.VehicleTypes) { #if DEBUG - //Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) -> segmentId={segmentId} @ NodeId={timedNode.NodeId} for vehicle {vehicleType}"); + //Log._Debug($"TimedTrafficLightsStep.SetLights({noTransition}) -> segmentId={segmentId} @ NodeId={timedNode.NodeId} for vehicle {vehicleType}"); #endif - ICustomSegmentLight liveSegmentLight = liveSegmentLights.GetCustomLight(vehicleType); - if (liveSegmentLight == null) { + 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."); + 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); - + 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; - } + 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();*/ + 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); + 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}"); + 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) { + //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}"); - } + 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 (!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}"); + 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= 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 (updateValues) { + lastFlowWaitCalc = curFrame; + //Log._Debug($"TTL @ {timedNode.NodeId}: updated lastFlowWaitCalc=curFrame={curFrame}"); + } + } else { + flow = CurrentFlow; + wait = CurrentWait; + //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 - } + 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; + // 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}"); + //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 (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); + //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 (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}"); - } + 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)) { + // 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}"); - } + 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; + 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); + + var 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())}"); - } + 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) { + uint numSegFlows = 0; + uint numSegWaits = 0; + float curTotalSegFlow = 0; + float curTotalSegWait = 0; + // loop over source lanes + for (byte laneIndex = 0; laneIndex < vehTypeByLaneIndex.Length; ++laneIndex) { + var 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}!"); + Log.Warning($"Timed traffic light step: Failed to get custom light for vehicleType {vehicleType} @ seg. {sourceSegmentId}, node {timedNode.NodeId}!"); #endif - continue; - } + continue; + } - IDictionary movingVehiclesMetric = countOnlyMovingIfGreenOnSegment ? movingVehiclesMetrics[laneIndex] : null; - IDictionary allVehiclesMetric = allVehiclesMetrics[laneIndex]; - if (allVehiclesMetrics == null) { + 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}"); - } + if (debug) { + Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: No cars on lane {laneIndex} @ seg. {sourceSegmentId}. Vehicle types: {vehicleType}"); + } #endif - continue; - } + continue; + } #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Checking lane {laneIndex} @ seg. {sourceSegmentId}. Vehicle types: {vehicleType}"); + 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; + // 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; - } + 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; + 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})"); + 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; + 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}"); + 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; + } 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}"); + 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}"); + 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; - } - } + } // 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}"); + 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; - } - } + } // 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}"); + 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; - } - } + } // 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}"); + 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 + } // 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 + 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; + wait = (float)meanWait; + flow = meanFlow; #if DEBUGTTL - if (debug) - Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: ***CALCULATION FINISHED*** for master node {timedNode.NodeId}: flow={flow} wait={wait}"); + 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) { + } + + internal void ChangeLightMode(ushort segmentId, + API.Traffic.Enums.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."); + 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; - } + ICustomSegmentLights ret = null; + if (CustomSegmentLights.TryGetValue(segmentId, out ret)) { + CustomSegmentLights.Remove(segmentId); + } + return ret; + } - public ICustomSegmentLights GetSegmentLights(ushort segmentId) { + 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."); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.GetSegmentLights({segmentId}) called."); #endif - return GetSegmentLights(timedNode.NodeId, segmentId); - } + return GetSegmentLights(timedNode.NodeId, segmentId); + } - public ICustomSegmentLights GetSegmentLights(ushort nodeId, ushort 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."); + 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 (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."); + 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; - } - - if (! Constants.ServiceFactory.NetService.IsSegmentValid(targetSegmentId)) { - Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Target segment {targetSegmentId} is invalid"); - return false; - } - - bool? startNode = Constants.ServiceFactory.NetService.IsStartNode(targetSegmentId, timedNode.NodeId); - if (startNode == null) { - Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Node {timedNode.NodeId} is neither start nor end node of target segment {targetSegmentId}"); - return false; - } - - CustomSegmentLights.Remove(sourceSegmentId); - Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(targetSegmentId, (bool)startNode).Housekeeping(true, true); - sourceLights.Relocate(targetSegmentId, (bool)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) { + 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; + } + + if (! Constants.ServiceFactory.NetService.IsSegmentValid(targetSegmentId)) { + Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Target segment {targetSegmentId} is invalid"); + return false; + } + + bool? startNode = Constants.ServiceFactory.NetService.IsStartNode(targetSegmentId, timedNode.NodeId); + if (startNode == null) { + Log.Error($"TimedTrafficLightsStep.RelocateSegmentLights({sourceSegmentId}, {targetSegmentId}): Node {timedNode.NodeId} is neither start nor end node of target segment {targetSegmentId}"); + return false; + } + + CustomSegmentLights.Remove(sourceSegmentId); + Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(targetSegmentId, (bool)startNode).Housekeeping(true, true); + sourceLights.Relocate(targetSegmentId, (bool)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}."); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}) called @ node {timedNode.NodeId}."); #endif - if (!Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { - Log.Error($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}): Segment {segmentId} is invalid"); - return false; - } + if (!Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { + Log.Error($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}): Segment {segmentId} is invalid"); + return false; + } - if (Constants.ServiceFactory.NetService.GetSegmentNodeId(segmentId, startNode) != timedNode.NodeId) { - Log.Error($"TimedTrafficLightsStep.AddSegment({segmentId}, {startNode}, {makeRed}): Segment {segmentId} is not connected to node {timedNode.NodeId} @ start {startNode}"); - return false; - } + if (Constants.ServiceFactory.NetService.GetSegmentNodeId(segmentId, startNode) != 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; + ICustomSegmentLightsManager customSegLightsMan = Constants.ManagerFactory.CustomSegmentLightsManager; - ICustomSegmentLights liveLights = customSegLightsMan.GetOrLiveSegmentLights(segmentId, startNode); - liveLights.Housekeeping(true, true); + ICustomSegmentLights liveLights = customSegLightsMan.GetOrLiveSegmentLights(segmentId, startNode); + liveLights.Housekeeping(true, true); - ICustomSegmentLights clonedLights = liveLights.Clone(this); + ICustomSegmentLights clonedLights = liveLights.Clone(this); - CustomSegmentLights.Add(segmentId, clonedLights); - if (makeRed) - CustomSegmentLights[segmentId].MakeRed(); - else - CustomSegmentLights[segmentId].MakeRedOrGreen(); - return customSegLightsMan.ApplyLightModes(segmentId, startNode, clonedLights); - } + 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) { + 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."); + 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; - } + 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); - } + return SetSegmentLights(segmentId, lights); + } - public bool SetSegmentLights(ushort segmentId, ICustomSegmentLights 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."); + if (GlobalConfig.Instance.Debug.Switches[7] && GlobalConfig.Instance.Debug.NodeId == timedNode.NodeId) + Log._Debug($"TimedTrafficLightsStep.SetSegmentLights({segmentId}, {lights}) called."); #endif - if (!Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { - Log.Error($"TimedTrafficLightsStep.SetSegmentLights({segmentId}): Segment {segmentId} is invalid"); - return false; - } - - bool? startNode = Constants.ServiceFactory.NetService.IsStartNode(segmentId, timedNode.NodeId); - if (startNode == null) { - Log.Error($"TimedTrafficLightsStep.SetSegmentLights: Segment {segmentId} is not connected to node {timedNode.NodeId}"); - return false; - } - - Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(segmentId, (bool)startNode).Housekeeping(true, true); - lights.Relocate(segmentId, (bool)startNode, this); - CustomSegmentLights[segmentId] = lights; - Log._Debug($"TimedTrafficLightsStep.SetSegmentLights: Set lights @ seg. {segmentId}, node {timedNode.NodeId}"); - return true; - } - - // 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(); - } - } -} + if (!Constants.ServiceFactory.NetService.IsSegmentValid(segmentId)) { + Log.Error($"TimedTrafficLightsStep.SetSegmentLights({segmentId}): Segment {segmentId} is invalid"); + return false; + } + + bool? startNode = Constants.ServiceFactory.NetService.IsStartNode(segmentId, timedNode.NodeId); + if (startNode == null) { + Log.Error($"TimedTrafficLightsStep.SetSegmentLights: Segment {segmentId} is not connected to node {timedNode.NodeId}"); + return false; + } + + Constants.ManagerFactory.CustomSegmentLightsManager.GetOrLiveSegmentLights(segmentId, (bool)startNode).Housekeeping(true, true); + lights.Relocate(segmentId, (bool)startNode, this); + CustomSegmentLights[segmentId] = lights; + Log._Debug($"TimedTrafficLightsStep.SetSegmentLights: Set lights @ seg. {segmentId}, node {timedNode.NodeId}"); + return true; + } + + // 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, + API.Traffic.Enums.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(); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/IncompatibleModsPanel.cs b/TLM/TLM/UI/IncompatibleModsPanel.cs index 9b20fb9e5..325536ae8 100644 --- a/TLM/TLM/UI/IncompatibleModsPanel.cs +++ b/TLM/TLM/UI/IncompatibleModsPanel.cs @@ -1,28 +1,42 @@ -using System; -using System.Collections.Generic; using ColossalFramework; -using ColossalFramework.Globalization; +using ColossalFramework.IO; using ColossalFramework.PlatformServices; -using ColossalFramework.Plugins; using ColossalFramework.UI; using CSUtil.Commons; +using System; +using System.Collections.Generic; using UnityEngine; +using static ColossalFramework.Plugins.PluginManager; -namespace TrafficManager.UI { - public class IncompatibleModsPanel : UIPanel { +namespace TrafficManager.UI +{ + public class IncompatibleModsPanel : UIPanel + { + private const ulong LOCAL_MOD = ulong.MaxValue; + + private static IncompatibleModsPanel _instance; private UILabel title; private UIButton closeButton; private UISprite warningIcon; private UIPanel mainPanel; private UICheckBox runModsCheckerOnStartup; private UIComponent blurEffect; - private static IncompatibleModsPanel _instance; - - public Dictionary IncompatibleMods { get; set; } - public void Initialize() { + /// + /// Gets or sets list of incompatible mods from . + /// + public Dictionary IncompatibleMods { get; set; } + + /// + /// Initialises the dialog, populates it with list of incompatible mods, and adds it to the modal stack. + /// + /// If the modal stack was previously empty, a blur effect is added over the screen background. + /// + public void Initialize() + { Log._Debug("IncompatibleModsPanel initialize"); - if (mainPanel != null) { + if (mainPanel != null) + { mainPanel.OnDestroy(); } @@ -37,7 +51,7 @@ public void Initialize() { mainPanel.height = 440; Vector2 resolution = UIView.GetAView().GetScreenResolution(); - relativePosition = new Vector3(resolution.x / 2 - 300, resolution.y / 3); + relativePosition = new Vector3((resolution.x / 2) - 300, resolution.y / 3); mainPanel.relativePosition = Vector3.zero; warningIcon = mainPanel.AddUIComponent(); @@ -50,7 +64,14 @@ public void Initialize() { title.autoSize = true; title.padding = new RectOffset(10, 10, 15, 15); title.relativePosition = new Vector2(60, 12); - title.text = Translation.GetString("Traffic_Manager_detected_incompatible_mods"); + +#if LABS + title.text = "TM:PE LABS " + Translation.GetString("Traffic_Manager_detected_incompatible_mods"); +#elif DEBUG + title.text = "TM:PE DEBUG " + Translation.GetString("Traffic_Manager_detected_incompatible_mods"); +#else // STABLE + title.text = "TM:PE STABLE " + Translation.GetString("Traffic_Manager_detected_incompatible_mods"); +#endif closeButton = mainPanel.AddUIComponent(); closeButton.eventClick += CloseButtonClick; @@ -68,20 +89,21 @@ public void Initialize() { runModsCheckerOnStartup.relativePosition = new Vector3(20, height - 30f); UIScrollablePanel scrollablePanel = panel.AddUIComponent(); - scrollablePanel.backgroundSprite = ""; + scrollablePanel.backgroundSprite = string.Empty; 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; + scrollablePanel.autoLayoutStart = LayoutStart.TopLeft; + scrollablePanel.autoLayoutDirection = LayoutDirection.Vertical; + scrollablePanel.autoLayout = true; + + // Populate list of incompatible mods + if (IncompatibleMods.Count != 0) + { + IncompatibleMods.ForEach((pair) => + { + CreateEntry(ref scrollablePanel, pair.Value, pair.Key); }); - item = null; } scrollablePanel.FitTo(panel); @@ -111,60 +133,185 @@ public void Initialize() { thumb.relativePosition = Vector3.zero; verticalScroll.thumbObject = thumb; - + // Add blur effect if applicable 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)); - } + blurEffect.eventPositionChanged += OnBlurEffectPositionChange; + blurEffect.eventZOrderChanged += OnBlurEffectZOrderChange; + blurEffect.opacity = 0; + blurEffect.isVisible = true; + ValueAnimator.Animate("ModalEffect", delegate (float val) { blurEffect.opacity = val; }, new AnimatedFloat(0f, 1f, 0.7f, EasingType.CubicEaseOut)); + // Make sure modal dialog is in front of all other UI BringToFront(); } - private void RunModsCheckerOnStartup_eventCheckChanged(bool value) { + private void OnBlurEffectPositionChange(UIComponent component, Vector2 position) { + blurEffect.absolutePosition = Vector3.zero; + } + + private void OnBlurEffectZOrderChange(UIComponent component, int value) { + blurEffect.zOrder = 0; + mainPanel.zOrder = 1000; + } + + /// + /// Allows the user to press "Esc" to close the dialog. + /// + /// + /// Details about the key press. + protected override void OnKeyDown(UIKeyEventParameter p) + { + if (Input.GetKey(KeyCode.Escape) || Input.GetKey(KeyCode.Return)) + { + TryPopModal(); + p.Use(); + Hide(); + Unfocus(); + } + + base.OnKeyDown(p); + } + + /// + /// Hnadles click of the "Run incompatible check on startup" checkbox and updates game options accordingly. + /// + /// + /// The new value of the checkbox; true if checked, otherwise false. + private void RunModsCheckerOnStartup_eventCheckChanged(bool value) + { Log._Debug("Incompatible mods checker run on game launch changed to " + value); State.Options.setScanForKnownIncompatibleMods(value); } - private void CloseButtonClick(UIComponent component, UIMouseEventParameter eventparam) { + /// + /// Handles click of the "close dialog" button; pops the dialog off the modal stack. + /// + /// + /// Handle to the close button UI component (not used). + /// Details about the click event. + private void CloseButtonClick(UIComponent component, UIMouseEventParameter eventparam) + { + CloseDialog(); + eventparam.Use(); + } + + /// + /// Pops the popup dialog off the modal stack. + /// + private void CloseDialog() + { closeButton.eventClick -= CloseButtonClick; TryPopModal(); Hide(); Unfocus(); - eventparam.Use(); } - private UIPanel CreateEntry(ref UIScrollablePanel parent, string name, ulong steamId) { + /// + /// Creates a panel representing the mod and adds it to the UI component. + /// + /// + /// The parent UI component that the panel will be added to. + /// The name of the mod, which is displayed to user. + /// The instance of the incompatible mod. + private void CreateEntry(ref UIScrollablePanel parent, string modName, PluginInfo mod) + { + string caption = mod.publishedFileID.AsUInt64 == LOCAL_MOD ? Translation.GetString("Delete") : Translation.GetString("Unsubscribe"); + UIPanel panel = parent.AddUIComponent(); panel.size = new Vector2(560, 50); panel.backgroundSprite = "ContentManagerItemBackground"; + UILabel label = panel.AddUIComponent(); - label.text = name; + label.text = modName; 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; + + CreateButton(panel, caption, (int)panel.width - 170, 10, delegate (UIComponent component, UIMouseEventParameter param) { UnsubscribeClick(component, param, mod); }); } - private void UnsubscribeClick(UIComponent component, UIMouseEventParameter eventparam, ulong steamId) { - Log.Info("Trying to unsubscribe workshop item " + steamId); + /// + /// Handles click of "Unsubscribe" or "Delete" button; removes the associated mod and updates UI. + /// + /// Once all incompatible mods are removed, the dialog will be closed automatically. + /// + /// + /// A handle to the UI button that was clicked. + /// Details of the click event. + /// The instance of the mod to remove. + private void UnsubscribeClick(UIComponent component, UIMouseEventParameter eventparam, PluginInfo mod) + { + eventparam.Use(); + bool success = false; + + // disable button to prevent accidental clicks component.isEnabled = false; - if (PlatformService.workshop.Unsubscribe(new PublishedFileId(steamId))) { - IncompatibleMods.Remove(steamId); + + Log.Info($"Removing incompatible mod '{mod.name}' from {mod.modPath}"); + if (mod.publishedFileID.AsUInt64 == LOCAL_MOD) + { + success = DeleteLocalTMPE(mod); + } + else + { + success = PlatformService.workshop.Unsubscribe(mod.publishedFileID); + } + + if (success) + { + IncompatibleMods.Remove(mod); component.parent.Disable(); component.isVisible = false; - Log.Info("Workshop item " + steamId + " unsubscribed"); - } else { - Log.Warning("Failed unsubscribing workshop item " + steamId); + + // automatically close the dialog if no more mods to remove + if (IncompatibleMods.Count == 0) + { + CloseDialog(); + } + } + else + { + Log.Warning($"Failed to remove mod '{mod.name}'"); component.isEnabled = true; } } - private UIButton CreateButton(UIComponent parent, string text, int x, int y, MouseEventHandler eventClick) { + /// + /// Deletes a locally installed TM:PE mod. + /// + /// + /// The associated with the mod that needs deleting. + /// + /// Returns true if successfully deleted, otherwise false. + private bool DeleteLocalTMPE(PluginInfo mod) + { + try + { + Log._Debug($"Deleting local TM:PE from {mod.modPath}"); + //mod.Unload(); + DirectoryUtils.DeleteDirectory(mod.modPath); + return true; + } + catch (Exception e) + { + return false; + } + } + + /// + /// Creates an `Unsubscribe` or `Delete` button (as applicable to mod location) and attaches it to the UI component. + /// + /// + /// The parent UI component which the button will be attached to. + /// The translated text to display on the button. + /// The x position of the top-left corner of the button, relative to . + /// The y position of the top-left corner of the button, relative to . + /// The event handler for when the button is clicked. + private void CreateButton(UIComponent parent, string text, int x, int y, MouseEventHandler eventClick) + { var button = parent.AddUIComponent(); button.textScale = 0.8f; button.width = 150f; @@ -179,56 +326,27 @@ private UIButton CreateButton(UIComponent parent, string text, int x, int y, Mou 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()) { + /// + /// Pops the dialog from the modal stack. If no more modal dialogs are present, the background blur effect is also removed. + /// + private void TryPopModal() + { + if (UIView.HasModalInput()) + { UIView.PopModal(); UIComponent component = UIView.GetModalComponent(); - if (component != null) { + 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(); }); + blurEffect.eventPositionChanged -= OnBlurEffectPositionChange; + blurEffect.eventZOrderChanged -= OnBlurEffectZOrderChange; + ValueAnimator.Animate("ModalEffect", delegate (float val) { blurEffect.opacity = val; }, new AnimatedFloat(1f, 0f, 0.7f, EasingType.CubicEaseOut), delegate () { blurEffect.Hide(); }); } } } diff --git a/TLM/TLM/UI/LinearSpriteButton.cs b/TLM/TLM/UI/LinearSpriteButton.cs index 5ea666009..fee032556 100644 --- a/TLM/TLM/UI/LinearSpriteButton.cs +++ b/TLM/TLM/UI/LinearSpriteButton.cs @@ -1,118 +1,153 @@ -using ColossalFramework.Math; -using ColossalFramework.UI; +using ColossalFramework.UI; using CSUtil.Commons; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.State; +using ColossalFramework; +using TrafficManager.State.Keybinds; 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(); - } - } -} + 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() { + var textureCount = Enum.GetValues(typeof(ButtonMouseState)).Length * (CanActivate() ? 2 : 1) + + FunctionNames.Length * 2; + string[] textureIds = new string[textureCount]; + + 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); + + public virtual KeybindSetting ShortcutKey { + get { return null; } + } + + 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); + + var shortcutText = GetShortcutTooltip(); + tooltip = Translation.GetString(Tooltip) + shortcutText; + + isVisible = Visible; + this.Invalidate(); + } + + + /// + /// If shortcut key was set to a non-empty something, then form a text tooltip, + /// otherwise an empty string is returned. + /// + /// Tooltip to append to the main tooltip text, or an empty string + private string GetShortcutTooltip() { + return ShortcutKey != null ? ShortcutKey.ToLocalizedString("\n") : string.Empty; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs b/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs index 8e8876679..eb02f9727 100644 --- a/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs +++ b/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs @@ -1,46 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework.UI; using TrafficManager.Manager.Impl; namespace TrafficManager.UI.MainMenu { - public class ClearTrafficButton : MenuButton { - public override bool Active { - get { - return false; - } - } + public class ClearTrafficButton : MenuButton { + public override bool Active => false; + public override ButtonFunction Function => ButtonFunction.ClearTraffic; + public override string Tooltip => "Clear_Traffic"; + public override bool Visible => true; - 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); - }); - } - } -} + 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); + }); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/DespawnButton.cs b/TLM/TLM/UI/MainMenu/DespawnButton.cs index 713782812..afc66b320 100644 --- a/TLM/TLM/UI/MainMenu/DespawnButton.cs +++ b/TLM/TLM/UI/MainMenu/DespawnButton.cs @@ -1,40 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework.UI; using TrafficManager.State; namespace TrafficManager.UI.MainMenu { - public class DespawnButton : MenuButton { - public override bool Active { - get { - return false; - } - } + public class DespawnButton : MenuButton { + public override bool Active => false; + public override ButtonFunction Function => Options.disableDespawning ? ButtonFunction.DespawnDisabled : ButtonFunction.DespawnEnabled; + public override string Tooltip => Options.disableDespawning ? "Enable_despawning" : "Disable_despawning"; + public override bool Visible => true; - 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); - } - } -} + public override void OnClickInternal(UIMouseEventParameter p) { + UIBase.GetTrafficManagerTool(true).SetToolMode(ToolMode.None); + Options.setDisableDespawning(!Options.disableDespawning); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs index 8b3a9361d..80d2b8227 100644 --- a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs @@ -1,35 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; -using TrafficManager.State; +using TrafficManager.State; +using TrafficManager.State.Keybinds; 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; - } - } - } -} + public class JunctionRestrictionsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.JunctionRestrictions; + public override ButtonFunction Function => ButtonFunction.JunctionRestrictions; + public override string Tooltip => "Junction_restrictions"; + public override bool Visible => Options.junctionRestrictionsEnabled; + public override KeybindSetting ShortcutKey => KeybindSettingsBase.JunctionRestrictionsTool; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs index 17b09ac85..69639e923 100644 --- a/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneArrowsButton.cs @@ -1,35 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; -using TrafficManager.State; +using TrafficManager.State.Keybinds; 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; - } - } - } -} + public class LaneArrowsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.LaneChange; + public override ButtonFunction Function => ButtonFunction.LaneArrows; + public override string Tooltip => "Change_lane_arrows"; + public override bool Visible => true; + public override KeybindSetting ShortcutKey => KeybindSettingsBase.LaneArrowTool; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs index 0394e8b98..e9315cccb 100644 --- a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs +++ b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs @@ -1,35 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework; using TrafficManager.State; +using TrafficManager.State.Keybinds; 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; - } - } - } -} + public class LaneConnectorButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.LaneConnector; + public override ButtonFunction Function => ButtonFunction.LaneConnector; + public override string Tooltip => "Lane_connector"; + public override bool Visible => Options.laneConnectorEnabled; + public override KeybindSetting ShortcutKey => KeybindSettingsBase.LaneConnectionsTool; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs index ea158ccca..5c701a9f8 100644 --- a/TLM/TLM/UI/MainMenu/MainMenuPanel.cs +++ b/TLM/TLM/UI/MainMenu/MainMenuPanel.cs @@ -12,199 +12,237 @@ using System.Collections.Generic; using TrafficManager.Manager; using CSUtil.Commons; +using TrafficManager.State.Keybinds; +using TrafficManager.UI.SubTools; 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(); - } - } -} + public class MainMenuPanel : UIPanel, IObserver { + private static readonly Type[] MENU_BUTTON_TYPES + = { + // 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 { + 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(); + } + + public void OnGUI() { + // Return if modal window is active or the main menu is hidden + if (!this.isVisible || UIView.HasModalInput() || UIView.HasInputFocus()) { + return; + } + + // Some safety checks to not trigger while full screen/modals are open + // Check the key and then click the corresponding button + if (KeybindSettingsBase.ToggleTrafficLightTool.IsPressed(Event.current)) { + ClickToolButton(typeof(ToggleTrafficLightsButton)); + } else if (KeybindSettingsBase.LaneArrowTool.IsPressed(Event.current)) { + ClickToolButton(typeof(LaneArrowsButton)); + } else if (KeybindSettingsBase.LaneConnectionsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(LaneConnectorButton)); + } else if (KeybindSettingsBase.PrioritySignsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(PrioritySignsButton)); + } else if (KeybindSettingsBase.JunctionRestrictionsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(JunctionRestrictionsButton)); + } else if (KeybindSettingsBase.SpeedLimitsTool.IsPressed(Event.current)) { + ClickToolButton(typeof(SpeedLimitsButton)); + } + } + + /// For given button class type, find it in the tool palette and send click + /// Something like typeof(ToggleTrafficLightsButton) + void ClickToolButton(Type t) { + for (var i = 0; i < MENU_BUTTON_TYPES.Length; i++) { + if (MENU_BUTTON_TYPES[i] == t) { + Buttons[i].SimulateClick(); + return; + } + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs index e19785e06..02473b712 100644 --- a/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs @@ -1,35 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; -using TrafficManager.State; +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; - } - } - } -} + public class ManualTrafficLightsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.ManualSwitch; + public override ButtonFunction Function => ButtonFunction.ManualTrafficLights; + public override string Tooltip => "Manual_traffic_lights"; + public override bool Visible => Options.timedLightsEnabled; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/MenuButton.cs b/TLM/TLM/UI/MainMenu/MenuButton.cs index 96fac43a9..f83b651d9 100644 --- a/TLM/TLM/UI/MainMenu/MenuButton.cs +++ b/TLM/TLM/UI/MainMenu/MenuButton.cs @@ -1,89 +1,63 @@ -using ColossalFramework.Math; -using ColossalFramework.UI; -using CSUtil.Commons; +using ColossalFramework.UI; 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; - } - } - } -} + public abstract class MenuButton : LinearSpriteButton { + public enum ButtonFunction { + LaneConnector, + ClearTraffic, + DespawnDisabled, + DespawnEnabled, + JunctionRestrictions, + LaneArrows, + ManualTrafficLights, + PrioritySigns, + SpeedLimits, + TimedTrafficLights, + ToggleTrafficLights, + VehicleRestrictions, + ParkingRestrictions + } + + private const string MENU_BUTTON = "TMPE_MenuButton"; + + 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 => MENU_BUTTON; + + public override string FunctionName => 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 => TextureResources.MainMenuButtonsTexture2D; + + public override int Width => 50; + + public override int Height => 50; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/MenuToolModeButton.cs b/TLM/TLM/UI/MainMenu/MenuToolModeButton.cs index ad0530221..846776779 100644 --- a/TLM/TLM/UI/MainMenu/MenuToolModeButton.cs +++ b/TLM/TLM/UI/MainMenu/MenuToolModeButton.cs @@ -1,26 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework.UI; namespace TrafficManager.UI.MainMenu { - public abstract class MenuToolModeButton : MenuButton { - public abstract ToolMode ToolMode { get; } + 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 bool Active => 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); - } - } - } -} + public override void OnClickInternal(UIMouseEventParameter p) { + if (Active) { + UIBase.GetTrafficManagerTool(true).SetToolMode(ToolMode.None); + } else { + UIBase.GetTrafficManagerTool(true).SetToolMode(this.ToolMode); + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs index f8cbda17c..b06b53e9a 100644 --- a/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs @@ -1,35 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; -using TrafficManager.State; +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; - } - } - } -} + public class ParkingRestrictionsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.ParkingRestrictions; + public override ButtonFunction Function => ButtonFunction.ParkingRestrictions; + public override string Tooltip => "Parking_restrictions"; + public override bool Visible => Options.parkingRestrictionsEnabled; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs index a7d296ba0..8003c9e99 100644 --- a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs +++ b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs @@ -1,35 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework; using TrafficManager.State; +using TrafficManager.State.Keybinds; 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; - } - } - } -} + public class PrioritySignsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.AddPrioritySigns; + public override ButtonFunction Function => ButtonFunction.PrioritySigns; + public override string Tooltip => "Add_priority_signs"; + public override bool Visible => Options.prioritySignsEnabled; + public override KeybindSetting ShortcutKey => KeybindSettingsBase.PrioritySignsTool; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs index dd25996b1..eeb0ae28a 100644 --- a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs +++ b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs @@ -1,35 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using ColossalFramework; using TrafficManager.State; +using TrafficManager.State.Keybinds; 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; - } - } - } -} + public class SpeedLimitsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.SpeedLimits; + public override ButtonFunction Function => ButtonFunction.SpeedLimits; + public override string Tooltip => "Speed_limits"; + public override bool Visible => Options.customSpeedLimitsEnabled; + public override KeybindSetting ShortcutKey => KeybindSettingsBase.SpeedLimitsTool; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs index f633040c3..b00126512 100644 --- a/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs @@ -1,35 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; -using TrafficManager.State; +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; - } - } - } -} + public class TimedTrafficLightsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.TimedLightsSelectNode; + public override ButtonFunction Function => ButtonFunction.TimedTrafficLights; + public override string Tooltip => "Timed_traffic_lights"; + public override bool Visible => Options.timedLightsEnabled; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs index f5c1c1026..2f7187ea0 100644 --- a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs +++ b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs @@ -1,34 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; +using TrafficManager.State.Keybinds; 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; - } - } - } -} + public class ToggleTrafficLightsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.SwitchTrafficLight; + public override ButtonFunction Function => ButtonFunction.ToggleTrafficLights; + public override string Tooltip => "Switch_traffic_lights"; + public override bool Visible => true; + public override KeybindSetting ShortcutKey => KeybindSettingsBase.ToggleTrafficLightTool; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs index 5163db5b4..c5ff91056 100644 --- a/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs +++ b/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs @@ -1,35 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ColossalFramework.UI; -using TrafficManager.Manager; -using TrafficManager.State; +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 class VehicleRestrictionsButton : MenuToolModeButton { + public override ToolMode ToolMode => ToolMode.VehicleRestrictions; + public override ButtonFunction Function => ButtonFunction.VehicleRestrictions; + public override string Tooltip => "Vehicle_restrictions"; - public override string Tooltip { - get { - return "Vehicle_restrictions"; - } - } - - public override bool Visible { - get { - return Options.vehicleRestrictionsEnabled; - } - } - } -} + public override bool Visible => Options.vehicleRestrictionsEnabled; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/MainMenu/VersionLabel.cs b/TLM/TLM/UI/MainMenu/VersionLabel.cs index 4434266e6..758c9a1a4 100644 --- a/TLM/TLM/UI/MainMenu/VersionLabel.cs +++ b/TLM/TLM/UI/MainMenu/VersionLabel.cs @@ -1,17 +1,21 @@ -using ColossalFramework.UI; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using ColossalFramework.UI; 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; - } - } + 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 + +#if LABS + text = "TM:PE " + TrafficManagerMod.Version + " LABS"; +#elif DEBUG + text = "TM:PE " + TrafficManagerMod.Version + " DEBUG"; +#else // STABLE + text = "TM:PE " + TrafficManagerMod.Version + " STABLE"; +#endif + relativePosition = new Vector3(5f, 5f); + + textAlignment = UIHorizontalAlignment.Left; + } + } } diff --git a/TLM/TLM/UI/SubTools/LaneArrowTool.cs b/TLM/TLM/UI/SubTools/LaneArrowTool.cs index dd99df9e7..b161f9baf 100644 --- a/TLM/TLM/UI/SubTools/LaneArrowTool.cs +++ b/TLM/TLM/UI/SubTools/LaneArrowTool.cs @@ -19,6 +19,8 @@ using UnityEngine; namespace TrafficManager.UI.SubTools { + using API.Traffic.Enums; + public class LaneArrowTool : SubTool { private bool _cursorInSecondaryPanel; diff --git a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs index 731189844..24d1b9a00 100644 --- a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs +++ b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs @@ -1,640 +1,663 @@ 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.State.Keybinds; 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() { + 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; + + /// Unity frame when OnGui detected the shortcut for Stay in Lane. + /// Resets when the event is consumed or after a few frames. + private int frameStayInLanePressed = 0; + + /// Clear lane lines is Delete/Backspace (configurable) + private int frameClearPressed = 0; + + 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}"); + + if (KeybindSettingsBase.LaneConnectorStayInLane.IsPressed(e)) { + frameStayInLanePressed = Time.frameCount; + // this will be consumed in RenderOverlay() if the key was pressed + // not too long ago (within 20 Unity frames or 0.33 sec) + } + + if (KeybindSettingsBase.LaneConnectorDelete.IsPressed(e)) { + frameClearPressed = Time.frameCount; + // this will be consumed in RenderOverlay() if the key was pressed + // not too long ago (within 20 Unity frames or 0.33 sec) + } + } + + 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; + var 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); + } + } + + var deleteAll = frameClearPressed > 0 && (Time.frameCount - frameClearPressed) < 20; // 0.33 sec + + // Must press Shift+S (or another shortcut) within last 20 frames for this to work + var stayInLane = frameStayInLanePressed > 0 + && (Time.frameCount - frameStayInLanePressed) < 20 // 0.33 sec + && Singleton.instance.m_nodes.m_buffer[SelectedNodeId].CountSegments() == 2; + + if (stayInLane) { + frameStayInLanePressed = 0; // not pressed anymore (consumed) + deleteAll = true; + } + + if (deleteAll) { + frameClearPressed = 0; // consumed + // 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 (IsCursorInPanel()) + return; - if (GetMarkerSelectionMode() == MarkerSelectionMode.None) { - if (HoveredNodeId != 0) { + 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 (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; - } + SelectedNodeId = 0; + selectedMarker = null; + stayInLaneMode = StayInLaneMode.None; + return; + } - if (SelectedNodeId != HoveredNodeId) { + 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 { + // 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; - } - } + // click on free spot. deselect node + SelectedNodeId = 0; + selectedMarker = null; + stayInLaneMode = StayInLaneMode.None; + return; + } + } - if (hoveredMarker != null) { - stayInLaneMode = StayInLaneMode.None; + 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; + // 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); + } 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); + //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() { + //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; + if (IsCursorInPanel()) + return; - switch (GetMarkerSelectionMode()) { - case MarkerSelectionMode.None: - default: + 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 + 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 + 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; - } - } + selectedMarker = null; + break; + } + } - public override void OnActivate() { + 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), - }; - } -} + 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(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), + }; + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs b/TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs index 2a42027a7..cdf479531 100644 --- a/TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs +++ b/TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs @@ -1,704 +1,694 @@ -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; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - -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) { - IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - 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 - - for (int i = 0; i < 8; ++i) { - ushort segmentId = Singleton.instance.m_nodes.m_buffer[SelectedNodeId].GetSegment(i); - if (segmentId == 0) { - continue; - } - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, SelectedNodeId); - - var position = CalculateNodePositionForSegment(Singleton.instance.m_nodes.m_buffer[SelectedNodeId], ref Singleton.instance.m_segments.m_buffer[segmentId]); - var segmentLights = customTrafficLightsManager.GetSegmentLights(segmentId, 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, segmentId, screenPos, lightWidth, segmentLights, hoveredSegment); - - // SWITCH PEDESTRIAN LIGHT - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == 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, 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(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, segmentId, hoveredSegment, segmentLight); - - // COUNTER - hoveredSegment = RenderCounter(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; - } - } - - ExtSegment seg = segMan.ExtSegments[segmentId]; - ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)]; - if (seg.oneWay && segEnd.outgoing) continue; - - bool hasLeftSegment; - bool hasForwardSegment; - bool hasRightSegment; - segEndMan.CalculateOutgoingLeftStraightRightSegments(ref segEnd, ref Singleton.instance.m_nodes.m_buffer[segmentId], out hasLeftSegment, out hasForwardSegment, out hasRightSegment); - - switch (segmentLight.CurrentMode) { - case LightMode.Simple: - hoveredSegment = SimpleManualSegmentLightMode(segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); - break; - case LightMode.SingleLeft: - hoveredSegment = LeftForwardRManualSegmentLightMode(hasLeftSegment, segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment, hasForwardSegment, hasRightSegment); - break; - case LightMode.SingleRight: - hoveredSegment = RightForwardLSegmentLightMode(segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, hasForwardSegment, hasLeftSegment, segmentLight, hasRightSegment, hoveredSegment); - break; - default: - // left arrow light - if (hasLeftSegment) - hoveredSegment = LeftArrowLightMode(segmentId, lightWidth, hasRightSegment, hasForwardSegment, offsetScreenPos, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); - - // forward arrow light - if (hasForwardSegment) - hoveredSegment = ForwardArrowLightMode(segmentId, lightWidth, hasRightSegment, offsetScreenPos, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); - - // right arrow light - if (hasRightSegment) - hoveredSegment = RightArrowLightMode(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); - } - } -} +namespace TrafficManager.UI.SubTools { + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using Manager; + using Manager.Impl; + using Traffic.Data; + using TrafficLight; + using UnityEngine; + + 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) { + IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + 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 + + for (int i = 0; i < 8; ++i) { + ushort segmentId = Singleton.instance.m_nodes.m_buffer[SelectedNodeId].GetSegment(i); + if (segmentId == 0) { + continue; + } + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(segmentId, SelectedNodeId); + + var position = CalculateNodePositionForSegment(Singleton.instance.m_nodes.m_buffer[SelectedNodeId], ref Singleton.instance.m_segments.m_buffer[segmentId]); + var segmentLights = customTrafficLightsManager.GetSegmentLights(segmentId, 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, segmentId, screenPos, lightWidth, segmentLights, hoveredSegment); + + // SWITCH PEDESTRIAN LIGHT + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == 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, 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(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, segmentId, hoveredSegment, segmentLight); + + // COUNTER + hoveredSegment = RenderCounter(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; + } + } + + ExtSegment seg = segMan.ExtSegments[segmentId]; + ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)]; + if (seg.oneWay && segEnd.outgoing) continue; + + bool hasLeftSegment; + bool hasForwardSegment; + bool hasRightSegment; + segEndMan.CalculateOutgoingLeftStraightRightSegments(ref segEnd, ref Singleton.instance.m_nodes.m_buffer[segmentId], out hasLeftSegment, out hasForwardSegment, out hasRightSegment); + + switch (segmentLight.CurrentMode) { + case LightMode.Simple: + hoveredSegment = SimpleManualSegmentLightMode(segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); + break; + case LightMode.SingleLeft: + hoveredSegment = LeftForwardRManualSegmentLightMode(hasLeftSegment, segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment, hasForwardSegment, hasRightSegment); + break; + case LightMode.SingleRight: + hoveredSegment = RightForwardLSegmentLightMode(segmentId, offsetScreenPos, lightWidth, pedestrianWidth, zoom, lightHeight, hasForwardSegment, hasLeftSegment, segmentLight, hasRightSegment, hoveredSegment); + break; + default: + // left arrow light + if (hasLeftSegment) + hoveredSegment = LeftArrowLightMode(segmentId, lightWidth, hasRightSegment, hasForwardSegment, offsetScreenPos, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); + + // forward arrow light + if (hasForwardSegment) + hoveredSegment = ForwardArrowLightMode(segmentId, lightWidth, hasRightSegment, offsetScreenPos, pedestrianWidth, zoom, lightHeight, segmentLight, hoveredSegment); + + // right arrow light + if (hasRightSegment) + hoveredSegment = RightArrowLightMode(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); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/PrioritySignsTool.cs b/TLM/TLM/UI/SubTools/PrioritySignsTool.cs index a7d0ee0b6..0fb1d4fd2 100644 --- a/TLM/TLM/UI/SubTools/PrioritySignsTool.cs +++ b/TLM/TLM/UI/SubTools/PrioritySignsTool.cs @@ -22,6 +22,8 @@ using TrafficManager.Traffic.Data; namespace TrafficManager.UI.SubTools { + using API.Traffic.Enums; + public class PrioritySignsTool : SubTool { public enum PrioritySignsMassEditMode { MainYield = 0, diff --git a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs index 33b5175ef..e008f5a71 100644 --- a/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs +++ b/TLM/TLM/UI/SubTools/SpeedLimitsTool.cs @@ -1,524 +1,653 @@ -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.curSeg.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.curSeg.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; - } - } -} +namespace TrafficManager.UI.SubTools { + using System; + using System.Collections.Generic; + using ColossalFramework; + using CSUtil.Commons; + using GenericGameBridge.Service; + using Manager.Impl; + using State; + using Traffic; + using Traffic.Data; + using UnityEngine; + using Util; + using static ColossalFramework.UI.UITextureAtlas; + using static Util.SegmentLaneTraverser; + + public class SpeedLimitsTool : SubTool { + /// Visible sign size, slightly reduced from 100 to accomodate another column for MPH + private const int GuiSpeedSignSize = 80; + private readonly float speedLimitSignSize = 70f; + + private bool _cursorInSecondaryPanel; + + /// Currently selected speed limit on the limits palette + private float currentPaletteSpeedLimit = -1f; + + private bool overlayHandleHovered; + private Dictionary> segmentCenterByDir = new Dictionary>(); + + private Rect paletteWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 0, 10 * (GuiSpeedSignSize + 5), 150)); + private Rect defaultsWindowRect = TrafficManagerTool.MoveGUI(new Rect(0, 80, 50, 50)); + private HashSet currentlyVisibleSegmentIds; + private bool defaultsWindowVisible = false; + private int currentInfoIndex = -1; + private float currentSpeedLimit = -1f; + 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); + + var unitTitle = " (" + (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? Translation.GetString("Miles_per_hour") + : Translation.GetString("Kilometers_per_hour")) + ")"; + paletteWindowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? 10 * (GuiSpeedSignSize + 5) + : 8 * (GuiSpeedSignSize + 5); + paletteWindowRect = GUILayout.Window(254, paletteWindowRect, _guiSpeedLimitsWindow, + Translation.GetString("Speed_limits") + unitTitle, + WindowStyle); + if (defaultsWindowVisible) { + defaultsWindowRect = GUILayout.Window( + 258, defaultsWindowRect, _guiDefaultsWindow, + Translation.GetString("Default_speed_limits"), + WindowStyle); + } + _cursorInSecondaryPanel = paletteWindowRect.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; + currentSpeedLimit = -1f; + } + + 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; + } + + /// + /// The window for setting the defaullt speeds per road type + /// + /// + private void _guiDefaultsWindow(int num) { + var 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 (currentSpeedLimit < 0f) { + currentSpeedLimit = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info); + Log._Debug($"set currentSpeedLimit to {currentSpeedLimit}"); + } + //Log._Debug($"currentInfoIndex={currentInfoIndex} currentSpeedLimitIndex={currentSpeedLimitIndex}"); + + // 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]; + currentSpeedLimit = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(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]; + currentSpeedLimit = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info); + UpdateRoadTex(info); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.EndHorizontal(); + + var centeredTextStyle = new GUIStyle("label") { 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))) { + // currentSpeedLimit = (currentSpeedLimitIndex + SpeedLimitManager.Instance.AvailableSpeedLimits.Count - 1) % SpeedLimitManager.Instance.AvailableSpeedLimits.Count; + currentSpeedLimit = SpeedLimit.GetPrevious(currentSpeedLimit); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.FlexibleSpace(); + + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + + // speed limit sign + GUILayout.Box(TextureResources.GetSpeedLimitTexture(currentSpeedLimit), + GUILayout.Width(GuiSpeedSignSize), + GUILayout.Height(GuiSpeedSignSize)); + GUILayout.Label(GlobalConfig.Instance.Main.DisplaySpeedLimitsMph + ? Translation.GetString("Miles_per_hour") + : Translation.GetString("Kilometers_per_hour")); + + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.FlexibleSpace(); + + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("→", GUILayout.Width(50))) { + // currentSpeedLimitIndex = (currentSpeedLimitIndex + 1) % SpeedLimitManager.Instance.AvailableSpeedLimits.Count; + currentSpeedLimit = SpeedLimit.GetNext(currentSpeedLimit); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.EndHorizontal(); + + // Save & Apply + GUILayout.BeginVertical(); + GUILayout.Space(10); + + GUILayout.BeginHorizontal(); + + // Close button. TODO: Make more visible or obey 'Esc' pressed or something + GUILayout.FlexibleSpace(); + if (GUILayout.Button("X", GUILayout.Width(80))) { + defaultsWindowVisible = false; + } + + GUILayout.FlexibleSpace(); + if (GUILayout.Button(Translation.GetString("Save"), GUILayout.Width(70))) { + SpeedLimitManager.Instance.FixCurrentSpeedLimits(info); + SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimit(info, currentSpeedLimit); + } + + GUILayout.FlexibleSpace(); + if (GUILayout.Button( + Translation.GetString("Save") + " & " + Translation.GetString("Apply"), + GUILayout.Width(160))) { + SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimit(info, currentSpeedLimit); + 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; + } + + /// + /// The window for selecting and applying a speed limit + /// + /// + private void _guiSpeedLimitsWindow(int num) { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + + var oldColor = GUI.color; + var allSpeedLimits = SpeedLimit.EnumerateSpeedLimits(SpeedUnit.CurrentlyConfigured); + allSpeedLimits.Add(0); // add last item: no limit + + var showMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph; + var column = 0u; // break palette to a new line at breakColumn + var breakColumn = showMph ? SpeedLimit.BREAK_PALETTE_COLUMN_MPH + : SpeedLimit.BREAK_PALETTE_COLUMN_KMPH; + + foreach (var speedLimit in allSpeedLimits) { + // Highlight palette item if it is very close to its float speed + if (SpeedLimit.NearlyEqual(currentPaletteSpeedLimit, speedLimit)) { + GUI.color = Color.gray; + } + + _guiSpeedLimitsWindow_AddButton(showMph, speedLimit); + GUI.color = oldColor; + + // TODO: This can be calculated from SpeedLimit MPH or KMPH limit constants + column++; + if (column % breakColumn == 0) { + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + } + } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + //--------------------- + // UI buttons row + //--------------------- + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button(Translation.GetString("Default_speed_limits"), + GUILayout.Width(200))) { + TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_Defaults"); + defaultsWindowVisible = true; + } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + //--------------------- + // Checkboxes row + //--------------------- + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + showLimitsPerLane = GUILayout.Toggle(showLimitsPerLane, Translation.GetString("Show_lane-wise_speed_limits")); + GUILayout.FlexibleSpace(); + + // Display MPH checkbox, if ticked will save global config + var displayMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph; + displayMph = GUILayout.Toggle(displayMph, Translation.GetString("Display_speed_limits_mph")); + if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph != displayMph) { + Options.setDisplayInMPH(displayMph); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + DragWindow(ref paletteWindowRect); + } + + /// Helper to create speed limit sign + label below converted to the opposite unit + /// Config value from GlobalConfig.I.M.ShowMPH + /// The float speed to show + private void _guiSpeedLimitsWindow_AddButton(bool showMph, float speedLimit) { + // The button is wrapped in vertical sub-layout and a label for MPH/KMPH is added + GUILayout.BeginVertical(); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + var signSize = TrafficManagerTool.AdaptWidth(GuiSpeedSignSize); + if (GUILayout.Button( + TextureResources.GetSpeedLimitTexture(speedLimit), + GUILayout.Width(signSize), + GUILayout.Height(signSize * SpeedLimit.GetVerticalTextureScale()))) { + currentPaletteSpeedLimit = speedLimit; + } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + // For MPH setting display KM/H below, for KM/H setting display MPH + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.Label(showMph ? SpeedLimit.ToKmphPreciseString(speedLimit) + : SpeedLimit.ToMphPreciseString(speedLimit)); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.EndVertical(); + } + + private bool drawSpeedLimitHandles(ushort segmentId, ref NetSegment segment, bool viewOnly, ref Vector3 camPos) { + if (viewOnly && !Options.speedLimitsOverlay) { + return false; + } + + var center = segment.m_bounds.center; + var netManager = Singleton.instance; + + var hovered = false; + var speedLimitToSet = viewOnly ? -1f : currentPaletteSpeedLimit; + + bool showPerLane = showLimitsPerLane; + if (!viewOnly) { + showPerLane = showLimitsPerLane ^ (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)); + } + + // US signs are rectangular, all other are round + var speedLimitSignVerticalScale = SpeedLimit.GetVerticalTextureScale(); + + if (showPerLane) { + // show individual speed limit handle per lane + int numDirections; + var numLanes = TrafficManagerTool.GetSegmentNumVehicleLanes(segmentId, null, out numDirections, SpeedLimitManager.VEHICLE_TYPES); + + var segmentInfo = segment.Info; + var yu = (segment.m_endDirection - segment.m_startDirection).normalized; + var xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized; + /*if ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) { + xu = -xu; + }*/ + var f = viewOnly ? 4f : 7f; // reserved sign size in game coordinates + var zero = center - 0.5f * (float)(numLanes - 1 + numDirections - 1) * f * xu; + + uint x = 0; + var guiColor = GUI.color; + var sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes( + segmentId, ref segment, null, SpeedLimitManager.LANE_TYPES, + SpeedLimitManager.VEHICLE_TYPES); + var onlyMonorailLanes = sortedLanes.Count > 0; + if (!viewOnly) { + foreach (LanePos laneData in sortedLanes) { + var laneIndex = laneData.laneIndex; + var laneInfo = segmentInfo.m_lanes[laneIndex]; + + if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) == VehicleInfo.VehicleType.None) { + onlyMonorailLanes = false; + break; + } + } + } + + var directions = new HashSet(); + var 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); + } + + var laneSpeedLimit = SpeedLimitManager.Instance.GetCustomSpeedLimit(laneId); + var hoveredHandle = MainTool.DrawGenericOverlayGridTexture( + TextureResources.GetSpeedLimitTexture(laneSpeedLimit), + camPos, zero, f, f, xu, yu, x, 0, + speedLimitSignSize, speedLimitSignSize * speedLimitSignVerticalScale, + !viewOnly); + + if (!viewOnly + && !onlyMonorailLanes + && (laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) != VehicleInfo.VehicleType.None) { + MainTool.DrawStaticSquareOverlayGridTexture( + TextureResources.VehicleInfoSignTextures[ + LegacyExtVehicleType.ToNew(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.curSeg.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; + var visible = MainTool.WorldToScreenPoint(e.Value, out screenPos); + + if (!visible) { + continue; + } + + var zoom = 1.0f / (e.Value - camPos).magnitude * 100f * MainTool.GetBaseZoom(); + var size = (viewOnly ? 0.8f : 1f) * speedLimitSignSize * zoom; + var guiColor = GUI.color; + var boundingBox = new Rect(screenPos.x - (size / 2), + screenPos.y - (size / 2), + size, + size * speedLimitSignVerticalScale); + var hoveredHandle = !viewOnly && TrafficManagerTool.IsMouseOver(boundingBox); + + guiColor.a = MainTool.GetHandleAlpha(hoveredHandle); + if (hoveredHandle) { + // mouse hovering over sign + hovered = true; + } + + // Draw something right here, the road sign texture + GUI.color = guiColor; + var displayLimit = SpeedLimitManager.Instance.GetCustomSpeedLimit(segmentId, e.Key); + var tex = TextureResources.GetSpeedLimitTexture(displayLimit); + GUI.DrawTexture(boundingBox, tex); + + 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, currentPaletteSpeedLimit); + + 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.curSeg.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; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/TimedTrafficLightsTool.cs b/TLM/TLM/UI/SubTools/TimedTrafficLightsTool.cs index 40fca7050..e5033c9dc 100644 --- a/TLM/TLM/UI/SubTools/TimedTrafficLightsTool.cs +++ b/TLM/TLM/UI/SubTools/TimedTrafficLightsTool.cs @@ -1,389 +1,383 @@ -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; -using TrafficManager.Traffic.Enums; -using TrafficManager.Traffic.Data; - -namespace TrafficManager.UI.SubTools { - public class TimedTrafficLightsTool : SubTool { - private readonly GUIStyle _counterStyle = new GUIStyle(); - private readonly int[] _hoveredButton = new int[2]; +namespace TrafficManager.UI.SubTools { + using System; + using System.Collections.Generic; + using System.Linq; + using API.Traffic.Enums; + using API.TrafficLight; + using ColossalFramework; + using CSUtil.Commons; + using Manager; + using Manager.Impl; + using State; + using Traffic.Data; + using TrafficLight; + using UnityEngine; + + 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 - int numSourceSegments = Singleton.instance.m_nodes.m_buffer[nodeIdToCopy].CountSegments(); - int numTargetSegments = Singleton.instance.m_nodes.m_buffer[nodeIdToCopy].CountSegments(); - - if (numSourceSegments != numTargetSegments) { - 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}"); + 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 + int numSourceSegments = Singleton.instance.m_nodes.m_buffer[nodeIdToCopy].CountSegments(); + int numTargetSegments = Singleton.instance.m_nodes.m_buffer[nodeIdToCopy].CountSegments(); + + if (numSourceSegments != numTargetSegments) { + 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); - } + 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); - } + } 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); - } + } else { + GUILayout.Space(50); + } - GUILayout.EndHorizontal(); + GUILayout.EndHorizontal(); GUI.color = Color.red; if (GUILayout.Button(Translation.GetString("Delete"), GUILayout.Width(70))) { - RemoveStep(i); + RemoveStep(i); } GUI.color = Color.white; @@ -413,240 +407,240 @@ private void _guiTimedControlPanel(int num) { } } } - } - } 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; - + } + } 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("X", GUILayout.Width(22))) { _timedPanelAdd = false; } 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; - } - - 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; + 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; + } + + 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; + } + } + } + + 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(); - } - } - } - } - } + tlsMan.TrafficLightSimulations[nodeId].timedLight?.Start(); + } + } + } + } + } - if (_timedEditStep >= 0) { - DragWindow(ref _windowRect); - return; - } + if (_timedEditStep >= 0) { + DragWindow(ref _windowRect); + return; + } - GUILayout.Space(30); + GUILayout.Space(30); - if (SelectedNodeIds.Count == 1 && timedNodeMain.NumSteps() > 0) { - GUILayout.BeginHorizontal(); + if (SelectedNodeIds.Count == 1 && timedNodeMain.NumSteps() > 0) { + GUILayout.BeginHorizontal(); - if (GUILayout.Button(Translation.GetString("Rotate_left"))) { - timedNodeMain.RotateLeft(); + 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("Copy"))) { + TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_Copy"); + nodeIdToCopy = SelectedNodeIds[0]; + MainTool.SetToolMode(ToolMode.TimedLightsCopyLights); + } - if (GUILayout.Button(Translation.GetString("Rotate_right"))) { - timedNodeMain.RotateRight(); + if (GUILayout.Button(Translation.GetString("Rotate_right"))) { + timedNodeMain.RotateRight(); _timedViewedStep = 0; } - GUILayout.EndHorizontal(); - } + GUILayout.EndHorizontal(); + } - if (!timedLightActive) { - GUILayout.Space(30); + 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); - } + MainTool.SetToolMode(ToolMode.TimedLightsAddNode); + } - if (SelectedNodeIds.Count > 1) { + if (SelectedNodeIds.Count > 1) { if (GUILayout.Button(Translation.GetString("Remove_junction_from_timed_light"))) { TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_RemoveJunction"); - MainTool.SetToolMode(ToolMode.TimedLightsRemoveNode); - } - } + MainTool.SetToolMode(ToolMode.TimedLightsRemoveNode); + } + } - GUILayout.Space(30); + GUILayout.Space(30); - if (GUILayout.Button(Translation.GetString("Remove_timed_traffic_light"))) { - DisableTimed(); - ClearSelectedNodes(); - MainTool.SetToolMode(ToolMode.TimedLightsSelectNode); - } - } + 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}"); - } - } + DragWindow(ref _windowRect); + } catch (Exception e) { + Log.Error($"TimedTrafficLightsTool._guiTimedControlPanel: {e}"); + } + } private void RemoveStep(int stepIndex) { TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; @@ -659,1180 +653,1180 @@ private void RemoveStep(int stepIndex) { } 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); + 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; + private void _guiTimedTrafficLights() { + TrafficLightSimulationManager tlsMan = TrafficLightSimulationManager.Instance; + CustomSegmentLightsManager customTrafficLightsManager = CustomSegmentLightsManager.Instance; + TrafficPriorityManager prioMan = TrafficPriorityManager.Instance; - _cursorInSecondaryPanel = false; + _cursorInSecondaryPanel = false; - _windowRect = GUILayout.Window(253, _windowRect, _guiTimedControlPanel, Translation.GetString("Timed_traffic_lights_manager"), WindowStyle); + _windowRect = GUILayout.Window(253, _windowRect, _guiTimedControlPanel, Translation.GetString("Timed_traffic_lights_manager"), WindowStyle); - _cursorInSecondaryPanel = _windowRect.Contains(Event.current.mousePosition); + _cursorInSecondaryPanel = _windowRect.Contains(Event.current.mousePosition); - GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, new Vector3(1, 1, 1)); // revert scaling + 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; - IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; - IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; - - 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(); - - for (int i = 0; i < 8; ++i) { - ushort srcSegmentId = 0; - Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { - srcSegmentId = node.GetSegment(i); - return true; - }); - - if (srcSegmentId == 0) { - continue; - } - - bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(srcSegmentId, nodeId); - - ICustomSegmentLights liveSegmentLights = customTrafficLightsManager.GetSegmentLights(srcSegmentId, 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; - } - } + 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; + IExtSegmentManager segMan = Constants.ManagerFactory.ExtSegmentManager; + IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager; + + 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(); + + for (int i = 0; i < 8; ++i) { + ushort srcSegmentId = 0; + Constants.ServiceFactory.NetService.ProcessNode(nodeId, delegate (ushort nId, ref NetNode node) { + srcSegmentId = node.GetSegment(i); + return true; + }); + + if (srcSegmentId == 0) { + continue; + } + + bool startNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(srcSegmentId, nodeId); + + ICustomSegmentLights liveSegmentLights = customTrafficLightsManager.GetSegmentLights(srcSegmentId, 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; + } + } #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); - } + 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; + if (lightOffset == 0 && showPedLight) { + // PEDESTRIAN COUNTER + if (timedActive && _timedShowNumbers) { + var counterSize = 20f * zoom; + + var counter = timedNode.CheckNextChange(srcSegmentId, 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; + } + } + } + + ExtSegment seg = segMan.ExtSegments[srcSegmentId]; + ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(srcSegmentId, startNode)]; + + if (seg.oneWay && segEnd.outgoing) + continue; + + bool hasOutgoingLeftSegment; + bool hasOutgoingForwardSegment; + bool hasOutgoingRightSegment; + segEndMan.CalculateOutgoingLeftStraightRightSegments(ref segEnd, ref Singleton.instance.m_nodes.m_buffer[nodeId], out hasOutgoingLeftSegment, out hasOutgoingForwardSegment, out hasOutgoingRightSegment); + + 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, 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, 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, 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, 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 counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 3); - - float numOffset; + 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); - if (liveSegmentLights.PedestrianLightState == RoadBaseAI.TrafficLightState.Red) { // TODO check this - numOffset = counterSize + 53f * zoom - modeHeight * 2; - } else { - numOffset = counterSize + 29f * zoom - modeHeight * 2; - } + _counterStyle.fontSize = (int)(18f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - 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); + GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - _counterStyle.fontSize = (int)(15f * zoom); - _counterStyle.normal.textColor = new Color(1f, 1f, 1f); + if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { + _hoveredButton[0] = srcSegmentId; + _hoveredButton[1] = 3; + _hoveredNode = nodeId; + hoveredSegment = true; + } + } - GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); + // right arrow light + if (hasOutgoingRightSegment) { + guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 4 && + _hoveredNode == nodeId); - if (myRectCounterNum.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 2; - _hoveredNode = nodeId; - hoveredSegment = true; - } - } - } + GUI.color = guiColor; - ExtSegment seg = segMan.ExtSegments[srcSegmentId]; - ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(srcSegmentId, startNode)]; + var rect5 = + new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0f) - pedestrianWidth + 5f * zoom, + offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); - if (seg.oneWay && segEnd.outgoing) - continue; + drawRightLightTexture(liveSegmentLight.LightRight, rect5); - bool hasOutgoingLeftSegment; - bool hasOutgoingForwardSegment; - bool hasOutgoingRightSegment; - segEndMan.CalculateOutgoingLeftStraightRightSegments(ref segEnd, ref Singleton.instance.m_nodes.m_buffer[nodeId], out hasOutgoingLeftSegment, out hasOutgoingForwardSegment, out hasOutgoingRightSegment); + 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(); + } + } - bool hasOtherLight = false; - switch (liveSegmentLight.CurrentMode) { - case LightMode.Simple: { - // no arrow light - guiColor.a = MainTool.GetHandleAlpha(_hoveredButton[0] == srcSegmentId && _hoveredButton[1] == 3 && _hoveredNode == nodeId); + // COUNTER + if (timedActive && _timedShowNumbers) { + var counterSize = 20f * zoom; - GUI.color = guiColor; + var counter = timedNode.CheckNextChange(srcSegmentId, startNode, vehicleType, 2); - var myRect4 = - new Rect(offsetScreenPos.x - lightWidth / 2 - (_timedPanelAdd || _timedEditStep >= 0 ? lightWidth : 0) - pedestrianWidth + 5f * zoom, - offsetScreenPos.y - lightHeight / 2, lightWidth, lightHeight); + float numOffset; - drawMainLightTexture(liveSegmentLight.LightMain, myRect4); + if (liveSegmentLight.LightRight == RoadBaseAI.TrafficLightState.Red) { + numOffset = counterSize + 96f * zoom - modeHeight * 2; + } else { + numOffset = counterSize + 40f * zoom - modeHeight * 2; + } - if (myRect4.Contains(Event.current.mousePosition) && !IsCursorInPanel()) { - _hoveredButton[0] = srcSegmentId; - _hoveredButton[1] = 3; - _hoveredNode = nodeId; - hoveredSegment = true; + 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); - if (MainTool.CheckClicked() && !timedActive && (_timedPanelAdd || _timedEditStep >= 0)) { - liveSegmentLight.ChangeMainLight(); - } - } + _counterStyle.fontSize = (int)(18f * zoom); + _counterStyle.normal.textColor = new Color(1f, 1f, 1f); - // COUNTER - if (timedActive && _timedShowNumbers) { - var counterSize = 20f * zoom; + GUI.Label(myRectCounterNum, counter.ToString(), _counterStyle); - var counter = timedNode.CheckNextChange(srcSegmentId, 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, 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, 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, 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, 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); + 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; + 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, 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, 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, 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; - } - } - } -} + 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, 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, 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, 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; + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs b/TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs index 05fd50643..1122de7cb 100644 --- a/TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs +++ b/TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs @@ -13,6 +13,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.UI.SubTools { + using API.Traffic.Enums; + public class ToggleTrafficLightsTool : SubTool { public ToggleTrafficLightsTool(TrafficManagerTool mainTool) : base(mainTool) { diff --git a/TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs b/TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs index 9e54a5ed1..28a66ceaa 100644 --- a/TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs +++ b/TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs @@ -1,354 +1,355 @@ -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.Traffic.Enums; -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; +namespace TrafficManager.UI.SubTools { + using System.Collections.Generic; + using API.Traffic.Enums; + using ColossalFramework; + using GenericGameBridge.Service; + using Manager.Impl; + using State; + using Util; + using UnityEngine; + using static Util.SegmentLaneTraverser; + + public class VehicleRestrictionsTool : SubTool { + private static ExtVehicleType[] roadVehicleTypes = { + ExtVehicleType.PassengerCar, ExtVehicleType.Bus, ExtVehicleType.Taxi, ExtVehicleType.CargoTruck, + ExtVehicleType.Service, ExtVehicleType.Emergency + }; + + private static ExtVehicleType[] railVehicleTypes = { + 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); - } + 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 + 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]; + 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); + ExtVehicleType baseMask = VehicleRestrictionsManager.Instance.GetBaseMask(laneInfo, VehicleRestrictionsMode.Configured); - if (baseMask == ExtVehicleType.None) - continue; + if (baseMask == ExtVehicleType.None) + continue; - VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, baseMask); - } - RefreshCurrentRestrictedSegmentIds(SelectedSegmentId); - } + VehicleRestrictionsManager.Instance.SetAllowedVehicleTypes(SelectedSegmentId, selectedSegmentInfo, laneIndex, laneInfo, laneId, baseMask); + } + RefreshCurrentRestrictedSegmentIds(SelectedSegmentId); + } - if (GUILayout.Button(Translation.GetString("Ban_all_vehicles"))) { - // ban all vehicle types + 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.curSeg.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; + 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.curSeg.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 @@ -368,36 +369,36 @@ private bool drawVehicleRestrictionHandles(ushort segmentId, ref NetSegment segm ++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; - } - } -} + 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; + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/TextureResources.cs b/TLM/TLM/UI/TextureResources.cs index 3a14191c2..e59f9f83c 100644 --- a/TLM/TLM/UI/TextureResources.cs +++ b/TLM/TLM/UI/TextureResources.cs @@ -1,22 +1,17 @@ -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.Traffic.Enums; -using TrafficManager.UI; -using TrafficManager.Util; -using UnityEngine; -using static TrafficManager.Traffic.Data.PrioritySegment; - -namespace TrafficManager.UI -{ - public class TextureResources - { +namespace TrafficManager.UI { + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + using API.Traffic.Enums; + using CSUtil.Commons; + using Manager.Impl; + using State; + using Traffic.Data; + using UnityEngine; + using Util; + + public class TextureResources { public static readonly Texture2D RedLightTexture2D; public static readonly Texture2D YellowRedLightTexture2D; public static readonly Texture2D YellowLightTexture2D; @@ -42,48 +37,49 @@ public class TextureResources 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 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 SpeedLimitTexturesKmph; + public static readonly IDictionary SpeedLimitTexturesMphUS; + public static readonly IDictionary SpeedLimitTexturesMphUK; + 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); + 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 @@ -112,58 +108,80 @@ static TextureResources() 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); + 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); + // 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); + + // TODO: Split loading here into dynamic sections, static enforces everything to stay in this ctor + SpeedLimitTexturesKmph = new TinyDictionary(); + SpeedLimitTexturesMphUS = new TinyDictionary(); + SpeedLimitTexturesMphUK = new TinyDictionary(); + + // Load shared speed limit signs for Kmph and Mph + // Assumes that signs from 0 to 140 with step 5 exist, 0 denotes no limit sign + for (var speedLimit = 0; speedLimit <= 140; speedLimit += 5) { + var resource = LoadDllResource($"SpeedLimits.Kmh.{speedLimit}.png", 200, 200); + SpeedLimitTexturesKmph.Add(speedLimit, resource ?? SpeedLimitTexturesKmph[5]); + } + // Signs from 0 to 90 for MPH + for (var speedLimit = 0; speedLimit <= 90; speedLimit += 5) { + // Load US textures, they are rectangular + var resourceUs = LoadDllResource($"SpeedLimits.Mph_US.{speedLimit}.png", 200, 250); + SpeedLimitTexturesMphUS.Add(speedLimit, resourceUs ?? SpeedLimitTexturesMphUS[5]); + // Load UK textures, they are square + var resourceUk = LoadDllResource($"SpeedLimits.Mph_UK.{speedLimit}.png", 200, 200); + SpeedLimitTexturesMphUK.Add(speedLimit, resourceUk ?? SpeedLimitTexturesMphUK[5]); + } + + 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); @@ -171,28 +189,92 @@ static TextureResources() LeftOnRedForbiddenTexture2D = LoadDllResource("left_on_red_forbidden.png", 200, 200); EnterBlockedJunctionAllowedTexture2D = LoadDllResource("enterblocked_allowed.png", 200, 200); - EnterBlockedJunctionForbiddenTexture2D = LoadDllResource("enterblocked_forbidden.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); + } - PedestrianCrossingAllowedTexture2D = LoadDllResource("crossing_allowed.png", 200, 200); - PedestrianCrossingForbiddenTexture2D = LoadDllResource("crossing_forbidden.png", 200, 200); + /// + /// Given speed limit, round it up to nearest Kmph or Mph and produce a texture + /// + /// Ingame speed + /// The texture, hopefully it existed + public static Texture2D GetSpeedLimitTexture(float speedLimit) { + var m = GlobalConfig.Instance.Main; + var unit = m.DisplaySpeedLimitsMph ? SpeedUnit.Mph : SpeedUnit.Kmph; + return GetSpeedLimitTexture(speedLimit, m.MphRoadSignStyle, unit); + } - 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); + /// + /// Given the float speed, style and MPH option return a texture to render. + /// + /// float speed + /// Signs theme + /// Mph or km/h + /// + public static Texture2D GetSpeedLimitTexture(float speedLimit, MphSignStyle mphStyle, SpeedUnit unit) { + // Select the source for the textures based on unit and the theme + var mph = unit == SpeedUnit.Mph; + var textures = SpeedLimitTexturesKmph; + if (mph) { + switch (mphStyle) { + case MphSignStyle.SquareUS: + textures = SpeedLimitTexturesMphUS; + break; + case MphSignStyle.RoundUK: + textures = SpeedLimitTexturesMphUK; + break; + case MphSignStyle.RoundGerman: + // Do nothing, this is the default above + break; + } + } - RemoveButtonTexture2D = LoadDllResource("remove-btn.png", 150, 30); + // Trim the range + if (speedLimit > SpeedLimitManager.MAX_SPEED * 0.95f) { + return textures[0]; + } - WindowBackgroundTexture2D = LoadDllResource("WindowBackground.png", 16, 60); - } + // Round to nearest 5 MPH or nearest 10 km/h + var index = mph ? SpeedLimit.ToMphRounded(speedLimit) : SpeedLimit.ToKmphRounded(speedLimit); + + // Trim the index since 140 km/h / 90 MPH is the max sign we have + var upper = mph ? SpeedLimit.UPPER_MPH : SpeedLimit.UPPER_KMPH; +#if DEBUG + if (index > upper) { + Log.Info($"Trimming speed={speedLimit} index={index} to {upper}"); + } +#endif + var trimIndex = Math.Min(upper, Math.Max((ushort)0, index)); + return textures[trimIndex]; + } private static Texture2D LoadDllResource(string resourceName, int width, int height) { @@ -265,5 +347,5 @@ static byte[] ReadToEnd(Stream stream) stream.Position = originalPosition; } } - } + } } \ No newline at end of file diff --git a/TLM/TLM/UI/ToolMode.cs b/TLM/TLM/UI/ToolMode.cs index efea4c192..af4ffa4b1 100644 --- a/TLM/TLM/UI/ToolMode.cs +++ b/TLM/TLM/UI/ToolMode.cs @@ -1,7 +1,5 @@ -namespace TrafficManager.UI -{ - public enum ToolMode - { +namespace TrafficManager.UI { + public enum ToolMode { None = 0, SwitchTrafficLight = 1, AddPrioritySigns = 2, @@ -9,13 +7,13 @@ public enum ToolMode TimedLightsSelectNode = 4, TimedLightsShowLights = 5, LaneChange = 6, - TimedLightsAddNode = 7, - TimedLightsRemoveNode = 8, - TimedLightsCopyLights = 9, - SpeedLimits = 10, - VehicleRestrictions = 11, - LaneConnector = 12, - JunctionRestrictions = 13, - ParkingRestrictions = 14 - } -} + TimedLightsAddNode = 7, + TimedLightsRemoveNode = 8, + TimedLightsCopyLights = 9, + SpeedLimits = 10, + VehicleRestrictions = 11, + LaneConnector = 12, + JunctionRestrictions = 13, + ParkingRestrictions = 14 + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/TrafficManagerTool.cs b/TLM/TLM/UI/TrafficManagerTool.cs index 7ec251cd1..969326c18 100644 --- a/TLM/TLM/UI/TrafficManagerTool.cs +++ b/TLM/TLM/UI/TrafficManagerTool.cs @@ -1,1182 +1,1191 @@ -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 TrafficManager.Traffic.Enums; -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; } +namespace TrafficManager.UI { + using System; + using System.Collections.Generic; + using System.Linq; + using API.Traffic.Data; + using API.Traffic.Enums; + using ColossalFramework; + using ColossalFramework.Math; + using ColossalFramework.UI; + using CSUtil.Commons; + using JetBrains.Annotations; + using Manager; + using Manager.Impl; + using State; + using Traffic.Data; + using Traffic.Enums; + using UI.MainMenu; + using UI.SubTools; + using Util; + using UnityEngine; + + [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 + 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 + || LoadingExtension.BaseUI.GetDebugMenu().containsMouse #endif - ) { + ) { #if DEBUG - Log._Debug($"TrafficManagerTool: OnToolUpdate: Menu contains mouse. Ignoring click."); + Log._Debug($"TrafficManagerTool: OnToolUpdate: Menu contains mouse. Ignoring click."); #endif - return; - } + return; + } - if (/*!elementsHovered || (*/activeSubTool != null && activeSubTool.IsCursorInPanel()/*)*/) { + if (/*!elementsHovered || (*/activeSubTool != null && activeSubTool.IsCursorInPanel()/*)*/) { #if DEBUG - Log._Debug($"TrafficManagerTool: OnToolUpdate: Subtool contains mouse. Ignoring click."); + 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; - } + //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 (HoveredSegmentId == 0 && HoveredNodeId == 0) { + //Log.Message("no hovered segment"); + return; + }*/ - if (activeSubTool != null) { - determineHoveredElements(); + if (activeSubTool != null) { + determineHoveredElements(); - if (primaryMouseClicked) - activeSubTool.OnPrimaryClickOverlay(); + if (primaryMouseClicked) + activeSubTool.OnPrimaryClickOverlay(); - if (secondaryMouseClicked) - activeSubTool.OnSecondaryClickOverlay(); - } - } + if (secondaryMouseClicked) + activeSubTool.OnSecondaryClickOverlay(); + } + } - protected override void OnToolGUI(Event e) { - try { - if (!Input.GetMouseButtonDown(0)) { - mouseClickProcessed = false; - } + protected override void OnToolGUI(Event e) { + try { + if (!Input.GetMouseButtonDown(0)) { + mouseClickProcessed = false; + } - if (Options.nodesOverlay) { - _guiSegments(); - _guiNodes(); - } + 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()}"); + 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; } - 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; - - LaneTrafficData laneTrafficData; - bool laneTrafficDataLoaded = TrafficMeasurementManager.Instance.GetLaneTrafficData(segmentId, (byte)i, out laneTrafficData); - - NetInfo.Lane laneInfo = segmentInfo.m_lanes[i]; + 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; + + 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; + uint pfTrafficBuf = TrafficMeasurementManager.Instance.segmentDirTrafficData[TrafficMeasurementManager.Instance.GetDirIndex(segmentId, laneInfo.m_finalDirection)].totalPathFindTrafficBuffer; #endif - //TrafficMeasurementManager.Instance.GetTrafficData(segmentId, laneInfo.m_finalDirection, out dirTrafficData); + //TrafficMeasurementManager.Instance.GetTrafficData(segmentId, laneInfo.m_finalDirection, out dirTrafficData); - //int dirIndex = laneInfo.m_finalDirection == NetInfo.Direction.Backward ? 1 : 0; + //int dirIndex = laneInfo.m_finalDirection == NetInfo.Direction.Backward ? 1 : 0; - labelStr += "L idx " + i + ", id " + curLaneId; + 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; + 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 (laneTrafficDataLoaded) { + labelStr += ", sp: " + (TrafficMeasurementManager.Instance.CalcLaneRelativeMeanSpeed(segmentId, (byte)i, curLaneId, laneInfo) / 100) + "%"; #if DEBUG - labelStr += ", buf: " + laneTrafficData.trafficBuffer + ", max: " + laneTrafficData.maxTrafficBuffer + ", acc: " + laneTrafficData.accumulatedSpeeds; + 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") + " %)"; + 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; + 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; + 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 + ")"; + //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; - NetManager netManager = Singleton.instance; - - GUIStyle _counterStyle = new GUIStyle(); - IExtSegmentEndManager endMan = Constants.ManagerFactory.ExtSegmentEndManager; - for (int i = 1; i < NetManager.MAX_SEGMENT_COUNT; ++i) { - if ((netManager.m_segments.m_buffer[i].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) // segment is unused - continue; - - ItemClass.Service service = netManager.m_segments.m_buffer[i].Info.GetService(); - ItemClass.SubService subService = netManager.m_segments.m_buffer[i].Info.GetSubService(); + //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; + NetManager netManager = Singleton.instance; + + GUIStyle _counterStyle = new GUIStyle(); + IExtSegmentEndManager endMan = Constants.ManagerFactory.ExtSegmentEndManager; + for (int i = 1; i < NetManager.MAX_SEGMENT_COUNT; ++i) { + if ((netManager.m_segments.m_buffer[i].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) // segment is unused + continue; + + ItemClass.Service service = netManager.m_segments.m_buffer[i].Info.GetService(); + ItemClass.SubService subService = netManager.m_segments.m_buffer[i].Info.GetSubService(); #if !DEBUG - if ((netManager.m_segments.m_buffer[i].m_flags & NetSegment.Flags.Untouchable) != NetSegment.Flags.None) - continue; + if ((netManager.m_segments.m_buffer[i].m_flags & NetSegment.Flags.Untouchable) != NetSegment.Flags.None) + continue; #endif - var segmentInfo = netManager.m_segments.m_buffer[i].Info; + var segmentInfo = netManager.m_segments.m_buffer[i].Info; - Vector3 centerPos = netManager.m_segments.m_buffer[i].m_bounds.center; - Vector3 screenPos; - bool visible = WorldToScreenPoint(centerPos, out screenPos); + Vector3 centerPos = netManager.m_segments.m_buffer[i].m_bounds.center; + Vector3 screenPos; + bool visible = WorldToScreenPoint(centerPos, out screenPos); - if (!visible) - continue; + 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 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; + var zoom = 1.0f / diff.magnitude * 150f; - _counterStyle.fontSize = (int)(12f * zoom); - _counterStyle.normal.textColor = new Color(1f, 0f, 0f); + _counterStyle.fontSize = (int)(12f * zoom); + _counterStyle.normal.textColor = new Color(1f, 0f, 0f); - String labelStr = "Segment " + i; + String labelStr = "Segment " + i; #if DEBUG - labelStr += ", flags: " + netManager.m_segments.m_buffer[i].m_flags.ToString(); // + ", condition: " + segments.m_buffer[i].m_condition; + labelStr += ", flags: " + netManager.m_segments.m_buffer[i].m_flags.ToString(); // + ", condition: " + segments.m_buffer[i].m_condition; #endif #if DEBUG - labelStr += "\nsvc: " + service + ", sub: " + subService; - uint startVehicles = endMan.GetRegisteredVehicleCount(ref endMan.ExtSegmentEnds[endMan.GetIndex((ushort)i, true)]); - uint endVehicles = endMan.GetRegisteredVehicleCount(ref endMan.ExtSegmentEnds[endMan.GetIndex((ushort)i, false)]); - labelStr += "\nstart veh.: " + startVehicles + ", end veh.: " + endVehicles; + labelStr += "\nsvc: " + service + ", sub: " + subService; + uint startVehicles = endMan.GetRegisteredVehicleCount(ref endMan.ExtSegmentEnds[endMan.GetIndex((ushort)i, true)]); + uint endVehicles = endMan.GetRegisteredVehicleCount(ref endMan.ExtSegmentEnds[endMan.GetIndex((ushort)i, false)]); + labelStr += "\nstart veh.: " + startVehicles + ", end veh.: " + endVehicles; #endif - labelStr += "\nTraffic: " + netManager.m_segments.m_buffer[i].m_trafficDensity + " %"; + labelStr += "\nTraffic: " + netManager.m_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); + int fwdSegIndex = trafficMeasurementManager.GetDirIndex((ushort)i, NetInfo.Direction.Forward); + int backSegIndex = trafficMeasurementManager.GetDirIndex((ushort)i, NetInfo.Direction.Backward); - labelStr += "\n"; + 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 % + 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 += ", "; + 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) + "%"; + labelStr += "mean speeds: "; + labelStr += " " + (trafficMeasurementManager.SegmentDirTrafficData[fwdSegIndex].meanSpeed / 100) + "%/" + (trafficMeasurementManager.SegmentDirTrafficData[backSegIndex].meanSpeed / 100) + "%"; #if PFTRAFFICSTATS || MEASURECONGESTION - labelStr += "\n"; + labelStr += "\n"; #endif #if PFTRAFFICSTATS - labelStr += "pf bufs: "; - labelStr += " " + (trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].totalPathFindTrafficBuffer) + "/" + (trafficMeasurementManager.segmentDirTrafficData[backSegIndex].totalPathFindTrafficBuffer); + labelStr += "pf bufs: "; + labelStr += " " + (trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].totalPathFindTrafficBuffer) + "/" + (trafficMeasurementManager.segmentDirTrafficData[backSegIndex].totalPathFindTrafficBuffer); #endif #if PFTRAFFICSTATS && MEASURECONGESTION - labelStr += ", "; + 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 + ")"; + labelStr += "cong: "; + labelStr += " " + fwdCongestionRatio + "% (" + trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongested + "/" + trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements + ")/" + backCongestionRatio + "% (" + trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongested + "/" + trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongestionMeasurements + ")"; #endif - labelStr += "\nstart: " + netManager.m_segments.m_buffer[i].m_startNode + ", end: " + netManager.m_segments.m_buffer[i].m_endNode; + labelStr += "\nstart: " + netManager.m_segments.m_buffer[i].m_startNode + ", end: " + netManager.m_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); + 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); - GUI.Label(labelRect, labelStr, _counterStyle); + if (Options.showLanes) + _guiLanes((ushort)i, ref netManager.m_segments.m_buffer[i], ref segmentInfo); + } + } - if (Options.showLanes) - _guiLanes((ushort)i, ref netManager.m_segments.m_buffer[i], ref segmentInfo); - } - } + /// + /// Displays node ids over nodes + /// + private void _guiNodes() { + GUIStyle _counterStyle = new GUIStyle(); + NetManager netManager = Singleton.instance; - /// - /// Displays node ids over nodes - /// - private void _guiNodes() { - NetManager netManager = Singleton.instance; - GUIStyle _counterStyle = new GUIStyle(); - - for (int i = 1; i < NetManager.MAX_NODE_COUNT; ++i) { - if ((netManager.m_nodes.m_buffer[i].m_flags & NetNode.Flags.Created) == NetNode.Flags.None) // node is unused - continue; + for (int i = 1; i < NetManager.MAX_NODE_COUNT; ++i) { + if ((netManager.m_nodes.m_buffer[i].m_flags & NetNode.Flags.Created) == NetNode.Flags.None) // node is unused + continue; - Vector3 pos = netManager.m_nodes.m_buffer[i].m_position; - Vector3 screenPos; - bool visible = WorldToScreenPoint(pos, out screenPos); + Vector3 pos = netManager.m_nodes.m_buffer[i].m_position; + Vector3 screenPos; + bool visible = WorldToScreenPoint(pos, out screenPos); - if (!visible) - continue; + 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 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; + var zoom = 1.0f / diff.magnitude * 150f; - _counterStyle.fontSize = (int)(15f * zoom); - _counterStyle.normal.textColor = new Color(0f, 0f, 1f); + _counterStyle.fontSize = (int)(15f * zoom); + _counterStyle.normal.textColor = new Color(0f, 0f, 1f); - String labelStr = "Node " + i; + String labelStr = "Node " + i; #if DEBUG labelStr += $"\nflags: {netManager.m_nodes.m_buffer[i].m_flags}"; labelStr += $"\nlane: {netManager.m_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(); - LaneConnectionManager connManager = LaneConnectionManager.Instance; - SimulationManager simManager = Singleton.instance; - NetManager netManager = Singleton.instance; - ExtVehicleManager vehStateManager = ExtVehicleManager.Instance; - VehicleManager vehicleManager = Singleton.instance; - - - int startVehicleId = 1; - int endVehicleId = (int)(VehicleManager.MAX_VEHICLE_COUNT - 1); + 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(); +// LaneConnectionManager connManager = LaneConnectionManager.Instance; + SimulationManager simManager = Singleton.instance; +// NetManager netManager = Singleton.instance; + ExtVehicleManager vehStateManager = ExtVehicleManager.Instance; + VehicleManager vehicleManager = Singleton.instance; + + + int startVehicleId = 1; + int endVehicleId = (int) (Constants.ServiceFactory.VehicleService.MaxVehicleCount - 1); #if DEBUG - if (GlobalConfig.Instance.Debug.VehicleId != 0) { - startVehicleId = endVehicleId = GlobalConfig.Instance.Debug.VehicleId; - } + if (GlobalConfig.Instance.Debug.VehicleId != 0) { + startVehicleId = endVehicleId = GlobalConfig.Instance.Debug.VehicleId; + } #endif - for (int i = startVehicleId; i <= endVehicleId; ++i) { - if (vehicleManager.m_vehicles.m_buffer[i].m_flags == 0) // node is unused - continue; + for (int i = startVehicleId; i <= endVehicleId; ++i) { + if (vehicleManager.m_vehicles.m_buffer[i].m_flags == 0) // node is unused + continue; - Vector3 vehPos = vehicleManager.m_vehicles.m_buffer[i].GetSmoothPosition((ushort)i); - Vector3 screenPos; - bool visible = WorldToScreenPoint(vehPos, out screenPos); + Vector3 vehPos = vehicleManager.m_vehicles.m_buffer[i].GetSmoothPosition((ushort) i); + Vector3 screenPos; + bool visible = WorldToScreenPoint(vehPos, out screenPos); - if (!visible) - continue; + 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 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; + 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)); + _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)); - ExtVehicle vState = vehStateManager.ExtVehicles[(ushort)i]; - ExtCitizenInstance driverInst = ExtCitizenInstanceManager.Instance.ExtInstances[Constants.ManagerFactory.ExtVehicleManager.GetDriverInstanceId((ushort)i, ref Singleton.instance.m_vehicles.m_buffer[i])]; - bool startNode = vState.currentStartNode; - ushort segmentId = vState.currentSegmentId; - ushort vehSpeed = SpeedLimitManager.Instance.VehicleToCustomSpeed(vehicleManager.m_vehicles.m_buffer[i].GetLastFrameVelocity().magnitude); + ExtVehicle vState = vehStateManager.ExtVehicles[(ushort) i]; + ExtCitizenInstance driverInst = ExtCitizenInstanceManager.Instance.ExtInstances[Constants.ManagerFactory.ExtVehicleManager.GetDriverInstanceId((ushort) i, ref Singleton.instance.m_vehicles.m_buffer[i])]; +// bool startNode = vState.currentStartNode; +// ushort segmentId = vState.currentSegmentId; + // Some magical constant converting magnitudes into km/h + float vehSpeed = SpeedLimit.ToKmphPrecise(vehicleManager.m_vehicles.m_buffer[i].GetLastFrameVelocity().magnitude / 8f); #if DEBUG - if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None && driverInst.pathMode != GlobalConfig.Instance.Debug.ExtPathMode) { - continue; - } + 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=" + Singleton.instance.m_vehicles.m_buffer[i].GetLastFrameVelocity().sqrMagnitude + "] (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: " + ExtCitizenInstanceManager.Instance.GetCitizenId(driverInst.instanceId) + " 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.ExtVehicleManager.GetStaticVehicleRand((ushort)i) + " trnd: " + Constants.ManagerFactory.ExtVehicleManager.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); - } - } - - private void _guiCitizens() { - CitizenManager citManager = Singleton.instance; - GUIStyle _counterStyle = new GUIStyle(); - for (int i = 1; i < CitizenManager.MAX_INSTANCE_COUNT; ++i) { - if ((citManager.m_instances.m_buffer[i].m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) - continue; + var labelStr = + $"V #{i} is a {(vState.recklessDriver ? "reckless " : string.Empty)}{vState.flags} " + + $"{vState.vehicleType} @ ~{vehSpeed:0.0} km/h (len: {vState.totalLength:0.0}, " + + $"{vState.junctionTransitState} @ {vState.currentSegmentId} " + + $"({vState.currentStartNode}), l. {vState.currentLaneIndex} -> {vState.nextSegmentId}, " + + $"l. {vState.nextLaneIndex}), w: {vState.waitTime}\n" + + $"di: {driverInst.instanceId} dc: {ExtCitizenInstanceManager.Instance.GetCitizenId(driverInst.instanceId)} m: {driverInst.pathMode} " + + $"f: {driverInst.failedParkingAttempts} l: {driverInst.parkingSpaceLocation} " + + $"lid: {driverInst.parkingSpaceLocationId} ltsu: {vState.lastTransitStateUpdate} " + + $"lpu: {vState.lastPositionUpdate} als: {vState.lastAltLaneSelSegmentId} " + + $"srnd: {Constants.ManagerFactory.ExtVehicleManager.GetStaticVehicleRand((ushort) i)} " + + $"trnd: {Constants.ManagerFactory.ExtVehicleManager.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); + } + } + + private void _guiCitizens() { + GUIStyle _counterStyle = new GUIStyle(); + CitizenManager citManager = Singleton.instance; + for (int i = 1; i < CitizenManager.MAX_INSTANCE_COUNT; ++i) { + if ((citManager.m_instances.m_buffer[i].m_flags & CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) + continue; #if DEBUG - if (GlobalConfig.Instance.Debug.Switches[14]) { + if (GlobalConfig.Instance.Debug.Switches[14]) { #endif - if (citManager.m_instances.m_buffer[i].m_path != 0) { - continue; - } + if (citManager.m_instances.m_buffer[i].m_path != 0) { + continue; + } #if DEBUG - } + } #endif - Vector3 pos = citManager.m_instances.m_buffer[i].GetSmoothPosition((ushort)i); - Vector3 screenPos; - bool visible = WorldToScreenPoint(pos, out screenPos); + Vector3 pos = citManager.m_instances.m_buffer[i].GetSmoothPosition((ushort) i); + Vector3 screenPos; + bool visible = WorldToScreenPoint(pos, out screenPos); - if (!visible) - continue; + 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 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; + 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)); + _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; - } + if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None && ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode != GlobalConfig.Instance.Debug.ExtPathMode) { + continue; + } #endif - String labelStr = "Inst. " + i + ", Cit. " + citManager.m_instances.m_buffer[i].m_citizen + ",\nm: " + ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode.ToString() + ", tm: " + ExtCitizenManager.Instance.ExtCitizens[citManager.m_instances.m_buffer[i].m_citizen].transportMode + ", ltm: " + ExtCitizenManager.Instance.ExtCitizens[citManager.m_instances.m_buffer[i].m_citizen].lastTransportMode + ", ll: " + ExtCitizenManager.Instance.ExtCitizens[citManager.m_instances.m_buffer[i].m_citizen].lastLocation; - if (citManager.m_instances.m_buffer[i].m_citizen != 0) { - Citizen citizen = Singleton.instance.m_citizens.m_buffer[citManager.m_instances.m_buffer[i].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() { - BuildingManager buildingManager = Singleton.instance; - GUIStyle _counterStyle = new GUIStyle(); - for (int i = 1; i < BuildingManager.MAX_BUILDING_COUNT; ++i) { - if ((buildingManager.m_buildings.m_buffer[i].m_flags & Building.Flags.Created) == Building.Flags.None) - continue; - - Vector3 pos = buildingManager.m_buildings.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)(10f * zoom); - _counterStyle.normal.textColor = new Color(0f, 1f, 0f); - //_counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f)); - - String labelStr = "Building " + i + ", PDemand: " + ExtBuildingManager.Instance.ExtBuildings[i].parkingSpaceDemand + ", IncTDem: " + ExtBuildingManager.Instance.ExtBuildings[i].incomingPublicTransportDemand + ", OutTDem: " + ExtBuildingManager.Instance.ExtBuildings[i].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;*/ - } - } -} + String labelStr = "Inst. " + i + ", Cit. " + citManager.m_instances.m_buffer[i].m_citizen + ",\nm: " + ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode.ToString() + ", tm: " + + ExtCitizenManager.Instance.ExtCitizens[citManager.m_instances.m_buffer[i].m_citizen].transportMode + ", ltm: " + ExtCitizenManager.Instance.ExtCitizens[citManager.m_instances.m_buffer[i].m_citizen].lastTransportMode + ", ll: " + + ExtCitizenManager.Instance.ExtCitizens[citManager.m_instances.m_buffer[i].m_citizen].lastLocation; + if (citManager.m_instances.m_buffer[i].m_citizen != 0) { + Citizen citizen = Singleton.instance.m_citizens.m_buffer[citManager.m_instances.m_buffer[i].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(); + BuildingManager buildingManager = Singleton.instance; + for (int i = 1; i < BuildingManager.MAX_BUILDING_COUNT; ++i) { + if ((buildingManager.m_buildings.m_buffer[i].m_flags & Building.Flags.Created) == Building.Flags.None) + continue; + + Vector3 pos = buildingManager.m_buildings.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)(10f * zoom); + _counterStyle.normal.textColor = new Color(0f, 1f, 0f); + //_counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f)); + + String labelStr = "Building " + i + ", PDemand: " + ExtBuildingManager.Instance.ExtBuildings[i].parkingSpaceDemand + ", IncTDem: " + ExtBuildingManager.Instance.ExtBuildings[i].incomingPublicTransportDemand + ", OutTDem: " + + ExtBuildingManager.Instance.ExtBuildings[i].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;*/ + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/UIBase.cs b/TLM/TLM/UI/UIBase.cs index 077930142..33d0b7123 100644 --- a/TLM/TLM/UI/UIBase.cs +++ b/TLM/TLM/UI/UIBase.cs @@ -1,183 +1,184 @@ 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 class UIBase : UICustomControl { - public UIMainMenuButton MainMenuButton { get; private set; } - public MainMenuPanel MainMenu { get; private set; } + public UIMainMenuButton MainMenuButton { get; private set; } + public MainMenuPanel MainMenu { get; private set; } #if DEBUG - public DebugMenuPanel DebugMenu { get; private set; } + 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(); + 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)); + 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); + 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); + UnityEngine.Object.Destroy(DebugMenu); #endif - } - var uiView = UIView.GetAView(); - MainMenu = (MainMenuPanel)uiView.AddUIComponent(typeof(MainMenuPanel)); - MainMenu.gameObject.AddComponent(); + } + + var uiView = UIView.GetAView(); + MainMenu = (MainMenuPanel)uiView.AddUIComponent(typeof(MainMenuPanel)); + MainMenu.gameObject.AddComponent(); #if DEBUG - DebugMenu = (DebugMenuPanel)uiView.AddUIComponent(typeof(DebugMenuPanel)); + 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"); + } + + public void Show() { + try { + ToolsModifierControl.mainToolbar.CloseEverything(); + } catch (Exception e) { + Log.Error("Error on Show(): " + e); + } + + foreach (var button in GetMenu().Buttons) { + button.UpdateProperties(); + } + + GetMenu().Show(); + Translation.ReloadTutorialTranslations(); + TrafficManagerTool.ShowAdvisor("MainMenu"); #if DEBUG - GetDebugMenu().Show(); + GetDebugMenu().Show(); #endif - SetToolMode(TrafficManagerMode.Activated); - _uiShown = true; - MainMenuButton.UpdateSprites(); - UIView.SetFocus(MainMenu); - } - - public void Close() { - var uiView = UIView.GetAView(); - GetMenu().Hide(); + SetToolMode(TrafficManagerMode.Activated); + _uiShown = true; + MainMenuButton.UpdateSprites(); + UIView.SetFocus(MainMenu); + } + + public void Close() { + var uiView = UIView.GetAView(); + GetMenu().Hide(); #if DEBUG - GetDebugMenu().Hide(); + 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; - } + 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; - } + 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!"); - } - } -} + 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!"); + } + } + } +} \ No newline at end of file diff --git a/TLM/TLM/UI/UIMainMenuButton.cs b/TLM/TLM/UI/UIMainMenuButton.cs index 642d86a8c..71b9041ca 100644 --- a/TLM/TLM/UI/UIMainMenuButton.cs +++ b/TLM/TLM/UI/UIMainMenuButton.cs @@ -1,127 +1,165 @@ -using ColossalFramework.Math; -using ColossalFramework.UI; +using ColossalFramework.UI; using CSUtil.Commons; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using TrafficManager.State; +using TrafficManager.State.Keybinds; 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 class UIMainMenuButton : UIButton, IObserver { + private const string MAIN_MENU_BUTTON_BG_BASE = "TMPE_MainMenuButtonBgBase"; + private const string MAIN_MENU_BUTTON_BG_HOVERED = "TMPE_MainMenuButtonBgHovered"; + private const string MAIN_MENU_BUTTON_BG_ACTIVE = "TMPE_MainMenuButtonBgActive"; + private const string MAIN_MENU_BUTTON_FG_BASE = "TMPE_MainMenuButtonFgBase"; + private const string MAIN_MENU_BUTTON_FG_HOVERED = "TMPE_MainMenuButtonFgHovered"; + private const string MAIN_MENU_BUTTON_FG_ACTIVE = "TMPE_MainMenuButtonFgActive"; + + private const int BUTTON_WIDTH = 50; + private const int BUTTON_HEIGHT = 50; + + private UIDragHandle Drag { get; set; } + + IDisposable confDisposable; + + public override void Start() { + // Place the button. + OnUpdate(GlobalConfig.Instance); + + confDisposable = GlobalConfig.Instance.Subscribe(this); + + // Set the atlas and background/foreground + var spriteNames = new[] { + 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 + }; + atlas = TextureUtil.GenerateLinearAtlas( + "TMPE_MainMenuButtonAtlas", + TextureResources.MainMenuButtonTexture2D, + 6, + spriteNames); + + 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; + + // Set up the tooltip + var uiView = GetUIView(); + if (uiView != null) { + m_TooltipBox = uiView.defaultTooltipBox; + } + + UpdateTooltip(); } - 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(); - } - } -} + /// + /// Reset the tooltip (or set for the first time), or if keybinding has changed + /// + public void UpdateTooltip() { + tooltip = Translation.GetString("Keybind_toggle_TMPE_main_menu") + + GetTooltip(); + } + + public override void OnDestroy() { + if (confDisposable != null) { + confDisposable.Dispose(); + } + } + + internal void SetPosLock(bool lck) { + Drag.enabled = !lck; + } + + protected override void OnClick(UIMouseEventParameter p) { + try { + Log._Debug($"Current tool: {ToolManager.instance.m_properties.CurrentTool}"); + LoadingExtension.BaseUI.ToggleMainMenu(); + UpdateSprites(); + } + catch (Exception e) { + Log.Error($"Toggle mainmenu failed {e}"); + } + } + + 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(); + } + + public void OnGUI() { + if (!UIView.HasModalInput() + && !UIView.HasInputFocus() + && KeybindSettingsBase.ToggleMainMenu.IsPressed(Event.current)) { + if (LoadingExtension.BaseUI != null) { + LoadingExtension.BaseUI.ToggleMainMenu(); + } + } + } + + private string GetTooltip() { + return KeybindSettingsBase.ToggleMainMenu.ToLocalizedString("\n"); + } + } +} \ No newline at end of file diff --git a/TLM/TLM/Util/ModsCompatibilityChecker.cs b/TLM/TLM/Util/ModsCompatibilityChecker.cs index df1b5e65a..6389e8cbf 100644 --- a/TLM/TLM/Util/ModsCompatibilityChecker.cs +++ b/TLM/TLM/Util/ModsCompatibilityChecker.cs @@ -1,82 +1,177 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; using ColossalFramework; -using ColossalFramework.PlatformServices; using ColossalFramework.Plugins; using ColossalFramework.UI; using CSUtil.Commons; +using ICities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; using TrafficManager.UI; using UnityEngine; +using static ColossalFramework.Plugins.PluginManager; -namespace TrafficManager.Util { - public class ModsCompatibilityChecker { - //TODO include %APPDATA% mods folder +namespace TrafficManager.Util +{ + public class ModsCompatibilityChecker + { + public const ulong LOCAL_MOD = ulong.MaxValue; + // Used for LoadIncompatibleModsList() 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; + private const string INCOMPATIBLE_MODS_FILE = "incompatible_mods.txt"; - public ModsCompatibilityChecker() { - incompatibleModList = LoadIncompatibleModList(); - userModList = GetUserModsList(); - } + // parsed contents of incompatible_mods.txt + private readonly Dictionary knownIncompatibleMods; - 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); - } - } + public ModsCompatibilityChecker() + { + knownIncompatibleMods = LoadListOfIncompatibleMods(); + } - if (incompatibleMods.Count > 0) { - Log.Warning("Incompatible mods detected! Count: " + incompatibleMods.Count); + /// + /// Initiates scan for incompatible mods. If any found, and the user has enabled the mod checker, it creates and initialises the modal dialog panel. + /// + public void PerformModCheck() + { + try + { + Dictionary detected = ScanForIncompatibleMods(); - if (State.GlobalConfig.Instance.Main.ScanForKnownIncompatibleModsAtStartup) { + if (detected.Count > 0) + { IncompatibleModsPanel panel = UIView.GetAView().AddUIComponent(typeof(IncompatibleModsPanel)) as IncompatibleModsPanel; - panel.IncompatibleMods = incompatibleMods; + panel.IncompatibleMods = detected; panel.Initialize(); UIView.PushModal(panel); UIView.SetFocus(panel); } - } else { - Log.Info("No incompatible mods detected"); + } + catch (Exception e) + { + Log.Info("Something went wrong while checking incompatible mods - see main game log for details."); + Debug.LogException(e); } } - 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); + /// + /// Iterates installed mods looking for known incompatibilities. + /// + /// + /// A list of detected incompatible mods. + /// + /// Invalid folder path (contains invalid characters, is empty, or contains only white spaces). + /// Path is too long (longer than the system-defined maximum length). + public Dictionary ScanForIncompatibleMods() + { + Guid selfGuid = Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId; + + Log.Info($"Scanning for incompatible mods; My GUID = {selfGuid}"); + + // list of installed incompatible mods + Dictionary results = new Dictionary(); + + // only check enabled mods? + bool filterToEnabled = State.GlobalConfig.Instance.Main.IgnoreDisabledMods; + + // iterate plugins + foreach (PluginInfo mod in Singleton.instance.GetPluginsInfo()) + { + if (!mod.isBuiltin && !mod.isCameraScript && (!filterToEnabled || mod.isEnabled)) + { + string modName = GetModName(mod); + ulong workshopID = mod.publishedFileID.AsUInt64; + + if (knownIncompatibleMods.ContainsKey(workshopID)) + { + // must be online workshop mod + Log.Info($"Incompatible with: {workshopID} - {modName}"); + results.Add(mod, modName); + } + else if (modName.Contains("TM:PE") || modName.Contains("Traffic Manager")) + { + // It's a TM:PE build - either local or workshop + string workshopIDstr = workshopID == LOCAL_MOD ? "LOCAL" : workshopID.ToString(); + Guid currentGuid = GetModGuid(mod); + + if (currentGuid == selfGuid) + { + Log.Info($"Found myself: '{modName}' (Workshop ID: {workshopIDstr}, GUID: {currentGuid}) in '{mod.modPath}'"); + } + else + { + Log.Info($"Detected conflicting '{modName}' (Workshop ID: {workshopIDstr}, GUID: {currentGuid}) in '{mod.modPath}'"); + results.Add(mod, $"{modName} in /{Path.GetFileName(mod.modPath)}"); + } + } } } - 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]); + Log.Info($"Scan complete: {results.Count} incompatible mod(s) found"); + + return results; + } + + /// + /// Gets the name of the specified mod. + /// + /// It will return the if found, otherwise it will return (assembly name). + /// + /// + /// The associated with the mod. + /// + /// The name of the specified plugin. + public string GetModName(PluginInfo plugin) + { + return ((IUserMod)plugin.userModInstance).Name; + } + + /// + /// Gets the of a mod. + /// + /// + /// The associated with the mod. + /// + /// The of the mod. + public Guid GetModGuid(PluginInfo plugin) + { + return plugin.userModInstance.GetType().Assembly.ManifestModule.ModuleVersionId; + } + + /// + /// Loads and parses the incompatible_mods.txt resource, adds other workshop branches of TM:PE as applicable. + /// + /// + /// A dictionary of mod names referenced by Steam Workshop ID. + private Dictionary LoadListOfIncompatibleMods() + { + // list of known incompatible mods + Dictionary results = new Dictionary(); + + // load the file + string[] lines; + using (Stream st = Assembly.GetExecutingAssembly().GetManifestResourceStream(RESOURCES_PREFIX + INCOMPATIBLE_MODS_FILE)) + { + using (StreamReader sr = new StreamReader(st)) + { + lines = sr.ReadToEnd().Split(new string[] { "\n", "\r\n" }, StringSplitOptions.None); } } - return incompatibleMods; - } + Log.Info($"{RESOURCES_PREFIX}{INCOMPATIBLE_MODS_FILE} contains {lines.Length} entries"); - private ulong[] GetUserModsList() { - if (State.GlobalConfig.Instance.Main.IgnoreDisabledMods) { - return PluginManager.instance.GetPluginsInfo().Where(plugin => plugin.isEnabled).Select(info => info.publishedFileID.AsUInt64).ToArray(); + // parse the file + for (int i = 0; i < lines.Length; i++) + { + ulong steamId; + string[] strings = lines[i].Split(';'); + if (ulong.TryParse(strings[0], out steamId)) + { + results.Add(steamId, strings[1]); + } } - PublishedFileId[] ids = ContentManagerPanel.subscribedItemsTable.ToArray(); - return ids.Select(id => id.AsUInt64).ToArray(); + + return results; } } } \ No newline at end of file diff --git a/TLM/TMPE.API/Geometry/ISegmentEndGeometry.cs b/TLM/TMPE.API/Geometry/ISegmentEndGeometry.cs index 7d6fcd693..8bd0cabc3 100644 --- a/TLM/TMPE.API/Geometry/ISegmentEndGeometry.cs +++ b/TLM/TMPE.API/Geometry/ISegmentEndGeometry.cs @@ -7,6 +7,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.Geometry { + using API.Traffic.Enums; + public interface ISegmentEndGeometry : ISegmentEndId { /// /// Holds the connected node id diff --git a/TLM/TMPE.API/Geometry/ISegmentGeometry.cs b/TLM/TMPE.API/Geometry/ISegmentGeometry.cs index 9dcecb5ab..b511bbc39 100644 --- a/TLM/TMPE.API/Geometry/ISegmentGeometry.cs +++ b/TLM/TMPE.API/Geometry/ISegmentGeometry.cs @@ -5,6 +5,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.Geometry { + using API.Traffic.Enums; + public interface ISegmentGeometry { /// /// Holds the id of the managed segment. diff --git a/TLM/TMPE.API/Manager/IAdvancedParkingManager.cs b/TLM/TMPE.API/Manager/IAdvancedParkingManager.cs index c00950fcd..1d89d73bd 100644 --- a/TLM/TMPE.API/Manager/IAdvancedParkingManager.cs +++ b/TLM/TMPE.API/Manager/IAdvancedParkingManager.cs @@ -9,6 +9,8 @@ using static TrafficManager.Traffic.Data.ExtCitizenInstance; namespace TrafficManager.Manager { + using API.Traffic.Enums; + public interface IAdvancedParkingManager : IFeatureManager { /// /// Determines the color the given building should be colorized with given the current info view mode. diff --git a/TLM/TMPE.API/Manager/ICustomSegmentLightsManager.cs b/TLM/TMPE.API/Manager/ICustomSegmentLightsManager.cs index cededb5b8..550ee5461 100644 --- a/TLM/TMPE.API/Manager/ICustomSegmentLightsManager.cs +++ b/TLM/TMPE.API/Manager/ICustomSegmentLightsManager.cs @@ -1,24 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; +using TrafficManager.Traffic.Enums; 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); - } -} + using API.Traffic.Enums; + using API.TrafficLight; + + 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); + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Manager/IExtVehicleManager.cs b/TLM/TMPE.API/Manager/IExtVehicleManager.cs index 86e3963db..0e68b8798 100644 --- a/TLM/TMPE.API/Manager/IExtVehicleManager.cs +++ b/TLM/TMPE.API/Manager/IExtVehicleManager.cs @@ -1,167 +1,165 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; +using TrafficManager.Traffic.Enums; using TrafficManager.Traffic.Data; namespace TrafficManager.Manager { - public interface IExtVehicleManager { - /// - /// Extended vehicle data - /// - ExtVehicle[] ExtVehicles { get; } - - /// - /// Handles a released vehicle. - /// - /// vehicle id - /// vehicle data - void OnReleaseVehicle(ushort vehicleId, ref Vehicle vehicleData); - - /// - /// Handles a released vehicle. - /// - /// vehicle - void OnRelease(ref ExtVehicle extVehicle, ref Vehicle vehicleData); - - /// - /// Handles a created vehicle. - /// - /// vehicle id - /// vehicle data - void OnCreateVehicle(ushort vehicleId, ref Vehicle vehicleData); - - /// - /// Handles a created vehicle. - /// - /// vehicle - void OnCreate(ref ExtVehicle extVehicle, ref Vehicle vehicleData); - - /// - /// Handles a spawned vehicle. - /// - /// vehicle id - /// vehicle data - void OnSpawnVehicle(ushort vehicleId, ref Vehicle vehicleData); - - /// - /// Handles a spawned vehicle. - /// - /// vehicle - void OnSpawn(ref ExtVehicle extVehicle, ref Vehicle vehicleData); - - /// - /// Handles a despawned vehicle. - /// - /// vehicle id - /// vehicle data - void OnDespawnVehicle(ushort vehicleId, ref Vehicle vehicleData); - - /// - /// Handles a despawned vehicle. - /// - /// vehicle - void OnDespawn(ref ExtVehicle extVehicle); - - /// - /// Handles a vehicle when path-finding starts. - /// - /// vehicle id - /// vehicle data - /// vehicle type to set, optional - /// vehicle type - ExtVehicleType OnStartPathFind(ushort vehicleId, ref Vehicle vehicleData, ExtVehicleType? vehicleType); - - /// - /// Handles a vehicle when path-finding starts. - /// - /// vehicle - /// vehicle data - /// vehicle type to set, optional - /// vehicle type - ExtVehicleType OnStartPathFind(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ExtVehicleType? vehicleType); - - /// - /// Retrieves the driver citizen instance id for the given vehicle. - /// - /// vehicle id - /// vehicle data - /// - ushort GetDriverInstanceId(ushort vehicleId, ref Vehicle data); - - /// - /// Updates the vehicle's current path position. - /// - /// vehicle id - /// vehicle data - void UpdateVehiclePosition(ushort vehicleId, ref Vehicle vehicleData); - - /// - /// Updates the vehicle's current path position. - /// - /// vehicle - /// vehicle data - /// ext. segment end - /// current path position - /// next path position - void UpdatePosition(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ref ExtSegmentEnd segEnd, ref PathUnit.Position curPos, ref PathUnit.Position nextPos); - - /// - /// Unlinks the given vehicle from any segment end that the vehicle is currently linked to. - /// - /// vehicle - void Unlink(ref ExtVehicle extVehicle); - - /// - /// Updates the vehicle's junction transit state to the given value. - /// - /// vehicle - /// junction transit state - void SetJunctionTransitState(ref ExtVehicle extVehicle, VehicleJunctionTransitState transitState); - - /// - /// Determines if the junction transit state has been recently modified - /// - /// /// vehicle - /// true if the junction transit state is considered new, false otherwise - bool IsJunctionTransitStateNew(ref ExtVehicle extVehicle); - - /// - /// Triggers the randomization mechanism that might select a new random value for time-varying vehicle behavior (e.g. speed) - /// - /// ext. vehicle data - /// whether to force generation of a new value - void StepRand(ref ExtVehicle extVehicle, bool force); - - /// - /// 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); - - /// - /// Logs the given vehicle for traffic measurement. - /// - /// vehicle id - /// vehicle - void LogTraffic(ushort vehicleId, ref Vehicle vehicle); - - /// - /// Randomly selects a new set of DLS parameters. - /// - /// ext. vehicle data - void UpdateDynamicLaneSelectionParameters(ref ExtVehicle extVehicle); - } -} + using API.Traffic.Data; + using API.Traffic.Enums; + + public interface IExtVehicleManager { + /// + /// Extended vehicle data + /// + ExtVehicle[] ExtVehicles { get; } + + /// + /// Handles a released vehicle. + /// + /// vehicle id + /// vehicle data + void OnReleaseVehicle(ushort vehicleId, ref Vehicle vehicleData); + + /// + /// Handles a released vehicle. + /// + /// vehicle + void OnRelease(ref ExtVehicle extVehicle, ref Vehicle vehicleData); + + /// + /// Handles a created vehicle. + /// + /// vehicle id + /// vehicle data + void OnCreateVehicle(ushort vehicleId, ref Vehicle vehicleData); + + /// + /// Handles a created vehicle. + /// + /// vehicle + void OnCreate(ref ExtVehicle extVehicle, ref Vehicle vehicleData); + + /// + /// Handles a spawned vehicle. + /// + /// vehicle id + /// vehicle data + void OnSpawnVehicle(ushort vehicleId, ref Vehicle vehicleData); + + /// + /// Handles a spawned vehicle. + /// + /// vehicle + void OnSpawn(ref ExtVehicle extVehicle, ref Vehicle vehicleData); + + /// + /// Handles a despawned vehicle. + /// + /// vehicle id + /// vehicle data + void OnDespawnVehicle(ushort vehicleId, ref Vehicle vehicleData); + + /// + /// Handles a despawned vehicle. + /// + /// vehicle + void OnDespawn(ref ExtVehicle extVehicle); + + /// + /// Handles a vehicle when path-finding starts. + /// + /// vehicle id + /// vehicle data + /// vehicle type to set, optional + /// vehicle type + ExtVehicleType OnStartPathFind(ushort vehicleId, ref Vehicle vehicleData, ExtVehicleType? vehicleType); + + /// + /// Handles a vehicle when path-finding starts. + /// + /// vehicle + /// vehicle data + /// vehicle type to set, optional + /// vehicle type + ExtVehicleType OnStartPathFind(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ExtVehicleType? vehicleType); + + /// + /// Retrieves the driver citizen instance id for the given vehicle. + /// + /// vehicle id + /// vehicle data + /// + ushort GetDriverInstanceId(ushort vehicleId, ref Vehicle data); + + /// + /// Updates the vehicle's current path position. + /// + /// vehicle id + /// vehicle data + void UpdateVehiclePosition(ushort vehicleId, ref Vehicle vehicleData); + + /// + /// Updates the vehicle's current path position. + /// + /// vehicle + /// vehicle data + /// ext. segment end + /// current path position + /// next path position + void UpdatePosition(ref ExtVehicle extVehicle, ref Vehicle vehicleData, ref ExtSegmentEnd segEnd, ref PathUnit.Position curPos, ref PathUnit.Position nextPos); + + /// + /// Unlinks the given vehicle from any segment end that the vehicle is currently linked to. + /// + /// vehicle + void Unlink(ref ExtVehicle extVehicle); + + /// + /// Updates the vehicle's junction transit state to the given value. + /// + /// vehicle + /// junction transit state + void SetJunctionTransitState(ref ExtVehicle extVehicle, VehicleJunctionTransitState transitState); + + /// + /// Determines if the junction transit state has been recently modified + /// + /// /// vehicle + /// true if the junction transit state is considered new, false otherwise + bool IsJunctionTransitStateNew(ref ExtVehicle extVehicle); + + /// + /// Triggers the randomization mechanism that might select a new random value for time-varying vehicle behavior (e.g. speed) + /// + /// ext. vehicle data + /// whether to force generation of a new value + void StepRand(ref ExtVehicle extVehicle, bool force); + + /// + /// 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); + + /// + /// Logs the given vehicle for traffic measurement. + /// + /// vehicle id + /// vehicle + void LogTraffic(ushort vehicleId, ref Vehicle vehicle); + + /// + /// Randomly selects a new set of DLS parameters. + /// + /// ext. vehicle data + void UpdateDynamicLaneSelectionParameters(ref ExtVehicle extVehicle); + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Manager/IManagerFactory.cs b/TLM/TMPE.API/Manager/IManagerFactory.cs index 7bc7b5d3f..a9818415c 100644 --- a/TLM/TMPE.API/Manager/IManagerFactory.cs +++ b/TLM/TMPE.API/Manager/IManagerFactory.cs @@ -5,6 +5,8 @@ using TrafficManager.Manager; namespace TrafficManager.Manager { + using API.Manager; + public interface IManagerFactory { IAdvancedParkingManager AdvancedParkingManager { get; } ICustomSegmentLightsManager CustomSegmentLightsManager { get; } diff --git a/TLM/TMPE.API/Manager/ITrafficLightManager.cs b/TLM/TMPE.API/Manager/ITrafficLightManager.cs index 17f51102a..8caaf8676 100644 --- a/TLM/TMPE.API/Manager/ITrafficLightManager.cs +++ b/TLM/TMPE.API/Manager/ITrafficLightManager.cs @@ -5,6 +5,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.Manager { + using API.Traffic.Enums; + public interface ITrafficLightManager { // TODO documentation bool AddTrafficLight(ushort nodeId, ref NetNode node); diff --git a/TLM/TMPE.API/Manager/ITrafficPriorityManager.cs b/TLM/TMPE.API/Manager/ITrafficPriorityManager.cs index dcf7119cb..a96264966 100644 --- a/TLM/TMPE.API/Manager/ITrafficPriorityManager.cs +++ b/TLM/TMPE.API/Manager/ITrafficPriorityManager.cs @@ -7,6 +7,8 @@ using static TrafficManager.Traffic.Data.PrioritySegment; namespace TrafficManager.Manager { + using API.Traffic.Enums; + public interface ITrafficPriorityManager { // TODO define me! diff --git a/TLM/TMPE.API/Manager/IVehicleBehaviorManager.cs b/TLM/TMPE.API/Manager/IVehicleBehaviorManager.cs index 42aa1ec67..6b77b6f0c 100644 --- a/TLM/TMPE.API/Manager/IVehicleBehaviorManager.cs +++ b/TLM/TMPE.API/Manager/IVehicleBehaviorManager.cs @@ -7,6 +7,8 @@ using UnityEngine; namespace TrafficManager.Manager { + using API.Traffic.Data; + public interface IVehicleBehaviorManager { // TODO define me! // TODO documentation diff --git a/TLM/TMPE.API/Manager/IVehicleRestrictionsManager.cs b/TLM/TMPE.API/Manager/IVehicleRestrictionsManager.cs index fd75364b0..abbcbcc3d 100644 --- a/TLM/TMPE.API/Manager/IVehicleRestrictionsManager.cs +++ b/TLM/TMPE.API/Manager/IVehicleRestrictionsManager.cs @@ -1,47 +1,44 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; +namespace TrafficManager.API.Manager { + using System.Collections.Generic; + using Traffic.Enums; + using TrafficManager.Traffic.Enums; -namespace TrafficManager.Manager { - 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); - void NotifyStartEndNode(ushort segmentId); - void OnLevelUnloading(); - void RemoveAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType); - void ToggleAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType, bool add); - } -} + 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); + void NotifyStartEndNode(ushort segmentId); + void OnLevelUnloading(); + void RemoveAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType); + void ToggleAllowedType(ushort segmentId, NetInfo segmentInfo, uint laneIndex, uint laneId, NetInfo.Lane laneInfo, ExtVehicleType vehicleType, bool add); + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/TMPE.API.csproj b/TLM/TMPE.API/TMPE.API.csproj index ee4a1aa2b..0e17acb7a 100644 --- a/TLM/TMPE.API/TMPE.API.csproj +++ b/TLM/TMPE.API/TMPE.API.csproj @@ -7,7 +7,7 @@ {C911D31C-C85D-42A8-A839-7B209A715495} Library Properties - TrafficManager + TrafficManager.API TMPE.API v3.5 512 diff --git a/TLM/TMPE.API/Traffic/Data/ExtCitizen.cs b/TLM/TMPE.API/Traffic/Data/ExtCitizen.cs index a7628202e..6872a90d7 100644 --- a/TLM/TMPE.API/Traffic/Data/ExtCitizen.cs +++ b/TLM/TMPE.API/Traffic/Data/ExtCitizen.cs @@ -6,6 +6,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.Traffic.Data { + using API.Traffic.Enums; + public struct ExtCitizen { public uint citizenId; diff --git a/TLM/TMPE.API/Traffic/Data/ExtCitizenInstance.cs b/TLM/TMPE.API/Traffic/Data/ExtCitizenInstance.cs index 90dbcfdbc..37a683d03 100644 --- a/TLM/TMPE.API/Traffic/Data/ExtCitizenInstance.cs +++ b/TLM/TMPE.API/Traffic/Data/ExtCitizenInstance.cs @@ -8,6 +8,8 @@ using UnityEngine; namespace TrafficManager.Traffic.Data { + using API.Traffic.Enums; + public struct ExtCitizenInstance { public ushort instanceId; diff --git a/TLM/TMPE.API/Traffic/Data/ExtVehicle.cs b/TLM/TMPE.API/Traffic/Data/ExtVehicle.cs index 177198fe7..58de23a39 100644 --- a/TLM/TMPE.API/Traffic/Data/ExtVehicle.cs +++ b/TLM/TMPE.API/Traffic/Data/ExtVehicle.cs @@ -1,101 +1,100 @@ -using TrafficManager.Traffic.Enums; -using UnityEngine; +namespace TrafficManager.API.Traffic.Data { + using Enums; -namespace TrafficManager.Traffic.Data { - public struct ExtVehicle { - public ushort vehicleId; - public uint lastPathId; - public byte lastPathPositionIndex; - public uint lastTransitStateUpdate; - public uint lastPositionUpdate; - public float totalLength; - public int waitTime; - public ExtVehicleFlags 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; - public VehicleJunctionTransitState junctionTransitState; - // Dynamic Lane Selection - public bool dlsReady; - public float maxReservedSpace; - public float laneSpeedRandInterval; - public int maxOptLaneChanges; - public float maxUnsafeSpeedDiff; - public float minSafeSpeedImprovement; - public float minSafeTrafficImprovement; + public struct ExtVehicle { + public ushort vehicleId; + public uint lastPathId; + public byte lastPathPositionIndex; + public uint lastTransitStateUpdate; + public uint lastPositionUpdate; + public float totalLength; + public int waitTime; + public ExtVehicleFlags 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; + public VehicleJunctionTransitState junctionTransitState; + // Dynamic Lane Selection + public bool dlsReady; + public float maxReservedSpace; + public float laneSpeedRandInterval; + public int maxOptLaneChanges; + public float maxUnsafeSpeedDiff; + public float minSafeSpeedImprovement; + public float minSafeTrafficImprovement; - 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" + $"waitTime = {waitTime}\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" + - "\t" + $"dlsReady = {dlsReady}\n" + - "\t" + $"maxReservedSpace = {maxReservedSpace}\n" + - "\t" + $"laneSpeedRandInterval = {laneSpeedRandInterval}\n" + - "\t" + $"maxOptLaneChanges = {maxOptLaneChanges}\n" + - "\t" + $"maxUnsafeSpeedDiff = {maxUnsafeSpeedDiff}\n" + - "\t" + $"minSafeSpeedImprovement = {minSafeSpeedImprovement}\n" + - "\t" + $"minSafeTrafficImprovement = {minSafeTrafficImprovement}\n" + - "VehicleState]"; - } + 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" + $"waitTime = {waitTime}\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" + + "\t" + $"dlsReady = {dlsReady}\n" + + "\t" + $"maxReservedSpace = {maxReservedSpace}\n" + + "\t" + $"laneSpeedRandInterval = {laneSpeedRandInterval}\n" + + "\t" + $"maxOptLaneChanges = {maxOptLaneChanges}\n" + + "\t" + $"maxUnsafeSpeedDiff = {maxUnsafeSpeedDiff}\n" + + "\t" + $"minSafeSpeedImprovement = {minSafeSpeedImprovement}\n" + + "\t" + $"minSafeTrafficImprovement = {minSafeTrafficImprovement}\n" + + "VehicleState]"; + } - public ExtVehicle(ushort vehicleId) { - this.vehicleId = vehicleId; - lastPathId = 0; - lastPathPositionIndex = 0; - lastTransitStateUpdate = 0; - lastPositionUpdate = 0; - totalLength = 0; - waitTime = 0; - flags = ExtVehicleFlags.None; - vehicleType = ExtVehicleType.None; - heavyVehicle = false; - recklessDriver = false; - currentSegmentId = 0; - currentStartNode = false; - currentLaneIndex = 0; - nextSegmentId = 0; - nextLaneIndex = 0; - previousVehicleIdOnSegment = 0; - nextVehicleIdOnSegment = 0; - lastAltLaneSelSegmentId = 0; - junctionTransitState = VehicleJunctionTransitState.None; - timedRand = 0; - dlsReady = false; - maxReservedSpace = 0; - laneSpeedRandInterval = 0; - maxOptLaneChanges = 0; - maxUnsafeSpeedDiff = 0; - minSafeSpeedImprovement = 0; - minSafeTrafficImprovement = 0; - } - } -} + public ExtVehicle(ushort vehicleId) { + this.vehicleId = vehicleId; + lastPathId = 0; + lastPathPositionIndex = 0; + lastTransitStateUpdate = 0; + lastPositionUpdate = 0; + totalLength = 0; + waitTime = 0; + flags = ExtVehicleFlags.None; + vehicleType = ExtVehicleType.None; + heavyVehicle = false; + recklessDriver = false; + currentSegmentId = 0; + currentStartNode = false; + currentLaneIndex = 0; + nextSegmentId = 0; + nextLaneIndex = 0; + previousVehicleIdOnSegment = 0; + nextVehicleIdOnSegment = 0; + lastAltLaneSelSegmentId = 0; + junctionTransitState = VehicleJunctionTransitState.None; + timedRand = 0; + dlsReady = false; + maxReservedSpace = 0; + laneSpeedRandInterval = 0; + maxOptLaneChanges = 0; + maxUnsafeSpeedDiff = 0; + minSafeSpeedImprovement = 0; + minSafeTrafficImprovement = 0; + } + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Data/PathCreationArgs.cs b/TLM/TMPE.API/Traffic/Data/PathCreationArgs.cs index 5d52eabf0..dda2fd986 100644 --- a/TLM/TMPE.API/Traffic/Data/PathCreationArgs.cs +++ b/TLM/TMPE.API/Traffic/Data/PathCreationArgs.cs @@ -1,114 +1,110 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic.Enums; - -namespace TrafficManager.Traffic.Data { - public struct PathCreationArgs { - /// - /// Extended path type - /// - public 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; - } -} +namespace TrafficManager.API.Traffic.Data { + using Enums; + + public struct PathCreationArgs { + /// + /// Extended path type + /// + public 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; + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Data/PathUnitQueueItem.cs b/TLM/TMPE.API/Traffic/Data/PathUnitQueueItem.cs index eb0f2bb92..94f2d2045 100644 --- a/TLM/TMPE.API/Traffic/Data/PathUnitQueueItem.cs +++ b/TLM/TMPE.API/Traffic/Data/PathUnitQueueItem.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Traffic.Enums; +namespace TrafficManager.Traffic.Data { + using API.Traffic.Enums; -namespace TrafficManager.Traffic.Data { public struct PathUnitQueueItem { public uint nextPathUnitId; // access requires acquisition of CustomPathFind.QueueLock public ExtVehicleType vehicleType; // access requires acquisition of m_bufferLock diff --git a/TLM/TMPE.API/Traffic/Data/PrioritySegment.cs b/TLM/TMPE.API/Traffic/Data/PrioritySegment.cs index 90b7d82cc..db382db15 100644 --- a/TLM/TMPE.API/Traffic/Data/PrioritySegment.cs +++ b/TLM/TMPE.API/Traffic/Data/PrioritySegment.cs @@ -2,6 +2,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.Traffic.Data { + using API.Traffic.Enums; + /// /// A priority segment specifies the priority signs that are present at each end of a certain segment. /// diff --git a/TLM/TMPE.API/Traffic/Enums/CarUsagePolicy.cs b/TLM/TMPE.API/Traffic/Enums/CarUsagePolicy.cs index 246847c1a..2d14ca4b3 100644 --- a/TLM/TMPE.API/Traffic/Enums/CarUsagePolicy.cs +++ b/TLM/TMPE.API/Traffic/Enums/CarUsagePolicy.cs @@ -1,28 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - /// - /// 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 - } -} +namespace TrafficManager.API.Traffic.Enums { + /// + /// 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 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/EmergencyBehavior.cs b/TLM/TMPE.API/Traffic/Enums/EmergencyBehavior.cs index 4c6137001..4b92a00ae 100644 --- a/TLM/TMPE.API/Traffic/Enums/EmergencyBehavior.cs +++ b/TLM/TMPE.API/Traffic/Enums/EmergencyBehavior.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { +namespace TrafficManager.API.Traffic.Enums { public enum EmergencyBehavior { /// /// No custom behavior diff --git a/TLM/TMPE.API/Traffic/Enums/ExtParkingSpaceLocation.cs b/TLM/TMPE.API/Traffic/Enums/ExtParkingSpaceLocation.cs index efe8cbcc8..a798b9a4c 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtParkingSpaceLocation.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtParkingSpaceLocation.cs @@ -1,21 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum ExtParkingSpaceLocation { - /// - /// No parking space location - /// - None = 0, - /// - /// Road-side parking space - /// - RoadSide = 1, - /// - /// Building parking space - /// - Building = 2 - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum ExtParkingSpaceLocation { + /// + /// No parking space location + /// + None = 0, + /// + /// Road-side parking space + /// + RoadSide = 1, + /// + /// Building parking space + /// + Building = 2 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ExtPathMode.cs b/TLM/TMPE.API/Traffic/Enums/ExtPathMode.cs index 536f4116a..12b7e0b36 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtPathMode.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtPathMode.cs @@ -1,84 +1,79 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - 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, - } -} +namespace TrafficManager.Traffic.Enums { + 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, + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ExtPathState.cs b/TLM/TMPE.API/Traffic/Enums/ExtPathState.cs index c00774acf..8786a6aeb 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtPathState.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtPathState.cs @@ -1,25 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - 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 - } -} +namespace TrafficManager.API.Traffic.Enums { + 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 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ExtPathType.cs b/TLM/TMPE.API/Traffic/Enums/ExtPathType.cs index 6afc844be..9e91b65d9 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtPathType.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtPathType.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { +namespace TrafficManager.API.Traffic.Enums { public enum ExtPathType { /// /// Mixed path diff --git a/TLM/TMPE.API/Traffic/Enums/ExtSoftPathState.cs b/TLM/TMPE.API/Traffic/Enums/ExtSoftPathState.cs index 353304b6d..27fa5be0d 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtSoftPathState.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtSoftPathState.cs @@ -1,33 +1,28 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - 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 - } -} +namespace TrafficManager.API.Traffic.Enums { + 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 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ExtTransportMode.cs b/TLM/TMPE.API/Traffic/Enums/ExtTransportMode.cs index adee440fa..eee7b7db8 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtTransportMode.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtTransportMode.cs @@ -1,22 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace TrafficManager.API.Traffic.Enums { + using System; -namespace TrafficManager.Traffic.Enums { - [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 - } -} + [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 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ExtVehicleFlags.cs b/TLM/TMPE.API/Traffic/Enums/ExtVehicleFlags.cs index 12e9b9a06..d9fdf2cda 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtVehicleFlags.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtVehicleFlags.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace TrafficManager.API.Traffic.Enums { + using System; -namespace TrafficManager.Traffic.Enums { - // TODO why do we need this? - [Flags] - public enum ExtVehicleFlags { - None = 0, - Created = 1, - Spawned = 1 << 1 - } -} + // TODO why do we need this? + [Flags] + public enum ExtVehicleFlags { + None = 0, + Created = 1, + Spawned = 1 << 1 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ExtVehicleType.cs b/TLM/TMPE.API/Traffic/Enums/ExtVehicleType.cs index 51e9116a8..ca7ef70cd 100644 --- a/TLM/TMPE.API/Traffic/Enums/ExtVehicleType.cs +++ b/TLM/TMPE.API/Traffic/Enums/ExtVehicleType.cs @@ -1,40 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Text; +namespace TrafficManager.API.Traffic.Enums { + using System; -namespace TrafficManager.Traffic { - // TODO this enum should be moved to package TrafficManager.Traffic.Enums but deserialization fails if we just do that now. - [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 - } -} + [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 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/FlowWaitCalcMode.cs b/TLM/TMPE.API/Traffic/Enums/FlowWaitCalcMode.cs index c66fd53f2..b7b06c312 100644 --- a/TLM/TMPE.API/Traffic/Enums/FlowWaitCalcMode.cs +++ b/TLM/TMPE.API/Traffic/Enums/FlowWaitCalcMode.cs @@ -1,18 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.TrafficLight { - // TODO this enum should be moved to TrafficManager.Traffic.Enums but deserialization fails if we just do that now. - public enum FlowWaitCalcMode { - /// - /// traffic measurements are averaged - /// - Mean, - /// - /// traffic measurements are summed up - /// - Total - } -} +namespace TrafficManager.API.Traffic.Enums { + // TODO this enum should be moved to TrafficManager.Traffic.Enums but deserialization fails if we just do that now. + public enum FlowWaitCalcMode { + /// + /// traffic measurements are averaged + /// + Mean, + /// + /// traffic measurements are summed up + /// + Total + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/GeometryCalculationMode.cs b/TLM/TMPE.API/Traffic/Enums/GeometryCalculationMode.cs index 9536899fc..9f364adb1 100644 --- a/TLM/TMPE.API/Traffic/Enums/GeometryCalculationMode.cs +++ b/TLM/TMPE.API/Traffic/Enums/GeometryCalculationMode.cs @@ -1,12 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum GeometryCalculationMode { - Init, - Propagate, - NoPropagate - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum GeometryCalculationMode { + Init, + Propagate, + NoPropagate + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/LaneArrows.cs b/TLM/TMPE.API/Traffic/Enums/LaneArrows.cs index 73b7e4f6c..5e7120b19 100644 --- a/TLM/TMPE.API/Traffic/Enums/LaneArrows.cs +++ b/TLM/TMPE.API/Traffic/Enums/LaneArrows.cs @@ -1,18 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace TrafficManager.API.Traffic.Enums { + using System; + using JetBrains.Annotations; -namespace TrafficManager.Traffic.Enums { - [Flags] - public enum LaneArrows { // compatible with NetLane.Flags - None = 0, - Forward = 16, - Left = 32, - Right = 64, - LeftForward = 48, - LeftRight = 96, - ForwardRight = 80, - LeftForwardRight = 112 - } -} + [Flags] + public enum LaneArrows { + // compatible with NetLane.Flags + None = 0, + Forward = 16, + Left = 32, + Right = 64, + + [UsedImplicitly] + LeftForward = Left + Forward, + + [UsedImplicitly] + LeftRight = Left + Right, + + [UsedImplicitly] + ForwardRight = Forward + Right, + LeftForwardRight = Left + Forward + Right + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/LaneEndTransitionType.cs b/TLM/TMPE.API/Traffic/Enums/LaneEndTransitionType.cs index 96b5492e7..c82940b14 100644 --- a/TLM/TMPE.API/Traffic/Enums/LaneEndTransitionType.cs +++ b/TLM/TMPE.API/Traffic/Enums/LaneEndTransitionType.cs @@ -1,25 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - 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 - } -} +namespace TrafficManager.Traffic.Enums { + 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 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/LightMode.cs b/TLM/TMPE.API/Traffic/Enums/LightMode.cs index 1e8b9eef4..77c28d400 100644 --- a/TLM/TMPE.API/Traffic/Enums/LightMode.cs +++ b/TLM/TMPE.API/Traffic/Enums/LightMode.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { +namespace TrafficManager.API.Traffic.Enums { public enum LightMode { Simple = 1, // <^> SingleLeft = 2, // <, ^> diff --git a/TLM/TMPE.API/Traffic/Enums/ParkedCarApproachState.cs b/TLM/TMPE.API/Traffic/Enums/ParkedCarApproachState.cs index 904f684d7..8bad7cdde 100644 --- a/TLM/TMPE.API/Traffic/Enums/ParkedCarApproachState.cs +++ b/TLM/TMPE.API/Traffic/Enums/ParkedCarApproachState.cs @@ -1,28 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - /// - /// 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 - } -} +namespace TrafficManager.API.Traffic.Enums { + /// + /// 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 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ParkingUnableReason.cs b/TLM/TMPE.API/Traffic/Enums/ParkingUnableReason.cs index 2f215c1ed..8b867fa7a 100644 --- a/TLM/TMPE.API/Traffic/Enums/ParkingUnableReason.cs +++ b/TLM/TMPE.API/Traffic/Enums/ParkingUnableReason.cs @@ -1,24 +1,19 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - /// - /// 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 - } -} +namespace TrafficManager.API.Traffic.Enums { + /// + /// 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 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/PriorityType.cs b/TLM/TMPE.API/Traffic/Enums/PriorityType.cs index fc4dc9b2f..031db619e 100644 --- a/TLM/TMPE.API/Traffic/Enums/PriorityType.cs +++ b/TLM/TMPE.API/Traffic/Enums/PriorityType.cs @@ -1,22 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum PriorityType { - None = 0, - /// - /// Priority road - /// - Main = 1, - /// - /// Stop sign - /// - Stop = 2, - /// - /// Yield sign - /// - Yield = 3 - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum PriorityType { + None = 0, + /// + /// Priority road + /// + Main = 1, + /// + /// Stop sign + /// + Stop = 2, + /// + /// Yield sign + /// + Yield = 3 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/SetLaneArrowUnableReason.cs b/TLM/TMPE.API/Traffic/Enums/SetLaneArrowUnableReason.cs index 865cbf603..1a09c1a66 100644 --- a/TLM/TMPE.API/Traffic/Enums/SetLaneArrowUnableReason.cs +++ b/TLM/TMPE.API/Traffic/Enums/SetLaneArrowUnableReason.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum SetLaneArrowUnableReason { - Invalid, - HighwayArrows, - LaneConnection, - Success - } -} +namespace TrafficManager.Traffic.Enums { + public enum SetLaneArrowUnableReason { + Invalid, + HighwayArrows, + LaneConnection, + Success + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/SetPrioritySignUnableReason.cs b/TLM/TMPE.API/Traffic/Enums/SetPrioritySignUnableReason.cs index 09bdfa8d7..4b3e36df5 100644 --- a/TLM/TMPE.API/Traffic/Enums/SetPrioritySignUnableReason.cs +++ b/TLM/TMPE.API/Traffic/Enums/SetPrioritySignUnableReason.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum SetPrioritySignUnableReason { - None, - NoJunction, - HasTimedLight, - InvalidSegment, - NotIncoming - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum SetPrioritySignUnableReason { + None, + NoJunction, + HasTimedLight, + InvalidSegment, + NotIncoming + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/StepChangeMetric.cs b/TLM/TMPE.API/Traffic/Enums/StepChangeMetric.cs index 1ab230dd5..9a715d100 100644 --- a/TLM/TMPE.API/Traffic/Enums/StepChangeMetric.cs +++ b/TLM/TMPE.API/Traffic/Enums/StepChangeMetric.cs @@ -1,29 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - 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, - } -} +namespace TrafficManager.API.Traffic.Enums { + 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, + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/ToggleTrafficLightUnableReason.cs b/TLM/TMPE.API/Traffic/Enums/ToggleTrafficLightUnableReason.cs index 8b2357024..891c68444 100644 --- a/TLM/TMPE.API/Traffic/Enums/ToggleTrafficLightUnableReason.cs +++ b/TLM/TMPE.API/Traffic/Enums/ToggleTrafficLightUnableReason.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum ToggleTrafficLightUnableReason { - None, - NoJunction, - HasTimedLight, - IsLevelCrossing, - InsufficientSegments - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum ToggleTrafficLightUnableReason { + None, + NoJunction, + HasTimedLight, + IsLevelCrossing, + InsufficientSegments + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/TrafficLightSimulationType.cs b/TLM/TMPE.API/Traffic/Enums/TrafficLightSimulationType.cs index 326f9fdc0..7581705c4 100644 --- a/TLM/TMPE.API/Traffic/Enums/TrafficLightSimulationType.cs +++ b/TLM/TMPE.API/Traffic/Enums/TrafficLightSimulationType.cs @@ -1,12 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - public enum TrafficLightSimulationType { - None, - Manual, - Timed - } -} +namespace TrafficManager.API.Traffic.Enums { + public enum TrafficLightSimulationType { + None, + Manual, + Timed + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/VehicleJunctionTransitState.cs b/TLM/TMPE.API/Traffic/Enums/VehicleJunctionTransitState.cs index 4c699fb6f..f7655b5b4 100644 --- a/TLM/TMPE.API/Traffic/Enums/VehicleJunctionTransitState.cs +++ b/TLM/TMPE.API/Traffic/Enums/VehicleJunctionTransitState.cs @@ -1,24 +1,24 @@ -namespace TrafficManager.Traffic.Enums { - 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 - } +namespace TrafficManager.API.Traffic.Enums { + 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 + } } \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsAggression.cs b/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsAggression.cs index b118ea1d9..ecde32fc4 100644 --- a/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsAggression.cs +++ b/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsAggression.cs @@ -1,28 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace TrafficManager.API.Traffic.Enums { + using JetBrains.Annotations; -namespace TrafficManager.Traffic.Enums { - /// - /// Represents vehicle restrictions effect strength - /// - public enum VehicleRestrictionsAggression { - /// - /// Low aggression - /// - Low = 0, - /// - /// Medium aggression - /// - Medium = 1, - /// - /// High aggression - /// - High = 2, - /// - /// Strict aggression - /// - Strict = 3 - } -} + /// + /// Represents vehicle restrictions effect strength + /// + public enum VehicleRestrictionsAggression { + /// + /// Low aggression + /// + [UsedImplicitly] + Low = 0, + /// + /// Medium aggression + /// + Medium = 1, + /// + /// High aggression + /// + [UsedImplicitly] + High = 2, + /// + /// Strict aggression + /// + Strict = 3 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsMode.cs b/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsMode.cs index 9591ccd51..1d03d5dd9 100644 --- a/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsMode.cs +++ b/TLM/TMPE.API/Traffic/Enums/VehicleRestrictionsMode.cs @@ -1,21 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TrafficManager.Traffic.Enums { - 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 - } -} +namespace TrafficManager.API.Traffic.Enums { + 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 + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/TrafficLight/Data/TrafficLightSimulation.cs b/TLM/TMPE.API/TrafficLight/Data/TrafficLightSimulation.cs index d0c046f06..56043ca09 100644 --- a/TLM/TMPE.API/TrafficLight/Data/TrafficLightSimulation.cs +++ b/TLM/TMPE.API/TrafficLight/Data/TrafficLightSimulation.cs @@ -2,6 +2,9 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.TrafficLight.Data { + using API.Traffic.Enums; + using API.TrafficLight; + public struct TrafficLightSimulation { /// /// Timed traffic light by node id diff --git a/TLM/TMPE.API/TrafficLight/ICustomSegmentLight.cs b/TLM/TMPE.API/TrafficLight/ICustomSegmentLight.cs index 129c38506..591457ca1 100644 --- a/TLM/TMPE.API/TrafficLight/ICustomSegmentLight.cs +++ b/TLM/TMPE.API/TrafficLight/ICustomSegmentLight.cs @@ -6,6 +6,8 @@ using TrafficManager.Traffic.Enums; namespace TrafficManager.TrafficLight { + using API.Traffic.Enums; + public interface ICustomSegmentLight : ICloneable { // TODO documentation ushort NodeId { get; } diff --git a/TLM/TMPE.API/TrafficLight/ICustomSegmentLights.cs b/TLM/TMPE.API/TrafficLight/ICustomSegmentLights.cs index c776f5865..2635b086f 100644 --- a/TLM/TMPE.API/TrafficLight/ICustomSegmentLights.cs +++ b/TLM/TMPE.API/TrafficLight/ICustomSegmentLights.cs @@ -1,43 +1,43 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TrafficManager.Manager; -using TrafficManager.Traffic; +namespace TrafficManager.API.TrafficLight { + using System; + using System.Collections.Generic; + using Traffic.Enums; + using TrafficManager.Manager; + using TrafficManager.Traffic; + using TrafficManager.TrafficLight; -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; } + 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(ref NetNode node, 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); - } -} + void CalculateAutoPedestrianLightState(ref NetNode node, 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); + } +} \ No newline at end of file diff --git a/TLM/TMPE.API/TrafficLight/ITimedTrafficLights.cs b/TLM/TMPE.API/TrafficLight/ITimedTrafficLights.cs index 78e73bc7c..94f860ace 100644 --- a/TLM/TMPE.API/TrafficLight/ITimedTrafficLights.cs +++ b/TLM/TMPE.API/TrafficLight/ITimedTrafficLights.cs @@ -1,45 +1,45 @@ -using System.Collections.Generic; -using CSUtil.Commons; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; -using TrafficManager.Util; +namespace TrafficManager.API.TrafficLight { + using System.Collections.Generic; + using CSUtil.Commons; + using Traffic.Enums; + using TrafficManager.Traffic.Enums; + using TrafficManager.TrafficLight; -namespace TrafficManager.TrafficLight { - public interface ITimedTrafficLights { - 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 + public interface ITimedTrafficLights { + 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); - } + 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); + } } \ No newline at end of file diff --git a/TLM/TMPE.API/TrafficLight/ITimedTrafficLightsStep.cs b/TLM/TMPE.API/TrafficLight/ITimedTrafficLightsStep.cs index 0957c83f0..f27e767ec 100644 --- a/TLM/TMPE.API/TrafficLight/ITimedTrafficLightsStep.cs +++ b/TLM/TMPE.API/TrafficLight/ITimedTrafficLightsStep.cs @@ -1,41 +1,41 @@ -using System.Collections.Generic; -using TrafficManager.Manager; -using TrafficManager.Traffic; -using TrafficManager.Traffic.Enums; +namespace TrafficManager.API.TrafficLight { + using System.Collections.Generic; + using Traffic.Enums; + using TrafficManager.Manager; + using TrafficManager.Traffic.Enums; -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; } + 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); - new 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); - } + 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); + new 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); + } } \ No newline at end of file diff --git a/TLM/TMPE.CitiesGameBridge/Service/VehicleService.cs b/TLM/TMPE.CitiesGameBridge/Service/VehicleService.cs index 5adc27f88..c31229af9 100644 --- a/TLM/TMPE.CitiesGameBridge/Service/VehicleService.cs +++ b/TLM/TMPE.CitiesGameBridge/Service/VehicleService.cs @@ -11,6 +11,8 @@ private VehicleService() { } + public int MaxVehicleCount => VehicleManager.instance.m_vehicles.m_buffer.Length; + 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) { diff --git a/TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj b/TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj index e6ced5290..e2a9c6202 100644 --- a/TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj +++ b/TLM/TMPE.CitiesGameBridge/TMPE.CitiesGameBridge.csproj @@ -12,6 +12,7 @@ v3.5 512 + latest true diff --git a/TLM/TMPE.GenericGameBridge/Service/IVehicleService.cs b/TLM/TMPE.GenericGameBridge/Service/IVehicleService.cs index 65a4138a0..9a0129803 100644 --- a/TLM/TMPE.GenericGameBridge/Service/IVehicleService.cs +++ b/TLM/TMPE.GenericGameBridge/Service/IVehicleService.cs @@ -8,6 +8,9 @@ namespace GenericGameBridge.Service { public delegate bool ParkedVehicleHandler(ushort parkedVehicleId, ref VehicleParked parkedVehicle); public interface IVehicleService { + + int MaxVehicleCount { get; } + 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); diff --git a/TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj b/TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj index 3e498fb0b..240c9d556 100644 --- a/TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj +++ b/TLM/TMPE.GenericGameBridge/TMPE.GenericGameBridge.csproj @@ -12,6 +12,7 @@ v3.5 512 + latest true diff --git a/TLM/TMPE.GlobalConfigGenerator/App.config b/TLM/TMPE.GlobalConfigGenerator/App.config deleted file mode 100644 index 343984d02..000000000 --- a/TLM/TMPE.GlobalConfigGenerator/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/TLM/TMPE.GlobalConfigGenerator/Generator.cs b/TLM/TMPE.GlobalConfigGenerator/Generator.cs deleted file mode 100644 index 295af4079..000000000 --- a/TLM/TMPE.GlobalConfigGenerator/Generator.cs +++ /dev/null @@ -1,56 +0,0 @@ -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); - } - } - } -} diff --git a/TLM/TMPE.GlobalConfigGenerator/Properties/AssemblyInfo.cs b/TLM/TMPE.GlobalConfigGenerator/Properties/AssemblyInfo.cs deleted file mode 100644 index 04ca5b3d7..000000000 --- a/TLM/TMPE.GlobalConfigGenerator/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -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.*")] \ No newline at end of file diff --git a/TLM/TMPE.GlobalConfigGenerator/TMPE.GlobalConfigGenerator.csproj b/TLM/TMPE.GlobalConfigGenerator/TMPE.GlobalConfigGenerator.csproj deleted file mode 100644 index f26d62478..000000000 --- a/TLM/TMPE.GlobalConfigGenerator/TMPE.GlobalConfigGenerator.csproj +++ /dev/null @@ -1,101 +0,0 @@ - - - - - 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 - - - - - - - - - \ No newline at end of file diff --git a/TLM/TMPE.GlobalConfigGenerator/packages.config b/TLM/TMPE.GlobalConfigGenerator/packages.config deleted file mode 100644 index 947d6d05b..000000000 --- a/TLM/TMPE.GlobalConfigGenerator/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj b/TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj index 68e466994..d78f66d98 100644 --- a/TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj +++ b/TLM/TMPE.TestGameBridge/TMPE.TestGameBridge.csproj @@ -12,6 +12,7 @@ v3.5 512 + latest true diff --git a/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj b/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj index 54dd5916f..4527aa17e 100644 --- a/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj +++ b/TLM/TMPE.UnitTest/TMPE.UnitTest.csproj @@ -17,6 +17,7 @@ False UnitTest + latest true diff --git a/TLM/TMPE.sln b/TLM/TMPE.sln index b4c7c85e3..c73b25033 100644 --- a/TLM/TMPE.sln +++ b/TLM/TMPE.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.28307.438 +VisualStudioVersion = 15.0.28307.539 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TLM", "TLM\TLM.csproj", "{7422AE58-8B0A-401C-9404-F4A438EFFE10}" ProjectSection(ProjectDependencies) = postProject @@ -16,11 +16,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\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}" @@ -46,6 +41,7 @@ Global Debug|Any CPU = Debug|Any CPU FullDebug|Any CPU = FullDebug|Any CPU PF2_Debug|Any CPU = PF2_Debug|Any CPU + Release LABS|Any CPU = Release LABS|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution @@ -57,18 +53,10 @@ Global {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 LABS|Any CPU.ActiveCfg = Release LABS|Any CPU + {7422AE58-8B0A-401C-9404-F4A438EFFE10}.Release LABS|Any CPU.Build.0 = Release LABS|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 @@ -77,6 +65,8 @@ Global {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 LABS|Any CPU.ActiveCfg = Release|Any CPU + {D0D1848A-9BAE-4121-89A0-66757D16BC73}.Release LABS|Any CPU.Build.0 = Release|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 @@ -87,6 +77,8 @@ Global {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 LABS|Any CPU.ActiveCfg = Release|Any CPU + {663B991F-32A1-46E1-A4D3-540F8EA7F386}.Release LABS|Any CPU.Build.0 = Release|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 @@ -97,6 +89,8 @@ Global {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 LABS|Any CPU.ActiveCfg = Release|Any CPU + {3F2F7926-5D51-4880-A2B7-4594A10D7E54}.Release LABS|Any CPU.Build.0 = Release|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 @@ -107,6 +101,8 @@ Global {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 LABS|Any CPU.ActiveCfg = Release|Any CPU + {97DBFE4D-0DB1-43B3-AA35-067C06CF125B}.Release LABS|Any CPU.Build.0 = Release|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 @@ -117,6 +113,8 @@ Global {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 LABS|Any CPU.ActiveCfg = Release|Any CPU + {D3ADE06E-F493-4819-865A-3BB44FEEDF01}.Release LABS|Any CPU.Build.0 = Release|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 @@ -127,6 +125,8 @@ Global {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 LABS|Any CPU.ActiveCfg = Release|Any CPU + {F4BEABA8-E56B-4201-99C8-5E0115E87D1C}.Release LABS|Any CPU.Build.0 = Release|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 @@ -137,6 +137,8 @@ Global {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 LABS|Any CPU.ActiveCfg = Release|Any CPU + {F8759084-DF5B-4A54-B73C-824640A8FA3F}.Release LABS|Any CPU.Build.0 = Release|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 {C911D31C-C85D-42A8-A839-7B209A715495}.Benchmark|Any CPU.ActiveCfg = Release|Any CPU @@ -154,6 +156,6 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {5D8CDC16-6B32-4B3B-8599-35B23E9958B7} + SolutionGuid = {CFCFBDC9-2706-483F-B4A3-F810E5CDE489} EndGlobalSection EndGlobal