Skip to content

Instantly share code, notes, and snippets.

@FloooD
Created April 27, 2011 22:57
Show Gist options
  • Save FloooD/945418 to your computer and use it in GitHub Desktop.
Save FloooD/945418 to your computer and use it in GitHub Desktop.
LaserMod = lasers + geometric optics
----------------------------------------------------------------
--LUA Lag Compensation version 1.0.1 + LaserMod by FlooD--------
----------------------------------------------------------------
--thx to Jermuk for his custom server, which first encouraged---
-- me to make lag compensation.------------------------------
--thx to lasthope and def3ct for initial testing.---------------
--thx to 3rr0r for ideas and suggestions for refining the code.-
----------------------------------------------------------------
--visit cs2d.nl/index.php?mod=board&action=thread&where=120 for-
-- the lastest version and information-----------------------
----------------------------------------------------------------
--[[initialization, arrays, reseting]]--------------------------
math.randomseed(os.time())
math.randomseed(os.time() * math.sin(os.time() * math.random()))
images = 0
max_images = 12 --lower to decrease lag
IOR = {1, 1.5, 0} --index of refraction of ground, obstacle, wall. < 1 means mirror.
power_threshold = 0.01 --raise to decrease lag
gamma = 1 / 2.2 --how bright faint lines are
fade_time = 1000 --how long lines last
ping = {} --array of filtered pings of living players
mode = {{}, {}, {}} --glock burst, famas burst, zoom
buffer = {{}, {}} --buffer of current and past positions in x and y coordinates
enable = {} --whether lag comp is enabled for one player
disabled = {} --weapons whose bullets are not compensated
for i, v in ipairs({0, 47, 51, 72, 73, 75, 76, 77, 86, 87, 253, 254, 255}) do
disabled[v] = true
end
armor = {} --special armor protection values
for i, v in ipairs({25, 50, 75, 50, 95}) do
armor[200 + i] = 1 - (v / 100)
end
function reset(id)
mode[1][id] = 0
mode[2][id] = 0
mode[3][id] = 0
buffer[1][id] = {}
buffer[2][id] = {}
ping[id] = nil
end
function clear(id)
reset(id)
enable[id] = 1
end
for i = 1, 32 do --initial
clear(i)
end
addhook("leave", "clear")
addhook("die", "reset")
function updateping(id)
local actualping = player(id, "ping")
local lastping = ping[id]
if not lastping then lastping = 0 end
if actualping then
if actualping - lastping <= 30 or lastping == 0 then --regular
ping[id] = actualping
else --spike is "damped"
ping[id] = 0.7 * lastping + 0.3 * actualping
end
end
end
addhook("spawn", "updateping")
----------------------------------------------------------------
--[[periodic functions]]----------------------------------------
frame = 1
BUFFER_SIZE = 25
function updatebuffer()
frame = frame + 1
for i in pairs(ping) do
buffer[1][i][frame], buffer[2][i][frame] = player(i, "x"), player(i, "y")
buffer[1][i][frame - BUFFER_SIZE], buffer[2][i][frame - BUFFER_SIZE] = nil, nil
end
images = 0
end
addhook("always", "updatebuffer")
function onsecond()
for i in pairs(ping) do
updateping(i)
end
end
addhook("second", "onsecond")
----------------------------------------------------------------
--[[new hit system]]--------------------------------------------
addhook("hit", "onhit")
function onhit(v, id, wpn)
if disabled[wpn] or id == 0 or enable[id] ~= 1 then --preserve cs2d's internal hit system in these cases
return 0
end
return 1
end
addhook("attack", "onattack")
function onattack(id)
local wpn = player(id, "weapon")
if disabled[wpn] or enable[id] ~= 1 then --preserve cs2d's internal hit system in these cases
return
end
local dmg = itemtype(wpn, "dmg") * game("mp_damagefactor")
if (wpn == 2 and mode[1][id] == 1) or (wpn == 39 and mode[2][id] == 1) then --burst weapons
dmg = math.floor(dmg * 0.64 + 0.5)
local rot1 = player(id, "rot") - 6 + 12 * math.random()
local rot2 = player(id, "rot") + 6 + 8 * math.random()
local rot3 = player(id, "rot") - 6 - 8 * math.random()
simulate_attack(id, wpn, dmg, rot1)
simulate_attack(id, wpn, dmg, rot2)
simulate_attack(id, wpn, dmg, rot3)
return
elseif wpn == 10 or wpn == 11 then
for i=1,5 do
simulate_attack(id, wpn, dmg, player(id, "rot") - 20 + 40 * math.random(), 180)
end
return
end
if mode[3][id] == 1 then --scoped weapons
dmg = itemtype(wpn, "dmg_z1") * game("mp_damagefactor")
elseif mode[3][id] == 2 then
dmg = itemtype(wpn, "dmg_z2") * game("mp_damagefactor")
end
local rot = player(id, "rot") + itemtype(wpn, "dispersion") * (2 * math.random() - 1)
simulate_attack(id, wpn, dmg, rot)
end
addhook("attack2", "onattack2")
function onattack2(id, m)
local wpn = player(id, "weapon")
if wpn == 50 or wpn == 69 then
if enable[id] == 1 then
simulate_attack(id, wpn, itemtype(wpn, "dmg_z1") * game("mp_damagefactor"))
end
----------------------------------------------------------------
--[[syncs burst/zoom for each player to actual cs2d burst/zoom]]
elseif wpn == 2 then
mode[1][id] = m
elseif wpn == 39 then
mode[2][id] = m
elseif wpn ~= 32 and wpn >= 31 and wpn <= 37 then
mode[3][id] = m
end
end
addhook("reload", "unzoom")
addhook("select", "unzoom")
function unzoom(id)
mode[3][id] = 0
end
addhook("drop", "ondrop")
function ondrop(id,iid,wpn,ain,a,m)
mode[3][id] = 0
if wpn == 2 then
mode[1][id] = 0
elseif wpn == 39 then
mode[2][id] = 0
end
end
addhook("collect", "oncollect")
function oncollect(id,iid,wpn,ain,a,m)
if wpn == 2 then
mode[1][id] = m
elseif wpn == 39 then
mode[2][id] = m
end
end
----------------------------------------------------------------
--[[bullet simulation]]-----------------------------------------
--[[simulates the shooting of a bullet of damage (dmg) from
(wpn) by (id) with angle (rot) and range (range). it has two
parts. part 1 finds bullet's path before hitting a wall; part 2
calculates hits on other players (with lag compensation).]]
function simulate_attack(id, wpn, dmg, rot, range)
if not wpn then wpn = player(id, "weapon") end
if not dmg then dmg = itemtype(wpn, "dmg") * game("mp_damagefactor") end
if not rot then rot = player(id, "rot") end
if not range then range = itemtype(wpn, "range") end
local start_x = player(id, "x")
local start_y = player(id, "y")
--part 1 - split up beams
local beams = flattenbeam(get_rays(start_x, start_y, rot, 3 * range, 1))
--part 2 - detect hits
local frames = math.floor(ping[id] / 20)
if frames > (BUFFER_SIZE - 1) then
frames = (BUFFER_SIZE - 1)
end
local victims = {}
if game("sv_friendlyfire") == "0" and game("sv_gamemode") ~= "1" then
for i in pairs(ping) do
if player(i, "team") ~= player(id, "team") then
victims[i] = true
end
end
else
for i in pairs(ping) do
victims[i] = true
end
victims[id] = nil
end
local olddmg = dmg
for _, beam in ipairs(beams) do
dmg = olddmg * (beam[5] or 1)
for i in pairs(victims) do
if intersect(beam[1], beam[2], beam[3], beam[4], buffer[1][i][frame - frames], buffer[2][i][frame - frames], 12) then
parse("sv_sound2 "..id.." player/hit"..math.ceil(3 * math.random())..".wav")
parse("sv_sound2 "..i.." player/hit"..math.ceil(3 * math.random())..".wav")
local newhealth
local newarmor = player(i, "armor")
if newarmor <= 200 then
newarmor = newarmor - dmg
if newarmor < 0 then
newarmor = 0
end
newhealth = player(i, "health") - (dmg - math.floor(game("mp_kevlar") * (player(i, "armor") - newarmor)))
parse("setarmor "..i.." "..newarmor)
else --special armor values
newhealth = player(i, "health") - math.floor((dmg * (armor[newarmor] or 1)))
end
if newhealth > 0 then
parse("sethealth "..i.." "..newhealth)
else
parse("customkill "..id.." "..itemtype(wpn, "name").." "..i)
end
end
end
end
end
--the following functions are used in simulate_attack.
function get_rays(start_x, start_y, rot, range, power)
if range <= 0 then return end
if power <= power_threshold then return end
if not rot then return end
local end_x = start_x + (range) * math.sin(math.rad(rot))
local end_y = start_y - (range) * math.cos(math.rad(rot))
local inc_x, inc_y
if rot < 0 then
inc_x = -1
elseif rot > 0 and rot ~= 180 then
inc_x = 1
end
if math.abs(rot) > 90 then
inc_y = 1
elseif math.abs(rot) < 90 then
inc_y = -1
end
local tile_x = math.floor(start_x / 32)
local tile_y = math.floor(start_y / 32)
if (inc_x or 0) == -1 and start_x % 32 == 0 then tile_x = tile_x - 1 end
if (inc_y or 0) == -1 and start_y % 32 == 0 then tile_y = tile_y - 1 end
local beams = {}
local n2 = tile_IOR(tile_x, tile_y)
while true do
n1 = n2
n2 = tile_IOR(tile_x, tile_y)
if n2 < 1 then --mirror, 100% reflection.
local i
end_x, end_y, i = intersect(start_x, start_y, end_x, end_y, topixel(tile_x), topixel(tile_y), 16)
if i == 1 then --vertical reflection
rot = -rot
elseif i == 0 then --horizontal reflection
rot = ((1 + (inc_x or 1)) * 180 - rot) % 360 - 180
else
--msg("wtf")
break
end
range = range - math.sqrt((end_x - start_x) ^ 2 + (end_y - start_y) ^ 2)
beams[#beams + 1] = {start_x, start_y, end_x, end_y, power} --incident
beams[#beams + 1] = get_rays(end_x, end_y, rot, range, power) --reflected
break
elseif n1 ~= n2 then --interface. fresnel + snell.
local i
end_x, end_y, i = intersect(start_x, start_y, end_x, end_y, topixel(tile_x), topixel(tile_y), 16)
local rot_r, rot_t --reflection and transmission angles
local R --fraction of power reflected. T = 1 - R by conservation of energy.
local a, b --helpers
if i == 1 then
rot_r = -rot
local something = n1 / n2 * math.sin(math.rad(rot - (inc_x or 1) * 90))
if math.abs(something) <=1 then
rot_t = (inc_x or 1) * 90 + math.deg(math.asin(something)) --snell's law
end
a = rot_t and n1 * math.cos(math.rad(rot - (inc_x or 1) * 90)) or 1
b = rot_t and n2 * math.cos(math.rad(rot_t - (inc_x or 1) * 90)) or 0
elseif i == 0 then
rot_r = (inc_x or 1) * 180 - rot
local something = n1 / n2 * math.sin(math.rad(90 * (1 + (inc_y or 1)) - rot))
if math.abs(something) <= 1 then
rot_t = (90 * (3 + (inc_y or 1)) - math.deg(math.asin(something))) % 360 - 180
end
a = rot_t and n1 * math.cos(math.rad(90 * (1 + (inc_y or 1)) - rot)) or 1
b = rot_t and n2 * math.cos(math.rad(90 * (1 + (inc_y or 1)) - rot_t)) or 0
else
--msg("debug: wtf")
break
end
R = ((a - b) / (a + b)) ^ 2 -- fresnel's equation
range = range - math.sqrt((end_x - start_x) ^ 2 + (end_y - start_y) ^ 2)
beams[#beams + 1] = {start_x, start_y, end_x, end_y, power} --incident
if R < 0.5 then --this makes sure beam is sorted by power
rot_r, rot_t = rot_t, rot_r
R = 1 - R
end
beams[#beams + 1] = get_rays(end_x, end_y, rot_r, range, power * R)
beams[#beams + 1] = get_rays(end_x, end_y, rot_t, range, power * (1 - R))
break
end
local temp_x, temp_y = tile_x, tile_y
if inc_x and intersect(start_x, start_y, end_x, end_y, topixel(temp_x + inc_x), topixel(temp_y), 16) then
tile_x = temp_x + inc_x
end
if inc_y and intersect(start_x, start_y, end_x, end_y, topixel(temp_x), topixel(temp_y + inc_y), 16) then
tile_y = temp_y + inc_y
end
if tile_x == temp_x and tile_y == temp_y then
--msg("range")
beams[#beams + 1] = {start_x, start_y, end_x, end_y, power}
break
end
end
return beams
end
function tile_IOR(tile_x, tile_y)
if tile(tile_x, tile_y, "wall") then
return IOR[3]
elseif tile(tile_x, tile_y, "obstacle") then
return IOR[2]
else
return IOR[1]
end
end
function isbeam(tab)
if type(tab) ~= "table" or #tab ~= 5 then return false end
for i, v in ipairs(tab) do
if type(v) == "table" then return false end
end
return true
end
function flattenbeam(tab) --also draws them :D
local flat = {}
for i, v in ipairs(tab) do
if isbeam(v) then
drawbeam(v)
flat[#flat + 1] = {}
for a, b in ipairs(v) do
flat[#flat][a] = b
end
else
for c, d in ipairs(flattenbeam(v)) do
flat[#flat + 1] = {}
for e, f in ipairs(d) do
flat[#flat][e] = f
end
end
end
end
return flat
end
function drawbeam(beam)
if images <= max_images then
images = images + 1
local x, y = (beam[1] + beam[3]) / 2, (beam[2] + beam[4]) / 2
local id = image("gfx/sprites/laserbeam1.bmp", x, y, 1)
imagealpha(id, beam[5] ^ gamma) --crude srgb gamma correction
imagecolor(id, 0, 255, 0)
imagescale(id, 0.25, math.sqrt((beam[4] - beam[2]) ^ 2 + (beam[3] - beam[1]) ^ 2) / 32)
imagepos(id, x, y, 90 + math.deg(math.atan2((beam[4] - beam[2]) , (beam[3] - beam[1]))))
tween_alpha(id, fade_time, 0)
timer(fade_time, "freeimage", id)
end
end
--converts a tile number to the tile's center pixel.
function topixel(tile)
return (tile * 32) + 16
end
--quick test to see if i is in between s and e. used in intersect().
function isinorder(s, i, e)
return (e >= i and i >= s) or (e <= i and i <= s)
end
--[[returns the first point of intersection between a box centered at (bx, by) with
half side length (bl) and a line segment starting from (sx, sy) and ending at (ex, ey).
if the line segment is enclosed by the box, (ex, ey) is returned.]]
function intersect(sx, sy, ex, ey, bx, by, bl)
if not (bx and by) then return end --fixes rare lua error
if math.abs(sx - bx) <= bl and math.abs(sy - by) <= bl then
if math.abs(ex - bx) <= bl and math.abs(ey - by) <= bl then
if math.abs(ey - by) == bl then
return ex, ey, 0
end
return ex, ey, 1
else
sx, sy, ex, ey = ex, ey, sx, sy
end
end
local i_x, i_y
if ey > sy then
i_y = by - bl
elseif ey < sy then
i_y = by + bl
end
if i_y and isinorder(sy, i_y, ey) then
i_x = ((ex - sx) * i_y + (sx * ey - sy * ex)) / (ey - sy)
if math.abs(i_x - bx) <= bl and isinorder(sx, i_x, ex) then
return i_x, i_y, 0 -- collision with horizontal edge
end
end
if ex > sx then
i_x = bx - bl
elseif ex < sx then
i_x = bx + bl
end
if i_x and isinorder(sx, i_x, ex) then
i_y = ((ey - sy) * i_x + (sy * ex - sx * ey)) / (ex - sx)
if math.abs(i_y - by) <= bl and isinorder(sy, i_y, ey) then
return i_x, i_y, 1 -- collision with vertical edge
end
end
end
----------------------------------------------------------------
--[[debug stuff]]-----------------------------------------------
addhook("serveraction","onserveraction")
function onserveraction(id, action)
if action == 1 then
msg2(id, "This server is running LaserMod by FlooD. your current ping: "..(ping[id] or (player(id, "ping").." (dead)")))
end
end
----------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment