Created
May 19, 2025 17:42
-
-
Save Earu/67dc5d6a619faac90535580904ea0f81 to your computer and use it in GitHub Desktop.
Scan Garry's Mod maps for bodies of water for various things.
This file contains hidden or 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
--[[ | |
THIS IS A LIBRARY THAT SCANS FOR THE SURFACE OF BODIES OF WATER IN A SECLUDED AREA. | |
IT IS USED TO FIND RANDOM POSITIONS IN WATER FOR ENTITIES TO SPAWN IN. | |
OR TO CHECK IF A SPECIFIC POSITION IS IN WATER. | |
/!\ IT IS HIGHLY RECOMMENDED TO CALL THIS FUNCTION ONCE AND STORE THE RESULT AS IT A VERY INTENSIVE FUNCTION! | |
EXAMPLE USAGE: | |
local origin = landmark.get("land_fish1") -- The origin point of the scan | |
local scan_precision = 100 -- The precision of the scan, higher = more accurate but slower | |
WaterScanner.ScanArea(origin, scan_precision, function(result) | |
-- Check if the point is in the water | |
local pos = ply:GetPos() | |
local is_water = result:Contains(pos.x, pos.y) | |
-- Get a random position in the water | |
local random_pos = result:GetRandomPos() | |
ent:SetPos(random_pos) | |
MsgC(Color(255, 0, 0), "DONE SCANNING WATER\n") | |
end) | |
]] | |
local meta = {} | |
meta.__index = meta | |
_G.WaterScanner = meta | |
function meta:Contains(x, y) | |
if x < self.MinX or x > self.MaxX or y < self.MinY or y > self.MaxY then | |
return false | |
end | |
local diff = x - self.MinX | |
local n = math.floor(diff / self.ScanInterval) | |
local index = self.MinX + (self.ScanInterval * n) | |
if not self.Bounds[index] then | |
return false | |
end | |
for _, bound in ipairs(self.Bounds[index]) do | |
if y >= bound.start and y <= bound["end"] then | |
return true | |
end | |
end | |
return false | |
end | |
function meta:GetRandomPos() | |
local all_x = table.GetKeys(self.Map) | |
local random_x = all_x[math.random(1, #all_x)] | |
local all_y = table.GetKeys(self.Map[random_x]) | |
local random_y = all_y[math.random(1, #all_y)] | |
return Vector(random_x, random_y, self.SurfaceZ) | |
end | |
local function get_world_bounds(origin_point) | |
-- Get the topmost point above the origin point | |
local top_most_point = util.TraceLine({ | |
start = origin_point, | |
endpos = origin_point + Vector(0, 0, 2e9), | |
mask = MASK_SOLID_BRUSHONLY, | |
}).HitPos - Vector(0, 0, 50) | |
-- Get max and min x world bounds | |
local max_x, min_x = 0, 0 | |
do | |
local x1 = util.TraceLine({ | |
start = top_most_point, | |
endpos = top_most_point + Vector(2e9, 0, 0), | |
mask = MASK_SOLID_BRUSHONLY, | |
}).HitPos.x | |
local x2 = util.TraceLine({ | |
start = top_most_point, | |
endpos = top_most_point + Vector(-2e9, 0, 0), | |
mask = MASK_SOLID_BRUSHONLY, | |
}).HitPos.x | |
max_x = math.max(x1, x2) | |
min_x = math.min(x1, x2) | |
end | |
-- Get max and min y world bounds | |
local max_y, min_y = 0, 0 | |
do | |
local y1 = util.TraceLine({ | |
start = top_most_point, | |
endpos = top_most_point + Vector(0, 2e9, 0), | |
mask = MASK_SOLID_BRUSHONLY, | |
}).HitPos.y | |
local y2 = util.TraceLine({ | |
start = top_most_point, | |
endpos = top_most_point + Vector(0, -2e9, 0), | |
mask = MASK_SOLID_BRUSHONLY, | |
}).HitPos.y | |
max_y = math.max(y1, y2) | |
min_y = math.min(y1, y2) | |
end | |
return min_x, max_x, min_y, max_y, top_most_point.z | |
end | |
function meta.ScanArea(origin, scan_interval, callback) | |
scan_interval = scan_interval or 100 | |
local min_x, max_x, min_y, max_y, top_most_z = get_world_bounds(origin) | |
local result = { | |
Map = {}, | |
Bounds = {}, | |
SurfaceZ = -1, | |
MinX = min_x, | |
MaxX = max_x, | |
MinY = min_y, | |
MaxY = max_y, | |
ScanInterval = scan_interval, | |
} | |
local co = coroutine.create(function() | |
local current_x = min_x | |
local current_y = min_y | |
local prev_state = "NOT_WATER" | |
local steps = 0 | |
while current_x < max_x do | |
result.Bounds[current_x] = result.Bounds[current_x] or {} | |
while current_y < max_y do | |
local point = Vector(current_x, current_y, top_most_z) | |
local water_trace = util.TraceLine({ | |
start = point, | |
endpos = point + Vector(0, 0, -2e9), | |
mask = MASK_WATER + MASK_SOLID_BRUSHONLY, | |
}) | |
local is_water = water_trace.Hit and water_trace.MatType == MAT_SLOSH | |
if is_water and result.SurfaceZ == -1 then | |
result.SurfaceZ = water_trace.HitPos.z | |
end | |
-- Add to the map | |
result.Map[current_x] = result.Map[current_x] or {} | |
result.Map[current_x][current_y] = is_water | |
local state = is_water and "WATER" or "NOT_WATER" | |
if prev_state ~= state then | |
if prev_state == "NOT_WATER" then | |
table.insert(result.Bounds[current_x], { start = current_y, ["end"] = -1 }) | |
else | |
local last_bound = result.Bounds[current_x][#result.Bounds[current_x]] | |
if last_bound["end"] == -1 then | |
last_bound["end"] = current_y | |
end | |
end | |
prev_state = state | |
end | |
current_y = current_y + scan_interval | |
steps = steps + 1 | |
if steps % 100 == 0 then | |
coroutine.yield() | |
end | |
end | |
local last_bound = result.Bounds[current_x][#result.Bounds[current_x]] | |
if last_bound and last_bound["end"] == -1 then | |
last_bound["end"] = current_y | |
end | |
current_y = min_y | |
current_x = current_x + scan_interval | |
prev_state = "NOT_WATER" | |
steps = steps + 1 | |
if steps % 100 == 0 then | |
coroutine.yield() | |
end | |
end | |
end) | |
local timer_name = "water_scanner_" .. FrameNumber() | |
timer.Create(timer_name, 0.25, 0, function() | |
if coroutine.status(co) == "dead" then | |
timer.Remove(timer_name) | |
callback(setmetatable(result, meta)) | |
return | |
end | |
local status, err = coroutine.resume(co) | |
if not status then | |
print(err) | |
end | |
end) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment