Add Trails to shop
This commit is contained in:
@@ -2,10 +2,12 @@
|
||||
|
||||
## Persistent progression and rewards shop
|
||||
|
||||
Players earn one Lifetime Score and one spendable Credit for every eligible
|
||||
monster kill. Lifetime Score determines rank and never decreases. Shop
|
||||
purchases deduct Credits only. Data is committed immediately to Garry's Mod's
|
||||
SQLite database at `garrysmod/sv.db`.
|
||||
Players earn Lifetime Score and spendable Credits for eligible monster kills;
|
||||
harder enemies award more. Lifetime Score determines rank and never decreases.
|
||||
Every ten connected minutes also awards five Credits. Shop purchases deduct
|
||||
Credits only. Changes remain in memory during active play and are written to
|
||||
Garry's Mod's SQLite database at `garrysmod/sv.db` only when the server is
|
||||
empty or shutting down, avoiding gameplay stalls from synchronous SQLite I/O.
|
||||
|
||||
Dying deducts up to five Credits, stopping at zero. It never deducts Lifetime
|
||||
Score and therefore cannot lower a player's rank.
|
||||
@@ -13,11 +15,11 @@ Score and therefore cannot lower a player's rank.
|
||||
Player controls:
|
||||
|
||||
- `F3`, chat `!shop`, or client console `coop_shop`: open the rewards shop
|
||||
- Chat `!points`: print personal Score, Credits, and rank
|
||||
- Chat `!points`: print personal Score, Credits, rank, and total playtime
|
||||
- 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
|
||||
- Hold `TAB`: compare connected players' Lifetime Scores, ranks, and total playtime
|
||||
|
||||
The shop contains persistent Half-Life 2 player models and permanent bonus
|
||||
weapon unlocks. Only one bonus weapon is equipped at a time. It also contains a
|
||||
@@ -46,6 +48,15 @@ Credits by default and immediately grants 90 seconds of server-enforced damage
|
||||
immunity. It creates no ownership record. Attempting to activate it while it is
|
||||
already running reports the remaining time and does not spend Credits.
|
||||
|
||||
A cheaper 30-second God Mode is available for 400 Credits by default. It follows
|
||||
the same rules and does not stack with an already active God Mode timer.
|
||||
|
||||
The Player Trails category contains permanent trail unlocks with one equipped at
|
||||
a time. The included choices use Garry's Mod's built-in LOL, love, plasma,
|
||||
electric, laser, and smoke materials, so they require no extra downloads. The
|
||||
rainbow plasma trail cycles colour continuously. Equipping `No Trail` disables
|
||||
the effect without removing purchased trails.
|
||||
|
||||
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.
|
||||
@@ -59,7 +70,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|behaviour> <item_id> <credits>`
|
||||
`coop_setprice <model|weapon|music|behaviour|trail> <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.
|
||||
@@ -71,9 +82,11 @@ Configuration is in `gamemode/progression_shared.lua`:
|
||||
- `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.Trails`: player-trail materials, colours, properties, and prices
|
||||
- `Progression.Hoverpack*`: hover delay, rise speed, AUX drain/recharge rates,
|
||||
and thruster sound settings
|
||||
- `Progression.MonsterClasses`: NPC classes that award points
|
||||
- `Progression.Playtime*`: in-memory update rate, reward interval, and Credits
|
||||
- `Progression.MonsterClasses`: NPC classes and their Score/Credit rewards
|
||||
|
||||
Catalogue prices are initial defaults. Once seeded, live prices come from the
|
||||
`custom_coop_shop_prices` SQLite table so in-game edits survive crashes and
|
||||
|
||||
+89
-23
@@ -5,10 +5,13 @@ local shopFrame
|
||||
local shopState = {
|
||||
score = 0,
|
||||
credits = 0,
|
||||
playtime = 0,
|
||||
equippedModel = Progression.DefaultModelID,
|
||||
equippedWeapon = Progression.DefaultWeaponID,
|
||||
equippedTrail = Progression.DefaultTrailID,
|
||||
ownedModels = {},
|
||||
ownedWeapons = {},
|
||||
ownedTrails = {},
|
||||
ownedMusic = {},
|
||||
ownedBehaviours = {},
|
||||
enabledBehaviours = {},
|
||||
@@ -107,8 +110,15 @@ hook.Add("PlayerButtonDown", "CustomCoopProgressionF3Fallback", function(ply, bu
|
||||
end)
|
||||
|
||||
local escapeWasDown = false
|
||||
local f3WasDown = false
|
||||
hook.Add("Think", "CustomCoopProgressionShopEscape", function()
|
||||
local escapeDown = input.IsKeyDown(KEY_ESCAPE)
|
||||
local f3Down = input.IsKeyDown(KEY_F3)
|
||||
|
||||
if f3Down and not f3WasDown and IsValid(shopFrame) then
|
||||
ToggleShop()
|
||||
end
|
||||
|
||||
if escapeDown and not escapeWasDown and IsValid(shopFrame) then
|
||||
shopFrame:Close()
|
||||
gui.HideGameUI()
|
||||
@@ -117,6 +127,7 @@ hook.Add("Think", "CustomCoopProgressionShopEscape", function()
|
||||
end)
|
||||
end
|
||||
escapeWasDown = escapeDown
|
||||
f3WasDown = f3Down
|
||||
end)
|
||||
|
||||
local function SelectItem(itemType, itemID)
|
||||
@@ -146,14 +157,17 @@ local function AddItemCard(layout, itemType, item)
|
||||
local isWeapon = itemType == "weapon"
|
||||
local isMusic = itemType == "music"
|
||||
local isBehaviour = itemType == "behaviour"
|
||||
local isTrail = itemType == "trail"
|
||||
local isConsumable = isBehaviour and item.consumable == true
|
||||
local owned = isModel and shopState.ownedModels[item.id]
|
||||
or isWeapon and shopState.ownedWeapons[item.id]
|
||||
or isMusic and shopState.ownedMusic[item.id]
|
||||
or isBehaviour and shopState.ownedBehaviours[item.id]
|
||||
or isTrail and shopState.ownedTrails[item.id]
|
||||
local selected = isModel and shopState.equippedModel == item.id
|
||||
or isWeapon and shopState.equippedWeapon == item.id
|
||||
or isBehaviour and shopState.enabledBehaviours[item.id]
|
||||
or isTrail and shopState.equippedTrail == item.id
|
||||
local currentPrice = CurrentPrice(itemType, item)
|
||||
|
||||
local card = layout:Add("DPanel")
|
||||
@@ -194,6 +208,7 @@ local function AddItemCard(layout, itemType, item)
|
||||
else
|
||||
local previewText = isMusic and "SERVER MUSIC"
|
||||
or isBehaviour and "PLAYER ABILITY"
|
||||
or isTrail and "PLAYER TRAIL"
|
||||
or isWeapon and "MAP LOADOUT"
|
||||
or "NO PREVIEW"
|
||||
local noPreview = AddText(card, previewText, "CustomCoopProgressionHeading", colors.muted)
|
||||
@@ -324,7 +339,8 @@ local function CreateAdminPage(sheet)
|
||||
{ itemType = "model", items = Progression.Models },
|
||||
{ itemType = "weapon", items = Progression.Weapons },
|
||||
{ itemType = "music", items = Progression.Music },
|
||||
{ itemType = "behaviour", items = Progression.Behaviours }
|
||||
{ itemType = "behaviour", items = Progression.Behaviours },
|
||||
{ itemType = "trail", items = Progression.Trails }
|
||||
}) do
|
||||
for _, item in ipairs(catalogue.items) do
|
||||
local row = list:AddLine(catalogue.itemType, item.id, item.name, CurrentPrice(catalogue.itemType, item))
|
||||
@@ -359,6 +375,14 @@ local function OpenShop()
|
||||
shopFrame:SetTitle("")
|
||||
shopFrame:ShowCloseButton(true)
|
||||
shopFrame:MakePopup()
|
||||
shopFrame.OnKeyCodePressed = function(self, keyCode)
|
||||
if keyCode == KEY_F3 then
|
||||
ToggleShop()
|
||||
elseif keyCode == KEY_ESCAPE then
|
||||
self:Close()
|
||||
gui.HideGameUI()
|
||||
end
|
||||
end
|
||||
shopFrame.OnClose = function()
|
||||
shopFrame = nil
|
||||
end
|
||||
@@ -370,20 +394,36 @@ local function OpenShop()
|
||||
local summary = AddText(
|
||||
shopFrame,
|
||||
string.format(
|
||||
"Lifetime Score: %d Credits: %d Rank: %s",
|
||||
"Lifetime Score: %d Credits: %d Rank: %s Played: %s",
|
||||
shopState.score,
|
||||
shopState.credits,
|
||||
Progression.GetRank(shopState.score)
|
||||
Progression.GetRank(shopState.score),
|
||||
Progression.FormatPlaytime(shopState.playtime)
|
||||
),
|
||||
"CustomCoopProgressionBody",
|
||||
colors.accent
|
||||
)
|
||||
summary:SetPos(19, 52)
|
||||
summary:SetSize(shopFrame:GetWide() - 38, 25)
|
||||
summary.Think = function(self)
|
||||
local ply = LocalPlayer()
|
||||
if not IsValid(ply) then return end
|
||||
self:SetText(string.format(
|
||||
"Lifetime Score: %d Credits: %d Rank: %s Played: %s",
|
||||
ply:GetNW2Int("CustomCoopScore", shopState.score),
|
||||
ply:GetNW2Int("CustomCoopCredits", shopState.credits),
|
||||
ply:GetNW2String("CustomCoopRank", Progression.GetRank(shopState.score)),
|
||||
Progression.FormatPlaytime(ply:GetNW2Int("CustomCoopPlaytime", shopState.playtime))
|
||||
))
|
||||
end
|
||||
|
||||
local help = AddText(
|
||||
shopFrame,
|
||||
"Kills add to both totals. Purchases and deaths spend Credits only; Lifetime Score and rank never decrease.",
|
||||
string.format(
|
||||
"Harder enemies pay more. Playing %s earns %d Credits. Purchases and deaths spend Credits only.",
|
||||
Progression.FormatPlaytime(Progression.PlaytimeRewardInterval),
|
||||
Progression.PlaytimeCreditsPerInterval
|
||||
),
|
||||
"CustomCoopProgressionSmall",
|
||||
colors.muted
|
||||
)
|
||||
@@ -398,6 +438,7 @@ local function OpenShop()
|
||||
CreateCataloguePage(sheet, "Bonus Weapons", "weapon", Progression.Weapons)
|
||||
CreateCataloguePage(sheet, "Music", "music", Progression.Music)
|
||||
CreateCataloguePage(sheet, "Behaviours", "behaviour", Progression.Behaviours)
|
||||
CreateCataloguePage(sheet, "Player Trails", "trail", Progression.Trails)
|
||||
if shopState.isAdmin then CreateAdminPage(sheet) end
|
||||
end
|
||||
|
||||
@@ -414,10 +455,13 @@ end
|
||||
net.Receive("CustomCoopProgressionOpenShop", function()
|
||||
shopState.score = net.ReadUInt(32)
|
||||
shopState.credits = net.ReadUInt(32)
|
||||
shopState.playtime = net.ReadUInt(32)
|
||||
shopState.equippedModel = net.ReadString()
|
||||
shopState.equippedWeapon = net.ReadString()
|
||||
shopState.equippedTrail = net.ReadString()
|
||||
shopState.ownedModels = { [Progression.DefaultModelID] = true }
|
||||
shopState.ownedWeapons = { [Progression.DefaultWeaponID] = true }
|
||||
shopState.ownedTrails = { [Progression.DefaultTrailID] = true }
|
||||
shopState.ownedMusic = {}
|
||||
shopState.ownedBehaviours = {}
|
||||
shopState.enabledBehaviours = {}
|
||||
@@ -433,6 +477,8 @@ net.Receive("CustomCoopProgressionOpenShop", function()
|
||||
shopState.ownedMusic[itemID] = true
|
||||
elseif itemType == "behaviour" then
|
||||
shopState.ownedBehaviours[itemID] = true
|
||||
elseif itemType == "trail" then
|
||||
shopState.ownedTrails[itemID] = true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -564,12 +610,13 @@ hook.Add("HUDPaint", "CustomCoopProgressionHUD", function()
|
||||
previousScore = score
|
||||
|
||||
local credits = ply:GetNW2Int("CustomCoopCredits", 0)
|
||||
local playtime = ply:GetNW2Int("CustomCoopPlaytime", 0)
|
||||
local rank = ply:GetNW2String("CustomCoopRank", Progression.GetRank(score))
|
||||
local hoverpackEnabled = ply:GetNW2Bool("CustomCoopHoverpackEnabled", false)
|
||||
local hoverpackThrusting = ply:GetNW2Bool("CustomCoopHoverpackThrusting", false)
|
||||
local godmodeSeconds = math.max(math.ceil(ply:GetNW2Float("CustomCoopGodmodeUntil", 0) - CurTime()), 0)
|
||||
local width = 288
|
||||
local height = 66 + (hoverpackEnabled and 24 or 0) + (godmodeSeconds > 0 and 24 or 0)
|
||||
local height = 88 + (hoverpackEnabled and 24 or 0) + (godmodeSeconds > 0 and 24 or 0)
|
||||
local x, y = ScrW() - width - 18, 18
|
||||
|
||||
draw.RoundedBox(8, x, y, width, height, colors.background)
|
||||
@@ -578,8 +625,10 @@ hook.Add("HUDPaint", "CustomCoopProgressionHUD", function()
|
||||
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)
|
||||
draw.SimpleText("Played " .. Progression.FormatPlaytime(playtime), "CustomCoopProgressionSmall",
|
||||
x + 12, y + 64, colors.muted)
|
||||
|
||||
local statusY = y + 64
|
||||
local statusY = y + 86
|
||||
if hoverpackEnabled then
|
||||
local suitPower = ply.GetSuitPower and math.Round(ply:GetSuitPower()) or 100
|
||||
local status = hoverpackThrusting and ("THRUSTING — AUX " .. suitPower .. "%")
|
||||
@@ -601,24 +650,37 @@ hook.Add("HUDPaint", "CustomCoopProgressionHUD", function()
|
||||
end)
|
||||
|
||||
local function AddProgressionToOfficialRow(ply, row)
|
||||
if row.CustomCoopProgressionAdded then return end
|
||||
if row.CustomCoopProgressionVersion == 2 then return end
|
||||
row.CustomCoopProgressionVersion = 2
|
||||
row.CustomCoopProgressionAdded = true
|
||||
|
||||
row.CustomCoopScore = row:Add("DLabel")
|
||||
row.CustomCoopScore:Dock(RIGHT)
|
||||
row.CustomCoopScore:SetWide(140)
|
||||
row.CustomCoopScore:SetFont("ScoreboardDefault")
|
||||
row.CustomCoopScore:SetTextColor(Color(75, 115, 155))
|
||||
row.CustomCoopScore:SetContentAlignment(5)
|
||||
row.CustomCoopScore:SetTooltip("Lifetime Score")
|
||||
if not IsValid(row.CustomCoopScore) then
|
||||
row.CustomCoopScore = row:Add("DLabel")
|
||||
row.CustomCoopScore:Dock(RIGHT)
|
||||
row.CustomCoopScore:SetWide(140)
|
||||
row.CustomCoopScore:SetFont("ScoreboardDefault")
|
||||
row.CustomCoopScore:SetTextColor(Color(75, 115, 155))
|
||||
row.CustomCoopScore:SetContentAlignment(5)
|
||||
row.CustomCoopScore:SetTooltip("Lifetime Score")
|
||||
end
|
||||
|
||||
row.CustomCoopRank = row:Add("DLabel")
|
||||
row.CustomCoopRank:Dock(RIGHT)
|
||||
row.CustomCoopRank:SetWide(165)
|
||||
row.CustomCoopRank:SetFont("ScoreboardDefault")
|
||||
row.CustomCoopRank:SetTextColor(Color(75, 115, 155))
|
||||
row.CustomCoopRank:SetContentAlignment(5)
|
||||
row.CustomCoopRank:SetTooltip("Co-op Rank")
|
||||
if not IsValid(row.CustomCoopRank) then
|
||||
row.CustomCoopRank = row:Add("DLabel")
|
||||
row.CustomCoopRank:Dock(RIGHT)
|
||||
row.CustomCoopRank:SetWide(165)
|
||||
row.CustomCoopRank:SetFont("ScoreboardDefault")
|
||||
row.CustomCoopRank:SetTextColor(Color(75, 115, 155))
|
||||
row.CustomCoopRank:SetContentAlignment(5)
|
||||
row.CustomCoopRank:SetTooltip("Co-op Rank")
|
||||
end
|
||||
|
||||
row.CustomCoopPlaytime = row:Add("DLabel")
|
||||
row.CustomCoopPlaytime:Dock(RIGHT)
|
||||
row.CustomCoopPlaytime:SetWide(145)
|
||||
row.CustomCoopPlaytime:SetFont("ScoreboardDefault")
|
||||
row.CustomCoopPlaytime:SetTextColor(Color(75, 115, 155))
|
||||
row.CustomCoopPlaytime:SetContentAlignment(5)
|
||||
row.CustomCoopPlaytime:SetTooltip("Total Playtime")
|
||||
|
||||
if IsValid(row.Name) then row.Name:SetFont("ScoreboardDefault") end
|
||||
row:SetTall(42)
|
||||
@@ -631,6 +693,9 @@ local function AddProgressionToOfficialRow(ply, row)
|
||||
local score = ply:GetNW2Int("CustomCoopScore", 0)
|
||||
self.CustomCoopScore:SetText("Score " .. score)
|
||||
self.CustomCoopRank:SetText(ply:GetNW2String("CustomCoopRank", "Newbie"))
|
||||
self.CustomCoopPlaytime:SetText("Played " .. Progression.FormatPlaytime(
|
||||
ply:GetNW2Int("CustomCoopPlaytime", 0)
|
||||
))
|
||||
|
||||
-- Keep the official row behavior, but order players by lifetime score.
|
||||
self:SetZPos(-math.Clamp(score, 0, 32000) + ply:EntIndex())
|
||||
@@ -640,10 +705,11 @@ end
|
||||
local function ExtendOfficialScoreboard()
|
||||
if not IsValid(g_Scoreboard) then return end
|
||||
|
||||
if not g_Scoreboard.CustomCoopProgressionAdded then
|
||||
if g_Scoreboard.CustomCoopProgressionVersion ~= 2 then
|
||||
g_Scoreboard.CustomCoopProgressionVersion = 2
|
||||
g_Scoreboard.CustomCoopProgressionAdded = true
|
||||
g_Scoreboard.PerformLayout = function(self)
|
||||
local width = math.min(ScrW() - 40, 980)
|
||||
local width = math.min(ScrW() - 40, 1120)
|
||||
local height = math.max(ScrH() - 160, 320)
|
||||
self:SetSize(width, height)
|
||||
self:SetPos((ScrW() - width) / 2, (ScrH() - height) / 2)
|
||||
|
||||
+152
-55
@@ -7,8 +7,12 @@ local Progression = CustomCoopProgression
|
||||
|
||||
Progression.KillReward = 1
|
||||
Progression.DeathCreditCost = 5
|
||||
Progression.PlaytimeUpdateInterval = 10
|
||||
Progression.PlaytimeRewardInterval = 600
|
||||
Progression.PlaytimeCreditsPerInterval = 5
|
||||
Progression.DefaultModelID = "kleiner"
|
||||
Progression.DefaultWeaponID = "none"
|
||||
Progression.DefaultTrailID = "none"
|
||||
Progression.MaximumShopPrice = 100000000
|
||||
Progression.MusicCooldownPadding = 10
|
||||
Progression.AdminShopTestingBypass = true
|
||||
@@ -178,72 +182,143 @@ Progression.Behaviours = {
|
||||
price = 500,
|
||||
description = "Hold Space after jumping to hover and rise slowly. Toggle this behaviour on or off."
|
||||
},
|
||||
{
|
||||
id = "godmode_30",
|
||||
name = "God Mode — 30 Seconds",
|
||||
price = 400,
|
||||
duration = 30,
|
||||
consumable = true,
|
||||
godmode = true,
|
||||
description = "Activate 30 seconds of complete damage immunity. Each use is purchased separately."
|
||||
},
|
||||
{
|
||||
id = "godmode_90",
|
||||
name = "God Mode — 90 Seconds",
|
||||
price = 1000,
|
||||
duration = 90,
|
||||
consumable = true,
|
||||
godmode = true,
|
||||
description = "Activate 90 seconds of complete damage immunity. Each use is purchased separately."
|
||||
}
|
||||
}
|
||||
|
||||
-- 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.
|
||||
Progression.MonsterClasses = {
|
||||
npc_advisor = true,
|
||||
npc_antlion = true,
|
||||
npc_antlionguard = true,
|
||||
npc_antlion_worker = true,
|
||||
npc_barnacle = true,
|
||||
npc_clawscanner = true,
|
||||
npc_combine_s = true,
|
||||
npc_combinedropship = true,
|
||||
npc_combinegunship = true,
|
||||
npc_cscanner = true,
|
||||
npc_fastzombie = true,
|
||||
npc_fastzombie_torso = true,
|
||||
npc_headcrab = true,
|
||||
npc_headcrab_black = true,
|
||||
npc_headcrab_fast = true,
|
||||
npc_headcrab_poison = true,
|
||||
npc_helicopter = true,
|
||||
npc_hunter = true,
|
||||
npc_manhack = true,
|
||||
npc_metropolice = true,
|
||||
npc_poisonzombie = true,
|
||||
npc_rollermine = true,
|
||||
npc_sniper = true,
|
||||
npc_stalker = true,
|
||||
npc_strider = true,
|
||||
npc_turret_ceiling = true,
|
||||
npc_turret_floor = true,
|
||||
npc_turret_ground = true,
|
||||
npc_vortigaunt = true,
|
||||
npc_zombie = true,
|
||||
npc_zombie_torso = true,
|
||||
npc_zombine = true,
|
||||
Progression.Trails = {
|
||||
{
|
||||
id = "none",
|
||||
name = "No Trail",
|
||||
price = 0,
|
||||
material = "",
|
||||
description = "Turn off your currently equipped player trail."
|
||||
},
|
||||
{
|
||||
id = "lol",
|
||||
name = "LOLOLOL Trail",
|
||||
price = 75,
|
||||
material = "trails/lol",
|
||||
color = Color(255, 255, 255),
|
||||
description = "Leave a stream of Garry's Mod's classic LOL material behind you."
|
||||
},
|
||||
{
|
||||
id = "love",
|
||||
name = "Love Trail",
|
||||
price = 100,
|
||||
material = "trails/love",
|
||||
color = Color(255, 90, 180),
|
||||
description = "A bright pink trail made from the built-in love material."
|
||||
},
|
||||
{
|
||||
id = "rainbow",
|
||||
name = "Rainbow Plasma",
|
||||
price = 150,
|
||||
material = "trails/plasma",
|
||||
color = Color(255, 255, 255),
|
||||
rainbow = true,
|
||||
description = "A plasma trail that continuously cycles through rainbow colours."
|
||||
},
|
||||
{
|
||||
id = "electric",
|
||||
name = "Electric Blue",
|
||||
price = 125,
|
||||
material = "trails/electric",
|
||||
color = Color(70, 150, 255),
|
||||
additive = true,
|
||||
description = "A glowing blue electrical trail."
|
||||
},
|
||||
{
|
||||
id = "laser",
|
||||
name = "Green Laser",
|
||||
price = 125,
|
||||
material = "trails/laser",
|
||||
color = Color(80, 255, 100),
|
||||
additive = true,
|
||||
description = "A sharp green laser trail."
|
||||
},
|
||||
{
|
||||
id = "smoke",
|
||||
name = "Smoke Trail",
|
||||
price = 50,
|
||||
material = "trails/smoke",
|
||||
color = Color(190, 190, 190),
|
||||
description = "A subtle grey smoke trail."
|
||||
}
|
||||
}
|
||||
|
||||
monster_alien_controller = true,
|
||||
monster_alien_grunt = true,
|
||||
monster_alien_slave = true,
|
||||
monster_apache = true,
|
||||
monster_barnacle = true,
|
||||
monster_bigmomma = true,
|
||||
monster_bullchicken = true,
|
||||
monster_gargantua = true,
|
||||
monster_headcrab = true,
|
||||
monster_houndeye = true,
|
||||
monster_human_assassin = true,
|
||||
monster_human_grunt = true,
|
||||
monster_ichthyosaur = true,
|
||||
monster_leech = true,
|
||||
monster_nihilanth = true,
|
||||
monster_osprey = true,
|
||||
monster_snark = true,
|
||||
monster_tentacle = true,
|
||||
monster_zombie = true
|
||||
-- Only these combat NPC classes award points. The numeric value is the Score
|
||||
-- and Credit reward, so bosses and other dangerous enemies are worth more.
|
||||
-- Keeping an allow-list avoids farming friendly or player-spawned NPCs.
|
||||
Progression.MonsterClasses = {
|
||||
npc_advisor = 25,
|
||||
npc_antlion = 1,
|
||||
npc_antlionguard = 12,
|
||||
npc_antlion_worker = 2,
|
||||
npc_barnacle = 2,
|
||||
npc_clawscanner = 1,
|
||||
npc_combine_s = 3,
|
||||
npc_combinedropship = 15,
|
||||
npc_combinegunship = 20,
|
||||
npc_cscanner = 1,
|
||||
npc_fastzombie = 3,
|
||||
npc_fastzombie_torso = 1,
|
||||
npc_headcrab = 1,
|
||||
npc_headcrab_black = 2,
|
||||
npc_headcrab_fast = 1,
|
||||
npc_headcrab_poison = 2,
|
||||
npc_helicopter = 20,
|
||||
npc_hunter = 8,
|
||||
npc_manhack = 1,
|
||||
npc_metropolice = 2,
|
||||
npc_poisonzombie = 6,
|
||||
npc_rollermine = 2,
|
||||
npc_sniper = 4,
|
||||
npc_stalker = 2,
|
||||
npc_strider = 25,
|
||||
npc_turret_ceiling = 2,
|
||||
npc_turret_floor = 2,
|
||||
npc_turret_ground = 2,
|
||||
npc_vortigaunt = 4,
|
||||
npc_zombie = 2,
|
||||
npc_zombie_torso = 1,
|
||||
npc_zombine = 4,
|
||||
|
||||
monster_alien_controller = 5,
|
||||
monster_alien_grunt = 3,
|
||||
monster_alien_slave = 2,
|
||||
monster_apache = 20,
|
||||
monster_barnacle = 2,
|
||||
monster_bigmomma = 25,
|
||||
monster_bullchicken = 3,
|
||||
monster_gargantua = 25,
|
||||
monster_headcrab = 1,
|
||||
monster_houndeye = 2,
|
||||
monster_human_assassin = 5,
|
||||
monster_human_grunt = 3,
|
||||
monster_ichthyosaur = 6,
|
||||
monster_leech = 1,
|
||||
monster_nihilanth = 40,
|
||||
monster_osprey = 20,
|
||||
monster_snark = 1,
|
||||
monster_tentacle = 8,
|
||||
monster_zombie = 2
|
||||
}
|
||||
|
||||
local function FindByID(items, id)
|
||||
@@ -268,6 +343,10 @@ function Progression.GetBehaviour(id)
|
||||
return FindByID(Progression.Behaviours, id)
|
||||
end
|
||||
|
||||
function Progression.GetTrail(id)
|
||||
return FindByID(Progression.Trails, id)
|
||||
end
|
||||
|
||||
function Progression.GetRank(score)
|
||||
local result = Progression.Ranks[1].name
|
||||
score = math.max(tonumber(score) or 0, 0)
|
||||
@@ -280,6 +359,24 @@ function Progression.GetRank(score)
|
||||
return result
|
||||
end
|
||||
|
||||
function Progression.GetMonsterReward(className)
|
||||
local configured = Progression.MonsterClasses[className]
|
||||
if configured == nil or configured == false then return end
|
||||
if configured == true then configured = Progression.KillReward end
|
||||
return math.max(math.floor(tonumber(configured) or Progression.KillReward), 1)
|
||||
end
|
||||
|
||||
function Progression.FormatPlaytime(totalSeconds)
|
||||
local totalMinutes = math.max(math.floor((tonumber(totalSeconds) or 0) / 60), 0)
|
||||
local days = math.floor(totalMinutes / 1440)
|
||||
local hours = math.floor(totalMinutes % 1440 / 60)
|
||||
local minutes = totalMinutes % 60
|
||||
|
||||
if days > 0 then return string.format("%dd %dh", days, hours) end
|
||||
if hours > 0 then return string.format("%dh %02dm", hours, minutes) end
|
||||
return string.format("%dm", minutes)
|
||||
end
|
||||
|
||||
local function SetHoverpackThrusting(ply, thrusting)
|
||||
if SERVER and Progression.SetHoverpackThrusting then
|
||||
Progression.SetHoverpackThrusting(ply, thrusting)
|
||||
|
||||
+203
-17
@@ -55,8 +55,10 @@ local function CreateTables()
|
||||
player_name TEXT NOT NULL DEFAULT '',
|
||||
lifetime_score INTEGER NOT NULL DEFAULT 0,
|
||||
credits INTEGER NOT NULL DEFAULT 0,
|
||||
playtime_seconds INTEGER NOT NULL DEFAULT 0,
|
||||
equipped_model TEXT NOT NULL DEFAULT 'kleiner',
|
||||
equipped_weapon TEXT NOT NULL DEFAULT 'none'
|
||||
equipped_weapon TEXT NOT NULL DEFAULT 'none',
|
||||
equipped_trail TEXT NOT NULL DEFAULT 'none'
|
||||
)
|
||||
]], "could not create player table")
|
||||
|
||||
@@ -86,6 +88,26 @@ local function CreateTables()
|
||||
PRIMARY KEY (steamid64, item_id)
|
||||
)
|
||||
]], "could not create enabled behaviour table")
|
||||
|
||||
local columns = Query("PRAGMA table_info(custom_coop_players)", "could not inspect player table")
|
||||
local hasEquippedTrail = false
|
||||
local hasPlaytime = false
|
||||
for _, column in ipairs(columns or {}) do
|
||||
if column.name == "equipped_trail" then hasEquippedTrail = true end
|
||||
if column.name == "playtime_seconds" then hasPlaytime = true end
|
||||
end
|
||||
if not hasEquippedTrail then
|
||||
Query(
|
||||
"ALTER TABLE custom_coop_players ADD COLUMN equipped_trail TEXT NOT NULL DEFAULT 'none'",
|
||||
"could not add equipped trail column"
|
||||
)
|
||||
end
|
||||
if not hasPlaytime then
|
||||
Query(
|
||||
"ALTER TABLE custom_coop_players ADD COLUMN playtime_seconds INTEGER NOT NULL DEFAULT 0",
|
||||
"could not add playtime column"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
CreateTables()
|
||||
@@ -94,7 +116,8 @@ local catalogues = {
|
||||
{ itemType = "model", items = Progression.Models },
|
||||
{ itemType = "weapon", items = Progression.Weapons },
|
||||
{ itemType = "music", items = Progression.Music },
|
||||
{ itemType = "behaviour", items = Progression.Behaviours }
|
||||
{ itemType = "behaviour", items = Progression.Behaviours },
|
||||
{ itemType = "trail", items = Progression.Trails }
|
||||
}
|
||||
|
||||
for _, item in ipairs(Progression.Music) do
|
||||
@@ -122,6 +145,7 @@ local function GetCatalogueItem(itemType, itemID)
|
||||
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
|
||||
if itemType == "trail" then return Progression.GetTrail(itemID) end
|
||||
end
|
||||
|
||||
local function GetShopPrice(itemType, item)
|
||||
@@ -177,10 +201,15 @@ local function EmptyState()
|
||||
return {
|
||||
score = 0,
|
||||
credits = 0,
|
||||
playtimeSeconds = 0,
|
||||
playtimeRemainder = 0,
|
||||
playtimeUpdatedAt = CurTime(),
|
||||
equippedModel = Progression.DefaultModelID,
|
||||
equippedWeapon = Progression.DefaultWeaponID,
|
||||
equippedTrail = Progression.DefaultTrailID,
|
||||
ownedModels = { [Progression.DefaultModelID] = true },
|
||||
ownedWeapons = { [Progression.DefaultWeaponID] = true },
|
||||
ownedTrails = { [Progression.DefaultTrailID] = true },
|
||||
ownedMusic = {},
|
||||
ownedBehaviours = {},
|
||||
enabledBehaviours = {},
|
||||
@@ -195,7 +224,9 @@ local function SyncPlayer(ply, state)
|
||||
|
||||
ply:SetNW2Int("CustomCoopScore", state.score)
|
||||
ply:SetNW2Int("CustomCoopCredits", state.credits)
|
||||
ply:SetNW2Int("CustomCoopPlaytime", math.min(math.floor(state.playtimeSeconds or 0), 2147483647))
|
||||
ply:SetNW2String("CustomCoopRank", Progression.GetRank(state.score))
|
||||
ply:SetNW2String("CustomCoopTrail", state.equippedTrail)
|
||||
ply:SetNW2Bool("CustomCoopHoverpackEnabled", state.enabledBehaviours.hoverpack == true)
|
||||
if state.enabledBehaviours.hoverpack and ply.EquipSuit
|
||||
and (not ply.IsSuitEquipped or not ply:IsSuitEquipped()) then
|
||||
@@ -203,6 +234,47 @@ local function SyncPlayer(ply, state)
|
||||
end
|
||||
end
|
||||
|
||||
local function AccruePlaytime(ply, state)
|
||||
if not state then return 0 end
|
||||
|
||||
local now = CurTime()
|
||||
local previousUpdate = state.playtimeUpdatedAt or now
|
||||
state.playtimeUpdatedAt = now
|
||||
state.playtimeRemainder = (state.playtimeRemainder or 0) + math.max(now - previousUpdate, 0)
|
||||
|
||||
local wholeSeconds = math.floor(state.playtimeRemainder)
|
||||
if wholeSeconds <= 0 then return 0 end
|
||||
|
||||
state.playtimeRemainder = state.playtimeRemainder - wholeSeconds
|
||||
local oldPlaytime = math.max(math.floor(state.playtimeSeconds or 0), 0)
|
||||
state.playtimeSeconds = oldPlaytime + wholeSeconds
|
||||
state.profileDirty = true
|
||||
|
||||
local rewardInterval = math.max(math.floor(Progression.PlaytimeRewardInterval or 0), 0)
|
||||
local creditsPerInterval = math.max(math.floor(Progression.PlaytimeCreditsPerInterval or 0), 0)
|
||||
local reward = 0
|
||||
if rewardInterval > 0 and creditsPerInterval > 0 then
|
||||
local oldMilestones = math.floor(oldPlaytime / rewardInterval)
|
||||
local newMilestones = math.floor(state.playtimeSeconds / rewardInterval)
|
||||
reward = math.max(newMilestones - oldMilestones, 0) * creditsPerInterval
|
||||
state.credits = state.credits + reward
|
||||
end
|
||||
|
||||
if IsValid(ply) then
|
||||
state.playerName = ply:Nick()
|
||||
SyncPlayer(ply, state)
|
||||
if reward > 0 then
|
||||
ply:ChatPrint(string.format(
|
||||
"[CO-OP] Playtime reward: +%d Credits for %s played.",
|
||||
reward,
|
||||
Progression.FormatPlaytime(state.playtimeSeconds)
|
||||
))
|
||||
end
|
||||
end
|
||||
|
||||
return reward
|
||||
end
|
||||
|
||||
local function LoadPlayer(ply)
|
||||
if not IsValid(ply) then return end
|
||||
|
||||
@@ -215,13 +287,14 @@ local function LoadPlayer(ply)
|
||||
if disconnectedState then
|
||||
disconnectedStates[steamID64] = nil
|
||||
disconnectedState.playerName = ply:Nick()
|
||||
disconnectedState.playtimeUpdatedAt = CurTime()
|
||||
playerStates[ply] = disconnectedState
|
||||
SyncPlayer(ply, disconnectedState)
|
||||
return disconnectedState
|
||||
end
|
||||
|
||||
local row = Query(
|
||||
"SELECT lifetime_score, credits, equipped_model, equipped_weapon " ..
|
||||
"SELECT lifetime_score, credits, playtime_seconds, equipped_model, equipped_weapon, equipped_trail " ..
|
||||
"FROM custom_coop_players WHERE steamid64 = " .. SQLString(steamID64) .. " LIMIT 1",
|
||||
"could not load player"
|
||||
)
|
||||
@@ -233,8 +306,10 @@ local function LoadPlayer(ply)
|
||||
if istable(row) and row[1] then
|
||||
state.score = math.max(tonumber(row[1].lifetime_score) or 0, 0)
|
||||
state.credits = math.max(tonumber(row[1].credits) or 0, 0)
|
||||
state.playtimeSeconds = math.max(tonumber(row[1].playtime_seconds) or 0, 0)
|
||||
state.equippedModel = row[1].equipped_model or Progression.DefaultModelID
|
||||
state.equippedWeapon = row[1].equipped_weapon or Progression.DefaultWeaponID
|
||||
state.equippedTrail = row[1].equipped_trail or Progression.DefaultTrailID
|
||||
end
|
||||
|
||||
local ownedRows = Query(
|
||||
@@ -252,6 +327,8 @@ local function LoadPlayer(ply)
|
||||
state.ownedMusic[owned.item_id] = true
|
||||
elseif owned.item_type == "behaviour" and Progression.GetBehaviour(owned.item_id) then
|
||||
state.ownedBehaviours[owned.item_id] = true
|
||||
elseif owned.item_type == "trail" and Progression.GetTrail(owned.item_id) then
|
||||
state.ownedTrails[owned.item_id] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -276,6 +353,10 @@ local function LoadPlayer(ply)
|
||||
state.equippedWeapon = Progression.DefaultWeaponID
|
||||
state.profileDirty = true
|
||||
end
|
||||
if not Progression.GetTrail(state.equippedTrail) or not state.ownedTrails[state.equippedTrail] then
|
||||
state.equippedTrail = Progression.DefaultTrailID
|
||||
state.profileDirty = true
|
||||
end
|
||||
|
||||
playerStates[ply] = state
|
||||
SyncPlayer(ply, state)
|
||||
@@ -308,14 +389,16 @@ local function SavePlayerState(ply, state)
|
||||
|
||||
local result = Query(string.format(
|
||||
"INSERT OR REPLACE INTO custom_coop_players " ..
|
||||
"(steamid64, player_name, lifetime_score, credits, equipped_model, equipped_weapon) " ..
|
||||
"VALUES (%s, %s, %d, %d, %s, %s)",
|
||||
"(steamid64, player_name, lifetime_score, credits, playtime_seconds, equipped_model, equipped_weapon, equipped_trail) " ..
|
||||
"VALUES (%s, %s, %d, %d, %d, %s, %s, %s)",
|
||||
SQLString(steamID64),
|
||||
SQLString(state.playerName),
|
||||
math.max(math.floor(state.score or 0), 0),
|
||||
math.max(math.floor(state.credits or 0), 0),
|
||||
math.max(math.floor(state.playtimeSeconds or 0), 0),
|
||||
SQLString(state.equippedModel),
|
||||
SQLString(state.equippedWeapon)
|
||||
SQLString(state.equippedWeapon),
|
||||
SQLString(state.equippedTrail)
|
||||
), "could not save player profile")
|
||||
|
||||
if result ~= false then
|
||||
@@ -442,6 +525,17 @@ cvars.AddChangeCallback("gc_progression_save_interval", function()
|
||||
StartProgressionSaveTimer()
|
||||
end, "CustomCoopProgressionSaveInterval")
|
||||
|
||||
timer.Create(
|
||||
"CustomCoopProgressionAccruePlaytime",
|
||||
math.max(tonumber(Progression.PlaytimeUpdateInterval) or 10, 1),
|
||||
0,
|
||||
function()
|
||||
for _, ply in ipairs(player.GetHumans()) do
|
||||
if not ply:IsBot() then AccruePlaytime(ply, GetState(ply)) end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
local function ApplyModel(ply)
|
||||
local state = GetState(ply)
|
||||
local item = state and Progression.GetModel(state.equippedModel)
|
||||
@@ -477,6 +571,51 @@ local function ApplyWeapon(ply, previousWeaponID)
|
||||
ply.CustomCoopGrantedWeapon = item.weaponClass
|
||||
end
|
||||
|
||||
local function RemovePlayerTrail(ply)
|
||||
if IsValid(ply.CustomCoopTrailEntity) then ply.CustomCoopTrailEntity:Remove() end
|
||||
ply.CustomCoopTrailEntity = nil
|
||||
end
|
||||
|
||||
local function ApplyTrail(ply)
|
||||
if not IsValid(ply) then return end
|
||||
RemovePlayerTrail(ply)
|
||||
|
||||
local state = GetState(ply)
|
||||
local item = state and Progression.GetTrail(state.equippedTrail)
|
||||
if not item or item.material == "" or not ply:Alive() then return end
|
||||
|
||||
local startWidth = math.Clamp(tonumber(item.startWidth) or 24, 1, 128)
|
||||
local endWidth = math.Clamp(tonumber(item.endWidth) or 2, 0, 128)
|
||||
local lifetime = math.Clamp(tonumber(item.lifetime) or 2.5, 0.1, 10)
|
||||
local textureResolution = 1 / math.max((startWidth + endWidth) * 0.5, 0.0001)
|
||||
local color = item.color or color_white
|
||||
|
||||
ply.CustomCoopTrailEntity = util.SpriteTrail(
|
||||
ply,
|
||||
0,
|
||||
color,
|
||||
item.additive == true,
|
||||
startWidth,
|
||||
endWidth,
|
||||
lifetime,
|
||||
textureResolution,
|
||||
item.material .. ".vmt"
|
||||
)
|
||||
end
|
||||
|
||||
hook.Add("Think", "CustomCoopProgressionRainbowTrails", function()
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
if IsValid(ply.CustomCoopTrailEntity) then
|
||||
local state = playerStates[ply]
|
||||
local item = state and Progression.GetTrail(state.equippedTrail)
|
||||
if item and item.rainbow then
|
||||
local color = HSVToColor((CurTime() * 90 + ply:EntIndex() * 23) % 360, 1, 1)
|
||||
ply.CustomCoopTrailEntity:SetColor(color)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function Progression.SetHoverpackThrusting(ply, thrusting)
|
||||
if not IsValid(ply) then return end
|
||||
|
||||
@@ -544,6 +683,7 @@ end)
|
||||
local function SendShop(ply)
|
||||
local state = GetState(ply)
|
||||
if not state then return end
|
||||
AccruePlaytime(ply, state)
|
||||
|
||||
local owned = {}
|
||||
for id in pairs(state.ownedModels) do
|
||||
@@ -558,12 +698,17 @@ local function SendShop(ply)
|
||||
for id in pairs(state.ownedBehaviours) do
|
||||
owned[#owned + 1] = { itemType = "behaviour", id = id }
|
||||
end
|
||||
for id in pairs(state.ownedTrails) do
|
||||
owned[#owned + 1] = { itemType = "trail", id = id }
|
||||
end
|
||||
|
||||
net.Start("CustomCoopProgressionOpenShop")
|
||||
net.WriteUInt(math.min(state.score, 4294967295), 32)
|
||||
net.WriteUInt(math.min(state.credits, 4294967295), 32)
|
||||
net.WriteUInt(math.min(math.floor(state.playtimeSeconds or 0), 4294967295), 32)
|
||||
net.WriteString(state.equippedModel)
|
||||
net.WriteString(state.equippedWeapon)
|
||||
net.WriteString(state.equippedTrail)
|
||||
net.WriteUInt(math.min(#owned, 255), 8)
|
||||
for index = 1, math.min(#owned, 255) do
|
||||
net.WriteString(owned[index].itemType)
|
||||
@@ -658,7 +803,7 @@ local function PlayMusic(ply, item, successMessage)
|
||||
end
|
||||
|
||||
local function ActivateConsumableBehaviour(ply, item)
|
||||
if item.id ~= "godmode_90" then return false end
|
||||
if not item.godmode then return false end
|
||||
|
||||
local now = CurTime()
|
||||
local activeUntil = ply.CustomCoopGodmodeUntil or 0
|
||||
@@ -741,6 +886,7 @@ local function PurchaseOrEquip(ply, itemType, itemID)
|
||||
local ownedItems = itemType == "model" and state.ownedModels
|
||||
or itemType == "weapon" and state.ownedWeapons
|
||||
or itemType == "music" and state.ownedMusic
|
||||
or itemType == "trail" and state.ownedTrails
|
||||
or state.ownedBehaviours
|
||||
local price = GetShopPrice(itemType, item)
|
||||
local alreadyOwned = ownedItems[itemID] == true
|
||||
@@ -810,6 +956,19 @@ local function PurchaseOrEquip(ply, itemType, itemID)
|
||||
return
|
||||
end
|
||||
|
||||
if itemType == "trail" then
|
||||
state.equippedTrail = itemID
|
||||
state.profileDirty = true
|
||||
SyncPlayer(ply, state)
|
||||
ApplyTrail(ply)
|
||||
local action = alreadyOwned and "Equipped "
|
||||
or adminCreditBypass and "Admin test-unlocked and equipped "
|
||||
or "Purchased and equipped "
|
||||
SendNotice(ply, true, action .. item.name .. ".")
|
||||
SendShop(ply)
|
||||
return
|
||||
end
|
||||
|
||||
if itemType == "model" then
|
||||
state.equippedModel = itemID
|
||||
ApplyModel(ply)
|
||||
@@ -846,11 +1005,11 @@ local function ResolvePlayerAttacker(attacker, inflictor)
|
||||
end
|
||||
end
|
||||
|
||||
local function AwardMonsterKill(ply)
|
||||
local function AwardMonsterKill(ply, reward)
|
||||
local state = GetState(ply)
|
||||
if not state then return end
|
||||
|
||||
local reward = math.max(math.floor(Progression.KillReward), 1)
|
||||
reward = math.max(math.floor(tonumber(reward) or Progression.KillReward), 1)
|
||||
state.score = state.score + reward
|
||||
state.credits = state.credits + reward
|
||||
state.playerName = ply:Nick()
|
||||
@@ -885,8 +1044,13 @@ 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 models, weapons, music, and toggleable behaviours.")
|
||||
ply:ChatPrint("[CO-OP] Harder monsters award more Score and Credits. Lifetime Score determines your rank.")
|
||||
ply:ChatPrint(string.format(
|
||||
"[CO-OP] Every %s played awards %d bonus Credits.",
|
||||
Progression.FormatPlaytime(Progression.PlaytimeRewardInterval),
|
||||
Progression.PlaytimeCreditsPerInterval
|
||||
))
|
||||
ply:ChatPrint("[CO-OP] Press F3 or type !shop to buy models, weapons, music, behaviours, and player trails.")
|
||||
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.")
|
||||
@@ -896,7 +1060,9 @@ end)
|
||||
|
||||
hook.Add("PlayerDisconnected", "CustomCoopProgressionForget", function(ply)
|
||||
Progression.SetHoverpackThrusting(ply, false)
|
||||
RemovePlayerTrail(ply)
|
||||
local state = playerStates[ply]
|
||||
if state then AccruePlaytime(nil, state) end
|
||||
if state and (state.profileDirty or state.behavioursDirty or next(state.pendingOwnedItems) ~= nil) then
|
||||
disconnectedStates[state.steamID64] = state
|
||||
end
|
||||
@@ -908,6 +1074,9 @@ hook.Add("PlayerDisconnected", "CustomCoopProgressionForget", function(ply)
|
||||
end)
|
||||
|
||||
hook.Add("ShutDown", "CustomCoopProgressionSaveOnShutdown", function()
|
||||
for ply, state in pairs(playerStates) do
|
||||
AccruePlaytime(ply, state)
|
||||
end
|
||||
FlushDirtyProgression()
|
||||
end)
|
||||
|
||||
@@ -923,7 +1092,10 @@ hook.Add("PlayerSpawn", "CustomCoopProgressionApplyLoadout", function(ply)
|
||||
ply.CustomCoopGrantedWeapon = nil
|
||||
|
||||
timer.Simple(0.5, function()
|
||||
if IsValid(ply) then ApplyWeapon(ply) end
|
||||
if IsValid(ply) then
|
||||
ApplyWeapon(ply)
|
||||
ApplyTrail(ply)
|
||||
end
|
||||
end)
|
||||
|
||||
-- mapperstuff.lua restores its carried model two seconds after some map
|
||||
@@ -939,7 +1111,8 @@ end)
|
||||
|
||||
hook.Add("OnNPCKilled", "CustomCoopProgressionMonsterKilled", function(npc, attacker, inflictor)
|
||||
if not IsValid(npc) or npc.CustomCoopNoScore or npc.CustomCoopScoreAwarded then return end
|
||||
if not Progression.MonsterClasses[npc:GetClass()] then return end
|
||||
local reward = Progression.GetMonsterReward(npc:GetClass())
|
||||
if not reward then return end
|
||||
|
||||
local creator = npc:GetCreator()
|
||||
if IsValid(creator) and creator:IsPlayer() then return end
|
||||
@@ -948,7 +1121,7 @@ hook.Add("OnNPCKilled", "CustomCoopProgressionMonsterKilled", function(npc, atta
|
||||
if not IsValid(ply) or ply:IsBot() then return end
|
||||
|
||||
npc.CustomCoopScoreAwarded = true
|
||||
AwardMonsterKill(ply)
|
||||
AwardMonsterKill(ply, reward)
|
||||
end)
|
||||
|
||||
hook.Add("EntityTakeDamage", "CustomCoopProgressionTimedGodmode", function(target, damageInfo)
|
||||
@@ -962,9 +1135,18 @@ end)
|
||||
hook.Add("PlayerDeath", "CustomCoopProgressionDeathPenalty", function(victim)
|
||||
if not IsValid(victim) or victim:IsBot() then return end
|
||||
Progression.SetHoverpackThrusting(victim, false)
|
||||
RemovePlayerTrail(victim)
|
||||
ChargeForDeath(victim)
|
||||
end)
|
||||
|
||||
hook.Add("PostCleanupMap", "CustomCoopProgressionRestoreTrails", function()
|
||||
timer.Simple(0.5, function()
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
if IsValid(ply) then ApplyTrail(ply) end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
hook.Add("PlayerSay", "CustomCoopProgressionChatCommands", function(ply, text)
|
||||
local command = string.Trim(string.lower(text or ""))
|
||||
if command == "!shop" or command == "/shop" then
|
||||
@@ -975,9 +1157,13 @@ hook.Add("PlayerSay", "CustomCoopProgressionChatCommands", function(ply, text)
|
||||
if command == "!points" or command == "/points" then
|
||||
local state = GetState(ply)
|
||||
if state then
|
||||
AccruePlaytime(ply, state)
|
||||
ply:ChatPrint(string.format(
|
||||
"[CO-OP] Score: %d | Credits: %d | Rank: %s",
|
||||
state.score, state.credits, Progression.GetRank(state.score)
|
||||
"[CO-OP] Score: %d | Credits: %d | Rank: %s | Played: %s",
|
||||
state.score,
|
||||
state.credits,
|
||||
Progression.GetRank(state.score),
|
||||
Progression.FormatPlaytime(state.playtimeSeconds)
|
||||
))
|
||||
end
|
||||
return ""
|
||||
@@ -1030,7 +1216,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|behaviour> <item_id> <credits>"
|
||||
local usage = "Usage: coop_setprice <model|weapon|music|behaviour|trail> <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