commit b09ead26a0d7e5ca96bc170062bff4a5de8b6993 Author: nullprop Date: Sat Mar 15 06:06:10 2025 +0200 Init diff --git a/timer.nut b/timer.nut new file mode 100644 index 0000000..282a625 --- /dev/null +++ b/timer.nut @@ -0,0 +1,482 @@ +MASK_ATTACK_TRACE <- + Constants.FContents.CONTENTS_SOLID | + Constants.FContents.CONTENTS_PLAYERCLIP | + Constants.FContents.CONTENTS_WINDOW | + Constants.FContents.CONTENTS_GRATE; + +::Log <- function(msg) +{ + local time = Time(); + printl(format("[timer][%.2f] | %s", time, msg)); +} + +::PrintToPlayer <- function(ply, msg) +{ + ClientPrint(ply, Constants.EHudNotify.HUD_PRINTTALK, msg); +} + +::GetPlayerName <- function(ply) +{ + return NetProps.GetPropString(ply, "m_szNetname"); +} + +class Zone +{ + // aabb + min = Vector(); + max = Vector(); + + // bottom vertices + b1 = Vector(); + b2 = Vector(); + b3 = Vector(); + b4 = Vector(); + + // top vertices + t1 = Vector(); + t2 = Vector(); + t3 = Vector(); + t4 = Vector(); + + constructor(min, max) + { + this.min = min; + this.max = max; + + this.b1 = Vector(min.x, min.y, min.z); + this.b2 = Vector(min.x, max.y, min.z); + this.b3 = Vector(max.x, max.y, min.z); + this.b4 = Vector(max.x, min.y, min.z); + + this.t1 = Vector(min.x, min.y, max.z); + this.t2 = Vector(min.x, max.y, max.z); + this.t3 = Vector(max.x, max.y, max.z); + this.t4 = Vector(max.x, min.y, max.z); + } + + function Draw(duration) + { + // bottom + DebugDrawLine(this.b1, this.b2, 0, 255, 0, true, duration); + DebugDrawLine(this.b2, this.b3, 0, 255, 0, true, duration); + DebugDrawLine(this.b3, this.b4, 0, 255, 0, true, duration); + DebugDrawLine(this.b4, this.b1, 0, 255, 0, true, duration); + + // top + DebugDrawLine(this.t1, this.t2, 0, 255, 0, true, duration); + DebugDrawLine(this.t2, this.t3, 0, 255, 0, true, duration); + DebugDrawLine(this.t3, this.t4, 0, 255, 0, true, duration); + DebugDrawLine(this.t4, this.t1, 0, 255, 0, true, duration); + + // sides + DebugDrawLine(this.b1, this.t1, 0, 255, 0, true, duration); + DebugDrawLine(this.b2, this.t2, 0, 255, 0, true, duration); + DebugDrawLine(this.b3, this.t3, 0, 255, 0, true, duration); + DebugDrawLine(this.b4, this.t4, 0, 255, 0, true, duration); + } + + // Returns true if any part of entity bounding box intersects with zone + function Clips(entity) + { + local entOrigin = entity.GetOrigin(); + local entMin = entOrigin + entity.GetBoundingMins(); + local entMax = entOrigin + entity.GetBoundingMaxs(); + + local xClip = entMin.x < this.max.x && entMax.x > this.min.x; + local yClip = entMin.y < this.max.y && entMax.y > this.min.y; + local zClip = entMin.z < this.max.z && entMax.z > this.min.z; + + return xClip && yClip && zClip; + } + + function Print() + { + Log(format("min: [%f, %f, %f]", this.min.x, this.min.y, this.min.z)); + Log(format("max: [%f, %f, %f]", this.max.x, this.max.y, this.max.z)); + } +} + +class ZoneBuilder +{ + points = []; + + constructor() + { + + } + + // Pick a point for the new zone. + // Should be called 3 times in total. + function PickPoint(point) + { + local num_points = this.points.len(); + + if (num_points == 1) + { + // Only care about horizontal area + point.z = this.points[0].z; + } + else if (num_points == 2) + { + // Only care about the height + point.x = this.points[0].x; + point.y = this.points[0].y; + } + + Log(format("Adding point [%f, %f, %f]", point.x, point.y, point.z)); + this.points.append(point); + + if (num_points == 0) + { + PrintToPlayer(null, "Pick second horizontal point"); + return null; + } + else if (num_points == 1) + { + PrintToPlayer(null, "Pick the height"); + return null; + } + + // Create the zone + local min = Vector(this.points[0].x, this.points[0].y, this.points[0].z); + local max = Vector(this.points[0].x, this.points[0].y, this.points[0].z); + for (local i = 1; i < 3; i++) + { + if (this.points[i].x < min.x) min.x = points[i].x; + if (this.points[i].y < min.y) min.y = points[i].y; + if (this.points[i].z < min.z) min.z = points[i].z; + + if (this.points[i].x > max.x) max.x = points[i].x; + if (this.points[i].y > max.y) max.y = points[i].y; + if (this.points[i].z > max.z) max.z = points[i].z; + } + + this.points = []; + + return Zone(min, max); + } + + // Draw the zone we are building, + // including potential point we are going to pick. + function Draw(point) + { + local num_points = this.points.len(); + if (num_points <= 0) + { + return; + } + + local min = Vector(this.points[0].x, this.points[0].y, this.points[0].z); + local max = Vector(this.points[0].x, this.points[0].y, this.points[0].z); + + // Adding a horizontal point? + if (num_points == 1) + { + if (point.x < min.x) min.x = point.x; + if (point.y < min.y) min.y = point.y; + + if (point.x > max.x) max.x = point.x; + if (point.y > max.y) max.y = point.y; + } + // Adding height? + else if (num_points == 2) + { + if (this.points[1].x < min.x) min.x = this.points[1].x; + if (this.points[1].y < min.y) min.y = this.points[1].y; + + if (this.points[1].x > max.x) max.x = this.points[1].x; + if (this.points[1].y > max.y) max.y = this.points[1].y; + + if (point.z < min.z) min.z = point.z; + if (point.z > max.z) max.z = point.z; + } + + local temp_zone = Zone(min, max); + temp_zone.Draw(0.1); + } + + function IsBuilding() + { + return this.points.len() > 0; + } + + function GetPlayerAimPoint(ply) + { + local vecStart = ply.EyePosition(); + local vecEnd = vecStart + ply.EyeAngles().Forward() * 2048.0; + local tr = + { + "start": vecStart, + "end": vecEnd, + "mask": MASK_ATTACK_TRACE, + "ignore": ply + }; + if (TraceLineEx(tr)) + { + return tr["endpos"]; + } + return vecEnd; + } +} + +class PlayerState +{ + prev_zone_index = -1; + start_time = -1; + + constructor() + { + + } +} + +class Timer +{ + zones = []; + players = {}; + + constructor() + { + + } + + function AddZone(zone) + { + zones.append(zone); + if (zones.len() > 1) + { + PrintToPlayer(null, "Zone " + (this.zones.len() - 1) + " added"); + } + else + { + PrintToPlayer(null, "Starting zone added"); + } + } + + function ClearZones() + { + this.zones = []; + PrintToPlayer(null, "Zones cleared"); + } + + function RemoveZone() + { + local zone_num = this.zones.len(); + if (zone_num <= 0) + { + PrintToPlayer(null, "No zones to remove"); + return; + } + + this.zones.pop(); + if (zone_num == 1) + { + PrintToPlayer(null, "Removed the start zone"); + } + else + { + PrintToPlayer(null, "Removed zone " + zone_num); + } + } + + function Tick() + { + local ply = null; + while (ply = Entities.FindByClassname(ply, "player")) + { + local entindex = ply.entindex(); + + if (!ply.IsAlive() || + (ply.GetTeam() != Constants.ETFTeam.TF_TEAM_RED && + ply.GetTeam() != Constants.ETFTeam.TF_TEAM_BLUE) + ) + { + if (entindex in this.players) + { + delete this.players[entindex]; + } + continue; + } + + if (!(entindex in this.players)) + { + this.players[entindex] <- PlayerState(); + } + + if (this.zones.len() <= 0) + { + continue; + } + + local prev_zone_index = this.players[entindex].prev_zone_index; + local start_time = this.players[entindex].start_time; + if (prev_zone_index < 0) + { + // Not yet in start zone, only check the first. + if (this.zones[0].Clips(ply)) + { + this.players[entindex].prev_zone_index = 0; + Log("Entered the start zone"); + PrintToPlayer(ply, "Entered the start zone"); + } + } + else if (prev_zone_index == 0 && start_time < 0) + { + // In start zone, check if leaving. + if (!this.zones[0].Clips(ply)) + { + this.players[entindex].start_time = Time(); + Log("Left the start zone"); + PrintToPlayer(ply, "Run started"); + } + } + else + { + // Run ongoing. + + // Check if re-entering the start zone. + if (this.zones[0].Clips(ply)) + { + this.players[entindex].prev_zone_index = 0; + this.players[entindex].start_time = -1; + Log("Entered the start zone"); + PrintToPlayer(ply, "Entered the start zone"); + continue; + } + + // Check if entering the next zone. + for (local i = prev_zone_index + 1; i < this.zones.len(); i++) + { + if (this.zones[i].Clips(ply)) + { + local run_time = Time() - start_time; + + if (i == this.zones.len() - 1) + { + // Entered the final zone. + this.players[entindex].prev_zone_index = -1; + this.players[entindex].start_time = -1; + Log("Finished run in " + run_time + "s"); + PrintToPlayer( + null, + format( + "\x03%s\x01 finished a run in \x04%f\x01s", + GetPlayerName(ply), + run_time + ) + ); + } + else + { + this.players[entindex].prev_zone_index = i; + Log("Entered zone " + i + " at " + run_time + "s"); + PrintToPlayer(ply, format("Entered zone \x03%d\x01 at \x04%f\x01s", i, run_time)); + } + break; + } + } + } + } + } +} + +// -------------------------------- +// Commands +// -------------------------------- + +::CreateZone <- function() +{ + if ("zone_builder" in getroottable()) + { + local ply = GetListenServerHost(); + local zone = ::zone_builder.PickPoint(::zone_builder.GetPlayerAimPoint(ply)); + if (zone != null) + { + ::cool_timer.AddZone(zone); + } + } +} + +::DrawZones <- function() +{ + if ("cool_timer" in getroottable()) + { + foreach (zone in ::cool_timer.zones) + { + zone.Draw(10.0); + } + } +} + +::RemoveZone <- function() +{ + if ("cool_timer" in getroottable()) + { + ::cool_timer.RemoveZone(); + } +} + +::ClearZones <- function() +{ + if ("cool_timer" in getroottable()) + { + ::cool_timer.ClearZones(); + } +} + +::DumpZones <- function() +{ + if ("cool_timer" in getroottable()) + { + local num_zones = ::cool_timer.zones.len(); + for (local i = 0; i < num_zones; i++) + { + Log("Zone " + i); + ::cool_timer.zones[i].Print(); + Log(""); + } + } +} + +// -------------------------------- +// Hooks +// -------------------------------- + +function TimerThink() +{ + if ("cool_timer" in getroottable()) + { + ::cool_timer.Tick(); + } + if ("zone_builder" in getroottable()) + { + if (::zone_builder.IsBuilding()) + { + local ply = GetListenServerHost(); + ::zone_builder.Draw(::zone_builder.GetPlayerAimPoint(ply)); + } + } + + return 0.0; // Think every tick +} + +if (!("cool_timer" in getroottable())) +{ + ::cool_timer <- Timer(); + ::zone_builder <- ZoneBuilder(); + + if (!("TIMER_HOOKED_EVENTS" in getroottable())) + { + Log("Hooking game events"); + __CollectGameEventCallbacks(this); + ::TIMER_HOOKED_EVENTS <- true; + } + + local thinker = SpawnEntityFromTable("info_target", { targetname = "cool_timer_thinker" } ); + if (thinker.ValidateScriptScope()) + { + Log("Creating thinker"); + thinker.GetScriptScope()["TimerThink"] <- TimerThink; + AddThinkToEnt(thinker, "TimerThink"); + } +}