Skip to content

Commit 2baa164

Browse files
committed
chore(menus): Messing around with old vMenu code inside a temporary vehicle spawner class to find bugs in Enhanced.
Note this is not how the new menu will be implemented, it's just testing and messing around to get a feel for what we need to fix or report before we can implement it properly. Also update mistake in docs that was made up by a clanker. And fix another broken native.
1 parent b94d3c1 commit 2baa164

5 files changed

Lines changed: 248 additions & 52 deletions

File tree

docs/src/content/docs/getting-started.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,18 @@ vMenu Enhanced is in early alpha. Installation and configuration steps are not f
1515

1616
### Filesystem permissions
1717

18-
vMenu Enhanced writes files into its own resource folder (saved vehicles, outfits, and other
19-
persistent data). FiveM Enhanced blocks resources from writing to disk unless you explicitly
20-
grant permission, so add the following line to your `server.cfg` **before** the line that starts
21-
vMenu Enhanced:
18+
vMenu Enhanced writes files into its own resource folder (used for creating template files).
19+
FiveM Enhanced blocks resources from writing to disk unless you explicitly grant permission,
20+
so add the following line to your `server.cfg` **before** the line that starts vMenu Enhanced:
2221

2322
```cfg
2423
add_filesystem_permission vMenu.Enhanced write vMenu.Enhanced
2524
ensure vMenu.Enhanced
2625
```
2726

2827
Both names in that command are resource names: the first is the resource being granted access
29-
(vMenu Enhanced), the second is the resource whose folder it may write to (its own). If you renamed
30-
the resource folder, use your own folder name in both places.
28+
(vMenu Enhanced), the second is the resource whose folder it may write to (its own).
29+
It's important for this reason (and others) that you call the resource `vMenu.Enhanced`.
3130

3231
:::danger[Order matters]
3332
The permission must be set before the resource starts. If `ensure vMenu.Enhanced` comes first,
@@ -37,7 +36,7 @@ console.
3736

3837
## Where things stand
3938

40-
Setup instructions, configuration, and permissions documentation will land here as vMenu Enhanced becomes usable.
39+
Setup instructions, configuration, and permissions documentation will land here as vMenu Enhanced becomes usable.
4140
In the meantime, follow along on the [GitHub repository](https://github.com/TomGrobbe/vMenu/) and the [Discord](https://vespura.com/discord).
4241

4342
If you are running the current stable version for FiveM Legacy, use the [vMenu Legacy documentation](/vmenu/legacy/) instead.

src/Client/vMenu.Enhanced.BrokenNatives/NativeFixer.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
using System.Numerics;
2+
13
using CitizenFX.Base;
4+
using CitizenFX.Base.Data;
25
using CitizenFX.FiveM.Client;
36

47
namespace vMenu.Enhanced.BrokenNatives;
@@ -11,7 +14,7 @@ public static class NativeFixer
1114
internal static NativeApi nativeApi = BaseEntrypoint.NativeApi;
1215

1316
/// <summary>
14-
/// Replacement call for <see cref="Native.GetAllVehicleModels" /> because that return type is <pre>byte[]</pre>
17+
/// Replacement call for <see cref="Native.GetAllVehicleModels()" /> because that return type is <em>byte[]</em>.
1518
/// </summary>
1619
/// <returns></returns>
1720
public static string[] GetAllVehicleModels()
@@ -20,4 +23,21 @@ public static string[] GetAllVehicleModels()
2023
nativeApi.Invoke(3612546629uL, "GetAllVehicleModels");
2124
return nativeApi.GetResObject(0).DeserializeTo<string[]>();
2225
}
26+
27+
/// <summary>
28+
/// Replacement call for <see cref="Native.GetModelDimensions(uint, out Vector3, out Vector3)" /> because <em>nativeApi.PushArg(default(Vector3))</em> is not supported.
29+
/// </summary>
30+
/// <param name="p0"></param>
31+
/// <param name="p1"></param>
32+
/// <param name="p2"></param>
33+
public static void GetModelDimensions(uint p0, out Vector3 p1, out Vector3 p2)
34+
{
35+
nativeApi.ResetContext();
36+
nativeApi.PushArg(p0);
37+
nativeApi.PushArg(default(ObjectArg));
38+
nativeApi.PushArg(default(ObjectArg));
39+
nativeApi.Invoke(14500376258260264975uL, "GetModelDimensions");
40+
p1 = nativeApi.GetResVector(1).ToVector();
41+
p2 = nativeApi.GetResVector(2).ToVector();
42+
}
2343
}

