--[[ | 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 = "ArcCW Base"; PLUGIN.description = "Adds compatibility with ArcCW, integrating the weapons and attachments with helix items. Also links ArcCW SWEPs spread, recoil and sway to characters \"gun\" skill."; PLUGIN.author = "Gr4Ss & LegAz"; if (!ArcCW) then return end PLUGIN.attachmentTranslate = {} for k, v in pairs(ArcCW.AttachmentTable) do local ITEM = ix.item.Register(k, nil, false, nil, true) ITEM.name = v.PrintName ITEM.category = "Attachment (ArcCW)" ITEM.model = "models/props_lab/box01a.mdl" ITEM.description = "A box containing a "..v.PrintName.." attachment." end ix.util.Include("sh_config.lua") -- moving config into another plugin just for visuals @offlegaz ix.util.Include("sv_plugin.lua") ix.util.Include("cl_plugin.lua") ix.config.Add("forceUseMagazineSystem", true, "Should magazines be mandatory for item weapons, or is it OK to fall back to the Source ammo pool?", nil, { category = "general", }) if (CLIENT) then function ArcCW:PlayerTakeAtt(ply, att, amt) end function ArcCW:PlayerGiveAtt(ply, att, amt) end end ix.config.Add("shootDbLevelReduction", 0, "How many DB to reduce the ARCCW sound levels by (this effects how far away it can be heard).", nil, { data = {min = 1, max = 50, decimals = 1}, category = "general" }) ix.config.Add("shootOtherDbLevelReduction", 50, "How many DB to reduce the ARCCW sound levels by (non gunshot sound effects).", nil, { data = {min = 1, max = 100, decimals = 1}, category = "general" }) ix.config.Add("shootOtherVolumeLevelReduction", 0.5, "How much to reduce the ARCCW sound levels by (non gunshot sound effects).", nil, { data = {min = 0, max = 1, decimals = 3}, category = "general" }) ix.config.Add("shootDbLevelReductionClose", 50, "How many DB to reduce the ARCCW sound levels by (this effects how far away close sounds can be heard).", nil, { data = {min = 1, max = 100, decimals = 1}, category = "general" }) ix.config.Add("shootVolumeReduction", 0, "How much to reduce the ARCCW sound levels by (this effects how far loud it is).", nil, { data = {min = 0, max = 1, decimals = 3}, category = "general" }) ix.config.Add("shootSoundDSP", 2, "Which DSP preset to use for close sounds (don't change this unless you know what you're doing!)", nil, { data = {min = 0, max = 140, decimals = 0}, category = "general" }) ix.config.Add("shootSoundDSPDistant", 31, "Which DSP preset to use for distant sounds (don't change this unless you know what you're doing!)", nil, { data = {min = 0, max = 140, decimals = 0}, category = "general" }) ix.config.Add("burnTime", 3, "How long someone should be ignited for after they've been lit up by an incendiary grenade", nil, { data = {min = 0, max = 60, decimals = 1}, category = "general" }) --[[ sound overrides for ArcCW ]] local sndTbl = {} -- a table which we can use to queue up sounds asynchronously local function networkAllPendingArcSounds() while true do for i, snd in ipairs(sndTbl) do if (!snd.filter) then snd.ent:EmitSound(snd.fsound, snd.lvl, 100, snd.vol, CHAN_STATIC, SND_NOFLAGS, snd.useDSP and snd.dsp or 0) else local _snd = CreateSound(snd.ent, snd.fsound, snd.filter) _snd:SetSoundLevel(snd.lvl) if (snd.useDSP) then _snd:SetDSP(snd.dsp or 0) end _snd:PlayEx(snd.vol, 255) end table.remove(sndTbl, i) end coroutine.yield() end end local function insertArcSound(ent, fsound, filter, lvl, vol, dsp, useDSP) sndTbl[#sndTbl + 1] = { ent = ent, fsound = fsound, filter = filter, lvl = lvl, vol = vol, dsp = dsp, useDSP = useDSP } end local sndCO = coroutine.create(networkAllPendingArcSounds) if (SERVER) then timer.Create("ixNetworkArcCWSounds", 0.1, 0, function() -- gun sounds are kind of low-priority. improves S2K performance if we don't block the main thread with them if (!sndCO or !coroutine.resume(sndCO)) then -- there is something wrong with the table.Add ErrorNoHalt("ArcCW sound coroutine failed to resume. Attempting to recreate coroutine.\n") sndTbl = {} sndCO = coroutine.create(networkAllPendingArcSounds) end end) end local function isShootSound(ent, fsound) if (fsound == ent.ShootSound or fsound == ent.ShootSoundSilenced or fsound == ent.DistantShootSound) then return true end return false end timer.Simple(1, function() local tbl = weapons.GetStored("arccw_base") -- baller way of shoving our own shit code into arccw -- ^^ make sure we're the last to load so the override actually works function tbl:MyEmitSound(fsound, level, pitch, _vol, chan, useWorld) if !fsound then return end fsound = self:GetBuff_Hook("Hook_TranslateSound", fsound) or fsound local client = self:GetOwner() local weapon = client:GetActiveWeapon() local useDSP = isShootSound(weapon, fsound) -- don't play DSP on reloading and equipping sfx if istable(fsound) then fsound = self:TableRandom(fsound) end if fsound and fsound != "" then local lvl = math.Clamp(level or 150 - ix.config.Get("shootDbLevelReductionClose", 10), 60, 160) local vol = _vol or 1 - ix.config.Get("shootVolumeReduction", 0.1) local dsp = ix.config.Get("shootSoundDSP", 2) local lvl2 = math.Clamp(level or 150 - ix.config.Get("shootDbLevelReduction", 60), 60, 160) local vol2 = math.Clamp(_vol or 1 - ix.config.Get("shootVolumeReduction", 0.1) - 0.1, 0, 1) local dsp2 = ix.config.Get("shootSoundDSPDistant", 31) if (!useDSP) then lvl = math.Clamp(lvl - ix.config.Get("shootOtherDbLevelReduction", 50), 60, 160) vol = vol - ix.config.Get("shootOtherVolumeLevelReduction", 0.1) end if (CLIENT and client == LocalPlayer()) then -- client plays their own sound -- nevermind, we can't do this because i can't use CRecipientFilters to filter out the client serverside -- nevermind, apparently they don't duplicate any more ????????????? -- TODO: reimplement when that feature makes it to main gmod branch weapon:EmitSound(fsound, level, pitch, vol, chan or CHAN_STATIC) elseif (client and SERVER) then -- filter for distant sounding gunshots if (useDSP) then local filter = RecipientFilter() filter:AddAllPlayers() filter:RemovePlayer(client) filter:RemovePVS(client:GetPos()) insertArcSound(weapon, fsound, filter, lvl2, vol2, dsp2, true) end weapon:EmitSound(fsound, lvl, 100, vol, CHAN_STATIC, SND_NOFLAGS, dsp) end end end end) function ArcCW:PlayerGetAtts(client, attachment) if (!IsValid(client)) then return 0 end if (GetConVar("arccw_attinv_free"):GetBool()) then return 999 end if (attachment == "") then return 999 end local attachmentData = ArcCW.AttachmentTable[attachment] if (!attachmentData) then return 0 end if (attachmentData.Free) then return 999 end local character = client:GetCharacter() if (!character) then return 0 end if (!client:IsAdmin() and attachmentData.AdminOnly) then return 0 end if (attachmentData.InvAtt) then attachment = attachmentData.InvAtt end local weapon = client:GetActiveWeapon() if (SERVER and client.ixAttachAttempt and client.ixAttachAttempt.attachment == attachment and client.ixAttachAttempt.time + 1 > CurTime()) then weapon = client.ixAttachAttempt.weapon end local amount = character:GetInventory():GetItemCount(PLUGIN.attachmentTranslate[attachment] or attachment) for _, v in ipairs(weapon:GetNetVar("ixItemDefaultWeaponAtts", {})) do if (v == attachment) then amount = amount + 1 end end if (SERVER and weapon.ixItem) then for _, v in pairs(weapon.ixItem:GetData("WeaponAttachments", {})) do if (v.attachment == attachment) then amount = amount + 1 break end end elseif (CLIENT and weapon:GetNetVar("ixItemID")) then local item = ix.item.instances[weapon:GetNetVar("ixItemID")] if (item) then for _, v in pairs(item:GetData("WeaponAttachments", {})) do if (v.attachment == attachment) then amount = amount + 1 break end end end end return amount end function PLUGIN:ArcCW_PlayerCanAttach(client, weapon, attachment, slot, detach) client.ixAttachAttempt = nil client.ixDeattachAttempt = nil if (detach) then client.ixDeattachAttempt = { weapon = weapon, attachment = attachment, slot = slot } return end if (attachment == "") then return end local weapon2 = client.ixAttachWeapon or client:GetActiveWeapon() if (weapon2 != weapon) then return end client.ixAttachAttempt = { weapon = weapon, attachment = attachment, slot = slot, time = CurTime() } for _, v in ipairs(weapon:GetNetVar("ixItemDefaultWeaponAtts", {})) do if (v == attachment) then client.ixAttachAttempt.mode = "default" return true end end if (weapon.ixItem) then for _, v in pairs(weapon.ixItem:GetData("WeaponAttachments", {})) do if (v.attachment == attachment) then client.ixAttachAttempt.mode = "reattach" return true end end end local character = client:GetCharacter() if (character) then local inventory = character:GetInventory() if (self.attachmentTranslate[attachment]) then if (!inventory:HasItem(self.attachmentTranslate[attachment])) then return false end elseif (!inventory:HasItem(attachment)) then return false end end client.ixAttachAttempt.mode = "item" return true end -- Give one ammo otherwise ArcCW doesn't let us reload function PLUGIN:KeyPress(client, key) if (key == IN_RELOAD) then local weapon = client:GetActiveWeapon() if (IsValid(weapon) and weapon:GetNetVar("ixItemID") and client:GetAmmoCount(weapon:GetPrimaryAmmoType()) == 0) then if (client.ixPreReloadAmmoGiven) then -- We still have 1 ammo from some previous attempt, lets remove this first! client:SetAmmo(math.max(client:GetAmmoCount(client.ixPreReloadAmmoGiven) - 1, 0), client.ixPreReloadAmmoGiven) end client:SetAmmo(1, weapon:GetPrimaryAmmoType()) client.ixPreReloadAmmoGiven = weapon:GetPrimaryAmmoType() end end end -- Take the ammo again if it wasn't used function PLUGIN:KeyRelease(client, key) if (client.ixPreReloadAmmoGiven) then client:SetAmmo(client:GetAmmoCount(client.ixPreReloadAmmoGiven) - 1, client.ixPreReloadAmmoGiven) client.ixPreReloadAmmoGiven = nil end end function PLUGIN:PlayerSwitchWeapon(client, oldWeapon, newWeapon) if (client.ixPreReloadAmmoGiven) then client:SetAmmo(client:GetAmmoCount(client.ixPreReloadAmmoGiven) - 1, client.ixPreReloadAmmoGiven) client.ixPreReloadAmmoGiven = nil end end local sortFunc = function(a, b) local aAmmo, bAmmo = a:GetAmmo(), b:GetAmmo() if (aAmmo != bAmmo) then return aAmmo > bAmmo else return a:GetID() > b:GetID() end end function PLUGIN:Hook_PreReload(weapon) local client = weapon:GetOwner() if (!IsValid(client) or client:IsNPC()) then return end -- Take the 1 ammo given to let us get past the 'not zero ammo' check that happens before this hook if (client.ixPreReloadAmmoGiven) then client:SetAmmo(client:GetAmmoCount(client.ixPreReloadAmmoGiven) - 1, client.ixPreReloadAmmoGiven) client.ixPreReloadAmmoGiven = nil end local character = client:GetCharacter() if (!character) then return end if (weapon:GetNetVar("ixItemID") == nil) then return end local itemID = weapon:GetNetVar("ixItemID") local item = ix.item.instances[itemID] if (!item) then return end local ammoID = weapon:GetPrimaryAmmoType() local ammoName = string.lower(game.GetAmmoName(ammoID) or "") if (ammoName == "") then return end local ammoItems = {} for k, v in ipairs(character:GetInventory():GetItemsByBase("base_arccwmag")) do if (item.magazines and item.magazines[v.uniqueID] and v:GetAmmo() > 0) then ammoItems[#ammoItems + 1] = v end end table.sort(ammoItems, sortFunc) local ammoItem = ammoItems[1] if (!ammoItem) then return ix.config.Get("forceUseMagazineSystem") end local chamber = math.Clamp(weapon:Clip1(), 0, weapon:GetChamberSize()) if (SERVER) then if (weapon.ixItem:GetData("reloadMagazineItem")) then self:RefundAmmoItem(weapon, character, weapon.ixItem:GetData("reloadMagazineItem"), weapon:Clip1() + client:GetAmmoCount(ammoID) - chamber, weapon.ixItem:GetData("reloadMagazineInvPos")) weapon:SetClip1(chamber) end weapon.ixItem:SetData("reloadMagazineItem", ammoItem:GetID()) weapon.ixItem:SetData("reloadMagazineInvPos", {ammoItem.invID, ammoItem.gridX, ammoItem.gridY}) ammoItem:Transfer(nil, nil, nil, client, nil, true) end client:SetAmmo(ammoItem:GetAmmo(), ammoID) end function PLUGIN:Hook_ModDispersion(weapon, hip) local weaponOwner = weapon:GetOwner() if (weaponOwner:IsPlayer()) then weaponOwner = weaponOwner:GetCharacter() else return end local skillLevel = weaponOwner:GetSkillLevel("guns") local minSkillLevel, maxSkillLevel = ix.weapons:GetWeaponSkillRequired(weapon:GetClass()) if (skillLevel < minSkillLevel) then return hip + math.ceil(hip * self.spreadMaxIncrease) * (1 - skillLevel / minSkillLevel) end skillLevel = math.min(skillLevel, maxSkillLevel) return hip - math.floor(hip * self.spreadMaxDecrease) * ((skillLevel - minSkillLevel) / (maxSkillLevel - minSkillLevel)) end function PLUGIN:Hook_ModifyRecoil(weapon, rec) local weaponOwner = weapon:GetOwner() if (weaponOwner:IsPlayer()) then weaponOwner = weaponOwner:GetCharacter() else return end local skillLevel = weaponOwner:GetSkillLevel("guns") local minSkillLevel, maxSkillLevel = ix.weapons:GetWeaponSkillRequired(weapon:GetClass()) local modifier if (skillLevel < minSkillLevel) then modifier = PLUGIN.recoilMaxIncrease * (1 - skillLevel / minSkillLevel) else skillLevel = math.min(skillLevel, maxSkillLevel) modifier = -(PLUGIN.recoilMaxDecrease * ((skillLevel - minSkillLevel) / (maxSkillLevel - minSkillLevel))) end rec.Recoil = rec.Recoil + (rec.Recoil * modifier) rec.RecoilSide = rec.RecoilSide + (rec.RecoilSide * modifier) -- rec.VisualRecoilMul = rec.VisualRecoilMul + (rec.VisualRecoilMul * modifier) return rec end function PLUGIN:O_Hook_Override_CanBash() return false end