├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── VStancer.Client ├── Data │ ├── WheelData.cs │ └── WheelModData.cs ├── Globals.cs ├── Preset │ ├── IPresetsCollection.cs │ ├── KvpPresetsCollection.cs │ └── VStancerPreset.cs ├── Scripts │ ├── ClientPresetsScript.cs │ ├── MainScript.cs │ ├── WheelModScript.cs │ └── WheelScript.cs ├── UI │ ├── ClientPresetsMenu.cs │ ├── MainMenu.cs │ ├── MenuUtilities.cs │ ├── WheelMenu.cs │ └── WheelModMenu.cs ├── VStancer.Client.csproj ├── VStancerConfig.cs └── VStancerUtilities.cs ├── appveyor.yml ├── dist ├── config.json └── fxmanifest.lua ├── fivem-vstancer.sln └── postbuild.bat /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: carmineos 4 | custom: https://www.paypal.me/carmineos 5 | -------------------------------------------------------------------------------- /.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 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Carmine Giugliano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VStancer 2 | |Master|Development| 3 | |:-:|:-:| 4 | |[![Build status](https://ci.appveyor.com/api/projects/status/qialhqew9j0i9528/branch/master?svg=true)](https://ci.appveyor.com/project/carmineos/fivem-vstancer/branch/master) |[![Build status](https://ci.appveyor.com/api/projects/status/qialhqew9j0i9528/branch/development?svg=true)](https://ci.appveyor.com/project/carmineos/fivem-vstancer/branch/development)| 5 | 6 | ### Description 7 | An attempt to use the features from ikt's VStancer as resource for FiveM servers to synchronize the edited vehicles with all the players. It is built using FiveM API and MenuAPI. 8 | 9 | When a client edits a vehicle, it will be automatically synchronized with all the players. 10 | If a vehicle is reset to the default values it will stop from being synchronized. 11 | The synchronization is made using decorators. 12 | 13 | The default key to open the menu is F6 14 | 15 | ### Glossary 16 | * **Track Width**: It's the X offset of the vehicle's wheels bones in the entity local coords system. Because wheels model are rotated it means to have a positivie Track Width you have to assign a negative value. 17 | * **Camber**: It's the Y rotation of the vehicle's wheels bones in the entity local coords system. 18 | * **Wheel Mod**: It refers to a custom wheel you can apply on a vehicle from in-game tuning features. Since this term can create ambiguity with custom assets mods (wheel modifications), we will refers to these as "tuning wheels" and to game modifications as "wheel mods" 19 | 20 | ### Features of the script 21 | * Edit Track Width of vehicles 22 | * Edit Camber of vehicles 23 | * Edit Tuning Wheel Size of vehicles (Requires a tuning wheel to be installed on the vehicle) 24 | * Edit Tuning Wheel Width of vehicles (Requires a tuning wheel to be installed on the vehicle) 25 | * Manage presets 26 | 27 | ### Note 28 | When a preset is created for the first time, it will use the current wheels' state as default. So in case of damaged vehicles (e.g. deformed wheels), the default values might be incorrect. 29 | Workaround: If a vehicle is damaged, be sure to fix it before to enter it and create a preset. (e.g. reset preset, fix the vehicle, exit the vehicle and enter again) 30 | 31 | ### Client Commands 32 | * `vstancer_preset`: Prints the preset of the current vehicle 33 | * `vstancer_decorators`: Prints the info about decorators on the current vehicle 34 | * `vstancer_decorators `: Prints the info about decorators on the vehicle with the specified int as local handle 35 | * `vstancer_print`: Prints the list of all the vehicles with any decorator of this script 36 | * `vstancer_range `: Sets the specified float as the maximum distance used to refresh wheels of the vehicles with decorators 37 | * `vstancer_debug `: Enables or disables the logs to be printed in the console 38 | * `vstancer`: Toggles the menu, this command has to be enabled in the config 39 | 40 | ### Config 41 | * `Debug`: Enables the debug mode, which prints some logs in the console 42 | * `DisableMenu`: Allows to disable the menu in case you want to allow editing in your own menu using the provided API 43 | * `ExposeCommand`: Enables the /vstancer command to toggle the menu 44 | * `ExposeEvent`: Enable the "vstancer:toggleMenu" event to toggle the menu 45 | * `ScriptRange`: The max distance within which each client refreshes edited vehicles 46 | * `Timer`: The value in milliseconds used by each client to do some specific timed tasks 47 | * `ToggleMenuControl`:The Control to toggle the Menu, default is 167 which is F6 (check the [controls list](https://docs.fivem.net/game-references/controls/)) 48 | * `FloatStep`: The step used to increase and decrease a value 49 | * `EnableWheelMod`: Enables the script to edit wheel size and width of tuning wheels 50 | * `EnableClientPresets`: Enables the script to manage clients' presets 51 | * `WheelLimits`: 52 | * `FrontTrackWidth`: The max value you can increase or decrease the front Track Width from its default value 53 | * `RearTrackWidth`: The max value you can increase or decrease the rear Track Width from its default value 54 | * `FrontCamber`: The max value you can increase or decrease the front Camber from its default value 55 | * `RearCamber`: The max value you can increase or decrease the rear Camber from its default value 56 | * `WheelModLimits`: 57 | * `WheelSize`: The max value you can increase or decrease the size of tuning wheels from its default value 58 | * `WheelWidth`: The max value you can increase or decrease the width of tuning wheels from its default value 59 | 60 | ### Exports 61 | The script exposes some API to manage the main features from other scripts: 62 | 63 | ```csharp 64 | bool SetWheelPreset(int vehicle, float frontTrackWidth, float frontCamber, float rearTrackWidth, float rearCamber); 65 | float[] GetWheelPreset(int vehicle); 66 | bool ResetWheelPreset(int vehicle); 67 | float[] GetFrontCamber(int vehicle); 68 | float[] GetRearCamber(int vehicle); 69 | float[] GetFrontTrackWidth(int vehicle); 70 | float[] GetRearTrackWidth(int vehicle); 71 | bool SetFrontCamber(int vehicle, float value); 72 | bool SetRearCamber(int vehicle, float value); 73 | bool SetFrontTrackWidth(int vehicle, float value); 74 | bool SetRearTrackWidth(int vehicle, float value); 75 | bool SaveClientPreset(string presetName, int vehicle); 76 | bool LoadClientPreset(string presetName, int vehicle); 77 | bool DeleteClientPreset(string presetName); 78 | string[] GetClientPresetList(); 79 | ``` 80 | 81 | **NOTE** 82 | Current API don't support editing of tuning wheel data (wheelSize and wheelWidth) yet. 83 | 84 | #### Remember that API require the resource to be called exactly “vstancer” 85 | **API Usage** 86 | 87 | * **SetWheelPreset** 88 | * int vehicle: the handle of the vehicle entity 89 | * float frontTrackWidth: the value you want to assign as front track width 90 | * float frontCamber: the value you want to assign as front camber 91 | * float rearTrackWidth: the value you want to assign as rear track width 92 | * float rearCamber: the value you want to assign as rear camber 93 | * bool result: returns `true` if the action successfully executed otherwise `false` 94 | 95 |
96 | Example 97 | 98 | C#: 99 | ```csharp 100 | bool result = Exports["vstancer"].SetWheelPreset(vehicle, frontTrackWidth, frontCamber, rearTrackWidth, rearCamber); 101 | ``` 102 | Lua: 103 | ```lua 104 | local result = exports["vstancer"]:SetWheelPreset(vehicle, frontTrackWidth, frontCamber, rearTrackWidth, rearCamber) 105 | ``` 106 | 107 |
108 | * **GetWheelPreset** 109 | * int vehicle: the handle of the vehicle entity 110 | * float result: the array containing the oreset values in this order frontTrackWidth, frontCamber, rearTrackWidth, rearCamber. 111 | 112 |
113 | Example 114 | 115 | C#: 116 | ```csharp 117 | float[] result = Exports["vstancer"].GetWheelPreset(vehicle); 118 | ``` 119 | Lua: 120 | ```lua 121 | local result = exports["vstancer"]:GetWheelPreset(vehicle); 122 | ``` 123 | 124 |
125 | * **ResetWheelPreset** 126 | * int vehicle: the handle of the vehicle entity 127 | * bool result: returns `true` if the action successfully executed otherwise `false` 128 | 129 |
130 | Example 131 | 132 | C#: 133 | ```csharp 134 | bool result = Exports["vstancer"].ResetWheelPreset(vehicle); 135 | ``` 136 | Lua: 137 | ```lua 138 | local result = exports["vstancer"]:ResetWheelPreset(vehicle); 139 | ``` 140 | 141 |
142 | * **GetFrontCamber** 143 | * int vehicle: the handle of the vehicle entity 144 | * float[] frontCamber: an array which contains the value as first element if the request has success, otherwise is empty 145 | 146 |
147 | Example 148 | 149 | C#: 150 | ```csharp 151 | float[] frontCamber = Exports["vstancer"].GetFrontCamber(vehicle); 152 | ``` 153 | Lua: 154 | ```lua 155 | local frontCamber = exports["vstancer"]:GetFrontCamber(vehicle); 156 | ``` 157 | 158 |
159 | * **GetRearCamber** 160 | * int vehicle: the handle of the vehicle entity 161 | * float[] rearCamber: an array which contains the value as first element if the request has success, otherwise is empty 162 | 163 |
164 | Example 165 | 166 | C#: 167 | ```csharp 168 | float[] rearCamber = Exports["vstancer"].GetRearCamber(vehicle); 169 | ``` 170 | Lua: 171 | ```lua 172 | local rearCamber = exports["vstancer"]:GetRearCamber(vehicle); 173 | ``` 174 | 175 |
176 | * **GetFrontTrackWidth** 177 | * int vehicle: the handle of the vehicle entity 178 | * float[] frontTrackWidth: an array which contains the value as first element if the request has success, otherwise is empty 179 | 180 |
181 | Example 182 | 183 | C#: 184 | ```csharp 185 | float[] frontTrackWidth = Exports["vstancer"].GetFrontTrackWidth(vehicle); 186 | ``` 187 | Lua: 188 | ```lua 189 | local frontTrackWidth = exports["vstancer"]:GetFrontTrackWidth(vehicle); 190 | ``` 191 | 192 |
193 | * **GetRearTrackWidth** 194 | * int vehicle: the handle of the vehicle entity 195 | * float[] rearTrackWidth: an array which contains the value as first element if the request has success, otherwise is empty 196 | 197 |
198 | Example 199 | 200 | C#: 201 | ```csharp 202 | float[] rearTrackWidth = Exports["vstancer"].GetRearTrackWidth(vehicle); 203 | ``` 204 | Lua: 205 | ```lua 206 | local rearTrackWidth = exports["vstancer"]:GetRearTrackWidth(vehicle); 207 | ``` 208 | 209 |
210 | * **SetFrontCamber** 211 | * int vehicle: the handle of the vehicle entity 212 | * float frontCamber: the value you want to assign as front camber 213 | * bool result: returns `true` if the action successfully executed otherwise `false` 214 | 215 |
216 | Example 217 | 218 | C#: 219 | ```csharp 220 | bool result = Exports["vstancer"].SetFrontCamber(vehicle, frontCamber); 221 | ``` 222 | Lua: 223 | ```lua 224 | local result = exports["vstancer"]:SetFrontCamber(vehicle, frontCamber); 225 | ``` 226 | 227 |
228 | * **SetRearCamber** 229 | * int vehicle: the handle of the vehicle entity 230 | * float rearCamber: the value you want to assign as rear camber 231 | * bool result: returns `true` if the action successfully executed otherwise `false` 232 | 233 |
234 | Example 235 | 236 | C#: 237 | ```csharp 238 | bool result = Exports["vstancer"].SetRearCamber(vehicle, rearCamber); 239 | ``` 240 | Lua: 241 | ```lua 242 | local result = exports["vstancer"]:SetRearCamber(vehicle, rearCamber); 243 | ``` 244 | 245 |
246 | * **SetFrontTrackWidth** 247 | * int vehicle: the handle of the vehicle entity 248 | * float frontTrackWidth: the value you want to assign as front track width 249 | * bool result: returns `true` if the action successfully executed otherwise `false` 250 | 251 |
252 | Example 253 | 254 | C#: 255 | ```csharp 256 | bool result = Exports["vstancer"].SetFrontTrackWidth(vehicle, frontTrackWidth); 257 | ``` 258 | Lua: 259 | ```lua 260 | local result = exports["vstancer"]:SetFrontTrackWidth(vehicle, frontTrackWidth); 261 | ``` 262 | 263 |
264 | * **SetRearTrackWidth** 265 | * int vehicle: the handle of the vehicle entity 266 | * float rearTrackWidth: the value you want to assign as rear track width 267 | * bool result: returns `true` if the action successfully executed otherwise `false` 268 | 269 |
270 | Example 271 | 272 | C#: 273 | ```csharp 274 | bool result = Exports["vstancer"].SetRearTrackWidth(vehicle, rearTrackWidth); 275 | ``` 276 | Lua: 277 | ```lua 278 | local result = exports["vstancer"]:SetRearTrackWidth(vehicle, rearTrackWidth); 279 | ``` 280 | 281 |
282 | * **SaveClientPreset** 283 | * string presetName: the name you want to use for the saved preset 284 | * int vehicle: the handle of the vehicle entity you want to save the preset from 285 | * bool result: returns `true` if the action successfully executed otherwise `false` 286 | 287 |
288 | Example 289 | 290 | C#: 291 | ```csharp 292 | bool result = Exports["vstancer"].SaveClientPreset(presetName, vehicle); 293 | ``` 294 | Lua: 295 | ```lua 296 | local result = exports["vstancer"]:SaveClientPreset(presetName, vehicle); 297 | ``` 298 | 299 |
300 | * **LoadClientPreset** 301 | * string presetName: the name of the preset you want to load 302 | * int vehicle: the handle of the vehicle entity you want to load the preset on 303 | * bool result: returns `true` if the action successfully executed otherwise `false` 304 | 305 |
306 | Example 307 | 308 | C#: 309 | ```csharp 310 | bool result = Exports["vstancer"].LoadClientPreset(presetName, vehicle); 311 | ``` 312 | Lua: 313 | ```lua 314 | local result = exports["vstancer"]:LoadClientPreset(presetName, vehicle); 315 | ``` 316 | 317 |
318 | * **DeleteClientPreset** 319 | * string presetName: the name of the preset you want to delete 320 | * bool result: returns `true` if the action successfully executed otherwise `false` 321 | 322 |
323 | Example 324 | 325 | C#: 326 | ```csharp 327 | bool result = Exports["vstancer"].DeleteClientPreset(presetName); 328 | ``` 329 | Lua: 330 | ```lua 331 | local result = exports["vstancer"]:DeleteClientPreset(presetName); 332 | ``` 333 | 334 |
335 | * **GetClientPresetList** 336 | * string[] presetList: the list of all the presets saved locally 337 | 338 |
339 | Example 340 | 341 | C#: 342 | ```csharp 343 | string[] presetList = Exports["vstancer"].GetClientPresetList(); 344 | ``` 345 | Lua: 346 | ```lua 347 | local presetList = exports["vstancer"]:GetClientPresetList(); 348 | ``` 349 | 350 |
351 | 352 | [Source](https://github.com/carmineos/fivem-vstancer) 353 | [Download](https://github.com/carmineos/fivem-vstancer/releases) 354 | I am open to any kind of feedback. Report suggestions and bugs you find. 355 | 356 | ### Build 357 | Open the `postbuild.bat` and edit the path of the resource folder. If in Debug configuration, the post build event will copy the following files to the specified path: the built assembly of the script, the `config.json`, the `fxmanifest.lua`. 358 | 359 | ### Requirements 360 | The script uses [MenuAPI](https://github.com/TomGrobbe/MenuAPI) by Vespura to render the UI, ~~it uses FiveM built-in resource dependency, so the script will only work if MenuAPI resource is found and running~~ and comes already with a built assembly so that it's ready to use. 361 | 362 | ### Installation 363 | 1. Download the zip file from the release page 364 | 2. Extract the content of the zip to the resources folder of your server (it should be a folder named `vstancer`) 365 | 3. Enable the resource in your server config (`start vstancer`) 366 | 367 | ### Todo 368 | * Add API for wheel mod data 369 | * Update local presets API to support wheel mod data 370 | * Add limits check for API 371 | * Add limits check for preset loading 372 | * Workaround wheel mod data being reset after any tuning component is changed 373 | * Clean duplicated code 374 | * API shouldn't allow to edit vehicles other players are driving 375 | 376 | ### Roadmap 377 | Once FiveM exposes extra-natives to edit `SubHandlingData` fields at runtime, the script will allow to edit XYZ rotation using the native handling fields of `CCarHandlingData` such as `fToeFront`, `fToeRear`, `fCamberFront`, `fCamberRear`, `fCastor`. (This will also improve a lot performances as such values won't need to be set each tick) 378 | 379 | ### Credits 380 | * [VStancer by ikt](https://github.com/E66666666/GTAVStancer) 381 | * [FiveM by CitizenFX](https://github.com/citizenfx/fivem) 382 | * [MenuAPI by Vespura](https://github.com/TomGrobbe/MenuAPI) 383 | * [GTADrifting members](https://gtad.club/) 384 | * All the testers 385 | 386 | ### Support 387 | If you would like to support my work, you can through: 388 | * [Patreon](https://patreon.com/carmineos) -------------------------------------------------------------------------------- /VStancer.Client/Data/WheelData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | using CitizenFX.Core; 5 | 6 | namespace VStancer.Client.Data 7 | { 8 | public class WheelData : IEquatable 9 | { 10 | private const float Epsilon = VStancerUtilities.Epsilon; 11 | 12 | public delegate void WheelDataPropertyEdited(string name, float value); 13 | public event WheelDataPropertyEdited PropertyChanged; 14 | 15 | private readonly WheelDataNode[] _nodes; 16 | private readonly WheelDataNode[] _defaultNodes; 17 | 18 | public int WheelsCount { get; private set; } 19 | public int FrontWheelsCount { get; private set; } 20 | 21 | public WheelDataNode[] GetNodes() => (WheelDataNode[])_nodes.Clone(); 22 | 23 | public float FrontTrackWidth 24 | { 25 | get => _nodes[0].PositionX; 26 | set 27 | { 28 | for (int index = 0; index < FrontWheelsCount; index++) 29 | _nodes[index].PositionX = (index % 2 == 0) ? value : -value; 30 | 31 | PropertyChanged?.Invoke(nameof(FrontTrackWidth), value); 32 | } 33 | } 34 | 35 | public float RearTrackWidth 36 | { 37 | get => _nodes[FrontWheelsCount].PositionX; 38 | set 39 | { 40 | for (int index = FrontWheelsCount; index < WheelsCount; index++) 41 | _nodes[index].PositionX = (index % 2 == 0) ? value : -value; 42 | 43 | PropertyChanged?.Invoke(nameof(RearTrackWidth), value); 44 | } 45 | } 46 | 47 | public float FrontCamber 48 | { 49 | get => _nodes[0].RotationY; 50 | set 51 | { 52 | for (int index = 0; index < FrontWheelsCount; index++) 53 | _nodes[index].RotationY = (index % 2 == 0) ? value : -value; 54 | 55 | PropertyChanged?.Invoke(nameof(FrontCamber), value); 56 | } 57 | } 58 | 59 | public float RearCamber 60 | { 61 | get => _nodes[FrontWheelsCount].RotationY; 62 | set 63 | { 64 | for (int index = FrontWheelsCount; index < WheelsCount; index++) 65 | _nodes[index].RotationY = (index % 2 == 0) ? value : -value; 66 | 67 | PropertyChanged?.Invoke(nameof(RearCamber), value); 68 | } 69 | } 70 | 71 | public float DefaultFrontTrackWidth { get => _defaultNodes[0].PositionX; } 72 | public float DefaultRearTrackWidth { get => _defaultNodes[FrontWheelsCount].PositionX; } 73 | public float DefaultFrontCamber { get => _defaultNodes[0].RotationY; } 74 | public float DefaultRearCamber { get => _defaultNodes[FrontWheelsCount].RotationY; } 75 | 76 | public bool IsEdited 77 | { 78 | get 79 | { 80 | for (int i = 0; i < WheelsCount; i++) 81 | { 82 | if (!MathUtil.WithinEpsilon(_defaultNodes[i].PositionX, _nodes[i].PositionX, Epsilon) || 83 | !MathUtil.WithinEpsilon(_defaultNodes[i].RotationY, _nodes[i].RotationY, Epsilon)) 84 | return true; 85 | } 86 | return false; 87 | } 88 | } 89 | 90 | public WheelData(int count, float defaultFrontOffset, float defaultFrontRotation, float defaultRearOffset, float defaultRearRotation) 91 | { 92 | WheelsCount = count; 93 | 94 | _defaultNodes = new WheelDataNode[WheelsCount]; 95 | 96 | FrontWheelsCount = VStancerUtilities.CalculateFrontWheelsCount(WheelsCount); 97 | 98 | for (int i = 0; i < FrontWheelsCount; i++) 99 | { 100 | if (i % 2 == 0) 101 | { 102 | _defaultNodes[i].RotationY = defaultFrontRotation; 103 | _defaultNodes[i].PositionX = defaultFrontOffset; 104 | } 105 | else 106 | { 107 | _defaultNodes[i].RotationY = -defaultFrontRotation; 108 | _defaultNodes[i].PositionX = -defaultFrontOffset; 109 | } 110 | } 111 | 112 | for (int i = FrontWheelsCount; i < WheelsCount; i++) 113 | { 114 | if (i % 2 == 0) 115 | { 116 | _defaultNodes[i].RotationY = defaultRearRotation; 117 | _defaultNodes[i].PositionX = defaultRearOffset; 118 | } 119 | else 120 | { 121 | _defaultNodes[i].RotationY = -defaultRearRotation; 122 | _defaultNodes[i].PositionX = -defaultRearOffset; 123 | } 124 | } 125 | 126 | _nodes = new WheelDataNode[WheelsCount]; 127 | for (int i = 0; i < WheelsCount; i++) 128 | { 129 | _nodes[i] = _defaultNodes[i]; 130 | } 131 | } 132 | 133 | public void Reset() 134 | { 135 | for (int i = 0; i < WheelsCount; i++) 136 | _nodes[i] = _defaultNodes[i]; 137 | 138 | PropertyChanged?.Invoke(nameof(Reset), default); 139 | } 140 | 141 | public override string ToString() 142 | { 143 | StringBuilder s = new StringBuilder(); 144 | s.AppendLine($"Edited:{IsEdited} Wheels count:{WheelsCount} Front count:{FrontWheelsCount}"); 145 | 146 | StringBuilder defOff = new StringBuilder(string.Format("{0,20}", "Default track width:")); 147 | StringBuilder defRot = new StringBuilder(string.Format("{0,20}", "Default camber:")); 148 | StringBuilder curOff = new StringBuilder(string.Format("{0,20}", "Current track width:")); 149 | StringBuilder curRot = new StringBuilder(string.Format("{0,20}", "Current camber:")); 150 | 151 | for (int i = 0; i < WheelsCount; i++) 152 | { 153 | defOff.Append(string.Format("{0,15}", _defaultNodes[i].PositionX)); 154 | defRot.Append(string.Format("{0,15}", _defaultNodes[i].RotationY)); 155 | curOff.Append(string.Format("{0,15}", _nodes[i].PositionX)); 156 | curRot.Append(string.Format("{0,15}", _nodes[i].RotationY)); 157 | } 158 | 159 | s.AppendLine(curOff.ToString()); 160 | s.AppendLine(defOff.ToString()); 161 | s.AppendLine(curRot.ToString()); 162 | s.AppendLine(defRot.ToString()); 163 | 164 | return s.ToString(); 165 | } 166 | 167 | public bool Equals(WheelData other) 168 | { 169 | if (WheelsCount != other.WheelsCount) 170 | return false; 171 | 172 | for (int i = 0; i < WheelsCount; i++) 173 | { 174 | if (!MathUtil.WithinEpsilon(_defaultNodes[i].PositionX, other._defaultNodes[i].PositionX, Epsilon) || 175 | !MathUtil.WithinEpsilon(_defaultNodes[i].RotationY, other._defaultNodes[i].RotationY, Epsilon) || 176 | !MathUtil.WithinEpsilon(_nodes[i].PositionX, other._nodes[i].PositionX, Epsilon) || 177 | !MathUtil.WithinEpsilon(_nodes[i].RotationY, other._nodes[i].RotationY, Epsilon)) 178 | return false; 179 | } 180 | return true; 181 | } 182 | } 183 | 184 | public struct WheelDataNode 185 | { 186 | /// 187 | /// The track width of the wheel 188 | /// 189 | public float PositionX { get; set; } 190 | 191 | /// 192 | /// The camber of the wheel 193 | /// 194 | public float RotationY { get; set; } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /VStancer.Client/Data/WheelModData.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | using CitizenFX.Core; 4 | 5 | namespace VStancer.Client.Data 6 | { 7 | public class WheelModData 8 | { 9 | private const float Epsilon = VStancerUtilities.Epsilon; 10 | 11 | private readonly WheelModNode[] _nodes; 12 | private readonly WheelModNode[] _defaultNodes; 13 | private float _wheelSize; 14 | private float _wheelWidth; 15 | 16 | public int WheelsCount { get; private set; } 17 | public int FrontWheelsCount { get; private set; } 18 | 19 | public delegate void WheelModPropertyEdited(string name, float value); 20 | public event WheelModPropertyEdited PropertyChanged; 21 | 22 | public float WheelSize 23 | { 24 | get => _wheelSize; 25 | set 26 | { 27 | if (value == _wheelSize) 28 | return; 29 | 30 | _wheelSize = value; 31 | PropertyChanged?.Invoke(nameof(WheelSize), value); 32 | } 33 | } 34 | 35 | public float WheelWidth 36 | { 37 | get => _wheelWidth; 38 | set 39 | { 40 | if (value == _wheelWidth) 41 | return; 42 | 43 | _wheelWidth = value; 44 | PropertyChanged?.Invoke(nameof(WheelWidth), value); 45 | } 46 | } 47 | 48 | public float DefaultWheelSize { get; private set; } 49 | public float DefaultWheelWidth { get; private set; } 50 | 51 | public float DefaultFrontTireColliderWidthRatio { get; private set; } 52 | public float DefaultFrontTireColliderSizeRatio { get; private set; } 53 | public float DefaultFrontRimColliderSizeRatio { get; private set; } 54 | public float DefaultRearTireColliderWidthRatio { get; private set; } 55 | public float DefaultRearTireColliderSizeRatio { get; private set; } 56 | public float DefaultRearRimColliderSizeRatio { get; private set; } 57 | 58 | 59 | 60 | public WheelModData(int wheelsCount, float width, float radius, 61 | float frontTireColliderWidth, float frontTireColliderSize, float frontRimColliderSize, 62 | float rearTireColliderWidth, float rearTireColliderSize, float rearRimColliderSize) 63 | { 64 | WheelsCount = wheelsCount; 65 | FrontWheelsCount = VStancerUtilities.CalculateFrontWheelsCount(WheelsCount); 66 | 67 | DefaultWheelSize = radius; 68 | DefaultWheelWidth = width; 69 | 70 | _wheelSize = radius; 71 | _wheelWidth = width; 72 | 73 | _defaultNodes = new WheelModNode[WheelsCount]; 74 | 75 | for (int i = 0; i < FrontWheelsCount; i++) 76 | { 77 | _defaultNodes[i].TireColliderWidth = frontTireColliderWidth; 78 | _defaultNodes[i].TireColliderSize = frontTireColliderSize; 79 | _defaultNodes[i].RimColliderSize = frontRimColliderSize; 80 | } 81 | 82 | for (int i = FrontWheelsCount; i < WheelsCount; i++) 83 | { 84 | _defaultNodes[i].TireColliderWidth = rearTireColliderWidth; 85 | _defaultNodes[i].TireColliderSize = rearTireColliderSize; 86 | _defaultNodes[i].RimColliderSize = rearRimColliderSize; 87 | } 88 | 89 | DefaultFrontTireColliderWidthRatio = DefaultWheelWidth / DefaultFrontTireColliderWidth; 90 | DefaultFrontTireColliderSizeRatio = DefaultWheelSize / DefaultFrontTireColliderSize; 91 | DefaultFrontRimColliderSizeRatio = DefaultWheelSize / DefaultFrontRimColliderSize; 92 | 93 | DefaultRearTireColliderWidthRatio = DefaultWheelWidth / DefaultRearTireColliderWidth; 94 | DefaultRearTireColliderSizeRatio = DefaultWheelSize / DefaultRearTireColliderSize; 95 | DefaultRearRimColliderSizeRatio = DefaultWheelSize / DefaultRearRimColliderSize; 96 | 97 | _nodes = new WheelModNode[WheelsCount]; 98 | for (int i = 0; i < WheelsCount; i++) 99 | _nodes[i] = _defaultNodes[i]; 100 | } 101 | 102 | 103 | public float FrontTireColliderWidth 104 | { 105 | get => _nodes[0].TireColliderWidth; 106 | set 107 | { 108 | for (int index = 0; index < FrontWheelsCount; index++) 109 | _nodes[index].TireColliderWidth = value; 110 | 111 | PropertyChanged?.Invoke(nameof(FrontTireColliderWidth), value); 112 | } 113 | } 114 | 115 | 116 | public float FrontTireColliderSize 117 | { 118 | get => _nodes[0].TireColliderSize; 119 | set 120 | { 121 | for (int index = 0; index < FrontWheelsCount; index++) 122 | _nodes[index].TireColliderSize = value; 123 | 124 | PropertyChanged?.Invoke(nameof(FrontTireColliderSize), value); 125 | } 126 | } 127 | 128 | 129 | public float FrontRimColliderSize 130 | { 131 | get => _nodes[0].RimColliderSize; 132 | set 133 | { 134 | for (int index = 0; index < FrontWheelsCount; index++) 135 | _nodes[index].RimColliderSize = value; 136 | 137 | PropertyChanged?.Invoke(nameof(FrontRimColliderSize), value); 138 | } 139 | } 140 | 141 | 142 | public float RearTireColliderWidth 143 | { 144 | get => _nodes[FrontWheelsCount].TireColliderWidth; 145 | set 146 | { 147 | for (int index = FrontWheelsCount; index < WheelsCount; index++) 148 | _nodes[index].TireColliderWidth = value; 149 | 150 | PropertyChanged?.Invoke(nameof(RearTireColliderWidth), value); 151 | } 152 | } 153 | 154 | 155 | public float RearTireColliderSize 156 | { 157 | get => _nodes[FrontWheelsCount].TireColliderSize; 158 | set 159 | { 160 | for (int index = FrontWheelsCount; index < WheelsCount; index++) 161 | _nodes[index].TireColliderSize = value; 162 | 163 | PropertyChanged?.Invoke(nameof(RearTireColliderSize), value); 164 | } 165 | } 166 | 167 | public float RearRimColliderSize 168 | { 169 | get => _nodes[FrontWheelsCount].RimColliderSize; 170 | set 171 | { 172 | for (int index = FrontWheelsCount; index < WheelsCount; index++) 173 | _nodes[index].RimColliderSize = value; 174 | 175 | PropertyChanged?.Invoke(nameof(RearRimColliderSize), value); 176 | } 177 | } 178 | 179 | public float DefaultFrontTireColliderWidth => _defaultNodes[0].TireColliderWidth; 180 | public float DefaultFrontTireColliderSize => _defaultNodes[0].TireColliderSize; 181 | public float DefaultFrontRimColliderSize => _defaultNodes[0].RimColliderSize; 182 | 183 | public float DefaultRearTireColliderWidth => _defaultNodes[FrontWheelsCount].TireColliderWidth; 184 | public float DefaultRearTireColliderSize => _defaultNodes[FrontWheelsCount].TireColliderSize; 185 | public float DefaultRearRimColliderSize => _defaultNodes[FrontWheelsCount].RimColliderSize; 186 | 187 | public void Reset() 188 | { 189 | WheelSize = DefaultWheelSize; 190 | WheelWidth = DefaultWheelWidth; 191 | 192 | for (int i = 0; i < WheelsCount; i++) 193 | _nodes[i] = _defaultNodes[i]; 194 | 195 | PropertyChanged?.Invoke(nameof(Reset), default); 196 | } 197 | 198 | public bool IsEdited 199 | { 200 | get 201 | { 202 | if (!MathUtil.WithinEpsilon(DefaultWheelSize, WheelSize, Epsilon) || 203 | !MathUtil.WithinEpsilon(DefaultWheelWidth, WheelWidth, Epsilon)) 204 | return true; 205 | 206 | for (int i = 0; i < WheelsCount; i++) 207 | { 208 | if (!MathUtil.WithinEpsilon(_defaultNodes[i].TireColliderWidth, _nodes[i].TireColliderWidth, Epsilon) || 209 | !MathUtil.WithinEpsilon(_defaultNodes[i].TireColliderSize, _nodes[i].TireColliderSize, Epsilon) || 210 | !MathUtil.WithinEpsilon(_defaultNodes[i].RimColliderSize, _nodes[i].RimColliderSize, Epsilon)) 211 | return true; 212 | } 213 | return false; 214 | } 215 | } 216 | 217 | public override string ToString() 218 | { 219 | StringBuilder s = new StringBuilder(); 220 | s.AppendLine($"{nameof(WheelModData)}, Edited:{IsEdited}"); 221 | s.AppendLine($"{nameof(WheelSize)}: {WheelSize} ({DefaultWheelSize})"); 222 | s.AppendLine($"{nameof(WheelWidth)}: {WheelWidth} ({DefaultWheelWidth})"); 223 | 224 | for (int i = 0; i < WheelsCount; i++) 225 | { 226 | var defNode = _defaultNodes[i]; 227 | var node = _nodes[i]; 228 | s.Append($"Wheel {i}: {nameof(WheelModNode.TireColliderWidth)}: {node.TireColliderWidth} ({defNode.TireColliderWidth})"); 229 | s.Append($" {nameof(WheelModNode.TireColliderSize)}: {node.TireColliderSize} ({defNode.TireColliderSize})"); 230 | s.AppendLine($" {nameof(WheelModNode.RimColliderSize)}: {node.RimColliderSize} ({defNode.RimColliderSize})"); 231 | } 232 | return s.ToString(); 233 | } 234 | } 235 | 236 | public struct WheelModNode 237 | { 238 | /// 239 | /// The collider wheel thread size 240 | /// For vanilla wheel mod this is always 50% of visual width 241 | /// 242 | public float TireColliderWidth { get; set; } 243 | 244 | /// 245 | /// The collider wheel radius 246 | /// For vanilla wheel mod this is always 50% of visual size 247 | /// 248 | public float TireColliderSize { get; set; } 249 | 250 | /// 251 | /// The collider wheel radius 252 | /// Defined as rimRadius in carcols for wheels (CVehicleModelInfoVarGlobal) 253 | /// 254 | public float RimColliderSize { get; set; } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /VStancer.Client/Globals.cs: -------------------------------------------------------------------------------- 1 | namespace VStancer.Client 2 | { 3 | public static class Globals 4 | { 5 | /// 6 | /// The name of the script 7 | /// 8 | public const string ScriptName = "VStancer"; 9 | 10 | /// 11 | /// The prefix used for key-value pairs used to store personal presets 12 | /// 13 | public const string KvpPrefix = "vstancer_"; 14 | 15 | /// 16 | /// The expected name of the resource 17 | /// 18 | public const string ResourceName = "vstancer"; 19 | 20 | /// 21 | /// The prefix used for commands exposed by the script 22 | /// 23 | public const string CommandPrefix = "vstancer_"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /VStancer.Client/Preset/IPresetsCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace VStancer.Client.Preset 5 | { 6 | public interface IPresetsCollection 7 | { 8 | /// 9 | /// Invoked when an element is saved or deleted 10 | /// 11 | event EventHandler PresetsCollectionChanged; 12 | 13 | /// 14 | /// Saves the using the as preset name 15 | /// 16 | /// 17 | /// 18 | /// 19 | bool Save(TKey name, TValue preset); 20 | 21 | /// 22 | /// Deletes the preset with the as preset name 23 | /// 24 | /// 25 | /// 26 | bool Delete(TKey name); 27 | 28 | /// 29 | /// Loads and the returns the named 30 | /// 31 | /// 32 | /// 33 | bool Load(TKey name, out TValue value); 34 | 35 | /// 36 | /// Returns the list of all the saved keys 37 | /// 38 | /// 39 | IEnumerable GetKeys(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /VStancer.Client/Preset/KvpPresetsCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | using static CitizenFX.Core.Native.API; 5 | using Newtonsoft.Json; 6 | using System.Linq; 7 | 8 | namespace VStancer.Client.Preset 9 | { 10 | /// 11 | /// The vstancer preset manager which saves the presets as key-value pairs built-in FiveM 12 | /// 13 | public class KvpPresetsCollection : IPresetsCollection 14 | { 15 | private readonly string mKvpPrefix; 16 | 17 | public event EventHandler PresetsCollectionChanged; 18 | 19 | public KvpPresetsCollection(string prefix) 20 | { 21 | mKvpPrefix = prefix; 22 | } 23 | 24 | public bool Delete(string name) 25 | { 26 | // Check if the preset ID is valid 27 | if (string.IsNullOrEmpty(name)) 28 | return false; 29 | 30 | // Get the KVP key 31 | string key = string.Concat(mKvpPrefix, name); 32 | 33 | // Check if a KVP with the given key exists 34 | if (GetResourceKvpString(key) == null) 35 | return false; 36 | 37 | // Delete the KVP 38 | DeleteResourceKvp(key); 39 | 40 | // Invoke the event 41 | PresetsCollectionChanged?.Invoke(this, EventArgs.Empty); 42 | 43 | return true; 44 | } 45 | 46 | public bool Save(string name, VStancerPreset preset) 47 | { 48 | // Check if the preset and the ID are valid 49 | if (string.IsNullOrEmpty(name) || preset == null) 50 | return false; 51 | 52 | // Get the KVP key 53 | string key = string.Concat(mKvpPrefix, name); 54 | 55 | // Be sure the key isn't already used 56 | if (GetResourceKvpString(key) != null) 57 | return false; 58 | 59 | // Get the Json 60 | var json = JsonConvert.SerializeObject(preset); 61 | 62 | // Save the KVP 63 | SetResourceKvp(key, json); 64 | 65 | // Invoke the event 66 | PresetsCollectionChanged?.Invoke(this, EventArgs.Empty); 67 | 68 | return true; 69 | } 70 | 71 | public bool Load(string name, out VStancerPreset preset) 72 | { 73 | preset = null; 74 | 75 | // Check if the preset ID is valid 76 | if (string.IsNullOrEmpty(name)) 77 | return false; 78 | 79 | // Get the KVP key 80 | string key = string.Concat(mKvpPrefix, name); 81 | 82 | // Get the KVP value 83 | string value = GetResourceKvpString(key); 84 | 85 | // Check if the value is valid 86 | if (string.IsNullOrEmpty(value)) 87 | return false; 88 | 89 | // Create a preset 90 | preset = JsonConvert.DeserializeObject(value); 91 | return true; 92 | } 93 | 94 | public IEnumerable GetKeys() 95 | { 96 | return VStancerUtilities.GetKeyValuePairs(mKvpPrefix).Select(key => key.Remove(0, mKvpPrefix.Length)); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /VStancer.Client/Preset/VStancerPreset.cs: -------------------------------------------------------------------------------- 1 | using VStancer.Client.Data; 2 | 3 | namespace VStancer.Client.Preset 4 | { 5 | public class VStancerPreset 6 | { 7 | public WheelPreset WheelPreset { get; set; } 8 | public WheelModPreset WheelModPreset { get; set; } 9 | } 10 | 11 | public class WheelPreset 12 | { 13 | public float FrontTrackWidth { get; set; } 14 | public float FrontCamber { get; set; } 15 | public float RearTrackWidth { get; set; } 16 | public float RearCamber { get; set; } 17 | 18 | public WheelPreset() 19 | { 20 | 21 | } 22 | 23 | public WheelPreset(float frontTrackWidth, float frontCamber, float rearTrackWidth, float rearCamber) 24 | { 25 | FrontTrackWidth = frontTrackWidth; 26 | FrontCamber = frontCamber; 27 | RearTrackWidth = rearTrackWidth; 28 | RearCamber = rearCamber; 29 | } 30 | 31 | public WheelPreset(WheelData data) 32 | { 33 | if (data == null) 34 | return; 35 | 36 | FrontTrackWidth = data.FrontTrackWidth; 37 | FrontCamber = data.FrontCamber; 38 | RearTrackWidth = data.RearTrackWidth; 39 | RearCamber = data.RearCamber; 40 | } 41 | 42 | public float[] ToArray() { return new float[] { FrontTrackWidth, FrontCamber, RearTrackWidth, RearCamber }; } 43 | } 44 | 45 | public class WheelModPreset 46 | { 47 | public float WheelSize { get; set; } 48 | public float WheelWidth { get; set; } 49 | public float FrontTireColliderWidth { get; set; } 50 | public float FrontTireColliderSize { get; set; } 51 | public float FrontRimColliderSize { get; set; } 52 | public float RearTireColliderWidth { get; set; } 53 | public float RearTireColliderSize { get; set; } 54 | public float RearRimColliderSize { get; set; } 55 | 56 | public WheelModPreset() 57 | { 58 | 59 | } 60 | 61 | public WheelModPreset(WheelModData data) 62 | { 63 | if (data == null) 64 | return; 65 | 66 | WheelSize = data.WheelSize; 67 | WheelWidth = data.WheelWidth; 68 | 69 | FrontTireColliderWidth = data.FrontTireColliderWidth; 70 | FrontTireColliderSize = data.FrontTireColliderSize; 71 | FrontRimColliderSize = data.FrontRimColliderSize; 72 | 73 | RearTireColliderWidth = data.RearTireColliderWidth; 74 | RearTireColliderSize = data.FrontTireColliderSize; 75 | RearRimColliderSize = data.RearRimColliderSize; 76 | } 77 | 78 | public float[] ToArray() { return new float[] { WheelSize, WheelWidth, FrontTireColliderWidth, FrontTireColliderSize, FrontRimColliderSize, RearTireColliderWidth, RearTireColliderSize, RearRimColliderSize }; } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /VStancer.Client/Scripts/ClientPresetsScript.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | using VStancer.Client.UI; 4 | 5 | using CitizenFX.Core.UI; 6 | using VStancer.Client.Preset; 7 | using System.Collections.Generic; 8 | using CitizenFX.Core; 9 | using static CitizenFX.Core.Native.API; 10 | 11 | namespace VStancer.Client.Scripts 12 | { 13 | internal class ClientPresetsScript 14 | { 15 | private readonly MainScript _mainScript; 16 | 17 | internal IPresetsCollection Presets { get; private set; } 18 | internal ClientPresetsMenu Menu { get; private set; } 19 | 20 | public ClientPresetsScript(MainScript mainScript) 21 | { 22 | _mainScript = mainScript; 23 | Presets = new KvpPresetsCollection(Globals.KvpPrefix); 24 | 25 | if (!_mainScript.Config.DisableMenu) 26 | { 27 | Menu = new ClientPresetsMenu(this); 28 | 29 | Menu.DeletePresetEvent += (sender, presetID) => OnDeletePresetInvoked(presetID); 30 | Menu.SavePresetEvent += (sender, presetID) => OnSavePresetInvoked(presetID); 31 | Menu.ApplyPresetEvent += (sender, presetID) => OnApplyPresetInvoked(presetID); 32 | } 33 | } 34 | 35 | internal async Task GetPresetNameFromUser(string title, string defaultText) 36 | { 37 | return await _mainScript.GetOnScreenString(title, defaultText); 38 | } 39 | 40 | private void OnDeletePresetInvoked(string presetKey) 41 | { 42 | if (Presets.Delete(presetKey)) 43 | Screen.ShowNotification($"Client preset ~r~{presetKey}~w~ deleted"); 44 | else 45 | Screen.ShowNotification($"~r~ERROR~w~ No preset found with {presetKey} key."); 46 | } 47 | 48 | private void OnSavePresetInvoked(string presetKey) 49 | { 50 | var wheelPreset = _mainScript.WheelScript?.GetWheelPreset(); 51 | var wheelModPreset = _mainScript.WheelModScript?.GetWheelModPreset(); 52 | 53 | if(wheelPreset == null && wheelModPreset == null) 54 | { 55 | Screen.ShowNotification($"~r~ERROR~w~ Nothing to save, be sure your vehicle is edited!"); 56 | return; 57 | } 58 | 59 | if (wheelPreset == null) 60 | Screen.ShowNotification($"~y~WARNING~w~ The preset doesn't contain any wheel data."); 61 | 62 | if (wheelModPreset == null) 63 | Screen.ShowNotification($"~y~WARNING~w~ The preset doesn't contain any wheel mod data."); 64 | 65 | VStancerPreset preset = new VStancerPreset 66 | { 67 | WheelPreset = wheelPreset, 68 | WheelModPreset = wheelModPreset, 69 | }; 70 | 71 | if (Presets.Save(presetKey, preset)) 72 | Screen.ShowNotification($"Client preset ~g~{presetKey}~w~ saved"); 73 | else 74 | Screen.ShowNotification($"~r~ERROR~w~ The name {presetKey} is invalid or already used."); 75 | } 76 | 77 | private async void OnApplyPresetInvoked(string presetKey) 78 | { 79 | if (!Presets.Load(presetKey, out VStancerPreset loadedPreset)) 80 | { 81 | Screen.ShowNotification($"~r~ERROR~w~ No Client preset with name ~b~{presetKey}~w~ found"); 82 | return; 83 | } 84 | 85 | if (loadedPreset == null) 86 | { 87 | Screen.ShowNotification($"~r~ERROR~w~ Client preset ~b~{presetKey}~w~ corrupted"); 88 | return; 89 | } 90 | 91 | await _mainScript.WheelScript.SetWheelPreset(loadedPreset.WheelPreset); 92 | await _mainScript.WheelModScript.SetWheelModPreset(loadedPreset.WheelModPreset); 93 | 94 | Screen.ShowNotification($"Client preset ~b~{presetKey}~w~ applied"); 95 | } 96 | 97 | internal bool API_DeletePreset(string presetKey) 98 | { 99 | return Presets.Delete(presetKey); 100 | } 101 | 102 | internal bool API_SavePreset(string presetKey, int vehicle) 103 | { 104 | if (_mainScript.WheelScript == null) 105 | return false; 106 | 107 | if (!_mainScript.WheelScript.API_GetWheelPreset(vehicle, out WheelPreset wheelPreset)) 108 | return false; 109 | 110 | if(wheelPreset != null) 111 | { 112 | VStancerPreset preset = new VStancerPreset 113 | { 114 | WheelPreset = wheelPreset 115 | }; 116 | 117 | return Presets.Save(presetKey, preset); 118 | } 119 | 120 | return false; 121 | } 122 | 123 | internal bool API_LoadPreset(string presetKey, int vehicle) 124 | { 125 | if (!Presets.Load(presetKey, out VStancerPreset loadedPreset)) 126 | return false; 127 | 128 | if (loadedPreset == null) 129 | return false; 130 | 131 | if(_mainScript.WheelScript != null) 132 | { 133 | return _mainScript.WheelScript.API_SetWheelPreset(vehicle, loadedPreset.WheelPreset); 134 | } 135 | 136 | // TODO: Load wheel mod preset on vehicle 137 | 138 | return false; 139 | } 140 | 141 | internal IEnumerable API_GetClientPresetList() 142 | { 143 | return Presets.GetKeys(); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /VStancer.Client/Scripts/MainScript.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | using VStancer.Client.UI; 6 | 7 | using CitizenFX.Core; 8 | using static CitizenFX.Core.Native.API; 9 | using Newtonsoft.Json; 10 | using VStancer.Client.Preset; 11 | using System.Linq; 12 | 13 | namespace VStancer.Client.Scripts 14 | { 15 | public class MainScript : BaseScript 16 | { 17 | private readonly MainMenu Menu; 18 | 19 | private long _lastTime; 20 | private int _playerVehicleHandle; 21 | private int _playerPedHandle; 22 | private Vector3 _playerPedCoords; 23 | private List _worldVehiclesHandles; 24 | private float _maxDistanceSquared; 25 | 26 | internal int PlayerVehicleHandle 27 | { 28 | get => _playerVehicleHandle; 29 | private set 30 | { 31 | if (Equals(_playerVehicleHandle, value)) 32 | return; 33 | 34 | _playerVehicleHandle = value; 35 | PlayerVehicleHandleChanged?.Invoke(this, value); 36 | } 37 | } 38 | 39 | internal int PlayerPedHandle 40 | { 41 | get => _playerPedHandle; 42 | private set 43 | { 44 | if (Equals(_playerPedHandle, value)) 45 | return; 46 | 47 | _playerPedHandle = value; 48 | PlayerPedHandleChanged?.Invoke(this, value); 49 | } 50 | } 51 | 52 | internal event EventHandler PlayerVehicleHandleChanged; 53 | internal event EventHandler PlayerPedHandleChanged; 54 | 55 | internal event EventHandler ToggleMenuVisibility; 56 | 57 | internal VStancerConfig Config { get; private set; } 58 | internal WheelScript WheelScript { get; private set; } 59 | internal WheelModScript WheelModScript { get; private set; } 60 | internal ClientPresetsScript ClientPresetsScript { get; private set; } 61 | 62 | public MainScript() 63 | { 64 | if (GetCurrentResourceName() != Globals.ResourceName) 65 | { 66 | Debug.WriteLine($"{nameof(MainScript)}: Invalid resource name, be sure the resource name is {Globals.ResourceName}"); 67 | return; 68 | } 69 | 70 | _lastTime = GetGameTimer(); 71 | _playerVehicleHandle = -1; 72 | _playerPedHandle = -1; 73 | _playerPedCoords = Vector3.Zero; 74 | _worldVehiclesHandles = new List(); 75 | _maxDistanceSquared = 10; 76 | 77 | Config = LoadConfig(); 78 | _maxDistanceSquared = (float)Math.Sqrt(Config.ScriptRange); 79 | WheelScript = new WheelScript(this); 80 | RegisterScript(WheelScript); 81 | 82 | if (Config.EnableWheelMod) 83 | { 84 | WheelModScript = new WheelModScript(this); 85 | RegisterScript(WheelModScript); 86 | } 87 | 88 | if (Config.EnableClientPresets) 89 | { 90 | ClientPresetsScript = new ClientPresetsScript(this); 91 | } 92 | 93 | if (!Config.DisableMenu) 94 | Menu = new MainMenu(this); 95 | 96 | Tick += GetPlayerAndVehicleTask; 97 | Tick += TimedTask; 98 | Tick += HideUITask; 99 | 100 | RegisterCommands(); 101 | 102 | Exports.Add("SetWheelPreset", new Func(SetWheelPreset)); 103 | Exports.Add("GetWheelPreset", new Func(GetWheelPreset)); 104 | Exports.Add("ResetWheelPreset", new Func(ResetWheelPreset)); 105 | 106 | Exports.Add("SetFrontCamber", new Func(SetFrontCamber)); 107 | Exports.Add("SetRearCamber", new Func(SetRearCamber)); 108 | Exports.Add("SetFrontTrackWidth", new Func(SetFrontTrackWidth)); 109 | Exports.Add("SetRearTrackWidth", new Func(SetRearTrackWidth)); 110 | 111 | Exports.Add("GetFrontCamber", new Func(GetFrontCamber)); 112 | Exports.Add("GetRearCamber", new Func(GetRearCamber)); 113 | Exports.Add("GetFrontTrackWidth", new Func(GetFrontTrackWidth)); 114 | Exports.Add("GetRearTrackWidth", new Func(GetRearTrackWidth)); 115 | 116 | Exports.Add("SaveClientPreset", new Func(SaveClientPreset)); 117 | Exports.Add("LoadClientPreset", new Func(LoadClientPreset)); 118 | Exports.Add("DeleteClientPreset", new Func(DeleteClientPreset)); 119 | Exports.Add("GetClientPresetList", new Func(GetClientPresetList)); 120 | } 121 | 122 | private async Task HideUITask() 123 | { 124 | if (Menu != null) 125 | Menu.HideMenu = _playerVehicleHandle == -1; 126 | 127 | await Task.FromResult(0); 128 | } 129 | 130 | internal List GetCloseVehicleHandles() 131 | { 132 | List closeVehicles = new List(); 133 | 134 | foreach (int handle in _worldVehiclesHandles) 135 | { 136 | if (!DoesEntityExist(handle)) 137 | continue; 138 | 139 | Vector3 coords = GetEntityCoords(handle, true); 140 | 141 | if (Vector3.DistanceSquared(_playerPedCoords, coords) <= _maxDistanceSquared) 142 | closeVehicles.Add(handle); 143 | } 144 | 145 | return closeVehicles; 146 | } 147 | 148 | private async Task TimedTask() 149 | { 150 | long currentTime = GetGameTimer() - _lastTime; 151 | 152 | if (currentTime > Config.Timer) 153 | { 154 | _playerPedCoords = GetEntityCoords(_playerPedHandle, true); 155 | 156 | _worldVehiclesHandles = VStancerUtilities.GetWorldVehicles(); 157 | 158 | _lastTime = GetGameTimer(); 159 | } 160 | 161 | await Task.FromResult(0); 162 | } 163 | 164 | private async Task GetPlayerAndVehicleTask() 165 | { 166 | await Task.FromResult(0); 167 | 168 | _playerPedHandle = PlayerPedId(); 169 | 170 | if (!IsPedInAnyVehicle(_playerPedHandle, false)) 171 | { 172 | PlayerVehicleHandle = -1; 173 | return; 174 | } 175 | 176 | int vehicle = GetVehiclePedIsIn(_playerPedHandle, false); 177 | 178 | // If this model isn't a car, or player isn't the driver, or vehicle is not driveable 179 | if (!IsThisModelACar((uint)GetEntityModel(vehicle)) || GetPedInVehicleSeat(vehicle, -1) != _playerPedHandle || !IsVehicleDriveable(vehicle, false)) 180 | { 181 | PlayerVehicleHandle = -1; 182 | return; 183 | } 184 | 185 | PlayerVehicleHandle = vehicle; 186 | } 187 | 188 | private VStancerConfig LoadConfig(string filename = "config.json") 189 | { 190 | VStancerConfig config; 191 | 192 | try 193 | { 194 | string strings = LoadResourceFile(Globals.ResourceName, filename); 195 | config = JsonConvert.DeserializeObject(strings); 196 | 197 | Debug.WriteLine($"{nameof(MainScript)}: Loaded config from {filename}"); 198 | } 199 | catch (Exception e) 200 | { 201 | Debug.WriteLine($"{nameof(MainScript)}: Impossible to load {filename}", e.Message); 202 | Debug.WriteLine(e.StackTrace); 203 | 204 | config = new VStancerConfig(); 205 | } 206 | 207 | return config; 208 | } 209 | 210 | public async Task GetOnScreenString(string title, string defaultText) 211 | { 212 | DisplayOnscreenKeyboard(1, title, "", defaultText, "", "", "", 128); 213 | while (UpdateOnscreenKeyboard() != 1 && UpdateOnscreenKeyboard() != 2) await Delay(100); 214 | 215 | return GetOnscreenKeyboardResult(); 216 | } 217 | 218 | private void RegisterCommands() 219 | { 220 | RegisterCommand($"{Globals.CommandPrefix}decorators", new Action((source, args) => 221 | { 222 | if (args.Count < 1) 223 | { 224 | WheelScript.PrintDecoratorsInfo(_playerVehicleHandle); 225 | WheelModScript.PrintDecoratorsInfo(_playerVehicleHandle); 226 | } 227 | else 228 | { 229 | if (int.TryParse(args[0], out int value)) 230 | { 231 | WheelScript.PrintDecoratorsInfo(value); 232 | WheelModScript.PrintDecoratorsInfo(value); 233 | } 234 | else Debug.WriteLine($"{nameof(MainScript)}: Error parsing entity handle {args[0]} as int"); 235 | } 236 | }), false); 237 | 238 | RegisterCommand($"{Globals.CommandPrefix}range", new Action((source, args) => 239 | { 240 | if (args.Count < 1) 241 | { 242 | Debug.WriteLine($"{nameof(MainScript)}: Missing float argument"); 243 | return; 244 | } 245 | 246 | if (float.TryParse(args[0], out float value)) 247 | { 248 | Config.ScriptRange = value; 249 | _maxDistanceSquared = (float)Math.Sqrt(value); 250 | Debug.WriteLine($"{nameof(MainScript)}: {nameof(Config.ScriptRange)} updated to {value}"); 251 | } 252 | else Debug.WriteLine($"{nameof(MainScript)}: Error parsing {args[0]} as float"); 253 | 254 | }), false); 255 | 256 | RegisterCommand($"{Globals.CommandPrefix}debug", new Action((source, args) => 257 | { 258 | if (args.Count < 1) 259 | { 260 | Debug.WriteLine($"{nameof(MainScript)}: Missing bool argument"); 261 | return; 262 | } 263 | 264 | if (bool.TryParse(args[0], out bool value)) 265 | { 266 | Config.Debug = value; 267 | Debug.WriteLine($"{nameof(MainScript)}: {nameof(Config.Debug)} updated to {value}"); 268 | } 269 | else Debug.WriteLine($"{nameof(MainScript)}: Error parsing {args[0]} as bool"); 270 | 271 | }), false); 272 | 273 | RegisterCommand($"{Globals.CommandPrefix}preset", new Action((source, args) => 274 | { 275 | if (WheelScript?.WheelData != null) 276 | Debug.WriteLine(WheelScript.WheelData.ToString()); 277 | else 278 | Debug.WriteLine($"{nameof(MainScript)}: {nameof(WheelScript.WheelData)} is null"); 279 | 280 | if (WheelModScript?.WheelModData != null) 281 | Debug.WriteLine(WheelModScript.WheelModData.ToString()); 282 | else 283 | Debug.WriteLine($"{nameof(MainScript)}: {nameof(WheelModScript.WheelModData)} is null"); 284 | }), false); 285 | 286 | RegisterCommand($"{Globals.CommandPrefix}print", new Action((source, args) => 287 | { 288 | if (WheelScript != null) 289 | WheelScript.PrintVehiclesWithDecorators(_worldVehiclesHandles); 290 | if (WheelModScript != null) 291 | WheelModScript.PrintVehiclesWithDecorators(_worldVehiclesHandles); 292 | }), false); 293 | 294 | if(!Config.DisableMenu) 295 | { 296 | if (Config.ExposeCommand) 297 | RegisterCommand("vstancer", new Action((source, args) => { ToggleMenuVisibility?.Invoke(this, EventArgs.Empty); }), false); 298 | 299 | if (Config.ExposeEvent) 300 | EventHandlers.Add("vstancer:toggleMenu", new Action(() => { ToggleMenuVisibility?.Invoke(this, EventArgs.Empty); })); 301 | } 302 | } 303 | 304 | public float[] GetWheelPreset(int vehicle) 305 | { 306 | if (WheelScript == null) 307 | return new float[] { }; 308 | 309 | if (WheelScript.API_GetWheelPreset(vehicle, out WheelPreset preset)) 310 | return preset.ToArray(); 311 | 312 | return new float[] { }; 313 | } 314 | 315 | public bool SetWheelPreset(int vehicle, float frontTrackWidth, float frontCamber, float rearTrackWidth, float rearCamber) 316 | { 317 | if (WheelScript == null) 318 | return false; 319 | 320 | WheelPreset preset = new WheelPreset(frontTrackWidth, frontCamber, rearTrackWidth, rearCamber); 321 | return WheelScript.API_SetWheelPreset(vehicle, preset); 322 | } 323 | 324 | public bool ResetWheelPreset(int vehicle) 325 | { 326 | if (WheelScript == null) 327 | return false; 328 | 329 | return WheelScript.API_ResetWheelPreset(vehicle); 330 | } 331 | 332 | public bool SetFrontCamber(int vehicle, float value) 333 | { 334 | if (WheelScript == null) 335 | return false; 336 | 337 | return WheelScript.API_SetFrontCamber(vehicle, value); 338 | } 339 | 340 | public bool SetRearCamber(int vehicle, float value) 341 | { 342 | if (WheelScript == null) 343 | return false; 344 | 345 | return WheelScript.API_SetRearCamber(vehicle, value); 346 | } 347 | 348 | public bool SetFrontTrackWidth(int vehicle, float value) 349 | { 350 | if (WheelScript == null) 351 | return false; 352 | 353 | return WheelScript.API_SetFrontTrackWidth(vehicle, value); 354 | } 355 | 356 | public bool SetRearTrackWidth(int vehicle, float value) 357 | { 358 | if (WheelScript == null) 359 | return false; 360 | 361 | return WheelScript.API_SetRearTrackWidth(vehicle, value); 362 | } 363 | 364 | public float[] GetFrontCamber(int vehicle) 365 | { 366 | if (WheelScript == null) 367 | return new float[] { }; 368 | 369 | if (WheelScript.API_GetFrontCamber(vehicle, out float value)) 370 | return new float[] { value }; 371 | 372 | return new float[] { }; 373 | } 374 | 375 | public float[] GetRearCamber(int vehicle) 376 | { 377 | if (WheelScript == null) 378 | return new float[] { }; 379 | 380 | if (WheelScript.API_GetRearCamber(vehicle, out float value)) 381 | return new float[] { value }; 382 | 383 | return new float[] { }; 384 | } 385 | 386 | public float[] GetFrontTrackWidth(int vehicle) 387 | { 388 | if (WheelScript == null) 389 | return new float[] { }; 390 | 391 | if (WheelScript.API_GetFrontTrackWidth(vehicle, out float value)) 392 | return new float[] { value }; 393 | 394 | return new float[] { }; 395 | } 396 | 397 | public float[] GetRearTrackWidth(int vehicle) 398 | { 399 | if (WheelScript == null) 400 | return new float[] { }; 401 | 402 | if (WheelScript.API_GetRearTrackWidth(vehicle, out float value)) 403 | return new float[] { value }; 404 | 405 | return new float[] { }; 406 | } 407 | 408 | public bool SaveClientPreset(string id, int vehicle) 409 | { 410 | if (ClientPresetsScript == null) 411 | return false; 412 | 413 | return ClientPresetsScript.API_SavePreset(id, vehicle); 414 | } 415 | 416 | public bool LoadClientPreset(string id, int vehicle) 417 | { 418 | if (ClientPresetsScript == null) 419 | return false; 420 | 421 | return ClientPresetsScript.API_LoadPreset(id, vehicle); 422 | } 423 | 424 | public bool DeleteClientPreset(string id) 425 | { 426 | if (ClientPresetsScript == null) 427 | return false; 428 | 429 | return ClientPresetsScript.API_DeletePreset(id); 430 | } 431 | 432 | public string[] GetClientPresetList() 433 | { 434 | if (ClientPresetsScript == null) 435 | return new string[] { }; 436 | 437 | return ClientPresetsScript.API_GetClientPresetList().ToArray(); 438 | } 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /VStancer.Client/Scripts/WheelModScript.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using VStancer.Client.Data; 8 | using VStancer.Client.Preset; 9 | using VStancer.Client.UI; 10 | 11 | using CitizenFX.Core; 12 | using static CitizenFX.Core.Native.API; 13 | 14 | namespace VStancer.Client.Scripts 15 | { 16 | internal class WheelModScript : BaseScript 17 | { 18 | private readonly MainScript _mainScript; 19 | 20 | private long _lastTime; 21 | private int _playerVehicleHandle; 22 | private int _vehicleWheelMod; 23 | 24 | internal int VehicleWheelMod 25 | { 26 | get => _vehicleWheelMod; 27 | set 28 | { 29 | if (Equals(_vehicleWheelMod, value)) 30 | return; 31 | 32 | _vehicleWheelMod = value; 33 | VehicleWheelModChanged(); 34 | } 35 | } 36 | 37 | private WheelModData _wheelModData; 38 | internal WheelModData WheelModData 39 | { 40 | get => _wheelModData; 41 | set 42 | { 43 | if (Equals(_wheelModData, value)) 44 | return; 45 | 46 | _wheelModData = value; 47 | WheelModDataChanged?.Invoke(this, EventArgs.Empty); 48 | } 49 | } 50 | 51 | internal VStancerConfig Config => _mainScript.Config; 52 | internal WheelModMenu Menu { get; private set; } 53 | public bool DataIsValid => _playerVehicleHandle != -1 && WheelModData != null && VehicleWheelMod != -1; 54 | 55 | internal const string ExtraResetID = "vstancer_extra_reset"; 56 | 57 | internal const string WheelWidthID = "vstancer_extra_width"; 58 | internal const string WheelSizeID = "vstancer_extra_size"; 59 | internal const string DefaultWidthID = "vstancer_extra_width_def"; 60 | internal const string DefaultSizeID = "vstancer_extra_size_def"; 61 | 62 | internal const string FrontTireColliderWidthID = "vstancer_extra_tirecollider_width_f"; 63 | internal const string FrontTireColliderSizeID = "vstancer_extra_tirecollider_size_f"; 64 | internal const string FrontRimColliderSizeID = "vstancer_extra_rimcollider_size_f"; 65 | 66 | internal const string RearTireColliderWidthID = "vstancer_extra_tirecollider_width_r"; 67 | internal const string RearTireColliderSizeID = "vstancer_extra_tirecollider_size_r"; 68 | internal const string RearRimColliderSizeID = "vstancer_extra_rimcollider_size_r"; 69 | 70 | internal const string DefaultFrontTireColliderWidthID = "vstancer_extra_tirecollider_width_f_def"; 71 | internal const string DefaultFrontTireColliderSizeID = "vstancer_extra_tirecollider_size_f_def"; 72 | internal const string DefaultFrontRimColliderSizeID = "vstancer_extra_rimcollider_size_f_def"; 73 | 74 | internal const string DefaultRearTireColliderWidthID = "vstancer_extra_tirecollider_width_r_def"; 75 | internal const string DefaultRearTireColliderSizeID = "vstancer_extra_tirecollider_size_r_def"; 76 | internal const string DefaultRearRimColliderSizeID = "vstancer_extra_rimcollider_size_r_def"; 77 | 78 | public event EventHandler WheelModDataChanged; 79 | 80 | internal WheelModScript(MainScript mainScript) 81 | { 82 | _mainScript = mainScript; 83 | 84 | RegisterDecorators(); 85 | 86 | _lastTime = GetGameTimer(); 87 | 88 | _playerVehicleHandle = -1; 89 | VehicleWheelMod = -1; 90 | WheelModData = null; 91 | 92 | if (!_mainScript.Config.DisableMenu) 93 | { 94 | Menu = new WheelModMenu(this); 95 | Menu.FloatPropertyChangedEvent += OnMenuFloatPropertyChanged; 96 | Menu.ResetPropertiesEvent += (sender, id) => OnMenuCommandInvoked(id); 97 | } 98 | 99 | // TODO: Consider using a task as workaround for values resetting when any tuning part is installed 100 | //Tick += TickTask; 101 | Tick += TimedTask; 102 | 103 | mainScript.PlayerVehicleHandleChanged += (sender, handle) => PlayerVehicleChanged(handle); 104 | 105 | WheelModDataChanged += (sender, args) => OnWheelModDataChanged(); 106 | 107 | PlayerVehicleChanged(_mainScript.PlayerVehicleHandle); 108 | } 109 | 110 | //private async Task TickTask() 111 | //{ 112 | // await Task.FromResult(0); 113 | // 114 | // if (!DataIsValid) 115 | // return; 116 | // 117 | // UpdateVehicleUsingData(_playerVehicleHandle, WheelModData); 118 | //} 119 | 120 | private async Task GetVehicleWheelModTask() 121 | { 122 | if (_playerVehicleHandle == -1) 123 | return; 124 | 125 | VehicleWheelMod = GetVehicleMod(_playerVehicleHandle, 23); 126 | 127 | await Task.FromResult(0); 128 | } 129 | 130 | private void OnWheelModDataChanged() 131 | { 132 | if (WheelModData != null) 133 | WheelModData.PropertyChanged += OnWheelModDataPropertyChanged; 134 | } 135 | 136 | private async void VehicleWheelModChanged() 137 | { 138 | if (_playerVehicleHandle == -1) 139 | return; 140 | 141 | if (WheelModData != null) 142 | WheelModData.PropertyChanged -= OnWheelModDataPropertyChanged; 143 | 144 | if (VehicleWheelMod == -1) 145 | { 146 | WheelModData = null; 147 | RemoveDecoratorsFromVehicle(_playerVehicleHandle); 148 | return; 149 | } 150 | 151 | WheelModData = await GetWheelModDataFromEntity(_playerVehicleHandle); 152 | } 153 | 154 | private void PlayerVehicleChanged(int vehicle) 155 | { 156 | if (vehicle == _playerVehicleHandle) 157 | return; 158 | 159 | _playerVehicleHandle = vehicle; 160 | 161 | if (WheelModData != null) 162 | WheelModData.PropertyChanged -= OnWheelModDataPropertyChanged; 163 | 164 | if (_playerVehicleHandle == -1) 165 | { 166 | VehicleWheelMod = -1; 167 | WheelModData = null; 168 | Tick -= GetVehicleWheelModTask; 169 | return; 170 | } 171 | 172 | Tick += GetVehicleWheelModTask; 173 | } 174 | 175 | private async Task TimedTask() 176 | { 177 | long currentTime = (GetGameTimer() - _lastTime); 178 | 179 | if (currentTime > _mainScript.Config.Timer) 180 | { 181 | UpdateWorldVehiclesUsingDecorators(); 182 | 183 | _lastTime = GetGameTimer(); 184 | } 185 | 186 | await Task.FromResult(0); 187 | } 188 | 189 | private void UpdateVehicleUsingData(int vehicle, WheelModData data) 190 | { 191 | if (!DoesEntityExist(vehicle) || data == null) 192 | return; 193 | 194 | //if (!MathUtil.WithinEpsilon(GetVehicleWheelWidth(vehicle), data.WheelWidth, VStancerUtilities.Epsilon)) 195 | SetVehicleWheelWidth(vehicle, data.WheelWidth); 196 | 197 | //if (!MathUtil.WithinEpsilon(GetVehicleWheelSize(vehicle), data.WheelSize, VStancerUtilities.Epsilon)) 198 | SetVehicleWheelSize(vehicle, data.WheelSize); 199 | 200 | for (int i = 0; i < data.FrontWheelsCount; i++) 201 | { 202 | SetVehicleWheelTireColliderWidth(vehicle, i, data.FrontTireColliderWidth); 203 | SetVehicleWheelTireColliderSize(vehicle, i, data.FrontTireColliderSize); 204 | SetVehicleWheelRimColliderSize(vehicle, i, data.FrontRimColliderSize); 205 | } 206 | 207 | for (int i = data.FrontWheelsCount; i < data.WheelsCount; i++) 208 | { 209 | SetVehicleWheelTireColliderWidth(vehicle, i, data.RearTireColliderWidth); 210 | SetVehicleWheelTireColliderSize(vehicle, i, data.RearTireColliderSize); 211 | SetVehicleWheelRimColliderSize(vehicle, i, data.RearRimColliderSize); 212 | } 213 | } 214 | 215 | private void UpdateWorldVehiclesUsingDecorators() 216 | { 217 | foreach (int entity in _mainScript.GetCloseVehicleHandles()) 218 | { 219 | if (entity == _playerVehicleHandle) 220 | continue; 221 | 222 | UpdateVehicleUsingDecorators(entity); 223 | } 224 | } 225 | 226 | private async Task GetWheelModDataFromEntity(int vehicle) 227 | { 228 | if (!DoesEntityExist(vehicle)) 229 | return null; 230 | 231 | int wheelsCount = GetVehicleNumberOfWheels(vehicle); 232 | int frontCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); 233 | 234 | float wheelWidth_def; 235 | float wheelSize_def; 236 | 237 | // wait for data to actually update, required if wheel mod is changed too fast 238 | // and data read by GetVehicleWheelWidth and GetVehicleWheelSize didn't update yet resulting in 0 239 | if (DecorExistOn(vehicle, DefaultWidthID)) 240 | { 241 | wheelWidth_def = DecorGetFloat(vehicle, DefaultWidthID); 242 | } 243 | else 244 | { 245 | do 246 | { 247 | wheelWidth_def = GetVehicleWheelWidth(vehicle); 248 | await Delay(100); 249 | } while (MathUtil.IsZero(wheelWidth_def) || MathUtil.IsOne(wheelWidth_def)); 250 | } 251 | 252 | if (DecorExistOn(vehicle, DefaultSizeID)) 253 | { 254 | wheelSize_def = DecorGetFloat(vehicle, DefaultSizeID); 255 | } 256 | else 257 | { 258 | do 259 | { 260 | wheelSize_def = GetVehicleWheelSize(vehicle); 261 | await Delay(100); 262 | } while (MathUtil.IsZero(wheelSize_def) || MathUtil.IsOne(wheelSize_def)); 263 | } 264 | 265 | float frontTireColliderWidth_def = DecorExistOn(vehicle, DefaultFrontTireColliderWidthID) ? DecorGetFloat(vehicle, DefaultFrontTireColliderWidthID) : GetVehicleWheelTireColliderWidth(vehicle, 0); 266 | float frontTireColliderSize_def = DecorExistOn(vehicle, DefaultFrontTireColliderSizeID) ? DecorGetFloat(vehicle, DefaultFrontTireColliderSizeID) : GetVehicleWheelTireColliderSize(vehicle, 0); 267 | float frontRimColliderSize_def = DecorExistOn(vehicle, DefaultFrontRimColliderSizeID) ? DecorGetFloat(vehicle, DefaultFrontRimColliderSizeID) : GetVehicleWheelRimColliderSize(vehicle, 0); 268 | 269 | float rearTireColliderWidth_def = DecorExistOn(vehicle, DefaultRearTireColliderWidthID) ? DecorGetFloat(vehicle, DefaultRearTireColliderWidthID) : GetVehicleWheelTireColliderWidth(vehicle, frontCount); 270 | float rearTireColliderSize_def = DecorExistOn(vehicle, DefaultRearTireColliderSizeID) ? DecorGetFloat(vehicle, DefaultRearTireColliderSizeID) : GetVehicleWheelTireColliderSize(vehicle, frontCount); 271 | float rearRimColliderSize_def = DecorExistOn(vehicle, DefaultRearRimColliderSizeID) ? DecorGetFloat(vehicle, DefaultRearRimColliderSizeID) : GetVehicleWheelRimColliderSize(vehicle, frontCount); 272 | 273 | // Create the preset with the default values 274 | return new WheelModData(wheelsCount, wheelWidth_def, wheelSize_def, 275 | frontTireColliderWidth_def, frontTireColliderSize_def, frontRimColliderSize_def, 276 | rearTireColliderWidth_def, rearTireColliderSize_def, rearRimColliderSize_def) 277 | { 278 | // Assign the current values 279 | WheelWidth = DecorExistOn(vehicle, WheelWidthID) ? DecorGetFloat(vehicle, WheelWidthID) : wheelWidth_def, 280 | WheelSize = DecorExistOn(vehicle, WheelSizeID) ? DecorGetFloat(vehicle, WheelSizeID) : wheelSize_def, 281 | 282 | FrontTireColliderWidth = DecorExistOn(vehicle, FrontTireColliderWidthID) ? DecorGetFloat(vehicle, FrontTireColliderWidthID) : frontTireColliderWidth_def, 283 | FrontTireColliderSize = DecorExistOn(vehicle, FrontTireColliderSizeID) ? DecorGetFloat(vehicle, FrontTireColliderSizeID) : frontTireColliderSize_def, 284 | FrontRimColliderSize = DecorExistOn(vehicle, FrontRimColliderSizeID) ? DecorGetFloat(vehicle, FrontRimColliderSizeID) : frontRimColliderSize_def, 285 | 286 | RearTireColliderWidth = DecorExistOn(vehicle, RearTireColliderWidthID) ? DecorGetFloat(vehicle, RearTireColliderWidthID) : rearTireColliderWidth_def, 287 | RearTireColliderSize = DecorExistOn(vehicle, RearTireColliderSizeID) ? DecorGetFloat(vehicle, RearTireColliderSizeID) : rearTireColliderSize_def, 288 | RearRimColliderSize = DecorExistOn(vehicle, RearRimColliderSizeID) ? DecorGetFloat(vehicle, RearRimColliderSizeID) : rearRimColliderSize_def 289 | }; 290 | } 291 | 292 | private void SetWheelWidthUsingData(int vehicle, WheelModData data) 293 | { 294 | float value = data.WheelWidth; 295 | 296 | bool result = SetVehicleWheelWidth(vehicle, value); 297 | if (result) 298 | { 299 | float defValue = data.DefaultWheelWidth; 300 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultWidthID, defValue, value); 301 | VStancerUtilities.UpdateFloatDecorator(vehicle, WheelWidthID, value, defValue); 302 | } 303 | } 304 | 305 | private void SetWheelSizeUsingData(int vehicle, WheelModData data) 306 | { 307 | float value = data.WheelSize; 308 | 309 | bool result = SetVehicleWheelSize(vehicle, value); 310 | if (result) 311 | { 312 | float defValue = data.DefaultWheelSize; 313 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultSizeID, defValue, value); 314 | VStancerUtilities.UpdateFloatDecorator(vehicle, WheelSizeID, value, defValue); 315 | } 316 | } 317 | 318 | private void SetFrontTireColliderWidthUsingData(int vehicle, WheelModData data) 319 | { 320 | float value = data.FrontTireColliderWidth; 321 | float defValue = data.DefaultFrontTireColliderWidth; 322 | 323 | for (int i = 0; i < WheelModData.FrontWheelsCount; i++) 324 | SetVehicleWheelTireColliderWidth(vehicle, i, value); 325 | 326 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontTireColliderWidthID, defValue, value); 327 | VStancerUtilities.UpdateFloatDecorator(vehicle, FrontTireColliderWidthID, value, defValue); 328 | } 329 | 330 | private void SetFrontTireColliderSizeUsingData(int vehicle, WheelModData data) 331 | { 332 | float value = data.FrontTireColliderSize; 333 | float defValue = data.DefaultFrontTireColliderSize; 334 | 335 | for (int i = 0; i < WheelModData.FrontWheelsCount; i++) 336 | SetVehicleWheelTireColliderSize(vehicle, i, value); 337 | 338 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontTireColliderSizeID, defValue, value); 339 | VStancerUtilities.UpdateFloatDecorator(vehicle, FrontTireColliderSizeID, value, defValue); 340 | } 341 | 342 | private void SetFrontRimColliderSizeUsingData(int vehicle, WheelModData data) 343 | { 344 | float value = data.FrontRimColliderSize; 345 | float defValue = data.DefaultFrontRimColliderSize; 346 | 347 | for (int i = 0; i < WheelModData.FrontWheelsCount; i++) 348 | SetVehicleWheelRimColliderSize(vehicle, i, value); 349 | 350 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontRimColliderSizeID, defValue, value); 351 | VStancerUtilities.UpdateFloatDecorator(vehicle, FrontRimColliderSizeID, value, defValue); 352 | } 353 | 354 | private void SetRearTireColliderWidthUsingData(int vehicle, WheelModData data) 355 | { 356 | float value = data.RearTireColliderWidth; 357 | float defValue = data.DefaultRearTireColliderWidth; 358 | 359 | for (int i = WheelModData.FrontWheelsCount; i < WheelModData.WheelsCount; i++) 360 | SetVehicleWheelTireColliderWidth(vehicle, i, value); 361 | 362 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearTireColliderWidthID, defValue, value); 363 | VStancerUtilities.UpdateFloatDecorator(vehicle, RearTireColliderWidthID, value, defValue); 364 | } 365 | 366 | private void SetRearTireColliderSizeUsingData(int vehicle, WheelModData data) 367 | { 368 | float value = data.RearTireColliderSize; 369 | float defValue = data.DefaultRearTireColliderSize; 370 | 371 | for (int i = WheelModData.FrontWheelsCount; i < WheelModData.WheelsCount; i++) 372 | SetVehicleWheelTireColliderSize(vehicle, i, value); 373 | 374 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearTireColliderSizeID, defValue, value); 375 | VStancerUtilities.UpdateFloatDecorator(vehicle, RearTireColliderSizeID, value, defValue); 376 | } 377 | 378 | private void SetRearRimColliderSizeUsingData(int vehicle, WheelModData data) 379 | { 380 | float value = data.RearRimColliderSize; 381 | float defValue = data.DefaultRearRimColliderSize; 382 | 383 | for (int i = WheelModData.FrontWheelsCount; i < WheelModData.WheelsCount; i++) 384 | SetVehicleWheelRimColliderSize(vehicle, i, value); 385 | 386 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearRimColliderSizeID, defValue, value); 387 | VStancerUtilities.UpdateFloatDecorator(vehicle, RearRimColliderSizeID, value, defValue); 388 | } 389 | 390 | private void OnWheelModDataPropertyChanged(string propertyName, float value) 391 | { 392 | switch (propertyName) 393 | { 394 | case nameof(WheelModData.WheelWidth): 395 | SetWheelWidthUsingData(_playerVehicleHandle, WheelModData); 396 | break; 397 | 398 | case nameof(WheelModData.WheelSize): 399 | SetWheelSizeUsingData(_playerVehicleHandle, WheelModData); 400 | break; 401 | 402 | case nameof(WheelModData.FrontTireColliderWidth): 403 | SetFrontTireColliderWidthUsingData(_playerVehicleHandle, WheelModData); 404 | break; 405 | case nameof(WheelModData.FrontTireColliderSize): 406 | SetFrontTireColliderSizeUsingData(_playerVehicleHandle, WheelModData); 407 | break; 408 | case nameof(WheelModData.FrontRimColliderSize): 409 | SetFrontRimColliderSizeUsingData(_playerVehicleHandle, WheelModData); 410 | break; 411 | 412 | case nameof(WheelModData.RearTireColliderWidth): 413 | SetRearTireColliderWidthUsingData(_playerVehicleHandle, WheelModData); 414 | break; 415 | case nameof(WheelModData.RearTireColliderSize): 416 | SetRearTireColliderSizeUsingData(_playerVehicleHandle, WheelModData); 417 | break; 418 | case nameof(WheelModData.RearRimColliderSize): 419 | SetRearRimColliderSizeUsingData(_playerVehicleHandle, WheelModData); 420 | break; 421 | 422 | case nameof(WheelModData.Reset): 423 | 424 | // TODO: Avoid updating decorators if we have to remove them anyway 425 | SetWheelWidthUsingData(_playerVehicleHandle, WheelModData); 426 | SetWheelSizeUsingData(_playerVehicleHandle, WheelModData); 427 | SetFrontTireColliderWidthUsingData(_playerVehicleHandle, WheelModData); 428 | SetFrontTireColliderSizeUsingData(_playerVehicleHandle, WheelModData); 429 | SetFrontRimColliderSizeUsingData(_playerVehicleHandle, WheelModData); 430 | SetRearTireColliderWidthUsingData(_playerVehicleHandle, WheelModData); 431 | SetRearTireColliderSizeUsingData(_playerVehicleHandle, WheelModData); 432 | SetRearRimColliderSizeUsingData(_playerVehicleHandle, WheelModData); 433 | 434 | RemoveDecoratorsFromVehicle(_playerVehicleHandle); 435 | 436 | WheelModDataChanged?.Invoke(this, EventArgs.Empty); 437 | break; 438 | 439 | default: 440 | break; 441 | } 442 | } 443 | 444 | private void UpdateVehicleUsingDecorators(int vehicle) 445 | { 446 | int wheelsCount = GetVehicleNumberOfWheels(vehicle); 447 | int frontWheelsCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); 448 | 449 | if (DecorExistOn(vehicle, WheelSizeID)) 450 | SetVehicleWheelSize(vehicle, DecorGetFloat(vehicle, WheelSizeID)); 451 | 452 | if (DecorExistOn(vehicle, WheelWidthID)) 453 | SetVehicleWheelWidth(vehicle, DecorGetFloat(vehicle, WheelWidthID)); 454 | 455 | if (DecorExistOn(vehicle, FrontTireColliderWidthID)) 456 | { 457 | float value = DecorGetFloat(vehicle, FrontTireColliderWidthID); 458 | for (int i = 0; i < frontWheelsCount; i++) 459 | SetVehicleWheelTireColliderWidth(vehicle, i, value); 460 | } 461 | 462 | if (DecorExistOn(vehicle, FrontTireColliderSizeID)) 463 | { 464 | float value = DecorGetFloat(vehicle, FrontTireColliderSizeID); 465 | for (int i = 0; i < frontWheelsCount; i++) 466 | SetVehicleWheelTireColliderSize(vehicle, i, value); 467 | } 468 | 469 | if (DecorExistOn(vehicle, FrontRimColliderSizeID)) 470 | { 471 | float value = DecorGetFloat(vehicle, FrontRimColliderSizeID); 472 | for (int i = 0; i < frontWheelsCount; i++) 473 | SetVehicleWheelRimColliderSize(vehicle, i, value); 474 | } 475 | 476 | if (DecorExistOn(vehicle, RearTireColliderWidthID)) 477 | { 478 | float value = DecorGetFloat(vehicle, RearTireColliderWidthID); 479 | for (int i = frontWheelsCount; i < wheelsCount; i++) 480 | SetVehicleWheelTireColliderWidth(vehicle, i, value); 481 | } 482 | 483 | if (DecorExistOn(vehicle, RearTireColliderSizeID)) 484 | { 485 | float value = DecorGetFloat(vehicle, RearTireColliderSizeID); 486 | for (int i = frontWheelsCount; i < wheelsCount; i++) 487 | SetVehicleWheelTireColliderSize(vehicle, i, value); 488 | } 489 | 490 | if (DecorExistOn(vehicle, RearRimColliderSizeID)) 491 | { 492 | float value = DecorGetFloat(vehicle, RearRimColliderSizeID); 493 | for (int i = frontWheelsCount; i < wheelsCount; i++) 494 | SetVehicleWheelRimColliderSize(vehicle, i, value); 495 | } 496 | } 497 | 498 | private void RegisterDecorators() 499 | { 500 | DecorRegister(WheelWidthID, 1); 501 | DecorRegister(DefaultWidthID, 1); 502 | 503 | DecorRegister(WheelSizeID, 1); 504 | DecorRegister(DefaultSizeID, 1); 505 | 506 | DecorRegister(FrontTireColliderWidthID, 1); 507 | DecorRegister(FrontTireColliderSizeID, 1); 508 | DecorRegister(FrontRimColliderSizeID, 1); 509 | DecorRegister(DefaultFrontTireColliderWidthID, 1); 510 | DecorRegister(DefaultFrontTireColliderSizeID, 1); 511 | DecorRegister(DefaultFrontRimColliderSizeID, 1); 512 | 513 | DecorRegister(RearTireColliderWidthID, 1); 514 | DecorRegister(RearTireColliderSizeID, 1); 515 | DecorRegister(RearRimColliderSizeID, 1); 516 | DecorRegister(DefaultRearTireColliderWidthID, 1); 517 | DecorRegister(DefaultRearTireColliderSizeID, 1); 518 | DecorRegister(DefaultRearRimColliderSizeID, 1); 519 | } 520 | 521 | private void RemoveDecoratorsFromVehicle(int vehicle) 522 | { 523 | if (DecorExistOn(vehicle, WheelSizeID)) 524 | DecorRemove(vehicle, WheelSizeID); 525 | 526 | if (DecorExistOn(vehicle, WheelWidthID)) 527 | DecorRemove(vehicle, WheelWidthID); 528 | 529 | if (DecorExistOn(vehicle, DefaultSizeID)) 530 | DecorRemove(vehicle, DefaultSizeID); 531 | 532 | if (DecorExistOn(vehicle, DefaultWidthID)) 533 | DecorRemove(vehicle, DefaultWidthID); 534 | 535 | if (DecorExistOn(vehicle, FrontTireColliderWidthID)) 536 | DecorRemove(vehicle, FrontTireColliderWidthID); 537 | 538 | if (DecorExistOn(vehicle, FrontTireColliderSizeID)) 539 | DecorRemove(vehicle, FrontTireColliderSizeID); 540 | 541 | if (DecorExistOn(vehicle, FrontRimColliderSizeID)) 542 | DecorRemove(vehicle, FrontRimColliderSizeID); 543 | 544 | if (DecorExistOn(vehicle, DefaultFrontTireColliderWidthID)) 545 | DecorRemove(vehicle, DefaultFrontTireColliderWidthID); 546 | 547 | if (DecorExistOn(vehicle, DefaultFrontTireColliderSizeID)) 548 | DecorRemove(vehicle, DefaultFrontTireColliderSizeID); 549 | 550 | if (DecorExistOn(vehicle, DefaultFrontRimColliderSizeID)) 551 | DecorRemove(vehicle, DefaultFrontRimColliderSizeID); 552 | 553 | if (DecorExistOn(vehicle, RearTireColliderWidthID)) 554 | DecorRemove(vehicle, RearTireColliderWidthID); 555 | 556 | if (DecorExistOn(vehicle, RearTireColliderSizeID)) 557 | DecorRemove(vehicle, RearTireColliderSizeID); 558 | 559 | if (DecorExistOn(vehicle, RearRimColliderSizeID)) 560 | DecorRemove(vehicle, RearRimColliderSizeID); 561 | 562 | if (DecorExistOn(vehicle, DefaultRearTireColliderWidthID)) 563 | DecorRemove(vehicle, DefaultRearTireColliderWidthID); 564 | 565 | if (DecorExistOn(vehicle, DefaultRearTireColliderSizeID)) 566 | DecorRemove(vehicle, DefaultRearTireColliderSizeID); 567 | 568 | if (DecorExistOn(vehicle, DefaultRearRimColliderSizeID)) 569 | DecorRemove(vehicle, DefaultRearRimColliderSizeID); 570 | } 571 | 572 | private void OnMenuCommandInvoked(string commandID) 573 | { 574 | if (!DataIsValid) 575 | return; 576 | 577 | switch (commandID) 578 | { 579 | case ExtraResetID: 580 | WheelModData.Reset(); 581 | break; 582 | } 583 | } 584 | 585 | private void OnMenuFloatPropertyChanged(string id, float value) 586 | { 587 | if (!DataIsValid) 588 | return; 589 | 590 | switch (id) 591 | { 592 | case WheelSizeID: 593 | WheelModData.WheelSize = value; 594 | WheelModData.FrontTireColliderSize = value / WheelModData.DefaultFrontTireColliderSizeRatio; 595 | WheelModData.FrontRimColliderSize = value / WheelModData.DefaultFrontRimColliderSizeRatio; 596 | WheelModData.RearTireColliderSize = value / WheelModData.DefaultRearTireColliderSizeRatio; 597 | WheelModData.RearRimColliderSize = value / WheelModData.DefaultRearRimColliderSizeRatio; 598 | break; 599 | case WheelWidthID: 600 | WheelModData.WheelWidth = value; 601 | WheelModData.FrontTireColliderWidth = value / WheelModData.DefaultFrontTireColliderWidthRatio; 602 | WheelModData.RearTireColliderWidth = value / WheelModData.DefaultRearTireColliderWidthRatio; 603 | break; 604 | 605 | // Update colliders with visual but keep visual/collider ratio constant as with default wheels 606 | // This ratio is usually 50% for for vanilla wheel mod 607 | 608 | /* 609 | case FrontTireColliderWidthID: 610 | WheelModData.FrontTireColliderWidth = value; 611 | break; 612 | case FrontTireColliderSizeID: 613 | WheelModData.FrontTireColliderSize = value; 614 | break; 615 | case FrontRimColliderSizeID: 616 | WheelModData.FrontRimColliderSize = value; 617 | break; 618 | 619 | case RearTireColliderWidthID: 620 | WheelModData.RearTireColliderWidth = value; 621 | break; 622 | case RearTireColliderSizeID: 623 | WheelModData.RearTireColliderSize = value; 624 | break; 625 | case RearRimColliderSizeID: 626 | WheelModData.RearRimColliderSize = value; 627 | break; 628 | */ 629 | } 630 | } 631 | 632 | private bool EntityHasDecorators(int entity) 633 | { 634 | return ( 635 | DecorExistOn(entity, WheelSizeID) || 636 | DecorExistOn(entity, WheelWidthID) || 637 | DecorExistOn(entity, DefaultSizeID) || 638 | DecorExistOn(entity, DefaultWidthID) || 639 | DecorExistOn(entity, FrontTireColliderWidthID) || 640 | DecorExistOn(entity, FrontTireColliderSizeID) || 641 | DecorExistOn(entity, FrontRimColliderSizeID) || 642 | DecorExistOn(entity, DefaultFrontTireColliderWidthID) || 643 | DecorExistOn(entity, DefaultFrontTireColliderSizeID) || 644 | DecorExistOn(entity, DefaultFrontRimColliderSizeID) || 645 | DecorExistOn(entity, RearTireColliderWidthID) || 646 | DecorExistOn(entity, RearTireColliderSizeID) || 647 | DecorExistOn(entity, RearRimColliderSizeID) || 648 | DecorExistOn(entity, DefaultRearTireColliderWidthID) || 649 | DecorExistOn(entity, DefaultRearTireColliderSizeID) || 650 | DecorExistOn(entity, DefaultRearRimColliderSizeID) 651 | ); 652 | } 653 | 654 | internal void PrintVehiclesWithDecorators(IEnumerable vehiclesList) 655 | { 656 | IEnumerable entities = vehiclesList.Where(entity => EntityHasDecorators(entity)); 657 | 658 | Debug.WriteLine($"{nameof(WheelModScript)}: Vehicles with decorators: {entities.Count()}"); 659 | 660 | foreach (int item in entities) 661 | Debug.WriteLine($"Vehicle: {item}"); 662 | } 663 | 664 | internal void PrintDecoratorsInfo(int vehicle) 665 | { 666 | if (!DoesEntityExist(vehicle)) 667 | { 668 | Debug.WriteLine($"{nameof(WheelModScript)}: Can't find vehicle with handle {vehicle}"); 669 | return; 670 | } 671 | 672 | int wheelsCount = GetVehicleNumberOfWheels(vehicle); 673 | int netID = NetworkGetNetworkIdFromEntity(vehicle); 674 | StringBuilder s = new StringBuilder(); 675 | s.AppendLine($"{nameof(WheelModScript)}: Vehicle:{vehicle} netID:{netID} wheelsCount:{wheelsCount}"); 676 | 677 | if (DecorExistOn(vehicle, WheelSizeID)) 678 | 679 | s.AppendLine($"{WheelSizeID}: {DecorGetFloat(vehicle, WheelSizeID)}"); 680 | 681 | if (DecorExistOn(vehicle, DefaultSizeID)) 682 | s.AppendLine($"{DefaultSizeID}: {DecorGetFloat(vehicle, DefaultSizeID)}"); 683 | 684 | if (DecorExistOn(vehicle, WheelWidthID)) 685 | s.AppendLine($"{WheelWidthID}: {DecorGetFloat(vehicle, WheelWidthID)}"); 686 | 687 | if (DecorExistOn(vehicle, DefaultWidthID)) 688 | s.AppendLine($"{DefaultWidthID}: {DecorGetFloat(vehicle, DefaultWidthID)}"); 689 | 690 | if (DecorExistOn(vehicle, FrontTireColliderWidthID)) 691 | s.AppendLine($"{FrontTireColliderWidthID}: {DecorGetFloat(vehicle, FrontTireColliderWidthID)}"); 692 | 693 | if (DecorExistOn(vehicle, DefaultFrontTireColliderWidthID)) 694 | s.AppendLine($"{DefaultFrontTireColliderWidthID}: {DecorGetFloat(vehicle, DefaultFrontTireColliderWidthID)}"); 695 | 696 | if (DecorExistOn(vehicle, RearTireColliderWidthID)) 697 | s.AppendLine($"{RearTireColliderWidthID}: {DecorGetFloat(vehicle, RearTireColliderWidthID)}"); 698 | 699 | if (DecorExistOn(vehicle, DefaultRearTireColliderWidthID)) 700 | s.AppendLine($"{DefaultRearTireColliderWidthID}: {DecorGetFloat(vehicle, DefaultRearTireColliderWidthID)}"); 701 | 702 | if (DecorExistOn(vehicle, FrontTireColliderSizeID)) 703 | s.AppendLine($"{FrontTireColliderSizeID}: {DecorGetFloat(vehicle, FrontTireColliderSizeID)}"); 704 | 705 | if (DecorExistOn(vehicle, DefaultFrontTireColliderSizeID)) 706 | s.AppendLine($"{DefaultFrontTireColliderSizeID}: {DecorGetFloat(vehicle, DefaultFrontTireColliderSizeID)}"); 707 | 708 | if (DecorExistOn(vehicle, RearTireColliderSizeID)) 709 | s.AppendLine($"{RearTireColliderSizeID}: {DecorGetFloat(vehicle, RearTireColliderSizeID)}"); 710 | 711 | if (DecorExistOn(vehicle, DefaultRearTireColliderSizeID)) 712 | s.AppendLine($"{DefaultRearTireColliderSizeID}: {DecorGetFloat(vehicle, DefaultRearTireColliderSizeID)}"); 713 | 714 | if (DecorExistOn(vehicle, FrontRimColliderSizeID)) 715 | s.AppendLine($"{FrontRimColliderSizeID}: {DecorGetFloat(vehicle, FrontRimColliderSizeID)}"); 716 | 717 | if (DecorExistOn(vehicle, DefaultFrontRimColliderSizeID)) 718 | s.AppendLine($"{DefaultFrontRimColliderSizeID}: {DecorGetFloat(vehicle, DefaultFrontRimColliderSizeID)}"); 719 | 720 | if (DecorExistOn(vehicle, RearRimColliderSizeID)) 721 | s.AppendLine($"{RearRimColliderSizeID}: {DecorGetFloat(vehicle, RearRimColliderSizeID)}"); 722 | 723 | if (DecorExistOn(vehicle, DefaultRearRimColliderSizeID)) 724 | s.AppendLine($"{DefaultRearRimColliderSizeID}: {DecorGetFloat(vehicle, DefaultRearRimColliderSizeID)}"); 725 | 726 | Debug.WriteLine(s.ToString()); 727 | } 728 | 729 | internal WheelModPreset GetWheelModPreset() 730 | { 731 | if (!DataIsValid) 732 | return null; 733 | 734 | // Only required to avoid saving this preset locally when not required 735 | if (!WheelModData.IsEdited) 736 | return null; 737 | 738 | return new WheelModPreset(WheelModData); 739 | } 740 | 741 | internal async Task SetWheelModPreset(WheelModPreset preset) 742 | { 743 | if (!DataIsValid || preset == null) 744 | return; 745 | 746 | // TODO: Check if values are within limits 747 | 748 | WheelModData.WheelSize = preset.WheelSize; 749 | WheelModData.WheelWidth = preset.WheelWidth; 750 | 751 | WheelModData.FrontTireColliderWidth = preset.WheelWidth / WheelModData.DefaultFrontTireColliderWidthRatio; 752 | WheelModData.FrontTireColliderSize = preset.WheelSize / WheelModData.DefaultFrontTireColliderSizeRatio; 753 | WheelModData.FrontRimColliderSize = preset.WheelSize / WheelModData.DefaultFrontRimColliderSizeRatio; 754 | WheelModData.RearTireColliderWidth = preset.WheelWidth / WheelModData.DefaultRearTireColliderWidthRatio; 755 | WheelModData.RearTireColliderSize = preset.WheelSize / WheelModData.DefaultRearTireColliderSizeRatio; 756 | WheelModData.RearRimColliderSize = preset.WheelSize / WheelModData.DefaultRearRimColliderSizeRatio; 757 | 758 | //WheelModData.FrontTireColliderWidth = preset.FrontTireColliderWidth; 759 | //WheelModData.FrontTireColliderSize = preset.FrontTireColliderSize; 760 | //WheelModData.FrontRimColliderSize = preset.FrontRimColliderSize; 761 | //WheelModData.RearTireColliderWidth = preset.RearTireColliderWidth; 762 | //WheelModData.RearTireColliderSize = preset.RearTireColliderSize; 763 | //WheelModData.RearRimColliderSize = preset.RearRimColliderSize; 764 | 765 | Debug.WriteLine($"{nameof(WheelModScript)}: wheel mod preset applied"); 766 | await Delay(200); 767 | WheelModDataChanged?.Invoke(this, EventArgs.Empty); 768 | } 769 | } 770 | } 771 | -------------------------------------------------------------------------------- /VStancer.Client/Scripts/WheelScript.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using VStancer.Client.Data; 8 | using VStancer.Client.Preset; 9 | using VStancer.Client.UI; 10 | 11 | using CitizenFX.Core; 12 | using static CitizenFX.Core.Native.API; 13 | 14 | namespace VStancer.Client.Scripts 15 | { 16 | internal class WheelScript : BaseScript 17 | { 18 | private readonly MainScript _mainScript; 19 | 20 | private long _lastTime; 21 | private int _playerVehicleHandle; 22 | 23 | private WheelData _wheelData; 24 | internal WheelData WheelData 25 | { 26 | get => _wheelData; 27 | set 28 | { 29 | if (Equals(_wheelData, value)) 30 | return; 31 | 32 | _wheelData = value; 33 | WheelDataChanged?.Invoke(this, EventArgs.Empty); 34 | } 35 | } 36 | 37 | internal VStancerConfig Config => _mainScript.Config; 38 | internal WheelMenu Menu { get; private set; } 39 | 40 | internal bool DataIsValid => _playerVehicleHandle != -1 && WheelData != null; 41 | 42 | internal const string FrontTrackWidthID = "vstancer_trackwidth_f"; 43 | internal const string RearTrackWidthID = "vstancer_trackwidth_r"; 44 | internal const string FrontCamberID = "vstancer_camber_f"; 45 | internal const string RearCamberID = "vstancer_camber_r"; 46 | 47 | internal const string DefaultFrontTrackWidthID = "vstancer_trackwidth_f_def"; 48 | internal const string DefaultRearTrackWidthID = "vstancer_trackwidth_r_def"; 49 | internal const string DefaultFrontCamberID = "vstancer_camber_f_def"; 50 | internal const string DefaultRearCamberID = "vstancer_camber_r_def"; 51 | 52 | internal const string ResetID = "vstancer_reset"; 53 | 54 | internal event EventHandler WheelDataChanged; 55 | 56 | internal WheelScript(MainScript mainScript) 57 | { 58 | _mainScript = mainScript; 59 | 60 | _lastTime = GetGameTimer(); 61 | _playerVehicleHandle = -1; 62 | 63 | RegisterDecorators(); 64 | 65 | if (!_mainScript.Config.DisableMenu) 66 | { 67 | Menu = new WheelMenu(this); 68 | Menu.FloatPropertyChangedEvent += OnMenuFloatPropertyChanged; 69 | Menu.ResetPropertiesEvent += (sender, id) => OnMenuCommandInvoked(id); 70 | } 71 | 72 | Tick += UpdateWorldVehiclesTask; 73 | Tick += UpdatePlayerVehicleTask; 74 | //Tick += TimedTask; 75 | 76 | mainScript.PlayerVehicleHandleChanged += (sender, handle) => PlayerVehicleChanged(handle); 77 | PlayerVehicleChanged(_mainScript.PlayerVehicleHandle); 78 | 79 | WheelDataChanged += (sender, args) => OnWheelDataChanged(); 80 | } 81 | 82 | private void OnWheelDataChanged() 83 | { 84 | if (WheelData != null) 85 | WheelData.PropertyChanged += OnWheelDataPropertyChanged; 86 | } 87 | 88 | private void PlayerVehicleChanged(int vehicle) 89 | { 90 | if (vehicle == _playerVehicleHandle) 91 | return; 92 | 93 | _playerVehicleHandle = vehicle; 94 | 95 | if (WheelData != null) 96 | WheelData.PropertyChanged -= OnWheelDataPropertyChanged; 97 | 98 | if (_playerVehicleHandle == -1) 99 | { 100 | WheelData = null; 101 | Tick -= UpdatePlayerVehicleTask; 102 | return; 103 | } 104 | 105 | WheelData = GetWheelDataFromEntity(vehicle); 106 | 107 | Tick += UpdatePlayerVehicleTask; 108 | } 109 | 110 | private async Task UpdatePlayerVehicleTask() 111 | { 112 | await Task.FromResult(0); 113 | 114 | // Check if current vehicle needs to be refreshed 115 | if (DataIsValid && WheelData.IsEdited) 116 | UpdateVehicleUsingWheelData(_playerVehicleHandle, WheelData); 117 | } 118 | 119 | private async Task UpdateWorldVehiclesTask() 120 | { 121 | await Task.FromResult(0); 122 | 123 | foreach (int entity in _mainScript.GetCloseVehicleHandles()) 124 | { 125 | if (entity == _playerVehicleHandle) 126 | continue; 127 | 128 | UpdateVehicleUsingDecorators(entity); 129 | } 130 | } 131 | 132 | //private async Task TimedTask() 133 | //{ 134 | // long currentTime = (GetGameTimer() - _lastTime); 135 | // 136 | // if (currentTime > _mainScript.Config.Timer) 137 | // { 138 | // // Check if decorators needs to be updated 139 | // //if (DataIsValid) 140 | // // UpdateVehicleDecorators(_playerVehicleHandle, WheelData); 141 | // 142 | // _lastTime = GetGameTimer(); 143 | // } 144 | // 145 | // await Task.FromResult(0); 146 | //} 147 | 148 | private async void OnWheelDataPropertyChanged(string propertyName, float value) 149 | { 150 | if (!DataIsValid) 151 | return; 152 | 153 | switch(propertyName) 154 | { 155 | case nameof(WheelData.Reset): 156 | RemoveDecoratorsFromVehicle(_playerVehicleHandle); 157 | UpdateVehicleUsingWheelData(_playerVehicleHandle, WheelData); 158 | await Delay(50); 159 | WheelDataChanged?.Invoke(this, EventArgs.Empty); 160 | break; 161 | 162 | case nameof(WheelData.FrontCamber): 163 | SetFrontCamberUsingData(_playerVehicleHandle, WheelData); 164 | break; 165 | case nameof(WheelData.RearCamber): 166 | SetRearCamberUsingData(_playerVehicleHandle, WheelData); 167 | break; 168 | case nameof(WheelData.FrontTrackWidth): 169 | SetFrontTrackWidthUsingData(_playerVehicleHandle, WheelData); 170 | break; 171 | case nameof(WheelData.RearTrackWidth): 172 | SetRearTrackWidthUsingData(_playerVehicleHandle, WheelData); 173 | break; 174 | } 175 | } 176 | 177 | private void SetFrontCamberUsingData(int vehicle, WheelData data) 178 | { 179 | if (!DoesEntityExist(vehicle) || data == null) 180 | return; 181 | 182 | int frontWheelsCount = data.FrontWheelsCount; 183 | WheelDataNode[] nodes = data.GetNodes(); 184 | 185 | for (int i = 0; i < frontWheelsCount; i++) 186 | SetVehicleWheelYRotation(vehicle, i, nodes[i].RotationY); 187 | 188 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontCamberID, data.DefaultFrontCamber, data.FrontCamber); 189 | VStancerUtilities.UpdateFloatDecorator(vehicle, FrontCamberID, data.FrontCamber, data.DefaultFrontCamber); 190 | } 191 | 192 | private void SetRearCamberUsingData(int vehicle, WheelData data) 193 | { 194 | if (!DoesEntityExist(vehicle) || data == null) 195 | return; 196 | 197 | int wheelsCount = data.WheelsCount; 198 | int frontWheelsCount = data.FrontWheelsCount; 199 | WheelDataNode[] nodes = data.GetNodes(); 200 | 201 | for (int i = frontWheelsCount; i < wheelsCount; i++) 202 | SetVehicleWheelYRotation(vehicle, i, nodes[i].RotationY); 203 | 204 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearCamberID, data.DefaultRearCamber, data.RearCamber); 205 | VStancerUtilities.UpdateFloatDecorator(vehicle, RearCamberID, data.RearCamber, data.DefaultRearCamber); 206 | } 207 | 208 | private void SetFrontTrackWidthUsingData(int vehicle, WheelData data) 209 | { 210 | if (!DoesEntityExist(vehicle) || data == null) 211 | return; 212 | 213 | int frontWheelsCount = data.FrontWheelsCount; 214 | WheelDataNode[] nodes = data.GetNodes(); 215 | 216 | for (int i = 0; i < frontWheelsCount; i++) 217 | SetVehicleWheelXOffset(vehicle, i, nodes[i].PositionX); 218 | 219 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontTrackWidthID, data.DefaultFrontTrackWidth, data.FrontTrackWidth); 220 | VStancerUtilities.UpdateFloatDecorator(vehicle, FrontTrackWidthID, data.FrontTrackWidth, data.DefaultFrontTrackWidth); 221 | } 222 | 223 | private void SetRearTrackWidthUsingData(int vehicle, WheelData data) 224 | { 225 | if (!DoesEntityExist(vehicle) || data == null) 226 | return; 227 | 228 | int wheelsCount = data.WheelsCount; 229 | int frontWheelsCount = data.FrontWheelsCount; 230 | WheelDataNode[] nodes = data.GetNodes(); 231 | 232 | for (int i = frontWheelsCount; i < wheelsCount; i++) 233 | SetVehicleWheelXOffset(vehicle, i, nodes[i].PositionX); 234 | 235 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearTrackWidthID, data.DefaultRearTrackWidth, data.RearTrackWidth); 236 | VStancerUtilities.UpdateFloatDecorator(vehicle, RearTrackWidthID, data.RearTrackWidth, data.DefaultRearTrackWidth); 237 | } 238 | 239 | private WheelData GetWheelDataFromEntity(int vehicle) 240 | { 241 | if (!DoesEntityExist(vehicle)) 242 | return null; 243 | 244 | int wheelsCount = GetVehicleNumberOfWheels(vehicle); 245 | int frontCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); 246 | 247 | // Get default values first 248 | float frontTrackWidth_def = DecorExistOn(vehicle, DefaultFrontTrackWidthID) ? DecorGetFloat(vehicle, DefaultFrontTrackWidthID) : GetVehicleWheelXOffset(vehicle, 0); 249 | float frontCamber_def = DecorExistOn(vehicle, DefaultFrontCamberID) ? DecorGetFloat(vehicle, DefaultFrontCamberID) : GetVehicleWheelYRotation(vehicle, 0); 250 | float rearTrackWidth_def = DecorExistOn(vehicle, DefaultRearTrackWidthID) ? DecorGetFloat(vehicle, DefaultRearTrackWidthID) : GetVehicleWheelXOffset(vehicle, frontCount); 251 | float rearCamber_def = DecorExistOn(vehicle, DefaultRearCamberID) ? DecorGetFloat(vehicle, DefaultRearCamberID) : GetVehicleWheelYRotation(vehicle, frontCount); 252 | 253 | float frontTrackWidth = DecorExistOn(vehicle, FrontTrackWidthID) ? DecorGetFloat(vehicle, FrontTrackWidthID) : frontTrackWidth_def; 254 | float frontCamber = DecorExistOn(vehicle, FrontCamberID) ? DecorGetFloat(vehicle, FrontCamberID) : frontCamber_def; 255 | float rearTrackWdith = DecorExistOn(vehicle, RearTrackWidthID) ? DecorGetFloat(vehicle, RearTrackWidthID) : rearTrackWidth_def; 256 | float rearCamber = DecorExistOn(vehicle, RearCamberID) ? DecorGetFloat(vehicle, RearCamberID) : rearCamber_def; 257 | 258 | return new WheelData(wheelsCount, frontTrackWidth_def, frontCamber_def, rearTrackWidth_def, rearCamber_def) 259 | { 260 | FrontTrackWidth = frontTrackWidth, 261 | FrontCamber = frontCamber, 262 | RearTrackWidth = rearTrackWdith, 263 | RearCamber = rearCamber, 264 | }; 265 | } 266 | 267 | private void UpdateVehicleUsingDecorators(int vehicle) 268 | { 269 | int wheelsCount = GetVehicleNumberOfWheels(vehicle); 270 | int frontCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); 271 | 272 | if (DecorExistOn(vehicle, FrontTrackWidthID)) 273 | { 274 | float value = DecorGetFloat(vehicle, FrontTrackWidthID); 275 | 276 | for (int index = 0; index < frontCount; index++) 277 | { 278 | if (index % 2 == 0) 279 | SetVehicleWheelXOffset(vehicle, index, value); 280 | else 281 | SetVehicleWheelXOffset(vehicle, index, -value); 282 | } 283 | } 284 | 285 | if (DecorExistOn(vehicle, FrontCamberID)) 286 | { 287 | float value = DecorGetFloat(vehicle, FrontCamberID); 288 | 289 | for (int index = 0; index < frontCount; index++) 290 | { 291 | if (index % 2 == 0) 292 | SetVehicleWheelYRotation(vehicle, index, value); 293 | else 294 | SetVehicleWheelYRotation(vehicle, index, -value); 295 | } 296 | } 297 | 298 | if (DecorExistOn(vehicle, RearTrackWidthID)) 299 | { 300 | float value = DecorGetFloat(vehicle, RearTrackWidthID); 301 | 302 | for (int index = frontCount; index < wheelsCount; index++) 303 | { 304 | if (index % 2 == 0) 305 | SetVehicleWheelXOffset(vehicle, index, value); 306 | else 307 | SetVehicleWheelXOffset(vehicle, index, -value); 308 | } 309 | } 310 | 311 | if (DecorExistOn(vehicle, RearCamberID)) 312 | { 313 | float value = DecorGetFloat(vehicle, RearCamberID); 314 | 315 | for (int index = frontCount; index < wheelsCount; index++) 316 | { 317 | if (index % 2 == 0) 318 | SetVehicleWheelYRotation(vehicle, index, value); 319 | else 320 | SetVehicleWheelYRotation(vehicle, index, -value); 321 | } 322 | } 323 | } 324 | 325 | private void UpdateVehicleUsingWheelData(int vehicle, WheelData data) 326 | { 327 | if (!DoesEntityExist(vehicle) || data == null) 328 | return; 329 | 330 | int wheelsCount = data.WheelsCount; 331 | WheelDataNode[] nodes = data.GetNodes(); 332 | 333 | for (int index = 0; index < wheelsCount; index++) 334 | { 335 | SetVehicleWheelXOffset(vehicle, index, nodes[index].PositionX); 336 | SetVehicleWheelYRotation(vehicle, index, nodes[index].RotationY); 337 | } 338 | } 339 | 340 | //private void UpdateVehicleDecorators(int vehicle, WheelData data) 341 | //{ 342 | // VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontTrackWidthID, data.DefaultFrontTrackWidth, data.FrontTrackWidth); 343 | // VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontCamberID, data.DefaultFrontCamber, data.FrontCamber); 344 | // VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearTrackWidthID, data.DefaultRearTrackWidth, data.RearTrackWidth); 345 | // VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearCamberID, data.DefaultRearCamber, data.RearCamber); 346 | // 347 | // VStancerUtilities.UpdateFloatDecorator(vehicle, FrontTrackWidthID, data.FrontTrackWidth, data.DefaultFrontTrackWidth); 348 | // VStancerUtilities.UpdateFloatDecorator(vehicle, FrontCamberID, data.FrontCamber, data.DefaultFrontCamber); 349 | // VStancerUtilities.UpdateFloatDecorator(vehicle, RearTrackWidthID, data.RearTrackWidth, data.DefaultRearTrackWidth); 350 | // VStancerUtilities.UpdateFloatDecorator(vehicle, RearCamberID, data.RearCamber, data.DefaultRearCamber); 351 | //} 352 | 353 | private void RegisterDecorators() 354 | { 355 | DecorRegister(FrontTrackWidthID, 1); 356 | DecorRegister(FrontCamberID, 1); 357 | DecorRegister(RearTrackWidthID, 1); 358 | DecorRegister(RearCamberID, 1); 359 | 360 | DecorRegister(DefaultFrontTrackWidthID, 1); 361 | DecorRegister(DefaultFrontCamberID, 1); 362 | DecorRegister(DefaultRearTrackWidthID, 1); 363 | DecorRegister(DefaultRearCamberID, 1); 364 | } 365 | 366 | private void RemoveDecoratorsFromVehicle(int vehicle) 367 | { 368 | if (DecorExistOn(vehicle, FrontTrackWidthID)) 369 | DecorRemove(vehicle, FrontTrackWidthID); 370 | 371 | if (DecorExistOn(vehicle, FrontCamberID)) 372 | DecorRemove(vehicle, FrontCamberID); 373 | 374 | if (DecorExistOn(vehicle, DefaultFrontTrackWidthID)) 375 | DecorRemove(vehicle, DefaultFrontTrackWidthID); 376 | 377 | if (DecorExistOn(vehicle, DefaultFrontCamberID)) 378 | DecorRemove(vehicle, DefaultFrontCamberID); 379 | 380 | if (DecorExistOn(vehicle, RearTrackWidthID)) 381 | DecorRemove(vehicle, RearTrackWidthID); 382 | 383 | if (DecorExistOn(vehicle, RearCamberID)) 384 | DecorRemove(vehicle, RearCamberID); 385 | 386 | if (DecorExistOn(vehicle, DefaultRearTrackWidthID)) 387 | DecorRemove(vehicle, DefaultRearTrackWidthID); 388 | 389 | if (DecorExistOn(vehicle, DefaultRearCamberID)) 390 | DecorRemove(vehicle, DefaultRearCamberID); 391 | } 392 | 393 | private void OnMenuCommandInvoked(string commandID) 394 | { 395 | switch (commandID) 396 | { 397 | case ResetID: 398 | if (!DataIsValid) 399 | return; 400 | 401 | WheelData.Reset(); 402 | break; 403 | } 404 | 405 | } 406 | 407 | private void OnMenuFloatPropertyChanged(string id, float value) 408 | { 409 | switch (id) 410 | { 411 | case FrontCamberID: 412 | if (DataIsValid) WheelData.FrontCamber = value; 413 | break; 414 | case RearCamberID: 415 | if (DataIsValid) WheelData.RearCamber = value; 416 | break; 417 | case FrontTrackWidthID: 418 | if (DataIsValid) WheelData.FrontTrackWidth = -value; 419 | break; 420 | case RearTrackWidthID: 421 | if (DataIsValid) WheelData.RearTrackWidth = -value; 422 | break; 423 | } 424 | } 425 | 426 | internal void PrintDecoratorsInfo(int vehicle) 427 | { 428 | if (!DoesEntityExist(vehicle)) 429 | { 430 | Debug.WriteLine($"{nameof(WheelScript)}: Can't find vehicle with handle {vehicle}"); 431 | return; 432 | } 433 | 434 | int wheelsCount = GetVehicleNumberOfWheels(vehicle); 435 | int netID = NetworkGetNetworkIdFromEntity(vehicle); 436 | StringBuilder s = new StringBuilder(); 437 | s.AppendLine($"{nameof(WheelScript)}: Vehicle:{vehicle} netID:{netID} wheelsCount:{wheelsCount}"); 438 | 439 | if (DecorExistOn(vehicle, FrontTrackWidthID)) 440 | s.AppendLine($"{FrontTrackWidthID}: {DecorGetFloat(vehicle, FrontTrackWidthID)}"); 441 | 442 | if (DecorExistOn(vehicle, DefaultFrontTrackWidthID)) 443 | s.AppendLine($"{DefaultFrontTrackWidthID}: {DecorGetFloat(vehicle, DefaultFrontTrackWidthID)}"); 444 | 445 | if (DecorExistOn(vehicle, RearTrackWidthID)) 446 | s.AppendLine($"{RearTrackWidthID}: {DecorGetFloat(vehicle, RearTrackWidthID)}"); 447 | 448 | if (DecorExistOn(vehicle, DefaultRearTrackWidthID)) 449 | s.AppendLine($"{DefaultRearTrackWidthID}: {DecorGetFloat(vehicle, DefaultRearTrackWidthID)}"); 450 | 451 | if (DecorExistOn(vehicle, FrontCamberID)) 452 | s.AppendLine($"{FrontCamberID}: {DecorGetFloat(vehicle, FrontCamberID)}"); 453 | 454 | if (DecorExistOn(vehicle, DefaultFrontCamberID)) 455 | s.AppendLine($"{DefaultFrontCamberID}: {DecorGetFloat(vehicle, DefaultFrontCamberID)}"); 456 | 457 | if (DecorExistOn(vehicle, RearCamberID)) 458 | s.AppendLine($"{RearCamberID}: {DecorGetFloat(vehicle, RearCamberID)}"); 459 | 460 | if (DecorExistOn(vehicle, DefaultRearCamberID)) 461 | s.AppendLine($"{RearCamberID}: {DecorGetFloat(vehicle, DefaultRearCamberID)}"); 462 | 463 | Debug.WriteLine(s.ToString()); 464 | } 465 | 466 | private bool EntityHasDecorators(int entity) 467 | { 468 | return ( 469 | DecorExistOn(entity, FrontTrackWidthID) || 470 | DecorExistOn(entity, FrontCamberID) || 471 | DecorExistOn(entity, RearTrackWidthID) || 472 | DecorExistOn(entity, RearCamberID) || 473 | DecorExistOn(entity, DefaultFrontTrackWidthID) || 474 | DecorExistOn(entity, DefaultFrontCamberID) || 475 | DecorExistOn(entity, DefaultRearTrackWidthID) || 476 | DecorExistOn(entity, DefaultRearCamberID) 477 | ); 478 | } 479 | 480 | internal void PrintVehiclesWithDecorators(IEnumerable vehiclesList) 481 | { 482 | IEnumerable entities = vehiclesList.Where(entity => EntityHasDecorators(entity)); 483 | 484 | Debug.WriteLine($"{nameof(WheelScript)}: Vehicles with decorators: {entities.Count()}"); 485 | 486 | foreach (int item in entities) 487 | Debug.WriteLine($"Vehicle: {item}"); 488 | } 489 | 490 | internal WheelPreset GetWheelPreset() 491 | { 492 | if (!DataIsValid) 493 | return null; 494 | 495 | // Only required to avoid saving this preset locally when not required 496 | if (!WheelData.IsEdited) 497 | return null; 498 | 499 | return new WheelPreset(WheelData); 500 | } 501 | 502 | internal async Task SetWheelPreset(WheelPreset preset) 503 | { 504 | if (!DataIsValid || preset == null) 505 | return; 506 | 507 | // TODO: Check if values are within limits 508 | 509 | WheelData.FrontTrackWidth = preset.FrontTrackWidth; 510 | WheelData.RearTrackWidth = preset.RearTrackWidth; 511 | WheelData.FrontCamber = preset.FrontCamber; 512 | WheelData.RearCamber = preset.RearCamber; 513 | 514 | // Don't refresh, as it's already done by OnWheelDataPropertyChanged 515 | 516 | Debug.WriteLine($"{nameof(WheelScript)}: wheel preset applied"); 517 | 518 | await Delay(200); 519 | WheelDataChanged?.Invoke(this, EventArgs.Empty); 520 | } 521 | 522 | internal bool API_SetWheelPreset(int vehicle, WheelPreset preset) 523 | { 524 | if (preset == null) 525 | return false; 526 | 527 | if (!DoesEntityExist(vehicle)) 528 | return false; 529 | 530 | float frontTrackWidth = preset.FrontTrackWidth; 531 | float rearTrackWidth = preset.RearTrackWidth; 532 | float frontCamber = preset.FrontCamber; 533 | float rearCamber = preset.RearCamber; 534 | 535 | int wheelsCount = GetVehicleNumberOfWheels(vehicle); 536 | int frontCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); 537 | 538 | float frontTrackWidth_def = DecorExistOn(vehicle, DefaultFrontTrackWidthID) ? DecorGetFloat(vehicle, DefaultFrontTrackWidthID) : GetVehicleWheelXOffset(vehicle, 0); 539 | float frontCamber_def = DecorExistOn(vehicle, DefaultFrontCamberID) ? DecorGetFloat(vehicle, DefaultFrontCamberID) : GetVehicleWheelYRotation(vehicle, 0); 540 | float rearTrackWidth_def = DecorExistOn(vehicle, DefaultRearTrackWidthID) ? DecorGetFloat(vehicle, DefaultRearTrackWidthID) : GetVehicleWheelXOffset(vehicle, frontCount); 541 | float rearCamber_def = DecorExistOn(vehicle, DefaultRearCamberID) ? DecorGetFloat(vehicle, DefaultRearCamberID) : GetVehicleWheelYRotation(vehicle, frontCount); 542 | 543 | if (vehicle == _playerVehicleHandle) 544 | { 545 | // TODO: Maybe this is useles and could just use SetWheelPreset instead 546 | WheelData = new WheelData(wheelsCount, frontTrackWidth_def, frontCamber_def, rearTrackWidth_def, rearCamber_def) 547 | { 548 | FrontTrackWidth = frontTrackWidth, 549 | FrontCamber = frontCamber, 550 | RearTrackWidth = rearTrackWidth, 551 | RearCamber = rearCamber 552 | }; 553 | } 554 | else 555 | { 556 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontTrackWidthID, frontTrackWidth_def, frontTrackWidth); 557 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontCamberID, frontCamber_def, frontCamber); 558 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearTrackWidthID, rearTrackWidth_def, rearTrackWidth); 559 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearCamberID, rearCamber_def, rearCamber); 560 | 561 | VStancerUtilities.UpdateFloatDecorator(vehicle, FrontTrackWidthID, frontTrackWidth, frontTrackWidth_def); 562 | VStancerUtilities.UpdateFloatDecorator(vehicle, FrontCamberID, frontCamber, frontCamber_def); 563 | VStancerUtilities.UpdateFloatDecorator(vehicle, RearTrackWidthID, rearTrackWidth, rearTrackWidth_def); 564 | VStancerUtilities.UpdateFloatDecorator(vehicle, RearCamberID, rearCamber, rearCamber_def); 565 | } 566 | 567 | return true; 568 | } 569 | 570 | internal bool API_GetWheelPreset(int vehicle, out WheelPreset preset) 571 | { 572 | preset = null; 573 | 574 | if (!DoesEntityExist(vehicle)) 575 | return false; 576 | 577 | WheelData data = (vehicle == _playerVehicleHandle && DataIsValid) ? WheelData : GetWheelDataFromEntity(vehicle); 578 | 579 | if(data == null) 580 | return false; 581 | 582 | preset = new WheelPreset(data); 583 | return true; 584 | } 585 | 586 | internal bool API_ResetWheelPreset(int vehicle) 587 | { 588 | if (!DoesEntityExist(vehicle)) 589 | return false; 590 | 591 | if (vehicle != _playerVehicleHandle) 592 | { 593 | RemoveDecoratorsFromVehicle(vehicle); 594 | UpdateVehicleUsingDecorators(vehicle); 595 | return true; 596 | } 597 | 598 | if (!DataIsValid) 599 | return false; 600 | 601 | WheelData.Reset(); 602 | 603 | return true; 604 | } 605 | 606 | internal bool API_SetFrontCamber(int vehicle, float value) 607 | { 608 | if (!DoesEntityExist(vehicle)) 609 | return false; 610 | 611 | if (vehicle != _playerVehicleHandle) 612 | { 613 | float value_def = DecorExistOn(vehicle, DefaultFrontCamberID) ? DecorGetFloat(vehicle, DefaultFrontCamberID) : GetVehicleWheelYRotation(vehicle, 0); 614 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontCamberID, value_def, value); 615 | VStancerUtilities.UpdateFloatDecorator(vehicle, FrontCamberID, value, value_def); 616 | return true; 617 | } 618 | 619 | if (!DataIsValid) 620 | return false; 621 | 622 | WheelData.FrontCamber = value; 623 | WheelDataChanged?.Invoke(this, EventArgs.Empty); 624 | 625 | return true; 626 | } 627 | 628 | internal bool API_SetRearCamber(int vehicle, float value) 629 | { 630 | if (!DoesEntityExist(vehicle)) 631 | return false; 632 | 633 | if (vehicle != _playerVehicleHandle) 634 | { 635 | int wheelsCount = GetVehicleNumberOfWheels(vehicle); 636 | int frontCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); 637 | 638 | float value_def = DecorExistOn(vehicle, DefaultRearCamberID) ? DecorGetFloat(vehicle, DefaultRearCamberID) : GetVehicleWheelYRotation(vehicle, frontCount); 639 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearCamberID, value_def, value); 640 | VStancerUtilities.UpdateFloatDecorator(vehicle, RearCamberID, value, value_def); 641 | return true; 642 | } 643 | 644 | if (!DataIsValid) 645 | return false; 646 | 647 | WheelData.RearCamber = value; 648 | WheelDataChanged?.Invoke(this, EventArgs.Empty); 649 | 650 | return true; 651 | } 652 | 653 | internal bool API_SetFrontTrackWidth(int vehicle, float value) 654 | { 655 | if (!DoesEntityExist(vehicle)) 656 | return false; 657 | 658 | if (vehicle != _playerVehicleHandle) 659 | { 660 | float value_def = DecorExistOn(vehicle, DefaultFrontTrackWidthID) ? DecorGetFloat(vehicle, DefaultFrontTrackWidthID) : GetVehicleWheelXOffset(vehicle, 0); 661 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontTrackWidthID, value_def, value); 662 | VStancerUtilities.UpdateFloatDecorator(vehicle, FrontTrackWidthID, value, value_def); 663 | return true; 664 | } 665 | 666 | if (!DataIsValid) 667 | return false; 668 | 669 | WheelData.FrontTrackWidth = value; 670 | WheelDataChanged?.Invoke(this, EventArgs.Empty); 671 | 672 | return true; 673 | } 674 | 675 | internal bool API_SetRearTrackWidth(int vehicle, float value) 676 | { 677 | if (!DoesEntityExist(vehicle)) 678 | return false; 679 | 680 | if (vehicle != _playerVehicleHandle) 681 | { 682 | int wheelsCount = GetVehicleNumberOfWheels(vehicle); 683 | int frontCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); 684 | 685 | float value_def = DecorExistOn(vehicle, DefaultRearTrackWidthID) ? DecorGetFloat(vehicle, DefaultRearTrackWidthID) : GetVehicleWheelXOffset(vehicle, frontCount); 686 | VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearTrackWidthID, value_def, value); 687 | VStancerUtilities.UpdateFloatDecorator(vehicle, RearTrackWidthID, value, value_def); 688 | return true; 689 | } 690 | 691 | if (!DataIsValid) 692 | return false; 693 | 694 | WheelData.RearTrackWidth = value; 695 | WheelDataChanged?.Invoke(this, EventArgs.Empty); 696 | 697 | return true; 698 | } 699 | 700 | internal bool API_GetFrontCamber(int vehicle, out float value) 701 | { 702 | value = default; 703 | 704 | if (!DoesEntityExist(vehicle)) 705 | return false; 706 | 707 | value = DecorExistOn(vehicle, FrontCamberID) ? DecorGetFloat(vehicle, FrontCamberID) : GetVehicleWheelYRotation(vehicle, 0); 708 | return true; 709 | } 710 | 711 | internal bool API_GetRearCamber(int vehicle, out float value) 712 | { 713 | value = default; 714 | 715 | if (!DoesEntityExist(vehicle)) 716 | return false; 717 | 718 | int frontCount = VStancerUtilities.CalculateFrontWheelsCount(GetVehicleNumberOfWheels(vehicle)); 719 | value = DecorExistOn(vehicle, RearCamberID) ? DecorGetFloat(vehicle, RearCamberID) : GetVehicleWheelYRotation(vehicle, frontCount); 720 | return true; 721 | } 722 | 723 | internal bool API_GetFrontTrackWidth(int vehicle, out float value) 724 | { 725 | value = default; 726 | 727 | if (!DoesEntityExist(vehicle)) 728 | return false; 729 | 730 | value = DecorExistOn(vehicle, FrontTrackWidthID) ? DecorGetFloat(vehicle, FrontTrackWidthID) : GetVehicleWheelXOffset(vehicle, 0); 731 | return true; 732 | } 733 | 734 | internal bool API_GetRearTrackWidth(int vehicle, out float value) 735 | { 736 | value = default; 737 | 738 | if (!DoesEntityExist(vehicle)) 739 | return false; 740 | 741 | int frontCount = VStancerUtilities.CalculateFrontWheelsCount(GetVehicleNumberOfWheels(vehicle)); 742 | value = DecorExistOn(vehicle, RearTrackWidthID) ? DecorGetFloat(vehicle, RearTrackWidthID) : GetVehicleWheelXOffset(vehicle, frontCount); 743 | return true; 744 | } 745 | } 746 | } 747 | -------------------------------------------------------------------------------- /VStancer.Client/UI/ClientPresetsMenu.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MenuAPI; 3 | using CitizenFX.Core; 4 | using static CitizenFX.Core.Native.API; 5 | using VStancer.Client.Scripts; 6 | 7 | namespace VStancer.Client.UI 8 | { 9 | internal class ClientPresetsMenu : Menu 10 | { 11 | private readonly ClientPresetsScript _script; 12 | 13 | internal ClientPresetsMenu(ClientPresetsScript script, string name = Globals.ScriptName, string subtitle = "Client Presets Menu") : base(name, subtitle) 14 | { 15 | _script = script; 16 | 17 | _script.Presets.PresetsCollectionChanged += new EventHandler((sender, args) => Update()); 18 | 19 | Update(); 20 | 21 | AddTextEntry("VSTANCER_ENTER_PRESET_NAME", "Enter a name for the preset"); 22 | 23 | OnItemSelect += ItemSelect; 24 | InstructionalButtons.Add(Control.PhoneExtraOption, GetLabelText("ITEM_SAVE")); 25 | InstructionalButtons.Add(Control.PhoneOption, GetLabelText("ITEM_DEL")); 26 | 27 | // Disable Controls binded on the same key 28 | ButtonPressHandlers.Add(new ButtonPressHandler(Control.SelectWeapon, ControlPressCheckType.JUST_RELEASED, new Action((sender, control) => 29 | { 30 | }), true)); 31 | 32 | ButtonPressHandlers.Add(new ButtonPressHandler(Control.VehicleExit, ControlPressCheckType.JUST_RELEASED, new Action((sender, control) => 33 | { 34 | }), true)); 35 | 36 | ButtonPressHandlers.Add(new ButtonPressHandler(Control.PhoneExtraOption, ControlPressCheckType.JUST_RELEASED, new Action(async (sender, control) => 37 | { 38 | string presetName = await _script.GetPresetNameFromUser("VSTANCER_ENTER_PRESET_NAME", ""); 39 | 40 | if (string.IsNullOrEmpty(presetName)) 41 | return; 42 | 43 | SavePresetEvent?.Invoke(this, presetName.Trim()); 44 | }), true)); 45 | 46 | ButtonPressHandlers.Add(new ButtonPressHandler(Control.PhoneOption, ControlPressCheckType.JUST_RELEASED, new Action((sender, control) => 47 | { 48 | if (GetMenuItems().Count > 0) 49 | { 50 | string presetName = GetMenuItems()[CurrentIndex].ItemData; 51 | DeletePresetEvent?.Invoke(this, presetName); 52 | } 53 | }), true)); 54 | } 55 | 56 | internal event EventHandler ApplyPresetEvent; 57 | internal event EventHandler SavePresetEvent; 58 | internal event EventHandler DeletePresetEvent; 59 | 60 | internal void Update() 61 | { 62 | ClearMenuItems(); 63 | 64 | if (_script.Presets == null) 65 | return; 66 | 67 | foreach (var key in _script.Presets.GetKeys()) 68 | { 69 | AddMenuItem(new MenuItem(key) { ItemData = key }); 70 | } 71 | } 72 | 73 | private void ItemSelect(Menu menu, MenuItem menuItem, int itemIndex) => ApplyPresetEvent?.Invoke(menu, menuItem.ItemData); 74 | } 75 | } -------------------------------------------------------------------------------- /VStancer.Client/UI/MainMenu.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CitizenFX.Core; 3 | using static CitizenFX.Core.Native.API; 4 | using static VStancer.Client.UI.MenuUtilities; 5 | using MenuAPI; 6 | using VStancer.Client.Scripts; 7 | 8 | namespace VStancer.Client.UI 9 | { 10 | internal class MainMenu : Menu 11 | { 12 | private readonly MainScript _script; 13 | 14 | private WheelMenu WheelMenu { get; set; } 15 | private WheelModMenu WheelModMenu { get; set; } 16 | private ClientPresetsMenu ClientPresetsMenu { get; set; } 17 | 18 | private MenuItem WheelMenuMenuItem { get; set; } 19 | private MenuItem WheelModMenuMenuItem { get; set; } 20 | private MenuItem ClientPresetsMenuMenuItem { get; set; } 21 | 22 | 23 | internal MainMenu(MainScript script, string name = Globals.ScriptName, string subtitle = "Main Menu") : base(name, subtitle) 24 | { 25 | _script = script; 26 | 27 | _script.ToggleMenuVisibility += new EventHandler((sender, args) => 28 | { 29 | var currentMenu = MenuController.MainMenu; 30 | 31 | if (currentMenu == null) 32 | return; 33 | 34 | currentMenu.Visible = !currentMenu.Visible; 35 | }); 36 | 37 | MenuController.MenuAlignment = MenuController.MenuAlignmentOption.Right; 38 | MenuController.MenuToggleKey = (Control)_script.Config.ToggleMenuControl; 39 | MenuController.EnableMenuToggleKeyOnController = false; 40 | MenuController.DontOpenAnyMenu = true; 41 | MenuController.MainMenu = this; 42 | 43 | if (_script.WheelScript != null) 44 | WheelMenu = _script.WheelScript.Menu; 45 | 46 | if (_script.WheelModScript != null) 47 | { 48 | WheelModMenu = _script.WheelModScript.Menu; 49 | WheelModMenu.PropertyChanged += (sender, args) => UpdateWheelModMenuMenuItem(); 50 | } 51 | 52 | if (_script.ClientPresetsScript != null) 53 | ClientPresetsMenu = _script.ClientPresetsScript.Menu; 54 | 55 | Update(); 56 | } 57 | 58 | internal void Update() 59 | { 60 | ClearMenuItems(); 61 | 62 | MenuController.Menus.Clear(); 63 | MenuController.AddMenu(this); 64 | 65 | if (WheelMenu != null) 66 | { 67 | WheelMenuMenuItem = new MenuItem("Wheel Menu", "The menu to edit main properties.") 68 | { 69 | Label = "→→→" 70 | }; 71 | 72 | AddMenuItem(WheelMenuMenuItem); 73 | 74 | MenuController.AddSubmenu(this, WheelMenu); 75 | MenuController.BindMenuItem(this, WheelMenu, WheelMenuMenuItem); 76 | } 77 | 78 | if (WheelModMenu != null) 79 | { 80 | WheelModMenuMenuItem = new MenuItem("Wheel Mod Menu") 81 | { 82 | Label = "→→→" 83 | }; 84 | UpdateWheelModMenuMenuItem(); 85 | 86 | AddMenuItem(WheelModMenuMenuItem); 87 | 88 | MenuController.AddSubmenu(this, WheelModMenu); 89 | MenuController.BindMenuItem(this, WheelModMenu, WheelModMenuMenuItem); 90 | } 91 | 92 | if (ClientPresetsMenu != null) 93 | { 94 | ClientPresetsMenuMenuItem = new MenuItem("Client Presets Menu", "The menu to manage the presets saved by you.") 95 | { 96 | Label = "→→→" 97 | }; 98 | 99 | AddMenuItem(ClientPresetsMenuMenuItem); 100 | 101 | MenuController.AddSubmenu(this, ClientPresetsMenu); 102 | MenuController.BindMenuItem(this, ClientPresetsMenu, ClientPresetsMenuMenuItem); 103 | } 104 | } 105 | 106 | internal bool HideMenu 107 | { 108 | get => MenuController.DontOpenAnyMenu; 109 | set 110 | { 111 | MenuController.DontOpenAnyMenu = value; 112 | } 113 | } 114 | 115 | private void UpdateWheelModMenuMenuItem() 116 | { 117 | if (WheelModMenuMenuItem == null) 118 | return; 119 | 120 | var enabled = WheelModMenu != null ? WheelModMenu.Enabled : false; 121 | 122 | WheelModMenuMenuItem.Enabled = enabled; 123 | WheelModMenuMenuItem.RightIcon = enabled ? MenuItem.Icon.NONE : MenuItem.Icon.LOCK; 124 | WheelModMenuMenuItem.Label = enabled ? "→→→" : string.Empty; 125 | WheelModMenuMenuItem.Description = enabled ? "The menu to edit custom wheel properties." : "Install a custom wheel to access to this menu."; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /VStancer.Client/UI/MenuUtilities.cs: -------------------------------------------------------------------------------- 1 | using CitizenFX.Core.UI; 2 | using MenuAPI; 3 | 4 | namespace VStancer.Client.UI 5 | { 6 | internal static class MenuUtilities 7 | { 8 | internal delegate void FloatPropertyChanged(string id, float value); 9 | 10 | internal static MenuDynamicListItem CreateDynamicFloatList(string name, float defaultValue, float value, float maxEditing, string id, float step = 0.01f) 11 | { 12 | float min = defaultValue - maxEditing; 13 | float max = defaultValue + maxEditing; 14 | 15 | var callback = FloatChangeCallback(name, value, min, max, step); 16 | 17 | return new MenuDynamicListItem(name, value.ToString("F3"), callback) { ItemData = id }; 18 | } 19 | 20 | internal static MenuDynamicListItem.ChangeItemCallback FloatChangeCallback(string name, float value, float minimum, float maximum, float step) 21 | { 22 | string callback(MenuDynamicListItem sender, bool left) 23 | { 24 | var min = minimum; 25 | var max = maximum; 26 | 27 | var newvalue = value; 28 | 29 | if (left) 30 | newvalue -= step; 31 | else if (!left) 32 | newvalue += step; 33 | else return value.ToString("F3"); 34 | 35 | // Hotfix to trim the value to 3 digits 36 | newvalue = float.Parse((newvalue).ToString("F3")); 37 | 38 | if (newvalue < min) 39 | Screen.ShowNotification($"~o~Warning~w~: Min ~b~{name}~w~ value allowed is {min}"); 40 | else if (newvalue > max) 41 | Screen.ShowNotification($"~o~Warning~w~: Max ~b~{name}~w~ value allowed is {max}"); 42 | else 43 | { 44 | value = newvalue; 45 | } 46 | return value.ToString("F3"); 47 | }; 48 | return callback; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /VStancer.Client/UI/WheelMenu.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MenuAPI; 3 | using VStancer.Client.Scripts; 4 | using static VStancer.Client.UI.MenuUtilities; 5 | 6 | namespace VStancer.Client.UI 7 | { 8 | internal class WheelMenu : Menu 9 | { 10 | private readonly WheelScript _script; 11 | 12 | internal WheelMenu(WheelScript script, string name = Globals.ScriptName, string subtitle = "Wheel Menu") : base(name, subtitle) 13 | { 14 | _script = script; 15 | 16 | _script.WheelDataChanged += new EventHandler((sender, args) => Update()); 17 | 18 | Update(); 19 | 20 | OnDynamicListItemCurrentItemChange += DynamicListItemCurrentItemChange; 21 | OnItemSelect += ItemSelect; 22 | } 23 | 24 | private void ItemSelect(Menu menu, MenuItem menuItem, int itemIndex) 25 | { 26 | if (menuItem == ResetItem) 27 | ResetPropertiesEvent?.Invoke(this, menuItem.ItemData as string); 28 | } 29 | 30 | private void DynamicListItemCurrentItemChange(Menu menu, MenuDynamicListItem dynamicListItem, string oldValue, string newValue) 31 | { 32 | // TODO: Does it need to check if newvalue != oldvalue? 33 | if (oldValue == newValue) 34 | return; 35 | 36 | if (float.TryParse(newValue, out float newfloatValue)) 37 | FloatPropertyChangedEvent?.Invoke(dynamicListItem.ItemData as string, newfloatValue); 38 | } 39 | 40 | private MenuDynamicListItem FrontCamberListItem { get; set; } 41 | private MenuDynamicListItem RearCamberListItem { get; set; } 42 | private MenuDynamicListItem FrontTrackWidthListItem { get; set; } 43 | private MenuDynamicListItem RearTrackWidthListItem { get; set; } 44 | private MenuItem ResetItem { get; set; } 45 | 46 | internal event FloatPropertyChanged FloatPropertyChangedEvent; 47 | internal event EventHandler ResetPropertiesEvent; 48 | 49 | internal void Update() 50 | { 51 | ClearMenuItems(); 52 | 53 | if (!_script.DataIsValid) 54 | return; 55 | 56 | FrontTrackWidthListItem = CreateDynamicFloatList("Front Track Width", 57 | -_script.WheelData.DefaultFrontTrackWidth, 58 | -_script.WheelData.FrontTrackWidth, 59 | _script.Config.WheelLimits.FrontTrackWidth, 60 | WheelScript.FrontTrackWidthID, 61 | _script.Config.FloatStep); 62 | 63 | RearTrackWidthListItem = CreateDynamicFloatList("Rear Track Width", 64 | -_script.WheelData.DefaultRearTrackWidth, 65 | -_script.WheelData.RearTrackWidth, 66 | _script.Config.WheelLimits.RearTrackWidth, 67 | WheelScript.RearTrackWidthID, 68 | _script.Config.FloatStep); 69 | 70 | FrontCamberListItem = CreateDynamicFloatList("Front Camber", 71 | _script.WheelData.DefaultFrontCamber, 72 | _script.WheelData.FrontCamber, 73 | _script.Config.WheelLimits.FrontCamber, 74 | WheelScript.FrontCamberID, 75 | _script.Config.FloatStep); 76 | 77 | RearCamberListItem = CreateDynamicFloatList("Rear Camber", 78 | _script.WheelData.DefaultRearCamber, 79 | _script.WheelData.RearCamber, 80 | _script.Config.WheelLimits.RearCamber, 81 | WheelScript.RearCamberID, 82 | _script.Config.FloatStep); 83 | 84 | ResetItem = new MenuItem("Reset", "Restores the default values") { ItemData = WheelScript.ResetID }; 85 | 86 | AddMenuItem(FrontTrackWidthListItem); 87 | AddMenuItem(RearTrackWidthListItem); 88 | AddMenuItem(FrontCamberListItem); 89 | AddMenuItem(RearCamberListItem); 90 | AddMenuItem(ResetItem); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /VStancer.Client/UI/WheelModMenu.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MenuAPI; 3 | using VStancer.Client.Scripts; 4 | using static VStancer.Client.UI.MenuUtilities; 5 | 6 | namespace VStancer.Client.UI 7 | { 8 | internal class WheelModMenu : Menu 9 | { 10 | private readonly WheelModScript _script; 11 | private bool _enabled; 12 | 13 | internal event EventHandler PropertyChanged; 14 | 15 | internal bool Enabled 16 | { 17 | get => _enabled; 18 | private set 19 | { 20 | if (Equals(value, _enabled)) 21 | return; 22 | 23 | _enabled = value; 24 | PropertyChanged?.Invoke(this, nameof(Enabled)); 25 | } 26 | } 27 | 28 | internal WheelModMenu(WheelModScript script, string name = Globals.ScriptName, string subtitle = "Wheel Mod Menu") : base(name, subtitle) 29 | { 30 | _script = script; 31 | 32 | _script.WheelModDataChanged += new EventHandler((sender, args) => 33 | { 34 | Update(); 35 | }); 36 | 37 | Update(); 38 | 39 | OnDynamicListItemCurrentItemChange += DynamicListItemCurrentItemChange; 40 | OnItemSelect += ItemSelect; 41 | } 42 | 43 | private void ItemSelect(Menu menu, MenuItem menuItem, int itemIndex) 44 | { 45 | if (menuItem == ResetItem) 46 | ResetPropertiesEvent?.Invoke(this, menuItem.ItemData as string); 47 | } 48 | 49 | private void DynamicListItemCurrentItemChange(Menu menu, MenuDynamicListItem dynamicListItem, string oldValue, string newValue) 50 | { 51 | // TODO: Does it need to check if newvalue != oldvalue? 52 | if (oldValue == newValue) 53 | return; 54 | 55 | if (float.TryParse(newValue, out float newfloatValue)) 56 | FloatPropertyChangedEvent?.Invoke(dynamicListItem.ItemData as string, newfloatValue); 57 | } 58 | 59 | private MenuDynamicListItem WheelSizeListItem { get; set; } 60 | private MenuDynamicListItem WheelWidthListItem { get; set; } 61 | 62 | //private MenuDynamicListItem FrontTireColliderWidthListItem { get; set; } 63 | //private MenuDynamicListItem FrontTireColliderSizeListItem { get; set; } 64 | //private MenuDynamicListItem FrontRimColliderSizeListItem { get; set; } 65 | //private MenuDynamicListItem RearTireColliderWidthListItem { get; set; } 66 | //private MenuDynamicListItem RearTireColliderSizeListItem { get; set; } 67 | //private MenuDynamicListItem RearRimColliderSizeListItem { get; set; } 68 | private MenuItem ResetItem { get; set; } 69 | 70 | internal event FloatPropertyChanged FloatPropertyChangedEvent; 71 | internal event EventHandler ResetPropertiesEvent; 72 | 73 | internal void Update() 74 | { 75 | ClearMenuItems(); 76 | 77 | Enabled = _script.DataIsValid; 78 | 79 | if (!Enabled) 80 | return; 81 | 82 | WheelSizeListItem = CreateDynamicFloatList("Wheel Size", 83 | _script.WheelModData.DefaultWheelSize, 84 | _script.WheelModData.WheelSize, 85 | _script.Config.WheelModLimits.WheelSize, 86 | WheelModScript.WheelSizeID, 87 | _script.Config.FloatStep); 88 | 89 | WheelWidthListItem = CreateDynamicFloatList("Wheel Width", 90 | _script.WheelModData.DefaultWheelWidth, 91 | _script.WheelModData.WheelWidth, 92 | _script.Config.WheelModLimits.WheelWidth, 93 | WheelModScript.WheelWidthID, 94 | _script.Config.FloatStep); 95 | 96 | /* 97 | FrontTireColliderWidthListItem = CreateDynamicFloatList("Front Tire Collider Width", 98 | _script.WheelModData.DefaultFrontTireColliderWidth, 99 | _script.WheelModData.FrontTireColliderWidth, 100 | _script.Config.WheelModLimits.FrontTireColliderWidth, 101 | WheelModScript.FrontTireColliderWidthID, 102 | _script.Config.FloatStep); 103 | 104 | FrontTireColliderSizeListItem = CreateDynamicFloatList("Front Tire Collider Size", 105 | _script.WheelModData.DefaultFrontTireColliderSize, 106 | _script.WheelModData.FrontTireColliderSize, 107 | _script.Config.WheelModLimits.FrontTireColliderSize, 108 | WheelModScript.FrontTireColliderSizeID, 109 | _script.Config.FloatStep); 110 | 111 | FrontRimColliderSizeListItem = CreateDynamicFloatList("Front Rim Collider Size", 112 | _script.WheelModData.DefaultFrontRimColliderSize, 113 | _script.WheelModData.FrontRimColliderSize, 114 | _script.Config.WheelModLimits.FrontRimColliderSize, 115 | WheelModScript.FrontRimColliderSizeID, 116 | _script.Config.FloatStep); 117 | 118 | RearTireColliderWidthListItem = CreateDynamicFloatList("Rear Tire Collider Width", 119 | _script.WheelModData.DefaultRearTireColliderWidth, 120 | _script.WheelModData.RearTireColliderWidth, 121 | _script.Config.WheelModLimits.RearTireColliderWidth, 122 | WheelModScript.RearTireColliderWidthID, 123 | _script.Config.FloatStep); 124 | 125 | RearTireColliderSizeListItem = CreateDynamicFloatList("Rear Tire Collider Size", 126 | _script.WheelModData.DefaultRearTireColliderSize, 127 | _script.WheelModData.RearTireColliderSize, 128 | _script.Config.WheelModLimits.RearTireColliderSize, 129 | WheelModScript.RearTireColliderSizeID, 130 | _script.Config.FloatStep); 131 | 132 | RearRimColliderSizeListItem = CreateDynamicFloatList("Rear Rim Collider Size", 133 | _script.WheelModData.DefaultRearRimColliderSize, 134 | _script.WheelModData.RearRimColliderSize, 135 | _script.Config.WheelModLimits.RearRimColliderSize, 136 | WheelModScript.RearRimColliderSizeID, 137 | _script.Config.FloatStep); 138 | */ 139 | ResetItem = new MenuItem("Reset", "Restores the default values") { ItemData = WheelModScript.ExtraResetID }; 140 | 141 | AddMenuItem(WheelSizeListItem); 142 | AddMenuItem(WheelWidthListItem); 143 | //AddMenuItem(FrontTireColliderWidthListItem); 144 | //AddMenuItem(FrontTireColliderSizeListItem); 145 | //AddMenuItem(FrontRimColliderSizeListItem); 146 | //AddMenuItem(RearTireColliderWidthListItem); 147 | //AddMenuItem(RearTireColliderSizeListItem); 148 | //AddMenuItem(RearRimColliderSizeListItem); 149 | AddMenuItem(ResetItem); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /VStancer.Client/VStancer.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net452 4 | VStancer.Client 5 | VStancer.Client.net 6 | 7 | 8 | x64 9 | 10 | 11 | 12 | FiveM VStancer 13 | A FiveM script to edit transform of vehicles' wheels bones 14 | carmineos 15 | Carmine Giugliano 16 | VStancer.Client 17 | 1.0.0 18 | 1.0.0.0 19 | 1.0.0.0 20 | 21 | 22 | 23 | 24 | 25 | 1.0.2460 26 | runtime 27 | compile; build; native; contentfiles; analyzers; buildtransitive 28 | 29 | 30 | 31 | 32 | 33 | 34 | $(PkgNewtonsoft_Json)\lib\portable-net40+sl5+win8+wp8+wpa81\Newtonsoft.Json.dll 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /VStancer.Client/VStancerConfig.cs: -------------------------------------------------------------------------------- 1 | namespace VStancer.Client 2 | { 3 | public class VStancerConfig 4 | { 5 | public bool Debug { get; set; } 6 | public bool DisableMenu { get; set; } 7 | public bool ExposeCommand { get; set; } 8 | public bool ExposeEvent { get; set; } 9 | public float ScriptRange { get; set; } 10 | public long Timer { get; set; } 11 | public int ToggleMenuControl { get; set; } 12 | public float FloatStep { get; set; } 13 | public bool EnableWheelMod { get; set; } 14 | public bool EnableClientPresets { get; set; } 15 | public WheelLimits WheelLimits { get; set; } 16 | public WheelModLimits WheelModLimits { get; set; } 17 | 18 | public VStancerConfig() 19 | { 20 | Debug = false; 21 | DisableMenu = false; 22 | ExposeCommand = false; 23 | ExposeEvent = false; 24 | ScriptRange = 150.0f; 25 | Timer = 1000; 26 | ToggleMenuControl = 167; 27 | FloatStep = 0.01f; 28 | EnableWheelMod = true; 29 | EnableClientPresets = true; 30 | 31 | WheelLimits = new WheelLimits 32 | { 33 | FrontTrackWidth= 0.25f, 34 | RearTrackWidth = 0.25f, 35 | FrontCamber = 0.20f, 36 | RearCamber = 0.20f, 37 | }; 38 | 39 | WheelModLimits = new WheelModLimits 40 | { 41 | WheelSize = 0.2f, 42 | WheelWidth = 0.2f, 43 | FrontTireColliderWidth = 0.1f, 44 | FrontTireColliderSize = 0.1f, 45 | FrontRimColliderSize = 0.1f, 46 | RearTireColliderWidth = 0.1f, 47 | RearTireColliderSize = 0.1f, 48 | RearRimColliderSize = 0.1f, 49 | }; 50 | } 51 | } 52 | 53 | public struct WheelLimits 54 | { 55 | public float FrontTrackWidth { get; set; } 56 | public float RearTrackWidth { get; set; } 57 | public float FrontCamber { get; set; } 58 | public float RearCamber { get; set; } 59 | } 60 | 61 | public struct WheelModColliderLimits 62 | { 63 | public float TireColliderScaleX { get; set; } 64 | public float TireColliderScaleYZ { get; set; } 65 | public float RimColliderScaleYZ { get; set; } 66 | } 67 | 68 | public struct WheelModLimits 69 | { 70 | public float WheelSize { get; set; } 71 | public float WheelWidth { get; set; } 72 | public float FrontTireColliderWidth { get; set; } 73 | public float FrontTireColliderSize { get; set; } 74 | public float FrontRimColliderSize { get; set; } 75 | public float RearTireColliderWidth { get; set; } 76 | public float RearTireColliderSize { get; set; } 77 | public float RearRimColliderSize { get; set; } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /VStancer.Client/VStancerUtilities.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | using CitizenFX.Core; 4 | using static CitizenFX.Core.Native.API; 5 | 6 | namespace VStancer.Client 7 | { 8 | public static class VStancerUtilities 9 | { 10 | public const float Epsilon = 0.001f; 11 | 12 | public static int CalculateFrontWheelsCount(int wheelsCount) 13 | { 14 | int _frontWheelsCount = wheelsCount / 2; 15 | 16 | if (_frontWheelsCount % 2 != 0) 17 | _frontWheelsCount -= 1; 18 | 19 | return _frontWheelsCount; 20 | } 21 | 22 | public static List GetKeyValuePairs(string prefix) 23 | { 24 | List pairs = new List(); 25 | 26 | int handle = StartFindKvp(prefix); 27 | 28 | if (handle != -1) 29 | { 30 | string kvp; 31 | do 32 | { 33 | kvp = FindKvp(handle); 34 | 35 | if (kvp != null) 36 | pairs.Add(kvp); 37 | } 38 | while (kvp != null); 39 | EndFindKvp(handle); 40 | } 41 | 42 | return pairs; 43 | } 44 | 45 | public static List GetWorldVehicles() 46 | { 47 | List handles = new List(); 48 | 49 | int entity = -1; 50 | int handle = FindFirstVehicle(ref entity); 51 | 52 | if (handle != -1) 53 | { 54 | do handles.Add(entity); 55 | while (FindNextVehicle(handle, ref entity)); 56 | 57 | EndFindVehicle(handle); 58 | } 59 | 60 | return handles; 61 | } 62 | 63 | public static void UpdateFloatDecorator(int vehicle, string name, float currentValue, float defaultValue) 64 | { 65 | // Decorator exists but needs to be updated 66 | if (DecorExistOn(vehicle, name)) 67 | { 68 | float decorValue = DecorGetFloat(vehicle, name); 69 | if (!MathUtil.WithinEpsilon(currentValue, decorValue, Epsilon)) 70 | { 71 | DecorSetFloat(vehicle, name, currentValue); 72 | #if DEBUG 73 | Debug.WriteLine($"Updated decorator {name} from {decorValue} to {currentValue} on vehicle {vehicle}"); 74 | #endif 75 | } 76 | } 77 | else // Decorator doesn't exist, create it if required 78 | { 79 | if (!MathUtil.WithinEpsilon(currentValue, defaultValue, Epsilon)) 80 | { 81 | DecorSetFloat(vehicle, name, currentValue); 82 | #if DEBUG 83 | Debug.WriteLine($"Added decorator {name} with value {currentValue} to vehicle {vehicle}"); 84 | #endif 85 | } 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - ps: $env:LAST_TAG = git describe --abbrev=0 --tags --exclude "*-dev" 3 | - ps: $env:TAG_NUMBER = $env:LAST_TAG.Substring($env:LAST_TAG.IndexOf('v') + 1) 4 | - ps: if ($env:APPVEYOR_REPO_TAG -ne $True){ $env:TAG_NUMBER = $env:TAG_NUMBER + "." + $env:APPVEYOR_BUILD_NUMBER } else { $env:TAG_NUMBER = $env:TAG_NUMBER + "." + 0 } 5 | 6 | version: 1.0.{build} 7 | image: Visual Studio 2019 8 | configuration: Release 9 | 10 | dotnet_csproj: 11 | patch: true 12 | file: '**\*.csproj' 13 | version: '$(TAG_NUMBER)' 14 | package_version: '$(TAG_NUMBER)' 15 | assembly_version: '$(TAG_NUMBER)' 16 | file_version: '$(TAG_NUMBER)' 17 | informational_version: '$(TAG_NUMBER)' 18 | 19 | build: 20 | parallel: true 21 | verbosity: minimal 22 | project: fivem-vstancer.sln 23 | 24 | before_build: 25 | - nuget restore 26 | 27 | after_build: 28 | - cmd: md %APPVEYOR_BUILD_FOLDER%\dist\vstancer 29 | - cmd: move /Y %APPVEYOR_BUILD_FOLDER%\dist\*.* %APPVEYOR_BUILD_FOLDER%\dist\vstancer 30 | - cmd: move /Y %APPVEYOR_BUILD_FOLDER%\VStancer.Client\bin\%CONFIGURATION%\net452\VStancer.Client.net.dll %APPVEYOR_BUILD_FOLDER%\dist\vstancer 31 | - cmd: move /Y %APPVEYOR_BUILD_FOLDER%\VStancer.Client\bin\%CONFIGURATION%\net452\MenuAPI.dll %APPVEYOR_BUILD_FOLDER%\dist\vstancer 32 | - cmd: move /Y %APPVEYOR_BUILD_FOLDER%\VStancer.Client\bin\%CONFIGURATION%\net452\Newtonsoft.Json.dll %APPVEYOR_BUILD_FOLDER%\dist\vstancer 33 | - 7z a vstancer-v%TAG_NUMBER%.zip %APPVEYOR_BUILD_FOLDER%\dist\* 34 | 35 | artifacts: 36 | - path: vstancer-v$(TAG_NUMBER).zip 37 | name: vstancer 38 | 39 | deploy: 40 | provider: GitHub 41 | tag: $(APPVEYOR_REPO_TAG_NAME) 42 | release: vstancer $(APPVEYOR_REPO_TAG_NAME) 43 | auth_token: 44 | secure: koH1kqQLiJXw2cH24CMO1v9OA/RZ/HKeFZA8RSKOznBMsIvodL4HhzKPciA4qyfE 45 | draft: true 46 | prerelease: false 47 | on: 48 | branch: master 49 | APPVEYOR_REPO_TAG: true 50 | -------------------------------------------------------------------------------- /dist/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Debug": false, 3 | "DisableMenu": false, 4 | "ExposeCommand": false, 5 | "ExposeEvent": false, 6 | "ScriptRange": 150.0, 7 | "Timer": 1000, 8 | "ToggleMenuControl": 167, 9 | "FloatStep": 0.01, 10 | "EnableWheelMod": true, 11 | "EnableClientPresets": true, 12 | "WheelLimits": { 13 | "FrontTrackWidth": 0.25, 14 | "RearTrackWidth": 0.25, 15 | "FrontCamber": 0.20, 16 | "RearCamber": 0.20 17 | }, 18 | "WheelModLimits": { 19 | "WheelSize": 0.20, 20 | "WheelWidth": 0.20, 21 | "FrontTireColliderWidth": 0.10, 22 | "FrontTireColliderSize": 0.10, 23 | "FrontRimColliderSize": 0.10, 24 | "RearTireColliderWidth": 0.10, 25 | "RearTireColliderSize": 0.10, 26 | "RearRimColliderSize": 0.10 27 | } 28 | } -------------------------------------------------------------------------------- /dist/fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'bodacious' 2 | games { 'gta5' } 3 | --dependency 'MenuAPI' 4 | 5 | name 'vstancer' 6 | author 'Neos7' 7 | description 'A script to edit wheels of vehicles' 8 | version 'v2.0.0' 9 | url 'https://github.com/carmineos/fivem-vstancer' 10 | 11 | files { 12 | --'@MenuAPI/MenuAPI.dll', 13 | 'MenuAPI.dll', 14 | 'Newtonsoft.Json.dll', 15 | 'config.json' 16 | } 17 | 18 | client_scripts { 19 | 'VStancer.Client.net.dll' 20 | } 21 | 22 | exports { 23 | "GetWheelPreset", 24 | "ResetWheelPreset", 25 | "GetFrontCamber", 26 | "GetRearCamber", 27 | "GetFrontTrackWidth", 28 | "GetRearTrackWidth", 29 | "SetFrontCamber", 30 | "SetRearCamber", 31 | "SetFrontTrackWidth", 32 | "SetRearTrackWidth", 33 | "SaveLocalPreset", 34 | "LoadLocalPreset", 35 | "DeleteLocalPreset", 36 | "GetLocalPresetList" 37 | } -------------------------------------------------------------------------------- /fivem-vstancer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.202 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VStancer.Client", "VStancer.Client\VStancer.Client.csproj", "{5CB92879-4500-47FC-BD3E-FE29369A621D}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{886C4B07-F58C-4E89-A80F-849679435A18}" 9 | ProjectSection(SolutionItems) = preProject 10 | dist\config.json = dist\config.json 11 | dist\fxmanifest.lua = dist\fxmanifest.lua 12 | LICENSE = LICENSE 13 | postbuild.bat = postbuild.bat 14 | README.md = README.md 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {5CB92879-4500-47FC-BD3E-FE29369A621D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {5CB92879-4500-47FC-BD3E-FE29369A621D}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {5CB92879-4500-47FC-BD3E-FE29369A621D}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {5CB92879-4500-47FC-BD3E-FE29369A621D}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(SolutionProperties) = preSolution 29 | HideSolutionNode = FALSE 30 | EndGlobalSection 31 | GlobalSection(ExtensibilityGlobals) = postSolution 32 | SolutionGuid = {54D7AD38-2888-40D9-B4A0-9A235A76D85E} 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /postbuild.bat: -------------------------------------------------------------------------------- 1 | copy /y %1 E:\Carmine\neos7\cfx-server\server-data\resources\[neos7]\[customscripts]\vstancer --------------------------------------------------------------------------------