Project Zomboid

Project Zomboid

Real Knockouts
Fluffy89 17 Jan, 2024 @ 2:30pm
Proper Vehicle Injuries Compatibility
Back again for more compatibility patches!

I've briefly looked through your code to see how I can hook PVI into Real Knockouts to trigger then depending on crash severity, and it seems like this line is what you're calling to handle triggering the knockout, correct?

Knockout.setUnconscious(character)

To my understanding, I should be able to access the 'Knockout' table since it's not marked as a local variable, so if compatibility is just a matter of adding in that line above with some logic and sandbox options, it should be pretty easy.

Can you confirm whether the line above is all I'd need to be calling to cause the player to go unconscious, or what function should be called initially when PVI determines a knockout should happen?
< >
Showing 1-15 of 16 comments
Arendameth  [developer] 17 Jan, 2024 @ 10:51pm 
Hello! Yes, you only need to call Knockout.setUnconscious(character), and the mod should take care of the rest
Fluffy89 18 Jan, 2024 @ 5:56pm 
Originally posted by Arendameth:
Hello! Yes, you only need to call Knockout.setUnconscious(character), and the mod should take care of the rest

Cool cool, Knockout should be very simple for compatibility; all that really needs to be done on my end is to add some logic when handling injuries to determine if the player should be knocked out.

I also intend to add flat damage that is currently missing from PVI, along with airbag support.

I haven't looked at airbags yet since they we're released after the Working Seatbelt patch, but I imagine you have them setup fairly similarly. Are airbags handled in the same sense where the status & condition of the airbag is stored in mod data like seatbelt status

(seatbeltIsBuckled = p:getModData().Seatbelt_sbStatus)

Or are the airbags physical car parts that I'd have to check for whatever car the player is driving?
Arendameth  [developer] 18 Jan, 2024 @ 6:15pm 
The airbags are actually only a tiny bit more complicated, since they are physical car parts, you have to check them first before applying any damage reduction that is airbag related. You'll find relevant code in WorkingSeatbelt_DamageEvent.lua. reductionType is defined in that file, then stored in moddata, which is then used for actual damage reduction in WorkingSeatbelt.onTickDamageReduction and WorkingSeatbelt.onTickFractureReduction.

Generally speaking, what you want to do is
1- if vars.airbagModule
2- Get character seat and check if its 0 or 1 (since airbags are only in driver or passenger seats)
3- local airbagPart = WorkingSeatbelt.getAirbagPart(seat, vehicle)
4- if airbagPart and not airbagPart:isInventoryItemUninstalled() then
5- if crash damage >= vars.airbagMinimumCrashStrength then
6- reductionType = "airbag" (Maybe? I have no idea how you handle that in your code, but like I said, this is passed later in my code to the ontick functions to handle damage reduction)
7- deployAirbag(character, charVehicleId, charSeat) (This doesn't apply actual damage reduction. Just does stuff like playing the sound, removing the airbag, etc)
Example:
--Do your airbag checks and whether or not should it be deployed local reductionType = "airbag" (or local reductionType = "both" for both airbag and seatbelt) deployAirbag(character, charVehicleId, charSeat) --This is not defined in the WorkingSeatbelt table, so you'll probably need to recreate it. if reductionType then -- Stop monitoring the player's body parts health and fractures. Events.OnTick.Remove(WorkingSeatbelt.onTickWatchBPHealth) Events.OnTick.Remove(WorkingSeatbelt.onTickWatchBPFractures) -- Get the player's body's average health before the damage. local healthBeforeDamage = character:getBodyDamage():getHealth() character:getModData().Seatbelt_healthBeforeDamage = healthBeforeDamage character:getModData().Seatbelt_reductionType = reductionType -- Start comparing the player's health and fractures before and after damage each tick. -- (In order to apply the damage reduction on the next tick, since it can't be applied -- on current tick due to script limitations) Events.OnTick.Add(WorkingSeatbelt.onTickDamageReduction) Events.OnTick.Add(WorkingSeatbelt.onTickFractureReduction) end



As a side note, I released an update yesterday for ejecting players out of the car and throwing them out of the windshield if seatbelt isn't buckled and windshield is smashed. It "probably" may need a compatibility patch as well!
Last edited by Arendameth; 18 Jan, 2024 @ 6:23pm
Fluffy89 18 Jan, 2024 @ 8:13pm 
Much appreciated about the info!

