Skip to content

Commit 970ac7a

Browse files
committed
feat(stancer): auto-reload on wheel change
1 parent 792993a commit 970ac7a

6 files changed

Lines changed: 104 additions & 18 deletions

File tree

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ A modular GTA V Lua framework focused on enhancing the player's experience throu
4343
- Type `!ls` or `!dump` to dump all available commands.
4444
- All default commands are prefixed with an exclamation mark `<!>`.
4545

46+
>[!Important]
47+
> **Do not use this in YimMenuV2. It will not work.**
48+
4649
## Contributing
4750

4851
Contributions are what make the open source community a great place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
@@ -74,14 +77,33 @@ This project was rewritten from scratch using [SmallBase](https://github.com/xes
7477
</a>
7578
</div>
7679

80+
## FAQ
81+
82+
- **Q:** Does this support Enhanced?
83+
- **A:** Prtially. You can use [YimLuaAPI](github.com/TupoyeMenu/YimLuaAPI) to test it but stability is not guaranteed as of now.
84+
85+
- **Q:** What is YimLuaAPI and how do I use it?
86+
- **A:**
87+
- **What is it:** [YimLuaAPI](github.com/TupoyeMenu/YimLuaAPI) is a universal Lua API that works for both Legacy and Enhanced.
88+
- **How to use it:** Right now there is no release because it's still in development. If you want to try it, you have to compile it yourself. Once you have `YimLuaAPI.dll`, inject it into any GTA branch (Legacy/Enhanced), it will create a folder on first injection: `%AppData%\YimLuaAPI`. Simply place the script there and you're done. You can still use YimMenu/YimMenuV2 but the script has to only exist in YimLuaAPI.
89+
90+
- **Q:** Why can't-I run this in YimMenuV2?
91+
- **A:** There are several reasons why:
92+
- YimMenuV2 doesn't have a finished Lua API. `require` isn't even present, let alone custom bindings.
93+
- There are several versions and flavors of the Lua programming language. This project is written in [Lua 5.4](https://www.lua.org/manual/5.4/) and YimMenuV2's API uses [LuaJIT](https://luajit.org/). Explaining the difference here is not ideal but it's not only the language difference, it's also how they are embedded in each menu.
94+
95+
- **Q:** Can this be made compatible with YimMenuV2 once its API is finished?
96+
- **Short Answer:** No.
97+
- **Long Answer:** Yes, a compatibility layer can be added to accomodate for all language and API differences but is it worth the trouble and code bloat? Absolutely not. We would be better off rewriting this for V2's API.
98+
7799
## Acknowledgments
78100

79-
| | |
101+
| Name | Contribution |
80102
| :---: | :---: |
81103
| <a href="https://github.com/harmless05"><img height="40" width="40" src="https://avatars.githubusercontent.com/harmless05"><br/>Harmless</a> | Shift-Drift |
82104
| <a href="https://github.com/NiiV3AU"><img height="40" width="40" src="https://avatars.githubusercontent.com/NiiV3AU"><br/>NiiV3AU</a> | German translations |
83105
| <a href="https://github.com/gir489returns"><img height="40" width="40" src="https://avatars.githubusercontent.com/gir489returns"><br/>gir489returns</a> | [Casino Pacino](https://github.com/YimMenu-Lua/Casino-Pacino) |
84106
| <a href="https://github.com/tupoy-ya"><img height="40" width="40" src="https://avatars.githubusercontent.com/tupoy-ya"><br/>tupoy-ya</a> | Several contributions and shared knowledge |
85107
| <a href="https://github.com/szalikdev"><img height="40" width="40" src="https://avatars.githubusercontent.com/szalikdev"><br/>szalikdev</a> | Revived the project and joined the cause |
86-
| <a href="https://github.com/shinywasabi"><img height="40" width="40" src="https://avatars.githubusercontent.com/shinywasabi"><br/>shinywasabi</a> | Foundational community tooling frequently used as reference |
108+
| <a href="https://github.com/shinywasabi"><img height="40" width="40" src="https://avatars.githubusercontent.com/shinywasabi"><br/>ShinyWasabi</a> | Foundational community tooling frequently used as reference |
87109
| <a href="https://unknowncheats.me"><img height="52" width="120" src="https://www.unknowncheats.me/forum/ambience/misc/forum_banner.png"><br/>UnknownCheats</a> | A treasure trove of information |

includes/classes/CVehicle.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,12 @@ function CVehicle:IsWheelBrokenOff(wheelIndex)
433433
return (self.m_ptr:add(0xA98):get_dword() >> (wheelIndex & 0x1F) & 1) ~= 0
434434
end
435435

436+
---@param refresh? boolean
437+
---@return CWheelDrawData
438+
function CVehicle:GetWheelDrawData(refresh)
439+
return self.m_draw_data:GetWheelDrawData(refresh)
440+
end
441+
436442
---@return float -- Wheel width or 0.f if invalid
437443
function CVehicle:GetWheelWidth()
438444
return self.m_draw_data:GetWheelWidth()

includes/classes/CVehicleDrawData.lua

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ function CVehicleDrawData:init(ptr)
3535
return self
3636
end
3737

38-
function CVehicleDrawData:GetWheelDrawData()
38+
---@param refresh? boolean
39+
function CVehicleDrawData:GetWheelDrawData(refresh)
40+
if (refresh) then
41+
self.m_wheel_draw_data = nil
42+
end
43+
3944
if not (self.m_wheel_draw_data and self.m_wheel_draw_data:IsValid()) then
4045
self.m_wheel_draw_data = CWheelDrawData.new(self.m_ptr:add(0x370):deref())
4146
end

includes/features/vehicle/stancer.lua

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ end
2525
---@field private m_entity PlayerVehicle
2626
---@field private m_last_tick milliseconds
2727
---@field private m_cached_model hash
28+
---@field private m_reloading boolean
29+
---@field private m_last_wheels_mod {type: integer, index: integer, var: integer}
30+
---@field private m_last_wheel_mod_check_time seconds
2831
---@field public m_base_values table<eWheelSide, StanceObject>
2932
---@field public m_deltas table<eWheelSide, StanceObject>
3033
---@field public m_wheels table<eWheelSide, array<CWheel>>
@@ -153,6 +156,15 @@ end
153156

154157
function Stancer:Init()
155158
self.m_last_tick = 0
159+
self.m_last_wheel_mod_check_time = 0
160+
self.m_reloading = false
161+
162+
self.m_last_wheels_mod = {
163+
index = -1,
164+
type = -1,
165+
var = -1
166+
}
167+
156168
self.m_suspension_height = {
157169
m_current = 0.0,
158170
m_last_seen = 0.0
@@ -161,6 +173,7 @@ function Stancer:Init()
161173
if (self.m_entity:IsValid()) then
162174
self:ReadWheelArray()
163175
self.m_cached_model = self.m_entity:GetModelHash()
176+
self.m_last_wheels_mod = self.m_entity:GetCustomWheels()
164177
end
165178
end
166179

@@ -174,13 +187,13 @@ function Stancer:CanApplyDrawData()
174187
end
175188

176189
function Stancer:Reset()
190+
self.m_suspension_height.m_current = 0.0
191+
self.m_suspension_height.m_last_seen = 0.0
192+
177193
if (not self.m_wheels) then
178194
return
179195
end
180196

181-
self.m_suspension_height.m_current = 0.0
182-
self.m_suspension_height.m_last_seen = 0.0
183-
184197
for _, v in ipairs(self.decorators) do
185198
self.m_deltas[v.wheel_side][v.key] = 0.0
186199
local wheel_array = self:GetAllWheelsForSide(v.wheel_side)
@@ -207,10 +220,6 @@ function Stancer:Reset()
207220
end
208221

209222
function Stancer:Cleanup()
210-
if (not self.m_wheels) then
211-
return
212-
end
213-
214223
self:Reset()
215224
Decorator:RemoveEntity(self.m_entity:GetHandle())
216225
self.m_cached_model = nil
@@ -222,8 +231,10 @@ function Stancer:OnNewVehicle()
222231
return
223232
end
224233

225-
self.m_cached_model = self.m_entity:GetModelHash()
234+
self.m_cached_model = self.m_entity:GetModelHash()
235+
self.m_last_wheels_mod = self.m_entity:GetCustomWheels()
226236
self:ReadDefaults()
237+
227238
if (self:IsVehicleModelSaved() and GVars.features.vehicle.stancer.auto_apply_saved) then
228239
self:LoadSavedDeltas()
229240
end
@@ -340,6 +351,39 @@ function Stancer:GetNthWheelForSide(side, wheel_n)
340351
return self.m_wheels[side][wheel_n]
341352
end
342353

354+
function Stancer:OnWheelsChanged()
355+
if (not self.m_entity or not self.m_entity:IsValid()) then
356+
return
357+
end
358+
359+
if (Time.now() - self.m_last_wheel_mod_check_time < 1) then
360+
return
361+
end
362+
363+
local current = self.m_entity:GetCustomWheels()
364+
if (not table.is_equal(self.m_last_wheels_mod, current)) then
365+
self.m_reloading = true
366+
local prev_sup = self.m_suspension_height.m_current
367+
local prev_deltas_f = table.copy(self.m_deltas[self.eWheelSide.FRONT])
368+
local prev_deltas_r = table.copy(self.m_deltas[self.eWheelSide.BACK])
369+
self:Cleanup()
370+
self:OnNewVehicle()
371+
self.m_last_wheels_mod = self.m_entity:GetCustomWheels()
372+
373+
for k, v in pairs(prev_deltas_f) do
374+
self.m_deltas[self.eWheelSide.FRONT][k] = v
375+
end
376+
for k, v in pairs(prev_deltas_r) do
377+
self.m_deltas[self.eWheelSide.BACK][k] = v
378+
end
379+
380+
self.m_suspension_height.m_current = prev_sup
381+
self.m_reloading = false
382+
end
383+
384+
self.m_last_wheel_mod_check_time = Time.now()
385+
end
386+
343387
---@return boolean
344388
function Stancer:AreSavedDeltasLoaded()
345389
if (not self.m_entity or not self.m_entity:IsValid()) then
@@ -481,6 +525,7 @@ function Stancer:ReadDefaults()
481525
return
482526
end
483527

528+
self.m_entity:GetWheelDrawData(true)
484529
local queued_decors_loaded = self:RestoreQueueFromDecors()
485530

486531
if (self:AreDefaultsRegistered()) then
@@ -523,7 +568,7 @@ function Stancer:Update()
523568
-- possible RAGE engine PTSD involved
524569
self.m_is_model_saved = self:IsVehicleModelSaved()
525570

526-
if (not self.m_is_active or not self.m_wheels) then
571+
if (not self.m_is_active or not self.m_wheels or self.m_reloading) then
527572
return
528573
end
529574

@@ -533,6 +578,8 @@ function Stancer:Update()
533578
return
534579
end
535580

581+
self:OnWheelsChanged()
582+
536583
-- must have high frequency updates, otherwise wheels will flicker
537584
-- when the game tries to force-reset them
538585
if (Game.GetGameTimer() - self.m_last_tick < 5) then

includes/lib/utils.lua

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -818,28 +818,28 @@ function table.is_equal(a, b, seen)
818818
return true
819819
end
820820

821-
if type(a) ~= type(b) then
821+
if (type(a) ~= type(b)) then
822822
return false
823823
end
824824

825-
if type(a) ~= "table" then
825+
if (type(a) ~= "table") then
826826
return false
827827
end
828828

829829
seen = seen or {}
830-
if seen[a] and seen[b] then
830+
if (seen[a] and seen[b]) then
831831
return true
832832
end
833833
seen[a], seen[b] = true, true
834834

835835
for k, v in pairs(a) do
836-
if not table.is_equal(v, b[k], seen) then
836+
if (not table.is_equal(v, b[k], seen)) then
837837
return false
838838
end
839839
end
840840

841841
for k in pairs(b) do
842-
if a[k] == nil then
842+
if (a[k] == nil) then
843843
return false
844844
end
845845
end

includes/modules/Vehicle.lua

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,12 @@ function Vehicle:IsWheelBrokenOff(wheelIndex)
550550
return self:Resolve():IsWheelBrokenOff(wheelIndex)
551551
end
552552

553+
---@param refresh? boolean
554+
---@return CWheelDrawData
555+
function Vehicle:GetWheelDrawData(refresh)
556+
return self:Resolve():GetWheelDrawData(refresh)
557+
end
558+
553559
---@return float -- Wheel width or 0.f if invalid
554560
function Vehicle:GetVisualWheelWidth()
555561
return self:Resolve():GetWheelWidth()
@@ -829,7 +835,7 @@ function Vehicle:GetColors()
829835
return col1, col2
830836
end
831837

832-
---@return table
838+
---@return { type: integer, index: integer, var: integer }
833839
function Vehicle:GetCustomWheels()
834840
if not self:IsValid() then
835841
return {}

0 commit comments

Comments
 (0)