src/Client/vMenu.Enhanced.Core/Main.cs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,10 @@ public async void Initialize()
3939
await API.Vehicles.RequestAndCreate(API.Hash("adder"), API.Players.Local.Position, 0, true, true, true);
4040
});
4141

42-
//foreach (var subClass in AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()))
43-
//{
44-
// if (subClass.Namespace == "vMenu.Enhanced.Configuration")
45-
// {
46-
// API.Log.Info("Found class inside vMenu.Enhanced.Configuration: {0}", subClass.Name);
47-
// }
48-
//}
49-
50-
var menu = new Menu("Test menu", "vMenu.Enhanced");
42+
var menu = new Menu("vMenu Enhanced", "Main Menu");
5143
MenuController.AddMenu(menu);
5244

53-
for (var i = 0; i < 3; i++)
54-
{
55-
menu.AddMenuItem(new MenuItem($"Menu item #{i + 1}"));
56-
}
57-
58-
API.Log.Info("Creating and requesting vehicle spawner menu now.");
59-
var vehicleSpawnerMenu = await (new VehicleSpawnerMenu()).GetMenu();
45+
await (new VehicleSpawnerMenu()).Initialize();
6046

6147
}
6248
}
Lines changed: 217 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Globalization;
2+
13
using CitizenFX.Base;
24
using CitizenFX.FiveM.Client;
35
using CitizenFX.FiveM.Client.Extensions;
@@ -13,57 +15,231 @@ namespace vMenu.Enhanced.Menus;
1315
public sealed class VehicleSpawnerMenu
1416
{
1517
internal static NativeApi nativeApi = BaseEntrypoint.NativeApi;
18+
private static readonly TextInfo _textInfo = new CultureInfo("en-US", false).TextInfo;
1619

17-
18-
public async Task<Menu> GetMenu()
20+
public async Task Initialize()
1921
{
20-
2122
var menu = new Menu("Vehicle Spawner", "Vehicle Spawner Menu");
2223
MenuController.AddSubmenu(MenuController.MainMenu, menu);
23-
var linkBtn = new MenuItem("VehicleSpawner Menu", "Temporary vehicle spawner menu.") { Label = "»»»" };
24+
var linkBtn = new MenuItem("VehicleSpawner Menu", "Temporary vehicle spawner menu.") { Label = "" };
2425
MenuController.MainMenu.AddMenuItem(linkBtn);
2526
MenuController.BindMenuItem(MenuController.MainMenu, menu, linkBtn);
2627

27-
var vehicles = BrokenNatives.NativeFixer.GetAllVehicleModels();
28-
28+
MenuItem spawnByClassBtn = new MenuItem("Spawn Vehicle By Class", "Spawn a vehicle from a list of vehicle classes.") { Label = "→" };
29+
Menu spawnByClassMenu = new Menu("Vehicle Spawner", "Spawn vehicles by class");
30+
menu.AddMenuItem(spawnByClassBtn);
31+
MenuController.AddSubmenu(menu, spawnByClassMenu);
32+
MenuController.BindMenuItem(menu, spawnByClassMenu, spawnByClassBtn);
2933

34+
var vehicles = BrokenNatives.NativeFixer.GetAllVehicleModels();
3035

3136
if (vehicles is null)
3237
{
33-
return menu;
38+
return;
3439
}
3540

36-
var vehicleSperCategory = vehicles
41+
var vehiclesPerCategory = vehicles
3742
.Where(veh => !string.IsNullOrWhiteSpace(veh))
3843
.Select(veh => veh.Trim())
3944
.OrderBy(vehicle => vehicle)
40-
.GroupBy(vehicle => Native.GetVehicleClassFromName(API.Hash(vehicle)));
45+
.GroupBy(vehicle => Native.GetVehicleClassFromName(API.Hash(vehicle)))
46+
.OrderBy(vehicle => Native.GetLabelText($"VEH_CLASS_{vehicle.Key}"));
4147

42-
foreach (var cat in vehicleSperCategory)
48+
// These are the max speed, acceleration, braking and traction values per vehicle class.
49+
var speedValues = new float[23]
50+
{
51+
44.9374657f,
52+
50.0000038f,
53+
48.862133f,
54+
48.1321335f,
55+
50.7077942f,
56+
51.3333359f,
57+
52.3922348f,
58+
53.86687f,
59+
52.03867f,
60+
49.2241631f,
61+
39.6176529f,
62+
37.5559425f,
63+
42.72843f,
64+
21.0f,
65+
45.0f,
66+
65.1952744f,
67+
109.764259f,
68+
42.72843f,
69+
56.5962219f,
70+
57.5398865f,
71+
43.3140678f,
72+
26.66667f,
73+
53.0537224f
74+
};
75+
var accelerationValues = new float[23]
76+
{
77+
0.34f,
78+
0.29f,
79+
0.335f,
80+
0.28f,
81+
0.395f,
82+
0.39f,
83+
0.66f,
84+
0.42f,
85+
0.425f,
86+
0.475f,
87+
0.21f,
88+
0.3f,
89+
0.32f,
90+
0.17f,
91+
18.0f,
92+
5.88f,
93+
21.0700016f,
94+
0.33f,
95+
14.0f,
96+
6.86f,
97+
0.32f,
98+
0.2f,
99+
0.76f
100+
};
101+
var brakingValues = new float[23]
102+
{
103+
0.72f,
104+
0.95f,
105+
0.85f,
106+
0.9f,
107+
1.0f,
108+
1.0f,
109+
1.3f,
110+
1.25f,
111+
1.52f,
112+
1.1f,
113+
0.6f,
114+
0.7f,
115+
0.8f,
116+
3.0f,
117+
0.4f,
118+
3.5920403f,
119+
20.58f,
120+
0.9f,
121+
2.93960738f,
122+
3.9472363f,
123+
0.85f,
124+
5.0f,
125+
1.3f
126+
};
127+
var tractionValues = new float[23]
43128
{
44-
var submenu = new Menu("Class: " + cat.Key.ToString(), "Vehicle Spawner Menu");
129+
2.3f,
130+
2.55f,
131+
2.3f,
132+
2.6f,
133+
2.625f,
134+
2.65f,
135+
2.8f,
136+
2.782f,
137+
2.9f,
138+
2.95f,
139+
2.0f,
140+
3.3f,
141+
2.175f,
142+
2.05f,
143+
0.0f,
144+
1.6f,
145+
2.15f,
146+
2.55f,
147+
2.57f,
148+
3.7f,
149+
2.05f,
150+
2.5f,
151+
3.2925f
152+
};
153+
154+
foreach (var cat in vehiclesPerCategory)
155+
{
156+
var className = Native.GetLabelText($"VEH_CLASS_{cat.Key}");
157+
158+
var vehicleClassSubMenu = new Menu(className, "Vehicle Spawner Menu");
45159
foreach (var vehicle in cat)
46160
{
47-
submenu.AddMenuItem(new MenuItem(vehicle));
161+
var hash = API.Hash(vehicle);
162+
var topSpeed = Map(Native.GetVehicleModelEstimatedMaxSpeed(hash), 0f, speedValues[cat.Key], 0f, 1f);
163+
var acceleration = Map(Native.GetVehicleModelAcceleration(hash), 0f, accelerationValues[cat.Key], 0f, 1f);
164+
var maxBraking = Map(Native.GetVehicleModelMaxBraking(hash), 0f, brakingValues[cat.Key], 0f, 1f);
165+
var maxTraction = Map(Native.GetVehicleModelMaxTraction(hash), 0f, tractionValues[cat.Key], 0f, 1f);
166+
167+
var vehicleDisplayName = GetVehicleDisplayName(hash);
168+
var vehicleSpawnButton = new MenuItem(vehicleDisplayName)
169+
{
170+
Label = vehicle,
171+
ItemData = new float[4] { topSpeed, acceleration, maxBraking, maxTraction }
172+
};
173+
174+
vehicleClassSubMenu.AddMenuItem(vehicleSpawnButton);
48175
}
49176

50-
submenu.OnItemSelect += Submenu_OnItemSelect;
51-
MenuController.AddSubmenu(menu, submenu);
177+
static void HandleStatsPanel(Menu openedMenu, MenuItem currentItem)
178+
{
179+
if (currentItem != null)
180+
{
181+
if (currentItem.ItemData is float[] data)
182+
{
183+
openedMenu.ShowVehicleStatsPanel = true;
184+
openedMenu.SetVehicleStats(data[0], data[1], data[2], data[3]);
185+
openedMenu.SetVehicleUpgradeStats(0f, 0f, 0f, 0f);
186+
}
187+
else
188+
{
189+
openedMenu.ShowVehicleStatsPanel = false;
190+
}
191+
}
192+
}
193+
194+
vehicleClassSubMenu.OnMenuOpen += (m) =>
195+
{
196+
HandleStatsPanel(m, m.GetCurrentMenuItem());
197+
};
198+
199+
vehicleClassSubMenu.OnIndexChange += (m, oldItem, newItem, oldIndex, newIndex) =>
200+
{
201+
HandleStatsPanel(m, newItem);
202+
};
203+
204+
vehicleClassSubMenu.OnItemSelect += Submenu_OnItemSelect;
52205

53-
var btn = new MenuItem("Veh Class: " + cat.Key.ToString());
54-
menu.AddMenuItem(btn);
206+
var btn = new MenuItem(className, $"Spawn a vehicle from the ~y~{className}~s~ class.") { Label = $"({cat.Count()}) →" };
207+
spawnByClassMenu.AddMenuItem(btn);
208+
MenuController.AddSubmenu(spawnByClassMenu, vehicleClassSubMenu);
209+
MenuController.BindMenuItem(spawnByClassMenu, vehicleClassSubMenu, btn);
210+
}
211+
}
55212

56-
MenuController.BindMenuItem(menu, submenu, btn);
57-
API.Log.Info("Added submenu for vehicle class: {0}", cat.Key.ToString());
213+
private static string GetVehicleDisplayName(uint hash)
214+
{
215+
var displayName = Native.GetDisplayNameFromVehicleModel(hash);
216+
var labelText = Native.GetLabelText(displayName);
217+
if (labelText == "NULL")
218+
{
219+
return _textInfo.ToTitleCase(displayName);
58220
}
59221

60-
return menu;
222+
return _textInfo.ToTitleCase(labelText);
223+
}
224+
225+
/// <summary>
226+
/// Maps the <paramref name="value"/> (which is a value between <paramref name="min_in"/> and <paramref name="max_in"/>) to a new value in the range of <paramref name="min_out"/> and <paramref name="max_out"/>.
227+
/// </summary>
228+
/// <param name="value">The value to map.</param>
229+
/// <param name="min_in">The minimum range value of the value.</param>
230+
/// <param name="max_in">The max range value of the value.</param>
231+
/// <param name="min_out">The min output range value.</param>
232+
/// <param name="max_out">The max output range value.</param>
233+
/// <returns></returns>
234+
public static float Map(float value, float min_in, float max_in, float min_out, float max_out)
235+
{
236+
return ((value - min_in) * (max_out - min_out) / (max_in - min_in)) + min_out;
61237
}
62238

63239

64240
private async void Submenu_OnItemSelect(Menu menu, MenuItem menuItem, int itemIndex)
65241
{
66-
var hash = API.Hash(menuItem.Text);
242+
var hash = API.Hash(menuItem.Label);
67243

68244
// Manually checking and requesting model because API.Vehicles.RequestAndCreate uses datetime which is currently broken and crashes the game.
69245
// https://github.com/citizenfx/rfc/discussions/328
@@ -79,10 +255,28 @@ private async void Submenu_OnItemSelect(Menu menu, MenuItem menuItem, int itemIn
79255
await API.Delay(0);
80256
}
81257

82-
var veh = await API.Vehicles.RequestAndCreate(hash, API.Players.Local.Ped!.Position, (int)API.Players.Local.Ped.Heading, true, true, true);
83-
if (veh is not null)
258+
var ped = API.Players.Local.Ped!;
259+
260+
var position = ped.Position;
261+
if (ped.IsPedInAnyVehicle())
84262
{
85-
API.Players.Local.Ped.SetPedIntoVehicle(veh!.Handle, -1);
263+
var currentVehicle = ped.Vehicle!;
264+
265+
BrokenNatives.NativeFixer.GetModelDimensions(currentVehicle.Model, out var p1, out var p2);
266+
BrokenNatives.NativeFixer.GetModelDimensions(hash, out var p3, out var p4);
267+
268+
var yOffset = (Math.Abs((p1 - p2).Y) / 2) + (Math.Abs((p3 - p4).Y) / 2) + 1f;
269+
position = Native.GetOffsetFromEntityInWorldCoords(currentVehicle.Handle, 0f, yOffset, 0f);
86270
}
271+
var newVehicle = await API.Vehicles.RequestAndCreate(hash, position, (int)ped.Heading, true, true, true);
272+
273+
Native.SetModelAsNoLongerNeeded(hash);
274+
275+
if (newVehicle is null)
276+
{
277+
return;
278+
}
279+
280+
ped.SetPedIntoVehicle(newVehicle!.Handle, -1);
87281
}
88282
}

0 commit comments

Comments
 (0)