This commit is contained in:
lifestorm
2024-08-04 23:12:27 +03:00
parent 0e770b2b49
commit ba1fc01b16
7084 changed files with 2173495 additions and 14 deletions

View File

@@ -0,0 +1,491 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local PLUGIN = PLUGIN
-- vars for the radio displays
PLUGIN.pitch = 180
PLUGIN.yaw = 270
PLUGIN.roll = 270
PLUGIN.x = 8.7
PLUGIN.y = -14
PLUGIN.z = 17
-- stores the vector that the signal lines are pointing to, per entity
-- tracks update targets for lerping
local mradioSigLines = {}
local leftMostSigLine = Vector(56, 62, 0)
local possibleSigLineTargets = {
[1] = Vector(103, 62, 0),
[2] = Vector(95, 55, 0),
[3] = Vector(89, 52, 0),
[4] = Vector(98, 57, 0),
[5] = Vector(101, 59, 0),
}
-- stores the X that the frequency cursor should be at
-- stores data used for lerping on frequency changes
local mradioFreqLines = {}
-- used to illuminate a 'hitbox' around the interactable 3d2d elements
local hitboxTargets = {
volume = {
x = 69,
y = 181,
w = 25,
h = 25,
shouldDraw = false
},
onOff = {
x = 160,
y = 130,
w = 40,
h = 30,
shouldDraw = false
},
dial = {
x = 280,
y = 170,
w = 50,
h = 50,
shouldDraw = false
},
}
local function drawActiveHitbox()
for _, hitbox in pairs(hitboxTargets) do
if (hitbox.shouldDraw) then
surface.SetDrawColor(180, 21, 0, 120)
surface.DrawOutlinedRect(hitbox.x, hitbox.y, hitbox.w, hitbox.h, 2)
end
end
end
-- lerp between current and desired line position; draw
local function drawMRadioSigLine(ent)
local idx = ent:EntIndex()
if (!mradioSigLines[idx] or !istable(mradioSigLines[idx])) then
return
end
local lineDat = mradioSigLines[idx]
lineDat.lineLastV = LerpVector(math.Clamp((SysTime() - (lineDat.lastUpdate or SysTime())) / 15, 0, 1),
lineDat.lineLastV or leftMostSigLine,
lineDat.lineNextV or leftMostSigLine)
if (ent:IsPlayingMusic()) then
render.DrawLine(Vector(83, 75, 0),
lineDat.lineLastV, Color(255, 0, 0), true)
end
end
-- update the desired position of the line
local function updateMRadioSigLine(ent)
local idx = ent:EntIndex()
if (!mradioSigLines[idx] or !istable(mradioSigLines[idx])) then
mradioSigLines[idx] = {}
mradioSigLines[idx].lineLastV = leftMostSigLine
mradioSigLines[idx].lineNextV = possibleSigLineTargets[1]
mradioSigLines[idx].lastUpdate = SysTime()
end
if (ent:IsPlayingMusic()) then
-- pick a random place towards the right to point to
mradioSigLines[idx].lineNextV = possibleSigLineTargets[math.random(1,
table.Count(possibleSigLineTargets))]
else
mradioSigLines[idx].lineNextV = leftMostSigLine
end
mradioSigLines[idx].lastUpdate = SysTime()
end
local function drawMRadioDLight(ent, pos)
if (ent:IsPlayingMusic()) then
local dlight = DynamicLight(ent:EntIndex())
if (dlight) then
dlight.pos = pos
dlight.r = 124
dlight.g = 107
dlight.b = 72
dlight.brightness = 1
dlight.decay = 256
dlight.size = 128
dlight.dietime = CurTime() + 1
dlight.style = 6 -- strobe
end
end
end
local function drawMRadioSigLevel(ent, pos, angle, scale, alpha)
surface.SetTextColor(0, 0, 0, alpha)
-- top left background
if (ent:IsPlayingMusic()) then
surface.SetDrawColor(209, 179, 120)
else
surface.SetDrawColor(12, 12, 12)
end
surface.DrawRect(46, 34, 76, 46)
surface.SetDrawColor(0, 0, 0)
surface.DrawOutlinedRect(46, 34, 76, 46, 2)
surface.SetTextPos(48, 35)
surface.SetFont("radioSurfaceSm")
surface.DrawText("SIGNAL LEVEL")
surface.SetTextPos(75, 65)
surface.SetFont("radioSurfaceXs")
surface.DrawText("dBm")
-- draw the db guides
local curDBm, _x, _y, symb
surface.SetDrawColor(0, 0, 0, alpha - 50)
for i=1, 5 do
curDBm = -30 + (10 * i) -- -20, -10, 0, +10, +20
_x = 38 + (13 * i)
if (i == 3) then
_y = 48
elseif (i == 2 or i == 4) then
_y = 50
else
_y = 52
end
if (i >= 4) then
symb = "+"
elseif (i == 3) then
symb = " " -- fix spacing for 0dbm
else
symb = ""
end
surface.SetTextPos(_x, _y)
surface.DrawText(symb..curDBm)
end
-- draw the vert guidelines
local markerAlpha = alpha - 50
if (!ent:IsPlayingMusic()) then
markerAlpha = 0
end
for i=1, 20 do
if (i > 10 and i <= 15) then
surface.SetDrawColor(2, 220, 17, markerAlpha)
elseif (i > 15) then
surface.SetDrawColor(255, 0, 0, markerAlpha)
else
surface.SetDrawColor(14, 15, 41, markerAlpha)
end
surface.DrawRect(52 + (3 * i), 61, 1, 2)
end
surface.SetDrawColor(0, 0, 0, alpha)
surface.DrawOutlinedRect(50, 46, 68, 30, 1)
end
local function drawMRadioFreqDialLine(ent, pos, ang, scale, alpha, freq)
if (!ent:IsPlayingMusic()) then return end
local idx = ent:EntIndex()
if (!mradioFreqLines[idx] or !istable(mradioFreqLines[idx])) then
return
end
local lineDat = mradioFreqLines[idx]
lineDat.lineLastX = Lerp(math.Clamp((SysTime() - (lineDat.lastUpdate or SysTime())) / 20, 0, 1),
lineDat.lineLastX, lineDat.lineNextX)
surface.SetDrawColor(255, 0, 0, alpha + 40)
surface.DrawRect(lineDat.lineLastX, 47, 4, 21)
end
local function updateMRadioFreqDialLine(ent)
local idx = ent:EntIndex()
if (!mradioFreqLines[idx] or !istable(mradioFreqLines[idx])) then
mradioFreqLines[idx] = {}
mradioFreqLines[idx].lineLastX = 220
mradioFreqLines[idx].lineNextX = 220
mradioFreqLines[idx].lastUpdate = SysTime()
end
if (ent:IsPlayingMusic()) then
-- pick a random place towards the right to point to
local ch = ent:GetChan()
if (ch and string.len(ch) > 0) then
local chan = ix.musicRadio:GetChannel(ch)
if (chan and istable(chan)) then
mradioFreqLines[idx].lineNextX = chan.freqMap.dispX or 220
end
end
else
mradioFreqLines[idx].lineNextX = 220
end
mradioFreqLines[idx].lastUpdate = SysTime()
end
local function drawMRadioOnOff(ent, pos, ang, scale, alpha)
surface.SetDrawColor(30, 32, 28, 129)
surface.DrawRect(166, 136, 30, 20)
surface.SetDrawColor(0, 0, 0, 255)
surface.DrawOutlinedRect(166, 136, 30, 20, 1)
surface.SetFont("radioSurfaceSmLight")
surface.SetTextColor(0, 0, 0)
if (ent:IsPlayingMusic()) then
surface.SetTextColor(40, 180, 0, alpha - 35)
end
surface.SetTextPos(171, 137)
surface.DrawText("ON")
end
local function drawMRadioVolume(ent, pos, ang, scale, alpha)
surface.SetDrawColor(0, 0, 0, alpha - 70)
surface.SetFont("radioSurfaceSm")
local function _drawMRadioVolumeButton(x, y, text, bOn)
surface.SetTextColor(141, 141, 141, alpha - 30)
surface.SetTextPos(x + 2, y - 1)
if (bOn and ent:IsPlayingMusic()) then
surface.SetTextColor(35, 175, 23, alpha)
end
surface.DrawText(text)
end
local onButton = ent:GetIlluminatedVolButton()
local volButtons = {
[1] = { x = 53, y = 202 },
[2] = { x = 50, y = 191 },
[3] = { x = 53, y = 180 },
[4] = { x = 61, y = 170 },
[5] = { x = 72, y = 164 },
[6] = { x = 84, y = 166 },
[7] = { x = 95, y = 172 },
[8] = { x = 101, y = 181 },
[9] = { x = 104, y = 191 },
[10] = { x = 99, y = 202 },
}
for key, button in ipairs(volButtons) do
if (key == onButton) then
_drawMRadioVolumeButton(button.x, button.y, tostring(key), true)
else
_drawMRadioVolumeButton(button.x, button.y, tostring(key), false)
end
end
end
local function drawMRadioSurface(ent, pos, ang, scale)
local backlightAlphaPulse = 150
if (ent:IsPlayingMusic()) then
backlightAlphaPulse = 255 - math.Clamp(
math.floor(math.sin(CurTime() * 120) * 100) - 10,
10,
30
)
end
drawMRadioSigLevel(ent, pos, angle, scale, backlightAlphaPulse)
drawMRadioSigLine(ent)
-- large display center
if (ent.IsPlayingMusic and ent:IsPlayingMusic()) then
surface.SetDrawColor(209, 179, 120)
else
surface.SetDrawColor(12, 12, 12)
end
surface.DrawRect(160, 34, 340, 68)
surface.SetDrawColor(10, 10, 10)
surface.DrawOutlinedRect(160, 34, 340, 68, 2)
-- draw the frequency info
surface.SetDrawColor(0, 0, 0, backlightAlphaPulse)
surface.DrawOutlinedRect(180, 77, 280, 18, 2)
surface.SetFont("radioSurfaceSm")
local xStep = 50
for i=1, 6 do
surface.SetTextPos(135 + (xStep * i), 80)
surface.DrawText(tostring(500 + (50 * i)))
surface.DrawRect(135 + (xStep * i) + 10, 69, 4, 8) -- center
if (i == 1) then
-- draw first guideline
surface.DrawRect(184, 73, 2, 4)
end
if (i < 6) then
-- draw four guidelines right of current (unless at end)
surface.DrawRect(135 + (xStep * i) + 22.5, 73, 2, 4)
surface.DrawRect(135 + (xStep * i) + 35, 73, 2, 4)
surface.DrawRect(135 + (xStep * i) + 47.5, 73, 2, 4)
end
end
surface.SetDrawColor(0, 0, 0, backlightAlphaPulse)
surface.DrawOutlinedRect(180, 45, 280, 35, 2)
drawMRadioFreqDialLine(ent, pos, angle, scale, backlightAlphaPulse)
-- draw the light from the backlight
drawMRadioDLight(ent, ent:LocalToWorld(Vector(10, 5, 1)))
-- draw the toggle on/off button
drawMRadioOnOff(ent, pos, ang, scale, backlightAlphaPulse)
-- draw the volume button
drawMRadioVolume(ent, pos, ang, scale, backlightAlphaPulse)
end
do
-- step one: use consolas for everything
-- step two: profit???
surface.CreateFont("radioSurfaceXs", {
font = "Consolas",
size = 9,
extended = true,
weight = 400,
antialias = true,
})
surface.CreateFont("radioSurfaceSm", {
font = "Consolas",
size = 12,
extended = true,
weight = 400,
antialias = true,
})
surface.CreateFont("radioSurfaceSmLight", {
font = "Open Sans Bold",
size = 18,
extended = true,
weight = 200,
antialias = true,
})
surface.CreateFont("radioSurfaceReg", {
font = "Consolas",
size = 16,
extended = true,
weight = 400,
antialias = true,
})
surface.CreateFont("radioSurfaceBold", {
font = "Consolas",
extended = true,
size = 20,
weight = 800,
antialias = true,
})
timer.Create("UpdateMusicRadioDisplayData", 0.15, 0, function()
for _, ent in ipairs(ents.FindByClass("wn_musicradio")) do
if (ent and ent.IsPlayingMusic) then
updateMRadioSigLine(ent)
updateMRadioFreqDialLine(ent)
end
end
end)
end
hook.Add("PostDrawOpaqueRenderables", "draw_music_radio_ui", function()
local epos
local eang
local lpos
local lang
local scale = 0.05 -- dat nice scale for good text (still not enough)
local distsqr = 500 ^ 2
local client = LocalPlayer()
for _, ent in ipairs(ents.FindByClass("wn_musicradio")) do
if (client:GetPos():DistToSqr(ent:GetPos()) < distsqr) then
if (!ent or !IsEntity(ent)) then
continue
end
epos = ent:GetPos()
eang = ent:GetAngles()
if (!epos or !isvector(epos) or !eang or !isangle(eang)) then
continue
end
--[[
Set the pos and angle so that they match the face of the radio prop
And so that they align to the top left corner
And are local to the entity in worldspace
]]
lpos = ent:LocalToWorld(Vector(PLUGIN.x, PLUGIN.y, PLUGIN.z))
lang = ent:LocalToWorldAngles(Angle(PLUGIN.pitch, PLUGIN.yaw, PLUGIN.roll))
cam.Start3D2D(lpos, lang, scale)
drawMRadioSurface(ent, lpos, lang, scale)
cam.End3D2D()
-- do the same thing, but a bit further forward, for the hitbox rectangles
lpos2 = ent:LocalToWorld(Vector(PLUGIN.x + 0.9, PLUGIN.y, PLUGIN.z))
cam.Start3D2D(lpos2, lang, scale)
drawActiveHitbox()
cam.End3D2D()
end
end
end)
--[[
this code is sus
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣤⣤⣤⣤⣤⣤⣤⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⡿⠛⠉⠙⠛⠛⠛⠛⠻⢿⣿⣷⣤⡀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⠋⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠈⢻⣿⣿⡄⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣸⣿⡏⠀⠀⠀⣠⣶⣾⣿⣿⣿⠿⠿⠿⢿⣿⣿⣿⣄⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣿⣿⠁⠀⠀⢰⣿⣿⣯⠁⠀⠀⠀⠀⠀⠀⠀⠈⠙⢿⣷⡄⠀
⠀⠀⣀⣤⣴⣶⣶⣿⡟⠀⠀⠀⢸⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣷⠀
⠀⢰⣿⡟⠋⠉⣹⣿⡇⠀⠀⠀⠘⣿⣿⣿⣿⣷⣦⣤⣤⣤⣶⣶⣶⣶⣿⣿⣿⠀
⠀⢸⣿⡇⠀⠀⣿⣿⡇⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀
⠀⣸⣿⡇⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠉⠻⠿⣿⣿⣿⣿⡿⠿⠿⠛⢻⣿⡇⠀⠀
⠀⣿⣿⠁⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣧⠀⠀
⠀⣿⣿⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⠀⠀
⠀⣿⣿⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⠀⠀
⠀⢿⣿⡆⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⡇⠀⠀
⠀⠸⣿⣧⡀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠃⠀⠀
⠀⠀⠛⢿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⣰⣿⣿⣷⣶⣶⣶⣶⠶⠀⢠⣿⣿⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⣿⣿⡇⠀⣽⣿⡏⠁⠀⠀⢸⣿⡇⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⣿⣿⡇⠀⢹⣿⡆⠀⠀⠀⣸⣿⠇⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢿⣿⣦⣄⣀⣠⣴⣿⣿⠁⠀⠈⠻⣿⣿⣿⣿⡿⠏⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⠛⠻⠿⠿⠿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
]]
function PLUGIN:Think()
-- we're checking every frame if the player is looking at a radio??
-- someone cooked here
local tr = LocalPlayer():GetEyeTrace()
if (tr.Entity and IsValid(tr.Entity) and tr.Entity:GetClass() == "wn_musicradio") then
local volButton = tr.Entity:GetAbsolutePanelButtonPosition(80, 170)
local onButton = tr.Entity:GetAbsolutePanelButtonPosition(135, 150)
local dialButton = tr.Entity:GetAbsolutePanelButtonPosition(190, 170)
hitboxTargets.volume.shouldDraw = false
hitboxTargets.onOff.shouldDraw = false
hitboxTargets.dial.shouldDraw = false
if (tr.HitPos:Distance(volButton) <= 2) then
if (tr.Entity:IsPlayingMusic()) then
hitboxTargets.volume.shouldDraw = true
end
elseif (tr.HitPos:Distance(onButton) <= 2) then
hitboxTargets.onOff.shouldDraw = true
elseif (tr.HitPos:Distance(dialButton) <= 2) then
if (tr.Entity:IsPlayingMusic()) then
hitboxTargets.dial.shouldDraw = true
end
end
end
end

