638 lines
18 KiB
Lua
638 lines
18 KiB
Lua
-- Shared, server-authoritative progression configuration.
|
|
-- Prices and rewards are only acted upon by sv_progression.lua. Clients receive
|
|
-- this file so the shop can render the same catalogue without duplicating it.
|
|
|
|
CustomCoopProgression = CustomCoopProgression or {}
|
|
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
|
|
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.ShopSpawnDistance = 500
|
|
Progression.PurchasedEntityLimit = 12
|
|
Progression.PurchasedHelperLimit = 6
|
|
|
|
Progression.Ranks = {
|
|
{ score = 0, name = "Newbie" },
|
|
{ score = 10, name = "Survivor" },
|
|
{ score = 50, name = "Hunter" },
|
|
{ score = 150, name = "Slayer" },
|
|
{ score = 500, name = "Veteran" },
|
|
{ score = 1500, name = "Exterminator" },
|
|
{ score = 5000, name = "Legend" }
|
|
}
|
|
|
|
Progression.Models = {
|
|
{
|
|
id = "kleiner",
|
|
name = "Dr. Kleiner",
|
|
model = "models/player/kleiner.mdl",
|
|
price = 0,
|
|
description = "The free default model."
|
|
},
|
|
{
|
|
id = "male_citizen",
|
|
name = "Male Citizen",
|
|
model = "models/player/group01/male_07.mdl",
|
|
price = 5,
|
|
description = "A survivor from City 17."
|
|
},
|
|
{
|
|
id = "female_citizen",
|
|
name = "Female Citizen",
|
|
model = "models/player/group01/female_04.mdl",
|
|
price = 5,
|
|
description = "A survivor from City 17."
|
|
},
|
|
{
|
|
id = "barney",
|
|
name = "Barney Calhoun",
|
|
model = "models/player/barney.mdl",
|
|
price = 10,
|
|
description = "Civil Protection's least loyal officer."
|
|
},
|
|
{
|
|
id = "alyx",
|
|
name = "Alyx Vance",
|
|
model = "models/player/alyx.mdl",
|
|
price = 15,
|
|
description = "Resistance fighter and engineer."
|
|
},
|
|
{
|
|
id = "monk",
|
|
name = "Father Grigori",
|
|
model = "models/player/monk.mdl",
|
|
price = 25,
|
|
description = "A familiar face from Ravenholm."
|
|
},
|
|
{
|
|
id = "combine",
|
|
name = "Combine Soldier",
|
|
model = "models/player/combine_soldier.mdl",
|
|
price = 50,
|
|
description = "Standard Combine infantry."
|
|
},
|
|
{
|
|
id = "combine_elite",
|
|
name = "Combine Elite",
|
|
model = "models/player/combine_super_soldier.mdl",
|
|
price = 100,
|
|
description = "An elite Overwatch soldier."
|
|
}
|
|
}
|
|
|
|
Progression.Weapons = {
|
|
{
|
|
id = "none",
|
|
name = "No Bonus Weapon",
|
|
weaponClass = "",
|
|
price = 0,
|
|
description = "Use only weapons supplied by the map."
|
|
},
|
|
{
|
|
id = "glock",
|
|
name = "Glock",
|
|
weaponClass = "coop_glock",
|
|
price = 15,
|
|
description = "A reliable permanent sidearm unlock."
|
|
},
|
|
{
|
|
id = "p228",
|
|
name = "P228",
|
|
weaponClass = "coop_p228",
|
|
price = 20,
|
|
description = "A permanent accurate 13-round sidearm unlock."
|
|
},
|
|
{
|
|
id = "usp",
|
|
name = "USP",
|
|
weaponClass = "coop_usp",
|
|
price = 25,
|
|
description = "A permanent tactical sidearm unlock with a detachable suppressor."
|
|
},
|
|
{
|
|
id = "fiveseven",
|
|
name = "Five-Seven",
|
|
weaponClass = "coop_fiveseven",
|
|
price = 30,
|
|
description = "A permanent fast-firing 20-round sidearm unlock."
|
|
},
|
|
{
|
|
id = "mac10",
|
|
name = "MAC-10",
|
|
weaponClass = "coop_mac10",
|
|
price = 35,
|
|
description = "A permanent compact automatic weapon unlock with a very high fire rate."
|
|
},
|
|
{
|
|
id = "mp5",
|
|
name = "MP5",
|
|
weaponClass = "coop_mp5",
|
|
price = 40,
|
|
description = "A permanent compact SMG unlock."
|
|
},
|
|
{
|
|
id = "pumpshotgun",
|
|
name = "Pump Shotgun",
|
|
weaponClass = "coop_pumpshotgun",
|
|
price = 75,
|
|
description = "A permanent close-range weapon unlock."
|
|
},
|
|
{
|
|
id = "famas",
|
|
name = "FAMAS",
|
|
weaponClass = "coop_famas",
|
|
price = 100,
|
|
description = "A permanent accurate, controllable automatic rifle unlock."
|
|
},
|
|
{
|
|
id = "ak47",
|
|
name = "AK-47",
|
|
weaponClass = "coop_ak47",
|
|
price = 110,
|
|
description = "A permanent hard-hitting rifle unlock with substantial recoil."
|
|
},
|
|
{
|
|
id = "m4",
|
|
name = "M4",
|
|
weaponClass = "coop_m4",
|
|
price = 125,
|
|
description = "A permanent rifle unlock."
|
|
},
|
|
{
|
|
id = "deagle",
|
|
name = "Desert Eagle",
|
|
weaponClass = "coop_deagle",
|
|
price = 150,
|
|
description = "A permanent seven-round hand cannon unlock with very high damage."
|
|
},
|
|
{
|
|
id = "autoshotgun",
|
|
name = "Automatic Shotgun",
|
|
weaponClass = "coop_autoshotgun",
|
|
price = 225,
|
|
description = "A permanent rapid-fire combat shotgun unlock."
|
|
},
|
|
{
|
|
id = "m249",
|
|
name = "M249",
|
|
weaponClass = "coop_para",
|
|
price = 300,
|
|
description = "A permanent light machine gun unlock with a 100-round magazine."
|
|
},
|
|
{
|
|
id = "nyan_gun",
|
|
name = "Nyan Gun",
|
|
weaponClass = "weapon_nyangun",
|
|
price = 1000,
|
|
description = "Rainbows, music and extremely colourful destruction."
|
|
},
|
|
{
|
|
id = "lightsaber",
|
|
name = "Lightsaber",
|
|
weaponClass = "weapon_lightsaber",
|
|
price = 1000,
|
|
description = "A permanent weapon from a galaxy far, far away."
|
|
},
|
|
{
|
|
id = "orbital_friendship_cannon",
|
|
name = "Orbital Friendship Cannon",
|
|
weaponClass = "orbital_friendship_cannon",
|
|
price = 5000,
|
|
description = "Deliver friendship from orbit."
|
|
},
|
|
{
|
|
id = "sexyness",
|
|
name = "Sexyness (2-bit)",
|
|
weaponClass = "pist_weagon",
|
|
price = 100000,
|
|
description = "The absurdly powerful Ultimate Admin Gun."
|
|
}
|
|
}
|
|
|
|
Progression.Music = {
|
|
{
|
|
id = "trololo",
|
|
name = "Trolololo",
|
|
sound = "custom_coop_music/trololo.mp3",
|
|
price = 250,
|
|
duration = 162,
|
|
spatial = true,
|
|
minimumDistance = 120,
|
|
maximumDistance = 2200,
|
|
description = "Your character plays this song as moving, positional audio. Unlocks permanently."
|
|
}
|
|
}
|
|
|
|
Progression.Behaviours = {
|
|
{
|
|
id = "hoverpack",
|
|
name = "Hoverpack",
|
|
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."
|
|
}
|
|
}
|
|
|
|
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."
|
|
}
|
|
}
|
|
|
|
-- These are consumable purchases: every successful purchase spawns one
|
|
-- server-controlled entity at the surface the player is aiming at.
|
|
Progression.Entities = {
|
|
{
|
|
id = "bouncy_ball",
|
|
name = "Bouncy Ball",
|
|
class = "sent_ball",
|
|
model = "models/Combine_Helicopter/helicopter_bomb01.mdl",
|
|
price = 10,
|
|
spawnOffset = 24,
|
|
ballSize = 24,
|
|
description = "A colourful edible ball that bounces and restores 5 health when used."
|
|
},
|
|
{
|
|
id = "glowstick",
|
|
name = "Cyan Glowstick",
|
|
class = "gmod_light",
|
|
model = "models/maxofs2d/light_tubular.mdl",
|
|
price = 20,
|
|
spawnOffset = 10,
|
|
color = Color(60, 220, 255),
|
|
brightness = 2,
|
|
lightSize = 320,
|
|
description = "A portable physics light for dark corners. It remains on until removed or cleaned up."
|
|
},
|
|
{
|
|
id = "healthkit",
|
|
name = "Health Kit",
|
|
class = "item_healthkit",
|
|
model = "models/items/healthkit.mdl",
|
|
price = 25,
|
|
spawnOffset = 10,
|
|
description = "Drop a Half-Life 2 health kit at the aimed location."
|
|
},
|
|
{
|
|
id = "suit_battery",
|
|
name = "Suit Battery",
|
|
class = "item_battery",
|
|
model = "models/items/battery.mdl",
|
|
price = 35,
|
|
spawnOffset = 10,
|
|
description = "Drop a suit battery for armour and AUX-equipped survivors."
|
|
},
|
|
{
|
|
id = "explosive_barrel",
|
|
name = "Explosive Barrel",
|
|
class = "prop_physics",
|
|
model = "models/props_c17/oildrum001_explosive.mdl",
|
|
price = 60,
|
|
spawnOffset = 48,
|
|
description = "A physics barrel that explodes when sufficiently damaged. Keep your distance."
|
|
},
|
|
{
|
|
id = "tnt",
|
|
name = "Timed TNT",
|
|
class = "gmod_dynamite",
|
|
model = "models/dynamite/dynamite.mdl",
|
|
price = 125,
|
|
spawnOffset = 8,
|
|
fuse = 5,
|
|
damage = 250,
|
|
description = "A 250-damage TNT charge that detonates five seconds after purchase."
|
|
},
|
|
{
|
|
id = "red_tnt",
|
|
name = "Red TNT — Shoot to Detonate",
|
|
class = "sent_tnt",
|
|
model = "models/dav0r/tnt/tnt.mdl",
|
|
price = 150,
|
|
spawnOffset = 16,
|
|
description = "The red Nuke Pack TNT. Damage it to detonate it; nearby charges can chain-react."
|
|
}
|
|
}
|
|
|
|
-- Helpers are also consumable purchases. They join the Resistance squad, but
|
|
-- their kills never award progression points to avoid an automatic score farm.
|
|
Progression.Helpers = {
|
|
{
|
|
id = "rebel",
|
|
name = "Rebel",
|
|
class = "npc_citizen",
|
|
model = "models/Humans/Group03/Male_07.mdl",
|
|
weapon = "weapon_smg1",
|
|
citizenType = 3,
|
|
price = 175,
|
|
description = "An armed Resistance fighter with an SMG."
|
|
},
|
|
{
|
|
id = "rebel_medic",
|
|
name = "Rebel Paramedic",
|
|
class = "npc_citizen",
|
|
model = "models/Humans/Group03m/Male_07.mdl",
|
|
weapon = "weapon_smg1",
|
|
citizenType = 3,
|
|
medic = true,
|
|
price = 275,
|
|
description = "An armed Resistance medic who can heal nearby players and allies."
|
|
},
|
|
{
|
|
id = "barney",
|
|
name = "Barney Calhoun",
|
|
class = "npc_barney",
|
|
model = "models/barney.mdl",
|
|
weapon = "weapon_smg1",
|
|
price = 325,
|
|
description = "A dependable Resistance companion armed with an SMG."
|
|
},
|
|
{
|
|
id = "alyx",
|
|
name = "Alyx Vance",
|
|
class = "npc_alyx",
|
|
model = "models/alyx.mdl",
|
|
weapon = "weapon_alyxgun",
|
|
price = 350,
|
|
description = "A fast and capable Resistance fighter with her signature weapon."
|
|
},
|
|
{
|
|
id = "father_grigori",
|
|
name = "Father Grigori",
|
|
class = "npc_monk",
|
|
model = "models/monk.mdl",
|
|
weapon = "weapon_annabelle",
|
|
price = 400,
|
|
description = "Ravenholm's fearless guardian, armed with Annabelle."
|
|
},
|
|
{
|
|
id = "rebel_turret",
|
|
name = "Rebel Floor Turret",
|
|
class = "npc_turret_floor",
|
|
model = "models/Combine_turrets/Floor_turret.mdl",
|
|
friendlyTurret = true,
|
|
price = 500,
|
|
spawnOffset = 4,
|
|
description = "A stationary floor turret configured to fight for the Resistance."
|
|
}
|
|
}
|
|
|
|
-- 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)
|
|
for _, item in ipairs(items) do
|
|
if item.id == id then return item end
|
|
end
|
|
end
|
|
|
|
function Progression.GetModel(id)
|
|
return FindByID(Progression.Models, id)
|
|
end
|
|
|
|
function Progression.GetWeapon(id)
|
|
return FindByID(Progression.Weapons, id)
|
|
end
|
|
|
|
function Progression.GetMusic(id)
|
|
return FindByID(Progression.Music, id)
|
|
end
|
|
|
|
function Progression.GetBehaviour(id)
|
|
return FindByID(Progression.Behaviours, id)
|
|
end
|
|
|
|
function Progression.GetTrail(id)
|
|
return FindByID(Progression.Trails, id)
|
|
end
|
|
|
|
function Progression.GetEntity(id)
|
|
return FindByID(Progression.Entities, id)
|
|
end
|
|
|
|
function Progression.GetHelper(id)
|
|
return FindByID(Progression.Helpers, id)
|
|
end
|
|
|
|
function Progression.GetRank(score)
|
|
local result = Progression.Ranks[1].name
|
|
score = math.max(tonumber(score) or 0, 0)
|
|
|
|
for _, rank in ipairs(Progression.Ranks) do
|
|
if score < rank.score then break end
|
|
result = rank.name
|
|
end
|
|
|
|
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)
|
|
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)
|