Counter-Strike: Source/Scripting/VScript Examples: Difference between revisions
Jump to navigation
Jump to search

No edit summary |
No edit summary |
||
Line 7: | Line 7: | ||
== Lag compensating any entity == | == Lag compensating any entity == | ||
CS:S only lag compensates players. It may be useful to also compensate other entities, and this can be approximated by reading the player's current ping. | |||
Note that this is not perfect compared to the game's built-in lag compensation, but it's significantly better for players on high ping! | Note that this is not perfect compared to the game's built-in lag compensation, but it's significantly better for players on high ping! | ||
Line 175: | Line 175: | ||
<source lang=js> | <source lang=js> | ||
local | local EventsID = UniqueString() | ||
getroottable()[ | getroottable()[EventsID] <- | ||
{ | { | ||
function OnGameEvent_player_say( d ) | function OnGameEvent_player_say( d ) | ||
Line 210: | Line 210: | ||
} | } | ||
} | } | ||
local | local Instance = self | ||
foreach( | local EventsTable = getroottable()[EventsID] | ||
__CollectGameEventCallbacks( | foreach (name, callback in EventsTable) | ||
{ | |||
local callback_binded = callback.bindenv(this) | |||
EventsTable[name] = @(params) Instance.IsValid() ? callback_binded(params) : delete getroottable()[EventsID] | |||
} | |||
__CollectGameEventCallbacks(EventsTable) | |||
function SetColor( e, r, g, b ) | function SetColor( e, r, g, b ) |
Revision as of 10:11, 22 February 2025


This page contains examples of vscripts specifically for Counter-Strike: Source.
Generic examples that work on all VScript games can be found on the SDK 2013 page.
Lag compensating any entity
CS:S only lag compensates players. It may be useful to also compensate other entities, and this can be approximated by reading the player's current ping. Note that this is not perfect compared to the game's built-in lag compensation, but it's significantly better for players on high ping!
This example demonstrates how to lag compensate a custom moving prop_dynamic prop. At the high level:
- The prop itself is made non-solid so the bullets don't hit, as the logic will be replaced by our own
- The prop will add its current position to a list every tick, into a so-called "lag record"
- The
bullet_impact
game event is listened for, to detect when a fired bullet hits anything (a bullet should always hit something) - On impact, the prop's position is backtracked depending on the player's ping
- An intersection test is done along the bullet path against the historical prop position to see if it hit
This also a useful example to see how to create moving entities, do collision tests, etc.
Projectiles <- []
function ProjectileCreate()
{
local projectile = SpawnEntityFromTable("prop_dynamic_override",
{
origin = Vector(-435, 2183, -63)
model = "models/items/cs_gift.mdl"
solid = 0 // not solid so bullets don't hit it
})
projectile.SetMoveType(8, 0) // noclip
projectile.SetAbsVelocity(Vector(-300, 0, 0))
projectile.ValidateScriptScope()
projectile.GetScriptScope().m_lag_record <- [projectile.GetOrigin()]
AddThinkToEnt(projectile, "ProjectileThink")
Projectiles.append(projectile)
}
function ProjectileThink()
{
local origin = self.GetOrigin()
local velocity = self.GetAbsVelocity()
local deltatime = FrameTime()
// save position into history
m_lag_record.append(origin)
if (m_lag_record.len() > 100)
m_lag_record.remove(0)
// apply gravity
local gravity = 800.0 * deltatime
velocity.z -= gravity
// check for collision
local trace =
{
start = origin
end = origin + velocity * deltatime
ignore = self
}
TraceLineEx(trace)
if (trace.hit)
{
local speed = velocity.Norm()
// bounce off
velocity = (velocity - (trace.plane_normal * (velocity.Dot(trace.plane_normal) * 2.0))) * speed
}
self.SetAbsVelocity(velocity)
return -1
}
LastImpactUserID <- null
LastImpactFrame <- null
function OnGameEvent_bullet_impact(params)
{
// optimization: don't lag compensate after 1st penetration
local userid = params.userid
local frame = GetFrameCount()
if (LastImpactUserID == userid && frame == LastImpactFrame)
return
LastImpactUserID = userid
LastImpactFrame = frame
local player = GetPlayerFromUserID(params.userid)
local start = player.EyePosition()
local end = Vector(params.x, params.y, params.z)
local dir = end - start
local dist = dir.Norm()
DebugDrawLine(start, end, 0, 255, 255, false, 3.0)
local lag = GetPlayerLag(player)
local tick = TimeToTicks(lag)
foreach (projectile in Projectiles)
{
if (projectile.IsValid())
{
local lag_record = projectile.GetScriptScope().m_lag_record
local lag_record_tick = lag_record.len() - 1 - tick
if (lag_record_tick < 0) lag_record_tick = 0
local lag_origin = lag_record[lag_record_tick]
local mins = projectile.GetBoundingMins()
local maxs = projectile.GetBoundingMaxs()
DebugDrawBox(projectile.GetOrigin(), mins, maxs, 255, 0, 0, 20, 3.0)
DebugDrawBox(lag_origin, mins, maxs 0, 255, 0, 20, 3.0)
if (IntersectRayWithBox(start, dir, lag_origin + mins, lag_origin + maxs, 0.0, dist) > 0.0)
{
DebugDrawScreenTextLine(0.5, 0.45, 0, "HIT!", 255, 255, 255, 255, 0.25)
}
}
}
}
__CollectGameEventCallbacks(this)
PlayerMgr <- Entities.FindByClassname(null, "cs_player_manager")
function GetPlayerLag(player)
{
local latency = NetProps.GetPropIntArray(PlayerMgr, "m_iPing", player.entindex()) * 0.001
printl(latency)
if (player != GetListenServerHost())
latency += NetProps.GetPropFloat(player, "m_fLerpTime")
return latency
}
function TimeToTicks(time)
{
return (0.5 + time / FrameTime()).tointeger()
}
function IntersectRayWithBox(start, dir, mins, maxs, near, far)
{
foreach (i in ["x", "y", "z"])
{
local d = dir[i]
if (fabs(d) > 0.0001)
{
local recip_dir = 1.0 / d
local t1 = (mins[i] - start[i]) * recip_dir
local t2 = (maxs[i] - start[i]) * recip_dir
if (t1 < t2)
{
if (t1 >= near) near = t1
if (t2 <= far) far = t2
}
else
{
if (t2 >= near) near = t2
if (t1 <= far) far = t1
}
if (near > far) return -1.0
}
else if (start[i] < mins[i] || start[i] > maxs[i])
{
return -1.0
}
}
return near
}
ProjectileCreate()
Add a chat command, that changes color of entity in crosshair
local EventsID = UniqueString()
getroottable()[EventsID] <-
{
function OnGameEvent_player_say( d )
{
if( d.text[0] != '.' ) return
local ply = GetPlayerFromUserID(d.userid)
local msg = split(d.text, " ")
if( msg[0] == ".color" && msg.len() == 4)
{
local tr =
{
start = ply.EyePosition()
end = ply.EyePosition() + ply.EyeAngles().Forward() * 1024
ignore = ply
}
TraceLineEx(tr)
DebugDrawLine(tr.start, tr.end, 255, 0, 0, false, 64.0)
if( tr.hit && tr.enthit.GetClassname() != "worldspawn" )
{
try{SetColor(tr.enthit, msg[1].tointeger(), msg[2].tointeger(), msg[3].tointeger())} catch(e){
ClientPrint(ply, 3, "\x07FF0000[INFO] \x01Command parameter must be a number")
return
}
ClientPrint(ply, 3, "\x07FF0000[INFO] \x01Changed the color of entity " + tr.enthit.GetClassname())
}
else ClientPrint(ply, 3, "\x07FF0000[INFO] \x01Aim to an entity to change color")
}
}
}
local Instance = self
local EventsTable = getroottable()[EventsID]
foreach (name, callback in EventsTable)
{
local callback_binded = callback.bindenv(this)
EventsTable[name] = @(params) Instance.IsValid() ? callback_binded(params) : delete getroottable()[EventsID]
}
__CollectGameEventCallbacks(EventsTable)
function SetColor( e, r, g, b )
{
local clr = (r) | (g << 8) | (b << 16)
NetProps.SetPropInt(e, "m_clrRender", clr)
}