View File

@@ -0,0 +1,338 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
ENT.Type = "anim"
ENT.Base = "base_gmodentity"
ENT.PrintName = "Music Radio"
ENT.Author = "M!NT"
ENT.Category = "HL2 RP"
ENT.Contact = ""
ENT.Purpose = ""
ENT.Instructions = ""
ENT.Spawnable = false
ENT.AdminOnly = true
ENT.Holdable = true
function ENT:SetupDataTables()
self:NetworkVar("Bool", 0, "Enabled")
end
function ENT:IsPlayingMusic()
return self:GetNWString("curChan", "") != ""
end
function ENT:GetChan()
return self:GetNWString("curChan", "")
end
function ENT:GetVolume()
return self:GetNWInt("vol", 1)
end
function ENT:GetLevel()
return self:GetNWInt("db", 70)
end
if (CLIENT) then
function ENT:GetIlluminatedVolButton()
-- volume is a value of 0 - 1. converts that to 1 - 10 (active illum button)
return math.Round(10 * self:GetNWInt("vol", 1), 0)
end
-- volume from 1 - 6
function ENT:SetVolume(vol)
if (vol > 10) then
vol = 1
end
-- normalize vol between 0 and 1
local normal = vol / 10
-- normalize vol between maxdb (72) and mindb (57)
local newLvl = math.ceil(57 + (vol - 1) * (72 - 57) / (10 - 1))
net.Start("SetMusicRadioVolume")
net.WriteEntity(self)
net.WriteFloat(normal)
net.WriteInt(newLvl, 8)
net.SendToServer()
end
end
function ENT:onTakeDmg()
ix.musicRadio:PlayTuneStatic(self, true)
end
function ENT:PhysicsCollide(data, phys)
if (!self:IsPlayingMusic()) then
return
end
if (data.Speed < 125) then
return
end
local curTime = CurTime()
if (self.nextCollide and self.nextCollide > curTime) then
return
end
self:onTakeDmg()
self.nextCollide = curTime + 3
end
function ENT:OnTakeDamage(dmginfo)
if (!self:IsPlayingMusic()) then
return
end
if (dmginfo:GetDamage() < 5) then
return
end
if (dmginfo:GetDamage() > 20) then
self:DoBreak(dmginfo)
return
end
local curTime = CurTime()
if (self.nextDmg and self.nextDmg > curTime) then
return
end
self:onTakeDmg()
self.nextDmg = curTime + 3
end
function ENT:DoBreak(dmgInfo)
self:StopCurrentSong(0)
local explode = ents.Create("env_explosion")
explode:SetPos(self:GetPos())
explode:SetOwner(dmgInfo:GetAttacker())
explode:Spawn()
explode:SetKeyValue("iMagnitude", "10")
explode:Fire("Explode", 0, 0)
explode:EmitSound("Glass.Break", 120, 90)
local gib = ents.Create("prop_physics")
gib:SetModel("models/props_lab/citizenradio.mdl")
gib:SetPos(self:GetPos())
gib:Spawn()
gib:Ignite(3, 100) -- ignite for 3s, ignite everything within 100hus
gib:EmitSound("willardnetworks/musicradio/musicradio_static_loopable.mp3", 110, 85)
self:Remove()
end
function ENT:SetChan(chan)
if (CLIENT) then
net.Start("SetMusicRadioChannel")
net.WriteEntity(self)
net.WriteString(chan)
net.SendToServer()
end
if (SERVER) then
if (self:IsPlayingMusic()) then
ix.musicRadio:TuneOut(self, self:GetChan(), chan)
end
ix.musicRadio:TuneIn(self, chan)
self:SetNWString("curChan", chan)
end
end
function ENT:GetAbsolutePanelButtonPosition(x, y)
local absPos = self:GetPos()
absPos = absPos + self:GetForward() * 10
absPos = absPos + self:GetRight() * (17 + (1 - (x * 0.1)))
absPos = absPos + self:GetUp() * (24 + (1 - (y * 0.1)))
return absPos
end
if (SERVER) then
util.AddNetworkString("SetMusicRadioChannel")
util.AddNetworkString("SetMusicRadioVolume")
net.Receive("SetMusicRadioChannel", function(len, ply)
local ent = net.ReadEntity()
local ch = net.ReadString()
ent:SetChan(ch)
end)
net.Receive("SetMusicRadioVolume", function(len, ply)
local ent = net.ReadEntity()
local vol = net.ReadString()
local db = net.ReadInt(8)
ent:SetVolume(vol, db)
end)
-- handle the interaction with the volume knob
function ENT:InteractVolumeKnob()
local vol = math.ceil(self:GetVolume() * 10)
if (vol >= 10) then
vol = 1
else
vol = vol + 1
end
-- normalize vol between 0 and 1
local newVol = vol / 10
-- normalize vol between maxdb (65) and mindb (55)
local newLvl = math.ceil(50 + (vol - 1) * (65 - 50) / (10 - 1))
self:SetVolume(newVol, newLvl)
-- play clicky noise for button
self:EmitSound("willardnetworks/musicradio/musicradio_click_"..tostring(math.random(1, 4))..".mp3", 70)
end
-- 0 - 1, 60 - 85
function ENT:SetVolume(vol, db, delta, bStop)
local ch = self:GetChan()
local curSong = ix.musicRadio.destinations[ch].curSong
if (!delta) then
delta = 1
end
ix.musicRadio.destinations[ch][self:EntIndex()].soundCache[curSong]
:ChangeVolume(vol, delta)
ix.musicRadio.destinations[ch][self:EntIndex()].soundCache[curSong]
:SetSoundLevel(db)
if (bStop) then
ix.musicRadio.destinations[ch][self:EntIndex()].soundCache[curSong]:Stop()
end
self:SetNWInt("vol", vol)
self:SetNWInt("db", db)
end
function ENT:StopCurrentSong(delta)
local ch = self:GetChan()
local curSong = ix.musicRadio.destinations[ch].curSong
if (!ch or !curSong) then
return
end
if (!delta) then
delta = 0.5
end
if (!ix.musicRadio.destinations[ch][self:EntIndex()]) then
return -- ??
end
ix.musicRadio.destinations[ch][self:EntIndex()].soundCache[curSong]:FadeOut(delta)
end
function ENT:Initialize()
self:SetModel("models/props_lab/citizenradio.mdl")
self:PhysicsInit(SOLID_VPHYSICS)
self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
local phys = self:GetPhysicsObject()
if IsValid(phys) then
phys:EnableMotion(true)
phys:Wake()
end
self:SetRadioClass("benefactor")
end
function ENT:SetRadioClass(className)
self.defaultStation = ix.musicRadio:GetDefaultChannel(className)
end
function ENT:GetRadioClass()
return ix.musicRadio.channels[self.defaultStation].class
end
function ENT:PerformPickup(client)
if timer.Exists("ixCharacterInteraction" .. client:SteamID()) then return end
local itemClassName = "musicradio_cmb"
if (self:GetRadioClass() == "pirate") then
itemClassName = "musicradio_reb"
end
client:PerformInteraction(0.5, self, function(_)
local success = client:GetCharacter():GetInventory():Add(itemClassName)
if (!success or success == false) then
client:Notify("There is not enough room in your inventory!")
return false
end
self:Remove()
timer.Remove("ixCharacterInteraction" .. client:SteamID())
end)
end
function ENT:Use(activator)
local curTime = CurTime()
if (self.nextUse and self.nextUse > curTime) then
return
end
if activator:KeyDown(IN_WALK) then
return self:PerformPickup(activator)
end
-- yay! magic numbers!!
-- these loosely fit the derma buttons, which is why we're doing it this way
local volButton = self:GetAbsolutePanelButtonPosition(80, 170)
local onButton = self:GetAbsolutePanelButtonPosition(135, 150)
local dialButton = self:GetAbsolutePanelButtonPosition(190, 170)
local tr = activator:GetEyeTrace()
if (tr.HitPos:Distance(volButton) <= 4) then
if (self:IsPlayingMusic()) then
self:InteractVolumeKnob()
end
elseif (tr.HitPos:Distance(onButton) <= 4) then
self:EmitSound("willardnetworks/musicradio/musicradio_click_"..tostring(math.random(1, 4))..".mp3", 70)
if (self:IsPlayingMusic()) then
self:StopCurrentSong()
ix.musicRadio:TuneOut(self, self:GetChan())
self:SetNWString("curChan", "")
else
self:SetNWString("curChan", self.defaultStation)
ix.musicRadio:TuneIn(self, self.defaultStation)
end
elseif (tr.HitPos:Distance(dialButton) <= 4) then
if (self.nextUse and (self.nextUse + 2) > curTime) then
return
end
if (self:IsPlayingMusic()) then
-- only allow channel changes when the radio is on
self:SetChan(ix.musicRadio:GetNextChannelName(
self:GetNWString("curChan", self.defaultStation)))
end
end
self.nextUse = curTime + 1
end
else
function ENT:Think()
self.ShowPlayerInteraction = LocalPlayer():KeyDown(IN_WALK)
end
end