I'm assuming 'vars.airbagModule' in your step 1 is just checking sandbox vars if airbags are enabled?

reductionType will be very simple, I already have code for determining what type of helmet a player wears in the event of a crash ("None", "Half" , "Full").

I'll have to dive into the javadoc and see if there's a method that returns the seat number (I imagine there is, I've just not looked very far into the Vehicle class).

PVI also has the luxury of not needing to add & remove the onTick functions because it has full control of the damage numbers it's dealing, as opposed to needing to detect damage, hook into it, then apply damage reductions, so the 'if reductionType then' check will be very simple for PVI.

Is there a chance I could get you to define 'deployAirbag' in the Working Seatbelt table so there doesn't have to be a bunch of redundant code in PVI?

I'll also have a look into your player ejection addon; is the function for handling it declared in the Working Seatbelt table like Knockout is, or is it a bit more hands on like airbags?
Arendameth  [developer] 18 Jan, 2024 @ 10:09pm 
Yes, vars.airbagModule checks if the module is enabled in the first place

I use onTick (surprise surprise) as well for ejecting player, but since you don't need it, you can directly call WorkingSeatbelt.ejectPlayer(), and the mod takes care of the rest. Before you do, you need to check if the player should be ejected in first place using your own conditions + the shouldBeEjected function (which is also not defined in the table). A little side note, the distance at which player gets thrown from the car and the damage dealt are currently hard coded in the ejectPlayer function. I'll try to release an update tonight that:

Adds shouldBeEjected and deployAirbag to the table
Makes the damage and distance more dynamic/not hardcoded

You could use vehicle:getSeat(character) to get the seat number.

By the way, great job with PVI! :)
Fluffy89 18 Jan, 2024 @ 10:22pm 
Your mods are great too! Always the little immersion mods that add things that just makes sense are my favourites.

I appreciate all the help and insight you've given me, and don't feel rushed at all to get a patch out if you feel that way from me pestering you lol. I've got university classes, of which a bunch have labs attached to them so I likely won't get an update out for a few months or over some weekends/weekdays where I'm more free.

One last thing, I've thought about making a library of some basic helper functions to use for future mods and haven't used Lua outside of PZ. Do I have to add functions to the table to be able to call them, or does simply not declaring the function as local and then having the 'PVI = PVI or {}' allow me to access functions & variables inside that file?

Thanks again for all your help!
Arendameth  [developer] 19 Jan, 2024 @ 12:25am 
Thank you!

I actually just released a hotfix for the mod, since I made a blunder in the code that had a little annoying damage indicator appearing above players when they crashed. Anyways, the update also includes moving previously mentioned functions to the tables.

You have to assign functions and variables to the table to be able to call them from other locations to the best of my knowledge. I too used Lua for the first time in PZ modding so I am not the most knowledgeable person about it haha

A better practice than both of these two is to use modules, which should be more efficient. However, I am guilty of always forgetting to use this. At one point in the future I want to update all of my mods to this

--WorkingSeatbeltUtils.lua local WorkingSeatbeltUtils = {} WorkingSeatbeltUtils.testFunction = function() --do something end WorkingSeatbeltUtils.player = getPlayer() return WorkingSeatbeltUtils

--WorkingSeatbelt.lua local WorkingSeatbeltUtils = require "WorkingSeatbeltUtils" WorkingSeatbeltUtils.testFunction() print(WorkingSeatbeltUtils.player)
Last edited by Arendameth; 19 Jan, 2024 @ 12:29am
Fluffy89 21 Jan, 2024 @ 5:27pm 
Originally posted by Arendameth:
Thank you!

I actually just released a hotfix for the mod, since I made a blunder in the code that had a little annoying damage indicator appearing above players when they crashed. Anyways, the update also includes moving previously mentioned functions to the tables.

You have to assign functions and variables to the table to be able to call them from other locations to the best of my knowledge. I too used Lua for the first time in PZ modding so I am not the most knowledgeable person about it haha

A better practice than both of these two is to use modules, which should be more efficient. However, I am guilty of always forgetting to use this. At one point in the future I want to update all of my mods to this

