Initial commit

This commit is contained in:
pavetr
2026-03-15 14:54:49 +03:00
commit 64f8029c06
4027 changed files with 254888 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
local updateModel, getModelValue, onModelUpdate =
DarkRP.tipJarUIModel.updateModel,
DarkRP.tipJarUIModel.getModelValue,
DarkRP.tipJarUIModel.onModelUpdate
onModelUpdate("lastTipAmount", function(amount)
if amount <= 0 then return end
local tipjar = getModelValue("tipjar")
if not IsValid(tipjar) then return end
net.Start("DarkRP_TipJarDonate")
net.WriteEntity(tipjar)
net.WriteUInt(amount, 32)
net.SendToServer()
end)
net.Receive("DarkRP_TipJarUI", fc{DarkRP.tipJarUI, net.ReadEntity})
net.Receive("DarkRP_TipJarDonate", function()
local tipjar = net.ReadEntity()
local ply = net.ReadEntity()
local amount = net.ReadUInt(32)
if not IsValid(tipjar) then return end
if not IsValid(ply) then return end
tipjar:Donated(ply, amount)
updateModel("donatedUpdate")
end)
onModelUpdate("amount", function(amount, old)
local tipjar = getModelValue("tipjar")
if not IsValid(tipjar) then return end
tipjar:UpdateActiveDonation(LocalPlayer(), amount)
if amount == old then return end
net.Start("DarkRP_TipJarUpdate")
net.WriteEntity(tipjar)
net.WriteUInt(amount, 32)
net.SendToServer()
end)
net.Receive("DarkRP_TipJarUpdate", function(len)
local tipjar = net.ReadEntity()
if not IsValid(tipjar) then return end
local bitsRead = 16
while bitsRead < len do
tipjar:UpdateActiveDonation(net.ReadEntity(), net.ReadUInt(32))
-- I thought there was a function for this?
bitsRead = bitsRead + 16 + 32
end
end)
onModelUpdate("frameVisible", function(visible)
local localply = LocalPlayer()
local tipjar = getModelValue("tipjar")
local amount = getModelValue("amount")
if not IsValid(localply) then return end
if not IsValid(tipjar) then return end
if visible then
tipjar:ClearActiveDonations()
tipjar:UpdateActiveDonation(localply, amount)
net.Start("DarkRP_TipJarUpdate")
net.WriteEntity(tipjar)
net.WriteUInt(amount, 32)
net.SendToServer()
else
net.Start("DarkRP_TipJarExit")
net.WriteEntity(tipjar)
net.SendToServer()
tipjar:ExitActiveDonation(localply)
end
end)
net.Receive("DarkRP_TipJarExit", function()
local tipjar = net.ReadEntity()
local ply = net.ReadEntity()
if not IsValid(tipjar) then return end
if not IsValid(ply) then return end
tipjar:ExitActiveDonation(ply)
end)
net.Receive("DarkRP_TipJarDonatedList", function()
local tipjar = net.ReadEntity()
local count = net.ReadUInt(8)
if not IsValid(tipjar) then return end
tipjar:ClearDonations()
for i = 1, count do
tipjar:AddDonation(net.ReadString(), net.ReadUInt(32))
end
updateModel("donatedUpdate")
end)
local function onUpdateActiveDonation(_, tipjar)
if not IsValid(tipjar) or tipjar ~= getModelValue("tipjar") then return end
updateModel("activeDonationUpdate")
end
DarkRP.hooks.tipjarUpdateActiveDonation = onUpdateActiveDonation
DarkRP.hooks.tipjarExitActiveDonation = onUpdateActiveDonation
DarkRP.hooks.tipjarClearActiveDonation = onUpdateActiveDonation

View File