View File

@@ -0,0 +1,48 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local PLUGIN = PLUGIN
ITEM.name = "Radio Benefaktora"
ITEM.model = Model("models/props_lab/citizenradio.mdl")
ITEM.description = "Masowo produkowane radio ustawione na częstotliwość kontrolowaną przez Kombinat na której gra patriotyczna oraz klasyczna muzyka."
ITEM.width = 4
ITEM.height = 3
ITEM.category = "Technology"
ITEM.functions.Deploy = {
name = "Rozmieść",
OnRun = function(itemTable)
local client = itemTable.player
if client.CantPlace then
client:NotifyLocalized("Musisz poczekać zanim będziesz mógł to postawić!..")
return false
end
client.CantPlace = true
timer.Simple(3, function()
if client then
client.CantPlace = false
end
end)
local radio = ents.Create("wn_musicradio")
local tr = client:GetEyeTrace()
local dist = client:EyePos():Distance(tr.HitPos)
radio:SetPos(client:EyePos() + (tr.Normal * math.Clamp(dist, 0, 75)))
radio:Spawn()
radio:SetRadioClass("benefactor")
ix.saveEnts:SaveEntity(radio)
return true
end
}

View File

@@ -0,0 +1,48 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local PLUGIN = PLUGIN
ITEM.name = "Radio Rebelii"
ITEM.model = Model("models/props_lab/citizenradio.mdl")
ITEM.description = "Małe radio z odzysku radio dostrojone na pirackie stacji radiowe nadawające przeciwko Kombiantu."
ITEM.category = "Technology"
ITEM.width = 4
ITEM.height = 3
ITEM.functions.Deploy = {
name = "Rozmieść",
OnRun = function(itemTable)
local client = itemTable.player
if client.CantPlace then
client:NotifyLocalized("Musisz poczekać zanim będziesz mógł to postawić!..")
return false
end
client.CantPlace = true
timer.Simple(3, function()
if client then
client.CantPlace = false
end
end)
local radio = ents.Create("wn_musicradio")
local tr = client:GetEyeTrace()
local dist = client:EyePos():Distance(tr.HitPos)
radio:SetPos(client:EyePos() + (tr.Normal * math.Clamp(dist, 0, 75)))
radio:Spawn()
radio:SetRadioClass("pirate")
ix.saveEnts:SaveEntity(radio)
return true
end
}

View File

@@ -0,0 +1,26 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
ITEM.name = "Tuner radiowy zatwierdzony przez Kombinat"
ITEM.description = "Tuner radiowy, za pomocą którego można zmieniać częstotliwości odbieranych przez radio, na które można się dostroić."
ITEM.category = "Technology"
ITEM.width = 1
ITEM.height = 1
ITEM.model = "models/willardnetworks/skills/circuit.mdl"
ITEM.colorAppendix = {["blue"] = "Przedmiot ten można zdobyć za pomocą umiejętności Wytwarzania."}
ITEM.maxStackSize = 1
ITEM.functions.install = {
name = "Zainstaluj",
tip = "Zainstaluj ten przedmiot do radia na które się patrzysz.",
icon = "icon16/wrench.png",
OnRun = function(item)
ix.musicRadio:InstallTuner(item.player, false)
end
}

View File

@@ -0,0 +1,26 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
ITEM.name = "Tuner radiowy ruchu oporu"
ITEM.description = "Tuner radiowy, za pomocą którego można dowolnie zmieniać częstotliwości odbieranych przez radio."
ITEM.category = "Technology"
ITEM.width = 1
ITEM.height = 1
ITEM.model = "models/willardnetworks/skills/circuit.mdl"
ITEM.colorAppendix = {["blue"] = "Przedmiot ten można zdobyć za pomocą umiejętności Wytwarzania.", ["red"] = "Posiadanie tego przedmiotu może wzbudzać podejrzenia."}
ITEM.maxStackSize = 1
ITEM.functions.install = {
name = "Zainstaluj",
tip = "Zainstaluj ten przedmiot do radia na które się patrzysz.",
icon = "icon16/wrench.png",
OnRun = function(item)
ix.musicRadio:InstallTuner(item.player, true)
end
}

View File