--WorkingSeatbeltUtils.lua local WorkingSeatbeltUtils = {} WorkingSeatbeltUtils.testFunction = function() --do something end WorkingSeatbeltUtils.player = getPlayer() return WorkingSeatbeltUtils

--WorkingSeatbelt.lua local WorkingSeatbeltUtils = require "WorkingSeatbeltUtils" WorkingSeatbeltUtils.testFunction() print(WorkingSeatbeltUtils.player)

So I've got the following working--as far as I can tell--correctly:
- Knockout on collision, knockout chance can be specified for low/med/high/fatal severity crashes individually in PVI's sandbox options.

- Airbags deploy and the damage reduction is stacked with seatbelts up to a cap that is specified in sandbox options

- Ejection from the vehicle.

Currently, I'm not checking for any conditions to eject the player and I'm just ejecting them whenever a collision happens (for testing). The only thing I'm noticing and can see from 'WorkingSeatbelt.ejectPlayer' is lines 118 - 127; the flat damage caused from the ejection you add to each body part stacks on top of what PVI will deal with this next update.

Do you think you could add a check to see if PVI is installed just before you modify the body part health to alleviate this issue?

You could use this from PVI to check if you'd like:
function modInstalled(ModID) return getActivatedMods():contains(ModID) end

Where the 'modID' parameter is just the Mod ID on the workshop page of that mod (for PVI this is: ProperVehicleInjuriesMP)

You can also modify the players general health without iterating through each body part which gives a much easier to understand idea for how much damage you're dealing with:
p:getBodyDamage():ReduceGeneralHealth(damageValue)
Where 'damageValue' is a float from 0 - 100. Executing the above code with a damage value of 10 will reduce the players overall health by 10%, the health each bodypart contributes to the players total health pool is not the same for each part so I figured this might be helpful for you.

Other than that, pretty much all of the next update is done, and hopefully functional! I'm doing some testing now to make sure the update behaves properly, but all is good for now.
Fluffy89 21 Jan, 2024 @ 7:40pm 
Something else I'm running into that may need looking into is line 17 in WorkingSeatbelt_DamageEvent.lua:
if character:getDir() ~= vehicle:getDir() then return false end

I've attached screenshots below, but it seems that vehicles do not update vehicle:getDir() like the player does, causing this condition to almost always return false.

When driving a test car I spawned in, driving southbound the Muldraugh highway will fail to cause an ejection, even when all other conditions are true.

The debug message on screen is the difference in speed pre/post collision, basically how severe it was, and the left is tostring(p:getDir() == p:getVehicle():getDir())
Southbound collision[gyazo.com]

Here's the same scenario, but this time going Northbound:
Northbound collision[gyazo.com]

I've checked all the other conditions in 'WorkingSeatbelt.shouldBeEjected()' and all are as expected, but the vehicle objects direction doesn't appear to be updating.

I've tried other vehicles, but their direction seems to be stuck at whatever direction they were facing when they spawned. Heres the same test but with a red van I found naturally spawned in Muldraugh:

This is when colliding in the direction the vehicle spawned facing
Collision while going southbound[gyazo.com]

This is going the direction opposite of the vehicle spawning
Collision while going northbound[gyazo.com]

I even tried checking the direction after colliding, but the vehicles direction is still stuck as south or 'S' despite very clearly facing north:
Sanity check in Lua console in-game[gyazo.com]

Any thoughts? Going to try looking into if theres another function that may be more reliable, or try ask for help as to why getDir() isn't updating for vehicles but does for the player.
Last edited by Fluffy89; 21 Jan, 2024 @ 7:57pm
Arendameth  [developer] 21 Jan, 2024 @ 8:46pm 
Originally posted by Fluffy89:
Originally posted by Arendameth:
Thank you!

I actually just released a hotfix for the mod, since I made a blunder in the code that had a little annoying damage indicator appearing above players when they crashed. Anyways, the update also includes moving previously mentioned functions to the tables.

You have to assign functions and variables to the table to be able to call them from other locations to the best of my knowledge. I too used Lua for the first time in PZ modding so I am not the most knowledgeable person about it haha

A better practice than both of these two is to use modules, which should be more efficient. However, I am guilty of always forgetting to use this. At one point in the future I want to update all of my mods to this