@@ -0,0 +1,294 @@
local updateModel, getModelValue, onModelUpdate =
DarkRP.tipJarUIModel.updateModel,
DarkRP.tipJarUIModel.getModelValue,
DarkRP.tipJarUIModel.onModelUpdate
--[[-------------------------------------------------------------------------
Donation amount entry
---------------------------------------------------------------------------]]
local DONATE_ENTRY = {}
function DONATE_ENTRY:Init()
self.BaseClass.Init(self)
self:SetNumeric(true)
self:SetSize(290, 70)
self:SetFont("HUDNumber5")
self:SetText("")
onModelUpdate("amount", function(new, _, src)
if not IsValid(self) or src == self then return end
local pos = self:GetCaretPos()
self:SetText(new)
self:SetCaretPos(math.min(pos, string.len(new)))
end)
onModelUpdate("lastTipAmount", function()
self:SelectAllText()
self:RequestFocus()
end)
end
function DONATE_ENTRY:CheckNumeric(value)
if DarkRP.toInt(value) then return false end
return true
end
function DONATE_ENTRY:OnChange()
local value = DarkRP.toInt(self:GetText())
if not value then return end
updateModel("amount", value, self)
end
function DONATE_ENTRY:OnEnter()
updateModel("frameVisible", false)
updateModel("lastTipAmount", getModelValue("amount"))
end
derma.DefineControl("DarkRP_TipJar_DONATE_ENTRY", "", DONATE_ENTRY, "DTextEntry")
local DONATE_BUTTON = {}
function DONATE_BUTTON:Init()
self.BaseClass.Init(self)
self:SetSize(290, 80)
self:SetText("")
self.donateLabel = vgui.Create("DLabel", self)
self.donateLabel:SetFont("HUDNumber5")
self.donateLabel:SetText(DarkRP.getPhrase("Donate"))
self.donateLabel:SizeToContents()
self.donateLabel:SetPos(50, 10)
self.donateLabel:CenterHorizontal()
self.amountLabel = vgui.Create("DLabel", self)
self.amountLabel:SetFont("HUDNumber5")
self.amountLabel:SetPos(50, 40)
onModelUpdate("amount", function(new)
if not IsValid(self) then return end
self.amountLabel:SetText(DarkRP.formatMoney(new))
self.amountLabel:SizeToContents()
self.amountLabel:CenterHorizontal()
end)
end
function DONATE_BUTTON:DoClick()
-- updateModel("frameVisible", false)
updateModel("lastTipAmount", getModelValue("amount"))
end
derma.DefineControl("DarkRP_TipJar_DONATE_BUTTON", "", DONATE_BUTTON, "DButton")
local DONATE_LIST_ITEM = {}
function DONATE_LIST_ITEM:Init()
self:SetSize(470, 25)
self:SetPaintBackground(false)
self.textL = vgui.Create("DLabel", self)
self.textL:SetFont("DarkRPHUD2")
self.textL:Dock(LEFT)
self.textR = vgui.Create("DLabel", self)
self.textR:SetFont("DarkRPHUD2")
self.textR:DockMargin(5, 5, 20, 5)
self.textR:Dock(RIGHT)
self.donatedColor = Color(50, 130, 50)
self.activeColor = Color(180, 180, 180)
self.moneyColor = Color(50, 130, 50)
end
function DONATE_LIST_ITEM:SetActive(name, amount)
self.textL:SetText(name)
self.textL:SetTextColor(self.activeColor)
self.textL:SizeToContents()
self.textR:SetText(amount)
self.textR:SetTextColor(self.activeColor)
self.textR:SizeToContents()
end
function DONATE_LIST_ITEM:SetDonated(name, amount)
self.textL:SetText(name)
self.textL:SetTextColor(self.donatedColor)
self.textL:SizeToContents()
self.textR:SetText(amount)
self.textR:SetTextColor(self.moneyColor)
self.textR:SizeToContents()
end
derma.DefineControl("DarkRP_TipJar_DONATE_LIST_ITEM", "", DONATE_LIST_ITEM, "DPanel")
local DONATE_LIST = {}
function DONATE_LIST:Init()
self.activeLines = {}
self.donatedLines = {}
self:SetSize(480, 465)
self:SetBackgroundColor(color_transparent)
self:EnableVerticalScrollbar()
self:SetSpacing(10)
self.VBar.Paint = fn.Id
self.VBar.btnUp.Paint = fn.Id
self.VBar.btnDown.Paint = fn.Id
onModelUpdate("activeDonationUpdate", function()
local tipjar = getModelValue("tipjar")
if not IsValid(self) then return end
if not IsValid(tipjar) then return end
self:RebuildLines(tipjar)
end)
onModelUpdate("donatedUpdate", function()
local tipjar = getModelValue("tipjar")
if not IsValid(self) then return end
if not IsValid(tipjar) then return end
self:RebuildLines(tipjar)
self:PerformLayout()
self.VBar:SetScroll(math.huge)
end)
end
function DONATE_LIST:AddActiveLine(name, amount)
local line = vgui.Create("DarkRP_TipJar_DONATE_LIST_ITEM", self)
line:SetActive(name, amount)
self:AddItem(line)
table.insert(self.activeLines, line)
end
function DONATE_LIST:AddDonatedLine(name, amount)
local line = vgui.Create("DarkRP_TipJar_DONATE_LIST_ITEM", self)
line:SetDonated(name, amount)
self:AddItem(line)
table.insert(self.donatedLines, line)
end
function DONATE_LIST:ClearLines()
for _, line in ipairs(self.activeLines) do
line:Remove()
end
for _, line in ipairs(self.donatedLines) do
line:Remove()
end
table.Empty(self.activeLines)
table.Empty(self.donatedLines)
end
function DONATE_LIST:RebuildLines(tipjar)
self:ClearLines()
for _, donation in ipairs(tipjar.madeDonations) do
self:AddDonatedLine(donation.name, DarkRP.formatMoney(donation.amount))
end
for ply, amount in pairs(tipjar.activeDonations) do
-- Don't show the owner looking at this page
if ply == tipjar:Getowning_ent() then continue end
self.activeLines[ply:Nick()] = DarkRP.formatMoney(amount)
end
for name, amount in SortedPairs(self.activeLines) do
self:AddActiveLine(name, amount)
end
end
derma.DefineControl("DarkRP_TipJar_DONATE_LIST", "", DONATE_LIST, "DPanelList")
--[[-------------------------------------------------------------------------
Main frame
---------------------------------------------------------------------------]]
local FRAME = {}
function FRAME:Init()
self:SetTitle("Tipping jar")
self:SetSize(800, 500)
self:Center()
self:SetVisible(true)
self:MakePopup()
self:SetDeleteOnClose(false)
self.donateEntry = vgui.Create("DarkRP_TipJar_DONATE_ENTRY", self)
self.donateEntry:SetPos(10, 175)
self.donateButton = vgui.Create("DarkRP_TipJar_DONATE_BUTTON", self)
self.donateButton:SetPos(10, 245)
self.donateList = vgui.Create("DarkRP_TipJar_DONATE_LIST", self)
self.donateList:SetPos(310, 25)
self:SetSkin(GAMEMODE.Config.DarkRPSkin)
end
function FRAME:OnClose()
updateModel("frameVisible", false)
end
function FRAME:Think()
local tipJar = getModelValue("tipjar")
if not IsValid(tipJar) or
tipJar:GetPos():DistToSqr(LocalPlayer():GetPos()) > 100 * 100 then
updateModel("frameVisible", false)
end
end
derma.DefineControl("DarkRP_TipJar_FRAME", "", FRAME, "DFrame")
onModelUpdate("frameVisible", function(visible)
local tipjar = getModelValue("tipjar")
if not IsValid(tipjar) then return end
if not getModelValue("frame") then
if not visible then return end
updateModel("frame", vgui.Create("DarkRP_TipJar_FRAME"))
end
local frame = getModelValue("frame")
frame:SetVisible(visible)
if visible then
updateModel("amount", 0)
frame.donateEntry:SelectAllText()
frame.donateEntry:RequestFocus()
local disable = getModelValue("isOwner")
frame.donateEntry:SetDisabled(disable)
frame.donateButton:SetDisabled(disable)
end
end)
function DarkRP.tipJarUI(tipjar)
updateModel("tipjar", tipjar)
updateModel("isOwner", tipjar:Getowning_ent() == LocalPlayer())
updateModel("amount", 0)
updateModel("frameVisible", true)
end

View File

@@ -0,0 +1,128 @@
--[[-------------------------------------------------------------------------
The model describes the data that the drives the UI.
Loosely based on the Elm architecture.
---------------------------------------------------------------------------]]
local model = {
-- The tip jar entity
tipjar = nil,
-- Whether the LocalPlayer is the owner of this tipjar
isOwner = false,
-- Whether the frame is visible
frameVisible = false,
-- The Derma frame object
frame = nil,
-- The amount the player is putting in the tip jar
amount = 0,
-- The last amount of money the player actually put in the tip jar
lastTipAmount = 0,
-- Action: when an active donation changes. The active donations
-- themselves are fetched from the tipjar, which keeps track of the proper
-- state for it.
activeDonationUpdate = nil,
-- Action: when someone donated. The list of donators is kept track of by
-- the tipjar.
donatedUpdate = nil,
}
local updaters = {}
DarkRP.tipJarUIModel = {}
--[[-------------------------------------------------------------------------
Update the model.
Automatically calls the registered update hook functions
---------------------------------------------------------------------------]]
function DarkRP.tipJarUIModel.updateModel(path, value, ...)
path = istable(path) and path or {path}
local updTbl = updaters
local mdlTbl = model
local key = path[#path]
for i = 1, #path - 1 do
mdlTbl = mdlTbl[path[i]]
updTbl = updTbl and updTbl[path[i]]
end
local oldValue = mdlTbl[key]
mdlTbl[key] = value
for _, updFunc in ipairs(updTbl and updTbl[key] or {}) do
updFunc(value, oldValue, ...)
-- the updFunc changed this value, break off
if mdlTbl[key] ~= value then break end
end
end
--[[-------------------------------------------------------------------------
Retrieve a value of the model
---------------------------------------------------------------------------]]
function DarkRP.tipJarUIModel.getModelValue(path)
path = istable(path) and path or {path}
local mdlTbl = model
local key = path[#path]
for i = 1, #path - 1 do
mdlTbl = mdlTbl[path[i]]
end
return mdlTbl[key]
end
--[[-------------------------------------------------------------------------
Registers a hook that gets triggered when a certain part of the model is
updated
---------------------------------------------------------------------------]]
function DarkRP.tipJarUIModel.onModelUpdate(path, func)
path = istable(path) and path or {path}
local updTbl = updaters
local mdlTbl = model
local key = path[#path]
for i = 1, #path - 1 do
mdlTbl = mdlTbl[path[i]]
updTbl[path[i]] = updTbl[path[i]] or {}
updTbl = updTbl[path[i]]
end
updTbl[key] = updTbl[key] or {}
table.insert(updTbl[key], func)
-- Call update with the initial value
if mdlTbl[key] ~= nil then
func(mdlTbl[key], mdlTbl[key])
end
end
--[[-------------------------------------------------------------------------
Default listeners
---------------------------------------------------------------------------]]
local updateModel, _getModelValue, onModelUpdate =
DarkRP.tipJarUIModel.updateModel,
DarkRP.tipJarUIModel.getModelValue,
DarkRP.tipJarUIModel.onModelUpdate
onModelUpdate("amount", function(new, _)
local localply = LocalPlayer()
if not IsValid(localply) then return end
local ownMoney = localply:getDarkRPVar("money") or 0
if new < 0 or new % 1 ~= 0 or new > ownMoney then
local amount = math.abs(math.floor(new))
amount = math.min(amount, ownMoney)
updateModel("amount", amount)
end
end)

View File

@@ -0,0 +1,139 @@
util.AddNetworkString("DarkRP_TipJarUI")
util.AddNetworkString("DarkRP_TipJarDonate")
util.AddNetworkString("DarkRP_TipJarUpdate")
util.AddNetworkString("DarkRP_TipJarExit")
util.AddNetworkString("DarkRP_TipJarDonatedList")
net.Receive("DarkRP_TipJarDonate", function(_, ply)
local tipjar = net.ReadEntity()
local amount = net.ReadUInt(32)
if not IsValid(tipjar) then return end
if not tipjar.IsTipjar then return end
local owner = tipjar:Getowning_ent()
if not IsValid(owner) then return end
if owner == ply then return end
ply.DarkRPLastTip = ply.DarkRPLastTip or -1
if ply.DarkRPLastTip > CurTime() - 0.1 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("wait_with_that"))
return
end
if not ply:canAfford(amount) then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("cant_afford", amount))
return
end
if tipjar:GetPos():DistToSqr(ply:GetPos()) > 100 * 100 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("distance_too_big"))
return
end
DarkRP.payPlayer(ply, owner, amount)
tipjar:AddDonation(ply:Nick(), amount)
tipjar:EmitSound("ambient/alarms/warningbell1.wav")
local strAmount = DarkRP.formatMoney(amount)
DarkRP.notify(ply, 3, 4, DarkRP.getPhrase("you_donated", strAmount, owner:Nick()))
DarkRP.notify(owner, 3, 4, DarkRP.getPhrase("has_donated", ply:Nick(), strAmount))
net.Start("DarkRP_TipJarDonate")
net.WriteEntity(tipjar)
net.WriteEntity(ply)
net.WriteUInt(amount, 32)
net.Broadcast()
ply.DarkRPLastTip = CurTime()
end)
net.Receive("DarkRP_TipJarUpdate", function(_, ply)
local tipjar = net.ReadEntity()
local amount = net.ReadUInt(32)
if not IsValid(tipjar) then return end
if not tipjar.IsTipjar then return end
-- Larger margin of distance, to prevent false positives
if tipjar:GetPos():DistToSqr(ply:GetPos()) > 150 * 150 then
return
end
tipjar:UpdateActiveDonation(ply, amount)
end)
-- Send a tipjar's donation data to a single player
local function sendJarData(tipjar, ply)
if not table.IsEmpty(tipjar.activeDonations) then
net.Start("DarkRP_TipJarUpdate")
net.WriteEntity(tipjar)
for p, amnt in pairs(tipjar.activeDonations) do
net.WriteEntity(p)
net.WriteUInt(amnt, 32)
end
net.Send(ply)
end
if not table.IsEmpty(tipjar.madeDonations) then
net.Start("DarkRP_TipJarDonatedList")
net.WriteEntity(tipjar)
net.WriteUInt(#tipjar.madeDonations, 8)
for _, donation in ipairs(tipjar.madeDonations) do
net.WriteString(donation.name)
net.WriteUInt(donation.amount, 32)
end
net.Send(ply)
end
end
function DarkRP.hooks:tipjarUpdateActiveDonation(tipjar, ply, amount, old)
-- Player is new to this jar, send all data
if not old then
sendJarData(tipjar, ply)
end
-- Tell the rest of the player's active donation
local updateTargets = RecipientFilter()
for p, _ in pairs(tipjar.activeDonations) do
updateTargets:AddPlayer(p)
end
updateTargets:RemovePlayer(ply)
net.Start("DarkRP_TipJarUpdate")
net.WriteEntity(tipjar)
net.WriteEntity(ply)
net.WriteUInt(amount, 32)
net.Send(updateTargets)
end
net.Receive("DarkRP_TipJarExit", function(_, ply)
local tipjar = net.ReadEntity()
if not IsValid(tipjar) then return end
if not tipjar.IsTipjar then return end
tipjar:ExitActiveDonation(ply)
end)
function DarkRP.hooks:tipjarExitActiveDonation(tipjar, ply, old)
local updateTargets = RecipientFilter()
for p, _ in pairs(tipjar.activeDonations) do
updateTargets:AddPlayer(p)
end
net.Start("DarkRP_TipJarExit")
net.WriteEntity(tipjar)
net.WriteEntity(ply)
net.Send(updateTargets)
end