File size: 22,128 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
if Platform.cmdline then return end

MapVar("g_dbgCoversShown", false)
MapVar("s_CoversUpdateThread", false)
MapVar("s_CoversThreadBBox", false)
MapVar("s_Diff", false)
MapVar("s_CoversUpdateOperationInProgress", false)

MapVar("s_DbgDrawLOS", false)
MapVar("s_DbgDrawLOF", false)
MapVar("s_DbgDrawLOF_Objects", {})
MapVar("s_DbgDrawLOF_EYE", false)
MapVar("s_DbgDrawIgnoreSmoke", false)
MapVar("s_DbgDrawLastTarget", false)
MapVar("s_DbgDrawTargetDummies", false)

DefineClass.DebugCoverDraw = {
	__parents = {"CObject"},
	flags = {efSelectable = false, gofPermanent = false},
}

DefineClass.DebugCoverDrawNorthLow = {__parents = {"DebugCoverDraw"}, entity = "CoverNorth"}
DefineClass.DebugCoverDrawEastLow = {__parents = {"DebugCoverDraw"}, entity = "CoverEast"}
DefineClass.DebugCoverDrawSouthLow = {__parents = {"DebugCoverDraw"}, entity = "CoverSouth"}
DefineClass.DebugCoverDrawWestLow = {__parents = {"DebugCoverDraw"}, entity = "CoverWest"}
DefineClass.DebugCoverDrawNorthHigh = {__parents = {"DebugCoverDraw"}, entity = "CoverNorth_High"}
DefineClass.DebugCoverDrawEastHigh = {__parents = {"DebugCoverDraw"}, entity = "CoverEast_High"}
DefineClass.DebugCoverDrawSouthHigh = {__parents = {"DebugCoverDraw"}, entity = "CoverSouth_High"}
DefineClass.DebugCoverDrawWestHigh = {__parents = {"DebugCoverDraw"}, entity = "CoverWest_High"}

local function StopCoversUpdateThread()
	DeleteThread(s_CoversUpdateThread)
	s_CoversUpdateThread = false
	s_CoversThreadBBox = false
end

local function HashPoint(pt)
	return string.format("point(%d,%d,%d)", pt:x(), pt:y(), pt:z())
end

function AreCoversShown()
	return not not g_dbgCoversShown
end

local debugcover_offset = const.SlabSizeX / 2 - 10 * guim / 100
local debugcovers = {
	{	-- up
		[const.CoverLow] = { cover = "DebugCoverDrawNorthLow", offset = point(0, -debugcover_offset, 0), angle = 90 * 60 },
		[const.CoverHigh] = { cover = "DebugCoverDrawNorthHigh", offset = point(0, -debugcover_offset, 0), angle = 90 * 60 },
	},
	{	-- right
		[const.CoverLow] = { cover = "DebugCoverDrawEastLow", offset = point(debugcover_offset, 0, 0), angle = 2 * 90 * 60 },
		[const.CoverHigh] = { cover = "DebugCoverDrawEastHigh", offset = point(debugcover_offset, 0, 0), angle = 2 * 90 * 60 },
	},
	{	-- down
		[const.CoverLow] = { cover = "DebugCoverDrawSouthLow", offset = point(0, debugcover_offset, 0), angle = 3 * 90 * 60 },
		[const.CoverHigh] = { cover = "DebugCoverDrawSouthHigh", offset = point(0, debugcover_offset, 0), angle = 3 * 90 * 60 },
	},
	{	-- left
		[const.CoverLow] = { cover = "DebugCoverDrawWestLow", offset = point(-debugcover_offset, 0, 0), angle = 0 },
		[const.CoverHigh] = { cover = "DebugCoverDrawWestHigh", offset = point(-debugcover_offset, 0, 0), angle = 0 },
	}
}