--WorkingSeatbeltUtils.lua local WorkingSeatbeltUtils = {} WorkingSeatbeltUtils.testFunction = function() --do something end WorkingSeatbeltUtils.player = getPlayer() return WorkingSeatbeltUtils

--WorkingSeatbelt.lua local WorkingSeatbeltUtils = require "WorkingSeatbeltUtils" WorkingSeatbeltUtils.testFunction() print(WorkingSeatbeltUtils.player)

So I've got the following working--as far as I can tell--correctly:
- Knockout on collision, knockout chance can be specified for low/med/high/fatal severity crashes individually in PVI's sandbox options.

- Airbags deploy and the damage reduction is stacked with seatbelts up to a cap that is specified in sandbox options

- Ejection from the vehicle.

Currently, I'm not checking for any conditions to eject the player and I'm just ejecting them whenever a collision happens (for testing). The only thing I'm noticing and can see from 'WorkingSeatbelt.ejectPlayer' is lines 118 - 127; the flat damage caused from the ejection you add to each body part stacks on top of what PVI will deal with this next update.

Do you think you could add a check to see if PVI is installed just before you modify the body part health to alleviate this issue?

You could use this from PVI to check if you'd like:
function modInstalled(ModID) return getActivatedMods():contains(ModID) end

Where the 'modID' parameter is just the Mod ID on the workshop page of that mod (for PVI this is: ProperVehicleInjuriesMP)

You can also modify the players general health without iterating through each body part which gives a much easier to understand idea for how much damage you're dealing with:
p:getBodyDamage():ReduceGeneralHealth(damageValue)
Where 'damageValue' is a float from 0 - 100. Executing the above code with a damage value of 10 will reduce the players overall health by 10%, the health each bodypart contributes to the players total health pool is not the same for each part so I figured this might be helpful for you.

Other than that, pretty much all of the next update is done, and hopefully functional! I'm doing some testing now to make sure the update behaves properly, but all is good for now.
Hello! I really like the updates you've worked on, keep it up!
I've modified the eject player code to apply player damage only if PVI mod is not enabled. Additionally, ReduceGeneralHealth is definitely a better and more straightforward way. I didn't know that function existed, but thanks! I'm using it now.
Arendameth  [developer] 21 Jan, 2024 @ 8:48pm 
Originally posted by Fluffy89:
Something else I'm running into that may need looking into is line 17 in WorkingSeatbelt_DamageEvent.lua:
if character:getDir() ~= vehicle:getDir() then return false end

I've attached screenshots below, but it seems that vehicles do not update vehicle:getDir() like the player does, causing this condition to almost always return false.

When driving a test car I spawned in, driving southbound the Muldraugh highway will fail to cause an ejection, even when all other conditions are true.

The debug message on screen is the difference in speed pre/post collision, basically how severe it was, and the left is tostring(p:getDir() == p:getVehicle():getDir())
Southbound collision[gyazo.com]

Here's the same scenario, but this time going Northbound:
Northbound collision[gyazo.com]

I've checked all the other conditions in 'WorkingSeatbelt.shouldBeEjected()' and all are as expected, but the vehicle objects direction doesn't appear to be updating.

I've tried other vehicles, but their direction seems to be stuck at whatever direction they were facing when they spawned. Heres the same test but with a red van I found naturally spawned in Muldraugh:

This is when colliding in the direction the vehicle spawned facing
Collision while going southbound[gyazo.com]

This is going the direction opposite of the vehicle spawning
Collision while going northbound[gyazo.com]

I even tried checking the direction after colliding, but the vehicles direction is still stuck as south or 'S' despite very clearly facing north:
Sanity check in Lua console in-game[gyazo.com]

Any thoughts? Going to try looking into if theres another function that may be more reliable, or try ask for help as to why getDir() isn't updating for vehicles but does for the player.
To the best of my knowledge, vehicle:getDir() will always be equal to the driver's :getDir(), unless the player is going in reverse (pressing S). This is done to ensure that even the player gets a massive damage while going in reverse, which is unlikely, he doesn't get ejected out of the windshield.
Fluffy89 21 Jan, 2024 @ 9:28pm 
Originally posted by Arendameth:
Originally posted by Fluffy89:
Something else I'm running into that may need looking into is line 17 in WorkingSeatbelt_DamageEvent.lua:
if character:getDir() ~= vehicle:getDir() then return false end

