Implement hoverpack

This commit is contained in:
2026-09-06 21:56:34 +02:00
parent 1a619cfb61
commit dd7f7deb90
4 changed files with 284 additions and 15 deletions
+16 -1
View File
@@ -16,6 +16,7 @@ Player controls:
- Chat `!points`: print personal Score, Credits, and rank
- Chat `!musicmute` or client console `coop_music_mute`: toggle purchased server music locally
- Client console `coop_music_volume 0.0` through `coop_music_volume 1.0`: set music volume
- With Hoverpack enabled, jump normally and continue holding `Space` to hover
- Hold `TAB`: compare all connected players' Lifetime Scores and ranks
The shop contains persistent Half-Life 2 player models and permanent bonus
@@ -29,6 +30,17 @@ Music entries may set `spatial = true` plus `minimumDistance` and
and fades over distance. Supply spatial audio as 44.1 kHz mono MP3 or WAV for
reliable in-game positioning.
The Behaviours category contains persistent, toggleable player abilities. The
Hoverpack costs 500 Credits by default and is enabled immediately after buying
it. A short Space press remains a normal jump; holding Space for 0.3 seconds in
the air maintains altitude and rises slowly. Releasing Space stops thrust and
restores normal gravity. Pressing and holding Space again can restart thrust
while still airborne. Thrust consumes the standard AUX power meter and stops at
zero. After a 1.25-second pause with Space released, AUX recharges while waiting
and recharges faster while walking; active sprinting does not receive the extra
hoverpack recharge. Its built-in looping thruster sound follows the player and
is audible to nearby players.
The server checks all kills, balances, live prices, ownership, equipment, and
music playback. A purchase request contains only the item type and ID: clients
cannot award points, submit a purchase price, or debit their own Credits.
@@ -42,7 +54,7 @@ their current non-negative value. Set `Progression.AdminShopTestingBypass` to
`false` to disable this behavior.
The equivalent server/admin console command is:
`coop_setprice <model|weapon|music> <item_id> <credits>`
`coop_setprice <model|weapon|music|behaviour> <item_id> <credits>`
For example: `coop_setprice music song_id 500`. The maximum accepted price is
100,000,000 Credits. Existing owners retain their unlocks when a price changes.
@@ -53,6 +65,9 @@ Configuration is in `gamemode/progression_shared.lua`:
- `Progression.Models`: player-model catalogue and prices
- `Progression.Weapons`: bonus-weapon catalogue and prices
- `Progression.Music`: music catalogue, default prices, paths, and durations
- `Progression.Behaviours`: toggleable ability catalogue and default prices
- `Progression.Hoverpack*`: hover delay, rise speed, AUX drain/recharge rates,
and thruster sound settings
- `Progression.MonsterClasses`: NPC classes that award points
Catalogue prices are initial defaults. Once seeded, live prices come from the
+40 -8
View File
@@ -11,6 +11,8 @@ local shopState = {
ownedModels = {},
ownedWeapons = {},
ownedMusic = {},
ownedBehaviours = {},
enabledBehaviours = {},
prices = {},
isAdmin = false
}
@@ -106,11 +108,14 @@ local function AddItemCard(layout, itemType, item)
local isModel = itemType == "model"
local isWeapon = itemType == "weapon"
local isMusic = itemType == "music"
local isBehaviour = itemType == "behaviour"
local owned = isModel and shopState.ownedModels[item.id]
or isWeapon and shopState.ownedWeapons[item.id]
or isMusic and shopState.ownedMusic[item.id]
local equipped = isModel and shopState.equippedModel == item.id
or isBehaviour and shopState.ownedBehaviours[item.id]
local selected = isModel and shopState.equippedModel == item.id
or isWeapon and shopState.equippedWeapon == item.id
or isBehaviour and shopState.enabledBehaviours[item.id]
local currentPrice = CurrentPrice(itemType, item)
local card = layout:Add("DPanel")
@@ -118,7 +123,7 @@ local function AddItemCard(layout, itemType, item)
card.Paint = function(self, width, height)
local color = self:IsHovered() and colors.panelHover or colors.panel
draw.RoundedBox(8, 0, 0, width, height, color)
if equipped then
if selected then
surface.SetDrawColor(colors.success)
surface.DrawOutlinedRect(0, 0, width, height, 2)
end
@@ -149,7 +154,10 @@ local function AddItemCard(layout, itemType, item)
if IsValid(entity) then entity:SetAngles(Angle(0, RealTime() * 18 % 360, 0)) end
end
else
local previewText = isMusic and "SERVER MUSIC" or isWeapon and "MAP LOADOUT" or "NO PREVIEW"
local previewText = isMusic and "SERVER MUSIC"
or isBehaviour and "PLAYER ABILITY"
or isWeapon and "MAP LOADOUT"
or "NO PREVIEW"
local noPreview = AddText(card, previewText, "CustomCoopProgressionHeading", colors.muted)
noPreview:SetContentAlignment(5)
noPreview:SetPos(8, 8)
@@ -176,18 +184,20 @@ local function AddItemCard(layout, itemType, item)
local actionText
if isMusic then
actionText = (owned or currentPrice == 0) and "Play" or "Buy"
elseif isBehaviour then
actionText = owned and (selected and "Disable" or "Enable") or "Buy & Enable"
else
actionText = equipped and "Equipped" or (owned or currentPrice == 0) and "Equip" or "Buy"
actionText = selected and "Equipped" or (owned or currentPrice == 0) and "Equip" or "Buy"
end
button:SetText(actionText)
button:SetEnabled(not equipped)
button:SetEnabled(isBehaviour or not selected)
button.DoClick = function()
button:SetEnabled(false)
button:SetText("Please wait...")
SelectItem(itemType, item.id)
timer.Simple(1, function()
if IsValid(button) then
button:SetEnabled(not equipped)
button:SetEnabled(isBehaviour or not selected)
button:SetText(actionText)
end
end)
@@ -273,7 +283,8 @@ local function CreateAdminPage(sheet)
for _, catalogue in ipairs({
{ itemType = "model", items = Progression.Models },
{ itemType = "weapon", items = Progression.Weapons },
{ itemType = "music", items = Progression.Music }
{ itemType = "music", items = Progression.Music },
{ itemType = "behaviour", items = Progression.Behaviours }
}) do
for _, item in ipairs(catalogue.items) do
local row = list:AddLine(catalogue.itemType, item.id, item.name, CurrentPrice(catalogue.itemType, item))
@@ -343,6 +354,7 @@ local function OpenShop()
CreateCataloguePage(sheet, "Player Models", "model", Progression.Models)
CreateCataloguePage(sheet, "Bonus Weapons", "weapon", Progression.Weapons)
CreateCataloguePage(sheet, "Music", "music", Progression.Music)
CreateCataloguePage(sheet, "Behaviours", "behaviour", Progression.Behaviours)
if shopState.isAdmin then CreateAdminPage(sheet) end
end
@@ -364,6 +376,8 @@ net.Receive("CustomCoopProgressionOpenShop", function()
shopState.ownedModels = { [Progression.DefaultModelID] = true }
shopState.ownedWeapons = { [Progression.DefaultWeaponID] = true }
shopState.ownedMusic = {}
shopState.ownedBehaviours = {}
shopState.enabledBehaviours = {}
for _ = 1, net.ReadUInt(8) do
local itemType = net.ReadString()
@@ -374,9 +388,14 @@ net.Receive("CustomCoopProgressionOpenShop", function()
shopState.ownedWeapons[itemID] = true
elseif itemType == "music" then
shopState.ownedMusic[itemID] = true
elseif itemType == "behaviour" then
shopState.ownedBehaviours[itemID] = true
end
end
for _ = 1, net.ReadUInt(8) do
shopState.enabledBehaviours[net.ReadString()] = true
end
shopState.isAdmin = net.ReadBool()
shopState.prices = ReadShopPrices()
@@ -503,14 +522,27 @@ hook.Add("HUDPaint", "CustomCoopProgressionHUD", function()
local credits = ply:GetNW2Int("CustomCoopCredits", 0)
local rank = ply:GetNW2String("CustomCoopRank", Progression.GetRank(score))
local width, height = 288, 66
local hoverpackEnabled = ply:GetNW2Bool("CustomCoopHoverpackEnabled", false)
local hoverpackThrusting = ply:GetNW2Bool("CustomCoopHoverpackThrusting", false)
local width, height = 288, hoverpackEnabled and 90 or 66
local x, y = ScrW() - width - 18, 18
draw.RoundedBox(8, x, y, width, height, colors.background)
draw.SimpleText(rank, "CustomCoopProgressionHeading", x + 12, y + 9, colors.accent)
draw.SimpleText("Press F3 for Shop", "CustomCoopProgressionSmall", x + width - 12, y + 13,
colors.muted, TEXT_ALIGN_RIGHT)
draw.SimpleText("Score " .. score, "CustomCoopProgressionBody", x + 12, y + 37, colors.text)
draw.SimpleText("Credits " .. credits, "CustomCoopProgressionBody", x + 151, y + 37, colors.text)
if hoverpackEnabled then
local suitPower = ply.GetSuitPower and math.Round(ply:GetSuitPower()) or 100
local status = hoverpackThrusting and ("THRUSTING — AUX " .. suitPower .. "%")
or suitPower <= 0 and "EMPTY — release Space to recharge"
or ("READY — AUX " .. suitPower .. "%")
draw.SimpleText("Hoverpack: " .. status, "CustomCoopProgressionSmall", x + 12, y + 64,
hoverpackThrusting and colors.success or colors.muted)
end
if CurTime() < gainExpires then
draw.SimpleText("+" .. gainedScore, "CustomCoopProgressionHeading", x - 10, y + height / 2, colors.success, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
end
+70
View File
@@ -12,6 +12,15 @@ Progression.DefaultWeaponID = "none"
Progression.MaximumShopPrice = 100000000
Progression.MusicCooldownPadding = 10
Progression.AdminShopTestingBypass = true
Progression.HoverpackHoldDelay = 0.3
Progression.HoverpackRiseSpeed = 24
Progression.HoverpackDrainPerSecond = 16
Progression.HoverpackRechargeDelay = 1.25
Progression.HoverpackRechargePerSecond = 14
Progression.HoverpackWalkingRechargePerSecond = 22
Progression.HoverpackSound = "PhysicsCannister.ThrusterLoop"
Progression.HoverpackSoundVolume = 0.55
Progression.HoverpackSoundPitch = 115
Progression.Ranks = {
{ score = 0, name = "Newbie" },
@@ -162,6 +171,15 @@ Progression.Music = {
}
}
Progression.Behaviours = {
{
id = "hoverpack",
name = "Hoverpack",
price = 500,
description = "Hold Space after jumping to hover and rise slowly. Toggle this behaviour on or off."
}
}
-- Only these combat NPC classes award points. Keeping an allow-list avoids
-- rewards for friendly citizens, scripted targets and manually invented entity
-- classes. Add a class here if a future coop map uses another real monster.
@@ -238,6 +256,10 @@ function Progression.GetMusic(id)
return FindByID(Progression.Music, id)
end
function Progression.GetBehaviour(id)
return FindByID(Progression.Behaviours, id)
end
function Progression.GetRank(score)
local result = Progression.Ranks[1].name
score = math.max(tonumber(score) or 0, 0)
@@ -249,3 +271,51 @@ function Progression.GetRank(score)
return result
end
local function SetHoverpackThrusting(ply, thrusting)
if SERVER and Progression.SetHoverpackThrusting then
Progression.SetHoverpackThrusting(ply, thrusting)
end
end
hook.Add("SetupMove", "CustomCoopProgressionHoverpack", function(ply, moveData)
local jumpHeld = moveData:KeyDown(IN_JUMP)
local suitPower = ply.GetSuitPower and ply:GetSuitPower() or 100
local canHover = ply:GetNW2Bool("CustomCoopHoverpackEnabled", false)
and ply:Alive()
and ply:GetMoveType() == MOVETYPE_WALK
and ply:WaterLevel() < 2
and not ply:InVehicle()
and suitPower > 0.01
if not jumpHeld or not canHover then
ply.CustomCoopHoverHoldStarted = nil
ply.CustomCoopHoverJumpWasHeld = false
SetHoverpackThrusting(ply, false)
return
end
if not ply.CustomCoopHoverJumpWasHeld then
ply.CustomCoopHoverHoldStarted = CurTime()
end
ply.CustomCoopHoverJumpWasHeld = true
-- Continually reset while grounded. A normal jump therefore happens first,
-- and hovering only starts after Space remains held in the air.
if ply:OnGround() then
ply.CustomCoopHoverHoldStarted = CurTime()
SetHoverpackThrusting(ply, false)
return
end
ply.CustomCoopHoverHoldStarted = ply.CustomCoopHoverHoldStarted or CurTime()
if CurTime() - ply.CustomCoopHoverHoldStarted < Progression.HoverpackHoldDelay then
SetHoverpackThrusting(ply, false)
return
end
local velocity = moveData:GetVelocity()
velocity.z = Progression.HoverpackRiseSpeed
moveData:SetVelocity(velocity)
SetHoverpackThrusting(ply, true)
end)
+158 -6
View File
@@ -78,6 +78,14 @@ local function CreateTables()
PRIMARY KEY (item_type, item_id)
)
]], "could not create shop price table")
Query([[
CREATE TABLE IF NOT EXISTS custom_coop_enabled_behaviours (
steamid64 TEXT NOT NULL,
item_id TEXT NOT NULL,
PRIMARY KEY (steamid64, item_id)
)
]], "could not create enabled behaviour table")
end
CreateTables()
@@ -85,7 +93,8 @@ CreateTables()
local catalogues = {
{ itemType = "model", items = Progression.Models },
{ itemType = "weapon", items = Progression.Weapons },
{ itemType = "music", items = Progression.Music }
{ itemType = "music", items = Progression.Music },
{ itemType = "behaviour", items = Progression.Behaviours }
}
for _, item in ipairs(Progression.Music) do
@@ -112,6 +121,7 @@ local function GetCatalogueItem(itemType, itemID)
if itemType == "model" then return Progression.GetModel(itemID) end
if itemType == "weapon" then return Progression.GetWeapon(itemID) end
if itemType == "music" then return Progression.GetMusic(itemID) end
if itemType == "behaviour" then return Progression.GetBehaviour(itemID) end
end
local function GetShopPrice(itemType, item)
@@ -172,6 +182,9 @@ local function EmptyState()
ownedModels = { [Progression.DefaultModelID] = true },
ownedWeapons = { [Progression.DefaultWeaponID] = true },
ownedMusic = {},
ownedBehaviours = {},
enabledBehaviours = {},
behavioursDirty = false,
pendingOwnedItems = {},
profileDirty = false
}
@@ -183,6 +196,11 @@ local function SyncPlayer(ply, state)
ply:SetNW2Int("CustomCoopScore", state.score)
ply:SetNW2Int("CustomCoopCredits", state.credits)
ply:SetNW2String("CustomCoopRank", Progression.GetRank(state.score))
ply:SetNW2Bool("CustomCoopHoverpackEnabled", state.enabledBehaviours.hoverpack == true)
if state.enabledBehaviours.hoverpack and ply.EquipSuit
and (not ply.IsSuitEquipped or not ply:IsSuitEquipped()) then
ply:EquipSuit()
end
end
local function LoadPlayer(ply)
@@ -232,6 +250,20 @@ local function LoadPlayer(ply)
state.ownedWeapons[owned.item_id] = true
elseif owned.item_type == "music" and Progression.GetMusic(owned.item_id) then
state.ownedMusic[owned.item_id] = true
elseif owned.item_type == "behaviour" and Progression.GetBehaviour(owned.item_id) then
state.ownedBehaviours[owned.item_id] = true
end
end
end
local enabledRows = Query(
"SELECT item_id FROM custom_coop_enabled_behaviours WHERE steamid64 = " .. SQLString(steamID64),
"could not load enabled behaviours"
)
if istable(enabledRows) then
for _, enabled in ipairs(enabledRows) do
if state.ownedBehaviours[enabled.item_id] and Progression.GetBehaviour(enabled.item_id) then
state.enabledBehaviours[enabled.item_id] = true
end
end
end
@@ -262,7 +294,7 @@ local function SavePlayerState(ply, state)
if not state then return true end
local hasPendingOwnership = next(state.pendingOwnedItems) ~= nil
if not state.profileDirty and not hasPendingOwnership then return true end
if not state.profileDirty and not hasPendingOwnership and not state.behavioursDirty then return true end
local steamID64 = state.steamID64
if not steamID64 or steamID64 == "" then return false end
@@ -301,6 +333,23 @@ local function SavePlayerState(ply, state)
end
end
if result ~= false and state.behavioursDirty then
result = Query(
"DELETE FROM custom_coop_enabled_behaviours WHERE steamid64 = " .. SQLString(steamID64),
"could not clear enabled behaviours"
)
if result ~= false then
for itemID in pairs(state.enabledBehaviours) do
result = Query(string.format(
"INSERT INTO custom_coop_enabled_behaviours (steamid64, item_id) VALUES (%s, %s)",
SQLString(steamID64), SQLString(itemID)
), "could not save enabled behaviour")
if result == false then break end
end
end
end
if result == false then
sql.Query("ROLLBACK")
return false
@@ -317,6 +366,7 @@ local function SavePlayerState(ply, state)
end
state.profileDirty = false
state.behavioursDirty = false
table.Empty(state.pendingOwnedItems)
return true
end
@@ -427,6 +477,70 @@ local function ApplyWeapon(ply, previousWeaponID)
ply.CustomCoopGrantedWeapon = item.weaponClass
end
function Progression.SetHoverpackThrusting(ply, thrusting)
if not IsValid(ply) then return end
thrusting = thrusting == true
and ply:GetNW2Bool("CustomCoopHoverpackEnabled", false)
and ply:Alive()
and (not ply.GetSuitPower or ply:GetSuitPower() > 0.01)
if ply:GetNW2Bool("CustomCoopHoverpackThrusting", false) == thrusting then return end
ply:SetNW2Bool("CustomCoopHoverpackThrusting", thrusting)
if ply.CustomCoopHoverpackSound then
ply.CustomCoopHoverpackSound:Stop()
ply.CustomCoopHoverpackSound = nil
end
if thrusting then
ply.CustomCoopHoverpackLastThrust = CurTime()
local soundPatch = CreateSound(ply, Progression.HoverpackSound)
if soundPatch then
ply.CustomCoopHoverpackSound = soundPatch
soundPatch:PlayEx(Progression.HoverpackSoundVolume, Progression.HoverpackSoundPitch)
end
end
end
hook.Add("Think", "CustomCoopProgressionHoverpackPower", function()
local now = CurTime()
for _, ply in ipairs(player.GetAll()) do
local previousUpdate = ply.CustomCoopHoverpackPowerUpdated or now
local elapsed = math.Clamp(now - previousUpdate, 0, 0.25)
ply.CustomCoopHoverpackPowerUpdated = now
if elapsed > 0 and ply:GetNW2Bool("CustomCoopHoverpackEnabled", false)
and ply.GetSuitPower and ply.SetSuitPower then
local power = math.Clamp(ply:GetSuitPower(), 0, 100)
local thrusting = ply:GetNW2Bool("CustomCoopHoverpackThrusting", false)
local newPower = power
if thrusting then
ply.CustomCoopHoverpackLastThrust = now
newPower = math.max(power - Progression.HoverpackDrainPerSecond * elapsed, 0)
if newPower <= 0 then Progression.SetHoverpackThrusting(ply, false) end
else
local lastThrust = ply.CustomCoopHoverpackLastThrust or 0
local sprinting = ply:KeyDown(IN_SPEED) and ply:GetVelocity():Length2D() > ply:GetWalkSpeed()
local releasedJump = not ply:KeyDown(IN_JUMP)
if releasedJump and not sprinting
and now - lastThrust >= Progression.HoverpackRechargeDelay then
local walking = ply:OnGround() and ply:GetVelocity():Length2D() > 20
local rechargeRate = walking
and Progression.HoverpackWalkingRechargePerSecond
or Progression.HoverpackRechargePerSecond
newPower = math.min(power + rechargeRate * elapsed, 100)
end
end
if math.abs(newPower - power) >= 0.001 then ply:SetSuitPower(newPower) end
end
end
end)
local function SendShop(ply)
local state = GetState(ply)
if not state then return end
@@ -441,6 +555,9 @@ local function SendShop(ply)
for id in pairs(state.ownedMusic) do
owned[#owned + 1] = { itemType = "music", id = id }
end
for id in pairs(state.ownedBehaviours) do
owned[#owned + 1] = { itemType = "behaviour", id = id }
end
net.Start("CustomCoopProgressionOpenShop")
net.WriteUInt(math.min(state.score, 4294967295), 32)
@@ -452,6 +569,14 @@ local function SendShop(ply)
net.WriteString(owned[index].itemType)
net.WriteString(owned[index].id)
end
local enabledBehaviours = {}
for id in pairs(state.enabledBehaviours) do
enabledBehaviours[#enabledBehaviours + 1] = id
end
net.WriteUInt(math.min(#enabledBehaviours, 255), 8)
for index = 1, math.min(#enabledBehaviours, 255) do
net.WriteString(enabledBehaviours[index])
end
net.WriteBool(ply:IsAdmin())
WriteShopPrices()
net.Send(ply)
@@ -551,7 +676,8 @@ local function PurchaseOrEquip(ply, itemType, itemID)
local ownedItems = itemType == "model" and state.ownedModels
or itemType == "weapon" and state.ownedWeapons
or state.ownedMusic
or itemType == "music" and state.ownedMusic
or state.ownedBehaviours
local price = GetShopPrice(itemType, item)
local alreadyOwned = ownedItems[itemID] == true
local previousWeaponID = state.equippedWeapon
@@ -563,6 +689,17 @@ local function PurchaseOrEquip(ply, itemType, itemID)
return
end
if itemType == "behaviour" and alreadyOwned then
local enabled = state.enabledBehaviours[itemID] ~= true
state.enabledBehaviours[itemID] = enabled or nil
state.behavioursDirty = true
SyncPlayer(ply, state)
if not enabled then Progression.SetHoverpackThrusting(ply, false) end
SendNotice(ply, true, item.name .. (enabled and " enabled." or " disabled."))
SendShop(ply)
return
end
if not alreadyOwned then
if state.credits < price then
adminCreditBypass = Progression.AdminShopTestingBypass and ply:IsAdmin()
@@ -598,6 +735,17 @@ local function PurchaseOrEquip(ply, itemType, itemID)
return
end
if itemType == "behaviour" then
state.enabledBehaviours[itemID] = true
state.behavioursDirty = true
SyncPlayer(ply, state)
local action = adminCreditBypass and "Admin test-unlocked and enabled " or "Purchased and enabled "
SendNotice(ply, true, action .. item.name .. ".")
SendShop(ply)
return
end
if itemType == "model" then
state.equippedModel = itemID
ApplyModel(ply)
@@ -674,7 +822,8 @@ hook.Add("PlayerInitialSpawn", "CustomCoopProgressionLoad", function(ply)
timer.Simple(4, function()
if not IsValid(ply) then return end
ply:ChatPrint("[CO-OP] Kill monsters to earn Score and Credits. Your lifetime Score determines your rank.")
ply:ChatPrint("[CO-OP] Press F3 or type !shop to buy permanent models, bonus weapons, and server music.")
ply:ChatPrint("[CO-OP] Press F3 or type !shop to buy models, weapons, music, and toggleable behaviours.")
ply:ChatPrint("[CO-OP] Enable the Hoverpack in Behaviours, then hold Space after jumping to hover.")
ply:ChatPrint("[CO-OP] Bought music plays for everyone. Type !musicmute if you do not want to hear it.")
ply:ChatPrint("[CO-OP] Dying costs up to " .. Progression.DeathCreditCost .. " Credits, but never reduces Score or rank.")
ply:ChatPrint("[CO-OP] Type !points for your totals. Hold TAB to compare lifetime scores.")
@@ -682,8 +831,9 @@ hook.Add("PlayerInitialSpawn", "CustomCoopProgressionLoad", function(ply)
end)
hook.Add("PlayerDisconnected", "CustomCoopProgressionForget", function(ply)
Progression.SetHoverpackThrusting(ply, false)
local state = playerStates[ply]
if state and (state.profileDirty or next(state.pendingOwnedItems) ~= nil) then
if state and (state.profileDirty or state.behavioursDirty or next(state.pendingOwnedItems) ~= nil) then
disconnectedStates[state.steamID64] = state
end
@@ -705,6 +855,7 @@ hook.Add("PlayerSetModel", "CustomCoopProgressionApplyModel", function(ply)
end)
hook.Add("PlayerSpawn", "CustomCoopProgressionApplyLoadout", function(ply)
Progression.SetHoverpackThrusting(ply, false)
ply.CustomCoopGrantedWeapon = nil
timer.Simple(0.5, function()
@@ -738,6 +889,7 @@ end)
hook.Add("PlayerDeath", "CustomCoopProgressionDeathPenalty", function(victim)
if not IsValid(victim) or victim:IsBot() then return end
Progression.SetHoverpackThrusting(victim, false)
ChargeForDeath(victim)
end)
@@ -806,7 +958,7 @@ concommand.Add("coop_setprice", function(ply, _, args)
local itemID = string.lower(args[2] or "")
local price = tonumber(args[3])
if not GetCatalogueItem(itemType, itemID) or not price then
local usage = "Usage: coop_setprice <model|weapon|music> <item_id> <credits>"
local usage = "Usage: coop_setprice <model|weapon|music|behaviour> <item_id> <credits>"
if IsValid(ply) then ply:ChatPrint("[CO-OP] " .. usage) else print("[CO-OP] " .. usage) end
return
end