function DbgDrawCovers(dbg, bbox, dont_toggle, dont_rebuild)
	local total = GetClock()
	dbg = dbg or ""
	
	local old = MapGet(s_CoversUpdateOperationInProgress and bbox or "map", "DebugCoverDraw")
	for _, dbg in ipairs(old) do
		DoneObject(dbg)
	end
	--DbgClearVectors()

	if not dont_toggle then
		g_dbgCoversShown = not g_dbgCoversShown
	end
	if not g_dbgCoversShown then
		DbgDrawPassSlabs(false)
		StopCoversUpdateThread()
		if not IsEditorActive() then
			StartWallInvisibilityThread()
			HideFloorsAbove(999)
		else
			XEditorFiltersUpdateVisibility()
		end
		Msg("DbgCoversUpdated", bbox)
		return
	end
	g_dbgCoversShown = dbg or g_dbgCoversShown

	local dbg_draw_pass_objs = DbgDrawPassSlabs("on", "terrain pass", nil, bbox)

	local rebuild = GetClock()
	bbox = bbox or GetMapBox()
	if not dont_rebuild then
		bbox = RebuildCovers(bbox)
	end
	rebuild = GetClock() - rebuild

	local floor = IsEditorActive() and XEditorFilters:GetFilter("HideFloor") or 0
	if floor == 0 then floor = 999 end
	BuildBuildingsData()

	local ptExtend = point(const.SlabSizeX, const.SlabSizeY, const.SlabSizeZ * 2)
	ForEachCover(bbox, -1, function(x, y, z, up, right, down, left)
		z = z or terrain.GetHeight(x, y)
		local pt = point(x, y, z)
		local bbox_room = Extend(empty_box, pt):grow(ptExtend)
		local room = EnumVolumes(bbox_room, "smallest")
		local bld_meta = VolumeBuildingsMeta[room and room.building or false]
		if bld_meta and bld_meta[floor] and z >= bld_meta[floor].box:minz() - 5 * guim / 10 then
			return -- don't add debug covers that would be hidden because of the floor editor filter
		end
		for i, val in ipairs{ up, right, down, left } do
			local t = debugcovers[i][val]
			if t then
				local cover = PlaceObject(t.cover)
				cover:SetPos(pt + t.offset)
				cover:SetAngle(t.angle)
			end
		end
	end)

	if string.match(dbg, "box") then
		local pt1 = bbox:min()
		local x, y, z = bbox:sizexyz()
		local xx, yy, zz = point(x, 0, 0), point(0, y, 0), point(0, 0, z)
		DbgAddPoly({
			pt1, pt1 + xx, pt1 + xx + yy, pt1 + yy,pt1,
			pt1 + zz, pt1 + zz + xx, pt1 + zz + xx + yy, pt1 + zz + yy, pt1 + zz})
		DbgAddPoly({pt1 + xx, pt1 + xx + zz})
		DbgAddPoly({pt1 + xx + yy, pt1 + xx + yy + zz})
		DbgAddPoly({pt1 + yy, pt1 + yy + zz})
	end

	total = GetClock() - total

	if not s_CoversUpdateOperationInProgress then
		local count = MapCount(bbox, "DebugCoverDraw")
		print(string.format("Covers: %d, DebugPassDraw objects: %d, Rebuild: %dms, Debug Info: %dms, Total: %dms", count, dbg_draw_pass_objs, rebuild, total - rebuild, total))
	end

	if not IsEditorActive() then
		StartWallInvisibilityThread()
	end
	Msg("DbgCoversUpdated", bbox)
end

local function DbgCoverUpdate(op_finished, bbox, objects)
	if not g_dbgCoversShown then return end
	
	bbox = GetCoversVoxelPatchAlignedTaskBox(bbox)
	s_CoversThreadBBox = s_CoversThreadBBox and AddRects(s_CoversThreadBBox, bbox) or bbox
	if op_finished then
		DbgDrawCovers(g_dbgCoversShown, s_CoversThreadBBox, "don't toggle")
		if s_CoversUpdateOperationInProgress then
			s_CoversUpdateOperationInProgress = false
			StopCoversUpdateThread()
		end
		return
	end

	s_CoversUpdateOperationInProgress = true
	DeleteThread(s_CoversUpdateThread)
	s_CoversUpdateThread = CreateMapRealTimeThread(function()
		Sleep(200)
		local resume = ArePassEditsForEditOpSuspended()
		if resume then
			ResumePassEditsForEditOp()
		end
		for _, obj in ipairs(objects) do
			obj:ApplySurfaces()
		end
		if resume then
			SuspendPassEditsForEditOp()
		end
		terrain.RebuildPassability(s_CoversThreadBBox)
		DbgDrawCovers(g_dbgCoversShown, s_CoversThreadBBox, "don't toggle")
		s_CoversUpdateThread = false
		s_CoversThreadBBox = false
	end)
