├── .gitignore ├── AdvancedVehicleOptions ├── AdvancedVehicleOptions.cs ├── AdvancedVehicleOptions.csproj ├── AdvancedVehicleOptions.sln ├── Configuration.cs ├── DebugUtils.cs ├── GUI │ ├── UIFastList.cs │ ├── UIMainPanel.cs │ ├── UIOptionPanel.cs │ ├── UITitleBar.cs │ ├── UIUtils.cs │ ├── UIVehicleItem.cs │ └── UIWarningModal.cs ├── PreviewRenderer.cs ├── Properties │ └── AssemblyInfo.cs ├── SerializableDataExtension.cs └── VehicleOptions.cs ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/AdvancedVehicleOptions.cs: -------------------------------------------------------------------------------- 1 | using ICities; 2 | using UnityEngine; 3 | 4 | using System; 5 | using System.Text; 6 | using System.Collections; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | 10 | using ColossalFramework; 11 | using ColossalFramework.Threading; 12 | using ColossalFramework.UI; 13 | 14 | namespace AdvancedVehicleOptions 15 | { 16 | public class ModInfo : IUserMod 17 | { 18 | public ModInfo() 19 | { 20 | try 21 | { 22 | // Creating setting file 23 | GameSettings.AddSettingsFile(new SettingsFile[] { new SettingsFile() { fileName = AdvancedVehicleOptions.settingsFileName } }); 24 | } 25 | catch (Exception e) 26 | { 27 | DebugUtils.Log("Couldn't load/create the setting file."); 28 | DebugUtils.LogException(e); 29 | } 30 | } 31 | 32 | public string Name 33 | { 34 | get { return "Advanced Vehicle Options " + version; } 35 | } 36 | 37 | public string Description 38 | { 39 | get { return "Customize your vehicles"; } 40 | } 41 | 42 | public void OnSettingsUI(UIHelperBase helper) 43 | { 44 | try 45 | { 46 | UICheckBox checkBox; 47 | UIHelperBase group = helper.AddGroup(Name); 48 | 49 | checkBox = (UICheckBox)group.AddCheckbox("Disable debug messages logging", DebugUtils.hideDebugMessages.value, (b) => 50 | { 51 | DebugUtils.hideDebugMessages.value = b; 52 | }); 53 | checkBox.tooltip = "If checked, debug messages won't be logged."; 54 | 55 | group.AddSpace(10); 56 | 57 | checkBox = (UICheckBox)group.AddCheckbox("Hide the user interface", AdvancedVehicleOptions.hideGUI.value, (b) => 58 | { 59 | AdvancedVehicleOptions.hideGUI.value = b; 60 | AdvancedVehicleOptions.UpdateGUI(); 61 | 62 | }); 63 | checkBox.tooltip = "Hide the UI completely if you feel like you are done with it\nand want to save the little bit of memory it takes\nEverything else will still be functional"; 64 | 65 | checkBox = (UICheckBox)group.AddCheckbox("Disable warning at map loading", !AdvancedVehicleOptions.onLoadCheck.value, (b) => 66 | { 67 | AdvancedVehicleOptions.onLoadCheck.value = !b; 68 | }); 69 | checkBox.tooltip = "Disable service vehicle availability check at the loading of a map"; 70 | 71 | } 72 | catch (Exception e) 73 | { 74 | DebugUtils.Log("OnSettingsUI failed"); 75 | DebugUtils.LogException(e); 76 | } 77 | } 78 | 79 | public const string version = "1.8.2"; 80 | } 81 | 82 | public class AdvancedVehicleOptionsLoader : LoadingExtensionBase 83 | { 84 | private static AdvancedVehicleOptions instance; 85 | 86 | #region LoadingExtensionBase overrides 87 | /// 88 | /// Called when the level (game, map editor, asset editor) is loaded 89 | /// 90 | public override void OnLevelLoaded(LoadMode mode) 91 | { 92 | try 93 | { 94 | // Is it an actual game ? 95 | if (mode != LoadMode.LoadGame && mode != LoadMode.NewGame) 96 | { 97 | DefaultOptions.Clear(); 98 | return; 99 | } 100 | 101 | AdvancedVehicleOptions.isGameLoaded = true; 102 | 103 | if (instance != null) 104 | { 105 | GameObject.DestroyImmediate(instance.gameObject); 106 | } 107 | 108 | instance = new GameObject("AdvancedVehicleOptions").AddComponent(); 109 | 110 | try 111 | { 112 | DefaultOptions.BuildVehicleInfoDictionary(); 113 | VehicleOptions.Clear(); 114 | DebugUtils.Log("UIMainPanel created"); 115 | } 116 | catch 117 | { 118 | DebugUtils.Log("Could not create UIMainPanel"); 119 | 120 | if (instance != null) 121 | GameObject.Destroy(instance.gameObject); 122 | 123 | return; 124 | } 125 | 126 | //new EnumerableActionThread(BrokenAssetsFix); 127 | } 128 | catch (Exception e) 129 | { 130 | if (instance != null) 131 | GameObject.Destroy(instance.gameObject); 132 | DebugUtils.LogException(e); 133 | } 134 | } 135 | 136 | /// 137 | /// Called when the level is unloaded 138 | /// 139 | public override void OnLevelUnloading() 140 | { 141 | try 142 | { 143 | DebugUtils.Log("Restoring default values"); 144 | DefaultOptions.RestoreAll(); 145 | DefaultOptions.Clear(); 146 | 147 | if (instance != null) 148 | GameObject.Destroy(instance.gameObject); 149 | 150 | AdvancedVehicleOptions.isGameLoaded = false; 151 | } 152 | catch (Exception e) 153 | { 154 | DebugUtils.LogException(e); 155 | } 156 | } 157 | #endregion 158 | } 159 | 160 | public class AdvancedVehicleOptions : MonoBehaviour 161 | { 162 | public const string settingsFileName = "AdvancedVehicleOptions"; 163 | 164 | public static SavedBool hideGUI = new SavedBool("hideGUI", settingsFileName, false, true); 165 | public static SavedBool onLoadCheck = new SavedBool("onLoadCheck", settingsFileName, true, true); 166 | 167 | private static GUI.UIMainPanel m_mainPanel; 168 | 169 | private static VehicleInfo m_removeInfo; 170 | private static VehicleInfo m_removeParkedInfo; 171 | 172 | private const string m_fileName = "AdvancedVehicleOptions.xml"; 173 | 174 | public static bool isGameLoaded = false; 175 | public static Configuration config = new Configuration(); 176 | 177 | public void Start() 178 | { 179 | try 180 | { 181 | // Loading config 182 | AdvancedVehicleOptions.InitConfig(); 183 | 184 | if (AdvancedVehicleOptions.onLoadCheck) 185 | { 186 | AdvancedVehicleOptions.CheckAllServicesValidity(); 187 | } 188 | 189 | m_mainPanel = GameObject.FindObjectOfType(); 190 | UpdateGUI(); 191 | } 192 | catch (Exception e) 193 | { 194 | DebugUtils.Log("UI initialization failed."); 195 | DebugUtils.LogException(e); 196 | 197 | GameObject.Destroy(gameObject); 198 | } 199 | } 200 | 201 | public static void UpdateGUI() 202 | { 203 | if(!isGameLoaded) return; 204 | 205 | if(!hideGUI && m_mainPanel == null) 206 | { 207 | // Creating GUI 208 | m_mainPanel = UIView.GetAView().AddUIComponent(typeof(GUI.UIMainPanel)) as GUI.UIMainPanel; 209 | } 210 | else if (hideGUI && m_mainPanel != null) 211 | { 212 | GameObject.Destroy(m_mainPanel.gameObject); 213 | m_mainPanel = null; 214 | } 215 | } 216 | 217 | /// 218 | /// Init the configuration 219 | /// 220 | public static void InitConfig() 221 | { 222 | // Store modded values 223 | DefaultOptions.StoreAllModded(); 224 | 225 | if(config.data != null) 226 | { 227 | config.DataToOptions(); 228 | 229 | // Remove unneeded options 230 | List optionsList = new List(); 231 | 232 | for (uint i = 0; i < config.options.Length; i++) 233 | { 234 | if (config.options[i] != null && config.options[i].prefab != null) optionsList.Add(config.options[i]); 235 | } 236 | 237 | config.options = optionsList.ToArray(); 238 | } 239 | else if (File.Exists(m_fileName)) 240 | { 241 | // Import config 242 | ImportConfig(); 243 | return; 244 | } 245 | else 246 | { 247 | DebugUtils.Log("No configuration found. Default values will be used."); 248 | } 249 | 250 | // Checking for new vehicles 251 | CompileVehiclesList(); 252 | 253 | // Checking for conflicts 254 | DefaultOptions.CheckForConflicts(); 255 | 256 | // Update existing vehicles 257 | new EnumerableActionThread(VehicleOptions.UpdateCapacityUnits); 258 | new EnumerableActionThread(VehicleOptions.UpdateBackEngines); 259 | 260 | DebugUtils.Log("Configuration initialized"); 261 | LogVehicleListSteamID(); 262 | } 263 | 264 | /// 265 | /// Import the configuration file 266 | /// 267 | public static void ImportConfig() 268 | { 269 | if (!File.Exists(m_fileName)) 270 | { 271 | DebugUtils.Log("Configuration file not found."); 272 | return; 273 | } 274 | 275 | config.Deserialize(m_fileName); 276 | 277 | if (config.options == null) 278 | { 279 | DebugUtils.Log("Configuration empty. Default values will be used."); 280 | } 281 | else 282 | { 283 | // Remove unneeded options 284 | List optionsList = new List(); 285 | 286 | for (uint i = 0; i < config.options.Length; i++) 287 | { 288 | if (config.options[i] != null && config.options[i].prefab != null) optionsList.Add(config.options[i]); 289 | } 290 | 291 | config.options = optionsList.ToArray(); 292 | } 293 | 294 | // Checking for new vehicles 295 | CompileVehiclesList(); 296 | 297 | // Checking for conflicts 298 | DefaultOptions.CheckForConflicts(); 299 | 300 | // Update existing vehicles 301 | new EnumerableActionThread(VehicleOptions.UpdateCapacityUnits); 302 | new EnumerableActionThread(VehicleOptions.UpdateBackEngines); 303 | 304 | DebugUtils.Log("Configuration imported"); 305 | LogVehicleListSteamID(); 306 | } 307 | 308 | /// 309 | /// Export the configuration file 310 | /// 311 | public static void ExportConfig() 312 | { 313 | config.Serialize(m_fileName); 314 | } 315 | 316 | public static void CheckAllServicesValidity() 317 | { 318 | string warning = ""; 319 | 320 | for (int i = 0; i < (int)VehicleOptions.Category.Natural; i++) 321 | if (!CheckServiceValidity((VehicleOptions.Category)i)) warning += "- " + GUI.UIMainPanel.categoryList[i + 1] + "\n"; 322 | 323 | if(warning != "") 324 | { 325 | GUI.UIWarningModal.instance.message = "The following services may not work correctly because no vehicles are allowed to spawn :\n\n" + warning; 326 | UIView.PushModal(GUI.UIWarningModal.instance); 327 | GUI.UIWarningModal.instance.Show(true); 328 | } 329 | 330 | } 331 | 332 | public static bool CheckServiceValidity(VehicleOptions.Category service) 333 | { 334 | if (config == null || config.options == null) return true; 335 | 336 | int count = 0; 337 | 338 | for (int i = 0; i < config.options.Length; i++) 339 | { 340 | if (config.options[i].category == service) 341 | { 342 | if(config.options[i].enabled) return true; 343 | count++; 344 | } 345 | } 346 | 347 | return count == 0; 348 | } 349 | 350 | public static void ClearVehicles(VehicleOptions options, bool parked) 351 | { 352 | if (parked) 353 | { 354 | if(options == null) 355 | { 356 | new EnumerableActionThread(ActionRemoveParkedAll); 357 | return; 358 | } 359 | 360 | m_removeParkedInfo = options.prefab; 361 | new EnumerableActionThread(ActionRemoveParked); 362 | } 363 | else 364 | { 365 | if (options == null) 366 | { 367 | new EnumerableActionThread(ActionRemoveExistingAll); 368 | return; 369 | } 370 | 371 | m_removeInfo = options.prefab; 372 | new EnumerableActionThread(ActionRemoveExisting); 373 | } 374 | } 375 | 376 | public static IEnumerator ActionRemoveExisting(ThreadBase t) 377 | { 378 | VehicleInfo info = m_removeInfo; 379 | 380 | for (ushort i = 0; i < VehicleManager.instance.m_vehicles.m_size; i++) 381 | { 382 | if (VehicleManager.instance.m_vehicles.m_buffer[i].Info != null) 383 | { 384 | if (info == VehicleManager.instance.m_vehicles.m_buffer[i].Info) 385 | VehicleManager.instance.ReleaseVehicle(i); 386 | } 387 | 388 | if (i % 256 == 255) yield return i; 389 | } 390 | } 391 | 392 | public static IEnumerator ActionRemoveParked(ThreadBase t) 393 | { 394 | VehicleInfo info = m_removeParkedInfo; 395 | 396 | for (ushort i = 0; i < VehicleManager.instance.m_parkedVehicles.m_size; i++) 397 | { 398 | if (VehicleManager.instance.m_parkedVehicles.m_buffer[i].Info != null) 399 | { 400 | if (info == VehicleManager.instance.m_parkedVehicles.m_buffer[i].Info) 401 | VehicleManager.instance.ReleaseParkedVehicle(i); 402 | } 403 | 404 | if (i % 256 == 255) yield return i; 405 | } 406 | } 407 | 408 | public static IEnumerator ActionRemoveExistingAll(ThreadBase t) 409 | { 410 | for (ushort i = 0; i < VehicleManager.instance.m_vehicles.m_size; i++) 411 | { 412 | VehicleManager.instance.ReleaseVehicle(i); 413 | if (i % 256 == 255) yield return i; 414 | } 415 | } 416 | 417 | public static IEnumerator ActionRemoveParkedAll(ThreadBase t) 418 | { 419 | for (ushort i = 0; i < VehicleManager.instance.m_parkedVehicles.m_size; i++) 420 | { 421 | VehicleManager.instance.ReleaseParkedVehicle(i); 422 | if (i % 256 == 255) yield return i; 423 | } 424 | } 425 | 426 | private static int ParseVersion(string version) 427 | { 428 | if (version.IsNullOrWhiteSpace()) return 0; 429 | 430 | int v = 0; 431 | string[] t = version.Split('.'); 432 | 433 | for (int i = 0; i < t.Length; i++) 434 | { 435 | v *= 100; 436 | if (int.TryParse(t[i], out int a)) 437 | v += a; 438 | } 439 | 440 | return v; 441 | } 442 | 443 | /// 444 | /// Check if there are new vehicles and add them to the options list 445 | /// 446 | private static void CompileVehiclesList() 447 | { 448 | List optionsList = new List(); 449 | if (config.options != null) optionsList.AddRange(config.options); 450 | 451 | for (uint i = 0; i < PrefabCollection.PrefabCount(); i++) 452 | { 453 | VehicleInfo prefab = PrefabCollection.GetPrefab(i); 454 | 455 | if (prefab == null || ContainsPrefab(prefab)) continue; 456 | 457 | // New vehicle 458 | VehicleOptions options = new VehicleOptions(); 459 | options.SetPrefab(prefab); 460 | 461 | optionsList.Add(options); 462 | } 463 | 464 | if (config.options != null) 465 | DebugUtils.Log("Found " + (optionsList.Count - config.options.Length) + " new vehicle(s)"); 466 | else 467 | DebugUtils.Log("Found " + optionsList.Count + " new vehicle(s)"); 468 | 469 | config.options = optionsList.ToArray(); 470 | 471 | } 472 | 473 | private static bool ContainsPrefab(VehicleInfo prefab) 474 | { 475 | if (config.options == null) return false; 476 | for (int i = 0; i < config.options.Length; i++) 477 | { 478 | if (config.options[i].prefab == prefab) return true; 479 | } 480 | return false; 481 | } 482 | 483 | private static void LogVehicleListSteamID() 484 | { 485 | StringBuilder steamIDs = new StringBuilder("Vehicle Steam IDs : "); 486 | 487 | for (int i = 0; i < config.options.Length; i++) 488 | { 489 | if (config.options[i] != null && config.options[i].name.Contains(".")) 490 | { 491 | steamIDs.Append(config.options[i].name.Substring(0, config.options[i].name.IndexOf("."))); 492 | steamIDs.Append(","); 493 | } 494 | } 495 | steamIDs.Length--; 496 | 497 | DebugUtils.Log(steamIDs.ToString()); 498 | } 499 | 500 | private static bool IsAICustom(VehicleAI ai) 501 | { 502 | Type type = ai.GetType(); 503 | return (type != typeof(AmbulanceAI) || 504 | type != typeof(BusAI) || 505 | type != typeof(CargoTruckAI) || 506 | type != typeof(FireTruckAI) || 507 | type != typeof(GarbageTruckAI) || 508 | type != typeof(HearseAI) || 509 | type != typeof(PassengerCarAI) || 510 | type != typeof(PoliceCarAI)); 511 | } 512 | } 513 | } 514 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/AdvancedVehicleOptions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C323E306-D11E-48A7-9620-121E4ADC4765} 8 | Library 9 | Properties 10 | AdvancedVehicleOptions 11 | AdvancedVehicleOptions 12 | v3.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | none 27 | true 28 | bin\Release\ 29 | 30 | 31 | prompt 32 | 4 33 | true 34 | 35 | 36 | 37 | ..\..\..\ProgramFiles\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\Assembly-CSharp.dll 38 | 39 | 40 | ..\..\..\ProgramFiles\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\ColossalManaged.dll 41 | 42 | 43 | ..\..\..\ProgramFiles\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\ICities.dll 44 | 45 | 46 | 47 | 48 | ..\..\..\ProgramFiles\Steam\SteamApps\common\Cities_Skylines\Cities_Data\Managed\UnityEngine.dll 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | mkdir "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)" 71 | del "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)\$(TargetFileName)" 72 | xcopy /y "$(TargetPath)" "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)" 73 | 74 | 81 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/AdvancedVehicleOptions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvancedVehicleOptions", "AdvancedVehicleOptions.csproj", "{C323E306-D11E-48A7-9620-121E4ADC4765}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {C323E306-D11E-48A7-9620-121E4ADC4765}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {C323E306-D11E-48A7-9620-121E4ADC4765}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {C323E306-D11E-48A7-9620-121E4ADC4765}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {C323E306-D11E-48A7-9620-121E4ADC4765}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/Configuration.cs: -------------------------------------------------------------------------------- 1 | using ColossalFramework.IO; 2 | 3 | using System; 4 | using System.IO; 5 | using System.Xml; 6 | using System.Xml.Serialization; 7 | using System.ComponentModel; 8 | using System.Collections.Generic; 9 | 10 | namespace AdvancedVehicleOptions 11 | { 12 | [XmlType("ArrayOfVehicleOptions")] 13 | [Serializable] 14 | public class Configuration : IDataContainer 15 | { 16 | public class VehicleData 17 | { 18 | #region serialized 19 | [XmlAttribute("name")] 20 | public string name; 21 | [DefaultValue(true)] 22 | public bool enabled = true; 23 | [DefaultValue(false)] 24 | public bool addBackEngine = false; 25 | public float maxSpeed; 26 | public float acceleration; 27 | public float braking; 28 | [DefaultValue(true)] 29 | public bool useColorVariations = true; 30 | public HexaColor color0; 31 | public HexaColor color1; 32 | public HexaColor color2; 33 | public HexaColor color3; 34 | [DefaultValue(-1)] 35 | public int capacity = -1; 36 | #endregion 37 | 38 | public bool isCustomAsset 39 | { 40 | get 41 | { 42 | return name.Contains("."); 43 | } 44 | } 45 | } 46 | 47 | [XmlElement("VehicleOptions")] 48 | public VehicleData[] data; 49 | 50 | [XmlIgnore] 51 | public VehicleOptions[] options; 52 | 53 | private List m_defaultVehicles = new List(); 54 | 55 | // Serialize to save 56 | public void Serialize(DataSerializer s) 57 | { 58 | try 59 | { 60 | int count = options.Length; 61 | s.WriteInt32(count); 62 | 63 | for (int i = 0; i < count; i++) 64 | { 65 | s.WriteUniqueString(options[i].name); 66 | s.WriteBool(options[i].enabled); 67 | s.WriteBool(options[i].addBackEngine); 68 | s.WriteFloat(options[i].maxSpeed); 69 | s.WriteFloat(options[i].acceleration); 70 | s.WriteFloat(options[i].braking); 71 | s.WriteBool(options[i].useColorVariations); 72 | s.WriteUniqueString(options[i].color0.Value); 73 | s.WriteUniqueString(options[i].color1.Value); 74 | s.WriteUniqueString(options[i].color2.Value); 75 | s.WriteUniqueString(options[i].color3.Value); 76 | s.WriteInt32(options[i].capacity); 77 | } 78 | } 79 | catch (Exception e) 80 | { 81 | DebugUtils.LogException(e); 82 | } 83 | } 84 | 85 | public void Deserialize(DataSerializer s) 86 | { 87 | try 88 | { 89 | options = null; 90 | data = null; 91 | 92 | int count = s.ReadInt32(); 93 | data = new VehicleData[count]; 94 | 95 | for (int i = 0; i < count; i++) 96 | { 97 | data[i] = new VehicleData(); 98 | data[i].name = s.ReadUniqueString(); 99 | data[i].enabled = s.ReadBool(); 100 | data[i].addBackEngine = s.ReadBool(); 101 | data[i].maxSpeed = s.ReadFloat(); 102 | data[i].acceleration = s.ReadFloat(); 103 | data[i].braking = s.ReadFloat(); 104 | data[i].useColorVariations = s.ReadBool(); 105 | data[i].color0 = new HexaColor(s.ReadUniqueString()); 106 | data[i].color1 = new HexaColor(s.ReadUniqueString()); 107 | data[i].color2 = new HexaColor(s.ReadUniqueString()); 108 | data[i].color3 = new HexaColor(s.ReadUniqueString()); 109 | data[i].capacity = s.ReadInt32(); 110 | } 111 | } 112 | catch (Exception e) 113 | { 114 | // Couldn't Deserialize 115 | DebugUtils.Warning("Couldn't deserialize"); 116 | DebugUtils.LogException(e); 117 | } 118 | } 119 | 120 | public void AfterDeserialize(DataSerializer s) 121 | { 122 | } 123 | 124 | // Serialize to file 125 | public void Serialize(string filename) 126 | { 127 | try 128 | { 129 | if (AdvancedVehicleOptions.isGameLoaded) OptionsToData(); 130 | 131 | // Add back default vehicle options that might not exist on the map 132 | // I.E. Snowplow on non-snowy maps 133 | if (m_defaultVehicles.Count > 0) 134 | { 135 | List new_data = new List(data); 136 | 137 | for (int i = 0; i < m_defaultVehicles.Count; i++) 138 | { 139 | bool found = false; 140 | for (int j = 0; j < data.Length; j++) 141 | { 142 | if (m_defaultVehicles[i].name == data[j].name) 143 | { 144 | found = true; 145 | break; 146 | } 147 | } 148 | if (!found) 149 | { 150 | new_data.Add(m_defaultVehicles[i]); 151 | } 152 | } 153 | 154 | data = new_data.ToArray(); 155 | } 156 | 157 | using (FileStream stream = new FileStream(filename, FileMode.OpenOrCreate)) 158 | { 159 | stream.SetLength(0); // Emptying the file !!! 160 | XmlSerializer xmlSerializer = new XmlSerializer(typeof(Configuration)); 161 | xmlSerializer.Serialize(stream, this); 162 | DebugUtils.Log("Configuration saved"); 163 | } 164 | } 165 | catch (Exception e) 166 | { 167 | DebugUtils.Warning("Couldn't save configuration at \"" + Directory.GetCurrentDirectory() + "\""); 168 | DebugUtils.LogException(e); 169 | } 170 | } 171 | 172 | public void Deserialize(string filename) 173 | { 174 | XmlSerializer xmlSerializer = new XmlSerializer(typeof(Configuration)); 175 | Configuration config = null; 176 | 177 | options = null; 178 | data = null; 179 | 180 | try 181 | { 182 | // Trying to Deserialize the configuration file 183 | using (FileStream stream = new FileStream(filename, FileMode.Open)) 184 | { 185 | config = xmlSerializer.Deserialize(stream) as Configuration; 186 | } 187 | } 188 | catch (Exception e) 189 | { 190 | // Couldn't Deserialize (XML malformed?) 191 | DebugUtils.Warning("Couldn't load configuration (XML malformed?)"); 192 | DebugUtils.LogException(e); 193 | 194 | config = null; 195 | } 196 | 197 | if(config != null) 198 | { 199 | data = config.data; 200 | 201 | if(data != null) 202 | { 203 | // Saves all default vehicle options that might not exist on the map 204 | // I.E. Snowplow on non-snowy maps 205 | m_defaultVehicles.Clear(); 206 | for (int i = 0; i < data.Length; i++) 207 | { 208 | if (data[i] != null && !data[i].isCustomAsset) 209 | m_defaultVehicles.Add(data[i]); 210 | } 211 | } 212 | 213 | 214 | if (AdvancedVehicleOptions.isGameLoaded) DataToOptions(); 215 | } 216 | } 217 | 218 | public void OptionsToData() 219 | { 220 | if (options == null) return; 221 | 222 | data = new VehicleData[options.Length]; 223 | 224 | for (int i = 0; i < options.Length; i++) 225 | { 226 | data[i] = new VehicleData(); 227 | data[i].name = options[i].name; 228 | data[i].enabled = options[i].enabled; 229 | data[i].addBackEngine = options[i].addBackEngine; 230 | data[i].maxSpeed = options[i].maxSpeed; 231 | data[i].acceleration = options[i].acceleration; 232 | data[i].braking = options[i].braking; 233 | data[i].useColorVariations = options[i].useColorVariations; 234 | data[i].color0 = options[i].color0; 235 | data[i].color1 = options[i].color1; 236 | data[i].color2 = options[i].color2; 237 | data[i].color3 = options[i].color3; 238 | data[i].capacity = options[i].capacity; 239 | } 240 | } 241 | 242 | public void DataToOptions() 243 | { 244 | if (data == null) return; 245 | 246 | options = new VehicleOptions[data.Length]; 247 | 248 | for (int i = 0; i < data.Length; i++) 249 | { 250 | if (data[i].name == null) continue; 251 | 252 | options[i] = new VehicleOptions(); 253 | options[i].name = data[i].name; 254 | options[i].enabled = data[i].enabled; 255 | options[i].addBackEngine = data[i].addBackEngine; 256 | options[i].maxSpeed = data[i].maxSpeed; 257 | options[i].acceleration = data[i].acceleration; 258 | options[i].braking = data[i].braking; 259 | options[i].useColorVariations = data[i].useColorVariations; 260 | options[i].color0 = data[i].color0; 261 | options[i].color1 = data[i].color1; 262 | options[i].color2 = data[i].color2; 263 | options[i].color3 = data[i].color3; 264 | options[i].capacity = data[i].capacity; 265 | } 266 | 267 | VehicleOptions.UpdateTransfertVehicles(); 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/DebugUtils.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using ColossalFramework; 3 | 4 | using System; 5 | 6 | namespace AdvancedVehicleOptions 7 | { 8 | public class DebugUtils 9 | { 10 | public const string modPrefix = "[Advanced Vehicle Options "+ModInfo.version+"] "; 11 | 12 | public static SavedBool hideDebugMessages = new SavedBool("hideDebugMessages", AdvancedVehicleOptions.settingsFileName, true, true); 13 | 14 | public static void Log(string message) 15 | { 16 | if (hideDebugMessages.value) return; 17 | 18 | if (message == m_lastLog) 19 | { 20 | m_duplicates++; 21 | } 22 | else if (m_duplicates > 0) 23 | { 24 | Debug.Log(modPrefix + m_lastLog + "(x" + (m_duplicates + 1) + ")"); 25 | Debug.Log(modPrefix + message); 26 | m_duplicates = 0; 27 | } 28 | else 29 | { 30 | Debug.Log(modPrefix + message); 31 | } 32 | m_lastLog = message; 33 | } 34 | 35 | public static void Warning(string message) 36 | { 37 | if (message != m_lastWarning) 38 | { 39 | Debug.LogWarning(modPrefix + "Warning: " + message); 40 | } 41 | m_lastWarning = message; 42 | } 43 | 44 | public static void LogException(Exception e) 45 | { 46 | Debug.LogError(modPrefix + "Intercepted exception (not game breaking):"); 47 | Debug.LogException(e); 48 | } 49 | 50 | private static string m_lastWarning; 51 | private static string m_lastLog; 52 | private static int m_duplicates = 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/GUI/UIFastList.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using ColossalFramework.UI; 3 | 4 | using System; 5 | 6 | namespace AdvancedVehicleOptions.GUI 7 | { 8 | public interface IUIFastListRow 9 | { 10 | #region Methods to implement 11 | /// 12 | /// Method invoked very often, make sure it is fast 13 | /// Avoid doing any calculations, the data should be already processed any ready to display. 14 | /// 15 | /// What needs to be displayed 16 | /// Use this to display a different look for your odd rows 17 | void Display(object data, bool isRowOdd); 18 | 19 | /// 20 | /// Change the style of the selected row here 21 | /// 22 | /// Use this to display a different look for your odd rows 23 | void Select(bool isRowOdd); 24 | /// 25 | /// Change the style of the row back from selected here 26 | /// 27 | /// Use this to display a different look for your odd rows 28 | void Deselect(bool isRowOdd); 29 | #endregion 30 | 31 | #region From UIPanel 32 | // No need to implement those, they are in UIPanel 33 | // Those are declared here so they can be used inside UIFastList 34 | float width { get; set; } 35 | bool enabled { get; set; } 36 | Vector3 relativePosition { get; set; } 37 | event MouseEventHandler eventClick; 38 | event MouseEventHandler eventMouseEnter; 39 | #endregion 40 | } 41 | 42 | /// 43 | /// This component is specifically designed the handle the display of 44 | /// very large amount of rows in a scrollable panel while minimizing 45 | /// the impact on the performances. 46 | /// 47 | /// This class will instantiate the rows for you based on the actual 48 | /// height of the UIFastList and the rowHeight value provided. 49 | /// 50 | /// The row class must inherit UIPanel and implement IUIFastListRow : 51 | /// public class MyCustomRow : UIPanel, IUIFastListRow 52 | /// 53 | /// How it works : 54 | /// This class only instantiate as many rows as visible on screen (+1 55 | /// extra to simulate in-between steps). Then the content of those is 56 | /// updated according to what needs to be displayed by calling the 57 | /// Display method declared in IUIFastListRow. 58 | /// 59 | /// Provide the list of data with rowData. This data is send back to 60 | /// your custom row when it needs to be displayed. For optimal 61 | /// performances, make sure this data is already processed and ready 62 | /// to display. 63 | /// 64 | /// Creation example : 65 | /// UIFastList myFastList = UIFastList.Create(this); 66 | /// myFastList.size = new Vector2(200f, 300f); 67 | /// myFastList.rowHeight = 40f; 68 | /// myFastList.rowData = myDataList; 69 | /// 70 | /// 71 | public class UIFastList : UIComponent 72 | { 73 | #region Private members 74 | private UIPanel m_panel; 75 | private UIScrollbar m_scrollbar; 76 | private FastList m_rows; 77 | private FastList m_rowsData; 78 | 79 | private Type m_rowType; 80 | private string m_backgroundSprite; 81 | private Color32 m_color = new Color32(255, 255, 255, 255); 82 | private float m_rowHeight = -1; 83 | private float m_pos = -1; 84 | private float m_stepSize = 0; 85 | private bool m_canSelect = false; 86 | private int m_selectedDataId = -1; 87 | private int m_selectedRowId = -1; 88 | private bool m_lock = false; 89 | private bool m_updateContent = true; 90 | private bool m_autoHideScrollbar = false; 91 | private UIComponent m_lastMouseEnter; 92 | #endregion 93 | 94 | /// 95 | /// Use this to create the UIFastList. 96 | /// Do NOT use AddUIComponent. 97 | /// I had to do that way because MonoBehaviors classes cannot be generic 98 | /// 99 | /// The type of the row UI component 100 | /// 101 | /// 102 | public static UIFastList Create(UIComponent parent) 103 | where T : UIPanel, IUIFastListRow 104 | { 105 | UIFastList list = parent.AddUIComponent(); 106 | list.m_rowType = typeof(T); 107 | return list; 108 | } 109 | 110 | #region Public accessors 111 | public bool autoHideScrollbar 112 | { 113 | get { return m_autoHideScrollbar; } 114 | set 115 | { 116 | if (m_autoHideScrollbar != value) 117 | { 118 | m_autoHideScrollbar = value; 119 | UpdateScrollbar(); 120 | } 121 | } 122 | } 123 | /// 124 | /// Change the color of the background 125 | /// 126 | public Color32 backgroundColor 127 | { 128 | get { return m_color; } 129 | set 130 | { 131 | m_color = value; 132 | if (m_panel != null) 133 | m_panel.color = value; 134 | } 135 | } 136 | 137 | /// 138 | /// Change the sprite of the background 139 | /// 140 | public string backgroundSprite 141 | { 142 | get { return m_backgroundSprite; } 143 | set 144 | { 145 | if (m_backgroundSprite != value) 146 | { 147 | m_backgroundSprite = value; 148 | if (m_panel != null) 149 | m_panel.backgroundSprite = value; 150 | } 151 | } 152 | } 153 | 154 | /// 155 | /// Can rows be selected by clicking on them 156 | /// Default value is false 157 | /// Rows can still be selected via selectedIndex 158 | /// 159 | public bool canSelect 160 | { 161 | get { return m_canSelect; } 162 | set 163 | { 164 | if (m_canSelect != value) 165 | { 166 | m_canSelect = value; 167 | 168 | if (m_rows == null) return; 169 | for (int i = 0; i < m_rows.m_size; i++) 170 | { 171 | if (m_canSelect) 172 | m_rows[i].eventClick += OnRowClicked; 173 | else 174 | m_rows[i].eventClick -= OnRowClicked; 175 | } 176 | } 177 | } 178 | } 179 | 180 | /// 181 | /// Change the position in the list 182 | /// Display the data at the position in the top row. 183 | /// This doesn't update the list if the position stay the same 184 | /// Use DisplayAt for that 185 | /// 186 | public float listPosition 187 | { 188 | get { return m_pos; } 189 | set 190 | { 191 | if (m_rowHeight <= 0) return; 192 | if (m_pos != value) 193 | { 194 | float pos = Mathf.Max(Mathf.Min(value, m_rowsData.m_size - height / m_rowHeight), 0); 195 | m_updateContent = Mathf.FloorToInt(m_pos) != Mathf.FloorToInt(pos); 196 | DisplayAt(pos); 197 | } 198 | } 199 | } 200 | 201 | /// 202 | /// This is the list of data that will be send to the IUIFastListRow.Display method 203 | /// Changing this list will reset the display position to 0 204 | /// You can also change rowsData.m_buffer and rowsData.m_size 205 | /// and refresh the display with DisplayAt method 206 | /// 207 | public FastList rowsData 208 | { 209 | get 210 | { 211 | if (m_rowsData == null) m_rowsData = new FastList(); 212 | return m_rowsData; 213 | } 214 | set 215 | { 216 | if (m_rowsData != value) 217 | { 218 | m_rowsData = value; 219 | DisplayAt(0); 220 | } 221 | } 222 | } 223 | 224 | /// 225 | /// This MUST be set, it is the height in pixels of each row 226 | /// 227 | public float rowHeight 228 | { 229 | get { return m_rowHeight; } 230 | set 231 | { 232 | if (m_rowHeight != value) 233 | { 234 | m_rowHeight = value; 235 | CheckRows(); 236 | } 237 | } 238 | } 239 | 240 | /// 241 | /// Currently selected row 242 | /// -1 if none selected 243 | /// 244 | public int selectedIndex 245 | { 246 | get { return m_selectedDataId; } 247 | set 248 | { 249 | if (m_rowsData == null || m_rowsData.m_size == 0) 250 | { 251 | m_selectedDataId = -1; 252 | return; 253 | } 254 | 255 | int oldId = m_selectedDataId; 256 | if (oldId >= m_rowsData.m_size) oldId = -1; 257 | m_selectedDataId = Mathf.Min(Mathf.Max(-1, value), m_rowsData.m_size - 1); 258 | 259 | int pos = Mathf.FloorToInt(m_pos); 260 | int newRowId = Mathf.Max(-1, m_selectedDataId - pos); 261 | if (newRowId >= m_rows.m_size) newRowId = -1; 262 | 263 | if (newRowId >= 0 && newRowId == m_selectedRowId && !m_updateContent) return; 264 | 265 | if (m_selectedRowId >= 0) 266 | { 267 | m_rows[m_selectedRowId].Deselect((oldId % 2) == 1); 268 | m_selectedRowId = -1; 269 | } 270 | 271 | if (newRowId >= 0) 272 | { 273 | m_selectedRowId = newRowId; 274 | m_rows[m_selectedRowId].Select((m_selectedDataId % 2) == 1); 275 | } 276 | 277 | if (eventSelectedIndexChanged != null && m_selectedDataId != oldId) 278 | eventSelectedIndexChanged(this, m_selectedDataId); 279 | } 280 | } 281 | 282 | public object selectedItem 283 | { 284 | get 285 | { 286 | if (m_selectedDataId == -1) return null; 287 | return m_rowsData.m_buffer[m_selectedDataId]; 288 | } 289 | } 290 | 291 | public bool selectOnMouseEnter 292 | { get; set; } 293 | 294 | /// 295 | /// The number of pixels moved at each scroll step 296 | /// When set to 0 or less, rowHeight is used instead. 297 | /// 298 | public float stepSize 299 | { 300 | get { return (m_stepSize > 0) ? m_stepSize : m_rowHeight; } 301 | set { m_stepSize = value; } 302 | } 303 | #endregion 304 | 305 | #region Events 306 | /// 307 | /// Called when the currently selected row changed 308 | /// 309 | public event PropertyChangedEventHandler eventSelectedIndexChanged; 310 | #endregion 311 | 312 | #region Public methods 313 | /// 314 | /// Clear the list 315 | /// 316 | public void Clear() 317 | { 318 | m_rowsData.Clear(); 319 | 320 | for (int i = 0; i < m_rows.m_size; i++) 321 | { 322 | m_rows[i].enabled = false; 323 | } 324 | 325 | UpdateScrollbar(); 326 | } 327 | 328 | /// 329 | /// Display the data at the position in the top row. 330 | /// This update the list even if the position remind the same 331 | /// 332 | /// Index position in the list 333 | public void DisplayAt(float pos) 334 | { 335 | if (m_rowsData == null || m_rowHeight <= 0) return; 336 | 337 | SetupControls(); 338 | 339 | m_pos = Mathf.Max(Mathf.Min(pos, m_rowsData.m_size - height / m_rowHeight), 0f); 340 | 341 | for (int i = 0; i < m_rows.m_size; i++) 342 | { 343 | int dataPos = Mathf.FloorToInt(m_pos + i); 344 | float offset = rowHeight * (m_pos + i - dataPos); 345 | if (dataPos < m_rowsData.m_size) 346 | { 347 | if (m_updateContent) 348 | m_rows[i].Display(m_rowsData[dataPos], (dataPos % 2) == 1); 349 | 350 | if (dataPos == m_selectedDataId && m_updateContent) 351 | { 352 | m_selectedRowId = i; 353 | m_rows[m_selectedRowId].Select((dataPos % 2) == 1); 354 | } 355 | 356 | m_rows[i].enabled = true; 357 | } 358 | else 359 | m_rows[i].enabled = false; 360 | 361 | m_rows[i].relativePosition = new Vector3(0, i * rowHeight - offset); 362 | } 363 | 364 | UpdateScrollbar(); 365 | m_updateContent = true; 366 | } 367 | 368 | /// 369 | /// Refresh the display 370 | /// 371 | public void Refresh() 372 | { 373 | DisplayAt(m_pos); 374 | } 375 | #endregion 376 | 377 | #region Overrides 378 | public override void Start() 379 | { 380 | base.Start(); 381 | 382 | SetupControls(); 383 | } 384 | 385 | protected override void OnSizeChanged() 386 | { 387 | base.OnSizeChanged(); 388 | 389 | if (m_panel == null) return; 390 | 391 | m_panel.size = size; 392 | 393 | m_scrollbar.height = height; 394 | m_scrollbar.trackObject.height = height; 395 | m_scrollbar.AlignTo(this, UIAlignAnchor.TopRight); 396 | 397 | CheckRows(); 398 | } 399 | 400 | protected override void OnMouseWheel(UIMouseEventParameter p) 401 | { 402 | base.OnMouseWheel(p); 403 | 404 | if (m_stepSize > 0 && m_rowHeight > 0) 405 | listPosition = m_pos - p.wheelDelta * m_stepSize / m_rowHeight; 406 | else 407 | listPosition = m_pos - p.wheelDelta; 408 | 409 | if (selectOnMouseEnter) 410 | OnRowClicked(m_lastMouseEnter, p); 411 | } 412 | #endregion 413 | 414 | #region Private methods 415 | 416 | protected void OnRowClicked(UIComponent component, UIMouseEventParameter p) 417 | { 418 | if (selectOnMouseEnter) m_lastMouseEnter = component; 419 | 420 | int max = Mathf.Min(m_rowsData.m_size, m_rows.m_size); 421 | for (int i = 0; i < max; i++) 422 | { 423 | if (component == (UIComponent)m_rows[i]) 424 | { 425 | selectedIndex = i + Mathf.FloorToInt(m_pos); 426 | return; 427 | } 428 | } 429 | } 430 | 431 | private void CheckRows() 432 | { 433 | if (m_panel == null || m_rowHeight <= 0) return; 434 | 435 | int nbRows = Mathf.CeilToInt(height / m_rowHeight) + 1; 436 | 437 | if (m_rows == null) 438 | { 439 | m_rows = new FastList(); 440 | m_rows.SetCapacity(nbRows); 441 | } 442 | 443 | if (m_rows.m_size < nbRows) 444 | { 445 | // Adding missing rows 446 | for (int i = m_rows.m_size; i < nbRows; i++) 447 | { 448 | m_rows.Add(m_panel.AddUIComponent(m_rowType) as IUIFastListRow); 449 | if (m_canSelect && !selectOnMouseEnter) m_rows[i].eventClick += OnRowClicked; 450 | else if (m_canSelect) m_rows[i].eventMouseEnter += OnRowClicked; 451 | } 452 | } 453 | else if (m_rows.m_size > nbRows) 454 | { 455 | // Remove excess rows 456 | for (int i = nbRows; i < m_rows.m_size; i++) 457 | Destroy(m_rows[i] as UnityEngine.Object); 458 | 459 | m_rows.SetCapacity(nbRows); 460 | } 461 | 462 | UpdateScrollbar(); 463 | } 464 | 465 | private void UpdateScrollbar() 466 | { 467 | if (m_rowsData == null || m_rowHeight <= 0) return; 468 | 469 | if (m_autoHideScrollbar) 470 | { 471 | bool isVisible = m_rowsData.m_size * m_rowHeight > height; 472 | float newPanelWidth = isVisible ? width - 10f : width; 473 | float newItemWidth = isVisible ? width - 20f : width; 474 | 475 | m_panel.width = newPanelWidth; 476 | for (int i = 0; i < m_rows.m_size; i++) 477 | { 478 | m_rows[i].width = newItemWidth; 479 | } 480 | 481 | m_scrollbar.isVisible = isVisible; 482 | } 483 | 484 | float H = m_rowHeight * m_rowsData.m_size; 485 | float scrollSize = height * height / (m_rowHeight * m_rowsData.m_size); 486 | float amount = stepSize * height / (m_rowHeight * m_rowsData.m_size); 487 | 488 | m_scrollbar.scrollSize = Mathf.Max(10f, scrollSize); 489 | m_scrollbar.minValue = 0f; 490 | m_scrollbar.maxValue = height; 491 | m_scrollbar.incrementAmount = Mathf.Max(1f, amount); 492 | 493 | UpdateScrollPosition(); 494 | } 495 | 496 | private void UpdateScrollPosition() 497 | { 498 | if (m_lock || m_rowHeight <= 0) return; 499 | 500 | m_lock = true; 501 | 502 | float pos = m_pos * (height - m_scrollbar.scrollSize) / (m_rowsData.m_size - height / m_rowHeight); 503 | if (pos != m_scrollbar.value) 504 | m_scrollbar.value = pos; 505 | 506 | m_lock = false; 507 | } 508 | 509 | 510 | private void SetupControls() 511 | { 512 | if (m_panel != null) return; 513 | 514 | // Panel 515 | m_panel = AddUIComponent(); 516 | m_panel.width = width - 10f; 517 | m_panel.height = height; 518 | m_panel.backgroundSprite = m_backgroundSprite; 519 | m_panel.color = m_color; 520 | m_panel.clipChildren = true; 521 | m_panel.relativePosition = Vector2.zero; 522 | 523 | // Scrollbar 524 | m_scrollbar = AddUIComponent(); 525 | m_scrollbar.width = 20f; 526 | m_scrollbar.height = height; 527 | m_scrollbar.orientation = UIOrientation.Vertical; 528 | m_scrollbar.pivot = UIPivotPoint.BottomLeft; 529 | m_scrollbar.AlignTo(this, UIAlignAnchor.TopRight); 530 | m_scrollbar.minValue = 0; 531 | m_scrollbar.value = 0; 532 | m_scrollbar.incrementAmount = 50; 533 | 534 | UISlicedSprite tracSprite = m_scrollbar.AddUIComponent(); 535 | tracSprite.relativePosition = Vector2.zero; 536 | tracSprite.autoSize = true; 537 | tracSprite.size = tracSprite.parent.size; 538 | tracSprite.fillDirection = UIFillDirection.Vertical; 539 | tracSprite.spriteName = "ScrollbarTrack"; 540 | 541 | m_scrollbar.trackObject = tracSprite; 542 | 543 | UISlicedSprite thumbSprite = tracSprite.AddUIComponent(); 544 | thumbSprite.relativePosition = Vector2.zero; 545 | thumbSprite.fillDirection = UIFillDirection.Vertical; 546 | thumbSprite.autoSize = true; 547 | thumbSprite.width = thumbSprite.parent.width - 8; 548 | thumbSprite.spriteName = "ScrollbarThumb"; 549 | 550 | m_scrollbar.thumbObject = thumbSprite; 551 | 552 | // Rows 553 | CheckRows(); 554 | 555 | m_scrollbar.eventValueChanged += (c, t) => 556 | { 557 | if (m_lock || m_rowHeight <= 0) return; 558 | 559 | m_lock = true; 560 | 561 | listPosition = m_scrollbar.value * (m_rowsData.m_size - height / m_rowHeight) / (height - m_scrollbar.scrollSize - 1f); 562 | m_lock = false; 563 | }; 564 | } 565 | #endregion 566 | } 567 | } 568 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/GUI/UIMainPanel.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using ColossalFramework.Globalization; 3 | using ColossalFramework.UI; 4 | 5 | using System; 6 | using System.Reflection; 7 | 8 | using UIUtils = SamsamTS.UIUtils; 9 | 10 | namespace AdvancedVehicleOptions.GUI 11 | { 12 | public class UIMainPanel : UIPanel 13 | { 14 | private UITitleBar m_title; 15 | private UIDropDown m_category; 16 | private UITextField m_search; 17 | private UIFastList m_fastList; 18 | private UIButton m_import; 19 | private UIButton m_export; 20 | private UITextureSprite m_preview; 21 | private UISprite m_followVehicle; 22 | private UIOptionPanel m_optionPanel; 23 | 24 | public UIButton m_button; 25 | 26 | private VehicleOptions[] m_optionsList; 27 | private PreviewRenderer m_previewRenderer; 28 | private Color m_previewColor; 29 | private CameraController m_cameraController; 30 | private uint m_seekStart = 0; 31 | 32 | private const int HEIGHT = 550; 33 | private const int WIDTHLEFT = 470; 34 | private const int WIDTHRIGHT = 315; 35 | 36 | public static readonly string[] categoryList = { "All", "Citizen", "Bicycle", 37 | "Forestry", "Farming", "Ore", "Oil", "Industry", 38 | "Police", "Prison", "Fire Safety", "Disaster", 39 | "Healthcare", "Deathcare", "Garbage", "Maintenance", 40 | "Taxi", "Bus", "Metro", "Tram", "Monorail", "CableCar", 41 | "Cargo Train", "Passenger Train", 42 | "Cargo Ship", "Passenger Ship", "Plane", "Tours", 43 | "Monument", "Natural" }; 44 | 45 | public static readonly string[] vehicleIconList = { "IconCitizenVehicle", "IconCitizenBicycleVehicle", 46 | "IconPolicyForest", "IconPolicyFarming", "IconPolicyOre", "IconPolicyOil", "IconPolicyNone", 47 | "ToolbarIconPolice", "IconPolicyDoubleSentences", "InfoIconFireSafety", "ToolbarIconFireDepartmentHovered", 48 | "ToolbarIconHealthcare", "ToolbarIconHealthcareHovered", "InfoIconGarbage", "InfoIconMaintenance", 49 | "SubBarPublicTransportTaxi", "SubBarPublicTransportBus", "SubBarPublicTransportMetro", "SubBarPublicTransportTram", "SubBarPublicTransportMonorail", "SubBarPublicTransportCableCar", 50 | "IconServiceVehicle", "SubBarPublicTransportTrain", 51 | "IconCargoShip", "SubBarPublicTransportShip", "SubBarPublicTransportPlane", "SubBarPublicTransportTours", 52 | "ToolbarIconMonuments", "SubBarFireDepartmentDisaster"}; 53 | 54 | public UIOptionPanel optionPanel 55 | { 56 | get { return m_optionPanel; } 57 | } 58 | 59 | public VehicleOptions[] optionList 60 | { 61 | get { return m_optionsList; } 62 | set 63 | { 64 | m_optionsList = value; 65 | Array.Sort(m_optionsList); 66 | 67 | PopulateList(); 68 | } 69 | } 70 | 71 | public override void Start() 72 | { 73 | try 74 | { 75 | UIView view = GetUIView(); 76 | 77 | name = "AdvancedVehicleOptions"; 78 | backgroundSprite = "UnlockingPanel2"; 79 | isVisible = false; 80 | canFocus = true; 81 | isInteractive = true; 82 | width = WIDTHLEFT + WIDTHRIGHT; 83 | height = HEIGHT; 84 | relativePosition = new Vector3(Mathf.Floor((view.fixedWidth - width) / 2), Mathf.Floor((view.fixedHeight - height) / 2)); 85 | 86 | // Get camera controller 87 | GameObject go = GameObject.FindGameObjectWithTag("MainCamera"); 88 | if (go != null) 89 | { 90 | m_cameraController = go.GetComponent(); 91 | } 92 | 93 | // Setting up UI 94 | SetupControls(); 95 | 96 | // Adding main button 97 | UITabstrip toolStrip = view.FindUIComponent("MainToolstrip"); 98 | m_button = AddUIComponent(); 99 | 100 | m_button.normalBgSprite = "IconCitizenVehicle"; 101 | m_button.focusedFgSprite = "ToolbarIconGroup6Focused"; 102 | m_button.hoveredFgSprite = "ToolbarIconGroup6Hovered"; 103 | 104 | m_button.size = new Vector2(43f, 49f); 105 | m_button.name = "Advanced Vehicle Options"; 106 | m_button.tooltip = "Advanced Vehicle Options " + ModInfo.version; 107 | m_button.relativePosition = new Vector3(0, 5); 108 | 109 | m_button.eventButtonStateChanged += (c, s) => 110 | { 111 | if (s == UIButton.ButtonState.Focused) 112 | { 113 | if (!isVisible) 114 | { 115 | isVisible = true; 116 | m_fastList.DisplayAt(m_fastList.listPosition); 117 | m_optionPanel.Show(m_fastList.rowsData[m_fastList.selectedIndex] as VehicleOptions); 118 | m_followVehicle.isVisible = m_preview.parent.isVisible = true; 119 | } 120 | } 121 | else 122 | { 123 | isVisible = false; 124 | m_button.Unfocus(); 125 | } 126 | }; 127 | 128 | toolStrip.AddTab("Advanced Vehicle Options", m_button.gameObject, null, null); 129 | 130 | FieldInfo m_ObjectIndex = typeof(MainToolbar).GetField("m_ObjectIndex", BindingFlags.Instance | BindingFlags.NonPublic); 131 | m_ObjectIndex.SetValue(ToolsModifierControl.mainToolbar, (int)m_ObjectIndex.GetValue(ToolsModifierControl.mainToolbar) + 1); 132 | 133 | m_title.closeButton.eventClick += (component, param) => 134 | { 135 | toolStrip.closeButton.SimulateClick(); 136 | }; 137 | 138 | Locale locale = (Locale)typeof(LocaleManager).GetField("m_Locale", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(LocaleManager.instance); 139 | Locale.Key key = new Locale.Key 140 | { 141 | m_Identifier = "TUTORIAL_ADVISER_TITLE", 142 | m_Key = m_button.name 143 | }; 144 | if (!locale.Exists(key)) 145 | { 146 | locale.AddLocalizedString(key, m_button.name); 147 | } 148 | key = new Locale.Key 149 | { 150 | m_Identifier = "TUTORIAL_ADVISER", 151 | m_Key = m_button.name 152 | }; 153 | if (!locale.Exists(key)) 154 | { 155 | locale.AddLocalizedString(key, ""); 156 | } 157 | 158 | view.FindUIComponent("TSContainer").AddUIComponent().color = new Color32(0, 0, 0, 0); 159 | 160 | optionList = AdvancedVehicleOptions.config.options; 161 | DebugUtils.Log("UI initialized."); 162 | } 163 | catch (Exception e) 164 | { 165 | DebugUtils.Log("UI initialization failed."); 166 | DebugUtils.LogException(e); 167 | 168 | if (m_button != null) Destroy(m_button.gameObject); 169 | 170 | Destroy(gameObject); 171 | } 172 | } 173 | 174 | /*public override void OnDestroy() 175 | { 176 | base.OnDestroy(); 177 | 178 | DebugUtils.Log("Destroying UIMainPanel"); 179 | 180 | if (m_button != null) GameObject.Destroy(m_button.gameObject); 181 | GameObject.Destroy(m_optionPanel.gameObject); 182 | }*/ 183 | 184 | private void SetupControls() 185 | { 186 | float offset = 40f; 187 | 188 | // Title Bar 189 | m_title = AddUIComponent(); 190 | m_title.iconSprite = "IconCitizenVehicle"; 191 | m_title.title = "Advanced Vehicle Options " + ModInfo.version; 192 | 193 | // Category DropDown 194 | UILabel label = AddUIComponent(); 195 | label.textScale = 0.8f; 196 | label.padding = new RectOffset(0, 0, 8, 0); 197 | label.relativePosition = new Vector3(10f, offset); 198 | label.text = "Category :"; 199 | 200 | m_category = UIUtils.CreateDropDown(this); 201 | m_category.width = 150; 202 | 203 | for (int i = 0; i < categoryList.Length; i++) 204 | m_category.AddItem(categoryList[i]); 205 | 206 | m_category.selectedIndex = 0; 207 | m_category.tooltip = "Select a category to display\nTip: Use the mouse wheel to switch between categories faster"; 208 | m_category.relativePosition = label.relativePosition + new Vector3(70f, 0f); 209 | 210 | m_category.eventSelectedIndexChanged += (c, t) => 211 | { 212 | m_category.enabled = false; 213 | PopulateList(); 214 | m_category.enabled = true; 215 | }; 216 | 217 | // Search 218 | m_search = UIUtils.CreateTextField(this); 219 | m_search.width = 150f; 220 | m_search.height = 30f; 221 | m_search.padding = new RectOffset(6, 6, 6, 6); 222 | m_search.tooltip = "Type the name of a vehicle type"; 223 | m_search.relativePosition = new Vector3(WIDTHLEFT - m_search.width, offset); 224 | 225 | m_search.eventTextChanged += (c, t) => PopulateList(); 226 | 227 | label = AddUIComponent(); 228 | label.textScale = 0.8f; 229 | label.padding = new RectOffset(0, 0, 8, 0); 230 | label.relativePosition = m_search.relativePosition - new Vector3(60f, 0f); 231 | label.text = "Search :"; 232 | 233 | // FastList 234 | m_fastList = UIFastList.Create(this); 235 | m_fastList.backgroundSprite = "UnlockingPanel"; 236 | m_fastList.width = WIDTHLEFT - 5; 237 | m_fastList.height = height - offset - 110; 238 | m_fastList.canSelect = true; 239 | m_fastList.relativePosition = new Vector3(5, offset + 35); 240 | 241 | // Configuration file buttons 242 | UILabel configLabel = this.AddUIComponent(); 243 | configLabel.text = "Configuration file:"; 244 | configLabel.textScale = 0.8f; 245 | configLabel.relativePosition = new Vector3(10, height - 60); 246 | 247 | m_import = UIUtils.CreateButton(this); 248 | m_import.text = "Import"; 249 | m_import.tooltip = "Import the configuration"; 250 | m_import.relativePosition = new Vector3(10, height - 40); 251 | 252 | m_export = UIUtils.CreateButton(this); 253 | m_export.text = "Export"; 254 | m_export.tooltip = "Export the configuration"; 255 | m_export.relativePosition = new Vector3(105, height - 40); 256 | 257 | // Preview 258 | UIPanel panel = AddUIComponent(); 259 | panel.backgroundSprite = "GenericPanel"; 260 | panel.width = WIDTHRIGHT - 10; 261 | panel.height = HEIGHT - 375; 262 | panel.relativePosition = new Vector3(WIDTHLEFT + 5, offset); 263 | 264 | m_preview = panel.AddUIComponent(); 265 | m_preview.size = panel.size; 266 | m_preview.relativePosition = Vector3.zero; 267 | 268 | m_previewRenderer = gameObject.AddComponent(); 269 | m_previewRenderer.size = m_preview.size * 2; // Twice the size for anti-aliasing 270 | 271 | m_preview.texture = m_previewRenderer.texture; 272 | 273 | // Follow 274 | if (m_cameraController != null) 275 | { 276 | m_followVehicle = AddUIComponent(); 277 | m_followVehicle.spriteName = "LocationMarkerFocused"; 278 | m_followVehicle.width = m_followVehicle.spriteInfo.width; 279 | m_followVehicle.height = m_followVehicle.spriteInfo.height; 280 | m_followVehicle.tooltip = "Click here to cycle through the existing vehicles of that type"; 281 | m_followVehicle.relativePosition = new Vector3(panel.relativePosition.x + panel.width - m_followVehicle.width - 5, panel.relativePosition.y + 5); 282 | 283 | m_followVehicle.eventClick += (c, p) => FollowNextVehicle(); 284 | } 285 | 286 | // Option panel 287 | m_optionPanel = AddUIComponent(); 288 | m_optionPanel.relativePosition = new Vector3(WIDTHLEFT, height - 330); 289 | 290 | // Event handlers 291 | m_fastList.eventSelectedIndexChanged += OnSelectedItemChanged; 292 | m_optionPanel.eventEnableCheckChanged += OnEnableStateChanged; 293 | m_import.eventClick += (c, t) => 294 | { 295 | DefaultOptions.RestoreAll(); 296 | AdvancedVehicleOptions.ImportConfig(); 297 | optionList = AdvancedVehicleOptions.config.options; 298 | }; 299 | m_export.eventClick += (c, t) => AdvancedVehicleOptions.ExportConfig(); 300 | 301 | panel.eventMouseDown += (c, p) => 302 | { 303 | eventMouseMove += RotateCamera; 304 | if (m_optionPanel.m_options != null && m_optionPanel.m_options.useColorVariations) 305 | m_previewRenderer.RenderVehicle(m_optionPanel.m_options.prefab, m_previewColor); 306 | else 307 | m_previewRenderer.RenderVehicle(m_optionPanel.m_options.prefab); 308 | 309 | }; 310 | 311 | panel.eventMouseUp += (c, p) => 312 | { 313 | eventMouseMove -= RotateCamera; 314 | if (m_optionPanel.m_options != null && m_optionPanel.m_options.useColorVariations) 315 | m_previewRenderer.RenderVehicle(m_optionPanel.m_options.prefab, m_previewColor); 316 | else 317 | m_previewRenderer.RenderVehicle(m_optionPanel.m_options.prefab); 318 | }; 319 | 320 | panel.eventMouseWheel += (c, p) => 321 | { 322 | m_previewRenderer.zoom -= Mathf.Sign(p.wheelDelta) * 0.25f; 323 | if (m_optionPanel.m_options != null && m_optionPanel.m_options.useColorVariations) 324 | m_previewRenderer.RenderVehicle(m_optionPanel.m_options.prefab, m_previewColor); 325 | else 326 | m_previewRenderer.RenderVehicle(m_optionPanel.m_options.prefab); 327 | }; 328 | } 329 | 330 | private void RotateCamera(UIComponent c, UIMouseEventParameter p) 331 | { 332 | m_previewRenderer.cameraRotation -= p.moveDelta.x / m_preview.width * 360f; 333 | if (m_optionPanel.m_options != null && m_optionPanel.m_options.useColorVariations) 334 | m_previewRenderer.RenderVehicle(m_optionPanel.m_options.prefab, m_previewColor); 335 | else 336 | m_previewRenderer.RenderVehicle(m_optionPanel.m_options.prefab); 337 | } 338 | 339 | private void PopulateList() 340 | { 341 | m_fastList.rowsData.Clear(); 342 | m_fastList.selectedIndex = -1; 343 | for (int i = 0; i < m_optionsList.Length; i++) 344 | { 345 | if (m_optionsList[i] != null && 346 | (m_category.selectedIndex == 0 || (int)m_optionsList[i].category == m_category.selectedIndex - 1) && 347 | (String.IsNullOrEmpty(m_search.text.Trim()) || m_optionsList[i].localizedName.ToLower().Contains(m_search.text.Trim().ToLower()))) 348 | { 349 | m_fastList.rowsData.Add(m_optionsList[i]); 350 | } 351 | } 352 | 353 | m_fastList.rowHeight = 40f; 354 | m_fastList.DisplayAt(0); 355 | m_fastList.selectedIndex = 0; 356 | 357 | m_optionPanel.isVisible = m_fastList.rowsData.m_size > 0; 358 | m_followVehicle.isVisible = m_preview.parent.isVisible = m_optionPanel.isVisible; 359 | } 360 | 361 | private void FollowNextVehicle() 362 | { 363 | Array16 vehicles = VehicleManager.instance.m_vehicles; 364 | VehicleOptions options = m_optionPanel.m_options; 365 | 366 | for (uint i = (m_seekStart + 1) % vehicles.m_size; i != m_seekStart; i = (i + 1) % vehicles.m_size) 367 | { 368 | if (vehicles.m_buffer[i].Info == m_optionPanel.m_options.prefab) 369 | { 370 | bool isSpawned = (vehicles.m_buffer[i].m_flags & Vehicle.Flags.Spawned) == Vehicle.Flags.Spawned; 371 | 372 | InstanceID instanceID = default(InstanceID); 373 | instanceID.Vehicle = (ushort)i; 374 | 375 | if (!isSpawned || instanceID.IsEmpty || !InstanceManager.IsValid(instanceID)) continue; 376 | 377 | Vector3 targetPosition; 378 | Quaternion quaternion; 379 | Vector3 vector; 380 | 381 | if (!InstanceManager.GetPosition(instanceID, out targetPosition, out quaternion, out vector)) continue; 382 | 383 | Vector3 pos = targetPosition; 384 | GameAreaManager.instance.ClampPoint(ref targetPosition); 385 | if (targetPosition != pos) continue; 386 | 387 | m_cameraController.SetTarget(instanceID, ToolsModifierControl.cameraController.transform.position, false); 388 | 389 | m_seekStart = (i + 1) % vehicles.m_size; 390 | return; 391 | } 392 | } 393 | m_seekStart = 0; 394 | } 395 | 396 | protected void OnSelectedItemChanged(UIComponent component, int i) 397 | { 398 | m_seekStart = 0; 399 | 400 | VehicleOptions options = m_fastList.rowsData[i] as VehicleOptions; 401 | 402 | m_optionPanel.Show(options); 403 | m_followVehicle.isVisible = m_preview.parent.isVisible = true; 404 | 405 | m_previewColor = options.color0; 406 | m_previewColor.a = 0; // Fixes the wrong lighting on one half of the vehicle 407 | m_previewRenderer.cameraRotation = -60;// 120f; 408 | m_previewRenderer.zoom = 3f; 409 | if (options.useColorVariations) 410 | m_previewRenderer.RenderVehicle(m_optionPanel.m_options.prefab, m_previewColor); 411 | else 412 | m_previewRenderer.RenderVehicle(m_optionPanel.m_options.prefab); 413 | } 414 | 415 | protected void OnEnableStateChanged(UIComponent component, bool state) 416 | { 417 | m_fastList.DisplayAt(m_fastList.listPosition); 418 | } 419 | 420 | public void ChangePreviewColor(Color color) 421 | { 422 | if (m_optionPanel.m_options != null) 423 | { 424 | if (m_optionPanel.m_options.useColorVariations && m_previewColor != color) 425 | { 426 | m_previewColor = color; 427 | m_previewColor.a = 0; // Fixes the wrong lighting on one half of the vehicle 428 | m_previewRenderer.RenderVehicle(m_optionPanel.m_options.prefab, m_previewColor); 429 | } 430 | else 431 | { 432 | m_previewRenderer.RenderVehicle(m_optionPanel.m_options.prefab); 433 | } 434 | } 435 | } 436 | } 437 | 438 | } 439 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/GUI/UIOptionPanel.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using ColossalFramework.UI; 3 | using ColossalFramework.Threading; 4 | 5 | using UIUtils = SamsamTS.UIUtils; 6 | 7 | namespace AdvancedVehicleOptions.GUI 8 | { 9 | public class UIOptionPanel : UIPanel 10 | { 11 | private UITextField m_maxSpeed; 12 | private UITextField m_acceleration; 13 | private UITextField m_braking; 14 | private UICheckBox m_useColors; 15 | private UIColorField m_color0; 16 | private UIColorField m_color1; 17 | private UIColorField m_color2; 18 | private UIColorField m_color3; 19 | private UITextField m_color0_hex; 20 | private UITextField m_color1_hex; 21 | private UITextField m_color2_hex; 22 | private UITextField m_color3_hex; 23 | private UICheckBox m_enabled; 24 | private UICheckBox m_addBackEngine; 25 | private UITextField m_capacity; 26 | private UIButton m_restore; 27 | private UILabel m_removeLabel; 28 | private UIButton m_clearVehicles; 29 | private UIButton m_clearParked; 30 | 31 | public VehicleOptions m_options = null; 32 | 33 | private bool m_initialized = false; 34 | 35 | public event PropertyChangedEventHandler eventEnableCheckChanged; 36 | 37 | public override void Start() 38 | { 39 | base.Start(); 40 | canFocus = true; 41 | isInteractive = true; 42 | width = 315; 43 | height = 330; 44 | 45 | SetupControls(); 46 | 47 | m_options = new VehicleOptions(); 48 | } 49 | 50 | public void Show(VehicleOptions options) 51 | { 52 | m_initialized = false; 53 | 54 | m_options = options; 55 | 56 | if (m_color0 == null) return; 57 | 58 | m_color0.relativePosition = new Vector3(13, 95 - 2); 59 | m_color1.relativePosition = new Vector3(13, 120 - 2); 60 | m_color2.relativePosition = new Vector3(158, 95 - 2); 61 | m_color3.relativePosition = new Vector3(158, 120 - 2); 62 | 63 | m_maxSpeed.text = Mathf.RoundToInt(options.maxSpeed * 5).ToString(); 64 | m_acceleration.text = options.acceleration.ToString(); 65 | m_braking.text = options.braking.ToString(); 66 | m_useColors.isChecked = options.useColorVariations; 67 | m_color0.selectedColor = options.color0; 68 | m_color1.selectedColor = options.color1; 69 | m_color2.selectedColor = options.color2; 70 | m_color3.selectedColor = options.color3; 71 | m_color0_hex.text = options.color0.ToString(); 72 | m_color1_hex.text = options.color1.ToString(); 73 | m_color2_hex.text = options.color2.ToString(); 74 | m_color3_hex.text = options.color3.ToString(); 75 | m_enabled.isChecked = options.enabled; 76 | //m_enabled.isVisible = !options.isTrailer; 77 | m_addBackEngine.isChecked = options.addBackEngine; 78 | m_addBackEngine.isVisible = options.isTrain; 79 | 80 | m_capacity.text = options.capacity.ToString(); 81 | m_capacity.parent.isVisible = options.hasCapacity; 82 | 83 | string name = options.localizedName; 84 | if (name.Length > 16) name = name.Substring(0, 16) + "..."; 85 | m_removeLabel.text = "Remove vehicles (" + name + "):"; 86 | 87 | (parent as UIMainPanel).ChangePreviewColor(m_color0.selectedColor); 88 | 89 | m_initialized = true; 90 | } 91 | 92 | private void SetupControls() 93 | { 94 | UIPanel panel = AddUIComponent(); 95 | panel.gameObject.AddComponent(); 96 | 97 | panel.backgroundSprite = "UnlockingPanel"; 98 | panel.width = width - 10; 99 | panel.height = height - 75; 100 | panel.relativePosition = new Vector3(5, 0); 101 | 102 | // Max Speed 103 | UILabel maxSpeedLabel = panel.AddUIComponent(); 104 | maxSpeedLabel.text = "Maximum speed:"; 105 | maxSpeedLabel.textScale = 0.8f; 106 | maxSpeedLabel.relativePosition = new Vector3(15, 15); 107 | 108 | m_maxSpeed = UIUtils.CreateTextField(panel); 109 | m_maxSpeed.numericalOnly = true; 110 | m_maxSpeed.width = 75; 111 | m_maxSpeed.tooltip = "Change the maximum speed of the vehicle\nPlease note that vehicles do not go beyond speed limits"; 112 | m_maxSpeed.relativePosition = new Vector3(15, 35); 113 | 114 | UILabel kmh = panel.AddUIComponent(); 115 | kmh.text = "km/h"; 116 | kmh.textScale = 0.8f; 117 | kmh.relativePosition = new Vector3(95, 40); 118 | 119 | // Acceleration 120 | UILabel accelerationLabel = panel.AddUIComponent(); 121 | accelerationLabel.text = "Acceleration/Brake:"; 122 | accelerationLabel.textScale = 0.8f; 123 | accelerationLabel.relativePosition = new Vector3(160, 15); 124 | 125 | m_acceleration = UIUtils.CreateTextField(panel); 126 | m_acceleration.numericalOnly = true; 127 | m_acceleration.allowFloats = true; 128 | m_acceleration.width = 60; 129 | m_acceleration.tooltip = "Change the vehicle acceleration factor"; 130 | m_acceleration.relativePosition = new Vector3(160, 35); 131 | 132 | // Braking 133 | m_braking = UIUtils.CreateTextField(panel); 134 | m_braking.numericalOnly = true; 135 | m_braking.allowFloats = true; 136 | m_braking.width = 60; 137 | m_braking.tooltip = "Change the vehicle braking factor"; 138 | m_braking.relativePosition = new Vector3(230, 35); 139 | 140 | // Colors 141 | m_useColors = UIUtils.CreateCheckBox(panel); 142 | m_useColors.text = "Color variations:"; 143 | m_useColors.isChecked = true; 144 | m_useColors.width = width - 40; 145 | m_useColors.tooltip = "Enable color variations\nA random color is chosen between the four following colors"; 146 | m_useColors.relativePosition = new Vector3(15, 70); 147 | 148 | m_color0 = UIUtils.CreateColorField(panel); 149 | m_color0.name = "AVO-color0"; 150 | m_color0.popupTopmostRoot = false; 151 | m_color0.relativePosition = new Vector3(13 , 95 - 2); 152 | m_color0_hex = UIUtils.CreateTextField(panel); 153 | m_color0_hex.maxLength = 6; 154 | m_color0_hex.relativePosition = new Vector3(55, 95); 155 | 156 | m_color1 = UIUtils.CreateColorField(panel); 157 | m_color1.name = "AVO-color1"; 158 | m_color1.popupTopmostRoot = false; 159 | m_color1.relativePosition = new Vector3(13, 120 - 2); 160 | m_color1_hex = UIUtils.CreateTextField(panel); 161 | m_color1_hex.maxLength = 6; 162 | m_color1_hex.relativePosition = new Vector3(55, 120); 163 | 164 | m_color2 = UIUtils.CreateColorField(panel); 165 | m_color2.name = "AVO-color2"; 166 | m_color2.popupTopmostRoot = false; 167 | m_color2.relativePosition = new Vector3(158, 95 - 2); 168 | m_color2_hex = UIUtils.CreateTextField(panel); 169 | m_color2_hex.maxLength = 6; 170 | m_color2_hex.relativePosition = new Vector3(200, 95); 171 | 172 | m_color3 = UIUtils.CreateColorField(panel); 173 | m_color3.name = "AVO-color3"; 174 | m_color3.popupTopmostRoot = false; 175 | m_color3.relativePosition = new Vector3(158, 120 - 2); 176 | m_color3_hex = UIUtils.CreateTextField(panel); 177 | m_color3_hex.maxLength = 6; 178 | m_color3_hex.relativePosition = new Vector3(200, 120); 179 | 180 | // Enable & BackEngine 181 | m_enabled = UIUtils.CreateCheckBox(panel); 182 | m_enabled.text = "Allow this vehicle to spawn"; 183 | m_enabled.isChecked = true; 184 | m_enabled.width = width - 40; 185 | m_enabled.tooltip = "Make sure you have at least one vehicle allowed to spawn for that category"; 186 | m_enabled.relativePosition = new Vector3(15, 155); ; 187 | 188 | m_addBackEngine = UIUtils.CreateCheckBox(panel); 189 | m_addBackEngine.text = "Replace last car with engine"; 190 | m_addBackEngine.isChecked = false; 191 | m_addBackEngine.width = width - 40; 192 | m_addBackEngine.tooltip = "Make the last car of this train be an engine"; 193 | m_addBackEngine.relativePosition = new Vector3(15, 175); 194 | 195 | // Capacity 196 | UIPanel capacityPanel = panel.AddUIComponent(); 197 | capacityPanel.size = Vector2.zero; 198 | capacityPanel.relativePosition = new Vector3(15, 200); 199 | 200 | UILabel capacityLabel = capacityPanel.AddUIComponent(); 201 | capacityLabel.text = "Capacity:"; 202 | capacityLabel.textScale = 0.8f; 203 | capacityLabel.relativePosition = Vector3.zero; 204 | 205 | m_capacity = UIUtils.CreateTextField(capacityPanel); 206 | m_capacity.numericalOnly = true; 207 | m_capacity.width = 110; 208 | m_capacity.tooltip = "Change the capacity of the vehicle"; 209 | m_capacity.relativePosition = new Vector3(0, 20); 210 | 211 | // Restore default 212 | m_restore = UIUtils.CreateButton(panel); 213 | m_restore.text = "Restore default"; 214 | m_restore.width = 130; 215 | m_restore.tooltip = "Restore all values to default"; 216 | m_restore.relativePosition = new Vector3(160, 215); 217 | 218 | // Remove Vehicles 219 | m_removeLabel = this.AddUIComponent(); 220 | m_removeLabel.text = "Remove vehicles:"; 221 | m_removeLabel.textScale = 0.8f; 222 | m_removeLabel.relativePosition = new Vector3(10, height - 60); 223 | 224 | m_clearVehicles = UIUtils.CreateButton(this); 225 | m_clearVehicles.text = "Driving"; 226 | m_clearVehicles.width = 90f; 227 | m_clearVehicles.tooltip = "Remove all driving vehicles of that type\nHold the SHIFT key to remove all types"; 228 | m_clearVehicles.relativePosition = new Vector3(10, height - 40); 229 | 230 | m_clearParked = UIUtils.CreateButton(this); 231 | m_clearParked.text = "Parked"; 232 | m_clearParked.width = 90f; 233 | m_clearParked.tooltip = "Remove all parked vehicles of that type\nHold the SHIFT key to remove all types"; 234 | m_clearParked.relativePosition = new Vector3(105, height - 40); 235 | 236 | panel.BringToFront(); 237 | 238 | // Event handlers 239 | m_maxSpeed.eventTextSubmitted += OnMaxSpeedSubmitted; 240 | m_acceleration.eventTextSubmitted += OnAccelerationSubmitted; 241 | m_braking.eventTextSubmitted += OnBrakingSubmitted; 242 | 243 | m_useColors.eventCheckChanged += OnCheckChanged; 244 | 245 | MouseEventHandler mousehandler = (c, p) => { if (m_initialized) (parent as UIMainPanel).ChangePreviewColor((c as UIColorField).selectedColor); }; 246 | 247 | m_color0.eventMouseEnter += mousehandler; 248 | m_color1.eventMouseEnter += mousehandler; 249 | m_color2.eventMouseEnter += mousehandler; 250 | m_color3.eventMouseEnter += mousehandler; 251 | 252 | m_color0_hex.eventMouseEnter += (c, p) => { if (m_initialized) (parent as UIMainPanel).ChangePreviewColor(m_color0.selectedColor); }; 253 | m_color1_hex.eventMouseEnter += (c, p) => { if (m_initialized) (parent as UIMainPanel).ChangePreviewColor(m_color1.selectedColor); }; 254 | m_color2_hex.eventMouseEnter += (c, p) => { if (m_initialized) (parent as UIMainPanel).ChangePreviewColor(m_color2.selectedColor); }; 255 | m_color3_hex.eventMouseEnter += (c, p) => { if (m_initialized) (parent as UIMainPanel).ChangePreviewColor(m_color3.selectedColor); }; 256 | 257 | m_color0.eventSelectedColorChanged += OnColorChanged; 258 | m_color1.eventSelectedColorChanged += OnColorChanged; 259 | m_color2.eventSelectedColorChanged += OnColorChanged; 260 | m_color3.eventSelectedColorChanged += OnColorChanged; 261 | 262 | m_color0_hex.eventTextSubmitted += OnColorHexSubmitted; 263 | m_color1_hex.eventTextSubmitted += OnColorHexSubmitted; 264 | m_color2_hex.eventTextSubmitted += OnColorHexSubmitted; 265 | m_color3_hex.eventTextSubmitted += OnColorHexSubmitted; 266 | 267 | m_enabled.eventCheckChanged += OnCheckChanged; 268 | m_addBackEngine.eventCheckChanged += OnCheckChanged; 269 | 270 | m_capacity.eventTextSubmitted += OnCapacitySubmitted; 271 | 272 | m_restore.eventClick += (c, p) => 273 | { 274 | m_initialized = false; 275 | bool isEnabled = m_options.enabled; 276 | DefaultOptions.Restore(m_options.prefab); 277 | VehicleOptions.UpdateTransfertVehicles(); 278 | 279 | VehicleOptions.prefabUpdateEngine = m_options.prefab; 280 | VehicleOptions.prefabUpdateUnits = m_options.prefab; 281 | new EnumerableActionThread(VehicleOptions.UpdateBackEngines); 282 | new EnumerableActionThread(VehicleOptions.UpdateCapacityUnits); 283 | 284 | Show(m_options); 285 | 286 | if (m_options.enabled != isEnabled) 287 | eventEnableCheckChanged(this, m_options.enabled); 288 | }; 289 | 290 | m_clearVehicles.eventClick += OnClearVehicleClicked; 291 | m_clearParked.eventClick += OnClearVehicleClicked; 292 | } 293 | 294 | protected void OnCheckChanged(UIComponent component, bool state) 295 | { 296 | if (!m_initialized || m_options == null) return; 297 | m_initialized = false; 298 | 299 | if (component == m_enabled) 300 | { 301 | if (m_options.isTrailer) 302 | { 303 | VehicleOptions engine = m_options.engine; 304 | 305 | if (engine.enabled != state) 306 | { 307 | engine.enabled = state; 308 | VehicleOptions.UpdateTransfertVehicles(); 309 | eventEnableCheckChanged(this, state); 310 | } 311 | } 312 | else 313 | { 314 | if (m_options.enabled != state) 315 | { 316 | m_options.enabled = state; 317 | VehicleOptions.UpdateTransfertVehicles(); 318 | eventEnableCheckChanged(this, state); 319 | } 320 | } 321 | 322 | if (!state && !AdvancedVehicleOptions.CheckServiceValidity(m_options.category)) 323 | { 324 | GUI.UIWarningModal.instance.message = UIMainPanel.categoryList[(int)m_options.category + 1] + " may not work correctly because no vehicles are allowed to spawn."; 325 | UIView.PushModal(GUI.UIWarningModal.instance); 326 | GUI.UIWarningModal.instance.Show(true); 327 | } 328 | } 329 | else if (component == m_addBackEngine && m_options.addBackEngine != state) 330 | { 331 | m_options.addBackEngine = state; 332 | if (m_options.addBackEngine == state) 333 | { 334 | VehicleOptions.prefabUpdateEngine = m_options.prefab; 335 | new EnumerableActionThread(VehicleOptions.UpdateBackEngines); 336 | } 337 | } 338 | else if (component == m_useColors && m_options.useColorVariations != state) 339 | { 340 | m_options.useColorVariations = state; 341 | (parent as UIMainPanel).ChangePreviewColor(m_color0.selectedColor); 342 | } 343 | 344 | m_initialized = true; 345 | } 346 | 347 | protected void OnMaxSpeedSubmitted(UIComponent component, string text) 348 | { 349 | if (!m_initialized || m_options == null) return; 350 | m_initialized = false; 351 | 352 | m_options.maxSpeed = float.Parse(text) / 5f; 353 | 354 | m_initialized = true; 355 | } 356 | 357 | protected void OnAccelerationSubmitted(UIComponent component, string text) 358 | { 359 | if (!m_initialized || m_options == null) return; 360 | m_initialized = false; 361 | 362 | m_options.acceleration = float.Parse(text); 363 | 364 | m_initialized = true; 365 | } 366 | 367 | protected void OnBrakingSubmitted(UIComponent component, string text) 368 | { 369 | if (!m_initialized || m_options == null) return; 370 | m_initialized = false; 371 | 372 | m_options.braking = float.Parse(text); 373 | 374 | m_initialized = true; 375 | } 376 | 377 | protected void OnCapacitySubmitted(UIComponent component, string text) 378 | { 379 | if (!m_initialized || m_options == null) return; 380 | m_initialized = false; 381 | 382 | m_options.capacity = int.Parse(text); 383 | VehicleOptions.prefabUpdateUnits = m_options.prefab; 384 | new EnumerableActionThread(VehicleOptions.UpdateCapacityUnits); 385 | 386 | m_initialized = true; 387 | } 388 | 389 | protected void OnColorChanged(UIComponent component, Color color) 390 | { 391 | if (!m_initialized || m_options == null) return; 392 | m_initialized = false; 393 | 394 | (parent as UIMainPanel).ChangePreviewColor(color); 395 | 396 | m_options.color0 = m_color0.selectedColor; 397 | m_options.color1 = m_color1.selectedColor; 398 | m_options.color2 = m_color2.selectedColor; 399 | m_options.color3 = m_color3.selectedColor; 400 | 401 | m_color0_hex.text = m_options.color0.ToString(); 402 | m_color1_hex.text = m_options.color1.ToString(); 403 | m_color2_hex.text = m_options.color2.ToString(); 404 | m_color3_hex.text = m_options.color3.ToString(); 405 | 406 | m_initialized = true; 407 | } 408 | 409 | protected void OnColorHexSubmitted(UIComponent component, string text) 410 | { 411 | if (!m_initialized || m_options == null) return; 412 | m_initialized = false; 413 | 414 | // Is text a valid color? 415 | if(text != "000000" && new HexaColor(text).ToString() == "000000") 416 | { 417 | m_color0_hex.text = m_options.color0.ToString(); 418 | m_color1_hex.text = m_options.color1.ToString(); 419 | m_color2_hex.text = m_options.color2.ToString(); 420 | m_color3_hex.text = m_options.color3.ToString(); 421 | 422 | m_initialized = true; 423 | return; 424 | } 425 | 426 | m_options.color0 = new HexaColor(m_color0_hex.text); 427 | m_options.color1 = new HexaColor(m_color1_hex.text); 428 | m_options.color2 = new HexaColor(m_color2_hex.text); 429 | m_options.color3 = new HexaColor(m_color3_hex.text); 430 | 431 | m_color0_hex.text = m_options.color0.ToString(); 432 | m_color1_hex.text = m_options.color1.ToString(); 433 | m_color2_hex.text = m_options.color2.ToString(); 434 | m_color3_hex.text = m_options.color3.ToString(); 435 | 436 | m_color0.selectedColor = m_options.color0; 437 | m_color1.selectedColor = m_options.color1; 438 | m_color2.selectedColor = m_options.color2; 439 | m_color3.selectedColor = m_options.color3; 440 | 441 | (parent as UIMainPanel).ChangePreviewColor(color); 442 | 443 | m_initialized = true; 444 | } 445 | 446 | protected void OnClearVehicleClicked(UIComponent component, UIMouseEventParameter p) 447 | { 448 | if (m_options == null) return; 449 | 450 | if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) 451 | AdvancedVehicleOptions.ClearVehicles(null, component == m_clearParked); 452 | else 453 | AdvancedVehicleOptions.ClearVehicles(m_options, component == m_clearParked); 454 | } 455 | } 456 | 457 | } 458 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/GUI/UITitleBar.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using ColossalFramework.UI; 3 | 4 | namespace AdvancedVehicleOptions.GUI 5 | { 6 | public class UITitleBar : UIPanel 7 | { 8 | private UISprite m_icon; 9 | private UILabel m_title; 10 | private UIButton m_close; 11 | private UIDragHandle m_drag; 12 | 13 | public bool isModal = false; 14 | 15 | public string iconSprite 16 | { 17 | get { return m_icon.spriteName; } 18 | set 19 | { 20 | if (m_icon == null) SetupControls(); 21 | m_icon.spriteName = value; 22 | 23 | if (m_icon.spriteInfo != null) 24 | { 25 | SamsamTS.UIUtils.ResizeIcon(m_icon, new Vector2(30, 30)); 26 | m_icon.relativePosition = new Vector3(10, 5); 27 | } 28 | } 29 | } 30 | 31 | public UIButton closeButton 32 | { 33 | get { return m_close; } 34 | } 35 | 36 | public string title 37 | { 38 | get { return m_title.text; } 39 | set 40 | { 41 | if (m_title == null) SetupControls(); 42 | m_title.text = value; 43 | } 44 | } 45 | 46 | private void SetupControls() 47 | { 48 | width = parent.width; 49 | height = 40; 50 | isVisible = true; 51 | canFocus = true; 52 | isInteractive = true; 53 | relativePosition = Vector3.zero; 54 | 55 | m_icon = AddUIComponent(); 56 | m_icon.spriteName = iconSprite; 57 | m_icon.relativePosition = new Vector3(10, 5); 58 | 59 | m_title = AddUIComponent(); 60 | m_title.relativePosition = new Vector3(50, 13); 61 | m_title.text = title; 62 | 63 | m_close = AddUIComponent(); 64 | m_close.relativePosition = new Vector3(width - 35, 2); 65 | m_close.normalBgSprite = "buttonclose"; 66 | m_close.hoveredBgSprite = "buttonclosehover"; 67 | m_close.pressedBgSprite = "buttonclosepressed"; 68 | m_close.eventClick += (component, param) => 69 | { 70 | if (isModal) 71 | UIView.PopModal(); 72 | parent.Hide(); 73 | }; 74 | 75 | m_drag = AddUIComponent(); 76 | m_drag.width = width - 50; 77 | m_drag.height = height; 78 | m_drag.relativePosition = Vector3.zero; 79 | m_drag.target = parent; 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /AdvancedVehicleOptions/GUI/UIUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | using UnityEngine; 4 | using ColossalFramework.UI; 5 | 6 | namespace SamsamTS 7 | { 8 | public class UIUtils 9 | { 10 | // Figuring all this was a pain (no documentation whatsoever) 11 | // So if your are using it for your mod consider thanking me (SamsamTS) 12 | // Extended Public Transport UI's code helped me a lot so thanks a lot AcidFire 13 | 14 | public static UIButton CreateButton(UIComponent parent) 15 | { 16 | UIButton button = (UIButton)parent.AddUIComponent(); 17 | 18 | button.atlas = GetAtlas("Ingame"); 19 | button.size = new Vector2(90f, 30f); 20 | button.textScale = 0.8f; 21 | button.normalBgSprite = "ButtonMenu"; 22 | button.hoveredBgSprite = "ButtonMenuHovered"; 23 | button.pressedBgSprite = "ButtonMenuPressed"; 24 | button.canFocus = false; 25 | 26 | return button; 27 | } 28 | 29 | public static UICheckBox CreateCheckBox(UIComponent parent) 30 | { 31 | UICheckBox checkBox = (UICheckBox)parent.AddUIComponent(); 32 | 33 | checkBox.width = 300f; 34 | checkBox.height = 20f; 35 | checkBox.clipChildren = true; 36 | 37 | UISprite sprite = checkBox.AddUIComponent(); 38 | sprite.atlas = GetAtlas("Ingame"); 39 | sprite.spriteName = "ToggleBase"; 40 | sprite.size = new Vector2(16f, 16f); 41 | sprite.relativePosition = Vector3.zero; 42 | 43 | checkBox.checkedBoxObject = sprite.AddUIComponent(); 44 | ((UISprite)checkBox.checkedBoxObject).atlas = GetAtlas("Ingame"); 45 | ((UISprite)checkBox.checkedBoxObject).spriteName = "ToggleBaseFocused"; 46 | checkBox.checkedBoxObject.size = new Vector2(16f, 16f); 47 | checkBox.checkedBoxObject.relativePosition = Vector3.zero; 48 | 49 | checkBox.label = checkBox.AddUIComponent(); 50 | checkBox.label.text = " "; 51 | checkBox.label.textScale = 0.8f; 52 | checkBox.label.relativePosition = new Vector3(22f, 2f); 53 | 54 | return checkBox; 55 | } 56 | 57 | public static UITextField CreateTextField(UIComponent parent) 58 | { 59 | UITextField textField = parent.AddUIComponent(); 60 | 61 | textField.atlas = GetAtlas("Ingame"); 62 | textField.size = new Vector2(90f, 20f); 63 | textField.padding = new RectOffset(6, 6, 3, 3); 64 | textField.builtinKeyNavigation = true; 65 | textField.isInteractive = true; 66 | textField.readOnly = false; 67 | textField.horizontalAlignment = UIHorizontalAlignment.Center; 68 | textField.selectionSprite = "EmptySprite"; 69 | textField.selectionBackgroundColor = new Color32(0, 172, 234, 255); 70 | textField.normalBgSprite = "TextFieldPanelHovered"; 71 | textField.disabledBgSprite = "TextFieldPanelHovered"; 72 | textField.textColor = new Color32(0, 0, 0, 255); 73 | textField.disabledTextColor = new Color32(80, 80, 80, 128); 74 | textField.color = new Color32(255, 255, 255, 255); 75 | 76 | return textField; 77 | } 78 | 79 | public static UIDropDown CreateDropDown(UIComponent parent) 80 | { 81 | UIDropDown dropDown = parent.AddUIComponent(); 82 | 83 | dropDown.atlas = GetAtlas("Ingame"); 84 | dropDown.size = new Vector2(90f, 30f); 85 | dropDown.listBackground = "GenericPanelLight"; 86 | dropDown.itemHeight = 30; 87 | dropDown.itemHover = "ListItemHover"; 88 | dropDown.itemHighlight = "ListItemHighlight"; 89 | dropDown.normalBgSprite = "ButtonMenu"; 90 | dropDown.disabledBgSprite = "ButtonMenuDisabled"; 91 | dropDown.hoveredBgSprite = "ButtonMenuHovered"; 92 | dropDown.focusedBgSprite = "ButtonMenu"; 93 | dropDown.listWidth = 90; 94 | dropDown.listHeight = 500; 95 | dropDown.foregroundSpriteMode = UIForegroundSpriteMode.Stretch; 96 | dropDown.popupColor = new Color32(45, 52, 61, 255); 97 | dropDown.popupTextColor = new Color32(170, 170, 170, 255); 98 | dropDown.zOrder = 1; 99 | dropDown.textScale = 0.8f; 100 | dropDown.verticalAlignment = UIVerticalAlignment.Middle; 101 | dropDown.horizontalAlignment = UIHorizontalAlignment.Left; 102 | dropDown.selectedIndex = 0; 103 | dropDown.textFieldPadding = new RectOffset(8, 0, 8, 0); 104 | dropDown.itemPadding = new RectOffset(14, 0, 8, 0); 105 | 106 | UIButton button = dropDown.AddUIComponent(); 107 | dropDown.triggerButton = button; 108 | button.atlas = GetAtlas("Ingame"); 109 | button.text = ""; 110 | button.size = dropDown.size; 111 | button.relativePosition = new Vector3(0f, 0f); 112 | button.textVerticalAlignment = UIVerticalAlignment.Middle; 113 | button.textHorizontalAlignment = UIHorizontalAlignment.Left; 114 | button.normalFgSprite = "IconDownArrow"; 115 | button.hoveredFgSprite = "IconDownArrowHovered"; 116 | button.pressedFgSprite = "IconDownArrowPressed"; 117 | button.focusedFgSprite = "IconDownArrowFocused"; 118 | button.disabledFgSprite = "IconDownArrowDisabled"; 119 | button.foregroundSpriteMode = UIForegroundSpriteMode.Fill; 120 | button.horizontalAlignment = UIHorizontalAlignment.Right; 121 | button.verticalAlignment = UIVerticalAlignment.Middle; 122 | button.zOrder = 0; 123 | button.textScale = 0.8f; 124 | 125 | dropDown.eventSizeChanged += new PropertyChangedEventHandler((c, t) => 126 | { 127 | button.size = t; dropDown.listWidth = (int)t.x; 128 | }); 129 | 130 | return dropDown; 131 | } 132 | 133 | private static UIColorField _colorFIeldTemplate; 134 | 135 | public static UIColorField CreateColorField(UIComponent parent) 136 | { 137 | // Creating a ColorField from scratch is tricky. Cloning an existing one instead. 138 | 139 | if (_colorFIeldTemplate == null) 140 | { 141 | // Get the LineTemplate (PublicTransportDetailPanel) 142 | UIComponent template = UITemplateManager.Get("LineTemplate"); 143 | if (template == null) return null; 144 | 145 | // Extract the ColorField 146 | _colorFIeldTemplate = template.Find("LineColor"); 147 | if (_colorFIeldTemplate == null) return null; 148 | } 149 | 150 | UIColorField colorField = UnityEngine.Object.Instantiate(_colorFIeldTemplate.gameObject).GetComponent(); 151 | parent.AttachUIComponent(colorField.gameObject); 152 | 153 | colorField.size = new Vector2(40f, 26f); 154 | colorField.pickerPosition = UIColorField.ColorPickerPosition.LeftAbove; 155 | 156 | return colorField; 157 | } 158 | 159 | public static void ResizeIcon(UISprite icon, Vector2 maxSize) 160 | { 161 | icon.width = icon.spriteInfo.width; 162 | icon.height = icon.spriteInfo.height; 163 | 164 | if (icon.height == 0) return; 165 | 166 | float ratio = icon.width / icon.height; 167 | 168 | if (icon.width > maxSize.x) 169 | { 170 | icon.width = maxSize.x; 171 | icon.height = maxSize.x / ratio; 172 | } 173 | 174 | if (icon.height > maxSize.y) 175 | { 176 | icon.height = maxSize.y; 177 | icon.width = maxSize.y * ratio; 178 | } 179 | } 180 | 181 | private static Dictionary _atlases; 182 | 183 | public static UITextureAtlas GetAtlas(string name) 184 | { 185 | if (_atlases == null) 186 | { 187 | _atlases = new Dictionary(); 188 | 189 | UITextureAtlas[] atlases = Resources.FindObjectsOfTypeAll(typeof(UITextureAtlas)) as UITextureAtlas[]; 190 | for (int i = 0; i < atlases.Length; i++) 191 | { 192 | if (!_atlases.ContainsKey(atlases[i].name)) 193 | _atlases.Add(atlases[i].name, atlases[i]); 194 | } 195 | } 196 | 197 | return _atlases[name]; 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/GUI/UIVehicleItem.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using ColossalFramework.UI; 3 | using ColossalFramework.PlatformServices; 4 | 5 | using UIUtils = SamsamTS.UIUtils; 6 | 7 | namespace AdvancedVehicleOptions.GUI 8 | { 9 | public class UIVehicleItem : UIPanel, IUIFastListRow 10 | { 11 | private UISprite m_icon; 12 | private UISprite m_disabled; 13 | private UILabel m_name; 14 | private UIPanel m_background; 15 | private UISprite m_steamIcon; 16 | 17 | private VehicleOptions m_options; 18 | 19 | public VehicleOptions options 20 | { 21 | get { return m_options; } 22 | set { m_options = value; } 23 | } 24 | 25 | public UIPanel background 26 | { 27 | get 28 | { 29 | if (m_background == null) 30 | { 31 | m_background = AddUIComponent(); 32 | m_background.width = width; 33 | m_background.height = 40; 34 | m_background.relativePosition = Vector2.zero; 35 | 36 | m_background.zOrder = 0; 37 | } 38 | 39 | return m_background; 40 | } 41 | } 42 | 43 | public override void Start() 44 | { 45 | base.Start(); 46 | 47 | isVisible = true; 48 | canFocus = true; 49 | isInteractive = true; 50 | width = parent.width; 51 | height = 40; 52 | 53 | m_icon = AddUIComponent(); 54 | 55 | m_disabled = AddUIComponent(); 56 | m_disabled.spriteName = "Niet"; 57 | m_disabled.size = m_disabled.spriteInfo.pixelSize; 58 | UIUtils.ResizeIcon(m_disabled, new Vector2(32, 32)); 59 | m_disabled.relativePosition = new Vector3(10, Mathf.Floor((height - m_disabled.height) / 2)); 60 | 61 | m_name = AddUIComponent(); 62 | m_name.textScale = 0.8f; 63 | m_name.relativePosition = new Vector3(55, 13); 64 | 65 | m_steamIcon = AddUIComponent(); 66 | m_steamIcon.spriteName = "SteamWorkshop"; 67 | m_steamIcon.isVisible = false; 68 | m_steamIcon.relativePosition = new Vector3(width - 45, 12.5f); 69 | 70 | UIUtils.ResizeIcon(m_steamIcon, new Vector2(25, 25)); 71 | 72 | if (PlatformService.IsOverlayEnabled()) 73 | { 74 | m_steamIcon.eventClick += (c, p) => 75 | { 76 | p.Use(); 77 | PlatformService.ActivateGameOverlayToWorkshopItem(new PublishedFileId(ulong.Parse(m_options.steamID))); 78 | }; 79 | } 80 | } 81 | 82 | protected override void OnClick(UIMouseEventParameter p) 83 | { 84 | base.OnClick(p); 85 | 86 | m_name.textColor = new Color32(255, 255, 255, 255); 87 | } 88 | 89 | public void Display(object data, bool isRowOdd) 90 | { 91 | m_options = data as VehicleOptions; 92 | 93 | if (m_icon == null || m_options == null) return; 94 | 95 | m_icon.spriteName = UIMainPanel.vehicleIconList[(int)m_options.category]; 96 | m_icon.size = m_icon.spriteInfo.pixelSize; 97 | UIUtils.ResizeIcon(m_icon, new Vector2(32, 32)); 98 | m_icon.relativePosition = new Vector3(10, Mathf.Floor((height - m_icon.height) / 2)); 99 | 100 | m_name.text = m_options.localizedName; 101 | 102 | m_disabled.isVisible = !options.enabled; 103 | m_name.textColor = options.enabled ? new Color32(255, 255, 255, 255) : new Color32(128, 128, 128, 255); 104 | 105 | m_steamIcon.tooltip = m_options.steamID; 106 | m_steamIcon.isVisible = m_options.steamID != null; 107 | 108 | if (isRowOdd) 109 | { 110 | background.backgroundSprite = "UnlockingItemBackground"; 111 | background.color = new Color32(0, 0, 0, 128); 112 | } 113 | else 114 | { 115 | background.backgroundSprite = null; 116 | } 117 | } 118 | 119 | public void Select(bool isRowOdd) 120 | { 121 | if (m_icon == null || m_options == null) return; 122 | 123 | m_name.textColor = new Color32(255, 255, 255, 255); 124 | 125 | background.backgroundSprite = "ListItemHighlight"; 126 | background.color = new Color32(255, 255, 255, 255); 127 | } 128 | 129 | public void Deselect(bool isRowOdd) 130 | { 131 | if (m_icon == null || m_options == null) return; 132 | 133 | m_name.textColor = options.enabled ? new Color32(255, 255, 255, 255) : new Color32(128, 128, 128, 255); 134 | 135 | if (isRowOdd) 136 | { 137 | background.backgroundSprite = "UnlockingItemBackground"; 138 | background.color = new Color32(0, 0, 0, 128); 139 | } 140 | else 141 | { 142 | background.backgroundSprite = null; 143 | } 144 | } 145 | 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/GUI/UIWarningModal.cs: -------------------------------------------------------------------------------- 1 | using ColossalFramework; 2 | using ColossalFramework.UI; 3 | using UnityEngine; 4 | 5 | using UIUtils = SamsamTS.UIUtils; 6 | 7 | namespace AdvancedVehicleOptions.GUI 8 | { 9 | public class UIWarningModal : UIPanel 10 | { 11 | private UITitleBar m_title; 12 | private UISprite m_warningIcon; 13 | private UILabel m_messageLabel; 14 | private UIButton m_ok; 15 | 16 | private string m_message; 17 | 18 | private static UIWarningModal _instance; 19 | 20 | public static UIWarningModal instance 21 | { 22 | get 23 | { 24 | if (_instance == null) 25 | { 26 | _instance = UIView.GetAView().AddUIComponent(typeof(UIWarningModal)) as UIWarningModal; 27 | } 28 | return _instance; 29 | } 30 | } 31 | 32 | public string message 33 | { 34 | get { return m_message; } 35 | set 36 | { 37 | m_message = value; 38 | if(m_messageLabel != null) 39 | { 40 | m_messageLabel.text = m_message; 41 | m_messageLabel.autoHeight = true; 42 | m_messageLabel.width = width - m_warningIcon.width - 15; 43 | 44 | height = 200; 45 | 46 | if ((m_title.height + 40 + m_messageLabel.height) > (height - 40)) 47 | { 48 | height = m_title.height + 40 + m_messageLabel.height; 49 | } 50 | 51 | m_warningIcon.relativePosition = new Vector3(5, m_title.height + (height - m_title.height - 40 - m_warningIcon.height) / 2); 52 | m_ok.relativePosition = new Vector3((width - m_ok.width) / 2, height - m_ok.height - 5); 53 | m_messageLabel.relativePosition = new Vector3(m_warningIcon.width + 10, m_title.height + (height - m_title.height - 40 - m_messageLabel.height) / 2); 54 | } 55 | } 56 | } 57 | 58 | public override void Start() 59 | { 60 | base.Start(); 61 | 62 | backgroundSprite = "UnlockingPanel2"; 63 | isVisible = false; 64 | canFocus = true; 65 | isInteractive = true; 66 | clipChildren = true; 67 | width = 600; 68 | height = 200; 69 | relativePosition = new Vector3(Mathf.Floor((GetUIView().fixedWidth - width) / 2), Mathf.Floor((GetUIView().fixedHeight - height) / 2)); 70 | 71 | // Title Bar 72 | m_title = AddUIComponent(); 73 | m_title.title = "Advanced Vehicle Options - Warning"; 74 | m_title.iconSprite = "IconCitizenVehicle"; 75 | m_title.isModal = true; 76 | 77 | // Icon 78 | m_warningIcon = AddUIComponent(); 79 | m_warningIcon.size = new Vector2(90, 90); 80 | m_warningIcon.spriteName = "IconWarning"; 81 | 82 | // Message 83 | m_messageLabel = AddUIComponent(); 84 | m_messageLabel.wordWrap = true; 85 | 86 | // Ok 87 | m_ok = UIUtils.CreateButton(this); 88 | m_ok.text = "OK"; 89 | 90 | m_ok.eventClick += (c, p) => 91 | { 92 | UIView.PopModal(); 93 | Hide(); 94 | }; 95 | 96 | message = m_message; 97 | 98 | isVisible = true; 99 | } 100 | 101 | protected override void OnVisibilityChanged() 102 | { 103 | base.OnVisibilityChanged(); 104 | 105 | UIComponent modalEffect = GetUIView().panelsLibraryModalEffect; 106 | 107 | if (isVisible) 108 | { 109 | Focus(); 110 | if (modalEffect != null) 111 | { 112 | modalEffect.Show(false); 113 | ValueAnimator.Animate("NewThemeModalEffect", delegate(float val) 114 | { 115 | modalEffect.opacity = val; 116 | }, new AnimatedFloat(0f, 1f, 0.7f, EasingType.CubicEaseOut)); 117 | } 118 | } 119 | else if (modalEffect != null) 120 | { 121 | ValueAnimator.Animate("NewThemeModalEffect", delegate(float val) 122 | { 123 | modalEffect.opacity = val; 124 | }, new AnimatedFloat(1f, 0f, 0.7f, EasingType.CubicEaseOut), delegate 125 | { 126 | modalEffect.Hide(); 127 | }); 128 | } 129 | } 130 | 131 | protected override void OnKeyDown(UIKeyEventParameter p) 132 | { 133 | if (Input.GetKey(KeyCode.Escape) || Input.GetKey(KeyCode.Return)) 134 | { 135 | p.Use(); 136 | UIView.PopModal(); 137 | Hide(); 138 | } 139 | 140 | base.OnKeyDown(p); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/PreviewRenderer.cs: -------------------------------------------------------------------------------- 1 | using ColossalFramework; 2 | using UnityEngine; 3 | 4 | namespace AdvancedVehicleOptions 5 | { 6 | public class PreviewRenderer : MonoBehaviour 7 | { 8 | private Camera m_camera; 9 | private float m_rotation = 120f; 10 | private float m_zoom = 3f; 11 | 12 | public PreviewRenderer() 13 | { 14 | m_camera = new GameObject("Camera").AddComponent(); 15 | m_camera.transform.SetParent(transform); 16 | m_camera.backgroundColor = new Color(0, 0, 0, 0); 17 | m_camera.fieldOfView = 30f; 18 | m_camera.nearClipPlane = 1f; 19 | m_camera.farClipPlane = 1000f; 20 | m_camera.allowHDR = true; 21 | m_camera.enabled = false; 22 | m_camera.targetTexture = new RenderTexture(512, 512, 24, RenderTextureFormat.ARGB32); 23 | m_camera.pixelRect = new Rect(0f, 0f, 512, 512); 24 | m_camera.clearFlags = CameraClearFlags.Color; 25 | } 26 | 27 | public Vector2 size 28 | { 29 | get { return new Vector2(m_camera.targetTexture.width, m_camera.targetTexture.height); } 30 | set 31 | { 32 | if (size != value) 33 | { 34 | m_camera.targetTexture = new RenderTexture((int)value.x, (int)value.y, 24, RenderTextureFormat.ARGB32); 35 | m_camera.pixelRect = new Rect(0f, 0f, value.x, value.y); 36 | } 37 | } 38 | } 39 | 40 | public RenderTexture texture 41 | { 42 | get { return m_camera.targetTexture; } 43 | } 44 | 45 | public float cameraRotation 46 | { 47 | get { return m_rotation; } 48 | set { m_rotation = value % 360f; } 49 | } 50 | 51 | public float zoom 52 | { 53 | get { return m_zoom; } 54 | set 55 | { 56 | m_zoom = Mathf.Clamp(value, 0.5f, 5f); 57 | } 58 | } 59 | 60 | public void RenderVehicle(VehicleInfo info) 61 | { 62 | RenderVehicle(info, info.m_color0, false); 63 | } 64 | 65 | public void RenderVehicle(VehicleInfo info, Color color, bool useColor = true) 66 | { 67 | InfoManager infoManager = Singleton.instance; 68 | InfoManager.InfoMode currentMod = infoManager.CurrentMode; 69 | InfoManager.SubInfoMode currentSubMod = infoManager.CurrentSubMode; ; 70 | infoManager.SetCurrentMode(InfoManager.InfoMode.None, InfoManager.SubInfoMode.Default); 71 | infoManager.UpdateInfoMode(); 72 | 73 | Light sunLight = DayNightProperties.instance.sunLightSource; 74 | float lightIntensity = sunLight.intensity; 75 | Color lightColor = sunLight.color; 76 | Vector3 lightAngles = sunLight.transform.eulerAngles; 77 | 78 | sunLight.intensity = 2f; 79 | sunLight.color = Color.white; 80 | sunLight.transform.eulerAngles = new Vector3(50, 180, 70); 81 | 82 | Light mainLight = RenderManager.instance.MainLight; 83 | RenderManager.instance.MainLight = sunLight; 84 | 85 | if(mainLight == DayNightProperties.instance.moonLightSource) 86 | { 87 | DayNightProperties.instance.sunLightSource.enabled = true; 88 | DayNightProperties.instance.moonLightSource.enabled = false; 89 | } 90 | 91 | Vector3 one = Vector3.one; 92 | 93 | float magnitude = info.m_mesh.bounds.extents.magnitude; 94 | float num = magnitude + 16f; 95 | float num2 = magnitude * m_zoom; 96 | 97 | m_camera.transform.position = Vector3.forward * num2; 98 | m_camera.transform.rotation = Quaternion.AngleAxis(180, Vector3.up); 99 | m_camera.nearClipPlane = Mathf.Max(num2 - num * 1.5f, 0.01f); 100 | m_camera.farClipPlane = num2 + num * 1.5f; 101 | 102 | Quaternion rotation = Quaternion.Euler(20f, 0f, 0f) * Quaternion.Euler(0f, m_rotation, 0f); 103 | Vector3 position = rotation * -info.m_mesh.bounds.center; 104 | 105 | Vector3 swayPosition = Vector3.zero; 106 | 107 | VehicleManager instance = Singleton.instance; 108 | Matrix4x4 matrixBody = Matrix4x4.TRS(position, rotation, Vector3.one); 109 | Matrix4x4 matrixTyre = info.m_vehicleAI.CalculateTyreMatrix(Vehicle.Flags.Created, ref position, ref rotation, ref one, ref matrixBody); 110 | 111 | MaterialPropertyBlock materialBlock = instance.m_materialBlock; 112 | materialBlock.Clear(); 113 | materialBlock.SetMatrix(instance.ID_TyreMatrix, matrixTyre); 114 | materialBlock.SetVector(instance.ID_TyrePosition, Vector3.zero); 115 | materialBlock.SetVector(instance.ID_LightState, Vector3.zero); 116 | if(useColor) materialBlock.SetColor(instance.ID_Color, color); 117 | 118 | instance.m_drawCallData.m_defaultCalls = instance.m_drawCallData.m_defaultCalls + 1; 119 | 120 | info.m_material.SetVectorArray(instance.ID_TyreLocation, info.m_generatedInfo.m_tyres); 121 | Graphics.DrawMesh(info.m_mesh, matrixBody, info.m_material, 0, m_camera, 0, materialBlock, true, true); 122 | 123 | m_camera.RenderWithShader(info.m_material.shader, ""); 124 | 125 | sunLight.intensity = lightIntensity; 126 | sunLight.color = lightColor; 127 | sunLight.transform.eulerAngles = lightAngles; 128 | 129 | RenderManager.instance.MainLight = mainLight; 130 | 131 | if (mainLight == DayNightProperties.instance.moonLightSource) 132 | { 133 | DayNightProperties.instance.sunLightSource.enabled = false; 134 | DayNightProperties.instance.moonLightSource.enabled = true; 135 | } 136 | 137 | infoManager.SetCurrentMode(currentMod, currentSubMod); 138 | infoManager.UpdateInfoMode(); 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /AdvancedVehicleOptions/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AdvancedVehicleOptions")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("AdvancedVehicleOptions")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f34328ec-b2f4-4984-8e88-dfe73a4d3af7")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.*")] 36 | //[assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /AdvancedVehicleOptions/SerializableDataExtension.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using ColossalFramework.IO; 4 | using ICities; 5 | 6 | namespace AdvancedVehicleOptions 7 | { 8 | public class SerializableDataExtension : SerializableDataExtensionBase 9 | { 10 | private const string ID = "AVO"; 11 | private const int VERSION = 1; 12 | 13 | public override void OnLoadData() 14 | { 15 | if (ToolManager.instance.m_properties.m_mode != ItemClass.Availability.Game) 16 | { 17 | return; 18 | } 19 | 20 | // Storing default values ASAP (before any mods have the time to change values) 21 | DefaultOptions.StoreAll(); 22 | 23 | if (!serializableDataManager.EnumerateData().Contains(ID)) 24 | { 25 | return; 26 | } 27 | var data = serializableDataManager.LoadData(ID); 28 | using (var ms = new MemoryStream(data)) 29 | { 30 | AdvancedVehicleOptions.config.data = DataSerializer.Deserialize(ms, DataSerializer.Mode.Memory).data; 31 | } 32 | } 33 | 34 | public override void OnSaveData() 35 | { 36 | if (ToolManager.instance.m_properties.m_mode != ItemClass.Availability.Game || 37 | AdvancedVehicleOptions.config == null) 38 | { 39 | return; 40 | } 41 | using (var ms = new MemoryStream()) 42 | { 43 | DataSerializer.Serialize(ms, DataSerializer.Mode.Memory, VERSION, AdvancedVehicleOptions.config); 44 | var data = ms.ToArray(); 45 | serializableDataManager.SaveData(ID, data); 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /AdvancedVehicleOptions/VehicleOptions.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using ColossalFramework.Threading; 3 | using ColossalFramework.Globalization; 4 | 5 | using System; 6 | using System.Text; 7 | using System.Xml.Serialization; 8 | using System.Collections; 9 | using System.Collections.Generic; 10 | using System.ComponentModel; 11 | using System.Reflection; 12 | 13 | namespace AdvancedVehicleOptions 14 | { 15 | public class VehicleOptions : IComparable 16 | { 17 | public enum Category 18 | { 19 | None = -1, 20 | Citizen, 21 | Bicycle, 22 | Forestry, 23 | Farming, 24 | Ore, 25 | Oil, 26 | IndustryGeneric, 27 | Police, 28 | Prison, 29 | FireSafety, 30 | Disaster, 31 | Healthcare, 32 | Deathcare, 33 | Garbage, 34 | Maintenance, 35 | TransportTaxi, 36 | TransportBus, 37 | TransportMetro, 38 | Tram, 39 | Monorail, 40 | CableCar, 41 | CargoTrain, 42 | TransportTrain, 43 | CargoShip, 44 | TransportShip, 45 | TransportPlane, 46 | TransportTours, 47 | Monument, 48 | Natural 49 | } 50 | 51 | #region serialized 52 | [XmlAttribute("name")] 53 | public string name 54 | { 55 | get { return m_prefab.name; } 56 | set 57 | { 58 | if(value == null) return; 59 | 60 | VehicleInfo prefab = PrefabCollection.FindLoaded(value); 61 | if (prefab == null) 62 | DebugUtils.Log("Couldn't find " + value); 63 | else 64 | SetPrefab(prefab); 65 | } 66 | } 67 | // enabled 68 | public bool enabled 69 | { 70 | get 71 | { 72 | if (m_engine != null) 73 | return m_engine.m_placementStyle != ItemClass.Placement.Manual; 74 | 75 | return m_prefab.m_placementStyle != ItemClass.Placement.Manual; 76 | } 77 | set 78 | { 79 | if (m_prefab == null || enabled == value) return; 80 | 81 | if (value) 82 | { 83 | ItemClass.Placement placement = DefaultOptions.GetPlacementStyle(m_prefab); 84 | 85 | m_prefab.m_placementStyle = (int)placement != -1 ? placement : m_placementStyle; 86 | 87 | if (hasTrailer) 88 | { 89 | for (uint i = 0; i < m_prefab.m_trailers.Length; i++) 90 | { 91 | if (m_prefab.m_trailers[i].m_info == null) continue; 92 | 93 | placement = DefaultOptions.GetPlacementStyle(m_prefab.m_trailers[i].m_info); 94 | m_prefab.m_trailers[i].m_info.m_placementStyle = (int)placement != -1 ? placement : m_placementStyle; 95 | } 96 | } 97 | } 98 | else 99 | { 100 | m_prefab.m_placementStyle = ItemClass.Placement.Manual; 101 | 102 | if (hasTrailer) 103 | { 104 | for (uint i = 0; i < m_prefab.m_trailers.Length; i++) 105 | { 106 | if (m_prefab.m_trailers[i].m_info == null) continue; 107 | 108 | m_prefab.m_trailers[i].m_info.m_placementStyle = ItemClass.Placement.Manual; 109 | } 110 | } 111 | } 112 | } 113 | } 114 | // addBackEngine 115 | public bool addBackEngine 116 | { 117 | get 118 | { 119 | if (!hasTrailer) return false; 120 | return m_prefab.m_trailers[m_prefab.m_trailers.Length - 1].m_info == m_prefab; 121 | } 122 | set 123 | { 124 | if (m_prefab == null || !isTrain) return; 125 | 126 | VehicleInfo newTrailer = value ? m_prefab : DefaultOptions.GetLastTrailer(m_prefab); 127 | int last = m_prefab.m_trailers.Length - 1; 128 | 129 | if (m_prefab.m_trailers[last].m_info == newTrailer || newTrailer == null) return; 130 | 131 | m_prefab.m_trailers[last].m_info = newTrailer; 132 | 133 | if (value) 134 | m_prefab.m_trailers[last].m_invertProbability = m_prefab.m_trailers[last].m_probability; 135 | else 136 | m_prefab.m_trailers[last].m_invertProbability = DefaultOptions.GetProbability(prefab); 137 | } 138 | } 139 | // maxSpeed 140 | public float maxSpeed 141 | { 142 | get { return m_prefab.m_maxSpeed; } 143 | set 144 | { 145 | if (m_prefab == null || value <= 0) return; 146 | m_prefab.m_maxSpeed = value; 147 | } 148 | } 149 | // acceleration 150 | public float acceleration 151 | { 152 | get { return m_prefab.m_acceleration; } 153 | set 154 | { 155 | if (m_prefab == null || value <= 0) return; 156 | m_prefab.m_acceleration = value; 157 | } 158 | } 159 | // braking 160 | public float braking 161 | { 162 | get { return m_prefab.m_braking; } 163 | set 164 | { 165 | if (m_prefab == null || value <= 0) return; 166 | m_prefab.m_braking = value; 167 | } 168 | } 169 | // useColorVariations 170 | public bool useColorVariations 171 | { 172 | get { return m_prefab.m_useColorVariations; } 173 | set 174 | { 175 | if (m_prefab == null) return; 176 | m_prefab.m_useColorVariations = value; 177 | } 178 | } 179 | // colors 180 | public HexaColor color0 181 | { 182 | get { return m_prefab.m_color0; } 183 | set 184 | { 185 | if (m_prefab == null) return; 186 | m_prefab.m_color0 = value; 187 | } 188 | } 189 | public HexaColor color1 190 | { 191 | get { return m_prefab.m_color1; } 192 | set 193 | { 194 | if (m_prefab == null) return; 195 | m_prefab.m_color1 = value; 196 | } 197 | } 198 | public HexaColor color2 199 | { 200 | get { return m_prefab.m_color2; } 201 | set 202 | { 203 | if (m_prefab == null) return; 204 | m_prefab.m_color2 = value; 205 | } 206 | } 207 | public HexaColor color3 208 | { 209 | get { return m_prefab.m_color3; } 210 | set 211 | { 212 | if (m_prefab == null) return; 213 | m_prefab.m_color3 = value; 214 | } 215 | } 216 | // capacity 217 | [DefaultValue(-1)] 218 | public int capacity 219 | { 220 | get 221 | { 222 | VehicleAI ai; 223 | 224 | ai = m_prefab.m_vehicleAI as AmbulanceAI; 225 | if (ai != null) return ((AmbulanceAI)ai).m_patientCapacity; 226 | 227 | ai = m_prefab.m_vehicleAI as BusAI; 228 | if (ai != null) return ((BusAI)ai).m_passengerCapacity; 229 | 230 | ai = m_prefab.m_vehicleAI as CargoShipAI; 231 | if (ai != null) return ((CargoShipAI)ai).m_cargoCapacity; 232 | 233 | ai = m_prefab.m_vehicleAI as CargoTrainAI; 234 | if (ai != null) return ((CargoTrainAI)ai).m_cargoCapacity; 235 | 236 | ai = m_prefab.m_vehicleAI as CargoTruckAI; 237 | if (ai != null) return ((CargoTruckAI)ai).m_cargoCapacity; 238 | 239 | ai = m_prefab.m_vehicleAI as GarbageTruckAI; 240 | if (ai != null) return ((GarbageTruckAI)ai).m_cargoCapacity; 241 | 242 | ai = m_prefab.m_vehicleAI as FireTruckAI; 243 | if (ai != null) return ((FireTruckAI)ai).m_fireFightingRate; 244 | 245 | ai = m_prefab.m_vehicleAI as HearseAI; 246 | if (ai != null) return ((HearseAI)ai).m_corpseCapacity; 247 | 248 | ai = m_prefab.m_vehicleAI as PassengerPlaneAI; 249 | if (ai != null) return ((PassengerPlaneAI)ai).m_passengerCapacity; 250 | 251 | ai = m_prefab.m_vehicleAI as PassengerShipAI; 252 | if (ai != null) return ((PassengerShipAI)ai).m_passengerCapacity; 253 | 254 | ai = m_prefab.m_vehicleAI as PassengerTrainAI; 255 | if (ai != null) return ((PassengerTrainAI)ai).m_passengerCapacity; 256 | 257 | ai = m_prefab.m_vehicleAI as PoliceCarAI; 258 | if (ai != null) return ((PoliceCarAI)ai).m_crimeCapacity; 259 | 260 | ai = m_prefab.m_vehicleAI as TaxiAI; 261 | if (ai != null) return ((TaxiAI)ai).m_passengerCapacity; 262 | 263 | ai = m_prefab.m_vehicleAI as TramAI; 264 | if (ai != null) return ((TramAI)ai).m_passengerCapacity; 265 | 266 | ai = m_prefab.m_vehicleAI as MaintenanceTruckAI; 267 | if (ai != null) return ((MaintenanceTruckAI)ai).m_maintenanceCapacity; 268 | 269 | ai = m_prefab.m_vehicleAI as SnowTruckAI; 270 | if (ai != null) return ((SnowTruckAI)ai).m_cargoCapacity; 271 | 272 | ai = m_prefab.m_vehicleAI as CableCarAI; 273 | if (ai != null) return ((CableCarAI)ai).m_passengerCapacity; 274 | 275 | ai = m_prefab.m_vehicleAI as PassengerFerryAI; 276 | if (ai != null) return ((PassengerFerryAI)ai).m_passengerCapacity; 277 | 278 | ai = m_prefab.m_vehicleAI as PassengerBlimpAI; 279 | if (ai != null) return ((PassengerBlimpAI)ai).m_passengerCapacity; 280 | 281 | return -1; 282 | } 283 | set 284 | { 285 | if (m_prefab == null || capacity == -1 || value <= 0) return; 286 | 287 | VehicleAI ai; 288 | 289 | ai = m_prefab.m_vehicleAI as AmbulanceAI; 290 | if (ai != null) { ((AmbulanceAI)ai).m_patientCapacity = value; return; } 291 | 292 | ai = m_prefab.m_vehicleAI as BusAI; 293 | if (ai != null) { ((BusAI)ai).m_passengerCapacity = value; return; } 294 | 295 | ai = m_prefab.m_vehicleAI as CargoShipAI; 296 | if (ai != null) { ((CargoShipAI)ai).m_cargoCapacity = value; return; } 297 | 298 | ai = m_prefab.m_vehicleAI as CargoTrainAI; 299 | if (ai != null) { ((CargoTrainAI)ai).m_cargoCapacity = value; return; } 300 | 301 | ai = m_prefab.m_vehicleAI as CargoTruckAI; 302 | if (ai != null) { ((CargoTruckAI)ai).m_cargoCapacity = value; return; } 303 | 304 | ai = m_prefab.m_vehicleAI as GarbageTruckAI; 305 | if (ai != null) { ((GarbageTruckAI)ai).m_cargoCapacity = value; return; } 306 | 307 | ai = m_prefab.m_vehicleAI as FireTruckAI; 308 | if (ai != null) { ((FireTruckAI)ai).m_fireFightingRate = value; return; } 309 | 310 | ai = m_prefab.m_vehicleAI as HearseAI; 311 | if (ai != null) { ((HearseAI)ai).m_corpseCapacity = value; return; } 312 | 313 | ai = m_prefab.m_vehicleAI as PassengerPlaneAI; 314 | if (ai != null) { ((PassengerPlaneAI)ai).m_passengerCapacity = value; return; } 315 | 316 | ai = m_prefab.m_vehicleAI as PassengerShipAI; 317 | if (ai != null) { ((PassengerShipAI)ai).m_passengerCapacity = value; return; } 318 | 319 | ai = m_prefab.m_vehicleAI as PassengerTrainAI; 320 | if (ai != null) { ((PassengerTrainAI)ai).m_passengerCapacity = value; return; } 321 | 322 | ai = m_prefab.m_vehicleAI as PoliceCarAI; 323 | if (ai != null) { ((PoliceCarAI)ai).m_crimeCapacity = value; return; } 324 | 325 | ai = m_prefab.m_vehicleAI as TaxiAI; 326 | if (ai != null) { ((TaxiAI)ai).m_passengerCapacity = value; return; } 327 | 328 | ai = m_prefab.m_vehicleAI as TramAI; 329 | if (ai != null) { ((TramAI)ai).m_passengerCapacity = value; return; } 330 | 331 | ai = m_prefab.m_vehicleAI as MaintenanceTruckAI; 332 | if (ai != null) { ((MaintenanceTruckAI)ai).m_maintenanceCapacity = value; return; } 333 | 334 | ai = m_prefab.m_vehicleAI as SnowTruckAI; 335 | if (ai != null) { ((SnowTruckAI)ai).m_cargoCapacity = value; return; } 336 | 337 | ai = m_prefab.m_vehicleAI as CableCarAI; 338 | if (ai != null) { ((CableCarAI)ai).m_passengerCapacity = value; return; } 339 | 340 | ai = m_prefab.m_vehicleAI as PassengerFerryAI; 341 | if (ai != null) { ((PassengerFerryAI)ai).m_passengerCapacity = value; return; } 342 | 343 | ai = m_prefab.m_vehicleAI as PassengerBlimpAI; 344 | if (ai != null) { ((PassengerBlimpAI)ai).m_passengerCapacity = value; return; } 345 | } 346 | } 347 | #endregion 348 | 349 | public static VehicleInfo prefabUpdateUnits = null; 350 | public static VehicleInfo prefabUpdateEngine = null; 351 | 352 | private VehicleInfo m_prefab = null; 353 | private VehicleInfo m_engine = null; 354 | private ItemClass.Placement m_placementStyle; 355 | private string m_localizedName; 356 | private bool m_hasCapacity = false; 357 | private string m_steamID; 358 | 359 | public VehicleOptions() { } 360 | 361 | public VehicleOptions(VehicleInfo prefab) 362 | { 363 | SetPrefab(prefab); 364 | } 365 | 366 | public VehicleInfo prefab 367 | { 368 | get { return m_prefab; } 369 | } 370 | 371 | public VehicleOptions engine 372 | { 373 | get { return new VehicleOptions(m_engine); } 374 | } 375 | 376 | public ItemClass.Placement placementStyle 377 | { 378 | get { return m_placementStyle; } 379 | } 380 | 381 | public bool hasCapacity 382 | { 383 | get { return m_hasCapacity; } 384 | } 385 | 386 | public bool hasTrailer 387 | { 388 | get { return m_prefab.m_trailers != null && m_prefab.m_trailers.Length > 0; } 389 | } 390 | 391 | public bool isTrain 392 | { 393 | get { return hasTrailer && (m_prefab.m_vehicleType == VehicleInfo.VehicleType.Train || m_prefab.m_vehicleType == VehicleInfo.VehicleType.Tram || m_prefab.m_vehicleType == VehicleInfo.VehicleType.Metro || m_prefab.m_vehicleType == VehicleInfo.VehicleType.Monorail); } 394 | } 395 | 396 | public bool isTrailer 397 | { 398 | get { return m_engine != null; } 399 | } 400 | 401 | public string localizedName 402 | { 403 | get { return m_localizedName; } 404 | } 405 | 406 | public Category category 407 | { 408 | get 409 | { 410 | if (prefab == null) return Category.None; 411 | 412 | switch (prefab.m_class.m_service) 413 | { 414 | case ItemClass.Service.PoliceDepartment: 415 | if (prefab.m_class.m_level == ItemClass.Level.Level4) 416 | return Category.Prison; 417 | else 418 | return Category.Police; 419 | case ItemClass.Service.FireDepartment: 420 | return Category.FireSafety; 421 | case ItemClass.Service.HealthCare: 422 | if (prefab.m_class.m_level == ItemClass.Level.Level1) 423 | return Category.Healthcare; 424 | else 425 | return Category.Deathcare; 426 | case ItemClass.Service.Garbage: 427 | return Category.Garbage; 428 | case ItemClass.Service.Water: 429 | case ItemClass.Service.Road: 430 | return Category.Maintenance; 431 | case ItemClass.Service.Disaster: 432 | return Category.Disaster; 433 | case ItemClass.Service.Monument: 434 | return Category.Monument; 435 | case ItemClass.Service.Natural: 436 | return Category.Natural; 437 | } 438 | 439 | switch (prefab.m_class.m_subService) 440 | { 441 | case ItemClass.SubService.PublicTransportBus: 442 | return Category.TransportBus; 443 | case ItemClass.SubService.PublicTransportMetro: 444 | return Category.TransportMetro; 445 | case ItemClass.SubService.PublicTransportTrain: 446 | if (prefab.m_class.m_level == ItemClass.Level.Level1) 447 | return Category.TransportTrain; 448 | else 449 | return Category.CargoTrain; 450 | case ItemClass.SubService.PublicTransportShip: 451 | if (prefab.m_class.m_level == ItemClass.Level.Level1 || 452 | prefab.m_class.m_level == ItemClass.Level.Level2) 453 | return Category.TransportShip; 454 | else 455 | return Category.CargoShip; 456 | case ItemClass.SubService.PublicTransportTaxi: 457 | return Category.TransportTaxi; 458 | case ItemClass.SubService.PublicTransportTours: 459 | return Category.TransportTours; 460 | case ItemClass.SubService.PublicTransportPlane: 461 | return Category.TransportPlane; 462 | case ItemClass.SubService.IndustrialForestry: 463 | return Category.Forestry; 464 | case ItemClass.SubService.IndustrialFarming: 465 | return Category.Farming; 466 | case ItemClass.SubService.IndustrialOre: 467 | return Category.Ore; 468 | case ItemClass.SubService.IndustrialOil: 469 | return Category.Oil; 470 | case ItemClass.SubService.IndustrialGeneric: 471 | return Category.IndustryGeneric; 472 | case ItemClass.SubService.PublicTransportTram: 473 | return Category.Tram; 474 | case ItemClass.SubService.PublicTransportMonorail: 475 | return Category.Monorail; 476 | case ItemClass.SubService.PublicTransportCableCar: 477 | return Category.CableCar; 478 | case ItemClass.SubService.ResidentialHigh: 479 | return Category.Bicycle; 480 | case ItemClass.SubService.BeautificationParks: 481 | return Category.Maintenance; 482 | } 483 | 484 | return Category.Citizen; 485 | } 486 | } 487 | 488 | public string steamID 489 | { 490 | get 491 | { 492 | if (m_steamID != null) return m_steamID; 493 | 494 | if (name.Contains(".")) 495 | { 496 | m_steamID = name.Substring(0, name.IndexOf(".")); 497 | 498 | ulong result; 499 | if (!ulong.TryParse(m_steamID, out result) || result == 0) 500 | m_steamID = null; 501 | } 502 | 503 | return m_steamID; 504 | } 505 | } 506 | 507 | private static int GetUnitsCapacity(VehicleAI vehicleAI) 508 | { 509 | VehicleAI ai; 510 | 511 | ai = vehicleAI as AmbulanceAI; 512 | if (ai != null) return ((AmbulanceAI)ai).m_patientCapacity + ((AmbulanceAI)ai).m_paramedicCount; 513 | 514 | ai = vehicleAI as BusAI; 515 | if (ai != null) return ((BusAI)ai).m_passengerCapacity; 516 | 517 | /*ai = prefab.m_vehicleAI as FireTruckAI; 518 | if (ai != null) return ((FireTruckAI)ai).m_firemanCount;*/ 519 | 520 | ai = vehicleAI as HearseAI; 521 | if (ai != null) return ((HearseAI)ai).m_corpseCapacity + ((HearseAI)ai).m_driverCount; 522 | 523 | ai = vehicleAI as PassengerPlaneAI; 524 | if (ai != null) return ((PassengerPlaneAI)ai).m_passengerCapacity; 525 | 526 | ai = vehicleAI as PassengerShipAI; 527 | if (ai != null) return ((PassengerShipAI)ai).m_passengerCapacity; 528 | 529 | ai = vehicleAI as PassengerTrainAI; 530 | if (ai != null) return ((PassengerTrainAI)ai).m_passengerCapacity; 531 | 532 | ai = vehicleAI as TramAI; 533 | if (ai != null) return ((TramAI)ai).m_passengerCapacity; 534 | 535 | ai = vehicleAI as CableCarAI; 536 | if (ai != null) return ((CableCarAI)ai).m_passengerCapacity; 537 | 538 | ai = vehicleAI as PassengerFerryAI; 539 | if (ai != null) return ((PassengerFerryAI)ai).m_passengerCapacity; 540 | 541 | ai = vehicleAI as PassengerBlimpAI; 542 | if (ai != null) return ((PassengerBlimpAI)ai).m_passengerCapacity; 543 | 544 | /*ai = prefab.m_vehicleAI as PoliceCarAI; 545 | if (ai != null) return ((PoliceCarAI)ai).m_policeCount;*/ 546 | 547 | return -1; 548 | } 549 | 550 | private static int GetTotalUnitGroups(uint unitID) 551 | { 552 | int count = 0; 553 | while (unitID != 0) 554 | { 555 | CitizenUnit unit = CitizenManager.instance.m_units.m_buffer[unitID]; 556 | unitID = unit.m_nextUnit; 557 | count++; 558 | } 559 | return count; 560 | } 561 | 562 | public static IEnumerator UpdateCapacityUnits(ThreadBase t) 563 | { 564 | int count = 0; 565 | Array16 vehicles = VehicleManager.instance.m_vehicles; 566 | for (int i = 0; i < vehicles.m_size; i++) 567 | { 568 | if ((vehicles.m_buffer[i].m_flags & Vehicle.Flags.Spawned) == Vehicle.Flags.Spawned) 569 | { 570 | if (prefabUpdateUnits == null || vehicles.m_buffer[i].Info == prefabUpdateUnits) 571 | { 572 | int capacity = GetUnitsCapacity(vehicles.m_buffer[i].Info.m_vehicleAI); 573 | 574 | if (capacity != -1) 575 | { 576 | CitizenUnit[] units = CitizenManager.instance.m_units.m_buffer; 577 | uint unit = vehicles.m_buffer[i].m_citizenUnits; 578 | 579 | int currentUnitCount = GetTotalUnitGroups(unit); 580 | int newUnitCount = Mathf.CeilToInt(capacity / 5f); 581 | 582 | // Capacity reduced 583 | if (newUnitCount < currentUnitCount) 584 | { 585 | // Get the first unit to remove 586 | uint n = unit; 587 | for (int j = 1; j < newUnitCount; j++) 588 | n = units[n].m_nextUnit; 589 | // Releasing units excess 590 | CitizenManager.instance.ReleaseUnits(units[n].m_nextUnit); 591 | units[n].m_nextUnit = 0; 592 | 593 | count++; 594 | } 595 | // Capacity increased 596 | else if (newUnitCount > currentUnitCount) 597 | { 598 | // Get the last unit 599 | uint n = unit; 600 | while (units[n].m_nextUnit != 0) 601 | n = units[n].m_nextUnit; 602 | 603 | // Creating missing units 604 | int newCapacity = capacity - currentUnitCount * 5; 605 | CitizenManager.instance.CreateUnits(out units[n].m_nextUnit, ref SimulationManager.instance.m_randomizer, 0, (ushort)i, 0, 0, 0, newCapacity, 0); 606 | 607 | count++; 608 | } 609 | } 610 | } 611 | } 612 | if (i % 256 == 255) yield return null; 613 | } 614 | prefabUpdateUnits = null; 615 | 616 | if (count > 0) 617 | { 618 | DebugUtils.Log("Modified capacity of " + count + " vehicle(s). Total unit count: " + CitizenManager.instance.m_unitCount + "/" + CitizenManager.MAX_UNIT_COUNT); 619 | } 620 | } 621 | 622 | public static IEnumerator UpdateBackEngines(ThreadBase t) 623 | { 624 | Array16 vehicles = VehicleManager.instance.m_vehicles; 625 | for (ushort i = 0; i < vehicles.m_size; i++) 626 | { 627 | try 628 | { 629 | VehicleInfo prefab = vehicles.m_buffer[i].Info; 630 | if (prefab != null) 631 | { 632 | bool isTrain = prefab.m_vehicleType == VehicleInfo.VehicleType.Train || prefab.m_vehicleType == VehicleInfo.VehicleType.Tram || prefab.m_vehicleType == VehicleInfo.VehicleType.Metro || prefab.m_vehicleType == VehicleInfo.VehicleType.Monorail; 633 | bool isLeading = vehicles.m_buffer[i].m_leadingVehicle == 0 && prefab.m_trailers != null && prefab.m_trailers.Length > 0; 634 | if ((prefabUpdateEngine == null || prefab == prefabUpdateEngine) && isTrain && isLeading && prefab.m_trailers[prefab.m_trailers.Length - 1].m_info != null) 635 | { 636 | ushort last = vehicles.m_buffer[i].GetLastVehicle((ushort)i); 637 | ushort oldPrefabID = vehicles.m_buffer[last].m_infoIndex; 638 | ushort newPrefabID = (ushort)prefab.m_trailers[prefab.m_trailers.Length - 1].m_info.m_prefabDataIndex; 639 | if (oldPrefabID != newPrefabID) 640 | { 641 | vehicles.m_buffer[last].m_infoIndex = newPrefabID; 642 | vehicles.m_buffer[last].m_flags = vehicles.m_buffer[vehicles.m_buffer[last].m_leadingVehicle].m_flags; 643 | 644 | if (prefab.m_trailers[prefab.m_trailers.Length - 1].m_info == prefab) 645 | vehicles.m_buffer[last].m_flags |= Vehicle.Flags.Inverted; 646 | } 647 | } 648 | } 649 | 650 | } 651 | catch (Exception e) 652 | { 653 | DebugUtils.Log("Couldn't update back engine :"); 654 | Debug.LogError(e); 655 | } 656 | if (i % 256 == 255) yield return null; 657 | } 658 | 659 | prefabUpdateEngine = null; 660 | } 661 | 662 | public static void UpdateTransfertVehicles() 663 | { 664 | try 665 | { 666 | typeof(VehicleManager).GetField("m_vehiclesRefreshed", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(VehicleManager.instance, false); 667 | VehicleManager.instance.RefreshTransferVehicles(); 668 | } 669 | catch(Exception e) 670 | { 671 | DebugUtils.Log("Couldn't update transfer vehicles :"); 672 | Debug.LogError(e); 673 | } 674 | } 675 | 676 | public void SetPrefab(VehicleInfo prefab) 677 | { 678 | if (prefab == null) return; 679 | 680 | m_prefab = prefab; 681 | m_placementStyle = prefab.m_placementStyle; 682 | 683 | m_engine = GetEngine(); 684 | if (m_engine != null) 685 | { 686 | m_localizedName = Locale.GetUnchecked("VEHICLE_TITLE", m_engine.name) + " (Trailer)"; 687 | } 688 | else 689 | { 690 | m_localizedName = Locale.GetUnchecked("VEHICLE_TITLE", prefab.name); 691 | if (m_localizedName.StartsWith("VEHICLE_TITLE")) 692 | { 693 | m_localizedName = prefab.name; 694 | // Removes the steam ID and trailing _Data from the name 695 | m_localizedName = m_localizedName.Substring(m_localizedName.IndexOf('.') + 1).Replace("_Data", ""); 696 | } 697 | } 698 | 699 | m_hasCapacity = capacity != -1; 700 | } 701 | 702 | public int CompareTo(object o) 703 | { 704 | VehicleOptions options = o as VehicleOptions; 705 | if (options == null) return 1; 706 | 707 | 708 | int delta = category - options.category; 709 | if (delta == 0) 710 | { 711 | if (steamID != null && options.steamID == null) 712 | delta = 1; 713 | else if (steamID == null && options.steamID != null) 714 | delta = -1; 715 | } 716 | if (delta == 0) return localizedName.CompareTo(options.localizedName); 717 | 718 | return delta; 719 | } 720 | 721 | private static Dictionary _trailerEngines = null; 722 | public static void Clear() 723 | { 724 | if (_trailerEngines != null) 725 | { 726 | _trailerEngines.Clear(); 727 | _trailerEngines = null; 728 | } 729 | } 730 | 731 | private VehicleInfo GetEngine() 732 | { 733 | if (_trailerEngines == null) 734 | { 735 | _trailerEngines = new Dictionary(); 736 | 737 | for (uint i = 0; i < PrefabCollection.PrefabCount(); i++) 738 | { 739 | try 740 | { 741 | VehicleInfo prefab = PrefabCollection.GetPrefab(i); 742 | 743 | if (prefab == null || prefab.m_trailers == null || prefab.m_trailers.Length == 0) continue; 744 | 745 | for (int j = 0; j < prefab.m_trailers.Length; j++) 746 | { 747 | if (prefab.m_trailers[j].m_info != null && prefab.m_trailers[j].m_info != prefab && !_trailerEngines.ContainsKey(prefab.m_trailers[j].m_info)) 748 | _trailerEngines.Add(prefab.m_trailers[j].m_info, prefab); 749 | } 750 | } 751 | catch (Exception e) 752 | { 753 | DebugUtils.LogException(e); 754 | } 755 | } 756 | } 757 | 758 | if (_trailerEngines.ContainsKey(m_prefab)) 759 | return _trailerEngines[m_prefab]; 760 | 761 | return null; 762 | } 763 | } 764 | 765 | public struct HexaColor : IXmlSerializable 766 | { 767 | private float r, g, b; 768 | 769 | public string Value 770 | { 771 | get 772 | { 773 | return ToString(); 774 | } 775 | 776 | set 777 | { 778 | value = value.Trim().Replace("#", ""); 779 | 780 | if (value.Length != 6) return; 781 | 782 | try 783 | { 784 | r = int.Parse(value.Substring(0, 2), System.Globalization.NumberStyles.HexNumber) / 255f; 785 | g = int.Parse(value.Substring(2, 2), System.Globalization.NumberStyles.HexNumber) / 255f; 786 | b = int.Parse(value.Substring(4, 2), System.Globalization.NumberStyles.HexNumber) / 255f; 787 | } 788 | catch 789 | { 790 | r = g = b = 0; 791 | } 792 | } 793 | } 794 | 795 | public HexaColor(string value) 796 | { 797 | try 798 | { 799 | r = int.Parse(value.Substring(0, 2), System.Globalization.NumberStyles.HexNumber) / 255f; 800 | g = int.Parse(value.Substring(2, 2), System.Globalization.NumberStyles.HexNumber) / 255f; 801 | b = int.Parse(value.Substring(4, 2), System.Globalization.NumberStyles.HexNumber) / 255f; 802 | } 803 | catch 804 | { 805 | r = g = b = 0; 806 | } 807 | } 808 | 809 | public System.Xml.Schema.XmlSchema GetSchema() 810 | { 811 | return null; 812 | } 813 | 814 | public void ReadXml(System.Xml.XmlReader reader) 815 | { 816 | Value = reader.ReadString(); 817 | } 818 | 819 | public void WriteXml(System.Xml.XmlWriter writer) 820 | { 821 | writer.WriteString(Value); 822 | } 823 | 824 | public override string ToString() 825 | { 826 | StringBuilder s = new StringBuilder(); 827 | 828 | s.Append(((int)(255 * r)).ToString("X2")); 829 | s.Append(((int)(255 * g)).ToString("X2")); 830 | s.Append(((int)(255 * b)).ToString("X2")); 831 | 832 | return s.ToString(); 833 | } 834 | 835 | public static implicit operator HexaColor(Color c) 836 | { 837 | HexaColor temp = new HexaColor(); 838 | 839 | temp.r = c.r; 840 | temp.g = c.g; 841 | temp.b = c.b; 842 | 843 | return temp; 844 | } 845 | 846 | public static implicit operator Color(HexaColor c) 847 | { 848 | return new Color(c.r, c.g, c.b, 1f); 849 | } 850 | } 851 | 852 | public class DefaultOptions 853 | { 854 | private static Dictionary m_prefabs = new Dictionary(); 855 | private static Dictionary m_default = new Dictionary(); 856 | private static Dictionary m_modded = new Dictionary(); 857 | 858 | public static ItemClass.Placement GetPlacementStyle(VehicleInfo prefab) 859 | { 860 | if (m_default.ContainsKey(prefab.name)) 861 | return m_default[prefab.name].m_placementStyle; 862 | return (ItemClass.Placement)(-1); 863 | } 864 | 865 | public static VehicleInfo GetLastTrailer(VehicleInfo prefab) 866 | { 867 | if (m_default.ContainsKey(prefab.name)) 868 | return m_default[prefab.name].m_lastTrailer; 869 | return null; 870 | } 871 | 872 | public static int GetProbability(VehicleInfo prefab) 873 | { 874 | if (m_default.ContainsKey(prefab.name)) 875 | return m_default[prefab.name].m_probability; 876 | return 0; 877 | } 878 | 879 | public static void Store(VehicleInfo prefab) 880 | { 881 | if (prefab != null && !m_default.ContainsKey(prefab.name)) 882 | { 883 | m_default.Add(prefab.name, new DefaultOptions(prefab)); 884 | } 885 | } 886 | 887 | public static void StoreAll() 888 | { 889 | DefaultOptions.Clear(); 890 | for (uint i = 0; i < PrefabCollection.PrefabCount(); i++) 891 | DefaultOptions.Store(PrefabCollection.GetPrefab(i)); 892 | 893 | DebugUtils.Log("Default values stored"); 894 | } 895 | 896 | public static void StoreAllModded() 897 | { 898 | if (m_modded.Count > 0) return; 899 | 900 | for (uint i = 0; i < PrefabCollection.PrefabCount(); i++) 901 | { 902 | VehicleInfo prefab = PrefabCollection.GetPrefab(i); 903 | 904 | if (prefab != null && !m_modded.ContainsKey(prefab.name)) 905 | m_modded.Add(prefab.name, new DefaultOptions(prefab)); 906 | } 907 | } 908 | 909 | public static void BuildVehicleInfoDictionary() 910 | { 911 | m_prefabs.Clear(); 912 | 913 | for (uint i = 0; i < PrefabCollection.PrefabCount(); i++) 914 | { 915 | VehicleInfo prefab = PrefabCollection.GetPrefab(i); 916 | 917 | if (prefab != null) 918 | m_prefabs[prefab.name] = prefab; 919 | } 920 | } 921 | 922 | public static void CheckForConflicts() 923 | { 924 | StringBuilder conflicts = new StringBuilder(); 925 | 926 | foreach (string name in m_default.Keys) 927 | { 928 | VehicleOptions options = new VehicleOptions(); 929 | options.SetPrefab(m_prefabs[name]); 930 | 931 | DefaultOptions modded = m_modded[name]; 932 | DefaultOptions stored = m_default[name]; 933 | 934 | StringBuilder details = new StringBuilder(); 935 | 936 | if (modded.m_enabled != stored.m_enabled && options.enabled == stored.m_enabled) 937 | { 938 | options.enabled = modded.m_enabled; 939 | details.Append("enabled, "); 940 | } 941 | if (modded.m_addBackEngine != stored.m_addBackEngine && options.addBackEngine == stored.m_addBackEngine) 942 | { 943 | options.addBackEngine = modded.m_addBackEngine; 944 | details.Append("back engine, "); 945 | } 946 | if (modded.m_maxSpeed != stored.m_maxSpeed && options.maxSpeed == stored.m_maxSpeed) 947 | { 948 | options.maxSpeed = modded.m_maxSpeed; 949 | details.Append("max speed, "); 950 | } 951 | if (modded.m_acceleration != stored.m_acceleration && options.acceleration == stored.m_acceleration) 952 | { 953 | options.acceleration = modded.m_acceleration; 954 | details.Append("acceleration, "); 955 | } 956 | if (modded.m_braking != stored.m_braking && options.braking == stored.m_braking) 957 | { 958 | options.braking = modded.m_braking; 959 | details.Append("braking, "); 960 | } 961 | if (modded.m_capacity != stored.m_capacity && options.capacity == stored.m_capacity) 962 | { 963 | options.capacity = modded.m_capacity; 964 | details.Append("capacity, "); 965 | } 966 | 967 | if (details.Length > 0) 968 | { 969 | details.Length -= 2; 970 | conflicts.AppendLine(options.name + ": " + details); 971 | } 972 | } 973 | 974 | if (conflicts.Length > 0) 975 | { 976 | VehicleOptions.UpdateTransfertVehicles(); 977 | DebugUtils.Log("Conflicts detected (this message is harmless):" + Environment.NewLine + conflicts); 978 | } 979 | } 980 | 981 | public static void Restore(VehicleInfo prefab) 982 | { 983 | if (prefab == null) return; 984 | 985 | VehicleOptions options = new VehicleOptions(); 986 | options.SetPrefab(prefab); 987 | 988 | DefaultOptions stored = m_default[prefab.name]; 989 | if (stored == null) return; 990 | 991 | options.enabled = stored.m_enabled; 992 | options.addBackEngine = stored.m_addBackEngine; 993 | options.maxSpeed = stored.m_maxSpeed; 994 | options.acceleration = stored.m_acceleration; 995 | options.braking = stored.m_braking; 996 | options.useColorVariations = stored.m_useColorVariations; 997 | options.color0 = stored.m_color0; 998 | options.color1 = stored.m_color1; 999 | options.color2 = stored.m_color2; 1000 | options.color3 = stored.m_color3; 1001 | options.capacity = stored.m_capacity; 1002 | prefab.m_placementStyle = stored.m_placementStyle; 1003 | } 1004 | 1005 | public static void RestoreAll() 1006 | { 1007 | foreach (string name in m_default.Keys) 1008 | { 1009 | Restore(m_prefabs[name]); 1010 | } 1011 | VehicleOptions.UpdateTransfertVehicles(); 1012 | } 1013 | 1014 | public static void Clear() 1015 | { 1016 | m_default.Clear(); 1017 | m_modded.Clear(); 1018 | } 1019 | 1020 | private DefaultOptions(VehicleInfo prefab) 1021 | { 1022 | VehicleOptions options = new VehicleOptions(); 1023 | options.SetPrefab(prefab); 1024 | 1025 | m_enabled = options.enabled; 1026 | m_addBackEngine = options.addBackEngine; 1027 | m_maxSpeed = options.maxSpeed; 1028 | m_acceleration = options.acceleration; 1029 | m_braking = options.braking; 1030 | m_useColorVariations = options.useColorVariations; 1031 | m_color0 = options.color0; 1032 | m_color1 = options.color1; 1033 | m_color2 = options.color2; 1034 | m_color3 = options.color3; 1035 | m_capacity = options.capacity; 1036 | m_placementStyle = options.placementStyle; 1037 | 1038 | if (prefab.m_trailers != null && prefab.m_trailers.Length > 0) 1039 | { 1040 | m_lastTrailer = prefab.m_trailers[prefab.m_trailers.Length - 1].m_info; 1041 | m_probability = prefab.m_trailers[prefab.m_trailers.Length - 1].m_invertProbability; 1042 | } 1043 | } 1044 | 1045 | private bool m_enabled; 1046 | private bool m_addBackEngine; 1047 | private float m_maxSpeed; 1048 | private float m_acceleration; 1049 | private float m_braking; 1050 | private bool m_useColorVariations; 1051 | private HexaColor m_color0; 1052 | private HexaColor m_color1; 1053 | private HexaColor m_color2; 1054 | private HexaColor m_color3; 1055 | private int m_capacity; 1056 | private ItemClass.Placement m_placementStyle; 1057 | private VehicleInfo m_lastTrailer; 1058 | private int m_probability; 1059 | } 1060 | } 1061 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS-AdvancedVehicleOptions 2 | --------------------------------------------------------------------------------