Created
April 27, 2011 22:57
-
-
Save FloooD/945418 to your computer and use it in GitHub Desktop.
LaserMod = lasers + geometric optics
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
---------------------------------------------------------------- | |
--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