end

function OnMsg.EditorCategoryFilterChanged(c, filter)
	if c == "HideFloor" and g_dbgCoversShown then
		DbgDrawCovers(nil, nil, "dont_toggle")
	end
end

function OnMsg.EditorHeightChanged(op_finished, bbox)
	DbgCoverUpdate(op_finished, bbox)
end

function OnMsg.OnPassabilityChanged(bbox)
	if not s_CoversUpdateOperationInProgress then
		DelayedCall(0, DbgCoverUpdate, true, bbox)
	end
end

local function CoversUpdate(op_finished, objects)
	local bbox = GetObjectsBBox(objects):SetInvalidZ()
	local ptExtend = point(const.SlabSizeX, const.SlabSizeY)
	bbox = box(bbox:min() - ptExtend, bbox:max() + ptExtend)
	DbgCoverUpdate(op_finished, bbox, objects)
end

function OnMsg.EditorObjectOperation(op_finished, objects)
	CoversUpdate(op_finished, objects)
end

function OnMsg.EditorCallbackPreUndoRedo(objects)
	CoversUpdate(false, objects)
end

function OnMsg.CombatObjectDied(obj, bbox)
	DbgCoverUpdate(false, bbox)
end

local function DbgCoverRemoveInvalidWallSlabs(maps)
	if not IsRealTimeThread() then
		CreateRealTimeThread(DbgCoverRemoveInvalidWallSlabs, maps)
		return
	end

	maps = maps or ListMaps()
	if type(maps) == "string" and string.match(maps, "current") then
		maps = {GetMapName()}
	end
	ForEachMap(maps, function()
		local to_remove = setmetatable({}, weak_values_meta)
		MapForEach("map", "WallSlab", function(obj)
			if obj.class == "WallSlab" and not IsValidEntity(obj:GetEntity()) then
				table.insert(to_remove, obj)
			end
		end)
		for _, obj in ipairs(to_remove) do
			DoneObject(obj)
		end
		print(string.format("Removed %d invalid WallSlab objects.", #to_remove))
		SaveMap("no backup")
	end)
end

DefineClass.DebugVisDummy =
{
	__parents = { "AppearanceObject" },
	flags = {
		efSelectable = false, efWalkable = false, efCollision = false, 
		efApplyToGrids = false, efShadow = false, efSunShadow = false,
		cfConstructible = false,
		cofComponentColorizationMaterial = true,
		cofComponentCollider = false,
	},
	attack_color_modifier = RGB(0, 200, 100),
	target_color_modifier = RGB(0, 100, 200),
	template_id = "Barry",

	__toluacode = Object.__toluacode,

	CloneFrom = function(self, obj)
		local appearance = obj and obj.Appearance or ChooseUnitAppearance(self.template_id, self.handle)
		self:ApplyAppearance(appearance)
		if obj then
			self.stance  = obj.stance
			self.current_weapon = obj.current_weapon
		end
		local weapon1, weapon2
		if obj then
			weapon1, weapon2 = obj:GetActiveWeapons()
		else
			weapon1 = Firearm
		end
		local wobj1 = IsKindOf(weapon1, "Firearm") and weapon1:CreateVisualObj(self)
		local wobj2 = IsKindOf(weapon2, "Firearm") and weapon2:CreateVisualObj(self)

		local attached_weapons = self:GetAttaches("WeaponVisual")
		for i, o in ipairs(attached_weapons or empty_table) do
			if o ~= wobj1 and o ~= wobj2 then
				DoneObject(o)
			end
		end
		if wobj1 then
			self:Attach(wobj1, self:GetSpotBeginIndex("Weaponr"))
		end
		if wobj2 then
			self:Attach(wobj2, self:GetSpotBeginIndex("Weaponl"))
		end
		
		self:SetState(obj:GetState(), 0, 0)
		local phase = self:GetAnimMoment(self:GetStateText(), "hit") or 0
		self:SetAnimPhase(1, phase)
		self:SetAngle(obj:GetAngle())
		self:SetPos(obj:GetPos())
	end,
}

local function DbgPlaceAttackDummy(obj, pos, angle, anim, phase, target_pos)
	local o = PlaceObject("DebugVisDummy")
	o:CloneFrom(obj)
	o:SetColorModifier(o.attack_color_modifier)
	for i, attach in ipairs(o:GetAttaches()) do
		attach:SetColorModifier(o.attack_color_modifier)
	end
	pos = pos or obj:GetPos()
	angle = target_pos and CalcOrientation(pos, target_pos) or angle or obj:GetAngle()
	anim = anim or obj:GetActionRandomAnim("Fire", obj.stance or "Standing")
	phase = phase or obj:GetAnimMoment(anim, "hit") or 0
	o:SetState(anim, 0, 0)
	o:SetAnimPhase(1, phase)
	o:SetAnimSpeed(1, 0)
	o:SetAngle(angle)
	o:SetPos(pos)
	if target_pos then
		local ikCmp = o:GetAnimComponentIndexFromLabel(1, "AimIK")
		if ikCmp ~= 0 then
			o:SetAnimComponentTarget(1, ikCmp, target_pos, InvalidPos(), 0, 0)
		end
	end
	return o
end

local function DbgPlaceTargetDummy(obj, dummy)
	local o = PlaceObject("DebugVisDummy")
	o:CloneFrom(obj)
	o:SetColorModifier(o.target_color_modifier)
	for i, attach in ipairs(o:GetAttaches()) do
		attach:SetColorModifier(o.target_color_modifier)
	end
	if not IsValid(dummy) and type(dummy) == "table" then
		if dummy.anim then
			o:SetStateText(dummy.anim, 0, 0)
			local phase = o:GetAnimMoment(dummy.anim, "hit") or 0
			o:SetAnimPhase(1, phase)
		end
		if dummy.phase then
			o:SetAnimPhase(1, dummy.phase)
		end
		if dummy.angle then
			o:SetAngle(dummy.angle)
		end
		if dummy.pos then
			o:SetPos(dummy.pos)
		end
	end
	o:SetAnimSpeed(1, 0)
	return o
end

local function ClearTargetObjects()
	local dummies = s_DbgDrawLOF_Objects
	for k, obj in ipairs(dummies) do
		if IsValid(obj) then
			DoneObject(obj)
		end
	end
	table.iclear(dummies)
end

local clrClearHit = RGB(0, 200, 100)
local clrObstructionHit = RGB(250, 100, 0)
local clrStuckToTarget = RGB(30, 30, 30)
local clrAttackPos     = RGB(0, 50, 255)
local clrForcedTargetHit = RGB(0, 130, 150)
local clrCollisionTarget = RGB(0, 255, 100)
local clrCollisionPierce = RGB(255, 100, 0)
local clrCollisionIgnored = RGB(160, 160, 160)
local clrCollisionStuckPower = RGB(255, 255, 0)
local clrCollisionImpenetrable = RGB(255, 0, 0)
local clrLOSLines = {
	const.clrWhite,
	const.clrRed,
	const.clrGreen,
	const.clrCyan,
	const.clrBlue,
	const.clrPink,
	const.clrYellow,
	const.clrOrange,
	const.clrMagenta,
}

local function AddLine(p1, p2, color)
	local path = pstr("")
	path:AppendVertex(p1, color)
	path:AppendVertex(p2)

	local line = PlaceObject("Polyline")
	table.insert(s_DbgDrawLOF_Objects, line)
	line:SetPos(p1)
	line:SetMesh(path)
end

local function AddCollision(pos, clr, power)
	local collision = PlaceObject("Mesh")
	collision:SetMesh(CreateSphereVertices(3*guic, clr))
	table.insert(s_DbgDrawLOF_Objects, collision)
	collision:SetPos(pos)
	if power then
		local text = PlaceObject("Text")
		table.insert(s_DbgDrawLOF_Objects, text)
		text:SetText(string.format("%d", power))
		text:SetPos(pos + point(0, 0, 5 * guim / 100))
		text:SetTextStyle("BugReportScreenshot")
		text:SetColor(clr)
	end
end

local function DbgLOFGetTargets(unit)
	local targets = MapGet("map", {"ExplosiveContainer", "Landmine"}) or {}
	for i = #targets, 1, -1 do
		if targets[i]:IsDead() then
			table.remove(targets, i)
		end
	end
	local enemies = GetEnemies(unit)
	for _, enemy in ipairs(enemies) do
		if not enemy:IsDead() then
			table.insert(targets, enemy)
		end
	end
	table.sortby_field(targets, "handle")
	return targets
end

function DbgDrawLOF(targets, attacker, pos)
	ClearTargetObjects()
	if targets == false then
		return
	end
	if attacker == nil and IsKindOf(SelectedObj, "Unit") then
		attacker = SelectedObj
	end
	if not IsValid(attacker) then
		return
	end
	if IsValid(targets) then
		targets = { targets }
	end
	if not targets and IsKindOf(attacker, "Unit") then
		targets = DbgLOFGetTargets(attacker)
	end
	if not targets or #targets == 0 then
		return
	end
	local default_attack = attacker:GetDefaultAttackAction()
	local weapons
	if default_attack then
		local weapon1, weapon2
		weapon1, weapon2, weapons = default_attack:GetAttackWeapons(attacker)
		weapons = weapons or { weapon1 }
	end

	local lof_params = {
		obj = attacker,
		step_pos = GetPassSlab(pos or attacker) or pos or attacker:GetPos(),
		action_id = default_attack.id,
		weapon = weapons[1],
		stance = attacker.stance,
		prediction = true,
		output_collisions = true,
		group_spots = false,
		can_use_covers = true,
		output_ignored_hits = true,
		force_hit_seen_target = not config.DisableForcedHitSeenTarget,
		ignore_smoke = s_DbgDrawIgnoreSmoke,
		output_all_segments = true,
	}
	if IsKindOf(weapons[1], "FirearmBase") and weapons[1].emplacement_weapon then
		lof_params.emplacement_weapon = true
	end
	
	local attack_dummies = GetAttackDummies(lof_params)

	local all_targets_attack_data = {}

	for attack_dummy_idx, dummy in ipairs(attack_dummies) do
		lof_params.stance = dummy.stance
		lof_params.los_stance = attacker.stance == "Crouch" and "Crouch" or dummy.stance
		lof_params.step_pos = dummy.step_pos
		lof_params.angle = dummy.angle
		lof_params.cone_angle = dummy.cone_angle
		lof_params.can_use_covers = false
		
		for k, weapon in ipairs(weapons) do
			lof_params.weapon = weapon
			local targets_attack_data = GetLoFData(attacker, targets, lof_params)
			all_targets_attack_data[attack_dummy_idx] = all_targets_attack_data[attack_dummy_idx] or targets_attack_data

			for target_idx, target in ipairs(targets) do
				local attack_data = targets_attack_data[target_idx]
				if attack_data and attack_data.lof and not (attack_dummy_idx > 1 and attacker.stance == "Crouch" and all_targets_attack_data[1][target_idx].los == 0) then
					if not attack_data.emplacement_weapon then
						local target_pos = attack_data.target_pos
						if not target_pos then
							local idx = table.find(attack_data.lof, "target_spot", "Torso") or 1
							target_pos = attack_data.lof[1] and attack_data.lof[1].lof_pos2
						end
						local o = DbgPlaceAttackDummy(attacker, attack_data.step_pos, attack_data.angle, attack_data.anim, attack_data.phase, target_pos)
						table.insert(s_DbgDrawLOF_Objects, o)
					end
					for j, line_data in ipairs(attack_data.lof) do
						if (line_data.eye_hit or false) == s_DbgDrawLOF_EYE then
							local pos = line_data.lof_pos1
							AddLine(pos, line_data.attack_pos, clrAttackPos)
							pos = line_data.attack_pos
							local target_pos = line_data.target_pos
							local clrLine = clrClearHit
							for k, collision_data in ipairs(line_data.hits) do
								local hit_pos = collision_data.pos
								AddLine(pos, hit_pos, clrLine)
								if collision_data.ignored then
									if not IsKindOf(collision_data.obj, "SmokeObj") then
										clrLine = clrForcedTargetHit
									end
								else
									clrLine = clrObstructionHit
								end
								local clrHit
								if k == 1 and collision_data.obj == target then
									clrHit = clrCollisionTarget
								elseif collision_data.ignored and IsKindOf(collision_data.obj, "SmokeObj") then
									clrHit = clrClearHit
								elseif k < #line_data.hits then
									clrHit = clrCollisionPierce
								else
									clrHit = clrCollisionStuckPower
								end
								AddCollision(hit_pos, clrHit)
								pos = hit_pos
							end
							if IsCloser(line_data.lof_pos1, pos, target_pos) then
								AddLine(pos, target_pos, clrStuckToTarget)
							end
						end
					end
				end
			end
		end
	end
end

function DbgDrawLOS(targets, attacker)
	ClearTargetObjects()
	if targets == false then
		return
	end
	if attacker == nil and IsKindOf(SelectedObj, "Unit") then
		attacker = SelectedObj
	end
	if IsValid(targets) then
		targets = { targets }
	end
	if targets == nil and attacker and IsKindOf(attacker, "Unit") then
		targets = DbgLOFGetTargets(attacker)
	end
	if not attacker or not targets or #targets == 0 then
		return
	end
	local attacker_pos = GetPassSlab(attacker) or attacker:GetPos()
	local lof_params = {
		obj = attacker,
		step_pos = attacker_pos,
		stance = attacker.stance,
		can_use_covers = true,
	}
	local attack_dummies = GetAttackDummies(lof_params)
	if attacker.stance == "Crouch" then
		attack_dummies = { attack_dummies[1] }
	end
	for i, dummy in ipairs(attack_dummies) do
		if dummy.step_pos ~= attacker_pos then
			local dummy_obj = DbgPlaceAttackDummy(attacker, dummy.step_pos, dummy.angle, dummy.anim, dummy.phase, dummy.target_pos)
			table.insert(s_DbgDrawLOF_Objects, dummy_obj)
		end
	end
	local los = DebugLOS(targets, attacker, -1, attacker.stance)
	for i, line_data in ipairs(los) do
		local start_pos = line_data.pos1
		local target_pos = line_data.pos2
		local clr = line_data.los_level == 1 and const.clrGreen or const.clrBlue
		if line_data.hits and #line_data.hits > 0 then
			local hit_pos = line_data.hits[1].pos
			AddLine(start_pos, hit_pos, clr)
			AddLine(hit_pos, target_pos, clrStuckToTarget)
			for k, collision_data in ipairs(line_data.hits) do
				AddCollision(collision_data.pos, clrStuckToTarget)
			end
		else
			AddLine(start_pos, target_pos, clr)
			AddCollision(target_pos, clr)
		end
	end
end

local function DbgUpdateLOFLines(unit)
	if unit == nil then
		unit = SelectedObj
	end
	if s_DbgDrawLOF then
		if unit and unit == SelectedObj then
			DbgDrawLOF(nil, unit)
		elseif not SelectedObj then
			DbgDrawLOF(false)
		end
	end
	if s_DbgDrawLOS then
		if unit and unit == SelectedObj then
			DbgDrawLOS(nil, unit)
		elseif not SelectedObj then
			DbgDrawLOS(false)
		end
	end
end

OnMsg.UnitMovementDone = DbgUpdateLOFLines
OnMsg.UnitStanceChanged = DbgUpdateLOFLines
OnMsg.SelectionChange = DbgUpdateLOFLines

function DbgDrawToggleLOS()
	PauseInfiniteLoopDetection("DebugLOSVis")
	if s_DbgDrawLOS then
		s_DbgDrawLOS = false
		DbgDrawLOS(false)
	else
		if s_DbgDrawLOF then
			DbgDrawToggleLOF()
		end
		s_DbgDrawLOS = true
		DbgDrawLOS()
	end
	ResumeInfiniteLoopDetection("DebugLOSVis")
end

function DbgDrawToggleLOF()
	PauseInfiniteLoopDetection("DebugLOFVis")
	if s_DbgDrawLOF then
		s_DbgDrawLOF = false
		DbgDrawLOF(false)
	else
		if s_DbgDrawLOS then
			DbgDrawToggleLOS()
		end
		s_DbgDrawLOF = true
		DbgDrawLOF()
	end
	ResumeInfiniteLoopDetection("DebugLOFVis")
end

function DbgDrawLOFNext()
	if s_DbgDrawLOS then
		DbgDrawToggleLOS()
	end
	s_DbgDrawLOF = true
	local targets = DbgLOFGetTargets(SelectedObj)
	s_DbgDrawLastTarget = targets[(table.find(targets, s_DbgDrawLastTarget) or 0) + 1] or targets[1] or false
	DbgDrawLOF(s_DbgDrawLastTarget, SelectedObj)
end

-- will update the visible covers according to the editor's floor filter
local function UpdateCoverDebug()
	if g_dbgCoversShown then
		DbgDrawCovers(nil, nil, "dont_toggle")
	end
end

OnMsg.GameEnterEditor = UpdateCoverDebug
OnMsg.GameExitEditor = UpdateCoverDebug

local target_dummy_color_modifier = RGB(30, 0, 100)
local function ShowTargetDummy(obj)
	if not obj then return end
	obj:SetColorModifier(target_dummy_color_modifier)
	for i, attach in ipairs(obj:GetAttaches()) do
		attach:SetColorModifier(target_dummy_color_modifier)
	end
	obj:SetVisible(true)
	--obj:SetOpacity(80)
end

function DbgDrawShowTargetDummies(show)
	s_DbgDrawTargetDummies = show or false
	if show then
		MapForEach("map", "TargetDummy", ShowTargetDummy)
	else
		MapForEach("map", "TargetDummy", Object.SetVisible, false)
	end
end

function DbgDrawToggleTargetDummies()
	DbgDrawShowTargetDummies(not s_DbgDrawTargetDummies)
end

OnMsg.NewTargetDummy = Object.SetVisible
function OnMsg.NewTargetDummy(obj)
	if s_DbgDrawTargetDummies then
		ShowTargetDummy(obj)
	end
end

function DumpStepVectors()
	local entities = { "EquipmentBarry_Top", "EquipmentLivewire_Top", "Animal_Hyena", "Animal_Crocodile", "Animal_Hen" }
	local filename = string.format("TmpData/AnimStepData.log")
	local f = io.open(filename, "w+")
	local log = {}
	for i, entity in ipairs(entities) do
		local states = GetStates(entity)
		for j, anim in ipairs(states) do
			local step_len = GetStepLength(entity, anim)
			if step_len ~= 0 then
				local duration = GetAnimDuration(entity, anim)
				local compensate = GetAnimCompensate(entity, anim)
				local txt = string.format('Entity "%s", Anim: "%s", Duration: %d, %s, Step Lenght: %d\n', entity, anim, duration, compensate, step_len)
				table.insert(log, txt)
				local last_step = point30
				for phase = 1, duration do
					local step = GetEntityStepVector(entity, anim, 0, phase)
					if step ~= last_step then
						last_step = step
						table.insert(log, string.format("%10d : %5d, %d, %d\n", phase, step:xyz()))
					end
				end
				table.insert(log, "\n")
				f:write(log)
				table.iclear(log)
			end
		end
	end
	f:close()
end