-- Headcrab canister SWEP -- Version 2 -- (c) phenex _OpenScript( "includes/defines.lua" ) _OpenScript( "includes/vector3.lua" ) _OpenScript( "includes/misc.lua" ) MyIndex = 0 -- Weapon's entity index. Owner = 0 -- The player that owns this weapon CurrentTime = 0 -- The current game time HeadCrabType = 0 -- Called when the weapon is created. function onInit( ) --_Msg( "runstring-declares\n" ) _RunString( [[ math.randomseed( _CurTime( ) ) -- remove the sky_camera. if _GetCurrentMap( ) == "gm_construct" and not sky_camera_removed then local old_sv_cheats = _GetConVar_Float( "sv_cheats" ) _ServerCommand( "sv_cheats 1\n" ) _ServerCommand( "ent_remove sky_camera\n" ) _ServerCommand( "sv_cheats " .. tostring( old_sv_cheats ) .. "\n" ) _Msg( " * weapon_canister: removed sky_camera. Thank you, bassa12.\n" ) sky_camera_removed = true end TRACELINE_DIDHITSKY_TRUE = 4 TRACELINE_DIDHITSKY_FALSE = 0 px = px or { } px.canister = px.canister or { } px.canister.userlist = px.canister.userlist or { } px.canister.entlist = px.canister.entlist or { } ------------------------------------------------------------------------------------------------ ----------- Headcrab Canister Property Definitions ------------------------------------------------------------------------------------------------ HEADCRABTYPE_NONE = -1 HEADCRABTYPE_NORMAL = 0 HEADCRABTYPE_FAST = 1 HEADCRABTYPE_POISON = 2 OPENDELAY_DONTOPEN = 0 OPENDELAY_SHORT = 1 OPENDELAY_NORMAL = 3 OPENDELAY_LONG = 5 SPAWNDELAY_NONE = 0 SPAWNDELAY_SHORT = 0.75 SPAWNDELAY_NORMAL = 1.5 SPAWNDELAY_LONG = 3 FLIGHTTIME_NOWARNING = 1 FLIGHTTIME_SHORT = 3 FLIGHTTIME_NORMAL = 5 FLIGHTTIME_LONG = 8 FLIGHTSPEED_GEOTRACKER = 1000 FLIGHTSPEED_SLOW = 2000 FLIGHTSPEED_NORMAL = 3000 FLIGHTSPEED_FAST = 4500 FLIGHTSPEED_REALLYFAST = 7500 -- This flightspeed looks nice for some reason. -- NORMAL for the impact damage and radius means the defaults from the original headcrab canister gun, not the Hammer default values. IMPACTDAMAGE_LIGHT = 75 IMPACTDAMAGE_NORMAL = 150 IMPACTDAMAGE_HEAVY = 225 IMPACTDAMAGE_MASSIVE = 350 IMPACTRADIUS_SMALL = 250 IMPACTRADIUS_NORMAL = 450 IMPACTRADIUS_LARGE = 650 IMPACTRADIUS_HUGE = 850 -- If you set the removedelay to REMOVEDELAY_PERSIST, the canisters won't automatically be removed... use with caution on busy servers. REMOVEDELAY_PERSIST = -1 ------------------------------------------------------------------------------------------------ ----------- Headcrab Canister Type List ------------------------------------------------------------------------------------------------ px.canister.canistertypes = px.canistertypes or { [1] = { name = "Normal Headcrabs", headcrabtype = HEADCRABTYPE_NORMAL, headcrabcount = 7, opendelay = OPENDELAY_NORMAL, spawndelay = SPAWNDELAY_NORMAL, removedelay = 16, flighttime = FLIGHTTIME_NORMAL, flightspeed = FLIGHTSPEED_REALLYFAST, impactdamage = IMPACTDAMAGE_HEAVY, impactradius = IMPACTRADIUS_NORMAL, acl = "AllowEveryone", }, [2] = { name = "Fast Headcrabs", headcrabtype = HEADCRABTYPE_FAST, headcrabcount = 6, opendelay = OPENDELAY_NORMAL, spawndelay = SPAWNDELAY_NORMAL, removedelay = 14, flighttime = FLIGHTTIME_NORMAL, flightspeed = FLIGHTSPEED_REALLYFAST, impactdamage = IMPACTDAMAGE_NORMAL, impactradius = IMPACTRADIUS_NORMAL, acl = "AllowEveryone", }, [3] = { name = "Poison Headcrabs", headcrabtype = HEADCRABTYPE_POISON, headcrabcount = 4, opendelay = OPENDELAY_SHORT, spawndelay = SPAWNDELAY_NORMAL, removedelay = 8, flighttime = FLIGHTTIME_LONG, flightspeed = FLIGHTSPEED_NORMAL, impactdamage = IMPACTDAMAGE_NORMAL, impactradius = IMPACTRADIUS_NORMAL, acl = "AllowEveryone", }, [4] = { name = "Empty Canisters", headcrabtype = HEADCRABTYPE_NONE, opendelay = OPENDELAY_DONTOPEN, removedelay = 8, flighttime = FLIGHTTIME_NORMAL, flightspeed = 2 * FLIGHTSPEED_FAST, impactdamage = IMPACTDAMAGE_MASSIVE, impactradius = IMPACTRADIUS_HUGE, acl = "AllowEveryone", }, } ]] ) --_Msg( "runstring-simpleacl\n" ) _RunString( [[ -- Check if SimpleACL is loaded. if simpleacl ~= nil and type( simpleacl ) == "table" and simpleacl.VERSIONSTRING ~= nil then _Msg( "* weapon_canister: SimpleACl found, initializing acls...\n" ) local errmsg, foundfile = "", nil if px.canister.aclobj == nil or px.canister.aclobj.type ~= "simpleacl" then -- Find the weapon_canister.cfg file. if _file.Exists( "lua/weapon_canister.cfg" ) then foundfile = "lua/weapon_canister.cfg" elseif _file.Exists( "lua/weapons/weapon_canister.cfg" ) then foundfile = "lua/weapons/weapon_canister.cfg" end if foundfile ~= nil then px.canister.aclobj, errmsg = simpleacl.newFromConfigFile( "px.canister.aclobj", foundfile ) end end if px.canister.aclobj == nil then if errmsg ~= "" then errmsg = "Error parsing weapon_canister.cfg: " .. errmsg elseif foundfile == nil then errmsg = "Could not locate weapon_canister.cfg for use with SimpleACL. Make sure you have extracted the downloaded ZIP file properly." end _Msg( "weapon_canister: While attempting to initialize SimpleACL access control, an error was encountered: " .. errmsg .. "\n" ) _Msg( "weapon_canister: Access control with SimpleACL has been disabled even though SimpleACL is installed properly.\n" ) px.canister.useacl = false else _Msg( "weapon_canister: Access control with SimpleACL has been enabled.\n" ) px.canister.useacl = true _Msg( "weapon_canister: Access control has been disabled due to a bug I still have to fix.\n" ) px.canister.useacl = false end else px.canister.useacl = false end ]] ) --_Msg( "runstring-canplayerfire\n" ) _RunString( [[ px.canister.canPlayerFire = px.canister.canPlayerFire or function( playerid ) if px.canister.userlist[playerid].cansFired < 3 then return true else return false end end ]] ) --_Msg( "runstring-events\n" ) _RunString( [[ if px.canister.onthink_id == nil then px.canister.onthink = function( ) for k,v in px.canister.entlist do if v ~= nil then if _EntExists( k ) and _EntGetType( k ) == "env_headcrabcanister" then if v.time < _CurTime( ) then if v.action == "open" then _EntFire( k, "OpenCanister", "", 0 ) if px.canister.canistertypes[v.canisterType].headcrabcount > 0 and px.canister.canistertypes[v.canisterType].headcrabtype >= 0 then v.action = "spawn" v.time = _CurTime( ) + px.canister.canistertypes[v.canisterType].spawndelay else v.action = "remove" v.time = _CurTime( ) + px.canister.canistertypes[v.canisterType].removedelay end elseif v.action == "spawn" then _EntFire( k, "SpawnHeadcrabs", "", 0 ) v.action = "remove" -- Recommend a fixed time delay of 2 seconds plus 2 seconds for each headcrab in the canister. v.time = _CurTime( ) + px.canister.canistertypes[v.canisterType].removedelay elseif v.action == "remove" then if px.canister.canistertypes[v.canisterType].removedelay ~= REMOVEDELAY_PERSIST then _EntRemove( k ) end local playerid = px.canister.entlist[k].playerid px.canister.userlist[playerid].cansFired = math.max( 0, px.canister.userlist[playerid].cansFired - 1 ) px.canister.entlist[k] = nil table.remove( px.canister.entlist, k ) end end else local playerid = px.canister.entlist[k].playerid px.canister.userlist[playerid].cansFired = math.max( 0, px.canister.userlist[playerid].cansFired - 1 ) px.canister.entlist[k] = nil table.remove( px.canister.entlist, k ) end end end end px.canister.onthink_id = table.getn( gLuaThinkFunctions ) + 1 AddThinkFunction( px.canister.onthink ) end if px.canister.event_propbreak == nil then px.canister.event_propbreak = function( breakerid, propid ) if px.canister.entlist[propid] ~= nil then local playerid = px.canister.entlist[propid].playerid px.canister.userlist[playerid].cansFired = math.max( 0, px.canister.userlist[playerid].cansFired - 1 ) px.canister.entlist[propid] = nil table.remove( px.canister.entlist, propid ) end end HookEvent( "eventPropBreak", px.canister.event_propbreak ) end ]] ) --_Msg( "runstring-spawn\n" ) _RunString( [[ px.canister.spawn = px.canister.spawn or function( playerid ) if px.canister.canPlayerFire( playerid ) == false then return nil end _TraceLine( _PlayerGetShootPos( playerid ), _PlayerGetShootAng( playerid ), 16384, playerid ) local hitpos = _TraceEndPos( ) local angvec for i=1,10 do angvec = vecNormalize( vector3( math.random( -5, 5 ), math.random( -5, 5 ), 20 ) ) _TraceLine( hitpos, angvec, 32768 ) if _TraceDidHitSky( ) == TRACELINE_DIDHITSKY_TRUE then break end end if _TraceDidHitSky( ) == TRACELINE_DIDHITSKY_FALSE then angvec = vector3( 0, 0, 1 ) _TraceLine( hitpos, angvec, 32768 ) if _TraceDidHitSky( ) == TRACELINE_DIDHITSKY_FALSE then _PrintMessage( playerid, 3, " * weapon_canister: Couldn't find a suitable angle to launch. Try getting \"more\" outdoors.\n" ) return nil end end local can = px.canister.canistertypes[px.canister.userlist[playerid].canisterType] local spawnpos = _TraceEndPos( ) local spawnheight = spawnpos.z - hitpos.z + 266 local ent = _EntCreate( "env_headcrabcanister" ) _EntSetOwner( ent, playerid ) _EntSetPos( ent, hitpos ) _EntSetAng( ent, angvec ) _EntSetKeyValue( ent, "FlightSpeed", tostring( can.flightspeed ) ) _EntSetKeyValue( ent, "FlightTime", tostring( can.flighttime ) ) _EntSetKeyValue( ent, "StartingHeight", spawnheight ) _EntSetKeyValue( ent, "SkyboxcanisterCount", 1 ) _EntSetKeyValue( ent, "spawnflags", 8192 + 16384 + 32768 ) _EntSetKeyValue( ent, "HeadcrabType", tostring( can.headcrabtype ) ) _EntSetKeyValue( ent, "HeadcrabCount", tostring( can.headcrabcount ) ) _EntSetKeyValue( ent, "Damage", tostring( can.impactdamage ) ) _EntSetKeyValue( ent, "DamageRadius", tostring( can.impactradius ) ) _EntSpawn( ent ) _EntFire( ent, "FireCanister", "", 0 ) px.canister.entlist[ent] = { ["playerid"] = playerid, canisterType = px.canister.userlist[playerid].canisterType, time = _CurTime( ) + can.flighttime + can.opendelay, action = "open" } if can.opendelay == OPENDELAY_DONTOPEN then -- skip to the remove wait state. -- while it is possible to also not remove the canister, this check is done in the think code. px.canister.entlist[ent].time = px.canister.entlist[ent].time + can.removedelay px.canister.entlist[ent].action = "remove" end px.canister.userlist[playerid].cansFired = px.canister.userlist[playerid].cansFired + 1 end ]] ) --_Msg( "runstring-findfirstcanistertype\n" ) _RunString( [[ px.canister.findfirstcanistertype = px.canister.findfirstcanistertype or function( playerid ) if not px.canister.useacl then return nil end local i = 1 local found = false while px.canister.canistertypes[i] ~= nil and not found do local acl = px.canister.canistertypes[i].acl if acl ~= nil and acl ~= "" and px.canister.aclobj.acls[acl] ~= nil then found = px.canister.checkACL( playerid, acl ) else found = true end end if found then px.canister.userlist[playerid].canisterType = i _PrintMessage( playerid, 3, " * weapon_canister: \"" .. px.canister.canistertypes[i].name .. "\" selected. Press secondary fire to cycle the available canister types.\n" ) else -- Shit, sorry bro, you can't use this weapon. _PlayerRemoveWeapon( playerid, "weapon_canister" ) _PrintMessage( playerid, 3, " * weapon_canister: No canister types were found that you are allowed to use, don't bother picking the SWEP up again.\n" ) end end ]] ) --_Msg( "runstring-incrementcanistertype\n" ) _RunString( [[ px.canister.incrementcanistertype = px.canister.incrementcanistertype or function( playerid ) local startType = px.canister.userlist[playerid].canisterType local i = startType local found = false local checksleft = 100 repeat i = i + 1 checksleft = checksleft - 1 if px.canister.canistertypes[i] == nil then i = 1 end local acl = px.canister.canistertypes[i].acl if px.canister.useacl and acl ~= nil and acl ~= "" and px.canister.aclobj.acls[acl] ~= nil then found = px.canister.checkACL( playerid, acl ) found = true else found = true end until found or i == startType or checksLeft < 1 _PrintMessage( playerid, 3, " * weapon_canister: \"" .. px.canister.canistertypes[i].name .. "\" selected.\n" ) px.canister.userlist[playerid].canisterType = i end ]] ) --_Msg( "runstring-checkacl\n" ) _RunString( [[ px.canister.checkACL = px.canister.checkACL or function( playerid, acl ) if not px.canister.useacl then return true end _Msg( "checkACL: playerid: " .. playerid .. "\n" ) _Msg( "checkACL: steamid: " .. px.canister.userlist[playerid].steamid .. "\n" ) return px.canister.aclobj:query( acl, px.canister.userlist[playerid].steamid ) or px.canister.aclobj:query( acl, "PlayerID:" .. playerid ) end ]] ) --_Msg( "post-runstring\n" ) end -- Called when player picks up weapon function onPickup( playerid ) --_Msg( "onPickup. playerid = " .. playerid .. " Owner = " .. Owner .. "\n" ) _RunString( [[ if px.canister.userlist[]] .. tostring( playerid ) .. [[] == nil then px.canister.userlist[]] .. tostring( playerid ) .. [[] = { cansFired = 0, canisterType = 1, steamid = _PlayerInfo( ]] .. tostring( playerid ) .. [[, "networkid" ), } end px.canister.findfirstcanistertype( ]] .. tostring( playerid ) .. [[ ) ]] ) end -- When the player presses left mouse button function onPrimaryAttack( ) if _PlayerInfo( Owner, "alive" ) == false then return nil end _RunString( string.format( "px.canister.spawn( %d )", Owner ) ) end function onSecondaryAttack( ) _RunString( string.format( "px.canister.incrementcanistertype( %d )", Owner ) ) end -- When player presses reload. Returning false means DON'T RELOAD. Although this will hitch on the client. function onReload( ) return true end -- Primary Attack Settings function getDamage() return 0 end function getPrimaryShotDelay() return 0.2 end function getPrimaryIsAutomatic() return false end function getBulletSpread() return vector3( 0, 0, 0 ) end function getViewKick() return vector3( 0, 0, 0 ) end function getViewKickRandom() return vector3( 0, 0, 0 ) end function getNumShotsPrimary() return 0 end function getPrimaryAmmoType() return "pistol" end function getMaxClipPrimary() -- return -1 if it doesn't use clips return -1 end function getDefClipPrimary() -- ammo in gun by default return 0 end -- 0 = none, 1 = every bullet, 2 = every 2nd bullet etc function getTracerFreqPrimary() return 1 end -- Secondary attack Settings function getDamageSecondary() return 0 end function getSecondaryShotDelay() return 0 end function getSecondaryIsAutomatic() return false; end function getBulletSpreadSecondary() return vector3( 0, 0, 0 ) end function getViewKickSecondary() return vector3( 0, 0, 0) end function getViewKickRandomSecondary() return vector3( 0, 0, 0 ) end function getNumShotsSecondary() return 0 end function getSecondaryAmmoType() return "pistol" end function getMaxClipSecondary() -- return -1 if it doesn't use clips return -1 end function getDefClipSecondary() return 0 end function getTracerFreqSecondary() return 0 end -- Weapon Configuration function getViewModel( ) return "models/weapons/v_smg_ump45.mdl" end function getWorldModel( ) return "models/weapons/w_smg_ump45.mdl" end function getClassName() return "weapon_canister" end function getHUDMaterial( ) return "gmod/SWEP/default" end function getDeathIcon( ) return "swep_default" end function getWeaponSwapHands() return false end function getWeaponFOV() return 70 end function getWeaponSlot() return 5 end function getWeaponSlotPos() return 2 end function getFiresUnderwater() return true end function getReloadsSingly() return false end -- How the player model holds the weapon: -- pistol, smg, ar2, shotgun, rpg, phys, crossbow, melee, slam, grenade function getAnimPrefix() return "shotgun" end function getPrintName() return "Headcrab Canister Gun" end -- 0 = Don't override, shoot bullets, make sound and flash -- 1 = Don't shoot bullets but do make flash/sounds -- 2 = Only play animations -- 3 = Don't do anything function getPrimaryScriptOverride() return 1 end function getSecondaryScriptOverride() return 3 end