I've attached screenshots below, but it seems that vehicles do not update vehicle:getDir() like the player does, causing this condition to almost always return false.

When driving a test car I spawned in, driving southbound the Muldraugh highway will fail to cause an ejection, even when all other conditions are true.

The debug message on screen is the difference in speed pre/post collision, basically how severe it was, and the left is tostring(p:getDir() == p:getVehicle():getDir())
Southbound collision[gyazo.com]

Here's the same scenario, but this time going Northbound:
Northbound collision[gyazo.com]

I've checked all the other conditions in 'WorkingSeatbelt.shouldBeEjected()' and all are as expected, but the vehicle objects direction doesn't appear to be updating.

I've tried other vehicles, but their direction seems to be stuck at whatever direction they were facing when they spawned. Heres the same test but with a red van I found naturally spawned in Muldraugh:

This is when colliding in the direction the vehicle spawned facing
Collision while going southbound[gyazo.com]

This is going the direction opposite of the vehicle spawning
Collision while going northbound[gyazo.com]

I even tried checking the direction after colliding, but the vehicles direction is still stuck as south or 'S' despite very clearly facing north:
Sanity check in Lua console in-game[gyazo.com]

Any thoughts? Going to try looking into if theres another function that may be more reliable, or try ask for help as to why getDir() isn't updating for vehicles but does for the player.
To the best of my knowledge, vehicle:getDir() will always be equal to the driver's :getDir(), unless the player is going in reverse (pressing S). This is done to ensure that even the player gets a massive damage while going in reverse, which is unlikely, he doesn't get ejected out of the windshield.

That's interesting then, I'll have to take another look at it tomorrow, and thanks for that update!

From the tests I'd been doing (just in the lua console in game to save time) the vehicles getDir() stayed static, even though the driver/player had very much changed directions :/

I'll try running some debug lines and see if I can see the direction of the vehicle changing, if not I'll see if anyone in the mod-development channel on the PZ discord has any ideas.

Thanks as always, I'll send an update either tomorrow or whenever I work on the mod again!
Fluffy89 22 Jan, 2024 @ 2:09pm 
So I may have found some things that will make this work on PVI's end. I have no clue how bad if at all the idea is, but I'm assuming it's not terrible since vehicle:getDir() doesn't seem to update, at least from what I can see from my testing.

Turns out it's pretty simple, I've attached a link to a gif showing this, but turns out BaseVehicle has a method 'getTransmissionNumberLetter()' that returns a string representing the gear the vehicles transmission is in ("R" for reverse, "N" for neutral, "1" for first gear, etc).

So with this transmission letter, I can check if it's anything but "R" and then set the vehicles direction to the players direction. Otherwise, we use the function 'reverse(IsoDirections directionToReverse)' in the 'IsoDirections' class and pass the player's getDir() as it's parameter, and set the vehicles direction to that.

The showcase gif[gyazo.com]

The code I'm talking about above:
---------------- TRANSMISSION TEST local p = getPlayer() local v = p:getVehicle() -- Get transmission gear as a string local transmissionGear = v:getTransmissionNumberLetter() if (transmissionGear ~= "R") then v:setDir(p:getDir()) else v:setDir(IsoDirections.reverse(p:getDir())) end ---------------- TRANSMISSION TEST

I think I'm going to head forward with this implementation to fix the issue I mentioned yesterday, will also add an option in PVI's sandbox options to enable or disable this if is seems to cause issues in the future.

Thanks again for all your help!
Last edited by Fluffy89; 22 Jan, 2024 @ 2:11pm
Fluffy89 24 Jan, 2024 @ 1:55pm 
After some extra testing, update 3.3 for PVI is live adding in support for Real Knockouts, along with airbag and ejection support from Working Seatbelt!

If there's any other mods/updates you publish, don't hesitate to post a discussion on PVI's workshop page to let me know :)

Thanks again for all your help!
Arendameth  [developer] 26 Jan, 2024 @ 5:36am 
Love the update! It was my pleasure as well :)

By the way, being in contact over discord might be easier for the future. You can message me at @arendameth
< >
Showing 1-15 of 16 comments
Per page: 1530 50