Implement hoverpack
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user