if Platform.cmdline then return end if FirstLoad then path_waypoints = false debug_pass_vectors = false end function OnMsg.DoneMap() path_waypoints = false debug_pass_vectors = false end function DrawWayPointReset() if path_waypoints and #path_waypoints > 0 then for i = 1, #path_waypoints do DoneObject(path_waypoints[i]) end end path_waypoints = {} end function DrawWayPointPath(pt_or_obj, pt_end, anim_time) DrawWayPointReset() if not pt_or_obj then pf.DbgDrawFindPath() return end local function DiagDistEx(p1, p2) local x1, y1 = p1:xy() local x2, y2 = p2:xy() local x = abs(x1 - x2) local y = abs(y1 - y2) if x > y then return y * 1448 / 1024 + x - y else return x * 1448 / 1024 + y - x end end local v = pf.DbgDrawFindPath(pt_or_obj, pt_end or GetTerrainCursor():SetInvalidZ(), anim_time or 5000) if not v then return "no path" end local len = #v local path_len = 0 for i = 1, len do local o = PlaceObject("WayPoint") path_waypoints[i] = o local pos = v[len+1-i] o:SetPos(pos) o:SetAngle(i) -- !WayPoint:GetAngle() shows index --if i > 1 then path_len = path_len + o:GetDist2D(path_waypoints[i-1]) end --local heuristics = path_len * 4 + DiagDistEx(pos, pt_end) * 8 --o:SetAngle(heuristics / 1000) end end -- Usage --!SelectedObj -> DrawPath(o) --!WayPoint:GetAngle() function DrawPath(obj) local path = IsValid(obj) and obj:GetComponentFlags(const.cofComponentPath) ~= 0 and obj:GetPath() or empty_table path_waypoints = path_waypoints or {} local index = 1 for i = 1, #path do local pos = path[i] if pos:IsValid() then local o = path_waypoints[index] if not IsValid(o) then o = PlaceObject("WayPoint") NetTempObject(o) path_waypoints[index] = o o:SetScale(40) end o:SetPos(pos) o:SetAngle(index) -- !WayPoint:GetAngle() shows index index = index + 1 end end for i = #path_waypoints, index, -1 do DoneObject(path_waypoints[i]) path_waypoints[i] = nil end return "" end function DbgDrawPath(path, color, offset) if IsValid(path) then local pt1 = path:GetPos() local pt0 = path:GetVisualPos() if not pt1:z() then pt0 = pt0:SetInvalidZ() end local pts = {pt0} if pt1 ~= pt0 then pts[#pts + 1] = pt1 end for i=path:GetPathPointCount(),1,-1 do local pt = path:GetPathPoint(i) if IsValidPos(pt) and pts[#pts] ~= pt then pts[#pts + 1] = pt end end path = pts end if #(path or "") < 2 then return end offset = offset or 5*guic color = color or white local pos0 = ValidateZ(path[1], offset) for i = 2, #path do local pos1 = ValidateZ(path[i], offset) DbgAddSegment(pos1, pos0, color) pos0 = pos1 end end function DbgSetTerrainBoxHeight(h, clip) clip = clip or box(0, 0, terrain.GetMapSize()) local htile = const.HeightTileSize local minx = clip:minx() / htile * htile local maxx = clip:maxx() / htile * htile local miny = clip:miny() / htile * htile local maxy = clip:maxy() / htile * htile local f = terrain.SetHeight for y = miny, maxy, htile do for x = minx, maxx, htile do f(x, y, h) end end end function DbgSnapTerrainToSlab(clip) clip = clip or box(0, 0, terrain.GetMapSize()) local htile = const.HeightTileSize local minx = clip:minx() / htile * htile local maxx = clip:maxx() / htile * htile local miny = clip:miny() / htile * htile local maxy = clip:maxy() / htile * htile local GetHeight = terrain.GetHeight local SetHeight = terrain.SetHeight local snapz = (const.SlabSizeZ or guim) / 2 local offsetz = -1 for y = miny, maxy, htile do for x = minx, maxx, htile do SetHeight(x, y, (GetHeight(x, y) + snapz/2) / snapz * snapz + offsetz) end end end function DbgRamp(pos, ptSize, angle2D, anglez) local htile = const.HeightTileSize local ztile = const.SlabSizeZ or guim local x, y, z = pos:xyz() x = (x + htile/2) / htile * htile y = (y + htile/2) / htile * htile z = (z + ztile/2) / ztile * ztile local dx1, dy1 = Rotate(point(ptSize:x(), 0), angle2D * 60):xy() dx1 = (dx1 + htile/2) / htile * htile dy1 = (dy1 + htile/2) / htile * htile local dx2, dy2 = Rotate(point(0, ptSize:y()), angle2D * 60):xy() dx2 = (dx2 + htile/2) / htile * htile dy2 = (dy2 + htile/2) / htile * htile local sz, cz = sincos(anglez*60) local dz = ptSize:y() * sz / cz dz = (dz + ztile/2) / ztile * ztile local poly = { point(x, y, z), point(x + dx1, y + dy1, z), point(x + dx1 + dx2, y + dy1 + dy2, z + dz), point(x + dx2, y + dy2, z + dz), } local spans = DbgGetPolySpans(poly) DbgSetTerraceHeightSpans(spans) end function DbgGetPolySpans(poly) if type(poly) ~= "table" or #poly == 0 then return end local xtile = const.HeightTileSize local ztile = const.SlabSizeZ or guim local offsz = -5*guic local spans = {} local miny, maxy local p1, p2 local function f(p) local x, y = p:xy() local x1, y1, z1 = p1:xyz() local x2, y2, z2 = p2:xyz() local dz = 0 if abs(x2 - x1) < abs(y2 - y1) then dz = ((z2 - z1) * (y - y1) + (y2 - y1) / 2) / (y2 - y1) elseif x2 ~= x1 then dz = ((z2 - z1) * (x - x1) + (x2 - x1) / 2) / (x2 - x1) end local pt = point(x * xtile, y * xtile, z1 + dz) local span = spans[y * xtile] if span then if pt:x() < span[1]:x() then span[1] = pt elseif pt:x() > span[2]:x() then span[2] = pt end else span = { pt, pt } spans[y * xtile] = span end if not miny or miny > y then miny = y end if not maxy or maxy < y then maxy = y end end for i = 1, #poly do local x1, y1, z1 = poly[i]:xyz() local x2, y2, z2 = poly[i == #poly and 1 or i + 1]:xyz() p1 = point((x1 + xtile/2) / xtile, (y1 + xtile/2) / xtile, z1) p2 = point((x2 + xtile/2) / xtile, (y2 + xtile/2) / xtile, z2) f(p1) if p1:x() ~= p2:x() or p1:y() ~= p2:y() then RasterizeLine(p1, p2, f) end end return spans end function DbgSetTerraceHeightSpans(spans) local SetHeight = terrain.SetHeight for y, span in pairs(spans) do for i = 1, #span, 2 do local x1, y1, z1 = span[i]:xyz() local x2, y2, z2 = span[i+1]:xyz() if x1 == x2 then SetHeight(x1, y, z1) else for x = x1, x2 do local z = z1 + ((z2 - z1) * (x - x1) + (x2 - x1) / 2) / (x2 - x1) SetHeight(x, y, z) end end end end end function DbgSetAnimPhase(unit, phase, anim) if anim and unit:GetStateText() ~= anim then unit:SetStateText(anim) end local v0 = unit:GetStepVector(anim, unit:GetAngle(), 0, unit:GetAnimPhase(1)) local v1 = unit:GetStepVector(anim, unit:GetAngle(), 0, phase) unit:SetPos(unit:GetPos() + (v1 - v0)) unit:SetAnimPhase(1, phase) unit:SetAnimSpeed(1, 0) end function ToggleDrawTunnels() DbgDrawTunnels(not debug_pass_vectors) end function DbgDrawTunnels(show) local count = 0 if show ~= false then debug_pass_vectors = debug_pass_vectors or {} local zoffset = (const.SlabSizeZ or guim) / 2 local invalid_z = const.InvalidZ local function f(obj, x1, y1, z1, x2, y2, z2, weight, tunnel_type, flags, param) if z1 == invalid_z then z1 = terrain.GetHeight(x1, y1) end if z2 == invalid_z then z2 = terrain.GetHeight(x2, y2) end count = count + 1 local vector = debug_pass_vectors[count] if not IsValid(vector) then vector = Vector:new() debug_pass_vectors[count] = vector vector:SetDepthTest(true) end local v1 = point(x1, y1, z1 + zoffset + obj.dbg_tunnel_zoffset) local v2 = point(x2, y2, z2 + zoffset + obj.dbg_tunnel_zoffset) vector:Set(v1, v2, obj.dbg_tunnel_color) end pf.ForEachTunnel("map", f) end for i = #(debug_pass_vectors or empty_table), count + 1, -1 do DoneObject(debug_pass_vectors[i]) debug_pass_vectors[i] = nil end if show == false then debug_pass_vectors = false end end function OnMsg.OnPassabilityChanged(clip) CreateMapRealTimeThread(function() if debug_pass_vectors then DbgDrawTunnels(true) end end) end function FollowForcedPath(unit, path) pf.SetForcedPath(unit, path) unit:Goto(path[#path], "sl") end function turn_test(angle, dist, unit, on_start, on_end) DbgClear() DbgSetVectorZTest(false) unit = unit or SelectedObj unit = GetTopmostParent(unit) angle = angle or -90 * 60 dist = dist or Max(2*unit:GetStepLength(), 4*unit:GetRadius()) if not unit then return end unit:SetCommand(function(self, angle, on_start, on_end) table.change(config, "turn_test", { DebugObjMovement = true }) if on_start then on_start(self) end local p0 = self:GetPos() local step = self:GetRelativePoint(dist, 0, 0) - self:GetVisualPos() local ps = p0 + SetLen(step, 10) local pts = {ps, p0 + step / 2} local n = (360*60) / abs(angle) if n * abs(angle) == 360*60 then n = n - 1 end for i=1,n do step = Rotate(step, angle) pts[#pts + 1] = pts[#pts] + step end pts[#pts + 1] = p0 DbgAddPoly(pts) self:SetPos(ps) FollowForcedPath(self, pts) if on_end then on_end(self) end table.restore(config, "turn_test") end, angle, on_start, on_end) end function OnMsg.SelectedObjChange(obj, prev) if config.DebugObjMovement then if IsValid(prev) and pf.GetDebugEnabled(prev) then pf.SetDebugEnabled(prev, false) rawset(prev, "auto_show_path", nil) end if IsValid(obj) then pf.SetDebugEnabled(obj, true) if pf.GetDebugEnabled(obj) then rawset(obj, "auto_show_path", false) DbgClearVectors() local path = pf.GetPath(obj) if path then DbgSetVectorZTest(false) DbgAddPoly(path, white, true); for i = 1,#path do DbgAddVector(path[i], const.PassTileSize, 0xff888888) end local dest, maxr, minr = pf.GetPathDest(obj) for _, pt in ipairs(dest) do DbgAddVector(pt, guim, 0xff0088ff) if maxr > 0 then DbgAddCircle(pt, maxr, 0xff88ff00) end if minr > 0 then DbgAddCircle(pt, minr, 0xffff8800) end end end end end end end --[[ Uncomment to keep track of the Step function __step_infos = false __step_info_time = 0 local CALLS, TIME, MAX, CLASS = 1, 2, 3, 4 function PrintStepInfo() local elapsed = Max(1, GameTime() - __step_info_time) print("Step ELAPSED", elapsed) table.sort(__step_infos, function(a, b) if a[TIME] ~= b[TIME] then return a[TIME] > b[TIME] end if a[MAX] ~= b[MAX] then return a[MAX] > b[MAX] end if a[CALLS] ~= b[CALLS] then return a[CALLS] < b[CALLS] end return a[CLASS] < b[CLASS] end) for _, info in ipairs(__step_infos) do printf("TIME %4d | CALLS %4d | LOAD %5.2f | AVGT %5.2f | MAXT %4d, CLASS %s", info[TIME], info[CALLS], info[TIME] * 100.0 / elapsed, info[TIME] * 1.0 / info[CALLS], info[MAX], info[CLASS]) end __step_infos = {} __step_info_time = GameTime() end local dbg_step = function(self, ...) local st = GetPreciseTicks() local status, new_path, res3 = pf.Step(self, ...) if new_path then if not __step_infos then __step_infos = {} __step_info_time = GameTime() end local step_info = __step_infos[self.class] local dt = GetPreciseTicks() - st if not step_info then step_info = {1, dt, dt, self.class} __step_infos[self.class] = step_info __step_infos[#__step_infos + 1] = step_info else step_info[CALLS] = step_info[CALLS] + 1 step_info[TIME] = step_info[TIME] + dt step_info[MAX] = Max(step_info[MAX], dt) local elapsed = Max(1, GameTime() - __step_info_time) local current_load = step_info[TIME] * 100 / elapsed if dt > 0 and (current_load > 0 or dt > MulDivRound(3, step_info[TIME], step_info[CALLS])) then local color = RandColor(xxhash(self.class)) if not self:CheckPassable(...) then local pos = self:ResolveGotoTarget(...) DbgAddCircle(pos, 600, color) DbgAddVector(pos, 100*guim, color) --StoreErrorSource(self, "Goto impassable: " .. tostring(pos)) end --DbgAddVector(self, 10 * guim * current_load, color) DbgDrawPath(self, color) DbgAddText(dt, self, color, nil, black) end end end assert(res3 == nil) return status, new_path end function OnMsg.ClassesPreprocess(classdefs) for name, def in next, classdefs do if def.Step == pf.Step then def.Step = dbg_step end end end --]] ---- MapVar("DbgDrawPathObjs", false) function DbgTogglePaths() DbgDrawPaths(not DbgDrawPathObjs) end local function DbgMarkPathObj(obj, time) local info = DbgDrawPathObjs[obj] if not info then info = { hashes = {} } DbgDrawPathObjs[obj] = info end info.time = time or GameTime() end local function DbgSetPathObj(parent, name, dbg_obj, attach) local info = DbgDrawPathObjs[parent] if not info then return end local prev_dbg_obj = info[name] info[name] = dbg_obj if dbg_obj and attach then dbg_obj:SetScale(10000/parent:GetScale()) parent:Attach(dbg_obj) end DoneObjects(prev_dbg_obj) if IsValid(prev_dbg_obj) then DoneObject(prev_dbg_obj) end end local function DbgUpdatePathObj(obj, name, hash) local prev_hash = table.get(DbgDrawPathObjs, obj, "hashes", name) if prev_hash == hash then return end table.set(DbgDrawPathObjs, obj, "hashes", name, hash) return true end function DbgDrawPaths(draw) if not draw then for obj, info in pairs(DbgDrawPathObjs) do for name, dbg_obj in pairs(info) do if type(dbg_obj) == "table" then DoneObjects(dbg_obj) if IsValid(dbg_obj) then DoneObject(dbg_obj) end end end end DbgDrawPathObjs = false return end print("Cyan: destlock, Green: resting, Yellow: collision, White: path, Pink: flight") DbgDrawPathObjs = {} CreateGameTimeThread(function(paths) while DbgDrawPathObjs == paths do local time = GameTime() MapForEach("map", nil, const.efDestlock, function(obj) DbgMarkPathObj(obj, time) local pos = obj:GetPos() local hash = xxhash(pos) if DbgUpdatePathObj(obj, "destlock", hash) then local circle = PlaceCircle(ValidateZ(pos), pf.GetDestlockRadius(obj), cyan, false) DbgSetPathObj(obj, "destlock", circle) end if DbgUpdatePathObj(obj, "path", hash) then local vector = PlaceVector(ValidateZ(pos), 2*guim, cyan, false) DbgSetPathObj(obj, "path", vector) end end) MapForEach("map", nil, const.efUnit, function(obj) if obj:GetComponentFlags(const.cofComponentPath) == 0 or not IsValidPos(obj) then return end DbgMarkPathObj(obj, time) local resting = obj:GetEnumFlags(const.efResting) ~= 0 local destlock_radius = pf.GetDestlockRadius(obj) local collision_radius = pf.GetCollisionRadius(obj) local scale = obj:GetScale() local resting_hash = xxhash(resting, destlock_radius, collision_radius, scale) if DbgUpdatePathObj(obj, "resting", resting_hash) then local collision_circle, destlock_circle if resting then destlock_circle = CreateCircleMesh(destlock_radius, green) else collision_circle = CreateCircleMesh(collision_radius, yellow) end DbgSetPathObj(obj, "collision", collision_circle, true) DbgSetPathObj(obj, "destlock", destlock_circle, true) end local flight_path = obj.flying and obj.flight_path or empty_table local path = pf.GetPath(obj) or empty_table local path_hash = obj:GetPathHash() if DbgUpdatePathObj(obj, "path", path_hash) then local destlock = obj:GetDestlock() local path_line, path_spline, path_steps if #flight_path > 0 then path_spline = PlaceSplines(obj.flight_path, 0xff00ff, false, obj.flight_spline_idx) else local path = pf.GetPath(obj) or "" if #path > 0 then for i=2,#path do local pt = path[i] if IsValidPos(pt) then pt = ValidateZ(pt) path_steps = table.create_add(path_steps, PlacePolyLine({pt:AddZ(-100), pt:AddZ(100)}, yellow, false)) end end path[#path + 1] = obj:GetVisualPos() path_line = PlacePolyLine(path, white, false) elseif IsValidPos(destlock) then path_line = PlacePolyLine({obj:GetVisualPos(), destlock:GetVisualPos()}, white, false) end end DbgSetPathObj(obj, "path_steps", path_steps) DbgSetPathObj(obj, "path", path_line) DbgSetPathObj(obj, "spline_path", path_spline) end end) for obj, info in pairs(DbgDrawPathObjs) do if info.time ~= time then for name, mesh in pairs(info) do if IsValid(mesh) then DoneObject(mesh) end end DbgDrawPathObjs[obj] = nil end end Sleep(100) end end, DbgDrawPathObjs) end -- requires config.DbgObjCallback = true function OnMsg.DbgObjCallback(method, obj, ...) print("DbgObjCallback:", obj.class, method, ...) end