--[[ | 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/ --]] -- luacheck: globals VENDOR_BUY VENDOR_SELL VENDOR_BOTH VENDOR_WELCOME VENDOR_LEAVE VENDOR_NOTRADE VENDOR_PRICE -- luacheck: globals VENDOR_STOCK VENDOR_MODE VENDOR_MAXSTOCK VENDOR_SELLANDBUY VENDOR_SELLONLY VENDOR_BUYONLY VENDOR_TEXT local PLUGIN = PLUGIN PLUGIN.name = "Vendors" PLUGIN.author = "Chessnut" PLUGIN.description = "Adds NPC vendors that can sell things." CAMI.RegisterPrivilege({ Name = "Helix - Manage Vendors", MinAccess = "admin" }) VENDOR_BUY = 1 VENDOR_SELL = 2 VENDOR_BOTH = 3 -- Keys for vendor messages. VENDOR_WELCOME = 1 VENDOR_LEAVE = 2 VENDOR_NOTRADE = 3 -- Keys for item information. VENDOR_PRICE = 1 VENDOR_STOCK = 2 VENDOR_MODE = 3 VENDOR_MAXSTOCK = 4 -- Sell and buy the item. VENDOR_SELLANDBUY = 1 -- Only sell the item to the player. VENDOR_SELLONLY = 2 -- Only buy the item from the player. VENDOR_BUYONLY = 3 if (SERVER) then util.AddNetworkString("ixVendorOpen") util.AddNetworkString("ixVendorClose") util.AddNetworkString("ixVendorTrade") util.AddNetworkString("ixVendorEdit") util.AddNetworkString("ixVendorEditFinish") util.AddNetworkString("ixVendorEditor") util.AddNetworkString("ixVendorMoney") util.AddNetworkString("ixVendorStock") util.AddNetworkString("ixVendorAddItem") function PLUGIN:RegisterSaveEnts() ix.saveEnts:RegisterEntity("ix_vendor", true, true, true, { OnSave = function(entity, data) --OnSave data.name = entity:GetDisplayName() data.description = entity:GetDescription() data.model = entity:GetModel() data.items = entity.items data.money = entity.money data.maxStock = entity.maxStock data.stashList = entity.stashList data.motion = nil data.bubble = entity:GetNoBubble() data.factions = entity.factions data.classes = entity.classes data.scale = entity.scale data.useCredits = entity:GetUseCredits() end, OnRestore = function(entity, data) --OnRestore entity:SetModel(data.model) entity:SetSolid(SOLID_BBOX) entity:PhysicsInit(SOLID_BBOX) local physObj = entity:GetPhysicsObject() if (IsValid(physObj)) then physObj:EnableMotion(false) physObj:Sleep() end entity:SetDisplayName(data.name) entity:SetDescription(data.description) entity:SetNoBubble(data.bubble) entity:SetUseCredits(data.useCredits or false) local items = {} for uniqueID, itemData in pairs(data.items) do items[tostring(uniqueID)] = itemData end entity.items = items entity.factions = data.factions or {} entity.classes = data.classes or {} entity.money = data.money entity.scale = data.scale or 1 end, }) end function PLUGIN:SaveData() local data = {} for _, entity in ipairs(ents.FindByClass("ix_vendor")) do local bodygroups = {} for _, v in ipairs(entity:GetBodyGroups() or {}) do bodygroups[v.id] = entity:GetBodygroup(v.id) end data[#data + 1] = { name = entity:GetDisplayName(), description = entity:GetDescription(), pos = entity:GetPos(), angles = entity:GetAngles(), model = entity:GetModel(), skin = entity:GetSkin(), bodygroups = bodygroups, bubble = entity:GetNoBubble(), items = entity.items, factions = entity.factions, classes = entity.classes, money = entity.money, scale = entity.scale, useCredits = entity:GetUseCredits() } end self:SetData(data) end function PLUGIN:LoadData() if (ix.saveEnts and !ix.config.Get("SaveEntsOldLoadingEnabled")) then return end for _, v in ipairs(self:GetData() or {}) do local entity = ents.Create("ix_vendor") entity:SetPos(v.pos) entity:SetAngles(v.angles) entity:Spawn() entity:SetModel(v.model) entity:SetSkin(v.skin or 0) entity:SetSolid(SOLID_BBOX) entity:PhysicsInit(SOLID_BBOX) local physObj = entity:GetPhysicsObject() if (IsValid(physObj)) then physObj:EnableMotion(false) physObj:Sleep() end entity:SetNoBubble(v.bubble) entity:SetDisplayName(v.name) entity:SetDescription(v.description) entity:SetUseCredits(v.useCredits or false) for id, bodygroup in pairs(v.bodygroups or {}) do entity:SetBodygroup(id, bodygroup) end local items = {} for uniqueID, data in pairs(v.items) do items[tostring(uniqueID)] = data end entity.items = items entity.factions = v.factions or {} entity.classes = v.classes or {} entity.money = v.money entity.scale = v.scale or 1 end end function PLUGIN:CanVendorSellItem(client, vendor, itemID) local tradeData = vendor.items[itemID] local char = client:GetCharacter() if (!tradeData or !char) then return false end if (!char:HasMoney(tradeData[1] or 0)) then return false end return true end ix.log.AddType("vendorUse", function(client, ...) local arg = {...} return string.format("%s used the '%s' vendor.", client:Name(), arg[1]) end) net.Receive("ixVendorClose", function(length, client) local entity = client.ixVendor if (IsValid(entity)) then for k, v in ipairs(entity.receivers) do if (v == client) then table.remove(entity.receivers, k) break end end client.ixVendor = nil end end) local function UpdateEditReceivers(receivers, key, value) net.Start("ixVendorEdit") net.WriteString(key) net.WriteType(value) net.Send(receivers) end net.Receive("ixVendorEdit", function(length, client) if (!CAMI.PlayerHasAccess(client, "Helix - Manage Vendors", nil)) then return end local entity = client.ixVendor if (!IsValid(entity)) then return end local key = net.ReadString() local data = net.ReadType() local feedback = true if (key == "name") then entity:SetDisplayName(data) elseif (key == "description") then entity:SetDescription(data) elseif (key == "bubble") then entity:SetNoBubble(data) elseif (key == "mode") then local uniqueID = data[1] entity.items[uniqueID] = entity.items[uniqueID] or {} entity.items[uniqueID][VENDOR_MODE] = data[2] UpdateEditReceivers(entity.receivers, key, data) elseif (key == "price") then local uniqueID = data[1] data[2] = tonumber(data[2]) if (data[2]) then data[2] = math.Round(data[2]) end entity.items[uniqueID] = entity.items[uniqueID] or {} entity.items[uniqueID][VENDOR_PRICE] = data[2] UpdateEditReceivers(entity.receivers, key, data) data = uniqueID elseif (key == "stockDisable") then local uniqueID = data[1] entity.items[data] = entity.items[uniqueID] or {} entity.items[data][VENDOR_MAXSTOCK] = nil UpdateEditReceivers(entity.receivers, key, data) elseif (key == "stockMax") then local uniqueID = data[1] data[2] = math.max(math.Round(tonumber(data[2]) or 1), 1) entity.items[uniqueID] = entity.items[uniqueID] or {} entity.items[uniqueID][VENDOR_MAXSTOCK] = data[2] entity.items[uniqueID][VENDOR_STOCK] = math.Clamp(entity.items[uniqueID][VENDOR_STOCK] or data[2], 1, data[2]) data[3] = entity.items[uniqueID][VENDOR_STOCK] UpdateEditReceivers(entity.receivers, key, data) data = uniqueID elseif (key == "stock") then local uniqueID = data[1] entity.items[uniqueID] = entity.items[uniqueID] or {} if (!entity.items[uniqueID][VENDOR_MAXSTOCK]) then data[2] = math.max(math.Round(tonumber(data[2]) or 0), 0) entity.items[uniqueID][VENDOR_MAXSTOCK] = data[2] end data[2] = math.Clamp(math.Round(tonumber(data[2]) or 0), 0, entity.items[uniqueID][VENDOR_MAXSTOCK]) entity.items[uniqueID][VENDOR_STOCK] = data[2] UpdateEditReceivers(entity.receivers, key, data) data = uniqueID elseif (key == "faction") then local faction = ix.faction.teams[data] if (faction) then entity.factions[data] = !entity.factions[data] if (!entity.factions[data]) then entity.factions[data] = nil end end local uniqueID = data data = {uniqueID, entity.factions[uniqueID]} elseif (key == "class") then local class for _, v in ipairs(ix.class.list) do if (v.uniqueID == data) then class = v break end end if (class) then entity.classes[data] = !entity.classes[data] if (!entity.classes[data]) then entity.classes[data] = nil end end local uniqueID = data data = {uniqueID, entity.classes[uniqueID]} elseif (key == "model") then entity:SetModel(data) entity:SetSolid(SOLID_BBOX) entity:PhysicsInit(SOLID_BBOX) entity:SetAnim() elseif (key == "useMoney") then if (entity.money) then entity:SetMoney() else entity:SetMoney(0) end elseif (key == "money") then data = math.Round(math.abs(tonumber(data) or 0)) entity:SetMoney(data) feedback = false elseif (key == "scale") then data = tonumber(data) or 0.5 entity.scale = data UpdateEditReceivers(entity.receivers, key, data) elseif (key == "useCredits") then entity:SetUseCredits(data) end if (ix.saveEnts) then ix.saveEnts:SaveEntity(entity) end PLUGIN:SaveData() if (feedback) then local receivers = {} for _, v in ipairs(entity.receivers) do if (CAMI.PlayerHasAccess(v, "Helix - Manage Vendors", nil)) then receivers[#receivers + 1] = v end end net.Start("ixVendorEditFinish") net.WriteString(key) net.WriteType(data) net.Send(receivers) end end) net.Receive("ixVendorTrade", function(length, client) if ((client.ixVendorTry or 0) < CurTime()) then client.ixVendorTry = CurTime() + 0.33 else return end local entity = client.ixVendor if (!IsValid(entity) or client:GetPos():Distance(entity:GetPos()) > 192) then return end local uniqueID = net.ReadString() local isSellingToVendor = net.ReadBool() if (entity.items[uniqueID] and hook.Run("CanPlayerTradeWithVendor", client, entity, uniqueID, isSellingToVendor) != false) then local price = entity:GetPrice(uniqueID, isSellingToVendor) if (isSellingToVendor) then local found = false local name if (!entity:HasMoney(price)) then return client:NotifyLocalized("vendorNoMoney") end local stock, max = entity:GetStock(uniqueID) if (stock and stock >= max) then return client:NotifyLocalized("vendorMaxStock") end local invOkay = true for _, v in pairs(client:GetCharacter():GetInventory():GetItems()) do if (v.uniqueID == uniqueID and v:GetID() != 0 and ix.item.instances[v:GetID()] and v:GetData("equip", false) == false) then invOkay = v:Remove() found = true name = L(v.name, client) break end end if (!found) then return end if (!invOkay) then client:GetCharacter():GetInventory():Sync(client, true) return client:NotifyLocalized("tellAdmin", "trd!iid") end if (entity:GetUseCredits()) then local card = client:GetCharacter():GetInventory():HasItem("id_card") card:LoadOwnerGenericData(PLUGIN.GiveTakeCredits, nil, client, entity, price, uniqueID, false) else client:GetCharacter():GiveMoney(price) end client:NotifyLocalized("businessSell", name, !entity:GetUseCredits() and ix.currency.Get(price) or (price .. " Credits")) entity:TakeMoney(price) entity:AddStock(uniqueID) if (ix.saveEnts) then ix.saveEnts:SaveEntity(entity) end PLUGIN:SaveData() hook.Run("CharacterVendorTraded", client, entity, uniqueID, isSellingToVendor) else local stock = entity:GetStock(uniqueID) if (stock and stock < 1) then return client:NotifyLocalized("vendorNoStock") end local name = L(ix.item.list[uniqueID].name, client) if (entity:GetUseCredits()) then local card = client:GetCharacter():GetInventory():HasItem("id_card") card:LoadOwnerGenericData(PLUGIN.GiveTakeCredits, nil, client, entity, price, uniqueID, true) else if (!client:GetCharacter():HasMoney(price)) then return client:NotifyLocalized("canNotAfford") end client:GetCharacter():TakeMoney(price) client:NotifyLocalized("businessPurchase", name, !entity:GetUseCredits() and ix.currency.Get(price) or L("vendorNCredits", price)) entity:GiveMoney(price) if (!client:GetCharacter():GetInventory():Add(uniqueID)) then ix.item.Spawn(uniqueID, client) else net.Start("ixVendorAddItem") net.WriteString(uniqueID) net.Send(client) end entity:TakeStock(uniqueID) end if (ix.saveEnts) then ix.saveEnts:SaveEntity(entity) end PLUGIN:SaveData() hook.Run("CharacterVendorTraded", client, entity, uniqueID, isSellingToVendor) end else client:NotifyLocalized("vendorNoTrade") end end) function PLUGIN.GiveTakeCredits(idCard, _, client, entity, price, itemID, isBuying) if (isBuying) then if (idCard:HasCredits(price)) then local item = ix.item.Get(itemID) local name = item and item.name or "" idCard:TakeCredits(price, entity:GetDisplayName(), "\"" .. name .. "\" vendor purchase.") client:NotifyLocalized("businessPurchase", name, price.." Credits") entity:GiveMoney(price) if (!client:GetCharacter():GetInventory():Add(itemID)) then ix.item.Spawn(itemID, client) else net.Start("ixVendorAddItem") net.WriteString(itemID) net.Send(client) end entity:TakeStock(itemID) else return client:NotifyLocalized("canNotAfford") end else idCard:GiveCredits(price, entity:GetDisplayName(), "\"" .. ix.item.Get(itemID).name .. " " .. entity:GetDisplayName() .. "\" vendor sale.") end end else VENDOR_TEXT = {} VENDOR_TEXT[VENDOR_SELLANDBUY] = "vendorBoth" VENDOR_TEXT[VENDOR_BUYONLY] = "vendorBuy" VENDOR_TEXT[VENDOR_SELLONLY] = "vendorSell" net.Receive("ixVendorOpen", function() local entity = net.ReadEntity() if (!IsValid(entity)) then return end entity.money = net.ReadUInt(16) entity.items = net.ReadTable() entity.scale = net.ReadFloat() local usingCredits = net.ReadBool() local credits = false if (usingCredits) then credits = net.ReadFloat() end ix.gui.vendor = vgui.Create("ixVendor") ix.gui.vendor:SetReadOnly(false) ix.gui.vendor:Populate(credits) ix.gui.vendor:Setup(entity) end) net.Receive("ixVendorEditor", function() local entity = net.ReadEntity() if (!IsValid(entity) or !CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage Vendors", nil)) then return end entity.money = net.ReadUInt(16) entity.items = net.ReadTable() entity.scale = net.ReadFloat() entity.messages = net.ReadTable() entity.factions = net.ReadTable() entity.classes = net.ReadTable() ix.gui.vendor = vgui.Create("ixVendor") ix.gui.vendor:SetReadOnly(true) ix.gui.vendor:Populate() ix.gui.vendor:Setup(entity) ix.gui.vendorEditor = vgui.Create("ixVendorEditor") end) net.Receive("ixVendorEdit", function() local panel = ix.gui.vendor if (!IsValid(panel)) then return end local entity = panel.entity if (!IsValid(entity)) then return end local key = net.ReadString() local data = net.ReadType() if (key == "mode") then entity.items[data[1]] = entity.items[data[1]] or {} entity.items[data[1]][VENDOR_MODE] = data[2] if (!data[2]) then panel:removeItem(data[1]) elseif (data[2] == VENDOR_SELLANDBUY) then panel:addItem(data[1]) else panel:addItem(data[1], data[2] == VENDOR_SELLONLY and "selling" or "buying") panel:removeItem(data[1], data[2] == VENDOR_SELLONLY and "buying" or "selling") end elseif (key == "price") then local uniqueID = data[1] entity.items[uniqueID] = entity.items[uniqueID] or {} entity.items[uniqueID][VENDOR_PRICE] = tonumber(data[2]) elseif (key == "stockDisable") then if (entity.items[data]) then entity.items[data][VENDOR_MAXSTOCK] = nil end elseif (key == "stockMax") then local uniqueID = data[1] local value = data[2] local current = data[3] entity.items[uniqueID] = entity.items[uniqueID] or {} entity.items[uniqueID][VENDOR_MAXSTOCK] = value entity.items[uniqueID][VENDOR_STOCK] = current elseif (key == "stock") then local uniqueID = data[1] local value = data[2] entity.items[uniqueID] = entity.items[uniqueID] or {} if (!entity.items[uniqueID][VENDOR_MAXSTOCK]) then entity.items[uniqueID][VENDOR_MAXSTOCK] = value end entity.items[uniqueID][VENDOR_STOCK] = value elseif (key == "scale") then entity.scale = data end end) net.Receive("ixVendorEditFinish", function() local panel = ix.gui.vendor local editor = ix.gui.vendorEditor if (!IsValid(panel) or !IsValid(editor)) then return end local entity = panel.entity if (!IsValid(entity)) then return end local key = net.ReadString() local data = net.ReadType() if (key == "name") then editor.name:SetText(data) elseif (key == "description") then editor.description:SetText(data) elseif (key == "bubble") then editor.bubble.noSend = true editor.bubble:SetValue(data and 1 or 0) elseif (key == "mode") then if (data[2] == nil) then editor.lines[data[1]]:SetValue(3, L"none") else editor.lines[data[1]]:SetValue(3, L(VENDOR_TEXT[data[2]])) end elseif (key == "price") then editor.lines[data]:SetValue(4, entity:GetPrice(data)) elseif (key == "stockDisable") then editor.lines[data]:SetValue(5, "-") elseif (key == "stockMax" or key == "stock") then local current, max = entity:GetStock(data) editor.lines[data]:SetValue(5, current.."/"..max) elseif (key == "faction") then local uniqueID = data[1] local state = data[2] local editPanel = ix.gui.editorFaction entity.factions[uniqueID] = state if (IsValid(editPanel) and IsValid(editPanel.factions[uniqueID])) then editPanel.factions[uniqueID]:SetChecked(state == true) end elseif (key == "class") then local uniqueID = data[1] local state = data[2] local editPanel = ix.gui.editorFaction entity.classes[uniqueID] = state if (IsValid(editPanel) and IsValid(editPanel.classes[uniqueID])) then editPanel.classes[uniqueID]:SetChecked(state == true) end elseif (key == "model") then editor.model:SetText(entity:GetModel()) elseif (key == "scale") then editor.sellScale.noSend = true editor.sellScale:SetValue(data) end surface.PlaySound("buttons/button14.wav") end) net.Receive("ixVendorMoney", function() local panel = ix.gui.vendor if (!IsValid(panel)) then return end local entity = panel.entity if (!IsValid(entity)) then return end local value = net.ReadUInt(16) value = value != -1 and value or nil entity.money = value local editor = ix.gui.vendorEditor if (IsValid(editor)) then local useMoney = tonumber(value) != nil editor.money:SetDisabled(!useMoney) editor.money:SetEnabled(useMoney) editor.money:SetText(useMoney and value or "∞") end end) net.Receive("ixVendorStock", function() local panel = ix.gui.vendor if (!IsValid(panel)) then return end local entity = panel.entity if (!IsValid(entity)) then return end local uniqueID = net.ReadString() local amount = net.ReadUInt(16) entity.items[uniqueID] = entity.items[uniqueID] or {} entity.items[uniqueID][VENDOR_STOCK] = amount local editor = ix.gui.vendorEditor if (IsValid(editor)) then local _, max = entity:GetStock(uniqueID) editor.lines[uniqueID]:SetValue(4, amount .. "/" .. max) end end) net.Receive("ixVendorAddItem", function() local uniqueID = net.ReadString() if (IsValid(ix.gui.vendor)) then ix.gui.vendor:addItem(uniqueID, "buying") end end) end properties.Add("vendor_edit", { MenuLabel = "Modifier Vendeur", Order = 999, MenuIcon = "icon16/user_edit.png", Filter = function(self, entity, client) if (!IsValid(entity)) then return false end if (entity:GetClass() != "ix_vendor") then return false end if (!gamemode.Call( "CanProperty", client, "vendor_edit", entity)) then return false end return CAMI.PlayerHasAccess(client, "Helix - Manage Vendors", nil) end, Action = function(self, entity) self:MsgStart() net.WriteEntity(entity) self:MsgEnd() end, Receive = function(self, length, client) local entity = net.ReadEntity() if (!IsValid(entity)) then return end if (!self:Filter(entity, client)) then return end entity.receivers[#entity.receivers + 1] = client local itemsTable = {} for k, v in pairs(entity.items) do if (!table.IsEmpty(v)) then itemsTable[k] = v end end client.ixVendor = entity net.Start("ixVendorEditor") net.WriteEntity(entity) net.WriteUInt(entity.money or 0, 16) net.WriteTable(itemsTable) net.WriteFloat(entity.scale or 1) net.WriteTable(entity.messages) net.WriteTable(entity.factions) net.WriteTable(entity.classes) net.Send(client) end })