@@ -0,0 +1,633 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local PLUGIN = PLUGIN
PLUGIN.name = "Better Music Radio"
PLUGIN.author = "M!NT"
PLUGIN.description = "Adds a radio that plays music, but better c:"
ix.util.Include("sv_hooks.lua")
ix.util.Include("cl_hooks.lua")
ix.musicRadio = ix.musicRadio or {}
ix.musicRadio.announcements = ix.musicRadio.annnouncements or {}
ix.musicRadio.channels = ix.musicRadio.channels or {}
ix.musicRadio.classes = ix.musicRadio.classes or {
["pirate"] = "wn_rebel_radio",
["benefactor"] = "wn_cmb_radio"
}
ix.musicRadio.static = ix.musicRadio.static or {}
ix.musicRadio.classes = ix.musicRadio.classes or {}
-- enums
ix.musicRadio.CHAN_DISABLED = true
ix.musicRadio.CHAN_ENABLED = false
CAMI.RegisterPrivilege({
Name = "Helix - Manage Music Radios",
MinAccess = "admin"
})
ix.command.Add("MusicRadioChannelSeek", {
description = "Seek the specific music radio channel forward one song.",
privilege = "Manage Music Radios",
adminOnly = true,
arguments = {
ix.type.string
},
OnRun = function(self, client, channelName)
if (!ix.musicRadio:ChannelIsValid(channelName)) then
client:Notify("Invalid channel name provided!")
else
ix.musicRadio:PlayNextSong(channelName)
end
end
})
ix.command.Add("PlaySoundOnRadioClass", {
description = "Play a specific song on all radios in a certain class on the map.",
privilege = "Manage Music Radios",
adminOnly = true,
arguments = {
ix.type.string,
ix.type.string,
ix.type.number
},
OnRun = function(self, client, class, soundName, length)
ix.musicRadio:PlaySoundOnClass(soundName, class, length)
end
})
ix.command.Add("SetMusicRadioClassEnabled", {
description = "Disable a specific class of music radios.",
privilege = "Manage Music Radios",
adminOnly = true,
arguments = {
ix.type.string
},
OnRun = function(self, client, class)
if (!ix.musicRadio:ClassIsValid(class)) then
client:Notify("Invalid class name provided!")
else
if (!ix.musicRadio:GetClassShouldPlayStatic(class)) then
client:Notify("This class is currently enabled. Use the 'SetMusicRadioClassDisabled' command to disable it.")
else
ix.musicRadio:SetClassStaticState(class, ix.musicRadio.CHAN_ENABLED)
end
end
end
})
ix.command.Add("SetMusicRadioClassDisabled", {
description = "Enable a specific class of music radios.",
privilege = "Manage Music Radios",
adminOnly = true,
arguments = {
ix.type.string
},
OnRun = function(self, client, class)
if (!ix.musicRadio:ClassIsValid(class)) then
client:Notify("Invalid class name provided!")
else
if (ix.musicRadio:GetClassShouldPlayStatic(class)) then
client:Notify("This class is currently disabled. Use the 'SetMusicRadioClassEnabled' command to enable it.")
else
ix.musicRadio:SetClassStaticState(class, ix.musicRadio.CHAN_DISABLED)
end
end
end
})
ix.command.Add("RestartMusicRadioClass", {
description = "Restart a specific class of music radios (in case they crash for some reason).",
privilege = "Manage Music Radios",
adminOnly = true,
arguments = {
ix.type.string
},
OnRun = function(self, client, class)
if (!ix.musicRadio:ClassIsValid(class)) then
client:Notify("Invalid class name provided!")
else
ix.musicRadio:RestartClass(class)
client:Notify("Restarted class: "..class)
end
end
})
ix.command.Add("RestartMusicRadioChannel", {
description = "Restart a specific radio channel (in case it crashes for some reason).",
privilege = "Manage Music Radios",
adminOnly = true,
arguments = {
ix.type.string
},
OnRun = function(self, client, channel)
if (!ix.musicRadio:ChannelIsValid(channel)) then
client:Notify("Invalid channel name provided!")
else
ix.musicRadio:RestartChannel(channel)
client:Notify("Restarted channel: "..channel)
end
end
})
function ix.musicRadio:InitStatic()
--[[
STATIC sounds for when the channel / class is KOd
]]
local static = {
[1] = { fname = "willardnetworks/musicradio/static/musicradio_static_1.mp3", length = 55 },
[2] = { fname = "willardnetworks/musicradio/static/musicradio_static_2.mp3", length = 55 },
[3] = { fname = "willardnetworks/musicradio/static/musicradio_static_3.mp3", length = 55 }
}
self.static.sounds = static
end
function ix.musicRadio:InitSpooky()
--[[
Spooky sounds for fun
]]
local classes = {
["pirate"] = true,
["benefactor"] = false
}
local sounds = {
[1] = { fname = "willardnetworks/musicradio/rebel/spooky/musicradio_spooky_1.mp3", length = 170 },
[2] = { fname = "willardnetworks/musicradio/rebel/spooky/musicradio_spooky_2.mp3", length = 170 },
[3] = { fname = "willardnetworks/musicradio/rebel/spooky/musicradio_spooky_3.mp3", length = 52 },
[4] = { fname = "willardnetworks/musicradio/rebel/spooky/musicradio_spooky_4.mp3", length = 11 },
[5] = { fname = "willardnetworks/musicradio/rebel/spooky/musicradio_spooky_5.mp3", length = 9 },
[6] = { fname = "willardnetworks/musicradio/rebel/spooky/musicradio_spooky_6.mp3", length = 12 },
[7] = { fname = "willardnetworks/musicradio/rebel/spooky/musicradio_spooky_7.mp3", length = 60 },
}
self.spooky = {}
self.spooky.sounds = sounds
self.spooky.classes = classes
end
function ix.musicRadio:PrecacheSpooky()
if (SERVER) then
return
end
if (!istable(self.spooky)) then
return
end
for k, song in ipairs(self.spooky.sounds) do
util.PrecacheSound(song.fname)
end
end
function ix.musicRadio:AddAnnouncementsToClass(className, announcerTbl)
if (!self.classes[className]) then
-- happens a lot during lua refresh
-- if that happens to you, safe to ignore
-- just keeping this here in case an announcer class doesn't come up during refresh
ErrorNoHaltWithStack("Attempt to add invalid announcer! Not adding.. Class: "..tostring(className))
return
else
if (!self.announcements) then
self.announcements = {}
end
self.announcements[className] = {}
self.announcements[className].sounds = announcerTbl
end
end
function ix.musicRadio:PrecacheAnnouncementsForClass(className)
if (SERVER) then
return
end
if (!istable(self.classes[className])) then
return
end
for k, snd in ipairs(self.announcements[className]) do
util.PrecacheSound(snd.fname)
end
end
function ix.musicRadio:PrecacheChannel(chName)
if (SERVER) then
return
end
if (!istable(self.channels[chName])) then
return
end
for k, song in ipairs(self.channels[chName]) do
util.PrecacheSound(song.fname)
end
end
-- get the 'next' channel; seek forward
function ix.musicRadio:GetNextChannelName(chName)
local class = self.channels[chName].class
if (!chName or chName == "") then
return self.chanList[class][1]
end
local k = table.KeyFromValue(self.chanList[class], chName)
if (k and isnumber(k)) then
if (k == table.Count(self.chanList[class])) then
return self.chanList[class][1] -- loop around
else
return self.chanList[class][k+1]
end
end
end
function ix.musicRadio:GetDefaultChannel(className)
if (!self.classes[className]) then
ErrorNoHaltWithStack("Attempt to add radio to invalid channel! Not adding.. Class: "..tostring(className))
return
end
return ix.musicRadio.chanList[className][1]
end
function ix.musicRadio:AddChannel(chName, mTbl, class, freqMap)
if (!self.classes[class]) then
-- happens a lot during lua refresh
-- if that happens to you, safe to ignore
-- just keeping this here in case a channel doesn't come up during refresh
ErrorNoHaltWithStack("Attempt to add invalid channel! Not adding.. Name: "..tostring(chName).." Class: "..tostring(class))
return
else
self.channels[chName] = {}
self.channels[chName].songs = mTbl
self.channels[chName].freqMap = freqMap -- used clientside to figure out where the dial goes
self.channels[chName].class = class
if (!self.chanList) then
self.chanList = {}
end
if (!self.chanList[class]) then
self.chanList[class] = {}
end
table.insert(self.chanList[class], chName)
end
end
function ix.musicRadio:GetChannel(chName)
if (self.channels[chName] and istable(self.channels[chName])) then
return self.channels[chName]
end
end
--[[
RADIO SETUP
]]
do
-- load up the current state of static
ix.musicRadio:InitStatic()
-- load up some stuff for spooks
ix.musicRadio:InitSpooky()
ix.musicRadio:PrecacheSpooky()
-- the static blurb that plays during tuning
local staticSoundFilePrefix = "willardnetworks/musicradio/musicradio_static_"
for i=1, 6 do
util.PrecacheSound(staticSoundFilePrefix..tostring(i)..".mp3")
end
--[[
REBEL RADIO STATIONS
]]
local bluesRock = {
[1] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_acdc_jailbreak.mp3", length = 275 },
[2] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_aerosmith_dreamon.mp3", length = 260 },
[3] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_allmanbrothers_midnightrider.mp3", length = 173 },
[4] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_allmanbrothers_whippingpost.mp3", length = 316 },
[5] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_bbking_thrillisgone.mp3", length = 309 },
[6] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_beatles_taxman.mp3", length = 151 },
[7] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_billwithers_aintnosunshine.mp3", length = 118 },
[8] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_cream_strangebrew.mp3", length = 164 },
[9] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_dirtymac_yerblues.mp3", length = 242 },
[10] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_jacksonbrowne_doctormyeyes.mp3", length = 186 },
[11] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_jimihendrix_allalongthewatchtower.mp3", length = 238 },
[12] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_jimihendrix_littlewing.mp3", length = 138 },
[13] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_jimihendrix_purplehaze.mp3", length = 164 },
[14] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_johnleehooker_boomboomboomboom.mp3", length = 156 },
[15] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_ledzepplin_rambleon.mp3", length = 240 },
[16] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_ledzepplin_stairwaytoheaven.mp3", length = 467 },
[17] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_lynyrdskynyrd_amilosin.mp3", length = 251 },
[18] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_lynyrdskynyrd_cryforthebadman.mp3", length = 280 },
[19] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_lynyrdskynyrd_freebird.mp3", length = 518 },
[20] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_lynyrdskynyrd_onthehunt.mp3", length = 315 },
[21] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_lynyrdskynyrd_saturdaynightspecial.mp3", length = 291 },
[22] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_srv_prideandjoy.mp3", length = 216 },
[23] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_srv_texasflood.mp3", length = 317 },
[24] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_srv_voodoochild.mp3", length = 455 },
[25] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_stonetemplepilots_interstatelovesong.mp3", length = 187 },
[26] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_tednugent_stranglehold.mp3", length = 500 },
[27] = { fname = "willardnetworks/musicradio/rebel/bluesrock/musicradio_theband_thenighttheydroveolddixiedown.mp3", length = 247 }
}
local hipHop = {
[1] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_big_hypnotize.mp3", length = 225 },
[2] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_fugees_zealots.mp3", length = 243 },
[3] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_getoboys_damnitfeelsgoodtobeagangasta.mp3", length = 298 },
[4] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_getoboys_still.mp3", length = 216 },
[5] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_heltahskeltah_lethabrainzblo.mp3", length = 243 },
[6] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_jamoroquai_virtualinstanity.mp3", length = 223 },
[7] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_mfdoom_doomsday.mp3", length = 247 },
[8] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_nas_nystateofmind.mp3", length = 288 },
[9] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_nwa_fuckdapolice.mp3", length = 313 },
[10] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_outkast_atliens.mp3", length = 208 },
[11] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_outkast_elevators.mp3", length = 234 },
[12] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_peterock_troy.mp3", length = 273 },
[13] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_puts_acidraindrops.mp3", length = 264 },
[14] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_sugarhillgang_rappersdelight.mp3", length = 236 },
[15] = { fname = "willardnetworks/musicradio/rebel/hiphop/musicradio_wutang_cream.mp3", length = 235 }
}
local country = {
[1] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_alabama_dixielanddelight.mp3", length = 233 },
[2] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_billmonroe_bluemoonofkentucky.mp3", length = 186 },
[3] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_charliedaniels_southsgondoitagain.mp3", length = 235 },
[4] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_docwatson_houseoftherisingsun.mp3", length = 193 },
[5] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_gordonlightfoot_edmundfitzgerald.mp3", length = 351 },
[6] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_jimmymartin_freebornman.mp3", length = 169 },
[7] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_jimmymartin_madeintheshade.mp3", length = 181 },
[8] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_keithwhitley_miamimyami.mp3", length = 201 },
[9] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_oldcrow_takeemaway.mp3", length = 208 },
[10] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_oldcrow_wagonwheel.mp3", legnth = 227 },
[11] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_osbornebrothers_rockytop.mp3", legnth = 151 },
[12] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_osbornebrothers_sunnysideofthemountain.mp3", legnth = 138 },
[13] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_ralphstanely_clinchmountainbackstep.mp3", legnth = 136 },
[14] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_ralphstanely_eastvirginiablues.mp3", legnth = 151 },
[15] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_ralphstanely_gloryland.mp3", legnth = 160 },
[16] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_ralphstanely_manofconstantsorrow.mp3", legnth = 165 },
[17] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_ralphstanely_rankstranger.mp3", legnth = 208 },
[18] = { fname = "willardnetworks/musicradio/rebel/bluegrass/musicradio_willienelson_hellowalls.mp3", legnth = 240 }
}
local metal = {
[1] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_bathory_callfromthegrave.mp3", length = 260 },
[2] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_blacksabbath_blacksabbath.mp3", length = 337 },
[3] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_burzum_dunkelheitwav.mp3", length = 417 },
[4] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_cannibalcorpse_hammersmashedface.mp3", length = 241 },
[5] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_celticfrost_adyinggod.mp3", length = 336 },
[6] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_celticfrost_cryptsofrays.mp3", length = 217 },
[7] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_danzig_evilthing.mp3", length = 192 },
[8] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_entombed_revelinflesh.mp3", length = 206 },
[9] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_fearfactory_pisschrist.mp3", length = 319 },
[10] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_mayhem_chainsawgutsfuck.mp3", length = 208 },
[11] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_megadeth_hangar18.mp3", length = 310 },
[12] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_megadeth_holywars.mp3", length = 390 },
[13] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_megadeth_killingroad.mp3", length = 228 },
[14] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_megadeth_symphonyofdestruction.mp3", length = 228 },
[15] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_sepultura_arise.mp3", length = 197 },
[16] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_sepultura_propaganda.mp3", length = 210 },
[17] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_sepultura_rootsbloodyroots.mp3", length = 203 },
[18] = { fname = "willardnetworks/musicradio/rebel/metal/musicradio_whitezombie_thunderkiss.mp3", length = 228 }
}
local punk = {
[1] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_agentorange_bloodstains.mp3", length = 108 },
[2] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_aliceinchains_maninthebox.mp3", length = 265 },
[3] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_aliceinchains_rooster.mp3", length = 367 },
[4] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_aliceinchains_thembones.mp3", length = 147 },
[5] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_aliceinchains_would.mp3", length = 204 },
[6] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_badbrains_bigtakeover.mp3", length = 177 },
[7] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_blackflag_mywar.mp3", length = 225 },
[8] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_blackflag_riseabove.mp3", length = 142 },
[9] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_clutch_bingeandpurge.mp3", length = 385 },
[10] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_eyehategod_sisterfucker.mp3", length = 120 },
[11] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_foofighters_everlong.mp3", length = 289 },
[12] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_foofighters_monkeywrench.mp3", length = 230 },
[13] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_helmet_unsung.mp3", length = 235 },
[14] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_megadeth_takenoprisoners.mp3", length = 204 },
[15] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_melvins_junebug.mp3", length = 112 },
[16] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_ministry_thieves.mp3", length = 297 },
[17] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_misfits_diediediemydarling.mp3", length = 184 },
[18] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_misfits_whereeaglesdare.mp3", length = 122 },
[19] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_ramones_blitzkriegbop.mp3", length = 130 },
[20] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_ratm_bornofabrokenman.mp3", length = 277 },
[21] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_ratm_killinginthenameof.mp3", length = 312 },
[22] = { fname = "willardnetworks/musicradio/rebel/punk/musicradio_typeoneg_idontwannabeme.mp3", length = 223 }
}
local alternative = {
[1] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_aphextwin_1.mp3", length = 434 },
[2] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_aphextwin_cockver10.mp3", length = 288 },
[3] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_aphextwin_vordhosbn.mp3", length = 273 },
[4] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_aphextwin_xtal.mp3", length = 273 },
[5] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_bjork_armyofme.mp3", length = 229 },
[6] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_massiveattack_dissolvedgirl.mp3", length = 358 },
[7] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_massiveattack_teardrop.mp3", length = 318 },
[8] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_nin_headlikeahole.mp3", length = 291 },
[9] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_nin_hurt.mp3", length = 331 },
[10] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_squarepusher_anirog09.mp3", length = 70 },
[11] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_squarepusher_beepstreet.mp3", length = 389 },
[12] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_squarepusher_craniumoxide.mp3", length = 29 },
[13] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_squarepusher_decathlonoxide.mp3", length = 243 },
[14] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_theprodigy_breathe.mp3", length = 235 },
[15] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_theprodigy_firestarter.mp3", length = 278 },
[16] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_whitezombie_morehumanthanhuman.mp3", length = 316 },
[17] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_nin_bigcomedown.mp3", length = 246 },
[18] = { fname = "willardnetworks/musicradio/rebel/alternative/musicradio_nin_thegreatbelow.mp3", length = 254 }
}
local pirateAnnouncer = {
[1] = { fname = "willardnetworks/musicradio/rebel/announcer/musicradio_pirate_announce_1.mp3", length = 49 },
[2] = { fname = "willardnetworks/musicradio/rebel/announcer/musicradio_pirate_announce_2.mp3", length = 70 },
[3] = { fname = "willardnetworks/musicradio/rebel/announcer/musicradio_pirate_announce_3.mp3", length = 87 },
[4] = { fname = "willardnetworks/musicradio/rebel/announcer/musicradio_pirate_announce_4.mp3", length = 92 },
[5] = { fname = "willardnetworks/musicradio/rebel/announcer/musicradio_pirate_announce_5.mp3", length = 111 },
[6] = { fname = "willardnetworks/musicradio/rebel/announcer/musicradio_pirate_announce_6.mp3", length = 121 },
[7] = { fname = "willardnetworks/musicradio/rebel/announcer/musicradio_pirate_announce_7.mp3", length = 110 },
[8] = { fname = "willardnetworks/musicradio/rebel/announcer/musicradio_pirate_announce_8.mp3", length = 98 }
}
-- Pirate (rebel) radio stations:
ix.musicRadio:AddChannel("Blues & Rock", bluesRock, "pirate", {
dispX = 195,
freq = 550
})
ix.musicRadio:AddChannel("Hiphop", hipHop, "pirate", {
dispX = 245,
freq = 600
})
ix.musicRadio:AddChannel("Country", country, "pirate", {
dispX = 295,
freq = 650
})
ix.musicRadio:AddChannel("Metal", metal, "pirate", {
dispX = 345,
freq = 700
})
ix.musicRadio:AddChannel("Punk", punk, "pirate", {
dispX = 395,
freq = 750
})
ix.musicRadio:AddChannel("Alternative", alternative, "pirate", {
dispX = 445,
freq = 800
})
if (CLIENT) then
ix.musicRadio:PrecacheChannel("Blues & Rock")
ix.musicRadio:PrecacheChannel("Hiphop")
ix.musicRadio:PrecacheChannel("Country")
ix.musicRadio:PrecacheChannel("Metal")
ix.musicRadio:PrecacheChannel("Punk")
ix.musicRadio:PrecacheChannel("Alternative")
end
ix.musicRadio:AddAnnouncementsToClass("pirate", pirateAnnouncer)
if (CLIENT) then
ix.musicRadio:PrecacheAnnouncementsForClass("pirate")
end
--[[
COMBINE RADIO STATIONS
]]
local classical = {
[1] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_bach_cellosuite1gmaj.mp3", length = 140 },
[2] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_chopin_balladeno1gminorop23.mp3", length = 537 },
[3] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_chopin_nocturneop9no2.mp3", length = 256 },
[4] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_chopin_nocturnno2.mp3", length = 244 },
[5] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_kleine_serenadeno13gmaj.mp3", length = 383 },
[6] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_kreisler_liebesleid.mp3", length = 201 },
[7] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_mozart_concertono21cmaj.mp3", length = 444 },
[8] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_satie_gymnopedie.mp3", length = 230 },
[9] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_shostakovich_waltzno2.mp3", length = 242 },
[10] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_tchaikovsky_waltzoftheflowers.mp3", length = 447 },
[11] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_theswan.mp3", length = 172 },
[11] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_rei_i.mp3", length = 176 },
[12] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_vengerov_concerto.mp3", length = 205 },
[13] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_tchaikovsky_lonelyhearts.mp3", length = 200 },
[14] = { fname = "willardnetworks/musicradio/combine/classical/musicradio_tchaikovsky_adagio.mp3", length = 379 }
}
local jazz = {
[1] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_chetbaker_autmnleaves.mp3", length = 413 },
[2] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_chetbaker_ifallinlovetooeasily.mp3", length = 195 },
[3] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_davebrubeck_takefive.mp3", length = 316 },
[4] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_ellafitzgerald_summertime.mp3", length = 293 },
[5] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_johncoltrane_giantsteps.mp3", length = 282 },
[6] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_johncoltrane_myfavoritethings.mp3", length = 817 },
[7] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_johncoltrane_mylittlebrownbook.mp3", length = 310 },
[8] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_milesdavis_sowhat.mp3", length = 540 },
[9] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_sinatra_way_you_look_tonight.mp3", length = 199 },
[10] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_pepper_four_brothers.mp3", length = 177 },
[11] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_milesdavis_blue_in_green.mp3", length = 330 },
[12] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_louis_armstrong_wonderful_world.mp3", length = 134 },
[13] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_stangetz_corcovado.mp3", length = 251 },
[14] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_chetbaker_almostblue.mp3", length = 293 },
[15] = { fname = "willardnetworks/musicradio/combine/jazz/musicradio_cohen_raincoat.mp3", length = 306 }
}
local funk = {
[1] = { fname = "willardnetworks/musicradio/combine/funk/anri_shyness.mp3", length = 188 },
[2] = { fname = "willardnetworks/musicradio/combine/funk/aran_im_in_love.mp3", length = 284 },
[3] = { fname = "willardnetworks/musicradio/combine/funk/mamiya_japaneese.mp3", length = 234 },
[4] = { fname = "willardnetworks/musicradio/combine/funk/matsubara_stay_with_me.mp3", length = 307 },
[5] = { fname = "willardnetworks/musicradio/combine/funk/ohashi_i_love_you_so.mp3", length = 273 },
[6] = { fname = "willardnetworks/musicradio/combine/funk/ohashi_sweet_love.mp3", length = 263 },
[7] = { fname = "willardnetworks/musicradio/combine/funk/ohashi_telephone_number.mp3", length = 230 },
[8] = { fname = "willardnetworks/musicradio/combine/funk/rick_james_give_it_to_me.mp3", length = 237 },
[9] = { fname = "willardnetworks/musicradio/combine/funk/takeuichi_plastic_love.mp3", length = 269 },
[10] = { fname = "willardnetworks/musicradio/combine/funk/yagami_bay_city.mp3", length = 237 },
[11] = { fname = "willardnetworks/musicradio/combine/funk/yasuha_flyday_chinatown.mp3", length = 203 },
[12] = { fname = "willardnetworks/musicradio/combine/funk/takahashi_sunset_road.mp3", length = 249 },
}
local speech = {
[1] = { fname = "willardnetworks/musicradio/combine/speech/musicradio_breen_instinct.mp3", length = 191 },
[2] = { fname = "willardnetworks/musicradio/combine/speech/musicradio_breen_civilprotection.mp3", length = 158 },
[3] = { fname = "willardnetworks/musicradio/combine/speech/musicradio_breen_accomplishment.mp3", length = 156 },
[4] = { fname = "willardnetworks/musicradio/combine/speech/musicradio_breen_civilprotection2.mp3", length = 173 },
[5] = { fname = "willardnetworks/musicradio/combine/speech/musicradio_breen_progress.mp3", length = 208 },
[6] = { fname = "willardnetworks/musicradio/combine/speech/musicradio_breen_support.mp3", length = 186 },
[7] = { fname = "willardnetworks/musicradio/combine/speech/musicradio_breen_workshifts.mp3", length = 162 },
[8] = { fname = "willardnetworks/musicradio/combine/speech/musicradio_breen_workshifts2.mp3", length = 167 }
}
local benefactorAnnouncer = {
[1] = { fname = "willardnetworks/musicradio/combine/announcer/musicradio_benefactor_announce_1.mp3", length = 65 },
[2] = { fname = "willardnetworks/musicradio/combine/announcer/musicradio_benefactor_announce_2.mp3", length = 65 }
}
-- Benefactor (cmb) radio stations:
ix.musicRadio:AddChannel("Classical", classical, "benefactor", {
dispX = 195,
freq = 550
})
ix.musicRadio:AddChannel("Jazz", jazz, "benefactor", {
dispX = 245,
freq = 600
})
ix.musicRadio:AddChannel("Funk", funk, "benefactor", {
dispX = 295,
freq = 650
})
ix.musicRadio:AddChannel("Speech", speech, "benefactor", {
dispX = 345,
freq = 700
})
if (CLIENT) then
ix.musicRadio:PrecacheChannel("Classical")
ix.musicRadio:PrecacheChannel("Jazz")
ix.musicRadio:PrecacheChannel("Funk")
ix.musicRadio:PrecacheChannel("Speech")
end
--[[ Not adding right now because the MoE is dragging me through the mud on this
ix.musicRadio:AddAnnouncementsToClass("benefactor", benefactorAnnouncer)
if (CLIENT) then
ix.musicRadio:PrecacheAnnouncementsForClass("benefactor")
end
]]
--[[
misc precaching
]]
for i=1, 6 do
util.PrecacheSound("willardnetworks/musicradio/musicradio_static_"..tostring(i)..".mp3")
end
for i=1, 4 do
util.PrecacheSound("willardnetworks/musicradio/musicradio_click_"..tostring(i)..".mp3")
end
end
if (CLIENT) then
local color = Color(75, 196, 0)
function PLUGIN:InitializedPlugins()
local function drawRadioESP(client, entity, x, y, factor)
local txt = "Music Radio - ".."Chan: "..tostring(entity:GetChan()).." Vol ("..tostring(entity:GetVolume())..")".." On: "..tostring(entity:IsPlayingMusic())
ix.util.DrawText(txt, x, y - math.max(10, 32 * factor), color,
TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, nil, math.max(255 * factor, 80))
end
ix.observer:RegisterESPType("wn_musicradio", drawRadioESP, "Music Radio")
end
end

