diff --git a/README.md b/README.md index 99c7839..a8bf9d1 100644 --- a/README.md +++ b/README.md @@ -12,87 +12,342 @@ The synchronization is made using decorators. The default key to open the menu is F6 -### Features -* Edit X Position of the wheels' bones (Track Width) -* Edit Y Rotation of the wheels' bones (Camber) - -### Limitations +### Glossary +* **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. +* **Camber**: It's the Y rotation of the vehicle's wheels bones in the entity local coords system. +* **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" + +### Features of the script +* Edit Track Width of vehicles +* Edit Camber of vehicles +* Edit Tuning Wheel Size of vehicles (Requires a tuning wheel to be installed on the vehicle) +* Edit Tuning Wheel Width of vehicles (Requires a tuning wheel to be installed on the vehicle) +* Manage presets + +### Note 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. 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) -### Roadmap -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) - ### Client Commands * `vstancer_preset`: Prints the preset of the current vehicle - * `vstancer_decorators`: Prints the info about decorators on the current vehicle - * `vstancer_decorators `: Prints the info about decorators on the vehicle with the specified int as local handle - * `vstancer_print`: Prints the list of all the vehicles with any decorator of this script - * `vstancer_range `: Sets the specified float as the maximum distance used to refresh wheels of the vehicles with decorators - * `vstancer_debug `: Enables or disables the logs to be printed in the console - * `vstancer`: Toggles the menu, this command has to be enabled in the config ### Config -* `ToggleMenuControl`:The Control to toggle the Menu, default is 167 which is F6 (check the [controls list](https://docs.fivem.net/game-references/controls/)) - -* `FloatStep`: The step used to increase and decrease a value - -* `PositionX`: The max value you can increase or decrease the Track Width - -* `RotationY`: The max value you can increase or decrease the Camber - -* `ScriptRange`: The max distance within which each client refreshes others clients' vehicles - -* `Timer`: The value in milliseconds used by each client to check if its preset requires to be synched again - * `Debug`: Enables the debug mode, which prints some logs in the console - +* `DisableMenu`: Allows to disable the menu in case you want to allow editing in your own menu using the provided API * `ExposeCommand`: Enables the /vstancer command to toggle the menu - * `ExposeEvent`: Enable the "vstancer:toggleMenu" event to toggle the menu +* `ScriptRange`: The max distance within which each client refreshes edited vehicles +* `Timer`: The value in milliseconds used by each client to do some specific timed tasks +* `ToggleMenuControl`:The Control to toggle the Menu, default is 167 which is F6 (check the [controls list](https://docs.fivem.net/game-references/controls/)) +* `FloatStep`: The step used to increase and decrease a value +* `EnableWheelMod`: Enables the script to edit wheel size and width of tuning wheels +* `EnableClientPresets`: Enables the script to manage clients' presets +* `WheelLimits`: + * `FrontTrackWidth`: The max value you can increase or decrease the front Track Width from its default value + * `RearTrackWidth`: The max value you can increase or decrease the rear Track Width from its default value + * `FrontCamber`: The max value you can increase or decrease the front Camber from its default value + * `RearCamber`: The max value you can increase or decrease the rear Camber from its default value +* `WheelModLimits`: + * `WheelSize`: The max value you can increase or decrease the size of tuning wheels from its default value + * `WheelWidth`: The max value you can increase or decrease the width of tuning wheels from its default value ### Exports +The script exposes some API to manage the main features from other scripts: -Remember that exports require the resource to be called “vstancer” - -```csharp -private void SetVstancerPreset(int vehicle, float off_f, float rot_f, float off_r, float rot_r, object defaultFrontOffset = null, object defaultFrontRotation = null, object defaultRearOffset = null, object defaultRearRotation = null); -private float[] GetVstancerPreset(int vehicle); -``` - -**SET** - -Note that when using the `SetVstancerPreset`, the default values are optional and the script will get them itself if you don't pass them. -This is an example of how to set a vstancer preset on a vehicle: - -C#: ```csharp -Exports["vstancer"].SetVstancerPreset(vehicle,offset_f,rotation_f,offset_r,rotation_r); +bool SetWheelPreset(int vehicle, float frontTrackWidth, float frontCamber, float rearTrackWidth, float rearCamber); +float[] GetWheelPreset(int vehicle); +bool ResetWheelPreset(int vehicle); +float[] GetFrontCamber(int vehicle); +float[] GetRearCamber(int vehicle); +float[] GetFrontTrackWidth(int vehicle); +float[] GetRearTrackWidth(int vehicle); +bool SetFrontCamber(int vehicle, float value); +bool SetRearCamber(int vehicle, float value); +bool SetFrontTrackWidth(int vehicle, float value); +bool SetRearTrackWidth(int vehicle, float value); +bool SaveClientPreset(string presetName, int vehicle); +bool LoadClientPreset(string presetName, int vehicle); +bool DeleteClientPreset(string presetName); +string[] GetClientPresetList(); ``` -Lua: -```lua -exports["vstancer"]:SetVstancerPreset(vehicle,offset_f,rotation_f,offset_r,rotation_r) -``` - -**GET** -When using the `GetVstancerPreset` the returned array will contain the following floats in order: off_f, rot_f, off_r, rot_r, off_f_def, rot_f_def, off_r_def, rot_r_def. -This is an example of how to get a vstancer preset (in case you want to store them): - -C#: -```csharp -float[] preset = Exports["vstancer"].GetVstancerPreset(vehicle); -``` -Lua: -```lua -local preset = exports["vstancer"]:GetVstancerPreset(vehicle); -``` +**NOTE** +Current API don't support editing of tuning wheel data (wheelSize and wheelWidth) yet. + +#### Remember that API require the resource to be called exactly “vstancer” +**API Usage** + +* **SetWheelPreset** + * int vehicle: the handle of the vehicle entity + * float frontTrackWidth: the value you want to assign as front track width + * float frontCamber: the value you want to assign as front camber + * float rearTrackWidth: the value you want to assign as rear track width + * float rearCamber: the value you want to assign as rear camber + * bool result: returns `true` if the action successfully executed otherwise `false` + +
+ Example + + C#: + ```csharp + bool result = Exports["vstancer"].SetWheelPreset(vehicle, frontTrackWidth, frontCamber, rearTrackWidth, rearCamber); + ``` + Lua: + ```lua + local result = exports["vstancer"]:SetWheelPreset(vehicle, frontTrackWidth, frontCamber, rearTrackWidth, rearCamber) + ``` + +
+* **GetWheelPreset** + * int vehicle: the handle of the vehicle entity + * float result: the array containing the oreset values in this order frontTrackWidth, frontCamber, rearTrackWidth, rearCamber. + +
+ Example + + C#: + ```csharp + float[] result = Exports["vstancer"].GetWheelPreset(vehicle); + ``` + Lua: + ```lua + local result = exports["vstancer"]:GetWheelPreset(vehicle); + ``` + +
+* **ResetWheelPreset** + * int vehicle: the handle of the vehicle entity + * bool result: returns `true` if the action successfully executed otherwise `false` + +
+ Example + + C#: + ```csharp + bool result = Exports["vstancer"].ResetWheelPreset(vehicle); + ``` + Lua: + ```lua + local result = exports["vstancer"]:ResetWheelPreset(vehicle); + ``` + +
+* **GetFrontCamber** + * int vehicle: the handle of the vehicle entity + * float[] frontCamber: an array which contains the value as first element if the request has success, otherwise is empty + +
+ Example + + C#: + ```csharp + float[] frontCamber = Exports["vstancer"].GetFrontCamber(vehicle); + ``` + Lua: + ```lua + local frontCamber = exports["vstancer"]:GetFrontCamber(vehicle); + ``` + +
+* **GetRearCamber** + * int vehicle: the handle of the vehicle entity + * float[] rearCamber: an array which contains the value as first element if the request has success, otherwise is empty + +
+ Example + + C#: + ```csharp + float[] rearCamber = Exports["vstancer"].GetRearCamber(vehicle); + ``` + Lua: + ```lua + local rearCamber = exports["vstancer"]:GetRearCamber(vehicle); + ``` + +
+* **GetFrontTrackWidth** + * int vehicle: the handle of the vehicle entity + * float[] frontTrackWidth: an array which contains the value as first element if the request has success, otherwise is empty + +
+ Example + + C#: + ```csharp + float[] frontTrackWidth = Exports["vstancer"].GetFrontTrackWidth(vehicle); + ``` + Lua: + ```lua + local frontTrackWidth = exports["vstancer"]:GetFrontTrackWidth(vehicle); + ``` + +
+* **GetRearTrackWidth** + * int vehicle: the handle of the vehicle entity + * float[] rearTrackWidth: an array which contains the value as first element if the request has success, otherwise is empty + +
+ Example + + C#: + ```csharp + float[] rearTrackWidth = Exports["vstancer"].GetRearTrackWidth(vehicle); + ``` + Lua: + ```lua + local rearTrackWidth = exports["vstancer"]:GetRearTrackWidth(vehicle); + ``` + +
+* **SetFrontCamber** + * int vehicle: the handle of the vehicle entity + * float frontCamber: the value you want to assign as front camber + * bool result: returns `true` if the action successfully executed otherwise `false` + +
+ Example + + C#: + ```csharp + bool result = Exports["vstancer"].SetFrontCamber(vehicle, frontCamber); + ``` + Lua: + ```lua + local result = exports["vstancer"]:SetFrontCamber(vehicle, frontCamber); + ``` + +
+* **SetRearCamber** + * int vehicle: the handle of the vehicle entity + * float rearCamber: the value you want to assign as rear camber + * bool result: returns `true` if the action successfully executed otherwise `false` + +
+ Example + + C#: + ```csharp + bool result = Exports["vstancer"].SetRearCamber(vehicle, rearCamber); + ``` + Lua: + ```lua + local result = exports["vstancer"]:SetRearCamber(vehicle, rearCamber); + ``` + +
+* **SetFrontTrackWidth** + * int vehicle: the handle of the vehicle entity + * float frontTrackWidth: the value you want to assign as front track width + * bool result: returns `true` if the action successfully executed otherwise `false` + +
+ Example + + C#: + ```csharp + bool result = Exports["vstancer"].SetFrontTrackWidth(vehicle, frontTrackWidth); + ``` + Lua: + ```lua + local result = exports["vstancer"]:SetFrontTrackWidth(vehicle, frontTrackWidth); + ``` + +
+* **SetRearTrackWidth** + * int vehicle: the handle of the vehicle entity + * float rearTrackWidth: the value you want to assign as rear track width + * bool result: returns `true` if the action successfully executed otherwise `false` + +
+ Example + + C#: + ```csharp + bool result = Exports["vstancer"].SetRearTrackWidth(vehicle, rearTrackWidth); + ``` + Lua: + ```lua + local result = exports["vstancer"]:SetRearTrackWidth(vehicle, rearTrackWidth); + ``` + +
+* **SaveClientPreset** + * string presetName: the name you want to use for the saved preset + * int vehicle: the handle of the vehicle entity you want to save the preset from + * bool result: returns `true` if the action successfully executed otherwise `false` + +
+ Example + + C#: + ```csharp + bool result = Exports["vstancer"].SaveClientPreset(presetName, vehicle); + ``` + Lua: + ```lua + local result = exports["vstancer"]:SaveClientPreset(presetName, vehicle); + ``` + +
+* **LoadClientPreset** + * string presetName: the name of the preset you want to load + * int vehicle: the handle of the vehicle entity you want to load the preset on + * bool result: returns `true` if the action successfully executed otherwise `false` + +
+ Example + + C#: + ```csharp + bool result = Exports["vstancer"].LoadClientPreset(presetName, vehicle); + ``` + Lua: + ```lua + local result = exports["vstancer"]:LoadClientPreset(presetName, vehicle); + ``` + +
+* **DeleteClientPreset** + * string presetName: the name of the preset you want to delete + * bool result: returns `true` if the action successfully executed otherwise `false` + +
+ Example + + C#: + ```csharp + bool result = Exports["vstancer"].DeleteClientPreset(presetName); + ``` + Lua: + ```lua + local result = exports["vstancer"]:DeleteClientPreset(presetName); + ``` + +
+* **GetClientPresetList** + * string[] presetList: the list of all the presets saved locally + +
+ Example + + C#: + ```csharp + string[] presetList = Exports["vstancer"].GetClientPresetList(); + ``` + Lua: + ```lua + local presetList = exports["vstancer"]:GetClientPresetList(); + ``` + +
[Source](https://github.com/carmineos/fivem-vstancer) [Download](https://github.com/carmineos/fivem-vstancer/releases) @@ -104,10 +359,30 @@ Open the `postbuild.bat` and edit the path of the resource folder. If in Debug c ### Requirements 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. +### Installation +1. Download the zip file from the release page +2. Extract the content of the zip to the resources folder of your server (it should be a folder named `vstancer`) +3. Enable the resource in your server config (`start vstancer`) + +### Todo +* Add API for wheel mod data +* Update local presets API to support wheel mod data +* Add limits check for API +* Add limits check for preset loading +* Workaround wheel mod data being reset after any tuning component is changed +* Clean duplicated code +* API shouldn't allow to edit vehicles other players are driving + +### Roadmap +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) ### Credits -* VStancer by ikt: https://github.com/E66666666/GTAVStancer -* FiveM by CitizenFX: https://github.com/citizenfx/fivem -* MenuAPI by Vespura: https://github.com/TomGrobbe/MenuAPI -* GTADrifting members: https://gtad.club/ +* [VStancer by ikt](https://github.com/E66666666/GTAVStancer) +* [FiveM by CitizenFX](https://github.com/citizenfx/fivem) +* [MenuAPI by Vespura](https://github.com/TomGrobbe/MenuAPI) +* [GTADrifting members](https://gtad.club/) * All the testers + +### Support +If you would like to support my work, you can through: +* [Patreon](https://patreon.com/carmineos) \ No newline at end of file diff --git a/VStancer.Client/Data/WheelData.cs b/VStancer.Client/Data/WheelData.cs new file mode 100644 index 0000000..213d5f3 --- /dev/null +++ b/VStancer.Client/Data/WheelData.cs @@ -0,0 +1,196 @@ +using System; +using System.Text; + +using CitizenFX.Core; + +namespace VStancer.Client.Data +{ + public class WheelData : IEquatable + { + private const float Epsilon = VStancerUtilities.Epsilon; + + public delegate void WheelDataPropertyEdited(string name, float value); + public event WheelDataPropertyEdited PropertyChanged; + + private readonly WheelDataNode[] _nodes; + private readonly WheelDataNode[] _defaultNodes; + + public int WheelsCount { get; private set; } + public int FrontWheelsCount { get; private set; } + + public WheelDataNode[] GetNodes() => (WheelDataNode[])_nodes.Clone(); + + public float FrontTrackWidth + { + get => _nodes[0].PositionX; + set + { + for (int index = 0; index < FrontWheelsCount; index++) + _nodes[index].PositionX = (index % 2 == 0) ? value : -value; + + PropertyChanged?.Invoke(nameof(FrontTrackWidth), value); + } + } + + public float RearTrackWidth + { + get => _nodes[FrontWheelsCount].PositionX; + set + { + for (int index = FrontWheelsCount; index < WheelsCount; index++) + _nodes[index].PositionX = (index % 2 == 0) ? value : -value; + + PropertyChanged?.Invoke(nameof(RearTrackWidth), value); + } + } + + public float FrontCamber + { + get => _nodes[0].RotationY; + set + { + for (int index = 0; index < FrontWheelsCount; index++) + _nodes[index].RotationY = (index % 2 == 0) ? value : -value; + + PropertyChanged?.Invoke(nameof(FrontCamber), value); + } + } + + public float RearCamber + { + get => _nodes[FrontWheelsCount].RotationY; + set + { + for (int index = FrontWheelsCount; index < WheelsCount; index++) + _nodes[index].RotationY = (index % 2 == 0) ? value : -value; + + PropertyChanged?.Invoke(nameof(RearCamber), value); + } + } + + public float DefaultFrontTrackWidth { get => _defaultNodes[0].PositionX; } + public float DefaultRearTrackWidth { get => _defaultNodes[FrontWheelsCount].PositionX; } + public float DefaultFrontCamber { get => _defaultNodes[0].RotationY; } + public float DefaultRearCamber { get => _defaultNodes[FrontWheelsCount].RotationY; } + + public bool IsEdited + { + get + { + for (int i = 0; i < WheelsCount; i++) + { + if (!MathUtil.WithinEpsilon(_defaultNodes[i].PositionX, _nodes[i].PositionX, Epsilon) || + !MathUtil.WithinEpsilon(_defaultNodes[i].RotationY, _nodes[i].RotationY, Epsilon)) + return true; + } + return false; + } + } + + public WheelData(int count, float defaultFrontOffset, float defaultFrontRotation, float defaultRearOffset, float defaultRearRotation) + { + WheelsCount = count; + + _defaultNodes = new WheelDataNode[WheelsCount]; + + FrontWheelsCount = VStancerUtilities.CalculateFrontWheelsCount(WheelsCount); + + for (int i = 0; i < FrontWheelsCount; i++) + { + if (i % 2 == 0) + { + _defaultNodes[i].RotationY = defaultFrontRotation; + _defaultNodes[i].PositionX = defaultFrontOffset; + } + else + { + _defaultNodes[i].RotationY = -defaultFrontRotation; + _defaultNodes[i].PositionX = -defaultFrontOffset; + } + } + + for (int i = FrontWheelsCount; i < WheelsCount; i++) + { + if (i % 2 == 0) + { + _defaultNodes[i].RotationY = defaultRearRotation; + _defaultNodes[i].PositionX = defaultRearOffset; + } + else + { + _defaultNodes[i].RotationY = -defaultRearRotation; + _defaultNodes[i].PositionX = -defaultRearOffset; + } + } + + _nodes = new WheelDataNode[WheelsCount]; + for (int i = 0; i < WheelsCount; i++) + { + _nodes[i] = _defaultNodes[i]; + } + } + + public void Reset() + { + for (int i = 0; i < WheelsCount; i++) + _nodes[i] = _defaultNodes[i]; + + PropertyChanged?.Invoke(nameof(Reset), default); + } + + public override string ToString() + { + StringBuilder s = new StringBuilder(); + s.AppendLine($"Edited:{IsEdited} Wheels count:{WheelsCount} Front count:{FrontWheelsCount}"); + + StringBuilder defOff = new StringBuilder(string.Format("{0,20}", "Default track width:")); + StringBuilder defRot = new StringBuilder(string.Format("{0,20}", "Default camber:")); + StringBuilder curOff = new StringBuilder(string.Format("{0,20}", "Current track width:")); + StringBuilder curRot = new StringBuilder(string.Format("{0,20}", "Current camber:")); + + for (int i = 0; i < WheelsCount; i++) + { + defOff.Append(string.Format("{0,15}", _defaultNodes[i].PositionX)); + defRot.Append(string.Format("{0,15}", _defaultNodes[i].RotationY)); + curOff.Append(string.Format("{0,15}", _nodes[i].PositionX)); + curRot.Append(string.Format("{0,15}", _nodes[i].RotationY)); + } + + s.AppendLine(curOff.ToString()); + s.AppendLine(defOff.ToString()); + s.AppendLine(curRot.ToString()); + s.AppendLine(defRot.ToString()); + + return s.ToString(); + } + + public bool Equals(WheelData other) + { + if (WheelsCount != other.WheelsCount) + return false; + + for (int i = 0; i < WheelsCount; i++) + { + if (!MathUtil.WithinEpsilon(_defaultNodes[i].PositionX, other._defaultNodes[i].PositionX, Epsilon) || + !MathUtil.WithinEpsilon(_defaultNodes[i].RotationY, other._defaultNodes[i].RotationY, Epsilon) || + !MathUtil.WithinEpsilon(_nodes[i].PositionX, other._nodes[i].PositionX, Epsilon) || + !MathUtil.WithinEpsilon(_nodes[i].RotationY, other._nodes[i].RotationY, Epsilon)) + return false; + } + return true; + } + } + + public struct WheelDataNode + { + /// + /// The track width of the wheel + /// + public float PositionX { get; set; } + + /// + /// The camber of the wheel + /// + public float RotationY { get; set; } + } +} diff --git a/VStancer.Client/Data/WheelModData.cs b/VStancer.Client/Data/WheelModData.cs new file mode 100644 index 0000000..987141e --- /dev/null +++ b/VStancer.Client/Data/WheelModData.cs @@ -0,0 +1,256 @@ +using System.Text; + +using CitizenFX.Core; + +namespace VStancer.Client.Data +{ + public class WheelModData + { + private const float Epsilon = VStancerUtilities.Epsilon; + + private readonly WheelModNode[] _nodes; + private readonly WheelModNode[] _defaultNodes; + private float _wheelSize; + private float _wheelWidth; + + public int WheelsCount { get; private set; } + public int FrontWheelsCount { get; private set; } + + public delegate void WheelModPropertyEdited(string name, float value); + public event WheelModPropertyEdited PropertyChanged; + + public float WheelSize + { + get => _wheelSize; + set + { + if (value == _wheelSize) + return; + + _wheelSize = value; + PropertyChanged?.Invoke(nameof(WheelSize), value); + } + } + + public float WheelWidth + { + get => _wheelWidth; + set + { + if (value == _wheelWidth) + return; + + _wheelWidth = value; + PropertyChanged?.Invoke(nameof(WheelWidth), value); + } + } + + public float DefaultWheelSize { get; private set; } + public float DefaultWheelWidth { get; private set; } + + public float DefaultFrontTireColliderWidthRatio { get; private set; } + public float DefaultFrontTireColliderSizeRatio { get; private set; } + public float DefaultFrontRimColliderSizeRatio { get; private set; } + public float DefaultRearTireColliderWidthRatio { get; private set; } + public float DefaultRearTireColliderSizeRatio { get; private set; } + public float DefaultRearRimColliderSizeRatio { get; private set; } + + + + public WheelModData(int wheelsCount, float width, float radius, + float frontTireColliderWidth, float frontTireColliderSize, float frontRimColliderSize, + float rearTireColliderWidth, float rearTireColliderSize, float rearRimColliderSize) + { + WheelsCount = wheelsCount; + FrontWheelsCount = VStancerUtilities.CalculateFrontWheelsCount(WheelsCount); + + DefaultWheelSize = radius; + DefaultWheelWidth = width; + + _wheelSize = radius; + _wheelWidth = width; + + _defaultNodes = new WheelModNode[WheelsCount]; + + for (int i = 0; i < FrontWheelsCount; i++) + { + _defaultNodes[i].TireColliderWidth = frontTireColliderWidth; + _defaultNodes[i].TireColliderSize = frontTireColliderSize; + _defaultNodes[i].RimColliderSize = frontRimColliderSize; + } + + for (int i = FrontWheelsCount; i < WheelsCount; i++) + { + _defaultNodes[i].TireColliderWidth = rearTireColliderWidth; + _defaultNodes[i].TireColliderSize = rearTireColliderSize; + _defaultNodes[i].RimColliderSize = rearRimColliderSize; + } + + DefaultFrontTireColliderWidthRatio = DefaultWheelWidth / DefaultFrontTireColliderWidth; + DefaultFrontTireColliderSizeRatio = DefaultWheelSize / DefaultFrontTireColliderSize; + DefaultFrontRimColliderSizeRatio = DefaultWheelSize / DefaultFrontRimColliderSize; + + DefaultRearTireColliderWidthRatio = DefaultWheelWidth / DefaultRearTireColliderWidth; + DefaultRearTireColliderSizeRatio = DefaultWheelSize / DefaultRearTireColliderSize; + DefaultRearRimColliderSizeRatio = DefaultWheelSize / DefaultRearRimColliderSize; + + _nodes = new WheelModNode[WheelsCount]; + for (int i = 0; i < WheelsCount; i++) + _nodes[i] = _defaultNodes[i]; + } + + + public float FrontTireColliderWidth + { + get => _nodes[0].TireColliderWidth; + set + { + for (int index = 0; index < FrontWheelsCount; index++) + _nodes[index].TireColliderWidth = value; + + PropertyChanged?.Invoke(nameof(FrontTireColliderWidth), value); + } + } + + + public float FrontTireColliderSize + { + get => _nodes[0].TireColliderSize; + set + { + for (int index = 0; index < FrontWheelsCount; index++) + _nodes[index].TireColliderSize = value; + + PropertyChanged?.Invoke(nameof(FrontTireColliderSize), value); + } + } + + + public float FrontRimColliderSize + { + get => _nodes[0].RimColliderSize; + set + { + for (int index = 0; index < FrontWheelsCount; index++) + _nodes[index].RimColliderSize = value; + + PropertyChanged?.Invoke(nameof(FrontRimColliderSize), value); + } + } + + + public float RearTireColliderWidth + { + get => _nodes[FrontWheelsCount].TireColliderWidth; + set + { + for (int index = FrontWheelsCount; index < WheelsCount; index++) + _nodes[index].TireColliderWidth = value; + + PropertyChanged?.Invoke(nameof(RearTireColliderWidth), value); + } + } + + + public float RearTireColliderSize + { + get => _nodes[FrontWheelsCount].TireColliderSize; + set + { + for (int index = FrontWheelsCount; index < WheelsCount; index++) + _nodes[index].TireColliderSize = value; + + PropertyChanged?.Invoke(nameof(RearTireColliderSize), value); + } + } + + public float RearRimColliderSize + { + get => _nodes[FrontWheelsCount].RimColliderSize; + set + { + for (int index = FrontWheelsCount; index < WheelsCount; index++) + _nodes[index].RimColliderSize = value; + + PropertyChanged?.Invoke(nameof(RearRimColliderSize), value); + } + } + + public float DefaultFrontTireColliderWidth => _defaultNodes[0].TireColliderWidth; + public float DefaultFrontTireColliderSize => _defaultNodes[0].TireColliderSize; + public float DefaultFrontRimColliderSize => _defaultNodes[0].RimColliderSize; + + public float DefaultRearTireColliderWidth => _defaultNodes[FrontWheelsCount].TireColliderWidth; + public float DefaultRearTireColliderSize => _defaultNodes[FrontWheelsCount].TireColliderSize; + public float DefaultRearRimColliderSize => _defaultNodes[FrontWheelsCount].RimColliderSize; + + public void Reset() + { + WheelSize = DefaultWheelSize; + WheelWidth = DefaultWheelWidth; + + for (int i = 0; i < WheelsCount; i++) + _nodes[i] = _defaultNodes[i]; + + PropertyChanged?.Invoke(nameof(Reset), default); + } + + public bool IsEdited + { + get + { + if (!MathUtil.WithinEpsilon(DefaultWheelSize, WheelSize, Epsilon) || + !MathUtil.WithinEpsilon(DefaultWheelWidth, WheelWidth, Epsilon)) + return true; + + for (int i = 0; i < WheelsCount; i++) + { + if (!MathUtil.WithinEpsilon(_defaultNodes[i].TireColliderWidth, _nodes[i].TireColliderWidth, Epsilon) || + !MathUtil.WithinEpsilon(_defaultNodes[i].TireColliderSize, _nodes[i].TireColliderSize, Epsilon) || + !MathUtil.WithinEpsilon(_defaultNodes[i].RimColliderSize, _nodes[i].RimColliderSize, Epsilon)) + return true; + } + return false; + } + } + + public override string ToString() + { + StringBuilder s = new StringBuilder(); + s.AppendLine($"{nameof(WheelModData)}, Edited:{IsEdited}"); + s.AppendLine($"{nameof(WheelSize)}: {WheelSize} ({DefaultWheelSize})"); + s.AppendLine($"{nameof(WheelWidth)}: {WheelWidth} ({DefaultWheelWidth})"); + + for (int i = 0; i < WheelsCount; i++) + { + var defNode = _defaultNodes[i]; + var node = _nodes[i]; + s.Append($"Wheel {i}: {nameof(WheelModNode.TireColliderWidth)}: {node.TireColliderWidth} ({defNode.TireColliderWidth})"); + s.Append($" {nameof(WheelModNode.TireColliderSize)}: {node.TireColliderSize} ({defNode.TireColliderSize})"); + s.AppendLine($" {nameof(WheelModNode.RimColliderSize)}: {node.RimColliderSize} ({defNode.RimColliderSize})"); + } + return s.ToString(); + } + } + + public struct WheelModNode + { + /// + /// The collider wheel thread size + /// For vanilla wheel mod this is always 50% of visual width + /// + public float TireColliderWidth { get; set; } + + /// + /// The collider wheel radius + /// For vanilla wheel mod this is always 50% of visual size + /// + public float TireColliderSize { get; set; } + + /// + /// The collider wheel radius + /// Defined as rimRadius in carcols for wheels (CVehicleModelInfoVarGlobal) + /// + public float RimColliderSize { get; set; } + } +} diff --git a/VStancer.Client/Enumerables.cs b/VStancer.Client/Enumerables.cs deleted file mode 100644 index 324ca5f..0000000 --- a/VStancer.Client/Enumerables.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using static CitizenFX.Core.Native.API; - -namespace VStancer.Client -{ - public class KvpEnumerable : IEnumerable - { - public string prefix { get; set; } = string.Empty; - - public KvpEnumerable(string kvpprefix) - { - this.prefix = kvpprefix; - } - - public IEnumerator GetEnumerator() - { - int handle = StartFindKvp(prefix); - - if (handle != -1) - { - string kvp; - do - { - kvp = FindKvp(handle); - - if (kvp != null) - yield return kvp; - } - while (kvp != null); - EndFindKvp(handle); - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } - - public class VehicleEnumerable : IEnumerable - { - public IEnumerator GetEnumerator() - { - int entity = -1; - int handle = FindFirstVehicle(ref entity); - - if (handle != -1) - { - do yield return entity; - while (FindNextVehicle(handle, ref entity)); - - EndFindVehicle(handle); - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} diff --git a/VStancer.Client/Globals.cs b/VStancer.Client/Globals.cs index 4861afb..6bbfa0a 100644 --- a/VStancer.Client/Globals.cs +++ b/VStancer.Client/Globals.cs @@ -16,5 +16,10 @@ public static class Globals /// The expected name of the resource /// public const string ResourceName = "vstancer"; + + /// + /// The prefix used for commands exposed by the script + /// + public const string CommandPrefix = "vstancer_"; } } diff --git a/VStancer.Client/PresetManager/IPresetManager.cs b/VStancer.Client/Preset/IPresetsCollection.cs similarity index 78% rename from VStancer.Client/PresetManager/IPresetManager.cs rename to VStancer.Client/Preset/IPresetsCollection.cs index 65ee95a..d4e90e9 100644 --- a/VStancer.Client/PresetManager/IPresetManager.cs +++ b/VStancer.Client/Preset/IPresetsCollection.cs @@ -1,14 +1,14 @@ using System; using System.Collections.Generic; -namespace VStancer.Client +namespace VStancer.Client.Preset { - public interface IPresetManager + public interface IPresetsCollection { /// /// Invoked when an element is saved or deleted /// - event EventHandler PresetsListChanged; + event EventHandler PresetsCollectionChanged; /// /// Saves the using the as preset name @@ -26,11 +26,11 @@ public interface IPresetManager bool Delete(TKey name); /// - /// Loads and the returns the named + /// Loads and the returns the named /// /// /// - TValue Load(TKey name); + bool Load(TKey name, out TValue value); /// /// Returns the list of all the saved keys diff --git a/VStancer.Client/PresetManager/KvpPresetManager.cs b/VStancer.Client/Preset/KvpPresetsCollection.cs similarity index 70% rename from VStancer.Client/PresetManager/KvpPresetManager.cs rename to VStancer.Client/Preset/KvpPresetsCollection.cs index 8261129..b466331 100644 --- a/VStancer.Client/PresetManager/KvpPresetManager.cs +++ b/VStancer.Client/Preset/KvpPresetsCollection.cs @@ -1,20 +1,22 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; + using static CitizenFX.Core.Native.API; +using Newtonsoft.Json; +using System.Linq; -namespace VStancer.Client +namespace VStancer.Client.Preset { /// /// The vstancer preset manager which saves the presets as key-value pairs built-in FiveM /// - public class KvpPresetManager : IPresetManager + public class KvpPresetsCollection : IPresetsCollection { - private string mKvpPrefix; + private readonly string mKvpPrefix; - public event EventHandler PresetsListChanged; + public event EventHandler PresetsCollectionChanged; - public KvpPresetManager(string prefix) + public KvpPresetsCollection(string prefix) { mKvpPrefix = prefix; } @@ -36,7 +38,7 @@ public bool Delete(string name) DeleteResourceKvp(key); // Invoke the event - PresetsListChanged?.Invoke(this, EventArgs.Empty); + PresetsCollectionChanged?.Invoke(this, EventArgs.Empty); return true; } @@ -61,16 +63,18 @@ public bool Save(string name, VStancerPreset preset) SetResourceKvp(key, json); // Invoke the event - PresetsListChanged?.Invoke(this, EventArgs.Empty); + PresetsCollectionChanged?.Invoke(this, EventArgs.Empty); return true; } - public VStancerPreset Load(string name) + public bool Load(string name, out VStancerPreset preset) { + preset = null; + // Check if the preset ID is valid if (string.IsNullOrEmpty(name)) - return null; + return false; // Get the KVP key string key = string.Concat(mKvpPrefix, name); @@ -80,17 +84,16 @@ public VStancerPreset Load(string name) // Check if the value is valid if (string.IsNullOrEmpty(value)) - return null; + return false; // Create a preset - VStancerPreset preset = JsonConvert.DeserializeObject(value); - - return preset; + preset = JsonConvert.DeserializeObject(value); + return true; } public IEnumerable GetKeys() { - return new KvpEnumerable(mKvpPrefix); + return VStancerUtilities.GetKeyValuePairs(mKvpPrefix).Select(key => key.Remove(0, mKvpPrefix.Length)); } } } diff --git a/VStancer.Client/Preset/VStancerPreset.cs b/VStancer.Client/Preset/VStancerPreset.cs new file mode 100644 index 0000000..c469378 --- /dev/null +++ b/VStancer.Client/Preset/VStancerPreset.cs @@ -0,0 +1,80 @@ +using VStancer.Client.Data; + +namespace VStancer.Client.Preset +{ + public class VStancerPreset + { + public WheelPreset WheelPreset { get; set; } + public WheelModPreset WheelModPreset { get; set; } + } + + public class WheelPreset + { + public float FrontTrackWidth { get; set; } + public float FrontCamber { get; set; } + public float RearTrackWidth { get; set; } + public float RearCamber { get; set; } + + public WheelPreset() + { + + } + + public WheelPreset(float frontTrackWidth, float frontCamber, float rearTrackWidth, float rearCamber) + { + FrontTrackWidth = frontTrackWidth; + FrontCamber = frontCamber; + RearTrackWidth = rearTrackWidth; + RearCamber = rearCamber; + } + + public WheelPreset(WheelData data) + { + if (data == null) + return; + + FrontTrackWidth = data.FrontTrackWidth; + FrontCamber = data.FrontCamber; + RearTrackWidth = data.RearTrackWidth; + RearCamber = data.RearCamber; + } + + public float[] ToArray() { return new float[] { FrontTrackWidth, FrontCamber, RearTrackWidth, RearCamber }; } + } + + public class WheelModPreset + { + public float WheelSize { get; set; } + public float WheelWidth { get; set; } + public float FrontTireColliderWidth { get; set; } + public float FrontTireColliderSize { get; set; } + public float FrontRimColliderSize { get; set; } + public float RearTireColliderWidth { get; set; } + public float RearTireColliderSize { get; set; } + public float RearRimColliderSize { get; set; } + + public WheelModPreset() + { + + } + + public WheelModPreset(WheelModData data) + { + if (data == null) + return; + + WheelSize = data.WheelSize; + WheelWidth = data.WheelWidth; + + FrontTireColliderWidth = data.FrontTireColliderWidth; + FrontTireColliderSize = data.FrontTireColliderSize; + FrontRimColliderSize = data.FrontRimColliderSize; + + RearTireColliderWidth = data.RearTireColliderWidth; + RearTireColliderSize = data.FrontTireColliderSize; + RearRimColliderSize = data.RearRimColliderSize; + } + + public float[] ToArray() { return new float[] { WheelSize, WheelWidth, FrontTireColliderWidth, FrontTireColliderSize, FrontRimColliderSize, RearTireColliderWidth, RearTireColliderSize, RearRimColliderSize }; } + } +} diff --git a/VStancer.Client/Scripts/ClientPresetsScript.cs b/VStancer.Client/Scripts/ClientPresetsScript.cs new file mode 100644 index 0000000..b16037c --- /dev/null +++ b/VStancer.Client/Scripts/ClientPresetsScript.cs @@ -0,0 +1,131 @@ +using System.Threading.Tasks; + +using VStancer.Client.UI; + +using CitizenFX.Core.UI; +using VStancer.Client.Preset; +using System.Collections.Generic; +using CitizenFX.Core; +using static CitizenFX.Core.Native.API; + +namespace VStancer.Client.Scripts +{ + internal class ClientPresetsScript + { + private readonly MainScript _mainScript; + + internal IPresetsCollection Presets { get; private set; } + internal ClientPresetsMenu Menu { get; private set; } + + public ClientPresetsScript(MainScript mainScript) + { + _mainScript = mainScript; + Presets = new KvpPresetsCollection(Globals.KvpPrefix); + + if (!_mainScript.Config.DisableMenu) + { + Menu = new ClientPresetsMenu(this); + + Menu.DeletePresetEvent += (sender, presetID) => OnDeletePresetInvoked(presetID); + Menu.SavePresetEvent += (sender, presetID) => OnSavePresetInvoked(presetID); + Menu.ApplyPresetEvent += (sender, presetID) => OnApplyPresetInvoked(presetID); + } + } + + internal async Task GetPresetNameFromUser(string title, string defaultText) + { + return await _mainScript.GetOnScreenString(title, defaultText); + } + + private void OnDeletePresetInvoked(string presetKey) + { + if (Presets.Delete(presetKey)) + Screen.ShowNotification($"Client preset ~r~{presetKey}~w~ deleted"); + else + Screen.ShowNotification($"~r~ERROR~w~ No preset found with {presetKey} key."); + } + + private void OnSavePresetInvoked(string presetKey) + { + VStancerPreset preset = new VStancerPreset + { + WheelPreset = _mainScript.WheelScript?.GetWheelPreset(), + WheelModPreset = _mainScript.WheelModScript?.GetWheelModPreset() + }; + + if (Presets.Save(presetKey, preset)) + Screen.ShowNotification($"Client preset ~g~{presetKey}~w~ saved"); + else + Screen.ShowNotification($"~r~ERROR~w~ The name {presetKey} is invalid or already used."); + } + + private async void OnApplyPresetInvoked(string presetKey) + { + if (!Presets.Load(presetKey, out VStancerPreset loadedPreset)) + { + Screen.ShowNotification($"~r~ERROR~w~ No Client preset with name ~b~{presetKey}~w~ found"); + return; + } + + if (loadedPreset == null) + { + Screen.ShowNotification($"~r~ERROR~w~ Client preset ~b~{presetKey}~w~ corrupted"); + return; + } + + await _mainScript.WheelScript.SetWheelPreset(loadedPreset.WheelPreset); + await _mainScript.WheelModScript.SetWheelModPreset(loadedPreset.WheelModPreset); + + Screen.ShowNotification($"Client preset ~b~{presetKey}~w~ applied"); + } + + internal bool API_DeletePreset(string presetKey) + { + return Presets.Delete(presetKey); + } + + internal bool API_SavePreset(string presetKey, int vehicle) + { + if (_mainScript.WheelScript == null) + return false; + + if (!_mainScript.WheelScript.API_GetWheelPreset(vehicle, out WheelPreset wheelPreset)) + return false; + + if(wheelPreset != null) + { + VStancerPreset preset = new VStancerPreset + { + WheelPreset = wheelPreset + }; + + return Presets.Save(presetKey, preset); + } + + return false; + } + + internal bool API_LoadPreset(string presetKey, int vehicle) + { + if (!Presets.Load(presetKey, out VStancerPreset loadedPreset)) + return false; + + if (loadedPreset == null) + return false; + + if(_mainScript.WheelScript != null) + { + return _mainScript.WheelScript.API_SetWheelPreset(vehicle, loadedPreset.WheelPreset); + } + + // TODO: Load wheel mod preset on vehicle + + return false; + } + + internal IEnumerable API_GetClientPresetList() + { + return Presets.GetKeys(); + } + } +} diff --git a/VStancer.Client/Scripts/MainScript.cs b/VStancer.Client/Scripts/MainScript.cs new file mode 100644 index 0000000..7ecdadc --- /dev/null +++ b/VStancer.Client/Scripts/MainScript.cs @@ -0,0 +1,440 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using VStancer.Client.UI; + +using CitizenFX.Core; +using static CitizenFX.Core.Native.API; +using Newtonsoft.Json; +using VStancer.Client.Preset; +using System.Linq; + +namespace VStancer.Client.Scripts +{ + public class MainScript : BaseScript + { + private readonly MainMenu Menu; + + private long _lastTime; + private int _playerVehicleHandle; + private int _playerPedHandle; + private Vector3 _playerPedCoords; + private List _worldVehiclesHandles; + private float _maxDistanceSquared; + + internal int PlayerVehicleHandle + { + get => _playerVehicleHandle; + private set + { + if (Equals(_playerVehicleHandle, value)) + return; + + _playerVehicleHandle = value; + PlayerVehicleHandleChanged?.Invoke(this, value); + } + } + + internal int PlayerPedHandle + { + get => _playerPedHandle; + private set + { + if (Equals(_playerPedHandle, value)) + return; + + _playerPedHandle = value; + PlayerPedHandleChanged?.Invoke(this, value); + } + } + + internal event EventHandler PlayerVehicleHandleChanged; + internal event EventHandler PlayerPedHandleChanged; + + internal event EventHandler ToggleMenuVisibility; + + internal VStancerConfig Config { get; private set; } + internal WheelScript WheelScript { get; private set; } + internal WheelModScript WheelModScript { get; private set; } + internal ClientPresetsScript ClientPresetsScript { get; private set; } + + public MainScript() + { + if (GetCurrentResourceName() != Globals.ResourceName) + { + Debug.WriteLine($"{nameof(MainScript)}: Invalid resource name, be sure the resource name is {Globals.ResourceName}"); + return; + } + + _lastTime = GetGameTimer(); + _playerVehicleHandle = -1; + _playerPedHandle = -1; + _playerPedCoords = Vector3.Zero; + _worldVehiclesHandles = new List(); + _maxDistanceSquared = 10; + + Config = LoadConfig(); + _maxDistanceSquared = (float)Math.Sqrt(Config.ScriptRange); + WheelScript = new WheelScript(this); + RegisterScript(WheelScript); + + if (Config.EnableWheelMod) + { + WheelModScript = new WheelModScript(this); + RegisterScript(WheelModScript); + } + + if (Config.EnableClientPresets) + { + ClientPresetsScript = new ClientPresetsScript(this); + } + + if (!Config.DisableMenu) + Menu = new MainMenu(this); + + Tick += GetPlayerAndVehicleTask; + Tick += TimedTask; + Tick += HideUITask; + + RegisterCommands(); + + Exports.Add("SetWheelPreset", new Func(SetWheelPreset)); + Exports.Add("GetWheelPreset", new Func(GetWheelPreset)); + Exports.Add("ResetWheelPreset", new Func(ResetWheelPreset)); + + Exports.Add("SetFrontCamber", new Func(SetFrontCamber)); + Exports.Add("SetRearCamber", new Func(SetRearCamber)); + Exports.Add("SetFrontTrackWidth", new Func(SetFrontTrackWidth)); + Exports.Add("SetRearTrackWidth", new Func(SetRearTrackWidth)); + + Exports.Add("GetFrontCamber", new Func(GetFrontCamber)); + Exports.Add("GetRearCamber", new Func(GetRearCamber)); + Exports.Add("GetFrontTrackWidth", new Func(GetFrontTrackWidth)); + Exports.Add("GetRearTrackWidth", new Func(GetRearTrackWidth)); + + Exports.Add("SaveClientPreset", new Func(SaveClientPreset)); + Exports.Add("LoadClientPreset", new Func(LoadClientPreset)); + Exports.Add("DeleteClientPreset", new Func(DeleteClientPreset)); + Exports.Add("GetClientPresetList", new Func(GetClientPresetList)); + } + + private async Task HideUITask() + { + if (Menu != null) + Menu.HideMenu = _playerVehicleHandle == -1; + + await Task.FromResult(0); + } + + internal List GetCloseVehicleHandles() + { + List closeVehicles = new List(); + + foreach (int handle in _worldVehiclesHandles) + { + if (!DoesEntityExist(handle)) + continue; + + Vector3 coords = GetEntityCoords(handle, true); + + if (Vector3.DistanceSquared(_playerPedCoords, coords) <= _maxDistanceSquared) + closeVehicles.Add(handle); + } + + return closeVehicles; + } + + private async Task TimedTask() + { + long currentTime = GetGameTimer() - _lastTime; + + if (currentTime > Config.Timer) + { + _playerPedCoords = GetEntityCoords(_playerPedHandle, true); + + _worldVehiclesHandles = VStancerUtilities.GetWorldVehicles(); + + _lastTime = GetGameTimer(); + } + + await Task.FromResult(0); + } + + private async Task GetPlayerAndVehicleTask() + { + await Task.FromResult(0); + + _playerPedHandle = PlayerPedId(); + + if (!IsPedInAnyVehicle(_playerPedHandle, false)) + { + PlayerVehicleHandle = -1; + return; + } + + int vehicle = GetVehiclePedIsIn(_playerPedHandle, false); + + // If this model isn't a car, or player isn't the driver, or vehicle is not driveable + if (!IsThisModelACar((uint)GetEntityModel(vehicle)) || GetPedInVehicleSeat(vehicle, -1) != _playerPedHandle || !IsVehicleDriveable(vehicle, false)) + { + PlayerVehicleHandle = -1; + return; + } + + PlayerVehicleHandle = vehicle; + } + + private VStancerConfig LoadConfig(string filename = "config.json") + { + VStancerConfig config; + + try + { + string strings = LoadResourceFile(Globals.ResourceName, filename); + config = JsonConvert.DeserializeObject(strings); + + Debug.WriteLine($"{nameof(MainScript)}: Loaded config from {filename}"); + } + catch (Exception e) + { + Debug.WriteLine($"{nameof(MainScript)}: Impossible to load {filename}", e.Message); + Debug.WriteLine(e.StackTrace); + + config = new VStancerConfig(); + } + + return config; + } + + public async Task GetOnScreenString(string title, string defaultText) + { + DisplayOnscreenKeyboard(1, title, "", defaultText, "", "", "", 128); + while (UpdateOnscreenKeyboard() != 1 && UpdateOnscreenKeyboard() != 2) await Delay(100); + + return GetOnscreenKeyboardResult(); + } + + private void RegisterCommands() + { + RegisterCommand($"{Globals.CommandPrefix}decorators", new Action((source, args) => + { + if (args.Count < 1) + { + WheelScript.PrintDecoratorsInfo(_playerVehicleHandle); + WheelModScript.PrintDecoratorsInfo(_playerVehicleHandle); + } + else + { + if (int.TryParse(args[0], out int value)) + { + WheelScript.PrintDecoratorsInfo(value); + WheelModScript.PrintDecoratorsInfo(value); + } + else Debug.WriteLine($"{nameof(MainScript)}: Error parsing entity handle {args[0]} as int"); + } + }), false); + + RegisterCommand($"{Globals.CommandPrefix}range", new Action((source, args) => + { + if (args.Count < 1) + { + Debug.WriteLine($"{nameof(MainScript)}: Missing float argument"); + return; + } + + if (float.TryParse(args[0], out float value)) + { + Config.ScriptRange = value; + _maxDistanceSquared = (float)Math.Sqrt(value); + Debug.WriteLine($"{nameof(MainScript)}: {nameof(Config.ScriptRange)} updated to {value}"); + } + else Debug.WriteLine($"{nameof(MainScript)}: Error parsing {args[0]} as float"); + + }), false); + + RegisterCommand($"{Globals.CommandPrefix}debug", new Action((source, args) => + { + if (args.Count < 1) + { + Debug.WriteLine($"{nameof(MainScript)}: Missing bool argument"); + return; + } + + if (bool.TryParse(args[0], out bool value)) + { + Config.Debug = value; + Debug.WriteLine($"{nameof(MainScript)}: {nameof(Config.Debug)} updated to {value}"); + } + else Debug.WriteLine($"{nameof(MainScript)}: Error parsing {args[0]} as bool"); + + }), false); + + RegisterCommand($"{Globals.CommandPrefix}preset", new Action((source, args) => + { + if (WheelScript?.WheelData != null) + Debug.WriteLine(WheelScript.WheelData.ToString()); + else + Debug.WriteLine($"{nameof(MainScript)}: {nameof(WheelScript.WheelData)} is null"); + + if (WheelModScript?.WheelModData != null) + Debug.WriteLine(WheelModScript.WheelModData.ToString()); + else + Debug.WriteLine($"{nameof(MainScript)}: {nameof(WheelModScript.WheelModData)} is null"); + }), false); + + RegisterCommand($"{Globals.CommandPrefix}print", new Action((source, args) => + { + if (WheelScript != null) + WheelScript.PrintVehiclesWithDecorators(_worldVehiclesHandles); + if (WheelModScript != null) + WheelModScript.PrintVehiclesWithDecorators(_worldVehiclesHandles); + }), false); + + if(!Config.DisableMenu) + { + if (Config.ExposeCommand) + RegisterCommand("vstancer", new Action((source, args) => { ToggleMenuVisibility?.Invoke(this, EventArgs.Empty); }), false); + + if (Config.ExposeEvent) + EventHandlers.Add("vstancer:toggleMenu", new Action(() => { ToggleMenuVisibility?.Invoke(this, EventArgs.Empty); })); + } + } + + public float[] GetWheelPreset(int vehicle) + { + if (WheelScript == null) + return new float[] { }; + + if (WheelScript.API_GetWheelPreset(vehicle, out WheelPreset preset)) + return preset.ToArray(); + + return new float[] { }; + } + + public bool SetWheelPreset(int vehicle, float frontTrackWidth, float frontCamber, float rearTrackWidth, float rearCamber) + { + if (WheelScript == null) + return false; + + WheelPreset preset = new WheelPreset(frontTrackWidth, frontCamber, rearTrackWidth, rearCamber); + return WheelScript.API_SetWheelPreset(vehicle, preset); + } + + public bool ResetWheelPreset(int vehicle) + { + if (WheelScript == null) + return false; + + return WheelScript.API_ResetWheelPreset(vehicle); + } + + public bool SetFrontCamber(int vehicle, float value) + { + if (WheelScript == null) + return false; + + return WheelScript.API_SetFrontCamber(vehicle, value); + } + + public bool SetRearCamber(int vehicle, float value) + { + if (WheelScript == null) + return false; + + return WheelScript.API_SetRearCamber(vehicle, value); + } + + public bool SetFrontTrackWidth(int vehicle, float value) + { + if (WheelScript == null) + return false; + + return WheelScript.API_SetFrontTrackWidth(vehicle, value); + } + + public bool SetRearTrackWidth(int vehicle, float value) + { + if (WheelScript == null) + return false; + + return WheelScript.API_SetRearTrackWidth(vehicle, value); + } + + public float[] GetFrontCamber(int vehicle) + { + if (WheelScript == null) + return new float[] { }; + + if (WheelScript.API_GetFrontCamber(vehicle, out float value)) + return new float[] { value }; + + return new float[] { }; + } + + public float[] GetRearCamber(int vehicle) + { + if (WheelScript == null) + return new float[] { }; + + if (WheelScript.API_GetRearCamber(vehicle, out float value)) + return new float[] { value }; + + return new float[] { }; + } + + public float[] GetFrontTrackWidth(int vehicle) + { + if (WheelScript == null) + return new float[] { }; + + if (WheelScript.API_GetFrontTrackWidth(vehicle, out float value)) + return new float[] { value }; + + return new float[] { }; + } + + public float[] GetRearTrackWidth(int vehicle) + { + if (WheelScript == null) + return new float[] { }; + + if (WheelScript.API_GetRearTrackWidth(vehicle, out float value)) + return new float[] { value }; + + return new float[] { }; + } + + public bool SaveClientPreset(string id, int vehicle) + { + if (ClientPresetsScript == null) + return false; + + return ClientPresetsScript.API_SavePreset(id, vehicle); + } + + public bool LoadClientPreset(string id, int vehicle) + { + if (ClientPresetsScript == null) + return false; + + return ClientPresetsScript.API_LoadPreset(id, vehicle); + } + + public bool DeleteClientPreset(string id) + { + if (ClientPresetsScript == null) + return false; + + return ClientPresetsScript.API_DeletePreset(id); + } + + public string[] GetClientPresetList() + { + if (ClientPresetsScript == null) + return new string[] { }; + + return ClientPresetsScript.API_GetClientPresetList().ToArray(); + } + } +} diff --git a/VStancer.Client/Scripts/WheelModScript.cs b/VStancer.Client/Scripts/WheelModScript.cs new file mode 100644 index 0000000..64faa9b --- /dev/null +++ b/VStancer.Client/Scripts/WheelModScript.cs @@ -0,0 +1,770 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using VStancer.Client.Data; +using VStancer.Client.Preset; +using VStancer.Client.UI; + +using CitizenFX.Core; +using static CitizenFX.Core.Native.API; + +namespace VStancer.Client.Scripts +{ + internal class WheelModScript : BaseScript + { + private readonly MainScript _mainScript; + + private long _lastTime; + private int _playerVehicleHandle; + private int _vehicleWheelMod; + + internal int VehicleWheelMod + { + get => _vehicleWheelMod; + set + { + if (Equals(_vehicleWheelMod, value)) + return; + + _vehicleWheelMod = value; + VehicleWheelModChanged(); + } + } + + private WheelModData _wheelModData; + internal WheelModData WheelModData + { + get => _wheelModData; + set + { + if (Equals(_wheelModData, value)) + return; + + _wheelModData = value; + WheelModDataChanged?.Invoke(this, EventArgs.Empty); + } + } + + internal VStancerConfig Config => _mainScript.Config; + internal WheelModMenu Menu { get; private set; } + public bool DataIsValid => _playerVehicleHandle != -1 && WheelModData != null && VehicleWheelMod != -1; + + internal const string ExtraResetID = "vstancer_extra_reset"; + + internal const string WheelWidthID = "vstancer_extra_width"; + internal const string WheelSizeID = "vstancer_extra_size"; + internal const string DefaultWidthID = "vstancer_extra_width_def"; + internal const string DefaultSizeID = "vstancer_extra_size_def"; + + internal const string FrontTireColliderWidthID = "vstancer_extra_tirecollider_width_f"; + internal const string FrontTireColliderSizeID = "vstancer_extra_tirecollider_size_f"; + internal const string FrontRimColliderSizeID = "vstancer_extra_rimcollider_size_f"; + + internal const string RearTireColliderWidthID = "vstancer_extra_tirecollider_width_r"; + internal const string RearTireColliderSizeID = "vstancer_extra_tirecollider_size_r"; + internal const string RearRimColliderSizeID = "vstancer_extra_rimcollider_size_r"; + + internal const string DefaultFrontTireColliderWidthID = "vstancer_extra_tirecollider_width_f_def"; + internal const string DefaultFrontTireColliderSizeID = "vstancer_extra_tirecollider_size_f_def"; + internal const string DefaultFrontRimColliderSizeID = "vstancer_extra_rimcollider_size_f_def"; + + internal const string DefaultRearTireColliderWidthID = "vstancer_extra_tirecollider_width_r_def"; + internal const string DefaultRearTireColliderSizeID = "vstancer_extra_tirecollider_size_r_def"; + internal const string DefaultRearRimColliderSizeID = "vstancer_extra_rimcollider_size_r_def"; + + public event EventHandler WheelModDataChanged; + + internal WheelModScript(MainScript mainScript) + { + _mainScript = mainScript; + + RegisterDecorators(); + + _lastTime = GetGameTimer(); + + _playerVehicleHandle = -1; + VehicleWheelMod = -1; + WheelModData = null; + + if (!_mainScript.Config.DisableMenu) + { + Menu = new WheelModMenu(this); + Menu.FloatPropertyChangedEvent += OnMenuFloatPropertyChanged; + Menu.ResetPropertiesEvent += (sender, id) => OnMenuCommandInvoked(id); + } + + // TODO: Consider using a task as workaround for values resetting when any tuning part is installed + //Tick += TickTask; + Tick += TimedTask; + + mainScript.PlayerVehicleHandleChanged += (sender, handle) => PlayerVehicleChanged(handle); + + WheelModDataChanged += (sender, args) => OnWheelModDataChanged(); + + PlayerVehicleChanged(_mainScript.PlayerVehicleHandle); + } + + //private async Task TickTask() + //{ + // await Task.FromResult(0); + // + // if (!DataIsValid) + // return; + // + // UpdateVehicleUsingData(_playerVehicleHandle, WheelModData); + //} + + private async Task GetVehicleWheelModTask() + { + if (_playerVehicleHandle == -1) + return; + + VehicleWheelMod = GetVehicleMod(_playerVehicleHandle, 23); + + await Task.FromResult(0); + } + + private void OnWheelModDataChanged() + { + if (WheelModData != null) + WheelModData.PropertyChanged += OnWheelModDataPropertyChanged; + } + + private async void VehicleWheelModChanged() + { + if (_playerVehicleHandle == -1) + return; + + if (WheelModData != null) + WheelModData.PropertyChanged -= OnWheelModDataPropertyChanged; + + if (VehicleWheelMod == -1) + { + WheelModData = null; + RemoveDecoratorsFromVehicle(_playerVehicleHandle); + return; + } + + WheelModData = await GetWheelModDataFromEntity(_playerVehicleHandle); + } + + private void PlayerVehicleChanged(int vehicle) + { + if (vehicle == _playerVehicleHandle) + return; + + _playerVehicleHandle = vehicle; + + if (WheelModData != null) + WheelModData.PropertyChanged -= OnWheelModDataPropertyChanged; + + if (_playerVehicleHandle == -1) + { + VehicleWheelMod = -1; + WheelModData = null; + Tick -= GetVehicleWheelModTask; + return; + } + + Tick += GetVehicleWheelModTask; + } + + private async Task TimedTask() + { + long currentTime = (GetGameTimer() - _lastTime); + + if (currentTime > _mainScript.Config.Timer) + { + UpdateWorldVehiclesUsingDecorators(); + + _lastTime = GetGameTimer(); + } + + await Task.FromResult(0); + } + + private void UpdateVehicleUsingData(int vehicle, WheelModData data) + { + if (!DoesEntityExist(vehicle) || data == null) + return; + + //if (!MathUtil.WithinEpsilon(GetVehicleWheelWidth(vehicle), data.WheelWidth, VStancerUtilities.Epsilon)) + SetVehicleWheelWidth(vehicle, data.WheelWidth); + + //if (!MathUtil.WithinEpsilon(GetVehicleWheelSize(vehicle), data.WheelSize, VStancerUtilities.Epsilon)) + SetVehicleWheelSize(vehicle, data.WheelSize); + + for (int i = 0; i < data.FrontWheelsCount; i++) + { + SetVehicleWheelTireColliderWidth(vehicle, i, data.FrontTireColliderWidth); + SetVehicleWheelTireColliderSize(vehicle, i, data.FrontTireColliderSize); + SetVehicleWheelRimColliderSize(vehicle, i, data.FrontRimColliderSize); + } + + for (int i = data.FrontWheelsCount; i < data.WheelsCount; i++) + { + SetVehicleWheelTireColliderWidth(vehicle, i, data.RearTireColliderWidth); + SetVehicleWheelTireColliderSize(vehicle, i, data.RearTireColliderSize); + SetVehicleWheelRimColliderSize(vehicle, i, data.RearRimColliderSize); + } + } + + private void UpdateWorldVehiclesUsingDecorators() + { + foreach (int entity in _mainScript.GetCloseVehicleHandles()) + { + if (entity == _playerVehicleHandle) + continue; + + UpdateVehicleUsingDecorators(entity); + } + } + + private async Task GetWheelModDataFromEntity(int vehicle) + { + if (!DoesEntityExist(vehicle)) + return null; + + int wheelsCount = GetVehicleNumberOfWheels(vehicle); + int frontCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); + + float wheelWidth_def; + float wheelSize_def; + + // wait for data to actually update, required if wheel mod is changed too fast + // and data read by GetVehicleWheelWidth and GetVehicleWheelSize didn't update yet resulting in 0 + if (DecorExistOn(vehicle, DefaultWidthID)) + { + wheelWidth_def = DecorGetFloat(vehicle, DefaultWidthID); + } + else + { + do + { + wheelWidth_def = GetVehicleWheelWidth(vehicle); + await Delay(100); + } while (MathUtil.IsZero(wheelWidth_def) || MathUtil.IsOne(wheelWidth_def)); + } + + if (DecorExistOn(vehicle, DefaultSizeID)) + { + wheelSize_def = DecorGetFloat(vehicle, DefaultSizeID); + } + else + { + do + { + wheelSize_def = GetVehicleWheelSize(vehicle); + await Delay(100); + } while (MathUtil.IsZero(wheelSize_def) || MathUtil.IsOne(wheelSize_def)); + } + + float frontTireColliderWidth_def = DecorExistOn(vehicle, DefaultFrontTireColliderWidthID) ? DecorGetFloat(vehicle, DefaultFrontTireColliderWidthID) : GetVehicleWheelTireColliderWidth(vehicle, 0); + float frontTireColliderSize_def = DecorExistOn(vehicle, DefaultFrontTireColliderSizeID) ? DecorGetFloat(vehicle, DefaultFrontTireColliderSizeID) : GetVehicleWheelTireColliderSize(vehicle, 0); + float frontRimColliderSize_def = DecorExistOn(vehicle, DefaultFrontRimColliderSizeID) ? DecorGetFloat(vehicle, DefaultFrontRimColliderSizeID) : GetVehicleWheelRimColliderSize(vehicle, 0); + + float rearTireColliderWidth_def = DecorExistOn(vehicle, DefaultRearTireColliderWidthID) ? DecorGetFloat(vehicle, DefaultRearTireColliderWidthID) : GetVehicleWheelTireColliderWidth(vehicle, frontCount); + float rearTireColliderSize_def = DecorExistOn(vehicle, DefaultRearTireColliderSizeID) ? DecorGetFloat(vehicle, DefaultRearTireColliderSizeID) : GetVehicleWheelTireColliderSize(vehicle, frontCount); + float rearRimColliderSize_def = DecorExistOn(vehicle, DefaultRearRimColliderSizeID) ? DecorGetFloat(vehicle, DefaultRearRimColliderSizeID) : GetVehicleWheelRimColliderSize(vehicle, frontCount); + + // Create the preset with the default values + return new WheelModData(wheelsCount, wheelWidth_def, wheelSize_def, + frontTireColliderWidth_def, frontTireColliderSize_def, frontRimColliderSize_def, + rearTireColliderWidth_def, rearTireColliderSize_def, rearRimColliderSize_def) + { + // Assign the current values + WheelWidth = DecorExistOn(vehicle, WheelWidthID) ? DecorGetFloat(vehicle, WheelWidthID) : wheelWidth_def, + WheelSize = DecorExistOn(vehicle, WheelSizeID) ? DecorGetFloat(vehicle, WheelSizeID) : wheelSize_def, + + FrontTireColliderWidth = DecorExistOn(vehicle, FrontTireColliderWidthID) ? DecorGetFloat(vehicle, FrontTireColliderWidthID) : frontTireColliderWidth_def, + FrontTireColliderSize = DecorExistOn(vehicle, FrontTireColliderSizeID) ? DecorGetFloat(vehicle, FrontTireColliderSizeID) : frontTireColliderSize_def, + FrontRimColliderSize = DecorExistOn(vehicle, FrontRimColliderSizeID) ? DecorGetFloat(vehicle, FrontRimColliderSizeID) : frontRimColliderSize_def, + + RearTireColliderWidth = DecorExistOn(vehicle, RearTireColliderWidthID) ? DecorGetFloat(vehicle, RearTireColliderWidthID) : rearTireColliderWidth_def, + RearTireColliderSize = DecorExistOn(vehicle, RearTireColliderSizeID) ? DecorGetFloat(vehicle, RearTireColliderSizeID) : rearTireColliderSize_def, + RearRimColliderSize = DecorExistOn(vehicle, RearRimColliderSizeID) ? DecorGetFloat(vehicle, RearRimColliderSizeID) : rearRimColliderSize_def + }; + } + + private void SetWheelWidthUsingData(int vehicle, WheelModData data) + { + float value = data.WheelWidth; + + bool result = SetVehicleWheelWidth(vehicle, value); + if (result) + { + float defValue = data.DefaultWheelWidth; + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultWidthID, defValue, value); + VStancerUtilities.UpdateFloatDecorator(vehicle, WheelWidthID, value, defValue); + } + } + + private void SetWheelSizeUsingData(int vehicle, WheelModData data) + { + float value = data.WheelSize; + + bool result = SetVehicleWheelSize(vehicle, value); + if (result) + { + float defValue = data.DefaultWheelSize; + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultSizeID, defValue, value); + VStancerUtilities.UpdateFloatDecorator(vehicle, WheelSizeID, value, defValue); + } + } + + private void SetFrontTireColliderWidthUsingData(int vehicle, WheelModData data) + { + float value = data.FrontTireColliderWidth; + float defValue = data.DefaultFrontTireColliderWidth; + + for (int i = 0; i < WheelModData.FrontWheelsCount; i++) + SetVehicleWheelTireColliderWidth(vehicle, i, value); + + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontTireColliderWidthID, defValue, value); + VStancerUtilities.UpdateFloatDecorator(vehicle, FrontTireColliderWidthID, value, defValue); + } + + private void SetFrontTireColliderSizeUsingData(int vehicle, WheelModData data) + { + float value = data.FrontTireColliderSize; + float defValue = data.DefaultFrontTireColliderSize; + + for (int i = 0; i < WheelModData.FrontWheelsCount; i++) + SetVehicleWheelTireColliderSize(vehicle, i, value); + + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontTireColliderSizeID, defValue, value); + VStancerUtilities.UpdateFloatDecorator(vehicle, FrontTireColliderSizeID, value, defValue); + } + + private void SetFrontRimColliderSizeUsingData(int vehicle, WheelModData data) + { + float value = data.FrontRimColliderSize; + float defValue = data.DefaultFrontRimColliderSize; + + for (int i = 0; i < WheelModData.FrontWheelsCount; i++) + SetVehicleWheelRimColliderSize(vehicle, i, value); + + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontRimColliderSizeID, defValue, value); + VStancerUtilities.UpdateFloatDecorator(vehicle, FrontRimColliderSizeID, value, defValue); + } + + private void SetRearTireColliderWidthUsingData(int vehicle, WheelModData data) + { + float value = data.RearTireColliderWidth; + float defValue = data.DefaultRearTireColliderWidth; + + for (int i = WheelModData.FrontWheelsCount; i < WheelModData.WheelsCount; i++) + SetVehicleWheelTireColliderWidth(vehicle, i, value); + + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearTireColliderWidthID, defValue, value); + VStancerUtilities.UpdateFloatDecorator(vehicle, RearTireColliderWidthID, value, defValue); + } + + private void SetRearTireColliderSizeUsingData(int vehicle, WheelModData data) + { + float value = data.RearTireColliderSize; + float defValue = data.DefaultRearTireColliderSize; + + for (int i = WheelModData.FrontWheelsCount; i < WheelModData.WheelsCount; i++) + SetVehicleWheelTireColliderSize(vehicle, i, value); + + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearTireColliderSizeID, defValue, value); + VStancerUtilities.UpdateFloatDecorator(vehicle, RearTireColliderSizeID, value, defValue); + } + + private void SetRearRimColliderSizeUsingData(int vehicle, WheelModData data) + { + float value = data.RearRimColliderSize; + float defValue = data.DefaultRearRimColliderSize; + + for (int i = WheelModData.FrontWheelsCount; i < WheelModData.WheelsCount; i++) + SetVehicleWheelRimColliderSize(vehicle, i, value); + + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearRimColliderSizeID, defValue, value); + VStancerUtilities.UpdateFloatDecorator(vehicle, RearRimColliderSizeID, value, defValue); + } + + private void OnWheelModDataPropertyChanged(string propertyName, float value) + { + switch (propertyName) + { + case nameof(WheelModData.WheelWidth): + SetWheelWidthUsingData(_playerVehicleHandle, WheelModData); + break; + + case nameof(WheelModData.WheelSize): + SetWheelSizeUsingData(_playerVehicleHandle, WheelModData); + break; + + case nameof(WheelModData.FrontTireColliderWidth): + SetFrontTireColliderWidthUsingData(_playerVehicleHandle, WheelModData); + break; + case nameof(WheelModData.FrontTireColliderSize): + SetFrontTireColliderSizeUsingData(_playerVehicleHandle, WheelModData); + break; + case nameof(WheelModData.FrontRimColliderSize): + SetFrontRimColliderSizeUsingData(_playerVehicleHandle, WheelModData); + break; + + case nameof(WheelModData.RearTireColliderWidth): + SetRearTireColliderWidthUsingData(_playerVehicleHandle, WheelModData); + break; + case nameof(WheelModData.RearTireColliderSize): + SetRearTireColliderSizeUsingData(_playerVehicleHandle, WheelModData); + break; + case nameof(WheelModData.RearRimColliderSize): + SetRearRimColliderSizeUsingData(_playerVehicleHandle, WheelModData); + break; + + case nameof(WheelModData.Reset): + + // TODO: Avoid updating decorators if we have to remove them anyway + SetWheelWidthUsingData(_playerVehicleHandle, WheelModData); + SetWheelSizeUsingData(_playerVehicleHandle, WheelModData); + SetFrontTireColliderWidthUsingData(_playerVehicleHandle, WheelModData); + SetFrontTireColliderSizeUsingData(_playerVehicleHandle, WheelModData); + SetFrontRimColliderSizeUsingData(_playerVehicleHandle, WheelModData); + SetRearTireColliderWidthUsingData(_playerVehicleHandle, WheelModData); + SetRearTireColliderSizeUsingData(_playerVehicleHandle, WheelModData); + SetRearRimColliderSizeUsingData(_playerVehicleHandle, WheelModData); + + RemoveDecoratorsFromVehicle(_playerVehicleHandle); + + WheelModDataChanged?.Invoke(this, EventArgs.Empty); + break; + + default: + break; + } + } + + private void UpdateVehicleUsingDecorators(int vehicle) + { + int wheelsCount = GetVehicleNumberOfWheels(vehicle); + int frontWheelsCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); + + if (DecorExistOn(vehicle, WheelSizeID)) + SetVehicleWheelSize(vehicle, DecorGetFloat(vehicle, WheelSizeID)); + + if (DecorExistOn(vehicle, WheelWidthID)) + SetVehicleWheelWidth(vehicle, DecorGetFloat(vehicle, WheelWidthID)); + + if (DecorExistOn(vehicle, FrontTireColliderWidthID)) + { + float value = DecorGetFloat(vehicle, FrontTireColliderWidthID); + for (int i = 0; i < frontWheelsCount; i++) + SetVehicleWheelTireColliderWidth(vehicle, i, value); + } + + if (DecorExistOn(vehicle, FrontTireColliderSizeID)) + { + float value = DecorGetFloat(vehicle, FrontTireColliderSizeID); + for (int i = 0; i < frontWheelsCount; i++) + SetVehicleWheelTireColliderSize(vehicle, i, value); + } + + if (DecorExistOn(vehicle, FrontRimColliderSizeID)) + { + float value = DecorGetFloat(vehicle, FrontRimColliderSizeID); + for (int i = 0; i < frontWheelsCount; i++) + SetVehicleWheelRimColliderSize(vehicle, i, value); + } + + if (DecorExistOn(vehicle, RearTireColliderWidthID)) + { + float value = DecorGetFloat(vehicle, RearTireColliderWidthID); + for (int i = frontWheelsCount; i < wheelsCount; i++) + SetVehicleWheelTireColliderWidth(vehicle, i, value); + } + + if (DecorExistOn(vehicle, RearTireColliderSizeID)) + { + float value = DecorGetFloat(vehicle, RearTireColliderSizeID); + for (int i = frontWheelsCount; i < wheelsCount; i++) + SetVehicleWheelTireColliderSize(vehicle, i, value); + } + + if (DecorExistOn(vehicle, RearRimColliderSizeID)) + { + float value = DecorGetFloat(vehicle, RearRimColliderSizeID); + for (int i = frontWheelsCount; i < wheelsCount; i++) + SetVehicleWheelRimColliderSize(vehicle, i, value); + } + } + + private void RegisterDecorators() + { + DecorRegister(WheelWidthID, 1); + DecorRegister(DefaultWidthID, 1); + + DecorRegister(WheelSizeID, 1); + DecorRegister(DefaultSizeID, 1); + + DecorRegister(FrontTireColliderWidthID, 1); + DecorRegister(FrontTireColliderSizeID, 1); + DecorRegister(FrontRimColliderSizeID, 1); + DecorRegister(DefaultFrontTireColliderWidthID, 1); + DecorRegister(DefaultFrontTireColliderSizeID, 1); + DecorRegister(DefaultFrontRimColliderSizeID, 1); + + DecorRegister(RearTireColliderWidthID, 1); + DecorRegister(RearTireColliderSizeID, 1); + DecorRegister(RearRimColliderSizeID, 1); + DecorRegister(DefaultRearTireColliderWidthID, 1); + DecorRegister(DefaultRearTireColliderSizeID, 1); + DecorRegister(DefaultRearRimColliderSizeID, 1); + } + + private void RemoveDecoratorsFromVehicle(int vehicle) + { + if (DecorExistOn(vehicle, WheelSizeID)) + DecorRemove(vehicle, WheelSizeID); + + if (DecorExistOn(vehicle, WheelWidthID)) + DecorRemove(vehicle, WheelWidthID); + + if (DecorExistOn(vehicle, DefaultSizeID)) + DecorRemove(vehicle, DefaultSizeID); + + if (DecorExistOn(vehicle, DefaultWidthID)) + DecorRemove(vehicle, DefaultWidthID); + + if (DecorExistOn(vehicle, FrontTireColliderWidthID)) + DecorRemove(vehicle, FrontTireColliderWidthID); + + if (DecorExistOn(vehicle, FrontTireColliderSizeID)) + DecorRemove(vehicle, FrontTireColliderSizeID); + + if (DecorExistOn(vehicle, FrontRimColliderSizeID)) + DecorRemove(vehicle, FrontRimColliderSizeID); + + if (DecorExistOn(vehicle, DefaultFrontTireColliderWidthID)) + DecorRemove(vehicle, DefaultFrontTireColliderWidthID); + + if (DecorExistOn(vehicle, DefaultFrontTireColliderSizeID)) + DecorRemove(vehicle, DefaultFrontTireColliderSizeID); + + if (DecorExistOn(vehicle, DefaultFrontRimColliderSizeID)) + DecorRemove(vehicle, DefaultFrontRimColliderSizeID); + + if (DecorExistOn(vehicle, RearTireColliderWidthID)) + DecorRemove(vehicle, RearTireColliderWidthID); + + if (DecorExistOn(vehicle, RearTireColliderSizeID)) + DecorRemove(vehicle, RearTireColliderSizeID); + + if (DecorExistOn(vehicle, RearRimColliderSizeID)) + DecorRemove(vehicle, RearRimColliderSizeID); + + if (DecorExistOn(vehicle, DefaultRearTireColliderWidthID)) + DecorRemove(vehicle, DefaultRearTireColliderWidthID); + + if (DecorExistOn(vehicle, DefaultRearTireColliderSizeID)) + DecorRemove(vehicle, DefaultRearTireColliderSizeID); + + if (DecorExistOn(vehicle, DefaultRearRimColliderSizeID)) + DecorRemove(vehicle, DefaultRearRimColliderSizeID); + } + + private void OnMenuCommandInvoked(string commandID) + { + if (!DataIsValid) + return; + + switch (commandID) + { + case ExtraResetID: + WheelModData.Reset(); + break; + } + } + + private void OnMenuFloatPropertyChanged(string id, float value) + { + if (!DataIsValid) + return; + + switch (id) + { + case WheelSizeID: + WheelModData.WheelSize = value; + WheelModData.FrontTireColliderSize = value / WheelModData.DefaultFrontTireColliderSizeRatio; + WheelModData.FrontRimColliderSize = value / WheelModData.DefaultFrontRimColliderSizeRatio; + WheelModData.RearTireColliderSize = value / WheelModData.DefaultRearTireColliderSizeRatio; + WheelModData.RearRimColliderSize = value / WheelModData.DefaultRearRimColliderSizeRatio; + break; + case WheelWidthID: + WheelModData.WheelWidth = value; + WheelModData.FrontTireColliderWidth = value / WheelModData.DefaultFrontTireColliderWidthRatio; + WheelModData.RearTireColliderWidth = value / WheelModData.DefaultRearTireColliderWidthRatio; + break; + + // Update colliders with visual but keep visual/collider ratio constant as with default wheels + // This ratio is usually 50% for for vanilla wheel mod + + /* + case FrontTireColliderWidthID: + WheelModData.FrontTireColliderWidth = value; + break; + case FrontTireColliderSizeID: + WheelModData.FrontTireColliderSize = value; + break; + case FrontRimColliderSizeID: + WheelModData.FrontRimColliderSize = value; + break; + + case RearTireColliderWidthID: + WheelModData.RearTireColliderWidth = value; + break; + case RearTireColliderSizeID: + WheelModData.RearTireColliderSize = value; + break; + case RearRimColliderSizeID: + WheelModData.RearRimColliderSize = value; + break; + */ + } + } + + private bool EntityHasDecorators(int entity) + { + return ( + DecorExistOn(entity, WheelSizeID) || + DecorExistOn(entity, WheelWidthID) || + DecorExistOn(entity, DefaultSizeID) || + DecorExistOn(entity, DefaultWidthID) || + DecorExistOn(entity, FrontTireColliderWidthID) || + DecorExistOn(entity, FrontTireColliderSizeID) || + DecorExistOn(entity, FrontRimColliderSizeID) || + DecorExistOn(entity, DefaultFrontTireColliderWidthID) || + DecorExistOn(entity, DefaultFrontTireColliderSizeID) || + DecorExistOn(entity, DefaultFrontRimColliderSizeID) || + DecorExistOn(entity, RearTireColliderWidthID) || + DecorExistOn(entity, RearTireColliderSizeID) || + DecorExistOn(entity, RearRimColliderSizeID) || + DecorExistOn(entity, DefaultRearTireColliderWidthID) || + DecorExistOn(entity, DefaultRearTireColliderSizeID) || + DecorExistOn(entity, DefaultRearRimColliderSizeID) + ); + } + + internal void PrintVehiclesWithDecorators(IEnumerable vehiclesList) + { + IEnumerable entities = vehiclesList.Where(entity => EntityHasDecorators(entity)); + + Debug.WriteLine($"{nameof(WheelModScript)}: Vehicles with decorators: {entities.Count()}"); + + foreach (int item in entities) + Debug.WriteLine($"Vehicle: {item}"); + } + + internal void PrintDecoratorsInfo(int vehicle) + { + if (!DoesEntityExist(vehicle)) + { + Debug.WriteLine($"{nameof(WheelModScript)}: Can't find vehicle with handle {vehicle}"); + return; + } + + int wheelsCount = GetVehicleNumberOfWheels(vehicle); + int netID = NetworkGetNetworkIdFromEntity(vehicle); + StringBuilder s = new StringBuilder(); + s.AppendLine($"{nameof(WheelModScript)}: Vehicle:{vehicle} netID:{netID} wheelsCount:{wheelsCount}"); + + if (DecorExistOn(vehicle, WheelSizeID)) + + s.AppendLine($"{WheelSizeID}: {DecorGetFloat(vehicle, WheelSizeID)}"); + + if (DecorExistOn(vehicle, DefaultSizeID)) + s.AppendLine($"{DefaultSizeID}: {DecorGetFloat(vehicle, DefaultSizeID)}"); + + if (DecorExistOn(vehicle, WheelWidthID)) + s.AppendLine($"{WheelWidthID}: {DecorGetFloat(vehicle, WheelWidthID)}"); + + if (DecorExistOn(vehicle, DefaultWidthID)) + s.AppendLine($"{DefaultWidthID}: {DecorGetFloat(vehicle, DefaultWidthID)}"); + + if (DecorExistOn(vehicle, FrontTireColliderWidthID)) + s.AppendLine($"{FrontTireColliderWidthID}: {DecorGetFloat(vehicle, FrontTireColliderWidthID)}"); + + if (DecorExistOn(vehicle, DefaultFrontTireColliderWidthID)) + s.AppendLine($"{DefaultFrontTireColliderWidthID}: {DecorGetFloat(vehicle, DefaultFrontTireColliderWidthID)}"); + + if (DecorExistOn(vehicle, RearTireColliderWidthID)) + s.AppendLine($"{RearTireColliderWidthID}: {DecorGetFloat(vehicle, RearTireColliderWidthID)}"); + + if (DecorExistOn(vehicle, DefaultRearTireColliderWidthID)) + s.AppendLine($"{DefaultRearTireColliderWidthID}: {DecorGetFloat(vehicle, DefaultRearTireColliderWidthID)}"); + + if (DecorExistOn(vehicle, FrontTireColliderSizeID)) + s.AppendLine($"{FrontTireColliderSizeID}: {DecorGetFloat(vehicle, FrontTireColliderSizeID)}"); + + if (DecorExistOn(vehicle, DefaultFrontTireColliderSizeID)) + s.AppendLine($"{DefaultFrontTireColliderSizeID}: {DecorGetFloat(vehicle, DefaultFrontTireColliderSizeID)}"); + + if (DecorExistOn(vehicle, RearTireColliderSizeID)) + s.AppendLine($"{RearTireColliderSizeID}: {DecorGetFloat(vehicle, RearTireColliderSizeID)}"); + + if (DecorExistOn(vehicle, DefaultRearTireColliderSizeID)) + s.AppendLine($"{DefaultRearTireColliderSizeID}: {DecorGetFloat(vehicle, DefaultRearTireColliderSizeID)}"); + + if (DecorExistOn(vehicle, FrontRimColliderSizeID)) + s.AppendLine($"{FrontRimColliderSizeID}: {DecorGetFloat(vehicle, FrontRimColliderSizeID)}"); + + if (DecorExistOn(vehicle, DefaultFrontRimColliderSizeID)) + s.AppendLine($"{DefaultFrontRimColliderSizeID}: {DecorGetFloat(vehicle, DefaultFrontRimColliderSizeID)}"); + + if (DecorExistOn(vehicle, RearRimColliderSizeID)) + s.AppendLine($"{RearRimColliderSizeID}: {DecorGetFloat(vehicle, RearRimColliderSizeID)}"); + + if (DecorExistOn(vehicle, DefaultRearRimColliderSizeID)) + s.AppendLine($"{DefaultRearRimColliderSizeID}: {DecorGetFloat(vehicle, DefaultRearRimColliderSizeID)}"); + + Debug.WriteLine(s.ToString()); + } + + internal WheelModPreset GetWheelModPreset() + { + if (!DataIsValid) + return null; + + // Only required to avoid saving this preset locally when not required + if (!WheelModData.IsEdited) + return null; + + return new WheelModPreset(WheelModData); + } + + internal async Task SetWheelModPreset(WheelModPreset preset) + { + if (!DataIsValid || preset == null) + return; + + // TODO: Check if values are within limits + + WheelModData.WheelSize = preset.WheelSize; + WheelModData.WheelWidth = preset.WheelWidth; + + WheelModData.FrontTireColliderWidth = preset.WheelWidth / WheelModData.DefaultFrontTireColliderWidthRatio; + WheelModData.FrontTireColliderSize = preset.WheelSize / WheelModData.DefaultFrontTireColliderSizeRatio; + WheelModData.FrontRimColliderSize = preset.WheelSize / WheelModData.DefaultFrontRimColliderSizeRatio; + WheelModData.RearTireColliderWidth = preset.WheelWidth / WheelModData.DefaultRearTireColliderWidthRatio; + WheelModData.RearTireColliderSize = preset.WheelSize / WheelModData.DefaultRearTireColliderSizeRatio; + WheelModData.RearRimColliderSize = preset.WheelSize / WheelModData.DefaultRearRimColliderSizeRatio; + + //WheelModData.FrontTireColliderWidth = preset.FrontTireColliderWidth; + //WheelModData.FrontTireColliderSize = preset.FrontTireColliderSize; + //WheelModData.FrontRimColliderSize = preset.FrontRimColliderSize; + //WheelModData.RearTireColliderWidth = preset.RearTireColliderWidth; + //WheelModData.RearTireColliderSize = preset.RearTireColliderSize; + //WheelModData.RearRimColliderSize = preset.RearRimColliderSize; + + Debug.WriteLine($"{nameof(WheelModScript)}: wheel mod preset applied"); + await Delay(200); + WheelModDataChanged?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/VStancer.Client/Scripts/WheelScript.cs b/VStancer.Client/Scripts/WheelScript.cs new file mode 100644 index 0000000..03ca5e4 --- /dev/null +++ b/VStancer.Client/Scripts/WheelScript.cs @@ -0,0 +1,749 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using VStancer.Client.Data; +using VStancer.Client.Preset; +using VStancer.Client.UI; + +using CitizenFX.Core; +using static CitizenFX.Core.Native.API; + +namespace VStancer.Client.Scripts +{ + internal class WheelScript : BaseScript + { + private readonly MainScript _mainScript; + + private long _lastTime; + private int _playerVehicleHandle; + + private WheelData _wheelData; + internal WheelData WheelData + { + get => _wheelData; + set + { + if (Equals(_wheelData, value)) + return; + + _wheelData = value; + WheelDataChanged?.Invoke(this, EventArgs.Empty); + } + } + + internal VStancerConfig Config => _mainScript.Config; + internal WheelMenu Menu { get; private set; } + + internal bool DataIsValid => _playerVehicleHandle != -1 && WheelData != null; + + internal const string FrontTrackWidthID = "vstancer_trackwidth_f"; + internal const string RearTrackWidthID = "vstancer_trackwidth_r"; + internal const string FrontCamberID = "vstancer_camber_f"; + internal const string RearCamberID = "vstancer_camber_r"; + + internal const string DefaultFrontTrackWidthID = "vstancer_trackwidth_f_def"; + internal const string DefaultRearTrackWidthID = "vstancer_trackwidth_r_def"; + internal const string DefaultFrontCamberID = "vstancer_camber_f_def"; + internal const string DefaultRearCamberID = "vstancer_camber_r_def"; + + internal const string ResetID = "vstancer_reset"; + + internal event EventHandler WheelDataChanged; + + internal WheelScript(MainScript mainScript) + { + _mainScript = mainScript; + + _lastTime = GetGameTimer(); + _playerVehicleHandle = -1; + + RegisterDecorators(); + + if (!_mainScript.Config.DisableMenu) + { + Menu = new WheelMenu(this); + Menu.FloatPropertyChangedEvent += OnMenuFloatPropertyChanged; + Menu.ResetPropertiesEvent += (sender, id) => OnMenuCommandInvoked(id); + } + + Tick += UpdateWorldVehiclesTask; + //Tick += TimedTask; + Tick += UpdatePlayerVehicleTask; + + mainScript.PlayerVehicleHandleChanged += (sender, handle) => PlayerVehicleChanged(handle); + PlayerVehicleChanged(_mainScript.PlayerVehicleHandle); + + WheelDataChanged += (sender, args) => OnWheelDataChanged(); + } + + private void OnWheelDataChanged() + { + if (WheelData != null) + WheelData.PropertyChanged += OnWheelDataPropertyChanged; + } + + private void PlayerVehicleChanged(int vehicle) + { + if (vehicle == _playerVehicleHandle) + return; + + _playerVehicleHandle = vehicle; + + if (WheelData != null) + WheelData.PropertyChanged -= OnWheelDataPropertyChanged; + + if (_playerVehicleHandle == -1) + { + WheelData = null; + Tick -= UpdatePlayerVehicleTask; + return; + } + + WheelData = GetWheelDataFromEntity(vehicle); + + Tick += UpdatePlayerVehicleTask; + } + + private async Task UpdatePlayerVehicleTask() + { + await Task.FromResult(0); + + // Check if current vehicle needs to be refreshed + if (DataIsValid && WheelData.IsEdited) + UpdateVehicleUsingWheelData(_playerVehicleHandle, WheelData); + } + + private async Task UpdateWorldVehiclesTask() + { + await Task.FromResult(0); + + foreach (int entity in _mainScript.GetCloseVehicleHandles()) + { + if (entity == _playerVehicleHandle) + continue; + + UpdateVehicleUsingDecorators(entity); + } + } + + private async Task TimedTask() + { + long currentTime = (GetGameTimer() - _lastTime); + + // Check if decorators needs to be updated + if (currentTime > _mainScript.Config.Timer) + { + //if (DataIsValid) + // UpdateVehicleDecorators(_playerVehicleHandle, WheelData); + + _lastTime = GetGameTimer(); + } + + await Task.FromResult(0); + } + + private async void OnWheelDataPropertyChanged(string propertyName, float value) + { + if (!DataIsValid) + return; + + switch(propertyName) + { + // If false then this has been invoked by after a reset + case nameof(WheelData.Reset): + RemoveDecoratorsFromVehicle(_playerVehicleHandle); + UpdateVehicleUsingWheelData(_playerVehicleHandle, WheelData); + await Delay(50); + WheelDataChanged?.Invoke(this, EventArgs.Empty); + break; + + case nameof(WheelData.FrontCamber): + SetFrontCamberUsingData(_playerVehicleHandle, WheelData); + break; + case nameof(WheelData.RearCamber): + SetRearCamberUsingData(_playerVehicleHandle, WheelData); + break; + case nameof(WheelData.FrontTrackWidth): + SetFrontTrackWidthUsingData(_playerVehicleHandle, WheelData); + break; + case nameof(WheelData.RearTrackWidth): + SetRearTrackWidthUsingData(_playerVehicleHandle, WheelData); + break; + } + } + + private void SetFrontCamberUsingData(int vehicle, WheelData data) + { + if (!DoesEntityExist(vehicle) || data == null) + return; + + int frontWheelsCount = data.FrontWheelsCount; + WheelDataNode[] nodes = data.GetNodes(); + + for (int i = 0; i < frontWheelsCount; i++) + SetVehicleWheelYRotation(vehicle, i, nodes[i].RotationY); + + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontCamberID, data.DefaultFrontCamber, data.FrontCamber); + VStancerUtilities.UpdateFloatDecorator(vehicle, FrontCamberID, data.FrontCamber, data.DefaultFrontCamber); + } + + private void SetRearCamberUsingData(int vehicle, WheelData data) + { + if (!DoesEntityExist(vehicle) || data == null) + return; + + int wheelsCount = data.WheelsCount; + int frontWheelsCount = data.FrontWheelsCount; + WheelDataNode[] nodes = data.GetNodes(); + + for (int i = frontWheelsCount; i < wheelsCount; i++) + SetVehicleWheelYRotation(vehicle, i, nodes[i].RotationY); + + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearCamberID, data.DefaultRearCamber, data.RearCamber); + VStancerUtilities.UpdateFloatDecorator(vehicle, RearCamberID, data.RearCamber, data.DefaultRearCamber); + } + + private void SetFrontTrackWidthUsingData(int vehicle, WheelData data) + { + if (!DoesEntityExist(vehicle) || data == null) + return; + + int frontWheelsCount = data.FrontWheelsCount; + WheelDataNode[] nodes = data.GetNodes(); + + for (int i = 0; i < frontWheelsCount; i++) + SetVehicleWheelXOffset(vehicle, i, nodes[i].PositionX); + + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontTrackWidthID, data.DefaultFrontTrackWidth, data.FrontTrackWidth); + VStancerUtilities.UpdateFloatDecorator(vehicle, FrontTrackWidthID, data.FrontTrackWidth, data.DefaultFrontTrackWidth); + } + + private void SetRearTrackWidthUsingData(int vehicle, WheelData data) + { + if (!DoesEntityExist(vehicle) || data == null) + return; + + int wheelsCount = data.WheelsCount; + int frontWheelsCount = data.FrontWheelsCount; + WheelDataNode[] nodes = data.GetNodes(); + + for (int i = frontWheelsCount; i < wheelsCount; i++) + SetVehicleWheelXOffset(vehicle, i, nodes[i].PositionX); + + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearTrackWidthID, data.DefaultRearTrackWidth, data.RearTrackWidth); + VStancerUtilities.UpdateFloatDecorator(vehicle, RearTrackWidthID, data.RearTrackWidth, data.DefaultRearTrackWidth); + } + + private WheelData GetWheelDataFromEntity(int vehicle) + { + if (!DoesEntityExist(vehicle)) + return null; + + int wheelsCount = GetVehicleNumberOfWheels(vehicle); + int frontCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); + + // Get default values first + float frontTrackWidth_def = DecorExistOn(vehicle, DefaultFrontTrackWidthID) ? DecorGetFloat(vehicle, DefaultFrontTrackWidthID) : GetVehicleWheelXOffset(vehicle, 0); + float frontCamber_def = DecorExistOn(vehicle, DefaultFrontCamberID) ? DecorGetFloat(vehicle, DefaultFrontCamberID) : GetVehicleWheelYRotation(vehicle, 0); + float rearTrackWidth_def = DecorExistOn(vehicle, DefaultRearTrackWidthID) ? DecorGetFloat(vehicle, DefaultRearTrackWidthID) : GetVehicleWheelXOffset(vehicle, frontCount); + float rearCamber_def = DecorExistOn(vehicle, DefaultRearCamberID) ? DecorGetFloat(vehicle, DefaultRearCamberID) : GetVehicleWheelYRotation(vehicle, frontCount); + + float frontTrackWidth = DecorExistOn(vehicle, FrontTrackWidthID) ? DecorGetFloat(vehicle, FrontTrackWidthID) : frontTrackWidth_def; + float frontCamber = DecorExistOn(vehicle, FrontCamberID) ? DecorGetFloat(vehicle, FrontCamberID) : frontCamber_def; + float rearTrackWdith = DecorExistOn(vehicle, RearTrackWidthID) ? DecorGetFloat(vehicle, RearTrackWidthID) : rearTrackWidth_def; + float rearCamber = DecorExistOn(vehicle, RearCamberID) ? DecorGetFloat(vehicle, RearCamberID) : rearCamber_def; + + return new WheelData(wheelsCount, frontTrackWidth_def, frontCamber_def, rearTrackWidth_def, rearCamber_def) + { + FrontTrackWidth = frontTrackWidth, + FrontCamber = frontCamber, + RearTrackWidth = rearTrackWdith, + RearCamber = rearCamber, + }; + } + + private void UpdateVehicleUsingDecorators(int vehicle) + { + int wheelsCount = GetVehicleNumberOfWheels(vehicle); + int frontCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); + + if (DecorExistOn(vehicle, FrontTrackWidthID)) + { + float value = DecorGetFloat(vehicle, FrontTrackWidthID); + + for (int index = 0; index < frontCount; index++) + { + if (index % 2 == 0) + SetVehicleWheelXOffset(vehicle, index, value); + else + SetVehicleWheelXOffset(vehicle, index, -value); + } + } + + if (DecorExistOn(vehicle, FrontCamberID)) + { + float value = DecorGetFloat(vehicle, FrontCamberID); + + for (int index = 0; index < frontCount; index++) + { + if (index % 2 == 0) + SetVehicleWheelYRotation(vehicle, index, value); + else + SetVehicleWheelYRotation(vehicle, index, -value); + } + } + + if (DecorExistOn(vehicle, RearTrackWidthID)) + { + float value = DecorGetFloat(vehicle, RearTrackWidthID); + + for (int index = frontCount; index < wheelsCount; index++) + { + if (index % 2 == 0) + SetVehicleWheelXOffset(vehicle, index, value); + else + SetVehicleWheelXOffset(vehicle, index, -value); + } + } + + if (DecorExistOn(vehicle, RearCamberID)) + { + float value = DecorGetFloat(vehicle, RearCamberID); + + for (int index = frontCount; index < wheelsCount; index++) + { + if (index % 2 == 0) + SetVehicleWheelYRotation(vehicle, index, value); + else + SetVehicleWheelYRotation(vehicle, index, -value); + } + } + } + + private void UpdateVehicleUsingWheelData(int vehicle, WheelData data) + { + if (!DoesEntityExist(vehicle) || data == null) + return; + + int wheelsCount = data.WheelsCount; + WheelDataNode[] nodes = data.GetNodes(); + + for (int index = 0; index < wheelsCount; index++) + { + SetVehicleWheelXOffset(vehicle, index, nodes[index].PositionX); + SetVehicleWheelYRotation(vehicle, index, nodes[index].RotationY); + } + + UpdateVehicleDecorators(vehicle, data); + } + + private void UpdateVehicleDecorators(int vehicle, WheelData data) + { + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontTrackWidthID, data.DefaultFrontTrackWidth, data.FrontTrackWidth); + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontCamberID, data.DefaultFrontCamber, data.FrontCamber); + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearTrackWidthID, data.DefaultRearTrackWidth, data.RearTrackWidth); + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearCamberID, data.DefaultRearCamber, data.RearCamber); + + VStancerUtilities.UpdateFloatDecorator(vehicle, FrontTrackWidthID, data.FrontTrackWidth, data.DefaultFrontTrackWidth); + VStancerUtilities.UpdateFloatDecorator(vehicle, FrontCamberID, data.FrontCamber, data.DefaultFrontCamber); + VStancerUtilities.UpdateFloatDecorator(vehicle, RearTrackWidthID, data.RearTrackWidth, data.DefaultRearTrackWidth); + VStancerUtilities.UpdateFloatDecorator(vehicle, RearCamberID, data.RearCamber, data.DefaultRearCamber); + } + + private void RegisterDecorators() + { + DecorRegister(FrontTrackWidthID, 1); + DecorRegister(FrontCamberID, 1); + DecorRegister(RearTrackWidthID, 1); + DecorRegister(RearCamberID, 1); + + DecorRegister(DefaultFrontTrackWidthID, 1); + DecorRegister(DefaultFrontCamberID, 1); + DecorRegister(DefaultRearTrackWidthID, 1); + DecorRegister(DefaultRearCamberID, 1); + } + + private void RemoveDecoratorsFromVehicle(int vehicle) + { + if (DecorExistOn(vehicle, FrontTrackWidthID)) + DecorRemove(vehicle, FrontTrackWidthID); + + if (DecorExistOn(vehicle, FrontCamberID)) + DecorRemove(vehicle, FrontCamberID); + + if (DecorExistOn(vehicle, DefaultFrontTrackWidthID)) + DecorRemove(vehicle, DefaultFrontTrackWidthID); + + if (DecorExistOn(vehicle, DefaultFrontCamberID)) + DecorRemove(vehicle, DefaultFrontCamberID); + + if (DecorExistOn(vehicle, RearTrackWidthID)) + DecorRemove(vehicle, RearTrackWidthID); + + if (DecorExistOn(vehicle, RearCamberID)) + DecorRemove(vehicle, RearCamberID); + + if (DecorExistOn(vehicle, DefaultRearTrackWidthID)) + DecorRemove(vehicle, DefaultRearTrackWidthID); + + if (DecorExistOn(vehicle, DefaultRearCamberID)) + DecorRemove(vehicle, DefaultRearCamberID); + } + + private void OnMenuCommandInvoked(string commandID) + { + switch (commandID) + { + case ResetID: + if (!DataIsValid) + return; + + WheelData.Reset(); + break; + } + + } + + private void OnMenuFloatPropertyChanged(string id, float value) + { + switch (id) + { + case FrontCamberID: + if (DataIsValid) WheelData.FrontCamber = value; + break; + case RearCamberID: + if (DataIsValid) WheelData.RearCamber = value; + break; + case FrontTrackWidthID: + if (DataIsValid) WheelData.FrontTrackWidth = -value; + break; + case RearTrackWidthID: + if (DataIsValid) WheelData.RearTrackWidth = -value; + break; + } + } + + internal void PrintDecoratorsInfo(int vehicle) + { + if (!DoesEntityExist(vehicle)) + { + Debug.WriteLine($"{nameof(WheelScript)}: Can't find vehicle with handle {vehicle}"); + return; + } + + int wheelsCount = GetVehicleNumberOfWheels(vehicle); + int netID = NetworkGetNetworkIdFromEntity(vehicle); + StringBuilder s = new StringBuilder(); + s.AppendLine($"{nameof(WheelScript)}: Vehicle:{vehicle} netID:{netID} wheelsCount:{wheelsCount}"); + + if (DecorExistOn(vehicle, FrontTrackWidthID)) + s.AppendLine($"{FrontTrackWidthID}: {DecorGetFloat(vehicle, FrontTrackWidthID)}"); + + if (DecorExistOn(vehicle, DefaultFrontTrackWidthID)) + s.AppendLine($"{DefaultFrontTrackWidthID}: {DecorGetFloat(vehicle, DefaultFrontTrackWidthID)}"); + + if (DecorExistOn(vehicle, RearTrackWidthID)) + s.AppendLine($"{RearTrackWidthID}: {DecorGetFloat(vehicle, RearTrackWidthID)}"); + + if (DecorExistOn(vehicle, DefaultRearTrackWidthID)) + s.AppendLine($"{DefaultRearTrackWidthID}: {DecorGetFloat(vehicle, DefaultRearTrackWidthID)}"); + + if (DecorExistOn(vehicle, FrontCamberID)) + s.AppendLine($"{FrontCamberID}: {DecorGetFloat(vehicle, FrontCamberID)}"); + + if (DecorExistOn(vehicle, DefaultFrontCamberID)) + s.AppendLine($"{DefaultFrontCamberID}: {DecorGetFloat(vehicle, DefaultFrontCamberID)}"); + + if (DecorExistOn(vehicle, RearCamberID)) + s.AppendLine($"{RearCamberID}: {DecorGetFloat(vehicle, RearCamberID)}"); + + if (DecorExistOn(vehicle, DefaultRearCamberID)) + s.AppendLine($"{RearCamberID}: {DecorGetFloat(vehicle, DefaultRearCamberID)}"); + + Debug.WriteLine(s.ToString()); + } + + private bool EntityHasDecorators(int entity) + { + return ( + DecorExistOn(entity, FrontTrackWidthID) || + DecorExistOn(entity, FrontCamberID) || + DecorExistOn(entity, RearTrackWidthID) || + DecorExistOn(entity, RearCamberID) || + DecorExistOn(entity, DefaultFrontTrackWidthID) || + DecorExistOn(entity, DefaultFrontCamberID) || + DecorExistOn(entity, DefaultRearTrackWidthID) || + DecorExistOn(entity, DefaultRearCamberID) + ); + } + + internal void PrintVehiclesWithDecorators(IEnumerable vehiclesList) + { + IEnumerable entities = vehiclesList.Where(entity => EntityHasDecorators(entity)); + + Debug.WriteLine($"{nameof(WheelScript)}: Vehicles with decorators: {entities.Count()}"); + + foreach (int item in entities) + Debug.WriteLine($"Vehicle: {item}"); + } + + internal WheelPreset GetWheelPreset() + { + if (!DataIsValid) + return null; + + // Only required to avoid saving this preset locally when not required + if (!WheelData.IsEdited) + return null; + + return new WheelPreset(WheelData); + } + + internal async Task SetWheelPreset(WheelPreset preset) + { + if (!DataIsValid || preset == null) + return; + + // TODO: Check if values are within limits + + WheelData.FrontTrackWidth = preset.FrontTrackWidth; + WheelData.RearTrackWidth = preset.RearTrackWidth; + WheelData.FrontCamber = preset.FrontCamber; + WheelData.RearCamber = preset.RearCamber; + + // Don't refresh, as it's already done by OnWheelDataPropertyChanged + + Debug.WriteLine($"{nameof(WheelScript)}: wheel preset applied"); + + await Delay(200); + WheelDataChanged?.Invoke(this, EventArgs.Empty); + } + + internal bool API_SetWheelPreset(int vehicle, WheelPreset preset) + { + if (preset == null) + return false; + + if (!DoesEntityExist(vehicle)) + return false; + + float frontTrackWidth = preset.FrontTrackWidth; + float rearTrackWidth = preset.RearTrackWidth; + float frontCamber = preset.FrontCamber; + float rearCamber = preset.RearCamber; + + int wheelsCount = GetVehicleNumberOfWheels(vehicle); + int frontCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); + + float frontTrackWidth_def = DecorExistOn(vehicle, DefaultFrontTrackWidthID) ? DecorGetFloat(vehicle, DefaultFrontTrackWidthID) : GetVehicleWheelXOffset(vehicle, 0); + float frontCamber_def = DecorExistOn(vehicle, DefaultFrontCamberID) ? DecorGetFloat(vehicle, DefaultFrontCamberID) : GetVehicleWheelYRotation(vehicle, 0); + float rearTrackWidth_def = DecorExistOn(vehicle, DefaultRearTrackWidthID) ? DecorGetFloat(vehicle, DefaultRearTrackWidthID) : GetVehicleWheelXOffset(vehicle, frontCount); + float rearCamber_def = DecorExistOn(vehicle, DefaultRearCamberID) ? DecorGetFloat(vehicle, DefaultRearCamberID) : GetVehicleWheelYRotation(vehicle, frontCount); + + if (vehicle == _playerVehicleHandle) + { + // TODO: Maybe this is useles and could just use SetWheelPreset instead + WheelData = new WheelData(wheelsCount, frontTrackWidth_def, frontCamber_def, rearTrackWidth_def, rearCamber_def) + { + FrontTrackWidth = frontTrackWidth, + FrontCamber = frontCamber, + RearTrackWidth = rearTrackWidth, + RearCamber = rearCamber + }; + } + else + { + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontTrackWidthID, frontTrackWidth_def, frontTrackWidth); + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontCamberID, frontCamber_def, frontCamber); + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearTrackWidthID, rearTrackWidth_def, rearTrackWidth); + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearCamberID, rearCamber_def, rearCamber); + + VStancerUtilities.UpdateFloatDecorator(vehicle, FrontTrackWidthID, frontTrackWidth, frontTrackWidth_def); + VStancerUtilities.UpdateFloatDecorator(vehicle, FrontCamberID, frontCamber, frontCamber_def); + VStancerUtilities.UpdateFloatDecorator(vehicle, RearTrackWidthID, rearTrackWidth, rearTrackWidth_def); + VStancerUtilities.UpdateFloatDecorator(vehicle, RearCamberID, rearCamber, rearCamber_def); + } + + return true; + } + + internal bool API_GetWheelPreset(int vehicle, out WheelPreset preset) + { + preset = null; + + if (!DoesEntityExist(vehicle)) + return false; + + WheelData data = (vehicle == _playerVehicleHandle && DataIsValid) ? WheelData : GetWheelDataFromEntity(vehicle); + + if(data == null) + return false; + + preset = new WheelPreset(data); + return true; + } + + internal bool API_ResetWheelPreset(int vehicle) + { + if (!DoesEntityExist(vehicle)) + return false; + + if (vehicle != _playerVehicleHandle) + { + RemoveDecoratorsFromVehicle(vehicle); + UpdateVehicleUsingDecorators(vehicle); + return true; + } + + if (!DataIsValid) + return false; + + WheelData.Reset(); + + return true; + } + + internal bool API_SetFrontCamber(int vehicle, float value) + { + if (!DoesEntityExist(vehicle)) + return false; + + if (vehicle != _playerVehicleHandle) + { + float value_def = DecorExistOn(vehicle, DefaultFrontCamberID) ? DecorGetFloat(vehicle, DefaultFrontCamberID) : GetVehicleWheelYRotation(vehicle, 0); + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontCamberID, value_def, value); + VStancerUtilities.UpdateFloatDecorator(vehicle, FrontCamberID, value, value_def); + return true; + } + + if (!DataIsValid) + return false; + + WheelData.FrontCamber = value; + WheelDataChanged?.Invoke(this, EventArgs.Empty); + + return true; + } + + internal bool API_SetRearCamber(int vehicle, float value) + { + if (!DoesEntityExist(vehicle)) + return false; + + if (vehicle != _playerVehicleHandle) + { + int wheelsCount = GetVehicleNumberOfWheels(vehicle); + int frontCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); + + float value_def = DecorExistOn(vehicle, DefaultRearCamberID) ? DecorGetFloat(vehicle, DefaultRearCamberID) : GetVehicleWheelYRotation(vehicle, frontCount); + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearCamberID, value_def, value); + VStancerUtilities.UpdateFloatDecorator(vehicle, RearCamberID, value, value_def); + return true; + } + + if (!DataIsValid) + return false; + + WheelData.RearCamber = value; + WheelDataChanged?.Invoke(this, EventArgs.Empty); + + return true; + } + + internal bool API_SetFrontTrackWidth(int vehicle, float value) + { + if (!DoesEntityExist(vehicle)) + return false; + + if (vehicle != _playerVehicleHandle) + { + float value_def = DecorExistOn(vehicle, DefaultFrontTrackWidthID) ? DecorGetFloat(vehicle, DefaultFrontTrackWidthID) : GetVehicleWheelXOffset(vehicle, 0); + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultFrontTrackWidthID, value_def, value); + VStancerUtilities.UpdateFloatDecorator(vehicle, FrontTrackWidthID, value, value_def); + return true; + } + + if (!DataIsValid) + return false; + + WheelData.FrontTrackWidth = value; + WheelDataChanged?.Invoke(this, EventArgs.Empty); + + return true; + } + + internal bool API_SetRearTrackWidth(int vehicle, float value) + { + if (!DoesEntityExist(vehicle)) + return false; + + if (vehicle != _playerVehicleHandle) + { + int wheelsCount = GetVehicleNumberOfWheels(vehicle); + int frontCount = VStancerUtilities.CalculateFrontWheelsCount(wheelsCount); + + float value_def = DecorExistOn(vehicle, DefaultRearTrackWidthID) ? DecorGetFloat(vehicle, DefaultRearTrackWidthID) : GetVehicleWheelXOffset(vehicle, frontCount); + VStancerUtilities.UpdateFloatDecorator(vehicle, DefaultRearTrackWidthID, value_def, value); + VStancerUtilities.UpdateFloatDecorator(vehicle, RearTrackWidthID, value, value_def); + return true; + } + + if (!DataIsValid) + return false; + + WheelData.RearTrackWidth = value; + WheelDataChanged?.Invoke(this, EventArgs.Empty); + + return true; + } + + internal bool API_GetFrontCamber(int vehicle, out float value) + { + value = default; + + if (!DoesEntityExist(vehicle)) + return false; + + value = DecorExistOn(vehicle, FrontCamberID) ? DecorGetFloat(vehicle, FrontCamberID) : GetVehicleWheelYRotation(vehicle, 0); + return true; + } + + internal bool API_GetRearCamber(int vehicle, out float value) + { + value = default; + + if (!DoesEntityExist(vehicle)) + return false; + + int frontCount = VStancerUtilities.CalculateFrontWheelsCount(GetVehicleNumberOfWheels(vehicle)); + value = DecorExistOn(vehicle, RearCamberID) ? DecorGetFloat(vehicle, RearCamberID) : GetVehicleWheelYRotation(vehicle, frontCount); + return true; + } + + internal bool API_GetFrontTrackWidth(int vehicle, out float value) + { + value = default; + + if (!DoesEntityExist(vehicle)) + return false; + + value = DecorExistOn(vehicle, FrontTrackWidthID) ? DecorGetFloat(vehicle, FrontTrackWidthID) : GetVehicleWheelXOffset(vehicle, 0); + return true; + } + + internal bool API_GetRearTrackWidth(int vehicle, out float value) + { + value = default; + + if (!DoesEntityExist(vehicle)) + return false; + + int frontCount = VStancerUtilities.CalculateFrontWheelsCount(GetVehicleNumberOfWheels(vehicle)); + value = DecorExistOn(vehicle, RearTrackWidthID) ? DecorGetFloat(vehicle, RearTrackWidthID) : GetVehicleWheelXOffset(vehicle, frontCount); + return true; + } + } +} diff --git a/VStancer.Client/UI/ClientPresetsMenu.cs b/VStancer.Client/UI/ClientPresetsMenu.cs new file mode 100644 index 0000000..ff0ed0b --- /dev/null +++ b/VStancer.Client/UI/ClientPresetsMenu.cs @@ -0,0 +1,71 @@ +using System; +using MenuAPI; +using CitizenFX.Core; +using static CitizenFX.Core.Native.API; +using VStancer.Client.Scripts; + +namespace VStancer.Client.UI +{ + internal class ClientPresetsMenu : Menu + { + private readonly ClientPresetsScript _script; + + internal ClientPresetsMenu(ClientPresetsScript script, string name = Globals.ScriptName, string subtitle = "Client Presets Menu") : base(name, subtitle) + { + _script = script; + + _script.Presets.PresetsCollectionChanged += new EventHandler((sender, args) => Update()); + + Update(); + + AddTextEntry("VSTANCER_ENTER_PRESET_NAME", "Enter a name for the preset"); + + OnItemSelect += ItemSelect; + InstructionalButtons.Add(Control.PhoneExtraOption, GetLabelText("ITEM_SAVE")); + InstructionalButtons.Add(Control.PhoneOption, GetLabelText("ITEM_DEL")); + + // Disable Controls binded on the same key + ButtonPressHandlers.Add(new ButtonPressHandler(Control.SelectWeapon, ControlPressCheckType.JUST_RELEASED, new Action((sender, control) => + { + }), true)); + + ButtonPressHandlers.Add(new ButtonPressHandler(Control.VehicleExit, ControlPressCheckType.JUST_RELEASED, new Action((sender, control) => + { + }), true)); + + ButtonPressHandlers.Add(new ButtonPressHandler(Control.PhoneExtraOption, ControlPressCheckType.JUST_RELEASED, new Action(async (sender, control) => + { + string presetName = await _script.GetPresetNameFromUser("VSTANCER_ENTER_PRESET_NAME", ""); + SavePresetEvent?.Invoke(this, presetName.Trim()); + }), true)); + + ButtonPressHandlers.Add(new ButtonPressHandler(Control.PhoneOption, ControlPressCheckType.JUST_RELEASED, new Action((sender, control) => + { + if (GetMenuItems().Count > 0) + { + string presetName = GetMenuItems()[CurrentIndex].ItemData; + DeletePresetEvent?.Invoke(this, presetName); + } + }), true)); + } + + internal event EventHandler ApplyPresetEvent; + internal event EventHandler SavePresetEvent; + internal event EventHandler DeletePresetEvent; + + internal void Update() + { + ClearMenuItems(); + + if (_script.Presets == null) + return; + + foreach (var key in _script.Presets.GetKeys()) + { + AddMenuItem(new MenuItem(key) { ItemData = key }); + } + } + + private void ItemSelect(Menu menu, MenuItem menuItem, int itemIndex) => ApplyPresetEvent?.Invoke(menu, menuItem.ItemData); + } +} \ No newline at end of file diff --git a/VStancer.Client/UI/MainMenu.cs b/VStancer.Client/UI/MainMenu.cs new file mode 100644 index 0000000..f52fe96 --- /dev/null +++ b/VStancer.Client/UI/MainMenu.cs @@ -0,0 +1,128 @@ +using System; +using CitizenFX.Core; +using static CitizenFX.Core.Native.API; +using static VStancer.Client.UI.MenuUtilities; +using MenuAPI; +using VStancer.Client.Scripts; + +namespace VStancer.Client.UI +{ + internal class MainMenu : Menu + { + private readonly MainScript _script; + + private WheelMenu WheelMenu { get; set; } + private WheelModMenu WheelModMenu { get; set; } + private ClientPresetsMenu ClientPresetsMenu { get; set; } + + private MenuItem WheelMenuMenuItem { get; set; } + private MenuItem WheelModMenuMenuItem { get; set; } + private MenuItem ClientPresetsMenuMenuItem { get; set; } + + + internal MainMenu(MainScript script, string name = Globals.ScriptName, string subtitle = "Main Menu") : base(name, subtitle) + { + _script = script; + + _script.ToggleMenuVisibility += new EventHandler((sender, args) => + { + var currentMenu = MenuController.MainMenu; + + if (currentMenu == null) + return; + + currentMenu.Visible = !currentMenu.Visible; + }); + + MenuController.MenuAlignment = MenuController.MenuAlignmentOption.Right; + MenuController.MenuToggleKey = (Control)_script.Config.ToggleMenuControl; + MenuController.EnableMenuToggleKeyOnController = false; + MenuController.DontOpenAnyMenu = true; + MenuController.MainMenu = this; + + if (_script.WheelScript != null) + WheelMenu = _script.WheelScript.Menu; + + if (_script.WheelModScript != null) + { + WheelModMenu = _script.WheelModScript.Menu; + WheelModMenu.PropertyChanged += (sender, args) => UpdateWheelModMenuMenuItem(); + } + + if (_script.ClientPresetsScript != null) + ClientPresetsMenu = _script.ClientPresetsScript.Menu; + + Update(); + } + + internal void Update() + { + ClearMenuItems(); + + MenuController.Menus.Clear(); + MenuController.AddMenu(this); + + if (WheelMenu != null) + { + WheelMenuMenuItem = new MenuItem("Wheel Menu", "The menu to edit main properties.") + { + Label = "→→→" + }; + + AddMenuItem(WheelMenuMenuItem); + + MenuController.AddSubmenu(this, WheelMenu); + MenuController.BindMenuItem(this, WheelMenu, WheelMenuMenuItem); + } + + if (WheelModMenu != null) + { + WheelModMenuMenuItem = new MenuItem("Wheel Mod Menu") + { + Label = "→→→" + }; + UpdateWheelModMenuMenuItem(); + + AddMenuItem(WheelModMenuMenuItem); + + MenuController.AddSubmenu(this, WheelModMenu); + MenuController.BindMenuItem(this, WheelModMenu, WheelModMenuMenuItem); + } + + if (ClientPresetsMenu != null) + { + ClientPresetsMenuMenuItem = new MenuItem("Client Presets Menu", "The menu to manage the presets saved by you.") + { + Label = "→→→" + }; + + AddMenuItem(ClientPresetsMenuMenuItem); + + MenuController.AddSubmenu(this, ClientPresetsMenu); + MenuController.BindMenuItem(this, ClientPresetsMenu, ClientPresetsMenuMenuItem); + } + } + + internal bool HideMenu + { + get => MenuController.DontOpenAnyMenu; + set + { + MenuController.DontOpenAnyMenu = value; + } + } + + private void UpdateWheelModMenuMenuItem() + { + if (WheelModMenuMenuItem == null) + return; + + var enabled = WheelModMenu != null ? WheelModMenu.Enabled : false; + + WheelModMenuMenuItem.Enabled = enabled; + WheelModMenuMenuItem.RightIcon = enabled ? MenuItem.Icon.NONE : MenuItem.Icon.LOCK; + WheelModMenuMenuItem.Label = enabled ? "→→→" : string.Empty; + WheelModMenuMenuItem.Description = enabled ? "The menu to edit custom wheel properties." : "Install a custom wheel to access to this menu."; + } + } +} diff --git a/VStancer.Client/UI/MenuUtilities.cs b/VStancer.Client/UI/MenuUtilities.cs new file mode 100644 index 0000000..9531a74 --- /dev/null +++ b/VStancer.Client/UI/MenuUtilities.cs @@ -0,0 +1,51 @@ +using CitizenFX.Core.UI; +using MenuAPI; + +namespace VStancer.Client.UI +{ + internal static class MenuUtilities + { + internal delegate void FloatPropertyChanged(string id, float value); + + internal static MenuDynamicListItem CreateDynamicFloatList(string name, float defaultValue, float value, float maxEditing, string id, float step = 0.01f) + { + float min = defaultValue - maxEditing; + float max = defaultValue + maxEditing; + + var callback = FloatChangeCallback(name, value, min, max, step); + + return new MenuDynamicListItem(name, value.ToString("F3"), callback) { ItemData = id }; + } + + internal static MenuDynamicListItem.ChangeItemCallback FloatChangeCallback(string name, float value, float minimum, float maximum, float step) + { + string callback(MenuDynamicListItem sender, bool left) + { + var min = minimum; + var max = maximum; + + var newvalue = value; + + if (left) + newvalue -= step; + else if (!left) + newvalue += step; + else return value.ToString("F3"); + + // Hotfix to trim the value to 3 digits + newvalue = float.Parse((newvalue).ToString("F3")); + + if (newvalue < min) + Screen.ShowNotification($"~o~Warning~w~: Min ~b~{name}~w~ value allowed is {min}"); + else if (newvalue > max) + Screen.ShowNotification($"~o~Warning~w~: Max ~b~{name}~w~ value allowed is {max}"); + else + { + value = newvalue; + } + return value.ToString("F3"); + }; + return callback; + } + } +} diff --git a/VStancer.Client/UI/WheelMenu.cs b/VStancer.Client/UI/WheelMenu.cs new file mode 100644 index 0000000..d5436dc --- /dev/null +++ b/VStancer.Client/UI/WheelMenu.cs @@ -0,0 +1,93 @@ +using System; +using MenuAPI; +using VStancer.Client.Scripts; +using static VStancer.Client.UI.MenuUtilities; + +namespace VStancer.Client.UI +{ + internal class WheelMenu : Menu + { + private readonly WheelScript _script; + + internal WheelMenu(WheelScript script, string name = Globals.ScriptName, string subtitle = "Wheel Menu") : base(name, subtitle) + { + _script = script; + + _script.WheelDataChanged += new EventHandler((sender, args) => Update()); + + Update(); + + OnDynamicListItemCurrentItemChange += DynamicListItemCurrentItemChange; + OnItemSelect += ItemSelect; + } + + private void ItemSelect(Menu menu, MenuItem menuItem, int itemIndex) + { + if (menuItem == ResetItem) + ResetPropertiesEvent?.Invoke(this, menuItem.ItemData as string); + } + + private void DynamicListItemCurrentItemChange(Menu menu, MenuDynamicListItem dynamicListItem, string oldValue, string newValue) + { + // TODO: Does it need to check if newvalue != oldvalue? + if (oldValue == newValue) + return; + + if (float.TryParse(newValue, out float newfloatValue)) + FloatPropertyChangedEvent?.Invoke(dynamicListItem.ItemData as string, newfloatValue); + } + + private MenuDynamicListItem FrontCamberListItem { get; set; } + private MenuDynamicListItem RearCamberListItem { get; set; } + private MenuDynamicListItem FrontTrackWidthListItem { get; set; } + private MenuDynamicListItem RearTrackWidthListItem { get; set; } + private MenuItem ResetItem { get; set; } + + internal event FloatPropertyChanged FloatPropertyChangedEvent; + internal event EventHandler ResetPropertiesEvent; + + internal void Update() + { + ClearMenuItems(); + + if (!_script.DataIsValid) + return; + + FrontTrackWidthListItem = CreateDynamicFloatList("Front Track Width", + -_script.WheelData.DefaultFrontTrackWidth, + -_script.WheelData.FrontTrackWidth, + _script.Config.WheelLimits.FrontTrackWidth, + WheelScript.FrontTrackWidthID, + _script.Config.FloatStep); + + RearTrackWidthListItem = CreateDynamicFloatList("Rear Track Width", + -_script.WheelData.DefaultRearTrackWidth, + -_script.WheelData.RearTrackWidth, + _script.Config.WheelLimits.RearTrackWidth, + WheelScript.RearTrackWidthID, + _script.Config.FloatStep); + + FrontCamberListItem = CreateDynamicFloatList("Front Camber", + _script.WheelData.DefaultFrontCamber, + _script.WheelData.FrontCamber, + _script.Config.WheelLimits.FrontCamber, + WheelScript.FrontCamberID, + _script.Config.FloatStep); + + RearCamberListItem = CreateDynamicFloatList("Rear Camber", + _script.WheelData.DefaultRearCamber, + _script.WheelData.RearCamber, + _script.Config.WheelLimits.RearCamber, + WheelScript.RearCamberID, + _script.Config.FloatStep); + + ResetItem = new MenuItem("Reset", "Restores the default values") { ItemData = WheelScript.ResetID }; + + AddMenuItem(FrontTrackWidthListItem); + AddMenuItem(RearTrackWidthListItem); + AddMenuItem(FrontCamberListItem); + AddMenuItem(RearCamberListItem); + AddMenuItem(ResetItem); + } + } +} diff --git a/VStancer.Client/UI/WheelModMenu.cs b/VStancer.Client/UI/WheelModMenu.cs new file mode 100644 index 0000000..083e748 --- /dev/null +++ b/VStancer.Client/UI/WheelModMenu.cs @@ -0,0 +1,152 @@ +using System; +using MenuAPI; +using VStancer.Client.Scripts; +using static VStancer.Client.UI.MenuUtilities; + +namespace VStancer.Client.UI +{ + internal class WheelModMenu : Menu + { + private readonly WheelModScript _script; + private bool _enabled; + + internal event EventHandler PropertyChanged; + + internal bool Enabled + { + get => _enabled; + private set + { + if (Equals(value, _enabled)) + return; + + _enabled = value; + PropertyChanged?.Invoke(this, nameof(Enabled)); + } + } + + internal WheelModMenu(WheelModScript script, string name = Globals.ScriptName, string subtitle = "Wheel Mod Menu") : base(name, subtitle) + { + _script = script; + + _script.WheelModDataChanged += new EventHandler((sender, args) => + { + Update(); + }); + + Update(); + + OnDynamicListItemCurrentItemChange += DynamicListItemCurrentItemChange; + OnItemSelect += ItemSelect; + } + + private void ItemSelect(Menu menu, MenuItem menuItem, int itemIndex) + { + if (menuItem == ResetItem) + ResetPropertiesEvent?.Invoke(this, menuItem.ItemData as string); + } + + private void DynamicListItemCurrentItemChange(Menu menu, MenuDynamicListItem dynamicListItem, string oldValue, string newValue) + { + // TODO: Does it need to check if newvalue != oldvalue? + if (oldValue == newValue) + return; + + if (float.TryParse(newValue, out float newfloatValue)) + FloatPropertyChangedEvent?.Invoke(dynamicListItem.ItemData as string, newfloatValue); + } + + private MenuDynamicListItem WheelSizeListItem { get; set; } + private MenuDynamicListItem WheelWidthListItem { get; set; } + + //private MenuDynamicListItem FrontTireColliderWidthListItem { get; set; } + //private MenuDynamicListItem FrontTireColliderSizeListItem { get; set; } + //private MenuDynamicListItem FrontRimColliderSizeListItem { get; set; } + //private MenuDynamicListItem RearTireColliderWidthListItem { get; set; } + //private MenuDynamicListItem RearTireColliderSizeListItem { get; set; } + //private MenuDynamicListItem RearRimColliderSizeListItem { get; set; } + private MenuItem ResetItem { get; set; } + + internal event FloatPropertyChanged FloatPropertyChangedEvent; + internal event EventHandler ResetPropertiesEvent; + + internal void Update() + { + ClearMenuItems(); + + Enabled = _script.DataIsValid; + + if (!Enabled) + return; + + WheelSizeListItem = CreateDynamicFloatList("Wheel Size", + _script.WheelModData.DefaultWheelSize, + _script.WheelModData.WheelSize, + _script.Config.WheelModLimits.WheelSize, + WheelModScript.WheelSizeID, + _script.Config.FloatStep); + + WheelWidthListItem = CreateDynamicFloatList("Wheel Width", + _script.WheelModData.DefaultWheelWidth, + _script.WheelModData.WheelWidth, + _script.Config.WheelModLimits.WheelWidth, + WheelModScript.WheelWidthID, + _script.Config.FloatStep); + + /* + FrontTireColliderWidthListItem = CreateDynamicFloatList("Front Tire Collider Width", + _script.WheelModData.DefaultFrontTireColliderWidth, + _script.WheelModData.FrontTireColliderWidth, + _script.Config.WheelModLimits.FrontTireColliderWidth, + WheelModScript.FrontTireColliderWidthID, + _script.Config.FloatStep); + + FrontTireColliderSizeListItem = CreateDynamicFloatList("Front Tire Collider Size", + _script.WheelModData.DefaultFrontTireColliderSize, + _script.WheelModData.FrontTireColliderSize, + _script.Config.WheelModLimits.FrontTireColliderSize, + WheelModScript.FrontTireColliderSizeID, + _script.Config.FloatStep); + + FrontRimColliderSizeListItem = CreateDynamicFloatList("Front Rim Collider Size", + _script.WheelModData.DefaultFrontRimColliderSize, + _script.WheelModData.FrontRimColliderSize, + _script.Config.WheelModLimits.FrontRimColliderSize, + WheelModScript.FrontRimColliderSizeID, + _script.Config.FloatStep); + + RearTireColliderWidthListItem = CreateDynamicFloatList("Rear Tire Collider Width", + _script.WheelModData.DefaultRearTireColliderWidth, + _script.WheelModData.RearTireColliderWidth, + _script.Config.WheelModLimits.RearTireColliderWidth, + WheelModScript.RearTireColliderWidthID, + _script.Config.FloatStep); + + RearTireColliderSizeListItem = CreateDynamicFloatList("Rear Tire Collider Size", + _script.WheelModData.DefaultRearTireColliderSize, + _script.WheelModData.RearTireColliderSize, + _script.Config.WheelModLimits.RearTireColliderSize, + WheelModScript.RearTireColliderSizeID, + _script.Config.FloatStep); + + RearRimColliderSizeListItem = CreateDynamicFloatList("Rear Rim Collider Size", + _script.WheelModData.DefaultRearRimColliderSize, + _script.WheelModData.RearRimColliderSize, + _script.Config.WheelModLimits.RearRimColliderSize, + WheelModScript.RearRimColliderSizeID, + _script.Config.FloatStep); + */ + ResetItem = new MenuItem("Reset", "Restores the default values") { ItemData = WheelModScript.ExtraResetID }; + + AddMenuItem(WheelSizeListItem); + AddMenuItem(WheelWidthListItem); + //AddMenuItem(FrontTireColliderWidthListItem); + //AddMenuItem(FrontTireColliderSizeListItem); + //AddMenuItem(FrontRimColliderSizeListItem); + //AddMenuItem(RearTireColliderWidthListItem); + //AddMenuItem(RearTireColliderSizeListItem); + //AddMenuItem(RearRimColliderSizeListItem); + AddMenuItem(ResetItem); + } + } +} diff --git a/VStancer.Client/VStancer.Client.csproj b/VStancer.Client/VStancer.Client.csproj index 56c6b44..86d3880 100644 --- a/VStancer.Client/VStancer.Client.csproj +++ b/VStancer.Client/VStancer.Client.csproj @@ -22,12 +22,12 @@ - 1.0.2407 + 1.0.2460 runtime compile; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/VStancer.Client/VStancerConfig.cs b/VStancer.Client/VStancerConfig.cs index 0b57be4..97bd77e 100644 --- a/VStancer.Client/VStancerConfig.cs +++ b/VStancer.Client/VStancerConfig.cs @@ -3,32 +3,77 @@ public class VStancerConfig { public bool Debug { get; set; } + public bool DisableMenu { get; set; } public bool ExposeCommand { get; set; } public bool ExposeEvent { get; set; } public float ScriptRange { get; set; } public long Timer { get; set; } public int ToggleMenuControl { get; set; } public float FloatStep { get; set; } - public NodeLimits FrontLimits { get; set; } - public NodeLimits RearLimits { get; set; } + public bool EnableWheelMod { get; set; } + public bool EnableClientPresets { get; set; } + public WheelLimits WheelLimits { get; set; } + public WheelModLimits WheelModLimits { get; set; } public VStancerConfig() { Debug = false; + DisableMenu = false; ExposeCommand = false; ExposeEvent = false; ScriptRange = 150.0f; Timer = 1000; ToggleMenuControl = 167; FloatStep = 0.01f; - FrontLimits = new NodeLimits { PositionX = 0.25f, RotationY = 0.20f }; - RearLimits = new NodeLimits { PositionX = 0.25f, RotationY = 0.20f }; + EnableWheelMod = true; + EnableClientPresets = true; + + WheelLimits = new WheelLimits + { + FrontTrackWidth= 0.25f, + RearTrackWidth = 0.25f, + FrontCamber = 0.20f, + RearCamber = 0.20f, + }; + + WheelModLimits = new WheelModLimits + { + WheelSize = 0.2f, + WheelWidth = 0.2f, + FrontTireColliderWidth = 0.1f, + FrontTireColliderSize = 0.1f, + FrontRimColliderSize = 0.1f, + RearTireColliderWidth = 0.1f, + RearTireColliderSize = 0.1f, + RearRimColliderSize = 0.1f, + }; } } - public struct NodeLimits + public struct WheelLimits + { + public float FrontTrackWidth { get; set; } + public float RearTrackWidth { get; set; } + public float FrontCamber { get; set; } + public float RearCamber { get; set; } + } + + public struct WheelModColliderLimits + { + public float TireColliderScaleX { get; set; } + public float TireColliderScaleYZ { get; set; } + public float RimColliderScaleYZ { get; set; } + } + + public struct WheelModLimits { - public float PositionX { get; set; } - public float RotationY { get; set; } + public float WheelSize { get; set; } + public float WheelWidth { get; set; } + public float FrontTireColliderWidth { get; set; } + public float FrontTireColliderSize { get; set; } + public float FrontRimColliderSize { get; set; } + public float RearTireColliderWidth { get; set; } + public float RearTireColliderSize { get; set; } + public float RearRimColliderSize { get; set; } } } diff --git a/VStancer.Client/VStancerEditor.cs b/VStancer.Client/VStancerEditor.cs deleted file mode 100644 index 29c2164..0000000 --- a/VStancer.Client/VStancerEditor.cs +++ /dev/null @@ -1,841 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Text; -using CitizenFX.Core; -using CitizenFX.Core.UI; -using static CitizenFX.Core.Native.API; -using Newtonsoft.Json; - -namespace VStancer.Client -{ - public class VStancerEditor : BaseScript - { - /// - /// The script which renders the menu - /// - private readonly VStancerMenu _vstancerMenu; - - /// - /// The handle of the current vehicle - /// - private int _playerVehicleHandle; - - /// - /// Indicates the last game time the timed tasks have been executed - /// - private long _lastTime; - - /// - /// The handle of the current player ped - /// - private int _playerPedHandle; - - /// - /// The list of all the vehicles' handles around the client's position - /// - private IEnumerable _worldVehiclesHandles; - - /// - /// The delta among which two float are considered equals - /// - private const float Epsilon = 0.001f; - - public const string FrontOffsetID = "vstancer_off_f"; - public const string FrontRotationID = "vstancer_rot_f"; - public const string RearOffsetID = "vstancer_off_r"; - public const string RearRotationID = "vstancer_rot_r"; - - public const string DefaultFrontOffsetID = "vstancer_off_f_def"; - public const string DefaultFrontRotationID = "vstancer_rot_f_def"; - public const string DefaultRearOffsetID = "vstancer_off_r_def"; - public const string DefaultRearRotationID = "vstancer_rot_r_def"; - - public const string ResetID = "vstancer_reset"; - - /// - /// Returns wheter and are valid - /// - public bool CurrentPresetIsValid => _playerVehicleHandle != -1 && CurrentPreset != null; - - /// - /// The preset associated to the player's vehicle - /// - public VStancerPreset CurrentPreset { get; private set; } - - /// - /// The configuration of the script - /// - public VStancerConfig Config { get; private set; } - - /// - /// The service which manages the local presets - /// - public IPresetManager LocalPresetsManager { get; private set; } - - /// - /// Invoked when is changed - /// - public event EventHandler NewPresetCreated; - - /// - /// Triggered when the client wants to manually toggle the menu visibility - /// using the optional command/event - /// - public event EventHandler ToggleMenuVisibility; - - public VStancerEditor() - { - // If the resource name is not the expected one ... - if (GetCurrentResourceName() != Globals.ResourceName) - { - Debug.WriteLine($"{Globals.ScriptName}: Invalid resource name, be sure the resource name is {Globals.ResourceName}"); - return; - } - - _lastTime = GetGameTimer(); - _playerVehicleHandle = -1; - CurrentPreset = null; - _worldVehiclesHandles = Enumerable.Empty(); - - RegisterRequiredDecorators(); - Config = LoadConfig(); - - LocalPresetsManager = new KvpPresetManager(Globals.KvpPrefix); - - RegisterCommand("vstancer_range", new Action((source, args) => - { - if (args.Count < 1) - { - Debug.WriteLine($"{Globals.ScriptName}: Missing float argument"); - return; - } - - if (float.TryParse(args[0], out float value)) - { - Config.ScriptRange = value; - Debug.WriteLine($"{Globals.ScriptName}: {nameof(Config.ScriptRange)} updated to {value}"); - } - else Debug.WriteLine($"{Globals.ScriptName}: Error parsing {args[0]} as float"); - - }), false); - - RegisterCommand("vstancer_debug", new Action((source, args) => - { - if (args.Count < 1) - { - Debug.WriteLine($"{Globals.ScriptName}: Missing bool argument"); - return; - } - - if (bool.TryParse(args[0], out bool value)) - { - Config.Debug = value; - Debug.WriteLine($"{Globals.ScriptName}: {nameof(Config.Debug)} updated to {value}"); - } - else Debug.WriteLine($"{Globals.ScriptName}: Error parsing {args[0]} as bool"); - - }), false); - - RegisterCommand("vstancer_decorators", new Action((source, args) => - { - if (args.Count < 1) - PrintDecoratorsInfo(_playerVehicleHandle); - else - { - if (int.TryParse(args[0], out int value)) - PrintDecoratorsInfo(value); - else Debug.WriteLine($"{Globals.ScriptName}: Error parsing entity handle {args[0]} as int"); - } - }), false); - - RegisterCommand("vstancer_preset", new Action((source, args) => - { - if (CurrentPreset != null) - Debug.WriteLine(CurrentPreset.ToString()); - else - Debug.WriteLine($"{Globals.ScriptName}: Current preset doesn't exist"); - }), false); - - RegisterCommand("vstancer_print", new Action((source, args) => - { - PrintVehiclesWithDecorators(_worldVehiclesHandles); - }), false); - - if (Config.ExposeCommand) - RegisterCommand("vstancer", new Action((source, args) => { ToggleMenuVisibility?.Invoke(this, EventArgs.Empty); }), false); - - if (Config.ExposeEvent) - EventHandlers.Add("vstancer:toggleMenu", new Action(() => { ToggleMenuVisibility?.Invoke(this, EventArgs.Empty); })); - - - Exports.Add("SetVstancerPreset", new Action(SetVstancerPreset)); - Exports.Add("GetVstancerPreset", new Func(GetVstancerPreset)); - - // Create a script for the menu ... - _vstancerMenu = new VStancerMenu(this); - - _vstancerMenu.EditorMenuResetPreset += OnEditorMenuResetPresetInvoked; - _vstancerMenu.EditorMenuPresetValueChanged += OnEditorMenuPresetValueChanged; - - _vstancerMenu.PersonalPresetsMenuApplyPreset += OnPersonalPresetsMenuApplyPresetInvoked; - _vstancerMenu.PersonalPresetsMenuSavePreset += OnPersonalPresetsMenuSavePresetInvoked; - _vstancerMenu.PersonalPresetsMenuDeletePreset += OnPersonalPresetsMenuDeletePresetInvoked; - - Tick += GetPlayerVehicleTask; - Tick += UpdatePlayerVehicleTask; - Tick += UpdateWorldVehiclesTask; - Tick += UpdatePlayerVehicleDecoratorsTask; - Tick += HideUITask; - } - - private async Task HideUITask() - { - if (_vstancerMenu != null) - _vstancerMenu.HideUI = !CurrentPresetIsValid; - - await Task.FromResult(0); - } - - /// - /// Invalidates the preset - /// - private void InvalidatePreset() - { - if(CurrentPresetIsValid) - { - CurrentPreset.PresetEdited -= OnPresetEdited; - CurrentPreset = null; - - _playerVehicleHandle = -1; - - Tick -= UpdatePlayerVehicleTask; - } - } - - /// - /// Invoked when a value of changes - /// - /// - /// - private void OnPresetEdited(object sender, EventArgs eventArgs) - { - if (!CurrentPresetIsValid) - return; - - // If false then this has been invoked by after a reset - if(!CurrentPreset.IsEdited) - RemoveDecoratorsFromVehicle(_playerVehicleHandle); - - // Force one single refresh to update rendering at correct position after reset - // This is required because otherwise the vehicle won't update immediately as - UpdateVehicleUsingPreset(_playerVehicleHandle, CurrentPreset); - } - - /// - /// Updates the and the - /// - /// - private async Task GetPlayerVehicleTask() - { - await Task.FromResult(0); - - _playerPedHandle = PlayerPedId(); - - if (!IsPedInAnyVehicle(_playerPedHandle, false)) - { - InvalidatePreset(); - return; - } - - int vehicle = GetVehiclePedIsIn(_playerPedHandle, false); - - // If this model isn't a car, or player isn't the driver, or vehicle is not driveable - if (!IsThisModelACar((uint)GetEntityModel(vehicle)) || GetPedInVehicleSeat(vehicle, -1) != _playerPedHandle || !IsVehicleDriveable(vehicle, false)) - { - InvalidatePreset(); - return; - } - - // Update current vehicle and get its preset - if (vehicle != _playerVehicleHandle) - { - InvalidatePreset(); - - CurrentPreset = CreatePresetFromHandle(vehicle); - CurrentPreset.PresetEdited += OnPresetEdited; - - _playerVehicleHandle = vehicle; - NewPresetCreated?.Invoke(this, EventArgs.Empty); - Tick += UpdatePlayerVehicleTask; - } - } - - /// - /// The task that updates the current vehicle - /// - /// - private async Task UpdatePlayerVehicleTask() - { - // Check if current vehicle needs to be refreshed - if (CurrentPresetIsValid && CurrentPreset.IsEdited) - UpdateVehicleUsingPreset(_playerVehicleHandle, CurrentPreset); - - await Task.FromResult(0); - } - - /// - /// The task that updates the vehicles of the world - /// - /// - private async Task UpdateWorldVehiclesTask() - { - // Refreshes the iterated vehicles - IEnumerable vehiclesList = _worldVehiclesHandles.Except(new List { _playerVehicleHandle }); - Vector3 currentCoords = GetEntityCoords(_playerPedHandle, true); - - foreach (int entity in vehiclesList) - { - if (DoesEntityExist(entity)) - { - Vector3 coords = GetEntityCoords(entity, true); - - if (Vector3.Distance(currentCoords, coords) <= Config.ScriptRange) - UpdateVehicleUsingDecorators(entity); - } - } - - await Task.FromResult(0); - } - - /// - /// The task that updates the script decorators attached on the current vehicle - /// - /// - private async Task UpdatePlayerVehicleDecoratorsTask() - { - long currentTime = (GetGameTimer() - _lastTime); - - // Check if decorators needs to be updated - if (currentTime > Config.Timer) - { - if (CurrentPresetIsValid) - UpdateVehicleDecorators(_playerVehicleHandle, CurrentPreset); - - // Also update world vehicles list - _worldVehiclesHandles = new VehicleEnumerable(); - - _lastTime = GetGameTimer(); - } - - await Task.FromResult(0); - } - - /// - /// Registers the decorators for this script - /// - private void RegisterRequiredDecorators() - { - DecorRegister(FrontOffsetID, 1); - DecorRegister(FrontRotationID, 1); - DecorRegister(RearOffsetID, 1); - DecorRegister(RearRotationID, 1); - - DecorRegister(DefaultFrontOffsetID, 1); - DecorRegister(DefaultFrontRotationID, 1); - DecorRegister(DefaultRearOffsetID, 1); - DecorRegister(DefaultRearRotationID, 1); - } - - /// - /// Removes the decorators from the - /// - /// The handle of the entity - private void RemoveDecoratorsFromVehicle(int vehicle) - { - if (DecorExistOn(vehicle, FrontOffsetID)) - DecorRemove(vehicle, FrontOffsetID); - - if (DecorExistOn(vehicle, FrontRotationID)) - DecorRemove(vehicle, FrontRotationID); - - if (DecorExistOn(vehicle, DefaultFrontOffsetID)) - DecorRemove(vehicle, DefaultFrontOffsetID); - - if (DecorExistOn(vehicle, DefaultFrontRotationID)) - DecorRemove(vehicle, DefaultFrontRotationID); - - if (DecorExistOn(vehicle, RearOffsetID)) - DecorRemove(vehicle, RearOffsetID); - - if (DecorExistOn(vehicle, RearRotationID)) - DecorRemove(vehicle, RearRotationID); - - if (DecorExistOn(vehicle, DefaultRearOffsetID)) - DecorRemove(vehicle, DefaultRearOffsetID); - - if (DecorExistOn(vehicle, DefaultRearRotationID)) - DecorRemove(vehicle, DefaultRearRotationID); - } - - /// - /// Returns the preset as an array of floats containing in order: - /// frontOffset, frontRotation, rearOffset, rearRotation, defaultFrontOffset, defaultFrontRotation, defaultRearOffset, defaultRearRotation - /// - /// The handle of the entity - /// The float array - public float[] GetVstancerPreset(int vehicle) - { - VStancerPreset preset = (vehicle == _playerVehicleHandle && CurrentPresetIsValid) ? CurrentPreset : CreatePresetFromHandle(vehicle); - return preset?.ToArray(); - } - - /// - /// Loads a Vstancer preset for the with the specified values. - /// - /// The handle of the entity - /// The front offset value - /// The front rotation value - /// The rear offset value - /// The rear rotation value - /// The default front offset value - /// The default front rotation value - /// The default rear offset value - /// The default rear rotation value - public void SetVstancerPreset(int vehicle, float frontOffset, float frontRotation, float rearOffset, float rearRotation, object defaultFrontOffset = null, object defaultFrontRotation = null, object defaultRearOffset = null, object defaultRearRotation = null) - { - if (Config.Debug) - Debug.WriteLine($"{Globals.ScriptName}: SetVstancerPreset parameters {frontOffset} {frontRotation} {rearOffset} {rearRotation} {defaultFrontOffset} {defaultFrontRotation} {defaultRearOffset} {defaultRearRotation}"); - - if (!DoesEntityExist(vehicle)) - return; - - int wheelsCount = GetVehicleNumberOfWheels(vehicle); - int frontCount = VStancerPreset.CalculateFrontWheelsCount(wheelsCount); - - float off_f_def = defaultFrontOffset is float - ? (float)defaultFrontOffset - : DecorExistOn(vehicle, DefaultFrontOffsetID) - ? DecorGetFloat(vehicle, DefaultFrontOffsetID) - : GetVehicleWheelXOffset(vehicle, 0); - - float rot_f_def = defaultFrontRotation is float - ? (float)defaultFrontRotation - : DecorExistOn(vehicle, DefaultFrontRotationID) - ? DecorGetFloat(vehicle, DefaultFrontRotationID) - : GetVehicleWheelYRotation(vehicle, 0); - - float off_r_def = defaultRearOffset is float - ? (float)defaultRearOffset - : DecorExistOn(vehicle, DefaultRearOffsetID) - ? DecorGetFloat(vehicle, DefaultRearOffsetID) - : GetVehicleWheelXOffset(vehicle, frontCount); - - float rot_r_def = defaultRearRotation is float - ? (float)defaultRearRotation - : DecorExistOn(vehicle, DefaultRearRotationID) - ? DecorGetFloat(vehicle, DefaultRearRotationID) - : GetVehicleWheelYRotation(vehicle, frontCount); - - if (vehicle == _playerVehicleHandle) - { - CurrentPreset = new VStancerPreset(wheelsCount, frontOffset, frontRotation, rearOffset, rearRotation, off_f_def, rot_f_def, off_r_def, rot_r_def); - CurrentPreset.PresetEdited += OnPresetEdited; - - NewPresetCreated?.Invoke(this, EventArgs.Empty); - } - else - { - UpdateFloatDecorator(vehicle, DefaultFrontOffsetID, off_f_def, frontOffset); - UpdateFloatDecorator(vehicle, DefaultFrontRotationID, rot_f_def, frontRotation); - UpdateFloatDecorator(vehicle, DefaultRearOffsetID, off_r_def, rearOffset); - UpdateFloatDecorator(vehicle, DefaultRearRotationID, rot_r_def, rearRotation); - - UpdateFloatDecorator(vehicle, FrontOffsetID, frontOffset, off_f_def); - UpdateFloatDecorator(vehicle, FrontRotationID, frontRotation, rot_f_def); - UpdateFloatDecorator(vehicle, RearOffsetID, rearOffset, off_r_def); - UpdateFloatDecorator(vehicle, RearRotationID, rearRotation, rot_r_def); - } - } - - /// - /// It checks if the has a decorator named and updates its value with , otherwise if isn't equal to it adds the decorator - /// - /// - /// - /// - /// - private void UpdateFloatDecorator(int vehicle, string name, float currentValue, float defaultValue) - { - // Decorator exists but needs to be updated - if (DecorExistOn(vehicle, name)) - { - float decorValue = DecorGetFloat(vehicle, name); - if (!MathUtil.WithinEpsilon(currentValue, decorValue, Epsilon)) - { - DecorSetFloat(vehicle, name, currentValue); - if (Config.Debug) - Debug.WriteLine($"{Globals.ScriptName}: Updated decorator {name} from {decorValue} to {currentValue} on vehicle {vehicle}"); - } - } - else // Decorator doesn't exist, create it if required - { - if (!MathUtil.WithinEpsilon(currentValue, defaultValue, Epsilon)) - { - DecorSetFloat(vehicle, name, currentValue); - if (Config.Debug) - Debug.WriteLine($"{Globals.ScriptName}: Added decorator {name} with value {currentValue} to vehicle {vehicle}"); - } - } - } - - /// - /// Updates the decorators on the with updated values from the - /// - /// The handle of the entity - /// The preset for this vehicle - private void UpdateVehicleDecorators(int vehicle, VStancerPreset preset) - { - int frontCount = preset.FrontWheelsCount; - - UpdateFloatDecorator(vehicle, DefaultFrontOffsetID, preset.DefaultFrontPositionX, preset.FrontPositionX); - UpdateFloatDecorator(vehicle, DefaultFrontRotationID, preset.DefaultFrontRotationY, preset.FrontRotationY); - UpdateFloatDecorator(vehicle, DefaultRearOffsetID, preset.DefaultRearPositionX, preset.RearPositionX); - UpdateFloatDecorator(vehicle, DefaultRearRotationID, preset.DefaultRearRotationY, preset.RearRotationY); - - UpdateFloatDecorator(vehicle, FrontOffsetID, preset.FrontPositionX, preset.DefaultFrontPositionX); - UpdateFloatDecorator(vehicle, FrontRotationID, preset.FrontRotationY, preset.DefaultFrontRotationY); - UpdateFloatDecorator(vehicle, RearOffsetID, preset.RearPositionX, preset.DefaultRearPositionX); - UpdateFloatDecorator(vehicle, RearRotationID, preset.RearRotationY, preset.DefaultRearRotationY); - } - - /// - /// Creates a preset for the to edit it locally - /// - /// The handle of the entity - /// - private VStancerPreset CreatePresetFromHandle(int vehicle) - { - if (!DoesEntityExist(vehicle)) - return null; - - if (Config.Debug && IsVehicleDamaged(vehicle)) - Screen.ShowNotification($"~o~Warning~w~: You are creating a vstancer preset for a damaged vehicle, default position and rotation of the wheels might be wrong"); - - int wheelsCount = GetVehicleNumberOfWheels(vehicle); - int frontCount = VStancerPreset.CalculateFrontWheelsCount(wheelsCount); - - // Get default values first - float off_f_def = DecorExistOn(vehicle, DefaultFrontOffsetID) ? DecorGetFloat(vehicle, DefaultFrontOffsetID) : GetVehicleWheelXOffset(vehicle, 0); - float rot_f_def = DecorExistOn(vehicle, DefaultFrontRotationID) ? DecorGetFloat(vehicle, DefaultFrontRotationID) : GetVehicleWheelYRotation(vehicle, 0); - float off_r_def = DecorExistOn(vehicle, DefaultRearOffsetID) ? DecorGetFloat(vehicle, DefaultRearOffsetID) : GetVehicleWheelXOffset(vehicle, frontCount); - float rot_r_def = DecorExistOn(vehicle, DefaultRearRotationID) ? DecorGetFloat(vehicle, DefaultRearRotationID) : GetVehicleWheelYRotation(vehicle, frontCount); - - float off_f = DecorExistOn(vehicle, FrontOffsetID) ? DecorGetFloat(vehicle, FrontOffsetID) : off_f_def; - float rot_f = DecorExistOn(vehicle, FrontRotationID) ? DecorGetFloat(vehicle, FrontRotationID) : rot_f_def; - float off_r = DecorExistOn(vehicle, RearOffsetID) ? DecorGetFloat(vehicle, RearOffsetID) : off_r_def; - float rot_r = DecorExistOn(vehicle, RearRotationID) ? DecorGetFloat(vehicle, RearRotationID) : rot_r_def; - - return new VStancerPreset(wheelsCount, off_f, rot_f, off_r, rot_r, off_f_def, rot_f_def, off_r_def, rot_r_def); - } - - /// - /// Refreshes the with values from the - /// - private void UpdateVehicleUsingPreset(int vehicle, VStancerPreset preset) - { - if (!DoesEntityExist(vehicle) || preset == null) - return; - - int wheelsCount = preset.WheelsCount; - for (int index = 0; index < wheelsCount; index++) - { - // TODO: Avoid exposing preset nodes - SetVehicleWheelXOffset(vehicle, index, preset.Nodes[index].PositionX); - SetVehicleWheelYRotation(vehicle, index, preset.Nodes[index].RotationY); - } - } - - /// - /// Refreshes the with values from its decorators (if exist) - /// - /// The handle of the entity - private void UpdateVehicleUsingDecorators(int vehicle) - { - int wheelsCount = GetVehicleNumberOfWheels(vehicle); - int frontCount = VStancerPreset.CalculateFrontWheelsCount(wheelsCount); - - if (DecorExistOn(vehicle, FrontOffsetID)) - { - float value = DecorGetFloat(vehicle, FrontOffsetID); - - for (int index = 0; index < frontCount; index++) - { - if (index % 2 == 0) - SetVehicleWheelXOffset(vehicle, index, value); - else - SetVehicleWheelXOffset(vehicle, index, -value); - } - } - - if (DecorExistOn(vehicle, FrontRotationID)) - { - float value = DecorGetFloat(vehicle, FrontRotationID); - - for (int index = 0; index < frontCount; index++) - { - if (index % 2 == 0) - SetVehicleWheelYRotation(vehicle, index, value); - else - SetVehicleWheelYRotation(vehicle, index, -value); - } - } - - if (DecorExistOn(vehicle, RearOffsetID)) - { - float value = DecorGetFloat(vehicle, RearOffsetID); - - for (int index = frontCount; index < wheelsCount; index++) - { - if (index % 2 == 0) - SetVehicleWheelXOffset(vehicle, index, value); - else - SetVehicleWheelXOffset(vehicle, index, -value); - } - } - - if (DecorExistOn(vehicle, RearRotationID)) - { - float value = DecorGetFloat(vehicle, RearRotationID); - - for (int index = frontCount; index < wheelsCount; index++) - { - if (index % 2 == 0) - SetVehicleWheelYRotation(vehicle, index, value); - else - SetVehicleWheelYRotation(vehicle, index, -value); - } - } - } - - /// - /// Prints the values of the decorators used on the - /// - /// The handle of the entity - private void PrintDecoratorsInfo(int vehicle) - { - if (!DoesEntityExist(vehicle)) - { - Debug.WriteLine($"{Globals.ScriptName}: Can't find vehicle with handle {vehicle}"); - return; - } - - int wheelsCount = GetVehicleNumberOfWheels(vehicle); - int netID = NetworkGetNetworkIdFromEntity(vehicle); - StringBuilder s = new StringBuilder(); - s.AppendLine($"{Globals.ScriptName}: Vehicle:{vehicle} netID:{netID} wheelsCount:{wheelsCount}"); - - if (DecorExistOn(vehicle, FrontOffsetID)) - { - float value = DecorGetFloat(vehicle, FrontOffsetID); - s.AppendLine($"{FrontOffsetID}: {value}"); - } - - if (DecorExistOn(vehicle, FrontRotationID)) - { - float value = DecorGetFloat(vehicle, FrontRotationID); - s.AppendLine($"{FrontRotationID}: {value}"); - } - - if (DecorExistOn(vehicle, RearOffsetID)) - { - float value = DecorGetFloat(vehicle, RearOffsetID); - s.AppendLine($"{RearOffsetID}: {value}"); - } - - if (DecorExistOn(vehicle, RearRotationID)) - { - float value = DecorGetFloat(vehicle, RearRotationID); - s.AppendLine($"{RearRotationID}: {value}"); - } - - Debug.WriteLine(s.ToString()); - } - - /// - /// Prints the list of vehicles using any vstancer decorator. - /// - /// The list of the vehicles' handles - private void PrintVehiclesWithDecorators(IEnumerable vehiclesList) - { - IEnumerable entities = vehiclesList.Where(entity => EntityHasDecorators(entity)); - - Debug.WriteLine($"{Globals.ScriptName}: Vehicles with decorators: {entities.Count()}"); - - foreach (int item in entities) - Debug.WriteLine($"Vehicle: {item}"); - } - - /// - /// Returns true if the has any vstancer decorator - /// - /// The handle of the entity - /// - private bool EntityHasDecorators(int entity) - { - return ( - DecorExistOn(entity, FrontOffsetID) || - DecorExistOn(entity, FrontRotationID) || - DecorExistOn(entity, RearOffsetID) || - DecorExistOn(entity, RearRotationID) || - DecorExistOn(entity, DefaultFrontOffsetID) || - DecorExistOn(entity, DefaultFrontRotationID) || - DecorExistOn(entity, DefaultRearOffsetID) || - DecorExistOn(entity, DefaultRearRotationID) - ); - } - - /// - /// Loads the config file containing all the customizable properties - /// - /// The name of the file - private VStancerConfig LoadConfig(string filename = "config.json") - { - VStancerConfig config; - - try - { - string strings = LoadResourceFile(Globals.ResourceName, filename); - config = JsonConvert.DeserializeObject(strings); - - Debug.WriteLine($"{Globals.ScriptName}: Loaded config from {filename}"); - } - catch (Exception e) - { - Debug.WriteLine($"{Globals.ScriptName}: Impossible to load {filename}", e.Message); - Debug.WriteLine(e.StackTrace); - - config = new VStancerConfig(); - } - - return config; - } - - private async void OnPersonalPresetsMenuApplyPresetInvoked(object sender, string presetKey) - { - var loadedPreset = LocalPresetsManager.Load(presetKey); - - if (loadedPreset != null) - { - // Assign new preset - CurrentPreset.CopyFrom(loadedPreset); - - // Force refresh - UpdateVehicleUsingPreset(_playerVehicleHandle, CurrentPreset); - - Screen.ShowNotification($"Personal preset ~b~{presetKey}~w~ applied"); - - await Delay(200); - NewPresetCreated?.Invoke(this, EventArgs.Empty); - } - else - Screen.ShowNotification($"~r~ERROR~w~ Personal preset ~b~{presetKey}~w~ corrupted"); - - await Task.FromResult(0); - } - - private void OnPersonalPresetsMenuSavePresetInvoked(object sender, string presetName) - { - if (LocalPresetsManager.Save(presetName, CurrentPreset)) - { - Screen.ShowNotification($"Personal preset ~g~{presetName}~w~ saved"); - } - else - Screen.ShowNotification($"~r~ERROR~w~ The name {presetName} is invalid or already used."); - } - - private void OnPersonalPresetsMenuDeletePresetInvoked(object sender, string presetKey) - { - if (LocalPresetsManager.Delete(presetKey)) - { - Screen.ShowNotification($"Personal preset ~r~{presetKey}~w~ deleted"); - } - else - Screen.ShowNotification($"~r~ERROR~w~ No preset found with {presetKey} key."); - } - - /// - /// Invoked when the reset button is pressed in the UI - /// - private async void OnEditorMenuResetPresetInvoked(object sender, EventArgs eventArgs) - { - if (!CurrentPresetIsValid) - return; - - CurrentPreset.Reset(); - - await Delay(200); - - // Used to updated the UI - // TODO: Maybe change this - NewPresetCreated?.Invoke(this, EventArgs.Empty); - } - - /// - /// Invoked when a value is changed in the UI - /// - /// The id of the property - /// The value of the property - private void OnEditorMenuPresetValueChanged(string id, string newValue) - { - if (!CurrentPresetIsValid) - return; - - if (!float.TryParse(newValue, out float value)) - return; - - switch (id) - { - case FrontRotationID: - CurrentPreset.FrontRotationY = value; - break; - case RearRotationID: - CurrentPreset.RearRotationY = value; - break; - case FrontOffsetID: - CurrentPreset.FrontPositionX = -value; - break; - case RearOffsetID: - CurrentPreset.RearPositionX = -value; - break; - default: - break; - } - } - - /// - /// Get a string from the user using the on screen keyboard - /// - /// The default value to display - /// - public async Task GetOnScreenString(string title, string defaultText) - { - //var currentMenu = MenuController.GetCurrentMenu(); - //currentMenu.Visible = false; - //MenuController.DisableMenuButtons = true; - - //DisableAllControlActions(1); - - DisplayOnscreenKeyboard(1, title, "", defaultText, "", "", "", 128); - while (UpdateOnscreenKeyboard() != 1 && UpdateOnscreenKeyboard() != 2) await Delay(100); - - //EnableAllControlActions(1); - - //MenuController.DisableMenuButtons = false; - //currentMenu.Visible = true; - - return GetOnscreenKeyboardResult(); - } - } -} diff --git a/VStancer.Client/VStancerMenu.cs b/VStancer.Client/VStancerMenu.cs deleted file mode 100644 index f71663c..0000000 --- a/VStancer.Client/VStancerMenu.cs +++ /dev/null @@ -1,286 +0,0 @@ -using System; -using System.Threading.Tasks; -using MenuAPI; -using CitizenFX.Core; -using CitizenFX.Core.UI; -using static CitizenFX.Core.Native.API; - -namespace VStancer.Client -{ - internal class VStancerMenu - { - /// - /// The script which owns this menu - /// - private readonly VStancerEditor _vstancerEditor; - - /// - /// The controller of the menu - /// - private MenuController _menuController; - - /// - /// The editor menu - /// - private Menu _editorMenu; - - /// - /// The local presets menu - /// - private Menu _personalPresetsMenu; - - /// - /// Invoked when a property has its value changed in the UI - /// - /// The id of the property - /// The new value of the property - public delegate void MenuPresetValueChangedEvent(string id, string value); - - /// - /// Invoked when a property has its value changed in the UI - /// - public event MenuPresetValueChangedEvent EditorMenuPresetValueChanged; - - /// - /// Invoked when the reset button is pressed in the UI - /// - public event EventHandler EditorMenuResetPreset; - - /// - /// Invoked when the button to apply a personal preset is pressed - /// - public event EventHandler PersonalPresetsMenuApplyPreset; - - /// - /// Invoked when the button to save a personal preset is pressed - /// - public event EventHandler PersonalPresetsMenuSavePreset; - - /// - /// Invoked when the button to delete a personal preset is pressed - /// - public event EventHandler PersonalPresetsMenuDeletePreset; - - private string ResetID => VStancerEditor.ResetID; - private string FrontOffsetID => VStancerEditor.FrontOffsetID; - private string FrontRotationID => VStancerEditor.FrontRotationID; - private string RearOffsetID => VStancerEditor.RearOffsetID; - private string RearRotationID => VStancerEditor.RearRotationID; - private VStancerPreset CurrentPreset => _vstancerEditor.CurrentPreset; - private float FloatStep => _vstancerEditor.Config.FloatStep; - - public bool HideUI - { - get => MenuController.DontOpenAnyMenu; - set - { - MenuController.DontOpenAnyMenu = value; - } - } - - /// - /// Create a method to determine the logic for when the left/right arrow are pressed - /// - /// The name of the item - /// The current value - /// The min allowed value - /// The max allowed value - /// The - private MenuDynamicListItem.ChangeItemCallback FloatChangeCallback(string name, float value, float minimum, float maximum) - { - string callback(MenuDynamicListItem sender, bool left) - { - var min = minimum; - var max = maximum; - - var newvalue = value; - - if (left) - newvalue -= FloatStep; - else if (!left) - newvalue += FloatStep; - else return value.ToString("F3"); - - // Hotfix to trim the value to 3 digits - newvalue = float.Parse((newvalue).ToString("F3")); - - if (newvalue < min) - Screen.ShowNotification($"~o~Warning~w~: Min ~b~{name}~w~ value allowed is {min} for this vehicle"); - else if (newvalue > max) - Screen.ShowNotification($"~o~Warning~w~: Max ~b~{name}~w~ value allowed is {max} for this vehicle"); - else - { - value = newvalue; - } - return value.ToString("F3"); - }; - return callback; - } - - /// - /// Creates a controller for the a float property - /// - /// The menu to add the controller to - /// The displayed name of the controller - /// The default value of the controller - /// The current value of the controller - /// The max delta allowed relative to the default value - /// The ID of the property linked to the controller - /// - private MenuDynamicListItem AddDynamicFloatList(Menu menu, string name, float defaultValue, float value, float maxEditing, string id) - { - float min = defaultValue - maxEditing; - float max = defaultValue + maxEditing; - - var callback = FloatChangeCallback(name, value, min, max); - - var newitem = new MenuDynamicListItem(name, value.ToString("F3"), callback) { ItemData = id }; - menu.AddMenuItem(newitem); - return newitem; - } - - /// - /// Setup the menu - /// - private void InitializeMenu() - { - if (_editorMenu == null) - { - _editorMenu = new Menu(Globals.ScriptName, "Editor"); - - // When the value of a MenuDynamicListItem is changed - _editorMenu.OnDynamicListItemCurrentItemChange += (menu, dynamicListItem, oldValue, newValue) => - { - string id = dynamicListItem.ItemData as string; - EditorMenuPresetValueChanged?.Invoke(id, newValue); - }; - - // When a MenuItem is selected - _editorMenu.OnItemSelect += (menu, menuItem, itemIndex) => - { - // If the selected item is the reset button - if (menuItem.ItemData as string == ResetID) - EditorMenuResetPreset.Invoke(this, EventArgs.Empty); - }; - } - - if (_personalPresetsMenu == null) - { - _personalPresetsMenu = new Menu(Globals.ScriptName, "Personal Presets"); - - _personalPresetsMenu.OnItemSelect += PersonalPresetsMenu_OnItemSelect; - - #region Save/Delete Handler - - _personalPresetsMenu.InstructionalButtons.Add(Control.PhoneExtraOption, GetLabelText("ITEM_SAVE")); - _personalPresetsMenu.InstructionalButtons.Add(Control.PhoneOption, GetLabelText("ITEM_DEL")); - - // Disable Controls binded on the same key - _personalPresetsMenu.ButtonPressHandlers.Add(new Menu.ButtonPressHandler(Control.SelectWeapon, Menu.ControlPressCheckType.JUST_PRESSED, new Action((sender, control) => { }), true)); - _personalPresetsMenu.ButtonPressHandlers.Add(new Menu.ButtonPressHandler(Control.VehicleExit, Menu.ControlPressCheckType.JUST_PRESSED, new Action((sender, control) => { }), true)); - - _personalPresetsMenu.ButtonPressHandlers.Add(new Menu.ButtonPressHandler(Control.PhoneExtraOption, Menu.ControlPressCheckType.JUST_PRESSED, new Action(async (sender, control) => - { - string presetName = await _vstancerEditor.GetOnScreenString("VSTANCER_ENTER_PRESET_NAME",""); - PersonalPresetsMenuSavePreset?.Invoke(_personalPresetsMenu, presetName.Trim()); - }), true)); - _personalPresetsMenu.ButtonPressHandlers.Add(new Menu.ButtonPressHandler(Control.PhoneOption, Menu.ControlPressCheckType.JUST_PRESSED, new Action((sender, control) => - { - if (_personalPresetsMenu.GetMenuItems().Count > 0) - { - string presetName = _personalPresetsMenu.GetMenuItems()[_personalPresetsMenu.CurrentIndex].Text; - PersonalPresetsMenuDeletePreset?.Invoke(_personalPresetsMenu, presetName); - } - }), true)); - - #endregion - } - - UpdatePersonalPresetsMenu(); - UpdateEditorMenu(); - - if (_menuController == null) - { - _menuController = new MenuController(); - MenuController.AddMenu(_editorMenu); - MenuController.AddSubmenu(_editorMenu, _personalPresetsMenu); - MenuController.MenuAlignment = MenuController.MenuAlignmentOption.Right; - MenuController.MenuToggleKey = (Control)_vstancerEditor.Config.ToggleMenuControl; - MenuController.EnableMenuToggleKeyOnController = false; - MenuController.DontOpenAnyMenu = true; - MenuController.MainMenu = _editorMenu; - } - } - - private void PersonalPresetsMenu_OnItemSelect(Menu menu, MenuItem menuItem, int itemIndex) => PersonalPresetsMenuApplyPreset?.Invoke(menu, menuItem.Text); - - /// - /// Rebuild the personal presets menu - /// - private void UpdatePersonalPresetsMenu() - { - if (_personalPresetsMenu == null) - return; - - _personalPresetsMenu.ClearMenuItems(); - - foreach (var key in _vstancerEditor.LocalPresetsManager.GetKeys()) - { - _personalPresetsMenu.AddMenuItem(new MenuItem(key.Remove(0, Globals.KvpPrefix.Length)) { ItemData = key }); - } - } - - /// - /// Update the items of the main menu - /// - private void UpdateEditorMenu() - { - if (_editorMenu == null) - return; - - _editorMenu.ClearMenuItems(); - - if (!_vstancerEditor.CurrentPresetIsValid) - return; - - AddDynamicFloatList(_editorMenu, "Front Track Width", -CurrentPreset.DefaultFrontPositionX, -CurrentPreset.FrontPositionX, _vstancerEditor.Config.FrontLimits.PositionX, FrontOffsetID); - AddDynamicFloatList(_editorMenu, "Rear Track Width", -CurrentPreset.DefaultRearPositionX, -CurrentPreset.RearPositionX, _vstancerEditor.Config.RearLimits.PositionX, RearOffsetID); - AddDynamicFloatList(_editorMenu, "Front Camber", CurrentPreset.DefaultFrontRotationY, CurrentPreset.FrontRotationY, _vstancerEditor.Config.FrontLimits.RotationY, FrontRotationID); - AddDynamicFloatList(_editorMenu, "Rear Camber", CurrentPreset.DefaultRearRotationY, CurrentPreset.RearRotationY, _vstancerEditor.Config.RearLimits.RotationY, RearRotationID); - _editorMenu.AddMenuItem(new MenuItem("Reset", "Restores the default values") { ItemData = ResetID }); - - // Create personal presets button and bind it to the submenu - var personalPresetsItem = new MenuItem("Personal Presets", "The vstancer presets saved by you.") - { - Label = "→→→" - }; - _editorMenu.AddMenuItem(personalPresetsItem); - MenuController.BindMenuItem(_editorMenu, _personalPresetsMenu, personalPresetsItem); - } - - /// - /// Constructor with dependency injection - /// - /// The script which owns this menu - internal VStancerMenu(VStancerEditor script) - { - _vstancerEditor = script; - _vstancerEditor.NewPresetCreated += new EventHandler((sender,args) => UpdateEditorMenu()); - _vstancerEditor.ToggleMenuVisibility += new EventHandler((sender,args) => - { - //var currentMenu = MenuController.GetCurrentMenu(); - var currentMenu = MenuController.MainMenu; - - if (currentMenu == null) - return; - - currentMenu.Visible = !currentMenu.Visible; - }); - - AddTextEntry("VSTANCER_ENTER_PRESET_NAME", "Enter a name for the preset"); - InitializeMenu(); - - _vstancerEditor.LocalPresetsManager.PresetsListChanged += new EventHandler((sender, args) => UpdatePersonalPresetsMenu()); - } - } -} diff --git a/VStancer.Client/VStancerPreset.cs b/VStancer.Client/VStancerPreset.cs deleted file mode 100644 index 97c6484..0000000 --- a/VStancer.Client/VStancerPreset.cs +++ /dev/null @@ -1,242 +0,0 @@ -using CitizenFX.Core; -using System; -using System.Text; - -namespace VStancer.Client -{ - public class VStancerPreset : IEquatable - { - private const float Epsilon = 0.001f; - - public event EventHandler PresetEdited; - - public int WheelsCount { get; set; } - public int FrontWheelsCount { get; set; } - - - public VStancerNode[] Nodes { get; set; } - public VStancerNode[] DefaultNodes { get; private set; } - - public float FrontPositionX - { - get => Nodes[0].PositionX; - set - { - for (int index = 0; index < FrontWheelsCount; index++) - Nodes[index].PositionX = (index % 2 == 0) ? value : -value; - - PresetEdited?.Invoke(this, EventArgs.Empty); - } - } - - public float RearPositionX - { - get => Nodes[FrontWheelsCount].PositionX; - set - { - for (int index = FrontWheelsCount; index < WheelsCount; index++) - Nodes[index].PositionX = (index % 2 == 0) ? value : -value; - - PresetEdited?.Invoke(this, EventArgs.Empty); - } - } - - public float FrontRotationY - { - get => Nodes[0].RotationY; - set - { - for (int index = 0; index < FrontWheelsCount; index++) - Nodes[index].RotationY = (index % 2 == 0) ? value : -value; - - PresetEdited?.Invoke(this, EventArgs.Empty); - } - } - - public float RearRotationY - { - get => Nodes[FrontWheelsCount].RotationY; - set - { - for (int index = FrontWheelsCount; index < WheelsCount; index++) - Nodes[index].RotationY = (index % 2 == 0) ? value : -value; - - PresetEdited?.Invoke(this, EventArgs.Empty); - } - } - - public float DefaultFrontPositionX { get => DefaultNodes[0].PositionX; } - public float DefaultRearPositionX { get => DefaultNodes[FrontWheelsCount].PositionX; } - public float DefaultFrontRotationY { get => DefaultNodes[0].RotationY; } - public float DefaultRearRotationY { get => DefaultNodes[FrontWheelsCount].RotationY; } - - public bool IsEdited - { - get - { - for (int index = 0; index < WheelsCount; index++) - { - if (!MathUtil.WithinEpsilon(DefaultNodes[index].PositionX, Nodes[index].PositionX, Epsilon) || - !MathUtil.WithinEpsilon(DefaultNodes[index].RotationY, Nodes[index].RotationY, Epsilon)) - return true; - } - return false; - } - } - - public VStancerPreset(int count, float frontOffset, float frontRotation, float rearOffset, float rearRotation, float defaultFrontOffset, float defaultFrontRotation, float defaultRearOffset, float defaultRearRotation) - { - WheelsCount = count; - - DefaultNodes = new VStancerNode[WheelsCount]; - Nodes = new VStancerNode[WheelsCount]; - - FrontWheelsCount = CalculateFrontWheelsCount(WheelsCount); - - for (int index = 0; index < FrontWheelsCount; index++) - { - if (index % 2 == 0) - { - DefaultNodes[index].RotationY = defaultFrontRotation; - DefaultNodes[index].PositionX = defaultFrontOffset; - Nodes[index].RotationY = frontRotation; - Nodes[index].PositionX = frontOffset; - } - else - { - DefaultNodes[index].RotationY = -defaultFrontRotation; - DefaultNodes[index].PositionX = -defaultFrontOffset; - Nodes[index].RotationY = -frontRotation; - Nodes[index].PositionX = - frontOffset; - } - } - - for (int index = FrontWheelsCount; index < WheelsCount; index++) - { - if (index % 2 == 0) - { - DefaultNodes[index].RotationY = defaultRearRotation; - DefaultNodes[index].PositionX = defaultRearOffset; - Nodes[index].RotationY = rearRotation; - Nodes[index].PositionX = rearOffset; - } - else - { - DefaultNodes[index].RotationY = -defaultRearRotation; - DefaultNodes[index].PositionX = -defaultRearOffset; - Nodes[index].RotationY = -rearRotation; - Nodes[index].PositionX = -rearOffset; - } - } - } - - public void Reset() - { - for (int index = 0; index < WheelsCount; index++) - { - Nodes[index] = DefaultNodes[index]; - } - - PresetEdited?.Invoke(this, EventArgs.Empty); - } - - public bool Equals(VStancerPreset other) - { - if (WheelsCount != other.WheelsCount) - return false; - - for (int index = 0; index < WheelsCount; index++) - { - if (!MathUtil.WithinEpsilon(DefaultNodes[index].PositionX, other.DefaultNodes[index].PositionX, Epsilon) || - !MathUtil.WithinEpsilon(DefaultNodes[index].RotationY, other.DefaultNodes[index].RotationY, Epsilon) || - !MathUtil.WithinEpsilon(Nodes[index].PositionX, other.Nodes[index].PositionX, Epsilon) || - !MathUtil.WithinEpsilon(Nodes[index].RotationY, other.Nodes[index].RotationY, Epsilon)) - return false; - } - return true; - } - - public override string ToString() - { - StringBuilder s = new StringBuilder(); - s.AppendLine($"Edited:{IsEdited} Wheels count:{WheelsCount} Front count:{FrontWheelsCount}"); - - StringBuilder defOff = new StringBuilder(string.Format("{0,20}", "Default offset:")); - StringBuilder defRot = new StringBuilder(string.Format("{0,20}", "Default rotation:")); - StringBuilder curOff = new StringBuilder(string.Format("{0,20}", "Current offset:")); - StringBuilder curRot = new StringBuilder(string.Format("{0,20}", "Current rotation:")); - - for (int i = 0; i < WheelsCount; i++) - { - defOff.Append(string.Format("{0,15}", DefaultNodes[i].PositionX)); - defRot.Append(string.Format("{0,15}", DefaultNodes[i].RotationY)); - curOff.Append(string.Format("{0,15}", Nodes[i].PositionX)); - curRot.Append(string.Format("{0,15}", Nodes[i].RotationY)); - } - - s.AppendLine(curOff.ToString()); - s.AppendLine(defOff.ToString()); - s.AppendLine(curRot.ToString()); - s.AppendLine(defRot.ToString()); - - return s.ToString(); - } - - - /// - /// Calculate the number of front wheels of a vehicle, starting from the number of all the wheels - /// - /// The number of wheels of a such vehicle - /// - public static int CalculateFrontWheelsCount(int wheelsCount) - { - int _frontWheelsCount = wheelsCount / 2; - - if (_frontWheelsCount % 2 != 0) - _frontWheelsCount -= 1; - - return _frontWheelsCount; - } - - /// - /// Returns the preset as an array of floats containing in order: - /// frontOffset, frontRotation, rearOffset, rearRotation, defaultFrontOffset, defaultFrontRotation, defaultRearOffset, defaultRearRotation - /// - /// The float array - public float[] ToArray() - { - return new float[] { - Nodes[0].PositionX, - Nodes[0].RotationY, - Nodes[FrontWheelsCount].PositionX, - Nodes[FrontWheelsCount].RotationY, - DefaultNodes[0].PositionX, - DefaultNodes[0].RotationY, - DefaultNodes[FrontWheelsCount].PositionX, - DefaultNodes[FrontWheelsCount].RotationY, - }; - } - - public void CopyFrom(VStancerPreset other) - { - if (other == null) - return; - - FrontPositionX = other.FrontPositionX; - FrontRotationY = other.FrontRotationY; - RearPositionX = other.RearPositionX; - RearRotationY = other.RearRotationY; - } - } - - - // TODO: Edit Preset to use nodes structs - public struct VStancerNode - { - //public Vector3 Position { get; set; } - //public Vector3 Rotation { get; set; } - //public Vector3 Scale { get; set; } - public float PositionX { get; set; } - public float RotationY { get; set; } - } -} diff --git a/VStancer.Client/VStancerUtilities.cs b/VStancer.Client/VStancerUtilities.cs new file mode 100644 index 0000000..c0e1a2b --- /dev/null +++ b/VStancer.Client/VStancerUtilities.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; + +using CitizenFX.Core; +using static CitizenFX.Core.Native.API; + +namespace VStancer.Client +{ + public static class VStancerUtilities + { + public const float Epsilon = 0.001f; + + public static int CalculateFrontWheelsCount(int wheelsCount) + { + int _frontWheelsCount = wheelsCount / 2; + + if (_frontWheelsCount % 2 != 0) + _frontWheelsCount -= 1; + + return _frontWheelsCount; + } + + public static List GetKeyValuePairs(string prefix) + { + List pairs = new List(); + + int handle = StartFindKvp(prefix); + + if (handle != -1) + { + string kvp; + do + { + kvp = FindKvp(handle); + + if (kvp != null) + pairs.Add(kvp); + } + while (kvp != null); + EndFindKvp(handle); + } + + return pairs; + } + + public static List GetWorldVehicles() + { + List handles = new List(); + + int entity = -1; + int handle = FindFirstVehicle(ref entity); + + if (handle != -1) + { + do handles.Add(entity); + while (FindNextVehicle(handle, ref entity)); + + EndFindVehicle(handle); + } + + return handles; + } + + public static void UpdateFloatDecorator(int vehicle, string name, float currentValue, float defaultValue) + { + // Decorator exists but needs to be updated + if (DecorExistOn(vehicle, name)) + { + float decorValue = DecorGetFloat(vehicle, name); + if (!MathUtil.WithinEpsilon(currentValue, decorValue, Epsilon)) + { + DecorSetFloat(vehicle, name, currentValue); +#if DEBUG + Debug.WriteLine($"Updated decorator {name} from {decorValue} to {currentValue} on vehicle {vehicle}"); +#endif + } + } + else // Decorator doesn't exist, create it if required + { + if (!MathUtil.WithinEpsilon(currentValue, defaultValue, Epsilon)) + { + DecorSetFloat(vehicle, name, currentValue); +#if DEBUG + Debug.WriteLine($"Added decorator {name} with value {currentValue} to vehicle {vehicle}"); +#endif + } + } + } + } +} diff --git a/dist/config.json b/dist/config.json index 30eff40..fd65241 100644 --- a/dist/config.json +++ b/dist/config.json @@ -1,17 +1,28 @@ { "Debug": false, + "DisableMenu": false, "ExposeCommand": false, "ExposeEvent": false, "ScriptRange": 150.0, "Timer": 1000, "ToggleMenuControl": 167, "FloatStep": 0.01, - "FrontLimits": { - "PositionX": 0.25, - "RotationY": 0.2 + "EnableWheelMod": true, + "EnableClientPresets": true, + "WheelLimits": { + "FrontTrackWidth": 0.25, + "RearTrackWidth": 0.25, + "FrontCamber": 0.20, + "RearCamber": 0.20 }, - "RearLimits": { - "PositionX": 0.25, - "RotationY": 0.2 + "WheelModLimits": { + "WheelSize": 0.20, + "WheelWidth": 0.20, + "FrontTireColliderWidth": 0.10, + "FrontTireColliderSize": 0.10, + "FrontRimColliderSize": 0.10, + "RearTireColliderWidth": 0.10, + "RearTireColliderSize": 0.10, + "RearRimColliderSize": 0.10 } } \ No newline at end of file diff --git a/dist/fxmanifest.lua b/dist/fxmanifest.lua index b0a003b..3c2ba43 100644 --- a/dist/fxmanifest.lua +++ b/dist/fxmanifest.lua @@ -1,7 +1,13 @@ -fx_version 'adamant' +fx_version 'bodacious' games { 'gta5' } --dependency 'MenuAPI' +name 'vstancer' +author 'Neos7' +description 'A script to edit wheels of vehicles' +version 'v2.0.0' +url 'https://github.com/carmineos/fivem-vstancer' + files { --'@MenuAPI/MenuAPI.dll', 'MenuAPI.dll', @@ -13,5 +19,19 @@ client_scripts { 'VStancer.Client.net.dll' } -export 'SetVstancerPreset' -export 'GetVstancerPreset' \ No newline at end of file +exports { + "GetWheelPreset", + "ResetWheelPreset", + "GetFrontCamber", + "GetRearCamber", + "GetFrontTrackWidth", + "GetRearTrackWidth", + "SetFrontCamber", + "SetRearCamber", + "SetFrontTrackWidth", + "SetRearTrackWidth", + "SaveLocalPreset", + "LoadLocalPreset", + "DeleteLocalPreset", + "GetLocalPresetList" +} \ No newline at end of file