View File

@@ -0,0 +1,801 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local PLUGIN = PLUGIN
--[[
LUA RADIO DJ!
;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;
; ;
; ;
; ;
; ;
; ;
; ;
; ;
,;;;;; ,;;;;;
;;;;;; ;;;;;;
`;;;;' `;;;;'
]]
ix.musicRadio = ix.musicRadio or {}
ix.musicRadio.channels = ix.musicRadio.channels or {}
-- a table for managing which radio is listening to which channel, basically
-- allows us to sync up the musics
ix.musicRadio.destinations = ix.musicRadio.destinations or {}
ix.musicRadio.staticTime = 3 -- how long does static play for between tunings?
ix.musicRadio.transitionTime = 3 -- how long should the transitions between songs last for?
ix.musicRadio.radioVolume = 0.7 -- DEFAULT volume between 0 to 1 of the radios
ix.musicRadio.radioLevel = 70 -- DEFAULT volume in db of the radios
ix.musicRadio.spookMinTime = 60 * 60 -- 1 hour (minimum time between spooky sfx)
ix.musicRadio.spookChance = 99999 -- probability (/second) of spooky sfx playing (after minimum time)
ix.musicRadio.dspPreset = 2 -- ROOM EMPTY SMALL BRIGHT https://maurits.tv/data/garrysmod/wiki/wiki.garrysmod.com/index67df-2.html
hook.Add("EntityRemoved", "StopMusicRadioSound", function(ent)
-- there is an undocumented engine bug which causes a memory leak here
-- it will cause a black hole to open as soon as someone removes a music radio
-- it will grow until EVERYTHING IS CONSUMED
if (ent.soundCache) then
for _, snd in pairs(ent.soundCache) do
if (snd and snd.Stop) then
snd:Stop()
end
end
end
end)
function ix.musicRadio:ChannelIsValid(chName)
if (!chName or !self.channels[chName] or !istable(self.channels[chName])) then
return false
end
return true
end
function ix.musicRadio:ClassIsValid(class)
if (!self.chanList[class] or !istable(self.chanList[class])) then
return false
end
return true
end
-- called to totally reinitialize all the channels on X class of the music radios
function ix.musicRadio:RestartClass(class)
if (!self.chanList[class]) then
ErrorNoHaltWithStack("Attempted to restart uninitialized class: "..tostring(class))
return
end
if (!istable(self.chanList[class])) then
ErrorNoHaltWithStack("Attempted to restart class which has no channels: "..tostring(class))
return
end
for _, chName in ipairs(self.chanList[class]) do
self:RestartChannel(chName)
end
end
function ix.musicRadio:RestartChannel(chName)
if (!self:ChannelIsValid(chName)) then
ErrorNoHaltWithStack("Attempted to restart uninitialized channel: "..tostring(chName))
return
end
-- pause the channel, which will stop the current song on all the entities listening to it
self:PauseDestination(chName, true)
-- destroy the timer so we can start it again
if (self.destinations[chName].timerName) then
timer.Remove(self.destinations[chName].timerName)
end
-- now rebuild the channel timer
self:StartDestination(chName)
end
-- called when a music radio tunes into a specific channel
function ix.musicRadio:TuneIn(ent, chName)
if (!self:ChannelIsValid(chName)) then
return
end
if (!self.destinations[chName] or
!self:DestinationHasTimer(chName) or
!self.destinations[chName].curSong
) then
self.destinations[chName] = {}
self:StartDestination(chName)
end
self.destinations[chName][ent:EntIndex()] = ent
ix.musicRadio:SyncEnt(chName, ent)
self:PlayTuneStatic(ent)
end
-- called when a music radio entity turns off
-- and/or switches off of their current station
function ix.musicRadio:TuneOut(ent, chName, otherChName)
if (!self:ChannelIsValid(chName)) then
return
end
local entIndex = ent:EntIndex()
if (!self.destinations[chName][entIndex]) then
return
end
if (otherChName) then
if (!self.destinations[otherChName]) then
self.destinations[otherChName] = {}
self:StartDestination(otherChName)
end
self.destinations[otherChName][entIndex] = ent -- < get updates on the new channel
ix.musicRadio:SyncEnt(otherChName, ent) -- < start playing the next song
end
-- stop the current song
local curSong = self.destinations[chName].curSong
if (cursong and
self.destinations[chName][entIndex].soundCache[curSong]
) then
if (!self.destinations[chName][entIndex].soundCache[curSong]:IsPlaying()) then
self.destinations[chName][entIndex].soundCache[curSong]:Stop()
end
end
self.destinations[chName][entIndex] = nil -- < no longer recieve updates on the current channel
end
-- starts timers that will auto play synced music for said channel
-- or unpauses them if theyre stopped
function ix.musicRadio:StartDestination(chName)
if (!self:ChannelIsValid(chName)) then
return
end
if (self.destinations[chName] and self.destinations[chName].paused) then
if (!self.destinations[chName].timerName) then
ErrorNoHaltWithStack("Attempted to unpause uninitialized channel: "..tostring(chName))
return
end
timer.UnPause(self.destinations[chName].timerName)
end
if (self.destinations[chName].timerName and
timer.Exists(self.destinations[chName].timerName)) then
return
end
-- initialize some vars for tracking things
self.destinations[chName].songsToNextAnn = math.random(8, 12)
self.destinations[chName].songsSinceLastAnn = 0
self.destinations[chName].ticksSinceLastSong = 0
self.destinations[chName].ticksSinceLastSpook = 0
self.destinations[chName].curSongLength = 0
self.destinations[chName].timerName = "MusicRadioSyncTimer"..chName
timer.Create(self.destinations[chName].timerName, 1, 0, function()
self:TickChanRunTimer(chName)
end)
end
function ix.musicRadio:DestinationHasTimer(chName)
if (!self.destinations[chName].timerName) then
return false
end
return timer.Exists(self.destinations[chName].timerName)
end
-- should tick every second that a channel is running to keep everything synced up!
function ix.musicRadio:TickChanRunTimer(chName)
if (!self:ChannelIsValid(chName)) then
return
end
self:CleanupNullEntities(chName)
self:TickSpook(chName)
self.destinations[chName].ticksSinceLastSong = self.destinations[chName].ticksSinceLastSong + 1
local songTimeWithTrans = self.destinations[chName].curSongLength or 0- self.transitionTime
if (self.destinations[chName].ticksSinceLastSong >= songTimeWithTrans or
!self.destinations[chName].curSong) then
self:PlayNextSong(chName)
end
end
function ix.musicRadio:TickSpook(chName)
if (!self:ChannelIsValid(chName)) then
return
end
local class = self.channels[chName].class
if (!class) then
ErrorNoHaltWithStack("Cannot check state of spooky sounds: Channel has invalid classname!")
return
end
if (!self:ClassIsEligibleForSpookySounds(class)) then
return
end
if (!self.destinations[chName].ticksSinceLastSpook) then
self.destinations[chName].ticksSinceLastSpook = 0
end
self.destinations[chName].ticksSinceLastSpook = self.destinations[chName].ticksSinceLastSpook + 1
if (self.destinations[chName].ticksSinceLastSpook < self.spookMinTime) then
return
end
if (math.random(1, self.spookChance) != 1) then
return
end
self.destinations[chName].ticksSinceLastSpook = 0
self:PlaySpookySound(chName)
end
function ix.musicRadio:PlaySpookySound(chName)
local snd = self:GetNextSpook()
for idx, _ in pairs(self.destinations[chName]) do
if (!isnumber(idx)) then
continue
end
local ent = Entity(idx)
if (ent and IsEntity(ent)) then
if (ent.GetVolume and ent.GetLevel) then -- is a music radio (just double checking <3)
self:InterruptCurrentSong(ent, snd.fname, snd.length, true)
end
end
end
end
function ix.musicRadio:ClassIsEligibleForSpookySounds(class)
if (!class or !self.spooky or !self.spooky.classes) then
return false
end
return self.spooky.classes[class]
end
function ix.musicRadio:GetNextSpook()
if (!self.spooky or !self.spooky.sounds) then
ErrorNoHaltWithStack("Attempted to roll next spooky sound when no spooky sounds were initialized!")
return
end
--return self.spooky.sounds[math.random(1, #self.spooky.sounds)]
return self.spooky.sounds[7]
end
function ix.musicRadio:CleanupNullEntities(chName)
--[[
Fixes null entities subscribed to the channel
]]
if (!self:ChannelIsValid(chName)) then
return
end
for idx, ent in pairs(self.destinations[chName]) do
local _idx = tonumber(idx)
if (_idx) then
if (!IsValid(Entity(_idx))) then
self.destinations[chName][idx] = nil
end
end
end
end
function ix.musicRadio:PlaySoundOnClass(soundName, className, length)
if (!soundName or string.len(soundName) < 1) then
return
end
if (!className or string.len(className) < 1 or !self.chanList[className]) then
return
end
if (length < 1) then
return
end
for i, chanName in ipairs(self.chanList[className]) do
if (self.destinations[chanName]) then
self.destinations[chanName].songsSinceLastAnn = self.destinations[chanName].songsSinceLastAnn + 1
self:PlayOnAllEnts(chanName, soundName)
self.destinations[chanName].curSong = soundName
self.destinations[chanName].curSongLength = length
self.destinations[chanName].ticksSinceLastSong = 0
end
end
end
function ix.musicRadio:SeekClass(className)
for i, chanName in ipairs(self.chanList[className]) do
if (self.destinations[chanName]) then
self:PlayNextSong(chanName)
end
end
end
function ix.musicRadio:PlayNextSong(chName)
-- current song over, time to start another one ;)
-- or we haven't started a song yet
-- reminder: the songs have baked in fade in/out which is why we do it this way
local snd
-- get class ;)
local class = self.channels[chName].class
if (!class) then
ErrorNoHaltWithStack("Cannot play next song: Channel has invalid classname!")
return
end
-- check if we're disabled
if (self:GetClassShouldPlayStatic(class)) then
snd = self.static.sounds[math.random(1, #self.static.sounds)]
-- check to see if its time for an announcement ;)
elseif (self.destinations[chName].songsSinceLastAnn >= self.destinations[chName].songsToNextAnn) then
-- its time!
snd = self:GetNextAnnouncement(chName)
if (snd) then
self.destinations[chName].songsSinceLastAnn = 0
self.destinations[chName].songsToNextAnn = math.random(8, 12)
else
-- no announcement for this class
snd = self:GetNextSong(chName)
self.destinations[chName].songsSinceLastAnn = self.destinations[chName].songsSinceLastAnn + 1
end
else
-- its not time yet, play a song instead ;(
snd = self:GetNextSong(chName)
self.destinations[chName].songsSinceLastAnn = self.destinations[chName].songsSinceLastAnn + 1
end
self:PlayOnAllEnts(chName, snd.fname)
self.destinations[chName].curSong = snd.fname
self.destinations[chName].curSongLength = snd.length
self.destinations[chName].ticksSinceLastSong = 0
end
-- returns the next announcement as it exists in the channel list. aka:
-- { fname = "filename", length = 420 seconds }
function ix.musicRadio:GetNextAnnouncement(chName)
if (!self:ChannelIsValid(chName)) then
return
end
if (!self.channels[chName].class) then
ErrorNoHaltWithStack("Attempted to roll next announcement on channel with no class: "..tostring(chName))
return
end
local class = self.channels[chName].class
if (!self.announcements[class] or !self.announcements[class].sounds) then
return
end
local nextSnd
-- prevent the same song from playing twice:
for i=1, 10 do
nextSnd = self.announcements[class].sounds[math.random(1,
table.Count(self.announcements[class].sounds)) or 1]
if (nextSnd.fname != self.channels[chName].curSong) then
return nextSnd
end
end
-- just reroll one more time and hope it isn't the same song ;)
return self.announcements[class].sounds[math.random(1, self.announcements[class].sounds)]
end
-- pause destination timers, which can be started again with self:StartDestination
function ix.musicRadio:PauseDestination(chName, bHardStop)
if (bHardStop == nil) then
bHardStop = true
end
if (!self:ChannelIsValid(chName)) then
return
end
if (!self.destinations[chName] or self.destinations[chName].paused) then
ErrorNoHaltWithStack("1Attempted to pause uninitialized channel: "..tostring(chName))
return
end
if (!self.destinations[chName].timerName) then
ErrorNoHaltWithStack("2Attempted to pause uninitialized channel: "..tostring(chName))
return
end
timer.Pause(self.destinations[chName].timerName)
if (bHardStop) then
local curSong = self.destinations[chName].curSong
for idx, _ in pairs(self.destinations[chName]) do
if (!isnumber(idx)) then
continue
end
local ent = Entity(idx)
if (ent.soundCache and ent.soundCache[curSong]) then
if (ent.soundCache[curSong]:IsPlaying()) then
ent.soundCache[curSong]:Stop()
end
end
end
self.destinations[chName].ticksSinceLastSong = 0
self.destinations[chName].curSongLength = 0
end
end
-- play sound for specified entity at the current time; sync
-- because we have no way to 'seek' the track forward, just start at the beginning
-- and then it'll sync up when the next song plays
function ix.musicRadio:SyncEnt(chName, ent)
if (!self:ChannelIsValid(chName)) then
return
end
if (self.destinations[chName].curSong) then
self:PlayOnEnt(chName, self.destinations[chName].curSong, ent,
ent:GetVolume() or self.radioVolume, ent:GetLevel() or self.radioLevel)
end
end
function ix.musicRadio:PlayOnEnt(chName, fileName, ent, vol, db)
if (!self:ChannelIsValid(chName)) then
ErrorNoHaltWithStack("Attempt to play on entity in invalid channel: "..tostring(chName))
return
end
if (!fileName or string.len(fileName) < 1) then
ErrorNoHaltWithStack("Invalid filename provided: "..tostring(fileName))
return
end
if (!ent or !IsValid(ent) or !ent:IsValid()) then
ErrorNoHaltWithStack("Invalid entity provided to channel: "..tostring(chName))
return
end
local idx = ent:EntIndex()
if (!self.destinations[chName][idx]) then
ErrorNoHaltWithStack("Attempt to play on entity not in channel! ID: "..tostring(idx))
return
end
if (!self.destinations[chName][idx].soundCache or
!istable(self.destinations[chName][idx].soundCache)
) then
self.destinations[chName][idx].soundCache = {}
end
local curSound = self.destinations[chName][idx].curSound -- string filename of the sound.
if (curSound and
self.destinations[chName][idx].soundCache[curSound] and -- string filename indexes the sound cache
self.destinations[chName][idx].soundCache[curSound]:IsPlaying()
) then
-- fade out the current track..
self.destinations[chName][idx].soundCache[curSound]:FadeOut(self.transitionTime)
end
if (self.destinations[chName][idx].soundCache[fileName]) then
-- cache hit
if (!self.destinations[chName][idx].soundCache[fileName]:IsPlaying()) then
-- stop the song if its already playing
self.destinations[chName][idx].soundCache[fileName]:Stop()
end
else
-- cache miss
-- by default CreateSound networks with the PAS of the sound
-- instead, we want to network it to everyone
self.destinations[chName][idx].soundCache[fileName] = CreateSound(ent,
fileName, RecipientFilter():AddAllPlayers())
-- clean the cache
for snd, csnd in pairs(self.destinations[chName][idx].soundCache) do
if (csnd != curSound and snd != fileName and !csnd:IsPlaying()) then
self.destinations[chName][idx].soundCache[snd] = nil
end
end
end
self.destinations[chName][idx].soundCache[fileName]:SetSoundLevel(ent:GetLevel() or db)
--[[
NOTE: There is a memory issue in SetDSP that FP knows about but refuses to fix!!!!
If there are too many CSoundPatches with an active DSP set on them playing in one room it will overrun the client's audio buffer.
This makes the game run at like 2fps and it sounds like someone put a microphone in a blender.
]]
self.destinations[chName][idx].soundCache[fileName]:SetDSP(self.dspPreset)
self.destinations[chName][idx].soundCache[fileName]:PlayEx(ent:GetVolume() or vol, 100)
self.destinations[chName][idx].curSound = fileName
end
-- play sound for all chan ents listening to a particular channel
function ix.musicRadio:PlayOnAllEnts(chName, fileName, vol, db)
if (!fileName or string.len(fileName) < 1) then
ErrorNoHaltWithStack("Attempt to play empty or nil filename!")
return
end
if (!self:ChannelIsValid(chName)) then
ErrorNoHaltWithStack("Attempt to play with invalid channel: "..tostring(chName))
return
end
for idx, _ in pairs(self.destinations[chName]) do
if (!isnumber(idx)) then
continue
end
local ent = Entity(idx)
if (ent and IsEntity(ent)) then
if (ent.GetVolume and ent.GetLevel) then -- is a music radio
ix.musicRadio:PlayOnEnt(chName, fileName, ent,
ent:GetVolume() or vol or self.radioVolume,
ent:GetLevel() or db or self.radioLevel)
end
end
end
end
-- returns the next song as it exists in the channel list. aka:
-- { fname = "filename", length = 420 seconds }
function ix.musicRadio:GetNextSong(chName)
if (!self:ChannelIsValid(chName)) then
ErrorNoHaltWithStack("Cannot play next song: Channel is invalid!")
return
end
local nextSong
-- prevent the same song from playing twice:
for i=1, 10 do
nextSong = self.channels[chName].songs[math.random(1, #self.channels[chName].songs)]
if (nextSong.fname != self.channels[chName].curSong) then
return nextSong
end
end
-- just reroll one more time and hope it isn't the same song ;)
return self.channels[chName].songs[math.random(1, #self.channels[chName].songs)]
end
-- plays a random splurt of static on the musicradio entity
function ix.musicRadio:PlayTuneStatic(ent, maxVol)
if (!ent or !ent.EmitSound) then
return
end
local staticFName = "willardnetworks/musicradio/musicradio_static_"..tostring(math.random(1, 6)..".mp3")
self:InterruptCurrentSong(ent, staticFName, self.staticTime, maxVol)
end
function ix.musicRadio:InterruptCurrentSong(ent, fName, time, maxVol)
local vol = ent:GetVolume() or self.radioVolume
if (maxVol) then
vol = 1
end
local chan = ent:GetNWString("curChan", "")
local curVol = ent:GetNWInt("vol", 1)
local curLvl = ent:GetNWInt("db", 70)
ent:SetNWInt("vol", 0.1) -- just in case ;)
local curSound = self.destinations[chan].curSong
if (ent.soundCache) then
ent.soundCache[curSound]:ChangeVolume(0.05, 1)
ent.soundCache[curSound]:SetSoundLevel(60)
end
ent:EmitSound(fName,
ent.db or self.radioLevel, 100, vol)
timer.Simple(time, function()
if (ent and IsValid(ent)) then
ent:StopSound(fName)
if (!ent.soundCache) then
return
end
chan = ent:GetNWString("curChan", "")
local curSoundNow = self.destinations[chan].curSong
ent:SetNWInt("vol", curVol)
ent.soundCache[curSoundNow]:ChangeVolume(curVol, 1) -- turn the old song back up
ent.soundCache[curSoundNow]:SetSoundLevel(curLvl)
end
end)
end
function ix.musicRadio:InstallTuner(client, isPirate)
local char = client:GetCharacter()
local trace = client:GetEyeTraceNoCursor()
local ent = trace.Entity
if (!ent or !IsValid(ent)) then
client:Notify("You are not looking at anything!")
return
end
if (!ent.SetVolume or !ent.GetRadioClass) then
client:Notify("The device you are looking at is not a music radio!")
return
end
local curCls = ent:GetRadioClass()
if (isPirate and curCls == "pirate") then
client:Notify("This radio is already tuned to the pirate frequencies!")
return
elseif (!isPirate and curCls == "benefactor") then
client:Notify("This radio is already tuned to the benefactor frequencies!")
return
end
local lvl = char:GetSkill("crafting")
if (lvl < 10) then
client:Notify("Your crafting skill is too low to perform this action.")
return
end
local cls = isPirate and "pirate" or "benefactor"
-- 'turn off' the radio
ent:StopCurrentSong()
ix.musicRadio:TuneOut(ent, ent:GetChan())
ent:SetNWString("curChan", "")
-- set the new class
ent:SetRadioClass(cls)
client:SetAction("Tuning...", 3, function()
-- tune it to the new class
ent:SetNWString("curChan", ent.defaultStation)
ix.musicRadio:TuneIn(ent, ent.defaultStation)
end)
end
local function OnSave(entity, data)
if (!IsValid(entity) or !entity.GetRadioClass) then
ErrorNoHaltWithStack("Attempted to save invalid entity as a radio!")
return
end
data.radioClass = entity:GetRadioClass() or "benefactor"
end
local function OnRestore(entity, data)
if (!IsValid(entity) or !entity.GetRadioClass) then
ErrorNoHaltWithStack("Attempted to restore invalid entity as a radio!")
return
end
if (!data.radioClass) then
data.radioClass = "benefactor"
end
entity:SetRadioClass(data.radioClass)
end
function PLUGIN:RegisterSaveEnts()
ix.saveEnts:RegisterEntity("wn_musicradio", true, true, true, {
OnSave = OnSave,
OnRestore = OnRestore
})
end
-- Called when loading all the data that has been saved.
function PLUGIN:LoadData()
if (!ix.config.Get("SaveEntsOldLoadingEnabled")) then return end
if (!istable(ix.musicRadio.static)) then
ix.musicRadio.static = {}
end
-- might as well load up the static classes here too
ix.musicRadio.static.classes = ix.musicRadio:GetSavedStatic()
for _, v in ipairs(ix.data.Get("musicRadios") or {}) do
local entity = ents.Create(v.class)
entity:SetPos(v.pos)
entity:SetAngles(v.angles)
entity:Spawn()
entity:SetSolid(SOLID_OBB)
entity:PhysicsInit(SOLID_OBB)
local physObj = entity:GetPhysicsObject()
if (IsValid(physObj)) then
physObj:EnableMotion(false)
physObj:Sleep()
end
end
end
function ix.musicRadio:GetClassShouldPlayStatic(className)
if (!self:ClassIsValid(className)) then
ErrorNoHaltWithStack("Attempted to get static state on invalid class: "..tostring(className))
return
end
if (!istable(self.static)) then
self:InitStatic()
end
if (!istable(self.static.classes)) then
self.static.classes = ix.musicRadio:GetSavedStatic()
end
if (self.static.classes[className] == nil) then
-- hasn't been set yet
self.static.classes[className] = self.CHAN_ENABLED
self:SaveStatic() -- save the new default
end
if (self.static.classes[className] == self.CHAN_DISABLED) then
return true
else
return false
end
end
-- sets a class of radio as 'disabled' so that it only plays static.
function ix.musicRadio:SetClassStaticState(className, state)
if (!self:ClassIsValid(className)) then
ErrorNoHaltWithStack("Attempted to set static state on invalid class: "..tostring(className))
return
end
self.static.classes[className] = state
if (state == self.CHAN_DISABLED) then
-- immediately start playing static on all the radios of this class
self:PlaySoundOnClass(self.static.sounds[math.random(1, #self.static.sounds)].fname,
className, 55)
else
self:RestartClass(className) -- otherwise, restart the classes channels
end
self:SaveStatic()
end
-- get the static classes from data folder
function ix.musicRadio:GetSavedStatic()
local jsonDat = file.Read("musicradio/static.json", "DATA")
local tab = util.JSONToTable(jsonDat or "{}")
if (!tab or !istable(tab)) then
ErrorNoHaltWithStack("Unable to load saved musicradio static")
return
end
return tab
end
-- save the current state to data folder
function ix.musicRadio:SaveStatic()
local tab = util.TableToJSON(self.static.classes or {})
file.CreateDir("musicradio")
file.Write("musicradio/static.json", tab)
end