From 0d26fc5b64486d5d5863c5e75b95baeb85e3bf4c Mon Sep 17 00:00:00 2001
From: xoxor4d <45299104+xoxor4d@users.noreply.github.com>
Date: Fri, 11 Feb 2022 22:10:08 +0100
Subject: [PATCH] custom sp mapents, crash fixes, bot support etc
- add custom mapEnts for each SP map (3 dm spawns, glob intermission)
- slightly modify map exporter to export original mapEnts and not the custom ones ^
- move MP-SP logic to module "_Map"
- intercept and skip a _entity visibility_ check on SP maps
- `scr_testclients` now works on SP maps
- fix damage and respawn on SP maps
---
mods/spmod/mapents/ac130.ents | 31 +
mods/spmod/mapents/aftermath.ents | 32 +
mods/spmod/mapents/airlift.ents | 34 +
mods/spmod/mapents/airplane.ents | 34 +
mods/spmod/mapents/ambush.ents | 36 +
mods/spmod/mapents/armada.ents | 30 +
mods/spmod/mapents/blackout.ents | 34 +
mods/spmod/mapents/bog_a.ents | 33 +
mods/spmod/mapents/bog_b.ents | 33 +
mods/spmod/mapents/cargoship.ents | 33 +
mods/spmod/mapents/coup.ents | 34 +
mods/spmod/mapents/hunted.ents | 33 +
mods/spmod/mapents/icbm.ents | 33 +
mods/spmod/mapents/jeepride.ents | 32 +
mods/spmod/mapents/killhouse.ents | 32 +
mods/spmod/mapents/launchfacility_a.ents | 36 +
mods/spmod/mapents/launchfacility_b.ents | 36 +
mods/spmod/mapents/scoutsniper.ents | 34 +
mods/spmod/mapents/sniperescape.ents | 34 +
mods/spmod/mapents/village_assault.ents | 34 +
mods/spmod/mapents/village_defend.ents | 34 +
mods/spmod/maps/aftermath.gsc | 2 +
mods/spmod/maps/coup.gsc | 2 +-
.../maps/mp/gametypes/_callbacksetup.gsc | 8 +-
mods/spmod/maps/mp/gametypes/_globallogic.gsc | 5844 -------------
src/Components/Modules/RB_DrawCollision.cpp | 7446 +++++++++--------
src/Components/Modules/_Common.cpp | 180 +-
src/Components/Modules/_Common.hpp | 30 +-
src/Components/Modules/_Map.cpp | 441 +-
src/Components/Modules/_Map.hpp | 37 +-
src/Components/Modules/_UI.cpp | 2 +-
src/Utils/Entities.cpp | 993 +--
src/Utils/Entities.hpp | 75 +-
src/defines.hpp | 6 +-
src/version.hpp | 2 +-
35 files changed, 5409 insertions(+), 10361 deletions(-)
create mode 100644 mods/spmod/mapents/ac130.ents
create mode 100644 mods/spmod/mapents/aftermath.ents
create mode 100644 mods/spmod/mapents/airlift.ents
create mode 100644 mods/spmod/mapents/airplane.ents
create mode 100644 mods/spmod/mapents/ambush.ents
create mode 100644 mods/spmod/mapents/armada.ents
create mode 100644 mods/spmod/mapents/blackout.ents
create mode 100644 mods/spmod/mapents/bog_a.ents
create mode 100644 mods/spmod/mapents/bog_b.ents
create mode 100644 mods/spmod/mapents/cargoship.ents
create mode 100644 mods/spmod/mapents/coup.ents
create mode 100644 mods/spmod/mapents/hunted.ents
create mode 100644 mods/spmod/mapents/icbm.ents
create mode 100644 mods/spmod/mapents/jeepride.ents
create mode 100644 mods/spmod/mapents/killhouse.ents
create mode 100644 mods/spmod/mapents/launchfacility_a.ents
create mode 100644 mods/spmod/mapents/launchfacility_b.ents
create mode 100644 mods/spmod/mapents/scoutsniper.ents
create mode 100644 mods/spmod/mapents/sniperescape.ents
create mode 100644 mods/spmod/mapents/village_assault.ents
create mode 100644 mods/spmod/mapents/village_defend.ents
delete mode 100644 mods/spmod/maps/mp/gametypes/_globallogic.gsc
diff --git a/mods/spmod/mapents/ac130.ents b/mods/spmod/mapents/ac130.ents
new file mode 100644
index 0000000..a909098
--- /dev/null
+++ b/mods/spmod/mapents/ac130.ents
@@ -0,0 +1,31 @@
+{
+"sundiffusecolor" "0 0 0"
+"suncolor" "0 0 0"
+"classname" "worldspawn"
+"diffusefraction" "0"
+"ambient" "0"
+"sunlight" "0"
+"sundirection" "0 0 0"
+"_color" "0 0 0"
+"contrastgain" "0"
+}
+{
+"angles" "0 235 0"
+"origin" "1302 5030 36"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 130 0"
+"origin" "1636 3603 60"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 40 0"
+"origin" "-443 3421 60"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "349 3295 269"
+"angles" "0 203 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/aftermath.ents b/mods/spmod/mapents/aftermath.ents
new file mode 100644
index 0000000..15b5d2e
--- /dev/null
+++ b/mods/spmod/mapents/aftermath.ents
@@ -0,0 +1,32 @@
+{
+"contrastGain" "0.15"
+"diffusefraction" "0.45"
+"_color" "0.90 0.90 1"
+"sunlight" "1"
+"sundirection" "-35 150 0"
+"sundiffusecolor" "1 1 1"
+"suncolor" "1 1 1"
+"ambient" ".15"
+"reflection_color_correction" "aftermath"
+"classname" "worldspawn"
+}
+{
+"angles" "0 280 0"
+"origin" "-671 8948 696"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 0 0"
+"origin" "-997 8631 699"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 280 0"
+"origin" "-781 9402 692"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "-695 8511 717"
+"angles" "0 90 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/airlift.ents b/mods/spmod/mapents/airlift.ents
new file mode 100644
index 0000000..beaa96d
--- /dev/null
+++ b/mods/spmod/mapents/airlift.ents
@@ -0,0 +1,34 @@
+{
+"bouncefraction" ".7"
+"ambient" ".05"
+"diffusefraction" "0.25"
+"sunlight" "1.5"
+"sundiffusecolor" ".9 0.75 .88"
+"suncolor" "1 0.92 0.88"
+"sundirection" "-17 56.333 0"
+"_color" ".8 .9 1"
+"contrastgain" "0.425"
+"classname" "worldspawn"
+"sunradiosity" "1.5"
+"radiosityscale" "1.2"
+}
+{
+"angles" "0 270 0"
+"origin" "-948 -3807 4"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 70 0"
+"origin" "-1328 -4788 144"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 0 0"
+"origin" "-2190 -4231 -1"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "-1717 -3673 143"
+"angles" "10 297 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/airplane.ents b/mods/spmod/mapents/airplane.ents
new file mode 100644
index 0000000..a1822cf
--- /dev/null
+++ b/mods/spmod/mapents/airplane.ents
@@ -0,0 +1,34 @@
+{
+"ambient" ".03"
+"diffusefraction" "0.01"
+"bouncefraction" "2"
+"sunlight" "1.5"
+"fov_inner" "60"
+"classname" "worldspawn"
+"suncolor" ".9 .9 1"
+"sundiffusecolor" "0.015 0.015 0.013"
+"sundirection" "-18 100 0"
+"_color" ".6 .6 .6"
+"fov_outer" "120"
+"reflection_color_correction" "airplane"
+}
+{
+"angles" "0 264 0"
+"origin" "-758 656 188"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 248 0"
+"origin" "57 606 188"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 270 0"
+"origin" "523 678 188"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "-1166 581 361"
+"angles" "18 334 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/ambush.ents b/mods/spmod/mapents/ambush.ents
new file mode 100644
index 0000000..6ab36fe
--- /dev/null
+++ b/mods/spmod/mapents/ambush.ents
@@ -0,0 +1,36 @@
+{
+"reflection_color_correction" "ambush"
+"sunradiosity" "2"
+"northyaw" "225"
+"sunlight" "1.5"
+"diffusefraction" "0.05"
+"ambient" ".1"
+"bouncefraction" ".1"
+"radiosityscale" "1"
+"contrastgain" "0.15"
+"_color" "0.90 0.90 1"
+"sundirection" "-146 -34 0"
+"sundiffusecolor" ".43 .39 1"
+"suncolor" ".9 .98 1"
+"classname" "worldspawn"
+}
+{
+"angles" "0 125 0"
+"origin" "235 -2102 84"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 125 0"
+"origin" "23 -1730 98"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 295 0"
+"origin" "-558 -1389 78"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "-585 -1408 176"
+"angles" "15 310 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/armada.ents b/mods/spmod/mapents/armada.ents
new file mode 100644
index 0000000..54e2eb0
--- /dev/null
+++ b/mods/spmod/mapents/armada.ents
@@ -0,0 +1,30 @@
+{
+"_color" ".75 .84 1"
+"ambient" "0"
+"sunlight" "1.4"
+"diffusefraction" "0.1"
+"classname" "worldspawn"
+"suncolor" ".85 .76 .53"
+"sundiffusecolor" ".75 .84 1"
+"sundirection" "-30 -340 0"
+}
+{
+"angles" "0 123 0"
+"origin" "2239 26714 70"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 326 0"
+"origin" "1357 27676 2"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 38 0"
+"origin" "611 26246 6"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "2239 26714 70"
+"angles" "0 123 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/blackout.ents b/mods/spmod/mapents/blackout.ents
new file mode 100644
index 0000000..81f740d
--- /dev/null
+++ b/mods/spmod/mapents/blackout.ents
@@ -0,0 +1,34 @@
+{
+"_color" "0.6 0.7 1.000000"
+"script_flag" "blackout_lightswitch_begins"
+"suncolor" "0.75 0.9 1"
+"sundiffusecolor" "0.5 0.6 1"
+"contrastgain" ".25"
+"sunradiosity" "1"
+"ambient" "0.1"
+"diffusefraction" "0.35"
+"sunlight" ".6"
+"sundirection" "-30 42 0"
+"classname" "worldspawn"
+"reflection_color_correction" "hunted"
+}
+{
+"angles" "0 20 0"
+"origin" "-6309 -1935 312"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 295 0"
+"origin" "-5211 -689 276"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 165 0"
+"origin" "-3938 -2000 164"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "-4097 -2369 397"
+"angles" "0 143 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/bog_a.ents b/mods/spmod/mapents/bog_a.ents
new file mode 100644
index 0000000..134ec20
--- /dev/null
+++ b/mods/spmod/mapents/bog_a.ents
@@ -0,0 +1,33 @@
+{
+"northyaw" "90"
+"sunradiosity" ".7"
+"ambient" "0.125"
+"diffusefraction" ".4"
+"sunlight" "0.5"
+"sundirection" "-22 247 0"
+"suncolor" ".68 .72 .76"
+"sundiffusecolor" "0.717647 0.776471 1.000000"
+"_color" ".67 .72 .94"
+"radiosityscale" "1.8"
+"classname" "worldspawn"
+}
+{
+"angles" "0 250 0"
+"origin" "7613 2471 109"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 306 0"
+"origin" "6559 2269 109"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 244 0"
+"origin" "7341 1769 109"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "7328 1154 263"
+"angles" "12 81 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/bog_b.ents b/mods/spmod/mapents/bog_b.ents
new file mode 100644
index 0000000..9eaef88
--- /dev/null
+++ b/mods/spmod/mapents/bog_b.ents
@@ -0,0 +1,33 @@
+{
+"sundiffusecolor" ".9 0.88 .75"
+"suncolor" "1 0.92 0.88"
+"classname" "worldspawn"
+"northyaw" "180"
+"sunlight" "1"
+"ambient" ".1"
+"bouncefraction" ".7"
+"diffusefraction" "0.5"
+"sundirection" "-50 136 0"
+"_color" ".8 .9 1"
+"contrastgain" "0.425"
+}
+{
+"angles" "0 196 0"
+"origin" "5335 -3159 -12"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 310 0"
+"origin" "4291 -2681 -4"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 334 0"
+"origin" "3392 -2771 -63"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "4273 -3439 -56"
+"angles" "0 50 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/cargoship.ents b/mods/spmod/mapents/cargoship.ents
new file mode 100644
index 0000000..230ffcd
--- /dev/null
+++ b/mods/spmod/mapents/cargoship.ents
@@ -0,0 +1,33 @@
+{
+"reflection_color_correction" "cargoship"
+"sunlight" "1"
+"diffusefraction" "0.15"
+"ambient" ".1"
+"radiosityscale" "2"
+"sundiffusecolor" "0.5 0.5 0.7"
+"contrastgain" "0.15"
+"_color" "0.5 0.5 0.7"
+"sundirection" "-25 315 0"
+"suncolor" "0.5 0.6 1.000000"
+"classname" "worldspawn"
+}
+{
+"angles" "0 181 0"
+"origin" "1074 5 236"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 41 0"
+"origin" "-1140 -277 76"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 317 0"
+"origin" "-2133 478 76"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "-2115 468 289"
+"angles" "11 334 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/coup.ents b/mods/spmod/mapents/coup.ents
new file mode 100644
index 0000000..bd0d49f
--- /dev/null
+++ b/mods/spmod/mapents/coup.ents
@@ -0,0 +1,34 @@
+{
+"reflection_color_correction" "coup"
+"diffusefraction" "0.2"
+"ambient" ".02"
+"radiosity" "1"
+"sunlight" "1.1"
+"sundirection" "-40 72 0"
+"sundiffusecolor" "1 .95 .8"
+"suncolor" "1 1 .85"
+"_color" "0 1 .5"
+"bouncefracion" ".1"
+"sunradiosity" "2.5"
+"classname" "worldspawn"
+}
+{
+"angles" "0 90 0"
+"origin" "67 -549 60"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 0 0"
+"origin" "-305 548 52"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 200 0"
+"origin" "741 671 41"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "67 -549 60"
+"angles" "0 90 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/hunted.ents b/mods/spmod/mapents/hunted.ents
new file mode 100644
index 0000000..be339d2
--- /dev/null
+++ b/mods/spmod/mapents/hunted.ents
@@ -0,0 +1,33 @@
+{
+"suncolor" ".75 .9 1"
+"sundiffusecolor" ".5 .6 1"
+"contrastgain" ".25"
+"diffusefraction" ".35"
+"ambient" "0.2"
+"sunlight" "0.6"
+"reflection_color_correction" "hunted"
+"_color" "0.6 0.7 1"
+"sundirection" "-40 49 0"
+"sunradiosity" "1.2"
+"classname" "worldspawn"
+}
+{
+"angles" "0 327 0"
+"origin" "1544 3067 108"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 282 0"
+"origin" "2495 3260 112"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 186 0"
+"origin" "3257 2935 109"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "2721 3170 255"
+"angles" "0 233 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/icbm.ents b/mods/spmod/mapents/icbm.ents
new file mode 100644
index 0000000..49e5411
--- /dev/null
+++ b/mods/spmod/mapents/icbm.ents
@@ -0,0 +1,33 @@
+{
+"suncolor" "1 1 1"
+"sundiffusecolor" "1 1 1"
+"contrastgain" ".5"
+"sunradiosity" "1.2"
+"brightness" "0.1"
+"diffusefraction" "0.6"
+"ambient" "0.1"
+"sunlight" "1"
+"sundirection" "-32.5 -4 0"
+"_color" "1 1 1"
+"classname" "worldspawn"
+}
+{
+"angles" "0 275 0"
+"origin" "146 -10371 4"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 198 0"
+"origin" "1562 -10763 44"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 115 0"
+"origin" "1284 -11955 -4"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "1526 -11587 129"
+"angles" "0 130 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/jeepride.ents b/mods/spmod/mapents/jeepride.ents
new file mode 100644
index 0000000..fa47143
--- /dev/null
+++ b/mods/spmod/mapents/jeepride.ents
@@ -0,0 +1,32 @@
+{
+"reflection_color_correction" "jeepride"
+"diffusefraction" ".1"
+"ambient" "0.1"
+"sunlight" "1.7"
+"sundirection" "-25 231 0"
+"suncolor" "0.996078 0.96 0.88"
+"sundiffusecolor" "0.717647 0.776471 1.000000"
+"northyaw" "90"
+"_color" "0.717647 0.776471 1.000000"
+"classname" "worldspawn"
+}
+{
+"angles" "0 233 0"
+"origin" "-16032 1298 523"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 141 0"
+"origin" "-14570 911 524"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 92 0"
+"origin" "-15172 -1019 524"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "-16103 273 707"
+"angles" "0 193 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/killhouse.ents b/mods/spmod/mapents/killhouse.ents
new file mode 100644
index 0000000..835766d
--- /dev/null
+++ b/mods/spmod/mapents/killhouse.ents
@@ -0,0 +1,32 @@
+{
+"ambient" "0.1"
+"diffusefraction" ".35"
+"sunlight" "1.5"
+"sundirection" "-40 290 0"
+"suncolor" "1 .95 0.8"
+"sundiffusecolor" ".8 .85 1"
+"northyaw" "90"
+"_color" ".85 .9 1"
+"classname" "worldspawn"
+"reflection_color_correction" "daylight"
+}
+{
+"angles" "0 229 0"
+"origin" "340 -152 224"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 38 0"
+"origin" "-438 -997 64"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 58 0"
+"origin" "-190 -2242 58"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "438 -2258 229"
+"angles" "0 229 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/launchfacility_a.ents b/mods/spmod/mapents/launchfacility_a.ents
new file mode 100644
index 0000000..507d03a
--- /dev/null
+++ b/mods/spmod/mapents/launchfacility_a.ents
@@ -0,0 +1,36 @@
+{
+"ambient" "0.2"
+"bouncefraction" "0.7"
+"diffusefraction" "0.4"
+"sunlight" "1.5"
+"spawnflags" "1024"
+"radiosityscale" "1.2"
+"sunradiosity" "1.3"
+"brightness" "0.1"
+"reflection_color_correction" "launchfacility_a"
+"_color" "0.02 0.02 .05"
+"sundirection" "-30 0 0"
+"sundiffusecolor" "0.717647 0.776471 1.000000"
+"suncolor" "1 .8 .6"
+"classname" "worldspawn"
+}
+{
+"angles" "0 328 0"
+"origin" "-10112 -12320 -909"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 192 0"
+"origin" "-8981 -12235 -981"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 316 0"
+"origin" "-10145 -13394 -942"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "-9684 -15154 -791"
+"angles" "0 326 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/launchfacility_b.ents b/mods/spmod/mapents/launchfacility_b.ents
new file mode 100644
index 0000000..91ff896
--- /dev/null
+++ b/mods/spmod/mapents/launchfacility_b.ents
@@ -0,0 +1,36 @@
+{
+"sunradiosity" "1.2"
+"sunisprimarylight" "0"
+"reflection_color_correction" "launchfacility_b"
+"sunlight" "0.8"
+"diffusefraction" "0.2"
+"ambient" "0.1"
+"bouncefraction" ".2"
+"radiosityscale" "1"
+"contrastgain" "0.5"
+"_color" "1 1 1"
+"sundirection" "-35 290 0"
+"sundiffusecolor" "1 1 1"
+"suncolor" "0.95 0.85 0.65"
+"classname" "worldspawn"
+}
+{
+"angles" "0 312 0"
+"origin" "-49 3405 -284"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 132 0"
+"origin" "1193 2150 -284"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 135 0"
+"origin" "396 2053 -285"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "-54 2638 -289"
+"angles" "292 264 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/scoutsniper.ents b/mods/spmod/mapents/scoutsniper.ents
new file mode 100644
index 0000000..b828656
--- /dev/null
+++ b/mods/spmod/mapents/scoutsniper.ents
@@ -0,0 +1,34 @@
+{
+"origin" "0 0 0"
+"sundiffusecolor" "0.466667 0.525490 0.658824"
+"suncolor" "1.000000 0.976471 0.874510"
+"classname" "worldspawn"
+"sundirection" "-30 -56 0"
+"diffusefraction" ".08"
+"ambient" ".2"
+"sunlight" "1.7"
+"_color" "0.466667 0.525490 0.658824"
+"northyaw" "144"
+"radiosityscale" "1.8"
+"sunradiosity" "1.7"
+}
+{
+"angles" "0 64 0"
+"origin" "11389 5752 -18"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 256 0"
+"origin" "11225 6295 -18"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 75 0"
+"origin" "11200 5074 118"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "12853 5749 -95"
+"angles" "356 138 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/sniperescape.ents b/mods/spmod/mapents/sniperescape.ents
new file mode 100644
index 0000000..3127cc2
--- /dev/null
+++ b/mods/spmod/mapents/sniperescape.ents
@@ -0,0 +1,34 @@
+{
+"sundiffusecolor" ".9 .92 1"
+"suncolor" ".9 .98 1"
+"classname" "worldspawn"
+"sunlight" ".9"
+"diffusefraction" "0.25"
+"bouncefraction" ".2"
+"ambient" "0"
+"sundirection" "-155 -50 0"
+"_color" "0.90 0.90 1"
+"contrastgain" "0.15"
+"sunradiosity" "2"
+"radiosityscale" "1"
+}
+{
+"angles" "0 149 0"
+"origin" "-2548 -2388 156"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 92 0"
+"origin" "-4380 -2392 57"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 358 0"
+"origin" "-5141 -955 104"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "-4287 -1761 92"
+"angles" "337 87 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/village_assault.ents b/mods/spmod/mapents/village_assault.ents
new file mode 100644
index 0000000..d61b82f
--- /dev/null
+++ b/mods/spmod/mapents/village_assault.ents
@@ -0,0 +1,34 @@
+{
+"sunradiosity" "1.2"
+"sunisprimarylight" "0"
+"suncolor" "0.8 0.9 1"
+"sundiffusecolor" "0.4 0.6 1"
+"contrastgain" ".5"
+"brightness" ".1"
+"sunlight" ".8"
+"sundirection" "-30 91 0"
+"ambient" "0.1"
+"diffusefraction" "0.6"
+"_color" "0.717647 0.776471 1.000000"
+"classname" "worldspawn"
+}
+{
+"angles" "0 174 0"
+"origin" "841 1508 586"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 264 0"
+"origin" "-128 2108 830"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 267 0"
+"origin" "-1402 1536 590"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "-1851 940 693"
+"angles" "12 317 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/mapents/village_defend.ents b/mods/spmod/mapents/village_defend.ents
new file mode 100644
index 0000000..e9b2f84
--- /dev/null
+++ b/mods/spmod/mapents/village_defend.ents
@@ -0,0 +1,34 @@
+{
+"reflection_color_correction" "village_daylight"
+"sundiffusecolor" "0.717647 0.776471 1.000000"
+"suncolor" "0.996078 0.921569 0.9"
+"classname" "worldspawn"
+"_color" "0.717647 0.776471 .9"
+"diffusefraction" ".2"
+"ambient" "0.04"
+"northyaw" "90"
+"sundirection" "-40 225 0"
+"sunlight" "1.6"
+"radiosityscale" "1.5"
+"sunradiosity" "2"
+}
+{
+"angles" "0 237 0"
+"origin" "1908 675 261"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 212 0"
+"origin" "459 724 374"
+"classname" "mp_dm_spawn"
+}
+{
+"angles" "0 109 0"
+"origin" "229 -788 163"
+"classname" "mp_dm_spawn"
+}
+{
+"origin" "249 -602 453"
+"angles" "0 108 0"
+"classname" "mp_global_intermission"
+}
\ No newline at end of file
diff --git a/mods/spmod/maps/aftermath.gsc b/mods/spmod/maps/aftermath.gsc
index d187b89..34072bf 100644
--- a/mods/spmod/maps/aftermath.gsc
+++ b/mods/spmod/maps/aftermath.gsc
@@ -4,4 +4,6 @@ main()
maps\mp\_load::main();
maps\aftermath_fx::main();
+
+ VisionSetNaked( "aftermath", 0 );
}
diff --git a/mods/spmod/maps/coup.gsc b/mods/spmod/maps/coup.gsc
index 6f13bfe..73778c1 100644
--- a/mods/spmod/maps/coup.gsc
+++ b/mods/spmod/maps/coup.gsc
@@ -10,7 +10,7 @@ main()
precachemodel( "com_spray_can01" );
maps\mp\_load::main();
- maps\coup_fx::main();0
+ maps\coup_fx::main();
setExpFog(0, 2610.72, 0.531857, 0.529929, 0.474802, 0);
VisionSetNaked( "coup", 0 );
diff --git a/mods/spmod/maps/mp/gametypes/_callbacksetup.gsc b/mods/spmod/maps/mp/gametypes/_callbacksetup.gsc
index 314b42d..c90e43e 100644
--- a/mods/spmod/maps/mp/gametypes/_callbacksetup.gsc
+++ b/mods/spmod/maps/mp/gametypes/_callbacksetup.gsc
@@ -56,7 +56,7 @@ self is the player that took damage.
CodeCallback_PlayerDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset)
{
self endon("disconnect");
- //[[level.callbackPlayerDamage]](eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset);
+ [[level.callbackPlayerDamage]](eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset);
}
/*================
@@ -66,7 +66,7 @@ self is the player that was killed.
CodeCallback_PlayerKilled(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration)
{
self endon("disconnect");
- //[[level.callbackPlayerKilled]](eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration);
+ [[level.callbackPlayerKilled]](eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration);
}
/*================
@@ -117,7 +117,7 @@ Called when a gametype is not supported.
================*/
AbortLevel()
{
- /* println("Aborting level - gametype is not supported");
+ println("USE GAMETYPE DM!");
level.callbackStartGameType = ::callbackVoid;
level.callbackPlayerConnect = ::callbackVoid;
@@ -128,7 +128,7 @@ AbortLevel()
setdvar("g_gametype", "dm");
- exitLevel(false); */
+ //exitLevel(false);
}
/*================
diff --git a/mods/spmod/maps/mp/gametypes/_globallogic.gsc b/mods/spmod/maps/mp/gametypes/_globallogic.gsc
deleted file mode 100644
index 79558c2..0000000
--- a/mods/spmod/maps/mp/gametypes/_globallogic.gsc
+++ /dev/null
@@ -1,5844 +0,0 @@
-#include maps\mp\_utility;
-#include maps\mp\gametypes\_hud_util;
-#include common_scripts\utility;
-
-init()
-{
- // hack to allow maps with no scripts to run correctly
- if ( !isDefined( level.tweakablesInitialized ) )
- maps\mp\gametypes\_tweakables::init();
-
- if ( getDvar( "scr_player_sprinttime" ) == "" )
- setDvar( "scr_player_sprinttime", getDvar( "player_sprintTime" ) );
-
- level.splitscreen = isSplitScreen();
- level.xenon = (getdvar("xenonGame") == "true");
- level.ps3 = (getdvar("ps3Game") == "true");
- level.onlineGame = true;
- level.console = (level.xenon || level.ps3);
-
- level.rankedMatch = ( level.onlineGame && getDvarInt( "sv_pure" ) );
- /#
- if ( getdvarint( "scr_forcerankedmatch" ) == 1 )
- level.rankedMatch = true;
- #/
-
- level.script = toLower( getDvar( "mapname" ) );
- level.gametype = toLower( getDvar( "g_gametype" ) );
-
- level.otherTeam["allies"] = "axis";
- level.otherTeam["axis"] = "allies";
-
- level.teamBased = false;
-
- level.overrideTeamScore = false;
- level.overridePlayerScore = false;
- level.displayHalftimeText = false;
- level.displayRoundEndText = true;
-
- level.endGameOnScoreLimit = true;
- level.endGameOnTimeLimit = true;
-
- precacheString( &"MP_HALFTIME" );
- precacheString( &"MP_OVERTIME" );
- precacheString( &"MP_ROUNDEND" );
- precacheString( &"MP_INTERMISSION" );
- precacheString( &"MP_SWITCHING_SIDES" );
- precacheString( &"MP_FRIENDLY_FIRE_WILL_NOT" );
-
- if ( level.splitScreen )
- precacheString( &"MP_ENDED_GAME" );
- else
- precacheString( &"MP_HOST_ENDED_GAME" );
-
- level.halftimeType = "halftime";
- level.halftimeSubCaption = &"MP_SWITCHING_SIDES";
-
- level.lastStatusTime = 0;
- level.wasWinning = "none";
-
- level.lastSlowProcessFrame = 0;
-
- level.placement["allies"] = [];
- level.placement["axis"] = [];
- level.placement["all"] = [];
-
- level.postRoundTime = 8.0;
-
- level.inOvertime = false;
-
- level.dropTeam = getdvarint( "sv_maxclients" );
-
- registerDvars();
- maps\mp\gametypes\_class::initPerkDvars();
-
- level.oldschool = ( getDvarInt( "scr_oldschool" ) == 1 );
- if ( level.oldschool )
- {
- logString( "game mode: oldschool" );
-
- setDvar( "jump_height", 64 );
- setDvar( "jump_slowdownEnable", 0 );
- setDvar( "bg_fallDamageMinHeight", 256 );
- setDvar( "bg_fallDamageMaxHeight", 512 );
- }
-
- precacheModel( "vehicle_mig29_desert" );
- precacheModel( "projectile_cbu97_clusterbomb" );
- precacheModel( "tag_origin" );
-
- precacheShader( "faction_128_usmc" );
- precacheShader( "faction_128_arab" );
- precacheShader( "faction_128_ussr" );
- precacheShader( "faction_128_sas" );
-
- level.fx_airstrike_afterburner = loadfx ("fire/jet_afterburner");
- level.fx_airstrike_contrail = loadfx ("smoke/jet_contrail");
-
- if ( !isDefined( game["tiebreaker"] ) )
- game["tiebreaker"] = false;
-}
-
-registerDvars()
-{
- if ( getdvar( "scr_oldschool" ) == "" )
- setdvar( "scr_oldschool", "0" );
-
- makeDvarServerInfo( "scr_oldschool" );
-
- setDvar( "ui_bomb_timer", 0 );
- makeDvarServerInfo( "ui_bomb_timer" );
-
-
- if ( getDvar( "scr_show_unlock_wait" ) == "" )
- setDvar( "scr_show_unlock_wait", 0.1 );
-
- if ( getDvar( "scr_intermission_time" ) == "" )
- setDvar( "scr_intermission_time", 30.0 );
-}
-
-SetupCallbacks()
-{
- level.spawnPlayer = ::spawnPlayer;
- level.spawnClient = ::spawnClient;
- level.spawnSpectator = ::spawnSpectator;
- level.spawnIntermission = ::spawnIntermission;
- level.onPlayerScore = ::default_onPlayerScore;
- level.onTeamScore = ::default_onTeamScore;
-
- level.onXPEvent = ::onXPEvent;
- level.waveSpawnTimer = ::waveSpawnTimer;
-
- level.onSpawnPlayer = ::blank;
- level.onSpawnSpectator = ::default_onSpawnSpectator;
- level.onSpawnIntermission = ::default_onSpawnIntermission;
- level.onRespawnDelay = ::blank;
-
- level.onForfeit = ::default_onForfeit;
- level.onTimeLimit = ::default_onTimeLimit;
- level.onScoreLimit = ::default_onScoreLimit;
- level.onDeadEvent = ::default_onDeadEvent;
- level.onOneLeftEvent = ::default_onOneLeftEvent;
- level.giveTeamScore = ::giveTeamScore;
- level.givePlayerScore = ::givePlayerScore;
-
- level._setTeamScore = ::_setTeamScore;
- level._setPlayerScore = ::_setPlayerScore;
-
- level._getTeamScore = ::_getTeamScore;
- level._getPlayerScore = ::_getPlayerScore;
-
- level.onPrecacheGametype = ::blank;
- level.onStartGameType = ::blank;
- level.onPlayerConnect = ::blank;
- level.onPlayerDisconnect = ::blank;
- level.onPlayerDamage = ::blank;
- level.onPlayerKilled = ::blank;
-
- level.onEndGame = ::blank;
-
- level.autoassign = ::menuAutoAssign;
- level.spectator = ::menuSpectator;
- level.class = ::menuClass;
- level.allies = ::menuAllies;
- level.axis = ::menuAxis;
-}
-
-
-// to be used with things that are slow.
-// unfortunately, it can only be used with things that aren't time critical.
-WaitTillSlowProcessAllowed()
-{
- // wait only a few frames if necessary
- // if we wait too long, we might get too many threads at once and run out of variables
- // i'm trying to avoid using a loop because i don't want any extra variables
- if ( level.lastSlowProcessFrame == gettime() )
- {
- wait .05;
- if ( level.lastSlowProcessFrame == gettime() )
- {
- wait .05;
- if ( level.lastSlowProcessFrame == gettime() )
- {
- wait .05;
- if ( level.lastSlowProcessFrame == gettime() )
- {
- wait .05;
- }
- }
- }
- }
-
- level.lastSlowProcessFrame = gettime();
-}
-
-
-blank( arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 )
-{
-}
-
-// when a team leaves completely, that team forfeited, team left wins round, ends game
-default_onForfeit( team )
-{
- level notify ( "forfeit in progress" ); //ends all other forfeit threads attempting to run
- level endon( "forfeit in progress" ); //end if another forfeit thread is running
- level endon( "abort forfeit" ); //end if the team is no longer in forfeit status
-
- // in 1v1 DM, give players time to change teams
- if ( !level.teambased && level.players.size > 1 )
- wait 10;
-
- forfeit_delay = 20.0; //forfeit wait, for switching teams and such
-
- announcement( game["strings"]["opponent_forfeiting_in"], forfeit_delay );
- wait (10.0);
- announcement( game["strings"]["opponent_forfeiting_in"], 10.0 );
- wait (10.0);
-
- endReason = &"";
- if ( !isDefined( team ) )
- {
- setDvar( "ui_text_endreason", game["strings"]["players_forfeited"] );
- endReason = game["strings"]["players_forfeited"];
- winner = level.players[0];
- }
- else if ( team == "allies" )
- {
- setDvar( "ui_text_endreason", game["strings"]["allies_forfeited"] );
- endReason = game["strings"]["allies_forfeited"];
- winner = "axis";
- }
- else if ( team == "axis" )
- {
- setDvar( "ui_text_endreason", game["strings"]["axis_forfeited"] );
- endReason = game["strings"]["axis_forfeited"];
- winner = "allies";
- }
- else
- {
- //shouldn't get here
- assertEx( isdefined( team ), "Forfeited team is not defined" );
- assertEx( 0, "Forfeited team " + team + " is not allies or axis" );
- winner = "tie";
- }
- //exit game, last round, no matter if round limit reached or not
- level.forcedEnd = true;
-
- if ( isPlayer( winner ) )
- logString( "forfeit, win: " + winner getXuid() + "(" + winner.name + ")" );
- else
- logString( "forfeit, win: " + winner + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] );
- thread endGame( winner, endReason );
-}
-
-
-default_onDeadEvent( team )
-{
- if ( team == "allies" )
- {
- iPrintLn( game["strings"]["allies_eliminated"] );
- makeDvarServerInfo( "ui_text_endreason", game["strings"]["allies_eliminated"] );
- setDvar( "ui_text_endreason", game["strings"]["allies_eliminated"] );
-
- logString( "team eliminated, win: opfor, allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] );
-
- thread endGame( "axis", game["strings"]["allies_eliminated"] );
- }
- else if ( team == "axis" )
- {
- iPrintLn( game["strings"]["axis_eliminated"] );
- makeDvarServerInfo( "ui_text_endreason", game["strings"]["axis_eliminated"] );
- setDvar( "ui_text_endreason", game["strings"]["axis_eliminated"] );
-
- logString( "team eliminated, win: allies, allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] );
-
- thread endGame( "allies", game["strings"]["axis_eliminated"] );
- }
- else
- {
- makeDvarServerInfo( "ui_text_endreason", game["strings"]["tie"] );
- setDvar( "ui_text_endreason", game["strings"]["tie"] );
-
- logString( "tie, allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] );
-
- if ( level.teamBased )
- thread endGame( "tie", game["strings"]["tie"] );
- else
- thread endGame( undefined, game["strings"]["tie"] );
- }
-}
-
-
-default_onOneLeftEvent( team )
-{
- if ( !level.teamBased )
- {
- winner = getHighestScoringPlayer();
-
- if ( isDefined( winner ) )
- logString( "last one alive, win: " + winner.name );
- else
- logString( "last one alive, win: unknown" );
-
- thread endGame( winner, &"MP_ENEMIES_ELIMINATED" );
- }
- else
- {
- for ( index = 0; index < level.players.size; index++ )
- {
- player = level.players[index];
-
- if ( !isAlive( player ) )
- continue;
-
- if ( !isDefined( player.pers["team"] ) || player.pers["team"] != team )
- continue;
-
- player maps\mp\gametypes\_globallogic::leaderDialogOnPlayer( "last_alive" );
- }
- }
-}
-
-
-default_onTimeLimit()
-{
- winner = undefined;
-
- if ( level.teamBased )
- {
- if ( game["teamScores"]["allies"] == game["teamScores"]["axis"] )
- winner = "tie";
- else if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] )
- winner = "axis";
- else
- winner = "allies";
-
- logString( "time limit, win: " + winner + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] );
- }
- else
- {
- winner = getHighestScoringPlayer();
-
- if ( isDefined( winner ) )
- logString( "time limit, win: " + winner.name );
- else
- logString( "time limit, tie" );
- }
-
- // i think these two lines are obsolete
- makeDvarServerInfo( "ui_text_endreason", game["strings"]["time_limit_reached"] );
- setDvar( "ui_text_endreason", game["strings"]["time_limit_reached"] );
-
- thread endGame( winner, game["strings"]["time_limit_reached"] );
-}
-
-
-forceEnd()
-{
- if ( level.hostForcedEnd || level.forcedEnd )
- return;
-
- winner = undefined;
-
- if ( level.teamBased )
- {
- if ( game["teamScores"]["allies"] == game["teamScores"]["axis"] )
- winner = "tie";
- else if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] )
- winner = "axis";
- else
- winner = "allies";
- logString( "host ended game, win: " + winner + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] );
- }
- else
- {
- winner = getHighestScoringPlayer();
- if ( isDefined( winner ) )
- logString( "host ended game, win: " + winner.name );
- else
- logString( "host ended game, tie" );
- }
-
- level.forcedEnd = true;
- level.hostForcedEnd = true;
-
- if ( level.splitscreen )
- endString = &"MP_ENDED_GAME";
- else
- endString = &"MP_HOST_ENDED_GAME";
-
- makeDvarServerInfo( "ui_text_endreason", endString );
- setDvar( "ui_text_endreason", endString );
- thread endGame( winner, endString );
-}
-
-
-default_onScoreLimit()
-{
- if ( !level.endGameOnScoreLimit )
- return;
-
- winner = undefined;
-
- if ( level.teamBased )
- {
- if ( game["teamScores"]["allies"] == game["teamScores"]["axis"] )
- winner = "tie";
- else if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] )
- winner = "axis";
- else
- winner = "allies";
- logString( "scorelimit, win: " + winner + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] );
- }
- else
- {
- winner = getHighestScoringPlayer();
- if ( isDefined( winner ) )
- logString( "scorelimit, win: " + winner.name );
- else
- logString( "scorelimit, tie" );
- }
-
- makeDvarServerInfo( "ui_text_endreason", game["strings"]["score_limit_reached"] );
- setDvar( "ui_text_endreason", game["strings"]["score_limit_reached"] );
-
- level.forcedEnd = true; // no more rounds if scorelimit is hit
- thread endGame( winner, game["strings"]["score_limit_reached"] );
-}
-
-
-updateGameEvents()
-{
- if ( level.rankedMatch && !level.inGracePeriod )
- {
- if ( level.teamBased )
- {
- // if allies disconnected, and axis still connected, axis wins round and game ends to lobby
- if ( (level.everExisted["allies"] || level.console) && level.playerCount["allies"] < 1 && level.playerCount["axis"] > 0 && game["state"] == "playing" )
- {
- //allies forfeited
- thread [[level.onForfeit]]( "allies" );
- return;
- }
-
- // if axis disconnected, and allies still connected, allies wins round and game ends to lobby
- if ( (level.everExisted["axis"] || level.console) && level.playerCount["axis"] < 1 && level.playerCount["allies"] > 0 && game["state"] == "playing" )
- {
- //axis forfeited
- thread [[level.onForfeit]]( "axis" );
- return;
- }
-
- if ( level.playerCount["axis"] > 0 && level.playerCount["allies"] > 0 )
- level notify( "abort forfeit" );
- }
- else
- {
- if ( level.playerCount["allies"] + level.playerCount["axis"] == 1 && level.maxPlayerCount > 1 )
- {
- thread [[level.onForfeit]]();
- return;
- }
-
- if ( level.playerCount["axis"] + level.playerCount["allies"] > 1 )
- level notify( "abort forfeit" );
- }
- }
-
- if ( !level.numLives && !level.inOverTime )
- return;
-
- if ( level.inGracePeriod )
- return;
-
- if ( level.teamBased )
- {
- // if both allies and axis were alive and now they are both dead in the same instance
- if ( level.everExisted["allies"] && !level.aliveCount["allies"] && level.everExisted["axis"] && !level.aliveCount["axis"] && !level.playerLives["allies"] && !level.playerLives["axis"] )
- {
- [[level.onDeadEvent]]( "all" );
- return;
- }
-
- // if allies were alive and now they are not
- if ( level.everExisted["allies"] && !level.aliveCount["allies"] && !level.playerLives["allies"] )
- {
- [[level.onDeadEvent]]( "allies" );
- return;
- }
-
- // if axis were alive and now they are not
- if ( level.everExisted["axis"] && !level.aliveCount["axis"] && !level.playerLives["axis"] )
- {
- [[level.onDeadEvent]]( "axis" );
- return;
- }
-
- // one ally left
- if ( level.lastAliveCount["allies"] > 1 && level.aliveCount["allies"] == 1 && level.playerLives["allies"] == 1 )
- {
- [[level.onOneLeftEvent]]( "allies" );
- return;
- }
-
- // one axis left
- if ( level.lastAliveCount["axis"] > 1 && level.aliveCount["axis"] == 1 && level.playerLives["axis"] == 1 )
- {
- [[level.onOneLeftEvent]]( "axis" );
- return;
- }
- }
- else
- {
- // everyone is dead
- if ( (!level.aliveCount["allies"] && !level.aliveCount["axis"]) && (!level.playerLives["allies"] && !level.playerLives["axis"]) && level.maxPlayerCount > 1 )
- {
- [[level.onDeadEvent]]( "all" );
- return;
- }
-
- // last man standing
- if ( (level.aliveCount["allies"] + level.aliveCount["axis"] == 1) && (level.playerLives["allies"] + level.playerLives["axis"] == 1) && level.maxPlayerCount > 1 )
- {
- [[level.onOneLeftEvent]]( "all" );
- return;
- }
- }
-}
-
-
-matchStartTimer()
-{
- visionSetNaked( "mpIntro", 0 );
-
- matchStartText = createServerFontString( "objective", 1.5 );
- matchStartText setPoint( "CENTER", "CENTER", 0, -20 );
- matchStartText.sort = 1001;
- matchStartText setText( game["strings"]["waiting_for_teams"] );
- matchStartText.foreground = false;
- matchStartText.hidewheninmenu = true;
-
- matchStartTimer = createServerTimer( "objective", 1.4 );
- matchStartTimer setPoint( "CENTER", "CENTER", 0, 0 );
- matchStartTimer setTimer( level.prematchPeriod );
- matchStartTimer.sort = 1001;
- matchStartTimer.foreground = false;
- matchStartTimer.hideWhenInMenu = true;
-
- waitForPlayers( level.prematchPeriod );
-
- if ( level.prematchPeriodEnd > 0 )
- {
- matchStartText setText( game["strings"]["match_starting_in"] );
- matchStartTimer setTimer( level.prematchPeriodEnd );
-
- wait level.prematchPeriodEnd;
- }
-
- visionSetNaked( getDvar( "mapname" ), 2.0 );
-
- matchStartText destroyElem();
- matchStartTimer destroyElem();
-}
-
-matchStartTimerSkip()
-{
- visionSetNaked( getDvar( "mapname" ), 0 );
-}
-
-
-spawnPlayer()
-{
- prof_begin( "spawnPlayer_preUTS" );
-
- self endon("disconnect");
- self endon("joined_spectators");
- self notify("spawned");
- self notify("end_respawn");
-
- self setSpawnVariables();
-
- if ( level.teamBased )
- self.sessionteam = self.team;
- else
- self.sessionteam = "none";
-
- hadSpawned = self.hasSpawned;
-
- self.sessionstate = "playing";
- self.spectatorclient = -1;
- self.killcamentity = -1;
- self.archivetime = 0;
- self.psoffsettime = 0;
- self.statusicon = "";
- if ( level.hardcoreMode )
- self.maxhealth = 30;
- else if ( level.oldschool )
- self.maxhealth = 200;
- else
- self.maxhealth = 100;
- self.health = self.maxhealth;
-
- self.friendlydamage = undefined;
- self.hasSpawned = true;
- self.spawnTime = getTime();
- self.afk = false;
- if ( self.pers["lives"] )
- self.pers["lives"]--;
- self.lastStand = undefined;
-
- if ( !self.wasAliveAtMatchStart )
- {
- acceptablePassedTime = 20;
- if ( level.timeLimit > 0 && acceptablePassedTime < level.timeLimit * 60 / 4 )
- acceptablePassedTime = level.timeLimit * 60 / 4;
-
- if ( level.inGracePeriod || getTimePassed() < acceptablePassedTime * 1000 )
- self.wasAliveAtMatchStart = true;
- }
-
- //self clearPerks();
-
- //self setClientDvar( "cg_thirdPerson", "0" );
- //self setDepthOfField( 0, 0, 512, 512, 4, 0 );
- //self setClientDvar( "cg_fov", "65" );
-
- [[level.onSpawnPlayer]]();
-
- self maps\mp\gametypes\_missions::playerSpawned();
-
- prof_end( "spawnPlayer_preUTS" );
-
- level thread updateTeamStatus();
-
- prof_begin( "spawnPlayer_postUTS" );
-
- if ( level.oldschool )
- {
- assert( !isDefined( self.class ) );
- self maps\mp\gametypes\_oldschool::giveLoadout();
- self maps\mp\gametypes\_class::setClass( level.defaultClass );
- }
- else
- {
- assert( isValidClass( self.class ) );
-
- self maps\mp\gametypes\_class::setClass( self.class );
- self maps\mp\gametypes\_class::giveLoadout( self.team, self.class );
- }
-
- if ( level.inPrematchPeriod )
- {
- self freezeControls( true );
- //self disableWeapons();
-
- self setClientDvar( "scr_objectiveText", getObjectiveHintText( self.pers["team"] ) );
-
- team = self.pers["team"];
-
- music = game["music"]["spawn_" + team];
- if ( level.splitscreen )
- {
- if ( isDefined( level.playedStartingMusic ) )
- music = undefined;
- else
- level.playedStartingMusic = true;
- }
-
- thread maps\mp\gametypes\_hud_message::oldNotifyMessage( game["strings"][team + "_name"], undefined, game["icons"][team], game["colors"][team], music );
- if ( isDefined( game["dialog"]["gametype"] ) && (!level.splitscreen || self == level.players[0]) )
- self leaderDialogOnPlayer( "gametype" );
-
- thread maps\mp\gametypes\_hud::showClientScoreBar( 5.0 );
- }
- else
- {
- self freezeControls( false );
- self enableWeapons();
- if ( !hadSpawned && game["state"] == "playing" )
- {
- team = self.team;
-
- music = game["music"]["spawn_" + team];
- if ( level.splitscreen )
- {
- if ( isDefined( level.playedStartingMusic ) )
- music = undefined;
- else
- level.playedStartingMusic = true;
- }
-
- thread maps\mp\gametypes\_hud_message::oldNotifyMessage( game["strings"][team + "_name"], undefined, game["icons"][team], game["colors"][team], music );
- if ( isDefined( game["dialog"]["gametype"] ) && (!level.splitscreen || self == level.players[0]) )
- {
- self leaderDialogOnPlayer( "gametype" );
- if ( team == game["attackers"] )
- self leaderDialogOnPlayer( "offense_obj", "introboost" );
- else
- self leaderDialogOnPlayer( "defense_obj", "introboost" );
- }
-
- self setClientDvar( "scr_objectiveText", getObjectiveHintText( self.pers["team"] ) );
- thread maps\mp\gametypes\_hud::showClientScoreBar( 5.0 );
- }
- }
-
- if ( getdvar( "scr_showperksonspawn" ) == "" )
- setdvar( "scr_showperksonspawn", "1" );
-
- if ( !level.splitscreen && getdvarint( "scr_showperksonspawn" ) == 1 && game["state"] != "postgame" )
- {
- perks = getPerks( self );
- self showPerk( 0, perks[0], -50 );
- self showPerk( 1, perks[1], -50 );
- self showPerk( 2, perks[2], -50 );
- self thread hidePerksAfterTime( 3.0 );
- self thread hidePerksOnDeath();
- }
-
- prof_end( "spawnPlayer_postUTS" );
-
- waittillframeend;
- self notify( "spawned_player" );
-
- self logstring( "S " + self.origin[0] + " " + self.origin[1] + " " + self.origin[2] );
-
- self thread maps\mp\gametypes\_hardpoints::hardpointItemWaiter();
-
- //self thread testHPs();
- //self thread testShock();
- //self thread testMenu();
-
- if ( game["state"] == "postgame" )
- {
- assert( !level.intermission );
- // We're in the victory screen, but before intermission
- self freezePlayerForRoundEnd();
- }
-}
-
-hidePerksAfterTime( delay )
-{
- self endon("disconnect");
- self endon("perks_hidden");
-
- wait delay;
-
- self thread hidePerk( 0, 2.0 );
- self thread hidePerk( 1, 2.0 );
- self thread hidePerk( 2, 2.0 );
- self notify("perks_hidden");
-}
-
-hidePerksOnDeath()
-{
- self endon("disconnect");
- self endon("perks_hidden");
-
- self waittill("death");
-
- self hidePerk( 0 );
- self hidePerk( 1 );
- self hidePerk( 2 );
- self notify("perks_hidden");
-}
-
-hidePerksOnKill()
-{
- self endon("disconnect");
- self endon("death");
- self endon("perks_hidden");
-
- self waittill( "killed_player" );
-
- self hidePerk( 0 );
- self hidePerk( 1 );
- self hidePerk( 2 );
- self notify("perks_hidden");
-}
-
-
-testMenu()
-{
- self endon ( "death" );
- self endon ( "disconnect" );
-
- for ( ;; )
- {
- wait ( 10.0 );
-
- notifyData = spawnStruct();
- notifyData.titleText = &"MP_CHALLENGE_COMPLETED";
- notifyData.notifyText = "wheee";
- notifyData.sound = "mp_challenge_complete";
-
- self thread maps\mp\gametypes\_hud_message::notifyMessage( notifyData );
- }
-}
-
-testShock()
-{
- self endon ( "death" );
- self endon ( "disconnect" );
-
- for ( ;; )
- {
- wait ( 3.0 );
-
- numShots = randomInt( 6 );
-
- for ( i = 0; i < numShots; i++ )
- {
- iPrintLnBold( numShots );
- self shellShock( "frag_grenade_mp", 0.2 );
- wait ( 0.1 );
- }
- }
-}
-
-testHPs()
-{
- self endon ( "death" );
- self endon ( "disconnect" );
-
- hps = [];
- hps[hps.size] = "radar_mp";
- hps[hps.size] = "airstrike_mp";
- hps[hps.size] = "helicopter_mp";
-
- for ( ;; )
- {
-// hp = hps[randomInt(hps.size)];
- hp = "radar_mp";
- if ( self thread maps\mp\gametypes\_hardpoints::giveHardpointItem( hp ) )
- {
- self playLocalSound( level.hardpointInforms[hp] );
- }
-
-// self thread maps\mp\gametypes\_hardpoints::upgradeHardpointItem();
-
- wait ( 20.0 );
- }
-}
-
-
-spawnSpectator( origin, angles )
-{
- self notify("spawned");
- self notify("end_respawn");
- in_spawnSpectator( origin, angles );
-}
-
-// spawnSpectator clone without notifies for spawning between respawn delays
-respawn_asSpectator( origin, angles )
-{
- in_spawnSpectator( origin, angles );
-}
-
-// spawnSpectator helper
-in_spawnSpectator( origin, angles )
-{
- self setSpawnVariables();
-
- // don't clear lower message if not actually a spectator,
- // because it probably has important information like when we'll spawn
- if ( self.pers["team"] == "spectator" )
- self clearLowerMessage();
-
- self.sessionstate = "spectator";
- self.spectatorclient = -1;
- self.killcamentity = -1;
- self.archivetime = 0;
- self.psoffsettime = 0;
- self.friendlydamage = undefined;
-
- if(self.pers["team"] == "spectator")
- self.statusicon = "";
- else
- self.statusicon = "hud_status_dead";
-
- maps\mp\gametypes\_spectating::setSpectatePermissions();
-
- [[level.onSpawnSpectator]]( origin, angles );
-
- if ( level.teamBased && !level.splitscreen )
- self thread spectatorThirdPersonness();
-
- level thread updateTeamStatus();
-}
-
-spectatorThirdPersonness()
-{
- self endon("disconnect");
- self endon("spawned");
-
- self notify("spectator_thirdperson_thread");
- self endon("spectator_thirdperson_thread");
-
- self.spectatingThirdPerson = false;
-
- self setThirdPerson( true );
-
- // we can reenable this if we ever get a way to determine who a player is spectating.
- // self.spectatorClient is write-only so it doesn't work.
- /*
- player = getPlayerFromClientNum( self.spectatorClient );
- prevClientNum = self.spectatorClient;
- prevWeap = "none";
- hasScope = false;
-
- while(1)
- {
- if ( self.spectatorClient != prevClientNum )
- {
- player = getPlayerFromClientNum( self.spectatorClient );
- prevClientNum = self.specatorClient;
- }
-
- if ( isDefined( player ) )
- {
- weap = player getCurrentWeapon();
- if ( weap != prevWeap )
- {
- hasScope = maps\mp\gametypes\_weapons::hasScope( weap );
- prevWeap = weap;
- }
- if ( hasScope && player playerADS() == 1 )
- self setThirdPerson( false );
- else
- self setThirdPerson( true );
- }
- else
- {
- self setThirdPerson( false );
- }
- wait .05;
- }
- */
-}
-
-getPlayerFromClientNum( clientNum )
-{
- if ( clientNum < 0 )
- return undefined;
-
- for ( i = 0; i < level.players.size; i++ )
- {
- if ( level.players[i] getEntityNumber() == clientNum )
- return level.players[i];
- }
- return undefined;
-}
-
-setThirdPerson( value )
-{
- if ( value != self.spectatingThirdPerson )
- {
- self.spectatingThirdPerson = value;
- if ( value )
- {
- //self setClientDvar( "cg_thirdPerson", "1" );
- //self setDepthOfField( 0, 128, 512, 4000, 6, 1.8 );
- //self setClientDvar( "cg_fov", "40" );
- }
- else
- {
- //self setClientDvar( "cg_thirdPerson", "0" );
- //self setDepthOfField( 0, 0, 512, 4000, 4, 0 );
- //self setClientDvar( "cg_fov", "65" );
- }
- }
-}
-
-waveSpawnTimer()
-{
- level endon( "game_ended" );
-
- while ( game["state"] == "playing" )
- {
- time = getTime();
-
- if ( time - level.lastWave["allies"] > (level.waveDelay["allies"] * 1000) )
- {
- level notify ( "wave_respawn_allies" );
- level.lastWave["allies"] = time;
- level.wavePlayerSpawnIndex["allies"] = 0;
- }
-
- if ( time - level.lastWave["axis"] > (level.waveDelay["axis"] * 1000) )
- {
- level notify ( "wave_respawn_axis" );
- level.lastWave["axis"] = time;
- level.wavePlayerSpawnIndex["axis"] = 0;
- }
-
- wait ( 0.05 );
- }
-}
-
-
-default_onSpawnSpectator( origin, angles)
-{
- if( isDefined( origin ) && isDefined( angles ) )
- {
- self spawn(origin, angles);
- return;
- }
-
- //spawnpointname = "mp_global_intermission";
- //spawnpoints = getentarray(spawnpointname, "classname");
- //assert( spawnpoints.size );
- //spawnpoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_Random(spawnpoints);
- //self spawn(spawnpoint.origin, spawnpoint.angles);
-
- self spawn((0, 0, 0), (90, 0, 0));
-}
-
-spawnIntermission()
-{
- self notify("spawned");
- self notify("end_respawn");
-
- self setSpawnVariables();
-
- self clearLowerMessage();
-
- self freezeControls( false );
-
- self setClientDvar( "cg_everyoneHearsEveryone", 1 );
-
- self.sessionstate = "intermission";
- self.spectatorclient = -1;
- self.killcamentity = -1;
- self.archivetime = 0;
- self.psoffsettime = 0;
- self.friendlydamage = undefined;
-
- [[level.onSpawnIntermission]]();
- self setDepthOfField( 0, 128, 512, 4000, 6, 1.8 );
-}
-
-
-default_onSpawnIntermission()
-{
- spawnpointname = "mp_global_intermission";
- spawnpoints = getentarray(spawnpointname, "classname");
-// spawnpoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_Random(spawnpoints);
- spawnpoint = spawnPoints[0];
-
- if( isDefined( spawnpoint ) )
- self spawn( spawnpoint.origin, spawnpoint.angles );
- else
- maps\mp\_utility::error("NO " + spawnpointname + " SPAWNPOINTS IN MAP");
-}
-
-// returns the best guess of the exact time until the scoreboard will be displayed and player control will be lost.
-// returns undefined if time is not known
-timeUntilRoundEnd()
-{
- if ( level.gameEnded )
- {
- timePassed = (getTime() - level.gameEndTime) / 1000;
- timeRemaining = level.postRoundTime - timePassed;
-
- if ( timeRemaining < 0 )
- return 0;
-
- return timeRemaining;
- }
-
- if ( level.inOvertime )
- return undefined;
-
- if ( level.timeLimit <= 0 )
- return undefined;
-
- if ( !isDefined( level.startTime ) )
- return undefined;
-
- timePassed = (getTime() - level.startTime)/1000;
- timeRemaining = (level.timeLimit * 60) - timePassed;
-
- return timeRemaining + level.postRoundTime;
-}
-
-freezePlayerForRoundEnd()
-{
- self clearLowerMessage();
-
- self closeMenu();
- self closeInGameMenu();
-
- self freezeControls( true );
-// self disableWeapons();
-}
-
-
-logXPGains()
-{
- if ( !isDefined( self.xpGains ) )
- return;
-
- xpTypes = getArrayKeys( self.xpGains );
- for ( index = 0; index < xpTypes.size; index++ )
- {
- gain = self.xpGains[xpTypes[index]];
- if ( !gain )
- continue;
-
- self logString( "xp " + xpTypes[index] + ": " + gain );
- }
-}
-
-freeGameplayHudElems()
-{
- // free up some hud elems so we have enough for other things.
-
- // perk icons
- if ( isdefined( self.perkicon ) )
- {
- if ( isdefined( self.perkicon[0] ) )
- {
- self.perkicon[0] destroyElem();
- self.perkname[0] destroyElem();
- }
- if ( isdefined( self.perkicon[1] ) )
- {
- self.perkicon[1] destroyElem();
- self.perkname[1] destroyElem();
- }
- if ( isdefined( self.perkicon[2] ) )
- {
- self.perkicon[2] destroyElem();
- self.perkname[2] destroyElem();
- }
- }
- self notify("perks_hidden"); // stop any threads that are waiting to hide the perk icons
-
- // lower message
- self.lowerMessage destroyElem();
- self.lowerTimer destroyElem();
-
- // progress bar
- if ( isDefined( self.proxBar ) )
- self.proxBar destroyElem();
- if ( isDefined( self.proxBarText ) )
- self.proxBarText destroyElem();
-}
-
-
-getHostPlayer()
-{
- players = getEntArray( "player", "classname" );
-
- for ( index = 0; index < players.size; index++ )
- {
- if ( players[index] getEntityNumber() == 0 )
- return players[index];
- }
-}
-
-
-hostIdledOut()
-{
- hostPlayer = getHostPlayer();
-
- // host never spawned
- if ( isDefined( hostPlayer ) && !hostPlayer.hasSpawned && !isDefined( hostPlayer.selectedClass ) )
- return true;
-
- return false;
-}
-
-
-endGame( winner, endReasonText )
-{
- // return if already ending via host quit or victory
- if ( game["state"] == "postgame" || level.gameEnded )
- return;
-
- if ( isDefined( level.onEndGame ) )
- [[level.onEndGame]]( winner );
-
- visionSetNaked( "mpOutro", 2.0 );
-
- game["state"] = "postgame";
- level.gameEndTime = getTime();
- level.gameEnded = true;
- level.inGracePeriod = false;
- level notify ( "game_ended" );
-
- setGameEndTime( 0 ); // stop/hide the timers
-
- if ( level.rankedMatch )
- {
- setXenonRanks();
-
- if ( hostIdledOut() )
- {
- level.hostForcedEnd = true;
- logString( "host idled out" );
- endLobby();
- }
- }
-
- updatePlacement();
- updateMatchBonusScores( winner );
- updateWinLossStats( winner );
-
- setdvar( "g_deadChat", 1 );
-
- // freeze players
- players = level.players;
- for ( index = 0; index < players.size; index++ )
- {
- player = players[index];
-
- player freezePlayerForRoundEnd();
- player thread roundEndDoF( 4.0 );
-
- player freeGameplayHudElems();
-
- player setClientDvars( "cg_everyoneHearsEveryone", 1 );
-
- if( level.rankedMatch )
- {
- if ( isDefined( player.setPromotion ) )
- player setClientDvar( "ui_lobbypopup", "promotion" );
- else
- player setClientDvar( "ui_lobbypopup", "summary" );
- }
- }
-
- // end round
- if ( (level.roundLimit > 1 || (!level.roundLimit && level.scoreLimit != 1)) && !level.forcedEnd )
- {
- if ( level.displayRoundEndText )
- {
- players = level.players;
- for ( index = 0; index < players.size; index++ )
- {
- player = players[index];
-
- if ( level.teamBased )
- player thread maps\mp\gametypes\_hud_message::teamOutcomeNotify( winner, true, endReasonText );
- else
- player thread maps\mp\gametypes\_hud_message::outcomeNotify( winner, endReasonText );
-
- player setClientDvars( "ui_hud_hardcore", 1,
- "cg_drawSpectatorMessages", 0,
- "g_compassShowEnemies", 0 );
- }
-
- if ( level.teamBased && !(hitRoundLimit() || hitScoreLimit()) )
- thread announceRoundWinner( winner, level.roundEndDelay / 4 );
-
- if ( hitRoundLimit() || hitScoreLimit() )
- roundEndWait( level.roundEndDelay / 2, false );
- else
- roundEndWait( level.roundEndDelay, true );
- }
-
- game["roundsplayed"]++;
- roundSwitching = false;
- if ( !hitRoundLimit() && !hitScoreLimit() )
- roundSwitching = checkRoundSwitch();
-
- if ( roundSwitching && level.teamBased )
- {
- players = level.players;
- for ( index = 0; index < players.size; index++ )
- {
- player = players[index];
-
- if ( !isDefined( player.pers["team"] ) || player.pers["team"] == "spectator" )
- {
- player [[level.spawnIntermission]]();
- player closeMenu();
- player closeInGameMenu();
- continue;
- }
-
- switchType = level.halftimeType;
- if ( switchType == "halftime" )
- {
- if ( level.roundLimit )
- {
- if ( (game["roundsplayed"] * 2) == level.roundLimit )
- switchType = "halftime";
- else
- switchType = "intermission";
- }
- else if ( level.scoreLimit )
- {
- if ( game["roundsplayed"] == (level.scoreLimit - 1) )
- switchType = "halftime";
- else
- switchType = "intermission";
- }
- else
- {
- switchType = "intermission";
- }
- }
- switch( switchType )
- {
- case "halftime":
- player leaderDialogOnPlayer( "halftime" );
- break;
- case "overtime":
- player leaderDialogOnPlayer( "overtime" );
- break;
- default:
- player leaderDialogOnPlayer( "side_switch" );
- break;
- }
- player thread maps\mp\gametypes\_hud_message::teamOutcomeNotify( switchType, true, level.halftimeSubCaption );
- player setClientDvar( "ui_hud_hardcore", 1 );
- }
-
- roundEndWait( level.halftimeRoundEndDelay, false );
- }
- else if ( !hitRoundLimit() && !hitScoreLimit() && !level.displayRoundEndText && level.teamBased )
- {
- players = level.players;
- for ( index = 0; index < players.size; index++ )
- {
- player = players[index];
-
- if ( !isDefined( player.pers["team"] ) || player.pers["team"] == "spectator" )
- {
- player [[level.spawnIntermission]]();
- player closeMenu();
- player closeInGameMenu();
- continue;
- }
-
- switchType = level.halftimeType;
- if ( switchType == "halftime" )
- {
- if ( level.roundLimit )
- {
- if ( (game["roundsplayed"] * 2) == level.roundLimit )
- switchType = "halftime";
- else
- switchType = "roundend";
- }
- else if ( level.scoreLimit )
- {
- if ( game["roundsplayed"] == (level.scoreLimit - 1) )
- switchType = "halftime";
- else
- switchTime = "roundend";
- }
- }
- switch( switchType )
- {
- case "halftime":
- player leaderDialogOnPlayer( "halftime" );
- break;
- case "overtime":
- player leaderDialogOnPlayer( "overtime" );
- break;
- }
- player thread maps\mp\gametypes\_hud_message::teamOutcomeNotify( switchType, true, endReasonText );
- player setClientDvar( "ui_hud_hardcore", 1 );
- }
-
- roundEndWait( level.halftimeRoundEndDelay, !(hitRoundLimit() || hitScoreLimit()) );
- }
-
- if ( !hitRoundLimit() && !hitScoreLimit() )
- {
- level notify ( "restarting" );
- game["state"] = "playing";
- map_restart( true );
- return;
- }
-
- if ( hitRoundLimit() )
- endReasonText = game["strings"]["round_limit_reached"];
- else if ( hitScoreLimit() )
- endReasonText = game["strings"]["score_limit_reached"];
- else
- endReasonText = game["strings"]["time_limit_reached"];
- }
-
- thread maps\mp\gametypes\_missions::roundEnd( winner );
-
- // catching gametype, since DM forceEnd sends winner as player entity, instead of string
- players = level.players;
- for ( index = 0; index < players.size; index++ )
- {
- player = players[index];
-
- if ( !isDefined( player.pers["team"] ) || player.pers["team"] == "spectator" )
- {
- player [[level.spawnIntermission]]();
- player closeMenu();
- player closeInGameMenu();
- continue;
- }
-
- if ( level.teamBased )
- {
- player thread maps\mp\gametypes\_hud_message::teamOutcomeNotify( winner, false, endReasonText );
- }
- else
- {
- player thread maps\mp\gametypes\_hud_message::outcomeNotify( winner, endReasonText );
-
- if ( isDefined( winner ) && player == winner )
- player playLocalSound( game["music"]["victory_" + player.pers["team"] ] );
- else if ( !level.splitScreen )
- player playLocalSound( game["music"]["defeat"] );
- }
-
- player setClientDvars( "ui_hud_hardcore", 1,
- "cg_drawSpectatorMessages", 0,
- "g_compassShowEnemies", 0 );
- }
-
- if ( level.teamBased )
- {
- thread announceGameWinner( winner, level.postRoundTime / 2 );
-
- if ( level.splitscreen )
- {
- if ( winner == "allies" )
- playSoundOnPlayers( game["music"]["victory_allies"], "allies" );
- else if ( winner == "axis" )
- playSoundOnPlayers( game["music"]["victory_axis"], "axis" );
- else
- playSoundOnPlayers( game["music"]["defeat"] );
- }
- else
- {
- if ( winner == "allies" )
- {
- playSoundOnPlayers( game["music"]["victory_allies"], "allies" );
- playSoundOnPlayers( game["music"]["defeat"], "axis" );
- }
- else if ( winner == "axis" )
- {
- playSoundOnPlayers( game["music"]["victory_axis"], "axis" );
- playSoundOnPlayers( game["music"]["defeat"], "allies" );
- }
- else
- {
- playSoundOnPlayers( game["music"]["defeat"] );
- }
- }
- }
-
- roundEndWait( level.postRoundTime, true );
-
- level.intermission = true;
-
- //regain players array since some might've disconnected during the wait above
- players = level.players;
- for ( index = 0; index < players.size; index++ )
- {
- player = players[index];
-
- player closeMenu();
- player closeInGameMenu();
- player notify ( "reset_outcome" );
- player thread spawnIntermission();
- player setClientDvar( "ui_hud_hardcore", 0 );
- player setclientdvar( "g_scriptMainMenu", game["menu_eog_main"] );
- }
-
- logString( "game ended" );
- wait getDvarFloat( "scr_show_unlock_wait" );
-
- if( level.console )
- {
- exitLevel( false );
- return;
- }
-
- // popup for game summary
- players = level.players;
- for ( index = 0; index < players.size; index++ )
- {
- player = players[index];
- //iPrintLnBold( "opening eog summary!" );
- //player.sessionstate = "dead";
- player openMenu( game["menu_eog_unlock"] );
- }
-
- thread timeLimitClock_Intermission( getDvarFloat( "scr_intermission_time" ) );
- wait getDvarFloat( "scr_intermission_time" );
-
- players = level.players;
- for ( index = 0; index < players.size; index++ )
- {
- player = players[index];
- //iPrintLnBold( "closing eog summary!" );
- player closeMenu();
- player closeInGameMenu();
- }
-
- exitLevel( false );
-}
-
-
-getWinningTeam()
-{
- if ( getGameScore( "allies" ) == getGameScore( "axis" ) )
- winner = "tie";
- else if ( getGameScore( "allies" ) > getGameScore( "axis" ) )
- winner = "allies";
- else
- winner = "axis";
-}
-
-
-roundEndWait( defaultDelay, matchBonus )
-{
- notifiesDone = false;
- while ( !notifiesDone )
- {
- players = level.players;
- notifiesDone = true;
- for ( index = 0; index < players.size; index++ )
- {
- if ( !isDefined( players[index].doingNotify ) || !players[index].doingNotify )
- continue;
-
- notifiesDone = false;
- }
- wait ( 0.5 );
- }
-
- if ( !matchBonus )
- {
- wait ( defaultDelay );
- return;
- }
-
- wait ( defaultDelay / 2 );
- level notify ( "give_match_bonus" );
- wait ( defaultDelay / 2 );
-
- notifiesDone = false;
- while ( !notifiesDone )
- {
- players = level.players;
- notifiesDone = true;
- for ( index = 0; index < players.size; index++ )
- {
- if ( !isDefined( players[index].doingNotify ) || !players[index].doingNotify )
- continue;
-
- notifiesDone = false;
- }
- wait ( 0.5 );
- }
-}
-
-
-roundEndDOF( time )
-{
- self setDepthOfField( 0, 128, 512, 4000, 6, 1.8 );
-}
-
-
-updateMatchBonusScores( winner )
-{
- if ( !game["timepassed"] )
- return;
-
- if ( !level.rankedMatch )
- return;
-
- if ( !level.timeLimit || level.forcedEnd )
- {
- gameLength = getTimePassed() / 1000;
- // cap it at 20 minutes to avoid exploiting
- gameLength = min( gameLength, 1200 );
- }
- else
- {
- gameLength = level.timeLimit * 60;
- }
-
- if ( level.teamBased )
- {
- if ( winner == "allies" )
- {
- winningTeam = "allies";
- losingTeam = "axis";
- }
- else if ( winner == "axis" )
- {
- winningTeam = "axis";
- losingTeam = "allies";
- }
- else
- {
- winningTeam = "tie";
- losingTeam = "tie";
- }
-
- if ( winningTeam != "tie" )
- {
- winnerScale = maps\mp\gametypes\_rank::getScoreInfoValue( "win" );
- loserScale = maps\mp\gametypes\_rank::getScoreInfoValue( "loss" );
- }
- else
- {
- winnerScale = maps\mp\gametypes\_rank::getScoreInfoValue( "tie" );
- loserScale = maps\mp\gametypes\_rank::getScoreInfoValue( "tie" );
- }
-
- players = level.players;
- for( i = 0; i < players.size; i++ )
- {
- player = players[i];
-
- if ( player.timePlayed["total"] < 1 || player.pers["participation"] < 1 )
- {
- player thread maps\mp\gametypes\_rank::endGameUpdate();
- continue;
- }
-
- // no bonus for hosts who force ends
- if ( level.hostForcedEnd && player getEntityNumber() == 0 )
- continue;
-
- spm = player maps\mp\gametypes\_rank::getSPM();
- if ( winningTeam == "tie" )
- {
- playerScore = int( (winnerScale * ((gameLength/60) * spm)) * (player.timePlayed["total"] / gameLength) );
- player thread giveMatchBonus( "tie", playerScore );
- player.matchBonus = playerScore;
- }
- else if ( isDefined( player.pers["team"] ) && player.pers["team"] == winningTeam )
- {
- playerScore = int( (winnerScale * ((gameLength/60) * spm)) * (player.timePlayed["total"] / gameLength) );
- player thread giveMatchBonus( "win", playerScore );
- player.matchBonus = playerScore;
- }
- else if ( isDefined(player.pers["team"] ) && player.pers["team"] == losingTeam )
- {
- playerScore = int( (loserScale * ((gameLength/60) * spm)) * (player.timePlayed["total"] / gameLength) );
- player thread giveMatchBonus( "loss", playerScore );
- player.matchBonus = playerScore;
- }
- }
- }
- else
- {
- if ( isDefined( winner ) )
- {
- winnerScale = maps\mp\gametypes\_rank::getScoreInfoValue( "win" );
- loserScale = maps\mp\gametypes\_rank::getScoreInfoValue( "loss" );
- }
- else
- {
- winnerScale = maps\mp\gametypes\_rank::getScoreInfoValue( "tie" );
- loserScale = maps\mp\gametypes\_rank::getScoreInfoValue( "tie" );
- }
-
- players = level.players;
- for( i = 0; i < players.size; i++ )
- {
- player = players[i];
-
- if ( player.timePlayed["total"] < 1 || player.pers["participation"] < 1 )
- {
- player thread maps\mp\gametypes\_rank::endGameUpdate();
- continue;
- }
-
- spm = player maps\mp\gametypes\_rank::getSPM();
-
- isWinner = false;
- for ( pIdx = 0; pIdx < min( level.placement["all"][0].size, 3 ); pIdx++ )
- {
- if ( level.placement["all"][pIdx] != player )
- continue;
- isWinner = true;
- }
-
- if ( isWinner )
- {
- playerScore = int( (winnerScale * ((gameLength/60) * spm)) * (player.timePlayed["total"] / gameLength) );
- player thread giveMatchBonus( "win", playerScore );
- player.matchBonus = playerScore;
- }
- else
- {
- playerScore = int( (loserScale * ((gameLength/60) * spm)) * (player.timePlayed["total"] / gameLength) );
- player thread giveMatchBonus( "loss", playerScore );
- player.matchBonus = playerScore;
- }
- }
- }
-}
-
-
-giveMatchBonus( scoreType, score )
-{
- self endon ( "disconnect" );
-
- level waittill ( "give_match_bonus" );
-
- self maps\mp\gametypes\_rank::giveRankXP( scoreType, score );
- logXPGains();
-
- self maps\mp\gametypes\_rank::endGameUpdate();
-}
-
-
-setXenonRanks( winner )
-{
- players = level.players;
-
- for ( i = 0; i < players.size; i++ )
- {
- player = players[i];
-
- if( !isdefined(player.score) || !isdefined(player.pers["team"]) )
- continue;
-
- }
-
- for ( i = 0; i < players.size; i++ )
- {
- player = players[i];
-
- if( !isdefined(player.score) || !isdefined(player.pers["team"]) )
- continue;
-
- setPlayerTeamRank( player, i, player.score - 5 * player.deaths );
- player logString( "team: score " + player.pers["team"] + ":" + player.score );
- }
- sendranks();
-}
-
-
-getHighestScoringPlayer()
-{
- players = level.players;
- winner = undefined;
- tie = false;
-
- for( i = 0; i < players.size; i++ )
- {
- if ( !isDefined( players[i].score ) )
- continue;
-
- if ( players[i].score < 1 )
- continue;
-
- if ( !isDefined( winner ) || players[i].score > winner.score )
- {
- winner = players[i];
- tie = false;
- }
- else if ( players[i].score == winner.score )
- {
- tie = true;
- }
- }
-
- if ( tie || !isDefined( winner ) )
- return undefined;
- else
- return winner;
-}
-
-
-checkTimeLimit()
-{
- if ( isDefined( level.timeLimitOverride ) && level.timeLimitOverride )
- return;
-
- if ( game["state"] != "playing" )
- {
- setGameEndTime( 0 );
- return;
- }
-
- if ( level.timeLimit <= 0 )
- {
- setGameEndTime( 0 );
- return;
- }
-
- if ( level.inPrematchPeriod )
- {
- setGameEndTime( 0 );
- return;
- }
-
- if ( !isdefined( level.startTime ) )
- return;
-
- timeLeft = getTimeRemaining();
-
- // want this accurate to the millisecond
- setGameEndTime( getTime() + int(timeLeft) );
-
- if ( timeLeft > 0 )
- return;
-
- [[level.onTimeLimit]]();
-}
-
-getTimeRemaining()
-{
- return level.timeLimit * 60 * 1000 - getTimePassed();
-}
-
-checkScoreLimit()
-{
- if ( game["state"] != "playing" )
- return;
-
- if ( level.scoreLimit <= 0 )
- return;
-
- if ( level.teamBased )
- {
- if( game["teamScores"]["allies"] < level.scoreLimit && game["teamScores"]["axis"] < level.scoreLimit )
- return;
- }
- else
- {
- if ( !isPlayer( self ) )
- return;
-
- if ( self.score < level.scoreLimit )
- return;
- }
-
- [[level.onScoreLimit]]();
-}
-
-
-hitRoundLimit()
-{
- if( level.roundLimit <= 0 )
- return false;
-
- return ( game["roundsplayed"] >= level.roundLimit );
-}
-
-hitScoreLimit()
-{
- if( level.scoreLimit <= 0 )
- return false;
-
- if ( level.teamBased )
- {
- if( game["teamScores"]["allies"] >= level.scoreLimit || game["teamScores"]["axis"] >= level.scoreLimit )
- return true;
- }
- else
- {
- for ( i = 0; i < level.players.size; i++ )
- {
- player = level.players[i];
- if ( isDefined( player.score ) && player.score >= level.scorelimit )
- return true;
- }
- }
- return false;
-}
-
-registerRoundSwitchDvar( dvarString, defaultValue, minValue, maxValue )
-{
- dvarString = ("scr_" + dvarString + "_roundswitch");
- if ( getDvar( dvarString ) == "" )
- setDvar( dvarString, defaultValue );
-
- if ( getDvarInt( dvarString ) > maxValue )
- setDvar( dvarString, maxValue );
- else if ( getDvarInt( dvarString ) < minValue )
- setDvar( dvarString, minValue );
-
-
- level.roundswitchDvar = dvarString;
- level.roundswitchMin = minValue;
- level.roundswitchMax = maxValue;
- level.roundswitch = getDvarInt( level.roundswitchDvar );
-}
-
-registerRoundLimitDvar( dvarString, defaultValue, minValue, maxValue )
-{
- dvarString = ("scr_" + dvarString + "_roundlimit");
- if ( getDvar( dvarString ) == "" )
- setDvar( dvarString, defaultValue );
-
- if ( getDvarInt( dvarString ) > maxValue )
- setDvar( dvarString, maxValue );
- else if ( getDvarInt( dvarString ) < minValue )
- setDvar( dvarString, minValue );
-
-
- level.roundLimitDvar = dvarString;
- level.roundlimitMin = minValue;
- level.roundlimitMax = maxValue;
- level.roundLimit = getDvarInt( level.roundLimitDvar );
-}
-
-
-registerScoreLimitDvar( dvarString, defaultValue, minValue, maxValue )
-{
- dvarString = ("scr_" + dvarString + "_scorelimit");
- if ( getDvar( dvarString ) == "" )
- setDvar( dvarString, defaultValue );
-
- if ( getDvarInt( dvarString ) > maxValue )
- setDvar( dvarString, maxValue );
- else if ( getDvarInt( dvarString ) < minValue )
- setDvar( dvarString, minValue );
-
- level.scoreLimitDvar = dvarString;
- level.scorelimitMin = minValue;
- level.scorelimitMax = maxValue;
- level.scoreLimit = getDvarInt( level.scoreLimitDvar );
-
- setDvar( "ui_scorelimit", level.scoreLimit );
-}
-
-
-registerTimeLimitDvar( dvarString, defaultValue, minValue, maxValue )
-{
- dvarString = ("scr_" + dvarString + "_timelimit");
- if ( getDvar( dvarString ) == "" )
- setDvar( dvarString, defaultValue );
-
- if ( getDvarFloat( dvarString ) > maxValue )
- setDvar( dvarString, maxValue );
- else if ( getDvarFloat( dvarString ) < minValue )
- setDvar( dvarString, minValue );
-
- level.timeLimitDvar = dvarString;
- level.timelimitMin = minValue;
- level.timelimitMax = maxValue;
- level.timelimit = getDvarFloat( level.timeLimitDvar );
-
- setDvar( "ui_timelimit", level.timelimit );
-}
-
-
-registerNumLivesDvar( dvarString, defaultValue, minValue, maxValue )
-{
- dvarString = ("scr_" + dvarString + "_numlives");
- if ( getDvar( dvarString ) == "" )
- setDvar( dvarString, defaultValue );
-
- if ( getDvarInt( dvarString ) > maxValue )
- setDvar( dvarString, maxValue );
- else if ( getDvarInt( dvarString ) < minValue )
- setDvar( dvarString, minValue );
-
- level.numLivesDvar = dvarString;
- level.numLivesMin = minValue;
- level.numLivesMax = maxValue;
- level.numLives = getDvarInt( level.numLivesDvar );
-}
-
-
-getValueInRange( value, minValue, maxValue )
-{
- if ( value > maxValue )
- return maxValue;
- else if ( value < minValue )
- return minValue;
- else
- return value;
-}
-
-updateGameTypeDvars()
-{
- level endon ( "game_ended" );
-
- while ( game["state"] == "playing" )
- {
- roundlimit = getValueInRange( getDvarInt( level.roundLimitDvar ), level.roundLimitMin, level.roundLimitMax );
- if ( roundlimit != level.roundlimit )
- {
- level.roundlimit = roundlimit;
- level notify ( "update_roundlimit" );
- }
-
- timeLimit = getValueInRange( getDvarFloat( level.timeLimitDvar ), level.timeLimitMin, level.timeLimitMax );
- if ( timeLimit != level.timeLimit )
- {
- level.timeLimit = timeLimit;
- setDvar( "ui_timelimit", level.timeLimit );
- level notify ( "update_timelimit" );
- }
- thread checkTimeLimit();
-
- scoreLimit = getValueInRange( getDvarInt( level.scoreLimitDvar ), level.scoreLimitMin, level.scoreLimitMax );
- if ( scoreLimit != level.scoreLimit )
- {
- level.scoreLimit = scoreLimit;
- setDvar( "ui_scorelimit", level.scoreLimit );
- level notify ( "update_scorelimit" );
- }
- thread checkScoreLimit();
-
- // make sure we check time limit right when game ends
- if ( isdefined( level.startTime ) )
- {
- if ( getTimeRemaining() < 3000 )
- {
- wait .1;
- continue;
- }
- }
- wait 1;
- }
-}
-
-
-menuAutoAssign()
-{
- teams[0] = "allies";
- teams[1] = "axis";
- assignment = teams[randomInt(2)];
-
- self closeMenus();
-
- if ( level.teamBased )
- {
- if ( getDvarInt( "party_autoteams" ) == 1 )
- {
- teamNum = getAssignedTeam( self );
- switch ( teamNum )
- {
- case 1:
- assignment = teams[1];
- break;
-
- case 2:
- assignment = teams[0];
- break;
-
- default:
- assignment = "";
- }
- }
-
- if ( assignment == "" || getDvarInt( "party_autoteams" ) == 0 )
- {
- playerCounts = self maps\mp\gametypes\_teams::CountPlayers();
-
- // if teams are equal return the team with the lowest score
- if ( playerCounts["allies"] == playerCounts["axis"] )
- {
- if( getTeamScore( "allies" ) == getTeamScore( "axis" ) )
- assignment = teams[randomInt(2)];
- else if ( getTeamScore( "allies" ) < getTeamScore( "axis" ) )
- assignment = "allies";
- else
- assignment = "axis";
- }
- else if( playerCounts["allies"] < playerCounts["axis"] )
- {
- assignment = "allies";
- }
- else
- {
- assignment = "axis";
- }
- }
-
- if ( assignment == self.pers["team"] && (self.sessionstate == "playing" || self.sessionstate == "dead") )
- {
- self beginClassChoice();
- return;
- }
- }
-
- if ( assignment != self.pers["team"] && (self.sessionstate == "playing" || self.sessionstate == "dead") )
- {
- self.switching_teams = true;
- self.joining_team = assignment;
- self.leaving_team = self.pers["team"];
- self suicide();
- }
-
- self.pers["team"] = assignment;
- self.team = assignment;
- self.pers["class"] = undefined;
- self.class = undefined;
- self.pers["weapon"] = undefined;
- self.pers["savedmodel"] = undefined;
-
- self updateObjectiveText();
-
- if ( level.teamBased )
- self.sessionteam = assignment;
- else
- {
- self.sessionteam = "none";
- }
-
- if ( !isAlive( self ) )
- self.statusicon = "hud_status_dead";
-
- self notify("joined_team");
- self notify("end_respawn");
-
- self beginClassChoice();
-
- self setclientdvar( "g_scriptMainMenu", game[ "menu_class_" + self.pers["team"] ] );
-}
-
-
-updateObjectiveText()
-{
- if ( self.pers["team"] == "spectator" )
- {
- self setClientDvar( "cg_objectiveText", "" );
- return;
- }
-
- if( level.scorelimit > 0 )
- {
- if ( level.splitScreen )
- self setclientdvar( "cg_objectiveText", getObjectiveScoreText( self.pers["team"] ) );
- else
- self setclientdvar( "cg_objectiveText", getObjectiveScoreText( self.pers["team"] ), level.scorelimit );
- }
- else
- {
- self setclientdvar( "cg_objectiveText", getObjectiveText( self.pers["team"] ) );
- }
-}
-
-closeMenus()
-{
- self closeMenu();
- self closeInGameMenu();
-}
-
-beginClassChoice( forceNewChoice )
-{
- assert( self.pers["team"] == "axis" || self.pers["team"] == "allies" );
-
- team = self.pers["team"];
-
- if ( level.oldschool )
- {
- // skip class choice and just spawn.
-
- self.pers["class"] = undefined;
- self.class = undefined;
-
- // open a menu that just sets the ui_team localvar
- self openMenu( game[ "menu_initteam_" + team ] );
-
- if ( self.sessionstate != "playing" && game["state"] == "playing" )
- self thread [[level.spawnClient]]();
- level thread updateTeamStatus();
- self thread maps\mp\gametypes\_spectating::setSpectatePermissions();
-
- return;
- }
-
- // menu_changeclass_team is the one where you choose one of the n classes to play as.
- // menu_class_team is where you can choose to change your team, class, controls, or leave game.
- self openMenu( game[ "menu_changeclass_" + team ] );
-
- //if ( level.rankedMatch )
- // self openMenu( game[ "menu_changeclass_" + team ] );
- //else
- // self openMenu( game[ "menu_class_" + team ] );
-}
-
-showMainMenuForTeam()
-{
- assert( self.pers["team"] == "axis" || self.pers["team"] == "allies" );
-
- team = self.pers["team"];
-
- // menu_changeclass_team is the one where you choose one of the n classes to play as.
- // menu_class_team is where you can choose to change your team, class, controls, or leave game.
-
- self openMenu( game[ "menu_class_" + team ] );
-}
-
-menuAllies()
-{
- self closeMenus();
-
- if(self.pers["team"] != "allies")
- {
- if( level.teamBased && !maps\mp\gametypes\_teams::getJoinTeamPermissions( "allies" ) )
- {
- self openMenu(game["menu_team"]);
- return;
- }
-
- // allow respawn when switching teams during grace period.
- if ( level.inGracePeriod && (!isdefined(self.hasDoneCombat) || !self.hasDoneCombat) )
- self.hasSpawned = false;
-
- if(self.sessionstate == "playing")
- {
- self.switching_teams = true;
- self.joining_team = "allies";
- self.leaving_team = self.pers["team"];
- self suicide();
- }
-
- self.pers["team"] = "allies";
- self.team = "allies";
- self.pers["class"] = undefined;
- self.class = undefined;
- self.pers["weapon"] = undefined;
- self.pers["savedmodel"] = undefined;
-
- self updateObjectiveText();
-
- if ( level.teamBased )
- self.sessionteam = "allies";
- else
- self.sessionteam = "none";
-
- self setclientdvar("g_scriptMainMenu", game["menu_class_allies"]);
-
- self notify("joined_team");
- self notify("end_respawn");
- }
-
- self beginClassChoice();
-}
-
-
-menuAxis()
-{
- self closeMenus();
-
- if(self.pers["team"] != "axis")
- {
- if( level.teamBased && !maps\mp\gametypes\_teams::getJoinTeamPermissions( "axis" ) )
- {
- self openMenu(game["menu_team"]);
- return;
- }
-
- // allow respawn when switching teams during grace period.
- if ( level.inGracePeriod && (!isdefined(self.hasDoneCombat) || !self.hasDoneCombat) )
- self.hasSpawned = false;
-
- if(self.sessionstate == "playing")
- {
- self.switching_teams = true;
- self.joining_team = "axis";
- self.leaving_team = self.pers["team"];
- self suicide();
- }
-
- self.pers["team"] = "axis";
- self.team = "axis";
- self.pers["class"] = undefined;
- self.class = undefined;
- self.pers["weapon"] = undefined;
- self.pers["savedmodel"] = undefined;
-
- self updateObjectiveText();
-
- if ( level.teamBased )
- self.sessionteam = "axis";
- else
- self.sessionteam = "none";
-
- self setclientdvar("g_scriptMainMenu", game["menu_class_axis"]);
-
- self notify("joined_team");
- self notify("end_respawn");
- }
-
- self beginClassChoice();
-}
-
-
-menuSpectator()
-{
- self closeMenus();
-
- if(self.pers["team"] != "spectator")
- {
- if(isAlive(self))
- {
- self.switching_teams = true;
- self.joining_team = "spectator";
- self.leaving_team = self.pers["team"];
- self suicide();
- }
-
- self.pers["team"] = "spectator";
- self.team = "spectator";
- self.pers["class"] = undefined;
- self.class = undefined;
- self.pers["weapon"] = undefined;
- self.pers["savedmodel"] = undefined;
-
- self updateObjectiveText();
-
- self.sessionteam = "spectator";
- [[level.spawnSpectator]]();
-
- self setclientdvar("g_scriptMainMenu", game["menu_team"]);
-
- self notify("joined_spectators");
- }
-}
-
-
-menuClass( response )
-{
- self closeMenus();
-
- // clears new status of unlocked classes
- if ( response == "demolitions_mp,0" && self getstat( int( tablelookup( "mp/statstable.csv", 4, "feature_demolitions", 1 ) ) ) != 1 )
- {
- demolitions_stat = int( tablelookup( "mp/statstable.csv", 4, "feature_demolitions", 1 ) );
- self setstat( demolitions_stat, 1 );
- //println( "Demolitions class [new status cleared]: stat(" + demolitions_stat + ") = " + self getstat( demolitions_stat ) );
- }
- if ( response == "sniper_mp,0" && self getstat( int( tablelookup( "mp/statstable.csv", 4, "feature_sniper", 1 ) ) ) != 1 )
- {
- sniper_stat = int( tablelookup( "mp/statstable.csv", 4, "feature_sniper", 1 ) );
- self setstat( sniper_stat, 1 );
- //println( "Sniper class [new status cleared]: stat(" + sniper_stat + ") = " + self getstat( sniper_stat ) );
- }
- assert( !level.oldschool );
-
- // this should probably be an assert
- if(!isDefined(self.pers["team"]) || (self.pers["team"] != "allies" && self.pers["team"] != "axis"))
- return;
-
- class = self maps\mp\gametypes\_class::getClassChoice( response );
- primary = self maps\mp\gametypes\_class::getWeaponChoice( response );
-
- if ( class == "restricted" )
- {
- self beginClassChoice();
- return;
- }
-
- if( (isDefined( self.pers["class"] ) && self.pers["class"] == class) &&
- (isDefined( self.pers["primary"] ) && self.pers["primary"] == primary) )
- return;
-
- if ( self.sessionstate == "playing" )
- {
- self.pers["class"] = class;
- self.class = class;
- self.pers["primary"] = primary;
- self.pers["weapon"] = undefined;
-
- if ( game["state"] == "postgame" )
- return;
-
- if ( level.inGracePeriod && !self.hasDoneCombat ) // used weapons check?
- {
- self maps\mp\gametypes\_class::setClass( self.pers["class"] );
- self.tag_stowed_back = undefined;
- self.tag_stowed_hip = undefined;
- self maps\mp\gametypes\_class::giveLoadout( self.pers["team"], self.pers["class"] );
- }
- else if ( !level.splitScreen )
- {
- self iPrintLnBold( game["strings"]["change_class"] );
- }
- }
- else
- {
- self.pers["class"] = class;
- self.class = class;
- self.pers["primary"] = primary;
- self.pers["weapon"] = undefined;
-
- if ( game["state"] == "postgame" )
- return;
-
- if ( game["state"] == "playing" )
- self thread [[level.spawnClient]]();
- }
-
- level thread updateTeamStatus();
-
- self thread maps\mp\gametypes\_spectating::setSpectatePermissions();
-}
-
-/#
-assertProperPlacement()
-{
- numPlayers = level.placement["all"].size;
- for ( i = 0; i < numPlayers - 1; i++ )
- {
- if ( level.placement["all"][i].score < level.placement["all"][i + 1].score )
- {
- println("^1Placement array:");
- for ( i = 0; i < numPlayers; i++ )
- {
- player = level.placement["all"][i];
- println("^1" + i + ". " + player.name + ": " + player.score );
- }
- assertmsg( "Placement array was not properly sorted" );
- break;
- }
- }
-}
-#/
-
-
-removeDisconnectedPlayerFromPlacement()
-{
- offset = 0;
- numPlayers = level.placement["all"].size;
- found = false;
- for ( i = 0; i < numPlayers; i++ )
- {
- if ( level.placement["all"][i] == self )
- found = true;
-
- if ( found )
- level.placement["all"][i] = level.placement["all"][ i + 1 ];
- }
- if ( !found )
- return;
-
- level.placement["all"][ numPlayers - 1 ] = undefined;
- assert( level.placement["all"].size == numPlayers - 1 );
-
- /#
- // no longer calling this here because it's possible, due to delayed assist credit,
- // for someone's score to change after updatePlacement() is called.
- //assertProperPlacement();
- #/
-
- updateTeamPlacement();
-
- if ( level.teamBased )
- return;
-
- numPlayers = level.placement["all"].size;
- for ( i = 0; i < numPlayers; i++ )
- {
- player = level.placement["all"][i];
- player notify( "update_outcome" );
- }
-
-}
-
-updatePlacement()
-{
- prof_begin("updatePlacement");
-
- if ( !level.players.size )
- return;
-
- level.placement["all"] = [];
- for ( index = 0; index < level.players.size; index++ )
- {
- if ( level.players[index].team == "allies" || level.players[index].team == "axis" )
- level.placement["all"][level.placement["all"].size] = level.players[index];
- }
-
- placementAll = level.placement["all"];
-
- for ( i = 1; i < placementAll.size; i++ )
- {
- player = placementAll[i];
- playerScore = player.score;
- for ( j = i - 1; j >= 0 && (playerScore > placementAll[j].score || (playerScore == placementAll[j].score && player.deaths < placementAll[j].deaths)); j-- )
- placementAll[j + 1] = placementAll[j];
- placementAll[j + 1] = player;
- }
-
- level.placement["all"] = placementAll;
-
- /#
- assertProperPlacement();
- #/
-
- updateTeamPlacement();
-
- prof_end("updatePlacement");
-}
-
-
-updateTeamPlacement()
-{
- placement["allies"] = [];
- placement["axis"] = [];
- placement["spectator"] = [];
-
- if ( !level.teamBased )
- return;
-
- placementAll = level.placement["all"];
- placementAllSize = placementAll.size;
-
- for ( i = 0; i < placementAllSize; i++ )
- {
- player = placementAll[i];
- team = player.pers["team"];
-
- placement[team][ placement[team].size ] = player;
- }
-
- level.placement["allies"] = placement["allies"];
- level.placement["axis"] = placement["axis"];
-}
-
-onXPEvent( event )
-{
- self maps\mp\gametypes\_rank::giveRankXP( event );
-}
-
-
-givePlayerScore( event, player, victim )
-{
- if ( level.overridePlayerScore )
- return;
-
- score = player.pers["score"];
- [[level.onPlayerScore]]( event, player, victim );
-
- if ( score == player.pers["score"] )
- return;
-
- player maps\mp\gametypes\_persistence::statAdd( "score", (player.pers["score"] - score) );
-
- player.score = player.pers["score"];
-
- if ( !level.teambased )
- thread sendUpdatedDMScores();
-
- player notify ( "update_playerscore_hud" );
- player thread checkScoreLimit();
-}
-
-
-default_onPlayerScore( event, player, victim )
-{
- score = maps\mp\gametypes\_rank::getScoreInfoValue( event );
-
- assert( isDefined( score ) );
- /*
- if ( event == "assist" )
- player.pers["score"] += 2;
- else
- player.pers["score"] += 10;
- */
-
- player.pers["score"] += score;
-}
-
-
-_setPlayerScore( player, score )
-{
- if ( score == player.pers["score"] )
- return;
-
- player.pers["score"] = score;
- player.score = player.pers["score"];
-
- player notify ( "update_playerscore_hud" );
- player thread checkScoreLimit();
-}
-
-
-_getPlayerScore( player )
-{
- return player.pers["score"];
-}
-
-
-giveTeamScore( event, team, player, victim )
-{
- if ( level.overrideTeamScore )
- return;
-
- teamScore = game["teamScores"][team];
- [[level.onTeamScore]]( event, team, player, victim );
-
- if ( teamScore == game["teamScores"][team] )
- return;
-
- updateTeamScores( team );
-
- thread checkScoreLimit();
-}
-
-_setTeamScore( team, teamScore )
-{
- if ( teamScore == game["teamScores"][team] )
- return;
-
- game["teamScores"][team] = teamScore;
-
- updateTeamScores( team );
-
- thread checkScoreLimit();
-}
-
-updateTeamScores( team1, team2 )
-{
- setTeamScore( team1, getGameScore( team1 ) );
- if ( isdefined( team2 ) )
- setTeamScore( team2, getGameScore( team2 ) );
-
- if ( level.teambased )
- thread sendUpdatedTeamScores();
-}
-
-
-_getTeamScore( team )
-{
- return game["teamScores"][team];
-}
-
-
-default_onTeamScore( event, team, player, victim )
-{
- score = maps\mp\gametypes\_rank::getScoreInfoValue( event );
-
- assert( isDefined( score ) );
-
- otherTeam = level.otherTeam[team];
-
- if ( game["teamScores"][team] > game["teamScores"][otherTeam] )
- level.wasWinning = team;
- else if ( game["teamScores"][otherTeam] > game["teamScores"][team] )
- level.wasWinning = otherTeam;
-
- game["teamScores"][team] += score;
-
- isWinning = "none";
- if ( game["teamScores"][team] > game["teamScores"][otherTeam] )
- isWinning = team;
- else if ( game["teamScores"][otherTeam] > game["teamScores"][team] )
- isWinning = otherTeam;
-
- if ( !level.splitScreen && isWinning != "none" && isWinning != level.wasWinning && getTime() - level.lastStatusTime > 5000 )
- {
- level.lastStatusTime = getTime();
- leaderDialog( "lead_taken", isWinning, "status" );
- if ( level.wasWinning != "none")
- leaderDialog( "lead_lost", level.wasWinning, "status" );
- }
-
- if ( isWinning != "none" )
- level.wasWinning = isWinning;
-}
-
-
-sendUpdatedTeamScores()
-{
- level notify("updating_scores");
- level endon("updating_scores");
- wait .05;
-
- WaitTillSlowProcessAllowed();
-
- for ( i = 0; i < level.players.size; i++ )
- {
- level.players[i] updateScores();
- }
-}
-
-sendUpdatedDMScores()
-{
- level notify("updating_dm_scores");
- level endon("updating_dm_scores");
- wait .05;
-
- WaitTillSlowProcessAllowed();
-
- for ( i = 0; i < level.players.size; i++ )
- {
- level.players[i] updateDMScores();
- level.players[i].updatedDMScores = true;
- }
-}
-
-initPersStat( dataName )
-{
- if( !isDefined( self.pers[dataName] ) )
- self.pers[dataName] = 0;
-}
-
-
-getPersStat( dataName )
-{
- return self.pers[dataName];
-}
-
-
-incPersStat( dataName, increment )
-{
- self.pers[dataName] += increment;
- self maps\mp\gametypes\_persistence::statAdd( dataName, increment );
-}
-
-
-updatePersRatio( ratio, num, denom )
-{
- numValue = self maps\mp\gametypes\_persistence::statGet( num );
- denomValue = self maps\mp\gametypes\_persistence::statGet( denom );
- if ( denomValue == 0 )
- denomValue = 1;
-
- self maps\mp\gametypes\_persistence::statSet( ratio, int( (numValue * 1000) / denomValue ) );
-}
-
-
-updateTeamStatus()
-{
- // run only once per frame, at the end of the frame.
- level notify("updating_team_status");
- level endon("updating_team_status");
- level endon ( "game_ended" );
- waittillframeend;
-
- wait 0; // Required for Callback_PlayerDisconnect to complete before updateTeamStatus can execute
-
- if ( game["state"] == "postgame" )
- return;
-
- resetTimeout();
-
- prof_begin( "updateTeamStatus" );
-
- level.playerCount["allies"] = 0;
- level.playerCount["axis"] = 0;
-
- level.lastAliveCount["allies"] = level.aliveCount["allies"];
- level.lastAliveCount["axis"] = level.aliveCount["axis"];
- level.aliveCount["allies"] = 0;
- level.aliveCount["axis"] = 0;
- level.playerLives["allies"] = 0;
- level.playerLives["axis"] = 0;
- level.alivePlayers["allies"] = [];
- level.alivePlayers["axis"] = [];
- level.activePlayers = [];
-
- players = level.players;
- for ( i = 0; i < players.size; i++ )
- {
- player = players[i];
-
- if ( !isDefined( player ) && level.splitscreen )
- continue;
-
- team = player.team;
- class = player.class;
-
- if ( team != "spectator" && (level.oldschool || (isDefined( class ) && class != "")) )
- {
- level.playerCount[team]++;
-
- if ( player.sessionstate == "playing" )
- {
- level.aliveCount[team]++;
- level.playerLives[team]++;
-
- if ( isAlive( player ) )
- {
- level.alivePlayers[team][level.alivePlayers.size] = player;
- level.activeplayers[ level.activeplayers.size ] = player;
- }
- }
- else
- {
- if ( player maySpawn() )
- level.playerLives[team]++;
- }
- }
- }
-
- if ( level.aliveCount["allies"] + level.aliveCount["axis"] > level.maxPlayerCount )
- level.maxPlayerCount = level.aliveCount["allies"] + level.aliveCount["axis"];
-
- if ( level.aliveCount["allies"] )
- level.everExisted["allies"] = true;
- if ( level.aliveCount["axis"] )
- level.everExisted["axis"] = true;
-
- prof_end( "updateTeamStatus" );
-
- level updateGameEvents();
-}
-
-isValidClass( class )
-{
- if ( level.oldschool )
- {
- assert( !isdefined( class ) );
- return true;
- }
- return isdefined( class ) && class != "";
-}
-
-playTickingSound()
-{
- self endon("death");
- self endon("stop_ticking");
- level endon("game_ended");
-
- while(1)
- {
- self playSound( "ui_mp_suitcasebomb_timer" );
- wait 1.0;
- }
-}
-
-stopTickingSound()
-{
- self notify("stop_ticking");
-}
-
-timeLimitClock()
-{
- level endon ( "game_ended" );
-
- wait .05;
-
- clockObject = spawn( "script_origin", (0,0,0) );
-
- while ( game["state"] == "playing" )
- {
- if ( !level.timerStopped && level.timeLimit )
- {
- timeLeft = getTimeRemaining() / 1000;
- timeLeftInt = int(timeLeft + 0.5); // adding .5 and flooring rounds it.
-
- if ( timeLeftInt >= 30 && timeLeftInt <= 60 )
- level notify ( "match_ending_soon" );
-
- if ( timeLeftInt <= 10 || (timeLeftInt <= 30 && timeLeftInt % 2 == 0) )
- {
- level notify ( "match_ending_very_soon" );
- // don't play a tick at exactly 0 seconds, that's when something should be happening!
- if ( timeLeftInt == 0 )
- break;
-
- clockObject playSound( "ui_mp_timer_countdown" );
- }
-
- // synchronize to be exactly on the second
- if ( timeLeft - floor(timeLeft) >= .05 )
- wait timeLeft - floor(timeLeft);
- }
-
- wait ( 1.0 );
- }
-}
-
-timeLimitClock_Intermission( waitTime )
-{
- setGameEndTime( getTime() + int(waitTime*1000) );
- clockObject = spawn( "script_origin", (0,0,0) );
-
- if ( waitTime >= 10.0 )
- wait ( waitTime - 10.0 );
-
- for ( ;; )
- {
- clockObject playSound( "ui_mp_timer_countdown" );
- wait ( 1.0 );
- }
-}
-
-
-gameTimer()
-{
- level endon ( "game_ended" );
-
- level waittill("prematch_over");
-
- level.startTime = getTime();
- level.discardTime = 0;
-
- if ( isDefined( game["roundMillisecondsAlreadyPassed"] ) )
- {
- level.startTime -= game["roundMillisecondsAlreadyPassed"];
- game["roundMillisecondsAlreadyPassed"] = undefined;
- }
-
- prevtime = gettime();
-
- while ( game["state"] == "playing" )
- {
- if ( !level.timerStopped )
- {
- // the wait isn't always exactly 1 second. dunno why.
- game["timepassed"] += gettime() - prevtime;
- }
- prevtime = gettime();
- wait ( 1.0 );
- }
-}
-
-getTimePassed()
-{
- if ( !isDefined( level.startTime ) )
- return 0;
-
- if ( level.timerStopped )
- return (level.timerPauseTime - level.startTime) - level.discardTime;
- else
- return (gettime() - level.startTime) - level.discardTime;
-
-}
-
-
-pauseTimer()
-{
- if ( level.timerStopped )
- return;
-
- level.timerStopped = true;
- level.timerPauseTime = gettime();
-}
-
-
-resumeTimer()
-{
- if ( !level.timerStopped )
- return;
-
- level.timerStopped = false;
- level.discardTime += gettime() - level.timerPauseTime;
-}
-
-
-startGame()
-{
- thread gameTimer();
- level.timerStopped = false;
- thread maps\mp\gametypes\_spawnlogic::spawnPerFrameUpdate();
-
- prematchPeriod();
- level notify("prematch_over");
-
- thread timeLimitClock();
- thread gracePeriod();
-
- thread musicController();
- thread maps\mp\gametypes\_missions::roundBegin();
-}
-
-
-musicController()
-{
- level endon ( "game_ended" );
-
- if ( !level.hardcoreMode )
- thread suspenseMusic();
-
- level waittill ( "match_ending_soon" );
-
- if ( level.roundLimit == 1 || game["roundsplayed"] == (level.roundLimit - 1) )
- {
- if ( !level.splitScreen )
- {
- if ( game["teamScores"]["allies"] > game["teamScores"]["axis"] )
- {
- if ( !level.hardcoreMode )
- {
- playSoundOnPlayers( game["music"]["winning"], "allies" );
- playSoundOnPlayers( game["music"]["losing"], "axis" );
- }
-
- leaderDialog( "winning", "allies" );
- leaderDialog( "losing", "axis" );
- }
- else if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] )
- {
- if ( !level.hardcoreMode )
- {
- playSoundOnPlayers( game["music"]["winning"], "axis" );
- playSoundOnPlayers( game["music"]["losing"], "allies" );
- }
-
- leaderDialog( "winning", "axis" );
- leaderDialog( "losing", "allies" );
- }
- else
- {
- if ( !level.hardcoreMode )
- playSoundOnPlayers( game["music"]["losing"] );
-
- leaderDialog( "timesup" );
- }
-
- level waittill ( "match_ending_very_soon" );
- leaderDialog( "timesup" );
- }
- }
- else
- {
- if ( !level.hardcoreMode )
- playSoundOnPlayers( game["music"]["losing"] );
-
- leaderDialog( "timesup" );
- }
-}
-
-
-suspenseMusic()
-{
- level endon ( "game_ended" );
- level endon ( "match_ending_soon" );
-
- numTracks = game["music"]["suspense"].size;
- for ( ;; )
- {
- wait ( randomFloatRange( 60, 120 ) );
-
- playSoundOnPlayers( game["music"]["suspense"][randomInt(numTracks)] );
- }
-}
-
-
-waitForPlayers( maxTime )
-{
- endTime = gettime() + maxTime * 1000 - 200;
-
- if ( level.teamBased )
- while( (!level.everExisted[ "axis" ] || !level.everExisted[ "allies" ]) && gettime() < endTime )
- wait ( 0.05 );
- else
- while ( level.maxPlayerCount < 2 && gettime() < endTime )
- wait ( 0.05 );
-}
-
-
-prematchPeriod()
-{
- makeDvarServerInfo( "ui_hud_hardcore", 1 );
- setDvar( "ui_hud_hardcore", 1 );
- level endon( "game_ended" );
-
- if ( level.prematchPeriod > 0 )
- {
- matchStartTimer();
- }
- else
- {
- matchStartTimerSkip();
- }
-
- level.inPrematchPeriod = false;
-
- for ( index = 0; index < level.players.size; index++ )
- {
- level.players[index] freezeControls( false );
- level.players[index] enableWeapons();
-
- hintMessage = getObjectiveHintText( level.players[index].pers["team"] );
- if ( !isDefined( hintMessage ) || !level.players[index].hasSpawned )
- continue;
-
- level.players[index] setClientDvar( "scr_objectiveText", hintMessage );
- level.players[index] thread maps\mp\gametypes\_hud_message::hintMessage( hintMessage );
-
- }
-
- leaderDialog( "offense_obj", game["attackers"], "introboost" );
- leaderDialog( "defense_obj", game["defenders"], "introboost" );
-
- if ( game["state"] != "playing" )
- return;
-
- setDvar( "ui_hud_hardcore", level.hardcoreMode );
-}
-
-
-gracePeriod()
-{
- level endon("game_ended");
-
- wait ( level.gracePeriod );
-
- level notify ( "grace_period_ending" );
- wait ( 0.05 );
-
- level.inGracePeriod = false;
-
- if ( game["state"] != "playing" )
- return;
-
- if ( level.numLives )
- {
- // Players on a team but without a weapon show as dead since they can not get in this round
- players = level.players;
-
- for ( i = 0; i < players.size; i++ )
- {
- player = players[i];
-
- if ( !player.hasSpawned && player.sessionteam != "spectator" && !isAlive( player ) )
- player.statusicon = "hud_status_dead";
- }
- }
-
- level thread updateTeamStatus();
-}
-
-
-announceRoundWinner( winner, delay )
-{
- if ( delay > 0 )
- wait delay;
-
- if ( !isDefined( winner ) || isPlayer( winner ) )
- return;
-
- if ( winner == "allies" )
- {
- leaderDialog( "round_success", "allies" );
- leaderDialog( "round_failure", "axis" );
- }
- else if ( winner == "axis" )
- {
- leaderDialog( "round_success", "axis" );
- leaderDialog( "round_failure", "allies" );
- }
- else
- {
-// leaderDialog( "mission_draw" );
- }
-}
-
-
-announceGameWinner( winner, delay )
-{
- if ( delay > 0 )
- wait delay;
-
- if ( !isDefined( winner ) || isPlayer( winner ) )
- return;
-
- if ( winner == "allies" )
- {
- leaderDialog( "mission_success", "allies" );
- leaderDialog( "mission_failure", "axis" );
- }
- else if ( winner == "axis" )
- {
- leaderDialog( "mission_success", "axis" );
- leaderDialog( "mission_failure", "allies" );
- }
- else
- {
- leaderDialog( "mission_draw" );
- }
-}
-
-
-updateWinStats( winner )
-{
- winner maps\mp\gametypes\_persistence::statAdd( "losses", -1 );
-
- println( "setting winner: " + winner maps\mp\gametypes\_persistence::statGet( "wins" ) );
- winner maps\mp\gametypes\_persistence::statAdd( "wins", 1 );
- winner updatePersRatio( "wlratio", "wins", "losses" );
- winner maps\mp\gametypes\_persistence::statAdd( "cur_win_streak", 1 );
-
- cur_win_streak = winner maps\mp\gametypes\_persistence::statGet( "cur_win_streak" );
- if ( cur_win_streak > winner maps\mp\gametypes\_persistence::statGet( "win_streak" ) )
- winner maps\mp\gametypes\_persistence::statSet( "win_streak", cur_win_streak );
-}
-
-
-updateLossStats( loser )
-{
- loser maps\mp\gametypes\_persistence::statAdd( "losses", 1 );
- loser updatePersRatio( "wlratio", "wins", "losses" );
- loser maps\mp\gametypes\_persistence::statSet( "cur_win_streak", 0 );
-}
-
-
-updateTieStats( loser )
-{
- loser maps\mp\gametypes\_persistence::statAdd( "losses", -1 );
-
- loser maps\mp\gametypes\_persistence::statAdd( "ties", 1 );
- loser updatePersRatio( "wlratio", "wins", "losses" );
- loser maps\mp\gametypes\_persistence::statSet( "cur_win_streak", 0 );
-}
-
-
-updateWinLossStats( winner )
-{
- if ( level.roundLimit > 1 && !hitRoundLimit() )
- return;
-
- players = level.players;
-
- if ( !isDefined( winner ) || ( isDefined( winner ) && !isPlayer( winner ) && winner == "tie" ) )
- {
- for ( i = 0; i < players.size; i++ )
- {
- if ( !isDefined( players[i].pers["team"] ) )
- continue;
-
- if ( level.hostForcedEnd && players[i] getEntityNumber() == 0 )
- return;
-
- updateTieStats( players[i] );
- }
- }
- else if ( isPlayer( winner ) )
- {
- if ( level.hostForcedEnd && winner getEntityNumber() == 0 )
- return;
-
- updateWinStats( winner );
- }
- else
- {
- for ( i = 0; i < players.size; i++ )
- {
- if ( !isDefined( players[i].pers["team"] ) )
- continue;
-
- if ( level.hostForcedEnd && players[i] getEntityNumber() == 0 )
- return;
-
- if ( winner == "tie" )
- updateTieStats( players[i] );
- else if ( players[i].pers["team"] == winner )
- updateWinStats( players[i] );
- }
- }
-}
-
-
-TimeUntilWaveSpawn( minimumWait )
-{
- // the time we'll spawn if we only wait the minimum wait.
- earliestSpawnTime = gettime() + minimumWait * 1000;
-
- lastWaveTime = level.lastWave[self.pers["team"]];
- waveDelay = level.waveDelay[self.pers["team"]] * 1000;
-
- // the number of waves that will have passed since the last wave happened, when the minimum wait is over.
- numWavesPassedEarliestSpawnTime = (earliestSpawnTime - lastWaveTime) / waveDelay;
- // rounded up
- numWaves = ceil( numWavesPassedEarliestSpawnTime );
-
- timeOfSpawn = lastWaveTime + numWaves * waveDelay;
-
- // avoid spawning everyone on the same frame
- if ( isdefined( self.waveSpawnIndex ) )
- timeOfSpawn += 50 * self.waveSpawnIndex;
-
- return (timeOfSpawn - gettime()) / 1000;
-}
-
-TeamKillDelay()
-{
- teamkills = self.pers["teamkills"];
- if ( level.minimumAllowedTeamKills < 0 || teamkills <= level.minimumAllowedTeamKills )
- return 0;
- exceeded = (teamkills - level.minimumAllowedTeamKills);
- return maps\mp\gametypes\_tweakables::getTweakableValue( "team", "teamkillspawndelay" ) * exceeded;
-}
-
-
-TimeUntilSpawn( includeTeamkillDelay )
-{
- if ( level.inGracePeriod && !self.hasSpawned )
- return 0;
-
- respawnDelay = 0;
- if ( self.hasSpawned )
- {
- result = self [[level.onRespawnDelay]]();
- if ( isDefined( result ) )
- respawnDelay = result;
- else
- respawnDelay = getDvarInt( "scr_" + level.gameType + "_playerrespawndelay" );
-
- if ( level.hardcoreMode && !isDefined( result ) && !respawnDelay )
- respawnDelay = 10.0;
-
- if ( includeTeamkillDelay && self.teamKillPunish )
- respawnDelay += TeamKillDelay();
- }
-
- waveBased = (getDvarInt( "scr_" + level.gameType + "_waverespawndelay" ) > 0);
-
- if ( waveBased )
- return self TimeUntilWaveSpawn( respawnDelay );
-
- return respawnDelay;
-}
-
-
-maySpawn()
-{
- if ( level.inOvertime )
- return false;
-
- if ( level.numLives )
- {
- if ( level.teamBased )
- gameHasStarted = ( level.everExisted[ "axis" ] && level.everExisted[ "allies" ] );
- else
- gameHasStarted = (level.maxPlayerCount > 1);
-
- if ( !self.pers["lives"] && gameHasStarted )
- {
- return false;
- }
- else if ( gameHasStarted )
- {
- // disallow spawning for late comers
- if ( !level.inGracePeriod && !self.hasSpawned )
- return false;
- }
- }
- return true;
-}
-
-spawnClient( timeAlreadyPassed )
-{
- assert( isDefined( self.team ) );
- assert( isValidClass( self.class ) );
-
- if ( !self maySpawn() )
- {
- currentorigin = self.origin;
- currentangles = self.angles;
-
- shouldShowRespawnMessage = true;
- if ( level.roundLimit > 1 && game["roundsplayed"] >= (level.roundLimit - 1) )
- shouldShowRespawnMessage = false;
- if ( level.scoreLimit > 1 && level.teambased && game["teamScores"]["allies"] >= level.scoreLimit - 1 && game["teamScores"]["axis"] >= level.scoreLimit - 1 )
- shouldShowRespawnMessage = false;
- if ( shouldShowRespawnMessage )
- {
- setLowerMessage( game["strings"]["spawn_next_round"] );
- self thread removeSpawnMessageShortly( 3 );
- }
- self thread [[level.spawnSpectator]]( currentorigin + (0, 0, 60), currentangles );
- return;
- }
-
- if ( self.waitingToSpawn )
- return;
- self.waitingToSpawn = true;
-
- self waitAndSpawnClient( timeAlreadyPassed );
-
- if ( isdefined( self ) )
- self.waitingToSpawn = false;
-}
-
-waitAndSpawnClient( timeAlreadyPassed )
-{
- self endon ( "disconnect" );
- self endon ( "end_respawn" );
- self endon ( "game_ended" );
-
- if ( !isdefined( timeAlreadyPassed ) )
- timeAlreadyPassed = 0;
-
- spawnedAsSpectator = false;
-
- if ( self.teamKillPunish )
- {
- teamKillDelay = TeamKillDelay();
- if ( teamKillDelay > timeAlreadyPassed )
- {
- teamKillDelay -= timeAlreadyPassed;
- timeAlreadyPassed = 0;
- }
- else
- {
- timeAlreadyPassed -= teamKillDelay;
- teamKillDelay = 0;
- }
-
- if ( teamKillDelay > 0 )
- {
- setLowerMessage( &"MP_FRIENDLY_FIRE_WILL_NOT", teamKillDelay );
-
- self thread respawn_asSpectator( self.origin + (0, 0, 60), self.angles );
- spawnedAsSpectator = true;
-
- wait( teamKillDelay );
- }
-
- self.teamKillPunish = false;
- }
-
- if ( !isdefined( self.waveSpawnIndex ) && isdefined( level.wavePlayerSpawnIndex[self.team] ) )
- {
- self.waveSpawnIndex = level.wavePlayerSpawnIndex[self.team];
- level.wavePlayerSpawnIndex[self.team]++;
- }
-
- timeUntilSpawn = TimeUntilSpawn( false );
- if ( timeUntilSpawn > timeAlreadyPassed )
- {
- timeUntilSpawn -= timeAlreadyPassed;
- timeAlreadyPassed = 0;
- }
- else
- {
- timeAlreadyPassed -= timeUntilSpawn;
- timeUntilSpawn = 0;
- }
-
- if ( timeUntilSpawn > 0 )
- {
- // spawn player into spectator on death during respawn delay, if he switches teams during this time, he will respawn next round
- setLowerMessage( game["strings"]["waiting_to_spawn"], timeUntilSpawn );
-
- if ( !spawnedAsSpectator )
- self thread respawn_asSpectator( self.origin + (0, 0, 60), self.angles );
- spawnedAsSpectator = true;
-
- self waitForTimeOrNotify( timeUntilSpawn, "force_spawn" );
- }
-
- waveBased = (getDvarInt( "scr_" + level.gameType + "_waverespawndelay" ) > 0);
- if ( maps\mp\gametypes\_tweakables::getTweakableValue( "player", "forcerespawn" ) == 0 && self.hasSpawned && !waveBased )
- {
- setLowerMessage( game["strings"]["press_to_spawn"] );
-
- if ( !spawnedAsSpectator )
- self thread respawn_asSpectator( self.origin + (0, 0, 60), self.angles );
- spawnedAsSpectator = true;
-
- self waitRespawnButton();
- }
-
- self.waitingToSpawn = false;
-
- self clearLowerMessage();
-
- self.waveSpawnIndex = undefined;
-
- self thread [[level.spawnPlayer]]();
-}
-
-
-waitForTimeOrNotify( time, notifyname )
-{
- self endon( notifyname );
- wait time;
-}
-
-
-removeSpawnMessageShortly( delay )
-{
- self endon("disconnect");
-
- waittillframeend; // so we don't endon the end_respawn from spawning as a spectator
-
- self endon("end_respawn");
-
- wait delay;
-
- self clearLowerMessage( 2.0 );
-}
-
-
-Callback_StartGameType()
-{
- level.prematchPeriod = 0;
- level.prematchPeriodEnd = 0;
-
- level.intermission = false;
-
- if ( !isDefined( game["gamestarted"] ) )
- {
- // defaults if not defined in level script
- if ( !isDefined( game["allies"] ) )
- game["allies"] = "marines";
- if ( !isDefined( game["axis"] ) )
- game["axis"] = "opfor";
- if ( !isDefined( game["attackers"] ) )
- game["attackers"] = "allies";
- if ( !isDefined( game["defenders"] ) )
- game["defenders"] = "axis";
-
- if ( !isDefined( game["state"] ) )
- game["state"] = "playing";
-
- precacheStatusIcon( "hud_status_dead" );
- precacheStatusIcon( "hud_status_connecting" );
-
- precacheRumble( "damage_heavy" );
-
- precacheShader( "white" );
- precacheShader( "black" );
-
- makeDvarServerInfo( "scr_allies", "usmc" );
- makeDvarServerInfo( "scr_axis", "arab" );
-
- //makeDvarServerInfo( "cg_thirdPersonAngle", 345 );
-
- //setDvar( "cg_thirdPersonAngle", 345 );
-
- game["strings"]["press_to_spawn"] = &"PLATFORM_PRESS_TO_SPAWN";
- if ( level.teamBased )
- {
- game["strings"]["waiting_for_teams"] = &"MP_WAITING_FOR_TEAMS";
- game["strings"]["opponent_forfeiting_in"] = &"MP_OPPONENT_FORFEITING_IN";
- }
- else
- {
- game["strings"]["waiting_for_teams"] = &"MP_WAITING_FOR_PLAYERS";
- game["strings"]["opponent_forfeiting_in"] = &"MP_OPPONENT_FORFEITING_IN";
- }
- game["strings"]["match_starting_in"] = &"MP_MATCH_STARTING_IN";
- game["strings"]["spawn_next_round"] = &"MP_SPAWN_NEXT_ROUND";
- game["strings"]["waiting_to_spawn"] = &"MP_WAITING_TO_SPAWN";
- game["strings"]["match_starting"] = &"MP_MATCH_STARTING";
- game["strings"]["change_class"] = &"MP_CHANGE_CLASS_NEXT_SPAWN";
- game["strings"]["last_stand"] = &"MPUI_LAST_STAND";
-
- game["strings"]["cowards_way"] = &"PLATFORM_COWARDS_WAY_OUT";
-
- game["strings"]["tie"] = &"MP_MATCH_TIE";
- game["strings"]["round_draw"] = &"MP_ROUND_DRAW";
-
- game["strings"]["enemies_eliminated"] = &"MP_ENEMIES_ELIMINATED";
- game["strings"]["score_limit_reached"] = &"MP_SCORE_LIMIT_REACHED";
- game["strings"]["round_limit_reached"] = &"MP_ROUND_LIMIT_REACHED";
- game["strings"]["time_limit_reached"] = &"MP_TIME_LIMIT_REACHED";
- game["strings"]["players_forfeited"] = &"MP_PLAYERS_FORFEITED";
-
- switch ( game["allies"] )
- {
- case "sas":
- game["strings"]["allies_win"] = &"MP_SAS_WIN_MATCH";
- game["strings"]["allies_win_round"] = &"MP_SAS_WIN_ROUND";
- game["strings"]["allies_mission_accomplished"] = &"MP_SAS_MISSION_ACCOMPLISHED";
- game["strings"]["allies_eliminated"] = &"MP_SAS_ELIMINATED";
- game["strings"]["allies_forfeited"] = &"MP_SAS_FORFEITED";
- game["strings"]["allies_name"] = &"MP_SAS_NAME";
-
- game["music"]["spawn_allies"] = "mp_spawn_sas";
- game["music"]["victory_allies"] = "mp_victory_sas";
- game["icons"]["allies"] = "faction_128_sas";
- game["colors"]["allies"] = (0.6,0.64,0.69);
- game["voice"]["allies"] = "UK_1mc_";
- setDvar( "scr_allies", "sas" );
- break;
- case "marines":
- default:
- game["strings"]["allies_win"] = &"MP_MARINES_WIN_MATCH";
- game["strings"]["allies_win_round"] = &"MP_MARINES_WIN_ROUND";
- game["strings"]["allies_mission_accomplished"] = &"MP_MARINES_MISSION_ACCOMPLISHED";
- game["strings"]["allies_eliminated"] = &"MP_MARINES_ELIMINATED";
- game["strings"]["allies_forfeited"] = &"MP_MARINES_FORFEITED";
- game["strings"]["allies_name"] = &"MP_MARINES_NAME";
-
- game["music"]["spawn_allies"] = "mp_spawn_usa";
- game["music"]["victory_allies"] = "mp_victory_usa";
- game["icons"]["allies"] = "faction_128_usmc";
- game["colors"]["allies"] = (0,0,0);
- game["voice"]["allies"] = "US_1mc_";
- setDvar( "scr_allies", "usmc" );
- break;
- }
- switch ( game["axis"] )
- {
- case "russian":
- game["strings"]["axis_win"] = &"MP_SPETSNAZ_WIN_MATCH";
- game["strings"]["axis_win_round"] = &"MP_SPETSNAZ_WIN_ROUND";
- game["strings"]["axis_mission_accomplished"] = &"MP_SPETSNAZ_MISSION_ACCOMPLISHED";
- game["strings"]["axis_eliminated"] = &"MP_SPETSNAZ_ELIMINATED";
- game["strings"]["axis_forfeited"] = &"MP_SPETSNAZ_FORFEITED";
- game["strings"]["axis_name"] = &"MP_SPETSNAZ_NAME";
-
- game["music"]["spawn_axis"] = "mp_spawn_soviet";
- game["music"]["victory_axis"] = "mp_victory_soviet";
- game["icons"]["axis"] = "faction_128_ussr";
- game["colors"]["axis"] = (0.52,0.28,0.28);
- game["voice"]["axis"] = "RU_1mc_";
- setDvar( "scr_axis", "ussr" );
- break;
- case "arab":
- case "opfor":
- default:
- game["strings"]["axis_win"] = &"MP_OPFOR_WIN_MATCH";
- game["strings"]["axis_win_round"] = &"MP_OPFOR_WIN_ROUND";
- game["strings"]["axis_mission_accomplished"] = &"MP_OPFOR_MISSION_ACCOMPLISHED";
- game["strings"]["axis_eliminated"] = &"MP_OPFOR_ELIMINATED";
- game["strings"]["axis_forfeited"] = &"MP_OPFOR_FORFEITED";
- game["strings"]["axis_name"] = &"MP_OPFOR_NAME";
-
- game["music"]["spawn_axis"] = "mp_spawn_opfor";
- game["music"]["victory_axis"] = "mp_victory_opfor";
- game["icons"]["axis"] = "faction_128_arab";
- game["colors"]["axis"] = (0.65,0.57,0.41);
- game["voice"]["axis"] = "AB_1mc_";
- setDvar( "scr_axis", "arab" );
- break;
- }
- game["music"]["defeat"] = "mp_defeat";
- game["music"]["victory_spectator"] = "mp_defeat";
- game["music"]["winning"] = "mp_time_running_out_winning";
- game["music"]["losing"] = "mp_time_running_out_losing";
- game["music"]["victory_tie"] = "mp_defeat";
-
- game["music"]["suspense"] = [];
- game["music"]["suspense"][game["music"]["suspense"].size] = "mp_suspense_01";
- game["music"]["suspense"][game["music"]["suspense"].size] = "mp_suspense_02";
- game["music"]["suspense"][game["music"]["suspense"].size] = "mp_suspense_03";
- game["music"]["suspense"][game["music"]["suspense"].size] = "mp_suspense_04";
- game["music"]["suspense"][game["music"]["suspense"].size] = "mp_suspense_05";
- game["music"]["suspense"][game["music"]["suspense"].size] = "mp_suspense_06";
-
- game["dialog"]["mission_success"] = "mission_success";
- game["dialog"]["mission_failure"] = "mission_fail";
- game["dialog"]["mission_draw"] = "draw";
-
- game["dialog"]["round_success"] = "encourage_win";
- game["dialog"]["round_failure"] = "encourage_lost";
- game["dialog"]["round_draw"] = "draw";
-
- // status
- game["dialog"]["timesup"] = "timesup";
- game["dialog"]["winning"] = "winning";
- game["dialog"]["losing"] = "losing";
- game["dialog"]["lead_lost"] = "lead_lost";
- game["dialog"]["lead_tied"] = "tied";
- game["dialog"]["lead_taken"] = "lead_taken";
- game["dialog"]["last_alive"] = "lastalive";
-
- game["dialog"]["boost"] = "boost";
-
- if ( !isDefined( game["dialog"]["offense_obj"] ) )
- game["dialog"]["offense_obj"] = "boost";
- if ( !isDefined( game["dialog"]["defense_obj"] ) )
- game["dialog"]["defense_obj"] = "boost";
-
- game["dialog"]["hardcore"] = "hardcore";
- game["dialog"]["oldschool"] = "oldschool";
- game["dialog"]["highspeed"] = "highspeed";
- game["dialog"]["tactical"] = "tactical";
-
- game["dialog"]["challenge"] = "challengecomplete";
- game["dialog"]["promotion"] = "promotion";
-
- game["dialog"]["bomb_taken"] = "bomb_taken";
- game["dialog"]["bomb_lost"] = "bomb_lost";
- game["dialog"]["bomb_defused"] = "bomb_defused";
- game["dialog"]["bomb_planted"] = "bomb_planted";
-
- game["dialog"]["obj_taken"] = "securedobj";
- game["dialog"]["obj_lost"] = "lostobj";
-
- game["dialog"]["obj_defend"] = "obj_defend";
- game["dialog"]["obj_destroy"] = "obj_destroy";
- game["dialog"]["obj_capture"] = "capture_obj";
- game["dialog"]["objs_capture"] = "capture_objs";
-
- game["dialog"]["hq_located"] = "hq_located";
- game["dialog"]["hq_enemy_captured"] = "hq_captured";
- game["dialog"]["hq_enemy_destroyed"] = "hq_destroyed";
- game["dialog"]["hq_secured"] = "hq_secured";
- game["dialog"]["hq_offline"] = "hq_offline";
- game["dialog"]["hq_online"] = "hq_online";
-
- game["dialog"]["move_to_new"] = "new_positions";
-
- game["dialog"]["attack"] = "attack";
- game["dialog"]["defend"] = "defend";
- game["dialog"]["offense"] = "offense";
- game["dialog"]["defense"] = "defense";
-
- game["dialog"]["halftime"] = "halftime";
- game["dialog"]["overtime"] = "overtime";
- game["dialog"]["side_switch"] = "switching";
-
- game["dialog"]["flag_taken"] = "ourflag";
- game["dialog"]["flag_dropped"] = "ourflag_drop";
- game["dialog"]["flag_returned"] = "ourflag_return";
- game["dialog"]["flag_captured"] = "ourflag_capt";
- game["dialog"]["enemy_flag_taken"] = "enemyflag";
- game["dialog"]["enemy_flag_dropped"] = "enemyflag_drop";
- game["dialog"]["enemy_flag_returned"] = "enemyflag_return";
- game["dialog"]["enemy_flag_captured"] = "enemyflag_capt";
-
- game["dialog"]["capturing_a"] = "capturing_a";
- game["dialog"]["capturing_b"] = "capturing_b";
- game["dialog"]["capturing_c"] = "capturing_c";
- game["dialog"]["captured_a"] = "capture_a";
- game["dialog"]["captured_b"] = "capture_c";
- game["dialog"]["captured_c"] = "capture_b";
-
- game["dialog"]["securing_a"] = "securing_a";
- game["dialog"]["securing_b"] = "securing_b";
- game["dialog"]["securing_c"] = "securing_c";
- game["dialog"]["secured_a"] = "secure_a";
- game["dialog"]["secured_b"] = "secure_b";
- game["dialog"]["secured_c"] = "secure_c";
-
- game["dialog"]["losing_a"] = "losing_a";
- game["dialog"]["losing_b"] = "losing_b";
- game["dialog"]["losing_c"] = "losing_c";
- game["dialog"]["lost_a"] = "lost_a";
- game["dialog"]["lost_b"] = "lost_b";
- game["dialog"]["lost_c"] = "lost_c";
-
- game["dialog"]["enemy_taking_a"] = "enemy_take_a";
- game["dialog"]["enemy_taking_b"] = "enemy_take_b";
- game["dialog"]["enemy_taking_c"] = "enemy_take_c";
- game["dialog"]["enemy_has_a"] = "enemy_has_a";
- game["dialog"]["enemy_has_b"] = "enemy_has_b";
- game["dialog"]["enemy_has_c"] = "enemy_has_c";
-
- game["dialog"]["lost_all"] = "take_positions";
- game["dialog"]["secure_all"] = "positions_lock";
-
- [[level.onPrecacheGameType]]();
-
- game["gamestarted"] = true;
-
- game["teamScores"]["allies"] = 0;
- game["teamScores"]["axis"] = 0;
-
- // first round, so set up prematch
- level.prematchPeriod = maps\mp\gametypes\_tweakables::getTweakableValue( "game", "playerwaittime" );
- level.prematchPeriodEnd = maps\mp\gametypes\_tweakables::getTweakableValue( "game", "matchstarttime" );
- }
-
- if(!isdefined(game["timepassed"]))
- game["timepassed"] = 0;
-
- if(!isdefined(game["roundsplayed"]))
- game["roundsplayed"] = 0;
-
- level.skipVote = false;
- level.gameEnded = false;
- level.teamSpawnPoints["axis"] = [];
- level.teamSpawnPoints["allies"] = [];
-
- level.objIDStart = 0;
- level.forcedEnd = false;
- level.hostForcedEnd = false;
-
- level.hardcoreMode = getDvarInt( "scr_hardcore" );
- if ( level.hardcoreMode )
- logString( "game mode: hardcore" );
-
- // this gets set to false when someone takes damage or a gametype-specific event happens.
- level.useStartSpawns = true;
-
- // set to 0 to disable
- if ( getdvar( "scr_teamKillPunishCount" ) == "" )
- setdvar( "scr_teamKillPunishCount", "3" );
- level.minimumAllowedTeamKills = getdvarint( "scr_teamKillPunishCount" ) - 1; // punishment starts at the next one
-
- if( getdvar( "r_reflectionProbeGenerate" ) == "1" )
- level waittill( "eternity" );
-
- thread maps\mp\gametypes\_persistence::init();
- thread maps\mp\gametypes\_menus::init();
- thread maps\mp\gametypes\_hud::init();
- thread maps\mp\gametypes\_serversettings::init();
- thread maps\mp\gametypes\_clientids::init();
- thread maps\mp\gametypes\_teams::init();
- thread maps\mp\gametypes\_weapons::init();
- thread maps\mp\gametypes\_scoreboard::init();
- thread maps\mp\gametypes\_killcam::init();
- thread maps\mp\gametypes\_shellshock::init();
- thread maps\mp\gametypes\_deathicons::init();
- thread maps\mp\gametypes\_damagefeedback::init();
- thread maps\mp\gametypes\_healthoverlay::init();
- thread maps\mp\gametypes\_spectating::init();
- thread maps\mp\gametypes\_objpoints::init();
- thread maps\mp\gametypes\_gameobjects::init();
- thread maps\mp\gametypes\_spawnlogic::init();
- thread maps\mp\gametypes\_oldschool::init();
- thread maps\mp\gametypes\_battlechatter_mp::init();
-
- thread maps\mp\gametypes\_hardpoints::init();
-
- if ( level.teamBased )
- thread maps\mp\gametypes\_friendicons::init();
-
- thread maps\mp\gametypes\_hud_message::init();
-
- if ( !level.console )
- thread maps\mp\gametypes\_quickmessages::init();
-
- stringNames = getArrayKeys( game["strings"] );
- for ( index = 0; index < stringNames.size; index++ )
- precacheString( game["strings"][stringNames[index]] );
-
- level.maxPlayerCount = 0;
- level.playerCount["allies"] = 0;
- level.playerCount["axis"] = 0;
- level.aliveCount["allies"] = 0;
- level.aliveCount["axis"] = 0;
- level.playerLives["allies"] = 0;
- level.playerLives["axis"] = 0;
- level.lastAliveCount["allies"] = 0;
- level.lastAliveCount["axis"] = 0;
- level.everExisted["allies"] = false;
- level.everExisted["axis"] = false;
- level.waveDelay["allies"] = 0;
- level.waveDelay["axis"] = 0;
- level.lastWave["allies"] = 0;
- level.lastWave["axis"] = 0;
- level.wavePlayerSpawnIndex["allies"] = 0;
- level.wavePlayerSpawnIndex["axis"] = 0;
- level.alivePlayers["allies"] = [];
- level.alivePlayers["axis"] = [];
- level.activePlayers = [];
-
- if ( !isDefined( level.timeLimit ) )
- registerTimeLimitDvar( "default", 10, 1, 1440 );
-
- if ( !isDefined( level.scoreLimit ) )
- registerScoreLimitDvar( "default", 100, 1, 500 );
-
- if ( !isDefined( level.roundLimit ) )
- registerRoundLimitDvar( "default", 1, 0, 10 );
-
- makeDvarServerInfo( "ui_scorelimit" );
- makeDvarServerInfo( "ui_timelimit" );
- makeDvarServerInfo( "ui_allow_classchange", getDvar( "ui_allow_classchange" ) );
- makeDvarServerInfo( "ui_allow_teamchange", getDvar( "ui_allow_teamchange" ) );
-
- if ( level.numlives )
- setdvar( "g_deadChat", 0 );
- else
- setdvar( "g_deadChat", 1 );
-
- waveDelay = getDvarInt( "scr_" + level.gameType + "_waverespawndelay" );
- if ( waveDelay )
- {
- level.waveDelay["allies"] = waveDelay;
- level.waveDelay["axis"] = waveDelay;
- level.lastWave["allies"] = 0;
- level.lastWave["axis"] = 0;
-
- level thread [[level.waveSpawnTimer]]();
- }
-
- level.inPrematchPeriod = true;
-
- level.gracePeriod = 15;
-
- level.inGracePeriod = true;
-
- level.roundEndDelay = 5;
- level.halftimeRoundEndDelay = 3;
-
- updateTeamScores( "axis", "allies" );
-
- if ( !level.teamBased )
- thread initialDMScoreUpdate();
-
- [[level.onStartGameType]]();
-
- // this must be after onstartgametype for scr_showspawns to work when set at start of game
- /#
- thread maps\mp\gametypes\_dev::init();
- #/
-
- thread startGame();
- level thread updateGameTypeDvars();
-}
-
-initialDMScoreUpdate()
-{
- // the first time we call updateDMScores on a player, we have to send them the whole scoreboard.
- // by calling updateDMScores on each player one at a time,
- // we can avoid having to send the entire scoreboard to every single player
- // the first time someone kills someone else.
- wait .2;
- numSent = 0;
- while(1)
- {
- didAny = false;
-
- players = level.players;
- for ( i = 0; i < players.size; i++ )
- {
- player = players[i];
-
- if ( !isdefined( player ) )
- continue;
-
- if ( isdefined( player.updatedDMScores ) )
- continue;
-
- player.updatedDMScores = true;
- player updateDMScores();
-
- didAny = true;
- wait .5;
- }
-
- if ( !didAny )
- wait 3; // let more players connect
- }
-}
-
-checkRoundSwitch()
-{
- if ( !isdefined( level.roundSwitch ) || !level.roundSwitch )
- return false;
- if ( !isdefined( level.onRoundSwitch ) )
- return false;
-
- assert( game["roundsplayed"] > 0 );
-
- if ( game["roundsplayed"] % level.roundswitch == 0 )
- {
- [[level.onRoundSwitch]]();
- return true;
- }
-
- return false;
-}
-
-
-getGameScore( team )
-{
- return game["teamScores"][team];
-}
-
-
-fakeLag()
-{
- self endon ( "disconnect" );
- self.fakeLag = randomIntRange( 50, 150 );
-
- for ( ;; )
- {
- self setClientDvar( "fakelag_target", self.fakeLag );
- wait ( randomFloatRange( 5.0, 15.0 ) );
- }
-}
-
-listenForGameEnd()
-{
- self waittill( "host_sucks_end_game" );
- if ( level.console )
- endparty();
- level.skipVote = true;
-
- if ( !level.gameEnded )
- level thread maps\mp\gametypes\_globallogic::forceEnd();
-}
-
-
-Callback_PlayerConnect()
-{
- thread notifyConnecting();
-
- self.statusicon = "hud_status_connecting";
- self waittill( "begin" );
- waittillframeend;
- self.statusicon = "";
-
- level notify( "connected", self );
-
-// self thread fakeLag();
- if ( level.console && self getEntityNumber() == 0 )
- self thread listenForGameEnd();
-
- // only print that we connected if we haven't connected in a previous round
- if( !level.splitscreen && !isdefined( self.pers["score"] ) )
- iPrintLn(&"MP_CONNECTED", self);
-
- lpselfnum = self getEntityNumber();
- lpGuid = self getGuid();
- logPrint("J;" + lpGuid + ";" + lpselfnum + ";" + self.name + "\n");
-
- self setClientDvars( "cg_drawSpectatorMessages", 1,
- "ui_hud_hardcore", getDvar( "ui_hud_hardcore" ),
- "player_sprintTime", getDvar( "scr_player_sprinttime" ),
- "ui_uav_client", getDvar( "ui_uav_client" ) );
-
- if ( level.hardcoreMode )
- {
- self setClientDvars( "cg_drawTalk", 3,
- "cg_drawCrosshairNames", 1,
- "cg_drawCrosshair", 0,
- "cg_hudGrenadeIconMaxRangeFrag", 0 );
- }
- else
- {
- self setClientDvars( "cg_drawCrosshair", 1,
- "cg_drawCrosshairNames", 1,
- "cg_hudGrenadeIconMaxRangeFrag", 250 );
- }
-
- self setClientDvars("cg_hudGrenadeIconHeight", "25",
- "cg_hudGrenadeIconWidth", "25",
- "cg_hudGrenadeIconOffset", "50",
- "cg_hudGrenadePointerHeight", "12",
- "cg_hudGrenadePointerWidth", "25",
- "cg_hudGrenadePointerPivot", "12 27",
- "cg_fovscale", "1");
-
- if ( level.oldschool )
- {
- self setClientDvars( "ragdoll_explode_force", 60000,
- "ragdoll_explode_upbias", 0.8,
- "bg_fallDamageMinHeight", 256,
- "bg_fallDamageMaxHeight", 512 );
- }
-
- if ( getdvarint("scr_hitloc_debug") )
- {
- for ( i = 0; i < 6; i++ )
- {
- self setClientDvar( "ui_hitloc_" + i, "" );
- }
- self.hitlocInited = true;
- }
-
- self initPersStat( "score" );
- self.score = self.pers["score"];
-
- self initPersStat( "deaths" );
- self.deaths = self getPersStat( "deaths" );
-
- self initPersStat( "suicides" );
- self.suicides = self getPersStat( "suicides" );
-
- self initPersStat( "kills" );
- self.kills = self getPersStat( "kills" );
-
- self initPersStat( "headshots" );
- self.headshots = self getPersStat( "headshots" );
-
- self initPersStat( "assists" );
- self.assists = self getPersStat( "assists" );
-
- self initPersStat( "teamkills" );
- self.teamKillPunish = false;
- if ( level.minimumAllowedTeamKills >= 0 && self.pers["teamkills"] > level.minimumAllowedTeamKills )
- self thread reduceTeamKillsOverTime();
-
- if( getdvar( "r_reflectionProbeGenerate" ) == "1" )
- level waittill( "eternity" );
-
- self.killedPlayers = [];
- self.killedPlayersCurrent = [];
- self.killedBy = [];
-
- self.leaderDialogQueue = [];
- self.leaderDialogActive = false;
- self.leaderDialogGroups = [];
- self.leaderDialogGroup = "";
-
- self.cur_kill_streak = 0;
- self.cur_death_streak = 0;
- self.death_streak = self maps\mp\gametypes\_persistence::statGet( "death_streak" );
- self.kill_streak = self maps\mp\gametypes\_persistence::statGet( "kill_streak" );
- self.lastGrenadeSuicideTime = -1;
-
- self.teamkillsThisRound = 0;
-
- self.pers["lives"] = level.numLives;
-
- self.hasSpawned = false;
- self.waitingToSpawn = false;
- self.deathCount = 0;
-
- self.wasAliveAtMatchStart = false;
-
- self thread maps\mp\_flashgrenades::monitorFlash();
-
- if ( level.numLives )
- {
- self setClientDvars("cg_deadChatWithDead", 1,
- "cg_deadChatWithTeam", 0,
- "cg_deadHearTeamLiving", 0,
- "cg_deadHearAllLiving", 0,
- "cg_everyoneHearsEveryone", 0 );
- }
- else
- {
- self setClientDvars("cg_deadChatWithDead", 0,
- "cg_deadChatWithTeam", 1,
- "cg_deadHearTeamLiving", 1,
- "cg_deadHearAllLiving", 0,
- "cg_everyoneHearsEveryone", 0 );
- }
-
- level.players[level.players.size] = self;
-
- if( level.splitscreen )
- setdvar( "splitscreen_playerNum", level.players.size );
-
- if ( level.teambased )
- self updateScores();
-
- // When joining a game in progress, if the game is at the post game state (scoreboard) the connecting player should spawn into intermission
- if ( game["state"] == "postgame" )
- {
- self.pers["team"] = "spectator";
- self.team = "spectator";
-
- self setClientDvars( "ui_hud_hardcore", 1,
- "cg_drawSpectatorMessages", 0 );
-
- [[level.spawnIntermission]]();
- self closeMenu();
- self closeInGameMenu();
- return;
- }
-
- updateLossStats( self );
-
- level endon( "game_ended" );
-
- if ( level.oldschool )
- {
- self.pers["class"] = undefined;
- self.class = self.pers["class"];
- }
-
- if ( isDefined( self.pers["team"] ) )
- self.team = self.pers["team"];
-
- if ( isDefined( self.pers["class"] ) )
- self.class = self.pers["class"];
-
- if ( !isDefined( self.pers["team"] ) )
- {
- // Don't set .sessionteam until we've gotten the assigned team from code,
- // because it overrides the assigned team.
- self.pers["team"] = "spectator";
- self.team = "spectator";
- self.sessionstate = "dead";
-
- self updateObjectiveText();
-
- [[level.spawnSpectator]]();
-
- if ( level.rankedMatch && level.console )
- {
- [[level.autoassign]]();
-
- //self thread forceSpawn();
- self thread kickIfDontSpawn();
- }
- else if ( !level.teamBased && level.console )
- {
- [[level.autoassign]]();
- }
- else
- {
- self setclientdvar( "g_scriptMainMenu", game["menu_team"] );
- self openMenu( game["menu_team"] );
- }
-
- if ( self.pers["team"] == "spectator" )
- self.sessionteam = "spectator";
-
- if ( level.teamBased )
- {
- // set team and spectate permissions so the map shows waypoint info on connect
- self.sessionteam = self.pers["team"];
- if ( !isAlive( self ) )
- self.statusicon = "hud_status_dead";
- self thread maps\mp\gametypes\_spectating::setSpectatePermissions();
- }
- }
- else if ( self.pers["team"] == "spectator" )
- {
- self setclientdvar( "g_scriptMainMenu", game["menu_team"] );
- self.sessionteam = "spectator";
- self.sessionstate = "spectator";
- [[level.spawnSpectator]]();
- }
- else
- {
- self.sessionteam = self.pers["team"];
- self.sessionstate = "dead";
-
- self updateObjectiveText();
-
- [[level.spawnSpectator]]();
-
- if ( isValidClass( self.pers["class"] ) )
- {
- self thread [[level.spawnClient]]();
- }
- else
- {
- self showMainMenuForTeam();
- }
-
- self thread maps\mp\gametypes\_spectating::setSpectatePermissions();
- }
-
- if ( isDefined( self.pers["isBot"] ) )
- return;
-
- // TO DO: DELETE THIS WHEN CODE HAS CHECKSUM SUPPORT! :: Check for stat integrity
- for( i=0; i<5; i++ )
- {
- if( !level.onlinegame )
- return;
-
- if( self getstat( 205+(i*10) ) == 0 )
- {
- kick( self getentitynumber() );
- return;
- }
- }
-}
-
-
-forceSpawn()
-{
- self endon ( "death" );
- self endon ( "disconnect" );
- self endon ( "spawned" );
-
- wait ( 60.0 );
-
- if ( self.hasSpawned )
- return;
-
- if ( self.pers["team"] == "spectator" )
- return;
-
- if ( !isValidClass( self.pers["class"] ) )
- {
- if ( getDvarInt( "onlinegame" ) )
- self.pers["class"] = "CLASS_CUSTOM1";
- else
- self.pers["class"] = "CLASS_ASSAULT";
-
- self.class = self.pers["class"];
- }
-
- self closeMenus();
- self thread [[level.spawnClient]]();
-}
-
-kickIfDontSpawn()
-{
- if ( self getEntityNumber() == 0 )
- {
- // don't try to kick the host
- return;
- }
-
- self kickIfIDontSpawnInternal();
- // clear any client dvars here,
- // like if we set anything to change the menu appearance to warn them of kickness
-}
-
-kickIfIDontSpawnInternal()
-{
- self endon ( "death" );
- self endon ( "disconnect" );
- self endon ( "spawned" );
-
- waittime = 90;
- if ( getdvar("scr_kick_time") != "" )
- waittime = getdvarfloat("scr_kick_time");
- mintime = 45;
- if ( getdvar("scr_kick_mintime") != "" )
- mintime = getdvarfloat("scr_kick_mintime");
-
- starttime = gettime();
-
- kickWait( waittime );
-
- timePassed = (gettime() - starttime)/1000;
- if ( timePassed < waittime - .1 && timePassed < mintime )
- return;
-
- if ( self.hasSpawned )
- return;
-
- if ( self.pers["team"] == "spectator" )
- return;
-
- kick( self getEntityNumber() );
-}
-
-kickWait( waittime )
-{
- level endon("game_ended");
- wait waittime;
-}
-
-Callback_PlayerDisconnect()
-{
- self removePlayerOnDisconnect();
-
- if ( !level.gameEnded )
- self logXPGains();
-
- if ( level.splitscreen )
- {
- players = level.players;
-
- if ( players.size <= 1 )
- level thread maps\mp\gametypes\_globallogic::forceEnd();
-
- // passing number of players to menus in splitscreen to display leave or end game option
- setdvar( "splitscreen_playerNum", players.size );
- }
-
- if ( isDefined( self.score ) && isDefined( self.pers["team"] ) )
- {
- setPlayerTeamRank( self, level.dropTeam, self.score - 5 * self.deaths );
- self logString( "team: score " + self.pers["team"] + ":" + self.score );
- level.dropTeam += 1;
- }
-
- [[level.onPlayerDisconnect]]();
-
- lpselfnum = self getEntityNumber();
- lpGuid = self getGuid();
- logPrint("Q;" + lpGuid + ";" + lpselfnum + ";" + self.name + "\n");
-
- for ( entry = 0; entry < level.players.size; entry++ )
- {
- if ( level.players[entry] == self )
- {
- while ( entry < level.players.size-1 )
- {
- level.players[entry] = level.players[entry+1];
- entry++;
- }
- level.players[entry] = undefined;
- break;
- }
- }
- for ( entry = 0; entry < level.players.size; entry++ )
- {
- if ( isDefined( level.players[entry].killedPlayers[""+self.clientid] ) )
- level.players[entry].killedPlayers[""+self.clientid] = undefined;
-
- if ( isDefined( level.players[entry].killedPlayersCurrent[""+self.clientid] ) )
- level.players[entry].killedPlayersCurrent[""+self.clientid] = undefined;
-
- if ( isDefined( level.players[entry].killedBy[""+self.clientid] ) )
- level.players[entry].killedBy[""+self.clientid] = undefined;
- }
-
- if ( level.gameEnded )
- self removeDisconnectedPlayerFromPlacement();
-
- level thread updateTeamStatus();
-}
-
-
-removePlayerOnDisconnect()
-{
- for ( entry = 0; entry < level.players.size; entry++ )
- {
- if ( level.players[entry] == self )
- {
- while ( entry < level.players.size-1 )
- {
- level.players[entry] = level.players[entry+1];
- entry++;
- }
- level.players[entry] = undefined;
- break;
- }
- }
-}
-
-isHeadShot( sWeapon, sHitLoc, sMeansOfDeath )
-{
- return (sHitLoc == "head" || sHitLoc == "helmet") && sMeansOfDeath != "MOD_MELEE" && sMeansOfDeath != "MOD_IMPACT" && !isMG( sWeapon );
-}
-
-
-Callback_PlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime )
-{
- // create a class specialty checks; CAC:bulletdamage, CAC:armorvest
- iDamage = maps\mp\gametypes\_class::cac_modified_damage( self, eAttacker, iDamage, sMeansOfDeath );
- self.iDFlags = iDFlags;
- self.iDFlagsTime = getTime();
-
- if ( game["state"] == "postgame" )
- return;
-
- if ( self.sessionteam == "spectator" )
- return;
-
- if ( isDefined( self.canDoCombat ) && !self.canDoCombat )
- return;
-
- if ( isDefined( eAttacker ) && isPlayer( eAttacker ) && isDefined( eAttacker.canDoCombat ) && !eAttacker.canDoCombat )
- return;
-
- prof_begin( "Callback_PlayerDamage flags/tweaks" );
-
- // Don't do knockback if the damage direction was not specified
- if( !isDefined( vDir ) )
- iDFlags |= level.iDFLAGS_NO_KNOCKBACK;
-
- friendly = false;
-
- if ( (level.teamBased && (self.health == self.maxhealth)) || !isDefined( self.attackers ) )
- {
- self.attackers = [];
- self.attackerData = [];
- }
-
- if ( isHeadShot( sWeapon, sHitLoc, sMeansOfDeath ) )
- sMeansOfDeath = "MOD_HEAD_SHOT";
-
- // explosive barrel/car detection
- if ( sWeapon == "none" && isDefined( eInflictor ) )
- {
- if ( isDefined( eInflictor.targetname ) && eInflictor.targetname == "explodable_barrel" )
- sWeapon = "explodable_barrel";
- else if ( isDefined( eInflictor.destructible_type ) && isSubStr( eInflictor.destructible_type, "vehicle_" ) )
- sWeapon = "destructible_car";
- }
-
- prof_end( "Callback_PlayerDamage flags/tweaks" );
-
- // check for completely getting out of the damage
- if( !(iDFlags & level.iDFLAGS_NO_PROTECTION) )
- {
- // return if helicopter friendly fire is on
- if ( level.teamBased && isdefined( level.chopper ) && isdefined( eAttacker ) && eAttacker == level.chopper && eAttacker.team == self.pers["team"] )
- {
-// if( level.friendlyfire == 0 )
-// {
- prof_end( "Callback_PlayerDamage player" );
- return;
-// }
- }
-
- if ( (isSubStr( sMeansOfDeath, "MOD_GRENADE" ) || isSubStr( sMeansOfDeath, "MOD_EXPLOSIVE" ) || isSubStr( sMeansOfDeath, "MOD_PROJECTILE" )) && isDefined( eInflictor ) )
- {
- // protect players from spawnkill grenades
- if ( eInflictor.classname == "grenade" && (self.lastSpawnTime + 3500) > getTime() && distance( eInflictor.origin, self.lastSpawnPoint.origin ) < 250 )
- {
- prof_end( "Callback_PlayerDamage player" );
- return;
- }
-
- self.explosiveInfo = [];
- self.explosiveInfo["damageTime"] = getTime();
- self.explosiveInfo["damageId"] = eInflictor getEntityNumber();
- self.explosiveInfo["returnToSender"] = false;
- self.explosiveInfo["counterKill"] = false;
- self.explosiveInfo["chainKill"] = false;
- self.explosiveInfo["cookedKill"] = false;
- self.explosiveInfo["throwbackKill"] = false;
- self.explosiveInfo["weapon"] = sWeapon;
-
- isFrag = isSubStr( sWeapon, "frag_" );
-
- if ( eAttacker != self )
- {
- if ( (isSubStr( sWeapon, "c4_" ) || isSubStr( sWeapon, "claymore_" )) && isDefined( eAttacker ) && isDefined( eInflictor.owner ) )
- {
- self.explosiveInfo["returnToSender"] = (eInflictor.owner == self);
- self.explosiveInfo["counterKill"] = isDefined( eInflictor.wasDamaged );
- self.explosiveInfo["chainKill"] = isDefined( eInflictor.wasChained );
- self.explosiveInfo["bulletPenetrationKill"] = isDefined( eInflictor.wasDamagedFromBulletPenetration );
- self.explosiveInfo["cookedKill"] = false;
- }
- if ( isDefined( eAttacker.lastGrenadeSuicideTime ) && eAttacker.lastGrenadeSuicideTime >= gettime() - 50 && isFrag )
- {
- self.explosiveInfo["suicideGrenadeKill"] = true;
- }
- else
- {
- self.explosiveInfo["suicideGrenadeKill"] = false;
- }
- }
-
- if ( isFrag )
- {
- self.explosiveInfo["cookedKill"] = isDefined( eInflictor.isCooked );
- self.explosiveInfo["throwbackKill"] = isDefined( eInflictor.threwBack );
- }
- }
-
- if ( isPlayer( eAttacker ) )
- eAttacker.pers["participation"]++;
-
- prevHealthRatio = self.health / self.maxhealth;
-
- if ( level.teamBased && isPlayer( eAttacker ) && (self != eAttacker) && (self.pers["team"] == eAttacker.pers["team"]) )
- {
- prof_begin( "Callback_PlayerDamage player" ); // profs automatically end when the function returns
- if ( level.friendlyfire == 0 ) // no one takes damage
- {
- if ( sWeapon == "artillery_mp" )
- self damageShellshockAndRumble( eInflictor, sWeapon, sMeansOfDeath, iDamage );
- return;
- }
- else if ( level.friendlyfire == 1 ) // the friendly takes damage
- {
- // Make sure at least one point of damage is done
- if ( iDamage < 1 )
- iDamage = 1;
-
- self.lastDamageWasFromEnemy = false;
-
- self finishPlayerDamageWrapper(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime);
- }
- else if ( level.friendlyfire == 2 && isAlive( eAttacker ) ) // only the attacker takes damage
- {
- iDamage = int(iDamage * .5);
-
- // Make sure at least one point of damage is done
- if(iDamage < 1)
- iDamage = 1;
-
- eAttacker.lastDamageWasFromEnemy = false;
-
- eAttacker.friendlydamage = true;
- eAttacker finishPlayerDamageWrapper(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime);
- eAttacker.friendlydamage = undefined;
- }
- else if ( level.friendlyfire == 3 && isAlive( eAttacker ) ) // both friendly and attacker take damage
- {
- iDamage = int(iDamage * .5);
-
- // Make sure at least one point of damage is done
- if ( iDamage < 1 )
- iDamage = 1;
-
- self.lastDamageWasFromEnemy = false;
- eAttacker.lastDamageWasFromEnemy = false;
-
- self finishPlayerDamageWrapper(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime);
- if ( isAlive( eAttacker ) ) // may have died due to friendly fire punishment
- {
- eAttacker.friendlydamage = true;
- eAttacker finishPlayerDamageWrapper(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime);
- eAttacker.friendlydamage = undefined;
- }
- }
-
- friendly = true;
- }
- else
- {
- prof_begin( "Callback_PlayerDamage world" );
- // Make sure at least one point of damage is done
- if(iDamage < 1)
- iDamage = 1;
-
- if ( level.teamBased && isDefined( eAttacker ) && isPlayer( eAttacker ) )
- {
- if ( !isdefined( self.attackerData[eAttacker.clientid] ) )
- {
- self.attackers[ self.attackers.size ] = eAttacker;
- // we keep an array of attackers by their client ID so we can easily tell
- // if they're already one of the existing attackers in the above if().
- // we store in this array data that is useful for other things, like challenges
- self.attackerData[eAttacker.clientid] = false;
- }
- if ( maps\mp\gametypes\_weapons::isPrimaryWeapon( sWeapon ) )
- self.attackerData[eAttacker.clientid] = true;
- }
-
- if ( isdefined( eAttacker ) )
- level.lastLegitimateAttacker = eAttacker;
-
- if ( isdefined( eAttacker ) && isPlayer( eAttacker ) && isDefined( sWeapon ) )
- eAttacker maps\mp\gametypes\_weapons::checkHit( sWeapon );
-
- /*
- if ( isPlayer( eInflictor ) )
- eInflictor maps\mp\gametypes\_persistence::statAdd( "hits", 1 );
- */
-
- if ( issubstr( sMeansOfDeath, "MOD_GRENADE" ) && isDefined( eInflictor.isCooked ) )
- self.wasCooked = getTime();
- else
- self.wasCooked = undefined;
-
- self.lastDamageWasFromEnemy = (isDefined( eAttacker ) && (eAttacker != self));
-
- self finishPlayerDamageWrapper(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime);
-
- self thread maps\mp\gametypes\_missions::playerDamaged(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, sHitLoc );
-
- prof_end( "Callback_PlayerDamage world" );
- }
-
- if ( isdefined(eAttacker) && eAttacker != self )
- {
- hasBodyArmor = false;
- if ( self hasPerk( "specialty_armorvest" ) )
- {
- hasBodyArmor = true;
- /*
- damageScalar = level.cac_armorvest_data / 100;
- if ( prevHealthRatio > damageScalar )
- hasBodyArmor = true;
- */
- }
- if ( iDamage > 0 )
- eAttacker thread maps\mp\gametypes\_damagefeedback::updateDamageFeedback( hasBodyArmor );
- }
-
- self.hasDoneCombat = true;
- }
-
- if ( isdefined( eAttacker ) && eAttacker != self && !friendly )
- level.useStartSpawns = false;
-
- prof_begin( "Callback_PlayerDamage log" );
-
- // Do debug print if it's enabled
- if(getDvarInt("g_debugDamage"))
- println("client:" + self getEntityNumber() + " health:" + self.health + " attacker:" + eAttacker.clientid + " inflictor is player:" + isPlayer(eInflictor) + " damage:" + iDamage + " hitLoc:" + sHitLoc);
-
- if(self.sessionstate != "dead")
- {
- lpselfnum = self getEntityNumber();
- lpselfname = self.name;
- lpselfteam = self.pers["team"];
- lpselfGuid = self getGuid();
- lpattackerteam = "";
-
- if(isPlayer(eAttacker))
- {
- lpattacknum = eAttacker getEntityNumber();
- lpattackGuid = eAttacker getGuid();
- lpattackname = eAttacker.name;
- lpattackerteam = eAttacker.pers["team"];
- }
- else
- {
- lpattacknum = -1;
- lpattackGuid = "";
- lpattackname = "";
- lpattackerteam = "world";
- }
-
- logPrint("D;" + lpselfGuid + ";" + lpselfnum + ";" + lpselfteam + ";" + lpselfname + ";" + lpattackGuid + ";" + lpattacknum + ";" + lpattackerteam + ";" + lpattackname + ";" + sWeapon + ";" + iDamage + ";" + sMeansOfDeath + ";" + sHitLoc + "\n");
- }
-
- if ( getdvarint("scr_hitloc_debug") )
- {
- if ( !isdefined( eAttacker.hitlocInited ) )
- {
- for ( i = 0; i < 6; i++ )
- {
- eAttacker setClientDvar( "ui_hitloc_" + i, "" );
- }
- eAttacker.hitlocInited = true;
- }
-
- if ( isPlayer( eAttacker ) && !level.splitscreen )
- {
- colors = [];
- colors[0] = 2;
- colors[1] = 3;
- colors[2] = 5;
- colors[3] = 7;
-
- elemcount = 6;
- if ( !isdefined( eAttacker.damageInfo ) )
- {
- eAttacker.damageInfo = [];
- for ( i = 0; i < elemcount; i++ )
- {
- eAttacker.damageInfo[i] = spawnstruct();
- eAttacker.damageInfo[i].damage = 0;
- eAttacker.damageInfo[i].hitloc = "";
- eAttacker.damageInfo[i].bp = false;
- eAttacker.damageInfo[i].jugg = false;
- eAttacker.damageInfo[i].colorIndex = 0;
- }
- eAttacker.damageInfoColorIndex = 0;
- eAttacker.damageInfoVictim = undefined;
- }
-
- for ( i = elemcount-1; i > 0; i-- )
- {
- eAttacker.damageInfo[i].damage = eAttacker.damageInfo[i - 1].damage;
- eAttacker.damageInfo[i].hitloc = eAttacker.damageInfo[i - 1].hitloc;
- eAttacker.damageInfo[i].bp = eAttacker.damageInfo[i - 1].bp;
- eAttacker.damageInfo[i].jugg = eAttacker.damageInfo[i - 1].jugg;
- eAttacker.damageInfo[i].colorIndex = eAttacker.damageInfo[i - 1].colorIndex;
- }
- eAttacker.damageInfo[0].damage = iDamage;
- eAttacker.damageInfo[0].hitloc = sHitLoc;
- eAttacker.damageInfo[0].bp = (iDFlags & level.iDFLAGS_PENETRATION);
- eAttacker.damageInfo[0].jugg = self hasPerk( "specialty_armorvest" );
- if ( isdefined( eAttacker.damageInfoVictim ) && eAttacker.damageInfoVictim != self )
- {
- eAttacker.damageInfoColorIndex++;
- if ( eAttacker.damageInfoColorIndex == colors.size )
- eAttacker.damageInfoColorIndex = 0;
- }
- eAttacker.damageInfoVictim = self;
- eAttacker.damageInfo[0].colorIndex = eAttacker.damageInfoColorIndex;
-
- for ( i = 0; i < elemcount; i++ )
- {
- color = "^" + colors[ eAttacker.damageInfo[i].colorIndex ];
- if ( eAttacker.damageInfo[i].hitloc != "" )
- {
- val = color + eAttacker.damageInfo[i].hitloc;
- if ( eAttacker.damageInfo[i].bp )
- val += " (BP)";
- if ( eAttacker.damageInfo[i].jugg )
- val += " (Jugg)";
- eAttacker setClientDvar( "ui_hitloc_" + i, val );
- }
- eAttacker setClientDvar( "ui_hitloc_damage_" + i, color + eAttacker.damageInfo[i].damage );
- }
- }
- }
-
- prof_end( "Callback_PlayerDamage log" );
-}
-
-finishPlayerDamageWrapper( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime )
-{
- self finishPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime );
-
- self damageShellshockAndRumble( eInflictor, sWeapon, sMeansOfDeath, iDamage );
-}
-
-damageShellshockAndRumble( eInflictor, sWeapon, sMeansOfDeath, iDamage )
-{
- self thread maps\mp\gametypes\_weapons::onWeaponDamage( eInflictor, sWeapon, sMeansOfDeath, iDamage );
- self PlayRumbleOnEntity( "damage_heavy" );
-}
-
-
-Callback_PlayerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration)
-{
- self endon( "spawned" );
- self notify( "killed_player" );
-
- if ( self.sessionteam == "spectator" )
- return;
-
- if ( game["state"] == "postgame" )
- return;
-
- prof_begin( "PlayerKilled pre constants" );
-
- deathTimeOffset = 0;
- if ( isdefined( self.useLastStandParams ) )
- {
- self.useLastStandParams = undefined;
-
- assert( isdefined( self.lastStandParams ) );
-
- eInflictor = self.lastStandParams.eInflictor;
- attacker = self.lastStandParams.attacker;
- iDamage = self.lastStandParams.iDamage;
- sMeansOfDeath = self.lastStandParams.sMeansOfDeath;
- sWeapon = self.lastStandParams.sWeapon;
- vDir = self.lastStandParams.vDir;
- sHitLoc = self.lastStandParams.sHitLoc;
-
- deathTimeOffset = (gettime() - self.lastStandParams.lastStandStartTime) / 1000;
-
- self.lastStandParams = undefined;
- }
-
- if( isHeadShot( sWeapon, sHitLoc, sMeansOfDeath ) )
- sMeansOfDeath = "MOD_HEAD_SHOT";
-
- if( attacker.classname == "script_vehicle" && isDefined( attacker.owner ) )
- attacker = attacker.owner;
-
- // send out an obituary message to all clients about the kill
- if( level.teamBased && isDefined( attacker.pers ) && self.team == attacker.team && sMeansOfDeath == "MOD_GRENADE" && level.friendlyfire == 0 )
- obituary(self, self, sWeapon, sMeansOfDeath);
- else
- obituary(self, attacker, sWeapon, sMeansOfDeath);
-
-// self maps\mp\gametypes\_weapons::updateWeaponUsageStats();
- if ( !level.inGracePeriod )
- {
- self maps\mp\gametypes\_weapons::dropWeaponForDeath( attacker );
- self maps\mp\gametypes\_weapons::dropOffhand();
- }
-
- maps\mp\gametypes\_spawnlogic::deathOccured(self, attacker);
-
- self.sessionstate = "dead";
- self.statusicon = "hud_status_dead";
-
- self.pers["weapon"] = undefined;
-
- self.killedPlayersCurrent = [];
-
- self.deathCount++;
-
- if( !isDefined( self.switching_teams ) )
- {
- // if team killed we reset kill streak, but dont count death and death streak
- if ( isPlayer( attacker ) && level.teamBased && ( attacker != self ) && ( self.pers["team"] == attacker.pers["team"] ) )
- {
- self.cur_kill_streak = 0;
- }
- else
- {
- self incPersStat( "deaths", 1 );
- self.deaths = self getPersStat( "deaths" );
- self updatePersRatio( "kdratio", "kills", "deaths" );
-
- self.cur_kill_streak = 0;
- self.cur_death_streak++;
-
- if ( self.cur_death_streak > self.death_streak )
- {
- self maps\mp\gametypes\_persistence::statSet( "death_streak", self.cur_death_streak );
- self.death_streak = self.cur_death_streak;
- }
- }
- }
-
- lpselfnum = self getEntityNumber();
- lpselfname = self.name;
- lpattackGuid = "";
- lpattackname = "";
- lpselfteam = "";
- lpselfguid = self getGuid();
- lpattackerteam = "";
-
- lpattacknum = -1;
-
- prof_end( "PlayerKilled pre constants" );
-
- if( isPlayer( attacker ) )
- {
- lpattackGuid = attacker getGuid();
- lpattackname = attacker.name;
-
- if ( attacker == self ) // killed himself
- {
- doKillcam = false;
-
- // suicide kill cam
- //lpattacknum = attacker getEntityNumber();
- //doKillcam = true;
-
- // switching teams
- if ( isDefined( self.switching_teams ) )
- {
- if ( !level.teamBased && ((self.leaving_team == "allies" && self.joining_team == "axis") || (self.leaving_team == "axis" && self.joining_team == "allies")) )
- {
- playerCounts = self maps\mp\gametypes\_teams::CountPlayers();
- playerCounts[self.leaving_team]--;
- playerCounts[self.joining_team]++;
-
- if( (playerCounts[self.joining_team] - playerCounts[self.leaving_team]) > 1 )
- {
- self thread [[level.onXPEvent]]( "suicide" );
- self incPersStat( "suicides", 1 );
- self.suicides = self getPersStat( "suicides" );
- }
- }
- }
- else
- {
- self thread [[level.onXPEvent]]( "suicide" );
- self incPersStat( "suicides", 1 );
- self.suicides = self getPersStat( "suicides" );
-
- if ( sMeansOfDeath == "MOD_SUICIDE" && sHitLoc == "none" && self.throwingGrenade )
- {
- self.lastGrenadeSuicideTime = gettime();
- }
- }
-
- if( isDefined( self.friendlydamage ) )
- self iPrintLn(&"MP_FRIENDLY_FIRE_WILL_NOT");
- }
- else
- {
- prof_begin( "PlayerKilled attacker" );
-
- lpattacknum = attacker getEntityNumber();
-
- doKillcam = true;
-
- if ( level.teamBased && self.pers["team"] == attacker.pers["team"] && sMeansOfDeath == "MOD_GRENADE" && level.friendlyfire == 0 )
- {
- }
- else if ( level.teamBased && self.pers["team"] == attacker.pers["team"] ) // killed by a friendly
- {
- attacker thread [[level.onXPEvent]]( "teamkill" );
-
- attacker.pers["teamkills"] += 1.0;
-
- attacker.teamkillsThisRound++;
-
- if ( maps\mp\gametypes\_tweakables::getTweakableValue( "team", "teamkillpointloss" ) )
- {
- scoreSub = maps\mp\gametypes\_rank::getScoreInfoValue( "kill" );
- _setPlayerScore( attacker, _getPlayerScore( attacker ) - scoreSub );
- }
-
- if ( getTimePassed() < 5000 )
- teamKillDelay = 1;
- else if ( attacker.pers["teamkills"] > 1 && getTimePassed() < (8000 + (attacker.pers["teamkills"] * 1000)) )
- teamKillDelay = 1;
- else
- teamKillDelay = attacker TeamKillDelay();
-
- if ( teamKillDelay > 0 )
- {
- attacker.teamKillPunish = true;
- attacker suicide();
- attacker thread reduceTeamKillsOverTime();
- }
- }
- else
- {
- prof_begin( "pks1" );
-
- if ( sMeansOfDeath == "MOD_HEAD_SHOT" )
- {
- attacker incPersStat( "headshots", 1 );
- attacker.headshots = attacker getPersStat( "headshots" );
-
- if ( isDefined( attacker.lastStand ) )
- value = maps\mp\gametypes\_rank::getScoreInfoValue( "headshot" ) * 2;
- else
- value = undefined;
-
- attacker thread maps\mp\gametypes\_rank::giveRankXP( "headshot", value );
- attacker playLocalSound( "bullet_impact_headshot_2" );
- }
- else
- {
- if ( isDefined( attacker.lastStand ) )
- value = maps\mp\gametypes\_rank::getScoreInfoValue( "kill" ) * 2;
- else
- value = undefined;
-
- attacker thread maps\mp\gametypes\_rank::giveRankXP( "kill", value );
- }
-
- attacker incPersStat( "kills", 1 );
- attacker.kills = attacker getPersStat( "kills" );
- attacker updatePersRatio( "kdratio", "kills", "deaths" );
-
- if ( isAlive( attacker ) )
- {
- if ( !isDefined( eInflictor ) || !isDefined( eInflictor.requiredDeathCount ) || attacker.deathCount == eInflictor.requiredDeathCount )
- attacker.cur_kill_streak++;
- }
-
- if ( isDefined( level.hardpointItems ) && isAlive( attacker ) )
- attacker thread maps\mp\gametypes\_hardpoints::giveHardpointItemForStreak();
-
-
- attacker.cur_death_streak = 0;
-
- if ( attacker.cur_kill_streak > attacker.kill_streak )
- {
- attacker maps\mp\gametypes\_persistence::statSet( "kill_streak", attacker.cur_kill_streak );
- attacker.kill_streak = attacker.cur_kill_streak;
- }
-
- givePlayerScore( "kill", attacker, self );
-
- name = ""+self.clientid;
- if ( !isDefined( attacker.killedPlayers[name] ) )
- attacker.killedPlayers[name] = 0;
-
- if ( !isDefined( attacker.killedPlayersCurrent[name] ) )
- attacker.killedPlayersCurrent[name] = 0;
-
- attacker.killedPlayers[name]++;
- attacker.killedPlayersCurrent[name]++;
-
- attackerName = ""+attacker.clientid;
- if ( !isDefined( self.killedBy[attackerName] ) )
- self.killedBy[attackerName] = 0;
-
- self.killedBy[attackerName]++;
-
- // helicopter score for team
- if( level.teamBased && isdefined( level.chopper ) && isdefined( Attacker ) && Attacker == level.chopper )
- giveTeamScore( "kill", attacker.team, attacker, self );
-
- // to prevent spectator gain score for team-spectator after throwing a granade and killing someone before he switched
- if ( level.teamBased && attacker.pers["team"] != "spectator")
- giveTeamScore( "kill", attacker.pers["team"], attacker, self );
-
- level thread maps\mp\gametypes\_battlechatter_mp::sayLocalSoundDelayed( attacker, "kill", 0.75 );
-
- prof_end( "pks1" );
-
- if ( level.teamBased )
- {
- prof_begin( "PlayerKilled assists" );
-
- if ( isdefined( self.attackers ) )
- {
- for ( j = 0; j < self.attackers.size; j++ )
- {
- player = self.attackers[j];
-
- if ( !isDefined( player ) )
- continue;
-
- if ( player == attacker )
- continue;
-
- player thread processAssist( self );
- }
- self.attackers = [];
- }
-
- prof_end( "PlayerKilled assists" );
- }
- }
-
- prof_end( "PlayerKilled attacker" );
- }
- }
- else
- {
- doKillcam = false;
- killedByEnemy = false;
-
- lpattacknum = -1;
- lpattackguid = "";
- lpattackname = "";
- lpattackerteam = "world";
-
- // even if the attacker isn't a player, it might be on a team
- if ( isDefined( attacker ) && isDefined( attacker.team ) && (attacker.team == "axis" || attacker.team == "allies") )
- {
- if ( attacker.team != self.pers["team"] )
- {
- killedByEnemy = true;
- if ( level.teamBased )
- giveTeamScore( "kill", attacker.team, attacker, self );
- }
- }
- }
-
- prof_begin( "PlayerKilled post constants" );
-
- if ( isDefined( attacker ) && isPlayer( attacker ) && attacker != self && (!level.teambased || attacker.pers["team"] != self.pers["team"]) )
- self thread maps\mp\gametypes\_missions::playerKilled(eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, sHitLoc );
- else
- self notify("playerKilledChallengesProcessed");
-
- logPrint( "K;" + lpselfguid + ";" + lpselfnum + ";" + lpselfteam + ";" + lpselfname + ";" + lpattackguid + ";" + lpattacknum + ";" + lpattackerteam + ";" + lpattackname + ";" + sWeapon + ";" + iDamage + ";" + sMeansOfDeath + ";" + sHitLoc + "\n" );
- attackerString = "none";
- if ( isPlayer( attacker ) ) // attacker can be the worldspawn if it's not a player
- attackerString = attacker getXuid() + "(" + lpattackname + ")";
- self logstring( "d " + sMeansOfDeath + "(" + sWeapon + ") a:" + attackerString + " d:" + iDamage + " l:" + sHitLoc + " @ " + int( self.origin[0] ) + " " + int( self.origin[1] ) + " " + int( self.origin[2] ) );
-
- level thread updateTeamStatus();
-
- self maps\mp\gametypes\_gameobjects::detachUseModels(); // want them detached before we create our corpse
-
- body = self clonePlayer( deathAnimDuration );
- if ( self isOnLadder() || self isMantling() )
- body startRagDoll();
-
- thread delayStartRagdoll( body, sHitLoc, vDir, sWeapon, eInflictor, sMeansOfDeath );
-
- self.body = body;
- if ( !isDefined( self.switching_teams ) )
- thread maps\mp\gametypes\_deathicons::addDeathicon( body, self, self.pers["team"], 5.0 );
-
- self.switching_teams = undefined;
- self.joining_team = undefined;
- self.leaving_team = undefined;
-
- self thread [[level.onPlayerKilled]](eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration);
-
- if ( sWeapon == "artillery_mp" || sWeapon == "claymore_mp" || sWeapon == "frag_grenade_short_mp" || sWeapon == "none" || isSubStr( sWeapon, "cobra" ) )
- doKillcam = false;
-
- if ( ( isSubStr( sWeapon, "cobra" ) ) && isdefined( eInflictor ) )
- {
- killcamentity = eInflictor getEntityNumber();
- doKillcam = true;
- }
- else
- {
- killcamentity = -1;
- }
-
- self.deathTime = getTime();
- perks = getPerks( attacker );
-
- // let the player watch themselves die
- wait ( 0.25 );
- self.cancelKillcam = false;
- self thread cancelKillCamOnUse();
- postDeathDelay = waitForTimeOrNotifies( 1.75 );
- self notify ( "death_delay_finished" );
-
- if ( game["state"] != "playing" )
- return;
-
- respawnTimerStartTime = gettime();
-
- /#
- if ( getDvarInt( "scr_forcekillcam" ) != 0 )
- {
- doKillcam = true;
- if ( lpattacknum < 0 )
- lpattacknum = self getEntityNumber();
- }
- #/
-
- if ( !self.cancelKillcam && doKillcam && level.killcam )
- {
- livesLeft = !(level.numLives && !self.pers["lives"]);
- timeUntilSpawn = TimeUntilSpawn( true );
- willRespawnImmediately = livesLeft && (timeUntilSpawn <= 0);
-
- self maps\mp\gametypes\_killcam::killcam( lpattacknum, killcamentity, sWeapon, postDeathDelay + deathTimeOffset, psOffsetTime, willRespawnImmediately, timeUntilRoundEnd(), perks, attacker );
- }
-
- prof_end( "PlayerKilled post constants" );
-
- if ( game["state"] != "playing" )
- {
- self.sessionstate = "dead";
- self.spectatorclient = -1;
- self.killcamentity = -1;
- self.archivetime = 0;
- self.psoffsettime = 0;
- return;
- }
-
- // class may be undefined if we have changed teams
- if ( isValidClass( self.class ) )
- {
- timePassed = (gettime() - respawnTimerStartTime) / 1000;
- self thread [[level.spawnClient]]( timePassed );
- }
-}
-
-
-cancelKillCamOnUse()
-{
- self endon ( "death_delay_finished" );
- self endon ( "disconnect" );
- level endon ( "game_ended" );
-
- for ( ;; )
- {
- if ( !self UseButtonPressed() )
- {
- wait ( 0.05 );
- continue;
- }
-
- buttonTime = 0;
- while( self UseButtonPressed() )
- {
- buttonTime += 0.05;
- wait ( 0.05 );
- }
-
- if ( buttonTime >= 0.5 )
- continue;
-
- buttonTime = 0;
-
- while ( !self UseButtonPressed() && buttonTime < 0.5 )
- {
- buttonTime += 0.05;
- wait ( 0.05 );
- }
-
- if ( buttonTime >= 0.5 )
- continue;
-
- self.cancelKillcam = true;
- return;
- }
-}
-
-
-waitForTimeOrNotifies( desiredDelay )
-{
- startedWaiting = getTime();
-
-// while( self.doingNotify )
-// wait ( 0.05 );
-
- waitedTime = (getTime() - startedWaiting)/1000;
-
- if ( waitedTime < desiredDelay )
- {
- wait desiredDelay - waitedTime;
- return desiredDelay;
- }
- else
- {
- return waitedTime;
- }
-}
-
-reduceTeamKillsOverTime()
-{
- timePerOneTeamkillReduction = 20.0;
- reductionPerSecond = 1.0 / timePerOneTeamkillReduction;
-
- while(1)
- {
- if ( isAlive( self ) )
- {
- self.pers["teamkills"] -= reductionPerSecond;
- if ( self.pers["teamkills"] < level.minimumAllowedTeamKills )
- {
- self.pers["teamkills"] = level.minimumAllowedTeamKills;
- break;
- }
- }
- wait 1;
- }
-}
-
-getPerks( player )
-{
- perks[0] = "specialty_null";
- perks[1] = "specialty_null";
- perks[2] = "specialty_null";
-
- if ( isPlayer( player ) && !level.oldschool )
- {
- // if public game, if is not bot, if class selection is custom, if is currently using a custom class instead of pending class change
- if ( level.onlineGame && !isdefined( player.pers["isBot"] ) && isSubstr( player.curClass, "CLASS_CUSTOM" ) && isdefined(player.custom_class) )
- {
- //assertex( isdefined(player.custom_class), "Player: " + player.name + "'s Custom Class: " + player.pers["class"] + " is corrupted." );
-
- class_num = player.class_num;
- if ( isDefined( player.custom_class[class_num]["specialty1"] ) )
- perks[0] = player.custom_class[class_num]["specialty1"];
- if ( isDefined( player.custom_class[class_num]["specialty2"] ) )
- perks[1] = player.custom_class[class_num]["specialty2"];
- if ( isDefined( player.custom_class[class_num]["specialty3"] ) )
- perks[2] = player.custom_class[class_num]["specialty3"];
- }
- else
- {
- if ( isDefined( level.default_perk[player.curClass][0] ) )
- perks[0] = level.default_perk[player.curClass][0];
- if ( isDefined( level.default_perk[player.curClass][1] ) )
- perks[1] = level.default_perk[player.curClass][1];
- if ( isDefined( level.default_perk[player.curClass][2] ) )
- perks[2] = level.default_perk[player.curClass][2];
- }
- }
-
- return perks;
-}
-
-processAssist( killedplayer )
-{
- self endon("disconnect");
- killedplayer endon("disconnect");
-
- wait .05; // don't ever run on the same frame as the playerkilled callback.
- WaitTillSlowProcessAllowed();
-
- if ( self.pers["team"] != "axis" && self.pers["team"] != "allies" )
- return;
-
- if ( self.pers["team"] == killedplayer.pers["team"] )
- return;
-
- self thread [[level.onXPEvent]]( "assist" );
- self incPersStat( "assists", 1 );
- self.assists = self getPersStat( "assists" );
-
- givePlayerScore( "assist", self, killedplayer );
-
- self thread maps\mp\gametypes\_missions::playerAssist();
-}
-
-Callback_PlayerLastStand( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration )
-{
- self.health = 1;
-
- self.lastStandParams = spawnstruct();
- self.lastStandParams.eInflictor = eInflictor;
- self.lastStandParams.attacker = attacker;
- self.lastStandParams.iDamage = iDamage;
- self.lastStandParams.sMeansOfDeath = sMeansOfDeath;
- self.lastStandParams.sWeapon = sWeapon;
- self.lastStandParams.vDir = vDir;
- self.lastStandParams.sHitLoc = sHitLoc;
- self.lastStandParams.lastStandStartTime = gettime();
-
- mayDoLastStand = mayDoLastStand( sWeapon, sMeansOfDeath, sHitLoc );
- /#
- if ( getdvar("scr_forcelaststand" ) == "1" )
- mayDoLastStand = true;
- #/
- if ( !mayDoLastStand )
- {
- self.useLastStandParams = true;
- self ensureLastStandParamsValidity();
- self suicide();
- return;
- }
-
- weaponslist = self getweaponslist();
- assertex( isdefined( weaponslist ) && weaponslist.size > 0, "Player's weapon(s) missing before dying -=Last Stand=-" );
-
- self thread maps\mp\gametypes\_gameobjects::onPlayerLastStand();
-
- self maps\mp\gametypes\_weapons::dropWeaponForDeath( attacker );
-
- notifyData = spawnStruct();
- notifyData.titleText = game["strings"]["last_stand"]; //"Last Stand!";
- notifyData.iconName = "specialty_pistoldeath";
- notifyData.glowColor = (1,0,0);
- notifyData.sound = "mp_last_stand";
- notifyData.duration = 2.0;
-
- self thread maps\mp\gametypes\_hud_message::notifyMessage( notifyData );
-
- grenadeTypePrimary = "frag_grenade_mp";
-
- // check if player has pistol
- for( i = 0; i < weaponslist.size; i++ )
- {
- weapon = weaponslist[i];
- if ( maps\mp\gametypes\_weapons::isPistol( weapon ) )
- {
- // take away all weapon and leave this pistol
- self takeallweapons();
- self maps\mp\gametypes\_hardpoints::giveOwnedHardpointItem();
- self giveweapon( weapon );
- self giveMaxAmmo( weapon );
- self switchToWeapon( weapon );
- self GiveWeapon( grenadeTypePrimary );
- self SetWeaponAmmoClip( grenadeTypePrimary, 0 );
- self SwitchToOffhand( grenadeTypePrimary );
- self thread lastStandTimer( 10 );
- return;
- }
- }
- self takeallweapons();
- self maps\mp\gametypes\_hardpoints::giveOwnedHardpointItem();
- self giveWeapon( "beretta_mp" );
- self giveMaxAmmo( "beretta_mp" );
- self switchToWeapon( "beretta_mp" );
- self GiveWeapon( grenadeTypePrimary );
- self SetWeaponAmmoClip( grenadeTypePrimary, 0 );
- self SwitchToOffhand( grenadeTypePrimary );
- self thread lastStandTimer( 10 );
-}
-
-
-lastStandTimer( delay )
-{
- self endon( "death" );
- self endon( "disconnect" );
- self endon( "game_ended" );
-
- self thread lastStandWaittillDeath();
-
- self.lastStand = true;
- self setLowerMessage( &"PLATFORM_COWARDS_WAY_OUT" );
-
- self thread lastStandAllowSuicide();
- self thread lastStandKeepOverlay();
-
- wait delay;
-
- self thread LastStandBleedOut();
-}
-
-LastStandBleedOut()
-{
- self.useLastStandParams = true;
- self ensureLastStandParamsValidity();
- self suicide();
-}
-
-lastStandAllowSuicide()
-{
- self endon( "death" );
- self endon( "disconnect" );
- self endon( "game_ended" );
-
- while(1)
- {
- if ( self useButtonPressed() )
- {
- pressStartTime = gettime();
- while ( self useButtonPressed() )
- {
- wait .05;
- if ( gettime() - pressStartTime > 700 )
- break;
- }
- if ( gettime() - pressStartTime > 700 )
- break;
- }
- wait .05;
- }
-
- self thread LastStandBleedOut();
-}
-
-lastStandKeepOverlay()
-{
- self endon( "death" );
- self endon( "disconnect" );
- self endon( "game_ended" );
-
- // keep the health overlay going by making code think the player is getting damaged
- while(1)
- {
- self.health = 2;
- wait .05;
- self.health = 1;
- wait .5;
- }
-}
-
-lastStandWaittillDeath()
-{
- self endon( "disconnect" );
-
- self waittill( "death" );
-
- self clearLowerMessage();
- self.lastStand = undefined;
-}
-
-mayDoLastStand( sWeapon, sMeansOfDeath, sHitLoc )
-{
- if ( sMeansOfDeath != "MOD_PISTOL_BULLET" && sMeansOfDeath != "MOD_RIFLE_BULLET" && sMeansOfDeath != "MOD_FALLING" )
- return false;
-
- if ( isHeadShot( sWeapon, sHitLoc, sMeansOfDeath ) )
- return false;
-
- return true;
-}
-
-ensureLastStandParamsValidity()
-{
- // attacker may have become undefined if the player that killed me has disconnected
- if ( !isDefined( self.lastStandParams.attacker ) )
- self.lastStandParams.attacker = self;
-}
-
-setSpawnVariables()
-{
- resetTimeout();
-
- // Stop shellshock and rumble
- self StopShellshock();
- self StopRumble( "damage_heavy" );
-}
-
-notifyConnecting()
-{
- waittillframeend;
-
- if( isDefined( self ) )
- level notify( "connecting", self );
-}
-
-
-setObjectiveText( team, text )
-{
- game["strings"]["objective_"+team] = text;
- precacheString( text );
-}
-
-setObjectiveScoreText( team, text )
-{
- game["strings"]["objective_score_"+team] = text;
- precacheString( text );
-}
-
-setObjectiveHintText( team, text )
-{
- game["strings"]["objective_hint_"+team] = text;
- precacheString( text );
-}
-
-getObjectiveText( team )
-{
- return game["strings"]["objective_"+team];
-}
-
-getObjectiveScoreText( team )
-{
- return game["strings"]["objective_score_"+team];
-}
-
-getObjectiveHintText( team )
-{
- return game["strings"]["objective_hint_"+team];
-}
-
-getHitLocHeight( sHitLoc )
-{
- switch( sHitLoc )
- {
- case "helmet":
- case "head":
- case "neck":
- return 60;
- case "torso_upper":
- case "right_arm_upper":
- case "left_arm_upper":
- case "right_arm_lower":
- case "left_arm_lower":
- case "right_hand":
- case "left_hand":
- case "gun":
- return 48;
- case "torso_lower":
- return 40;
- case "right_leg_upper":
- case "left_leg_upper":
- return 32;
- case "right_leg_lower":
- case "left_leg_lower":
- return 10;
- case "right_foot":
- case "left_foot":
- return 5;
- }
- return 48;
-}
-
-debugLine( start, end )
-{
- for ( i = 0; i < 50; i++ )
- {
- line( start, end );
- wait .05;
- }
-}
-
-delayStartRagdoll( ent, sHitLoc, vDir, sWeapon, eInflictor, sMeansOfDeath )
-{
- if ( isDefined( ent ) )
- {
- deathAnim = ent getcorpseanim();
- if ( animhasnotetrack( deathAnim, "ignore_ragdoll" ) )
- return;
- }
-
- if ( level.oldschool )
- {
- if ( !isDefined( vDir ) )
- vDir = (0,0,0);
-
- explosionPos = ent.origin + ( 0, 0, getHitLocHeight( sHitLoc ) );
- explosionPos -= vDir * 20;
- //thread debugLine( ent.origin + (0,0,(explosionPos[2] - ent.origin[2])), explosionPos );
- explosionRadius = 40;
- explosionForce = .75;
- if ( sMeansOfDeath == "MOD_IMPACT" || sMeansOfDeath == "MOD_EXPLOSIVE" || isSubStr(sMeansOfDeath, "MOD_GRENADE") || isSubStr(sMeansOfDeath, "MOD_PROJECTILE") || sHitLoc == "head" || sHitLoc == "helmet" )
- {
- explosionForce = 2.5;
- }
-
- ent startragdoll( 1 );
-
- wait .05;
-
- if ( !isDefined( ent ) )
- return;
-
- // apply extra physics force to make the ragdoll go crazy
- physicsExplosionSphere( explosionPos, explosionRadius, explosionRadius/2, explosionForce );
- return;
- }
-
- wait( 0.2 );
-
- if ( !isDefined( ent ) )
- return;
-
- if ( ent isRagDoll() )
- return;
-
- deathAnim = ent getcorpseanim();
-
- startFrac = 0.35;
-
- if ( animhasnotetrack( deathAnim, "start_ragdoll" ) )
- {
- times = getnotetracktimes( deathAnim, "start_ragdoll" );
- if ( isDefined( times ) )
- startFrac = times[0];
- }
-
- waitTime = startFrac * getanimlength( deathAnim );
- wait( waitTime );
-
- if ( isDefined( ent ) )
- {
- println( "Ragdolling after " + waitTime + " seconds" );
- ent startragdoll( 1 );
- }
-}
-
-
-isExcluded( entity, entityList )
-{
- for ( index = 0; index < entityList.size; index++ )
- {
- if ( entity == entityList[index] )
- return true;
- }
- return false;
-}
-
-leaderDialog( dialog, team, group, excludeList )
-{
- assert( isdefined( level.players ) );
-
- if ( level.splitscreen )
- return;
-
- if ( !isDefined( team ) )
- {
- leaderDialogBothTeams( dialog, "allies", dialog, "axis", group, excludeList );
- return;
- }
-
- if ( level.splitscreen )
- {
- if ( level.players.size )
- level.players[0] leaderDialogOnPlayer( dialog, group );
- return;
- }
-
- if ( isDefined( excludeList ) )
- {
- for ( i = 0; i < level.players.size; i++ )
- {
- player = level.players[i];
- if ( (isDefined( player.pers["team"] ) && (player.pers["team"] == team )) && !isExcluded( player, excludeList ) )
- player leaderDialogOnPlayer( dialog, group );
- }
- }
- else
- {
- for ( i = 0; i < level.players.size; i++ )
- {
- player = level.players[i];
- if ( isDefined( player.pers["team"] ) && (player.pers["team"] == team ) )
- player leaderDialogOnPlayer( dialog, group );
- }
- }
-}
-
-leaderDialogBothTeams( dialog1, team1, dialog2, team2, group, excludeList )
-{
- assert( isdefined( level.players ) );
-
- if ( level.splitscreen )
- return;
-
- if ( level.splitscreen )
- {
- if ( level.players.size )
- level.players[0] leaderDialogOnPlayer( dialog1, group );
- return;
- }
-
- if ( isDefined( excludeList ) )
- {
- for ( i = 0; i < level.players.size; i++ )
- {
- player = level.players[i];
- team = player.pers["team"];
-
- if ( !isDefined( team ) )
- continue;
-
- if ( isExcluded( player, excludeList ) )
- continue;
-
- if ( team == team1 )
- player leaderDialogOnPlayer( dialog1, group );
- else if ( team == team2 )
- player leaderDialogOnPlayer( dialog2, group );
- }
- }
- else
- {
- for ( i = 0; i < level.players.size; i++ )
- {
- player = level.players[i];
- team = player.pers["team"];
-
- if ( !isDefined( team ) )
- continue;
-
- if ( team == team1 )
- player leaderDialogOnPlayer( dialog1, group );
- else if ( team == team2 )
- player leaderDialogOnPlayer( dialog2, group );
- }
- }
-}
-
-
-leaderDialogOnPlayer( dialog, group )
-{
- team = self.pers["team"];
-
- if ( level.splitscreen )
- return;
-
- if ( !isDefined( team ) )
- return;
-
- if ( team != "allies" && team != "axis" )
- return;
-
- if ( isDefined( group ) )
- {
- // ignore the message if one from the same group is already playing
- if ( self.leaderDialogGroup == group )
- return;
-
- hadGroupDialog = isDefined( self.leaderDialogGroups[group] );
-
- self.leaderDialogGroups[group] = dialog;
- dialog = group;
-
- // exit because the "group" dialog call is already in the queue
- if ( hadGroupDialog )
- return;
- }
-
- if ( !self.leaderDialogActive )
- self thread playLeaderDialogOnPlayer( dialog, team );
- else
- self.leaderDialogQueue[self.leaderDialogQueue.size] = dialog;
-}
-
-
-playLeaderDialogOnPlayer( dialog, team )
-{
- self endon ( "disconnect" );
-
- self.leaderDialogActive = true;
- if ( isDefined( self.leaderDialogGroups[dialog] ) )
- {
- group = dialog;
- dialog = self.leaderDialogGroups[group];
- self.leaderDialogGroups[group] = undefined;
- self.leaderDialogGroup = group;
- }
-
- self playLocalSound( game["voice"][team]+game["dialog"][dialog] );
-
- wait ( 3.0 );
- self.leaderDialogActive = false;
- self.leaderDialogGroup = "";
-
- if ( self.leaderDialogQueue.size > 0 )
- {
- nextDialog = self.leaderDialogQueue[0];
-
- for ( i = 1; i < self.leaderDialogQueue.size; i++ )
- self.leaderDialogQueue[i-1] = self.leaderDialogQueue[i];
- self.leaderDialogQueue[i-1] = undefined;
-
- self thread playLeaderDialogOnPlayer( nextDialog, team );
- }
-}
-
-
-getMostKilledBy()
-{
- mostKilledBy = "";
- killCount = 0;
-
- killedByNames = getArrayKeys( self.killedBy );
-
- for ( index = 0; index < killedByNames.size; index++ )
- {
- killedByName = killedByNames[index];
- if ( self.killedBy[killedByName] <= killCount )
- continue;
-
- killCount = self.killedBy[killedByName];
- mostKilleBy = killedByName;
- }
-
- return mostKilledBy;
-}
-
-
-getMostKilled()
-{
- mostKilled = "";
- killCount = 0;
-
- killedNames = getArrayKeys( self.killedPlayers );
-
- for ( index = 0; index < killedNames.size; index++ )
- {
- killedName = killedNames[index];
- if ( self.killedPlayers[killedName] <= killCount )
- continue;
-
- killCount = self.killedPlayers[killedName];
- mostKilled = killedName;
- }
-
- return mostKilled;
-}
-
-
diff --git a/src/Components/Modules/RB_DrawCollision.cpp b/src/Components/Modules/RB_DrawCollision.cpp
index 9ac9462..8f2aa5e 100644
--- a/src/Components/Modules/RB_DrawCollision.cpp
+++ b/src/Components/Modules/RB_DrawCollision.cpp
@@ -1,3712 +1,3736 @@
-#include "STDInclude.hpp"
-
-#define CM_CONTENTS_SOLID 0x1
-#define CM_CONTENTS_CLIPSHOT 0x2000 // weapon clip
-#define CM_CONTENTS_DETAIL 0x8000000 // detail
-
-#define CM_MAX_BRUSHPOINTS_FROM_INTERSECTIONS 128
-
-std::chrono::time_point export_time_exportStart;
-std::chrono::time_point export_time_brushGenerationStart;
-
-bool export_mapExportCmd = false;
-bool export_inProgress = false;
-bool export_selectionAdd;
-int export_currentBrushIndex = 0;
-float export_quadEpsilon = 0.0f;
-std::ofstream export_mapFile;
-Utils::Entities export_mapEntities;
-Game::boundingBox_t export_selectionBox;
-
-int collisionFlickerCounter;
-const char *g_mapNameCm = "";
-
-// last origin we sorted brushes at
-glm::vec3 g_oldSortingOrigin = glm::vec3(0.0f);
-
-// winding pool
-char windingPool[12292];
-
-// dynamic dvar strings
-//std::string g_dvarMaterialList_str;
-
-// material lists
-std::vector g_mapMaterialList;
-std::vector g_mapMaterialListDuplicates;
-std::vector g_mapMaterialListSingle;
-
-// brush lists
-std::vector g_mapBrushList;
-std::vector g_mapBrushListForIndexFiltering;
-std::vector g_mapBrushModelList;
-
-// used to calculate the delta between sv_fps and client fps
-int SvFramerateToRendertime_Counter = 0;
-int SvFramerateToRendertime_CurrentDelta = 0;
-
-// view-frustum planes
-const Game::vec4_t frustumSidePlanes[5] =
-{
- {-1.0f, 0.0f, 0.0f, 1.0f}, // left?
- { 1.0f, 0.0f, 0.0f, 1.0f}, // right?
- { 0.0f, -1.0f, 0.0f, 1.0f}, // bottom?
- { 0.0f, 1.0f, 0.0f, 1.0f}, // top?
- { 0.0f, 0.0f, 1.0f, 1.0f} // back?
-};
-
-namespace Components
-{
- // scale strings with distance :: when looking directly at them
- float StringScaleDot(Game::GfxViewParms *viewParms, const glm::vec3 origin)
- {
- glm::vec3 delta = glm::vec3(origin.x, origin.y, origin.z) - glm::vec3(viewParms->origin[0], viewParms->origin[1], viewParms->origin[2]);
-
- float scale = Utils::vector::_GLM_VectorNormalize(delta);
- float dot = glm::dot(delta, glm::vec3(viewParms->axis[0][0], viewParms->axis[0][1], viewParms->axis[0][2]));
-
- scale = (dot - 0.995f) * scale;
-
- if (scale < 1.0f)
- {
- scale = 1.0f;
- }
-
- return scale;
- }
-
- // show brush index numbers (used for r_drawCollision_brushIndexFilter) ---- adding strings will cause flicker or duplicates because renderthread runs at diff. framerate?
- void CM_ShowBrushIndexNumbers(Game::GfxViewParms *viewParms, int brushIndex, const glm::vec3 origin, int sortedBrushIndex, int maxStringAmount)
- {
- float scale;
- glm::vec4 color;
-
- // we need the viewAxis to scale strings, calling Set3D populates our struct
- Game::R_Set3D();
-
- // scale strings with distance when looking directly at them
- scale = StringScaleDot(viewParms, origin);
- color = glm::vec4(1.0f);
-
- const char *brushIndexStr = Utils::VA("Index: %d", brushIndex);
-
- //char color_packed[4];
- //float color_test[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
- //Game::R_ConvertColorToBytes(color_test, color_packed);
-
- //float pixel_x[3], pixel_y[3], org[3];
- //Utils::vector::_VectorScale(viewParms->axis[1], -scale, pixel_x);
- //Utils::vector::_VectorScale(viewParms->axis[2], -scale, pixel_y);
-
- ////auto font = Game::R_RegisterFont(FONT_NORMAL, sizeof(FONT_NORMAL));
- //Game::Font_s* font = (Game::Font_s*) *(DWORD*)(0xD070C74);
-
- //glm::setFloat3(org, origin);
-
- //Game::RB_DrawTextInSpace(pixel_x, pixel_y, brushIndexStr, font, org, color_packed); // no idea why thats not working
-
- _Debug::AddDebugStringClient(origin, color, scale, brushIndexStr, 2); // not propper but will do for now
- }
-
- // some kind of wizardry
- void SetPlaneSignbits(Game::cplane_s *out)
- {
- int j; char bits;
-
- // for fast box on planeside test
- bits = 0;
- for (j = 0; j < 3; j++)
- {
- if (out->normal[j] < 0.0f)
- {
- bits |= 1 << j;
- }
- }
-
- out->signbits = bits;
- }
-
- // build view frustum
- void R_SetDpvsPlaneSides(Game::DpvsPlane *plane)
- {
- plane->side[0] = char(plane->coeffs[0]) <= 0 ? 0 : 12;
- plane->side[1] = char(plane->coeffs[1]) <= 0 ? 4 : 16;
- plane->side[2] = char(plane->coeffs[2]) <= 0 ? 8 : 20;
- }
-
- // build view frustum
- void R_FrustumClipPlanes(const Game::GfxMatrix *viewProjMtx, const float(*sidePlanes)[4], const int sidePlaneCount, Game::DpvsPlane *frustumPlanes)
- {
- int term, planeIndex;
- float length;
-
- Game::DpvsPlane *plane;
-
- for (planeIndex = 0; planeIndex < sidePlaneCount; ++planeIndex)
- {
- for (term = 0; term < 4; ++term)
- {
- frustumPlanes[planeIndex].coeffs[term] = ((((*sidePlanes)[4 * planeIndex + 0] * viewProjMtx->m[term][0])
- + ((*sidePlanes)[4 * planeIndex + 1] * viewProjMtx->m[term][1]))
- + ((*sidePlanes)[4 * planeIndex + 2] * viewProjMtx->m[term][2]))
- + ((*sidePlanes)[4 * planeIndex + 3] * viewProjMtx->m[term][3]);
- }
-
- length = Utils::vector::_VectorLength(frustumPlanes[planeIndex].coeffs);
-
- if (length <= 0.0f)
- {
- Game::Com_PrintMessage(0, Utils::VA("^1R_FrustumClipPlanes L#%d ^7:: length <= 0 \n", __LINE__), 0);
- }
-
- plane = &frustumPlanes[planeIndex];
-
- plane->coeffs[0] = (1.0f / length) * plane->coeffs[0];
- plane->coeffs[1] = (1.0f / length) * plane->coeffs[1];
- plane->coeffs[2] = (1.0f / length) * plane->coeffs[2];
- plane->coeffs[3] = (1.0f / length) * plane->coeffs[3];
-
- R_SetDpvsPlaneSides(&frustumPlanes[planeIndex]);
- }
- }
-
- // build view frustum
- void BuildFrustumPlanes(Game::GfxViewParms *viewParms, Game::cplane_s *frustumPlanes)
- {
- char frustumType;
- unsigned int planeIndex;
-
- Game::DpvsPlane dpvsFrustumPlanes[5];
- Game::DpvsPlane *dpvsPlane;
- Game::cplane_s *cplane;
-
- if (!viewParms)
- {
- Game::Com_Error(0, Utils::VA("BuildFrustumPlanes L#%d :: viewparams \n", __LINE__));
- return;
- }
-
- if (!frustumPlanes)
- {
- Game::Com_Error(0, Utils::VA("BuildFrustumPlanes L#%d :: frustumPlanes \n", __LINE__));
- return;
- }
-
- R_FrustumClipPlanes(&viewParms->viewProjectionMatrix, frustumSidePlanes, 5, dpvsFrustumPlanes);
-
- for (planeIndex = 0; planeIndex < 5; ++planeIndex)
- {
- cplane = &frustumPlanes[planeIndex];
- dpvsPlane = &dpvsFrustumPlanes[planeIndex];
-
- cplane->normal[0] = dpvsPlane->coeffs[0];
- cplane->normal[1] = dpvsPlane->coeffs[1];
- cplane->normal[2] = dpvsPlane->coeffs[2];
- cplane->dist = dpvsPlane->coeffs[3];
-
- frustumPlanes[planeIndex].dist = frustumPlanes[planeIndex].dist * -1.0f;
-
- if (frustumPlanes[planeIndex].normal[0] == 1.0f)
- {
- frustumType = 0;
- }
- else
- {
- if (frustumPlanes[planeIndex].normal[1] == 1.0f)
- {
- frustumType = 1;
- }
- else
- {
- if (frustumPlanes[planeIndex].normal[2] == 1.0f)
- {
- frustumType = 2;
- }
- else
- {
- frustumType = 3;
- }
- }
- }
-
- frustumPlanes[planeIndex].type = frustumType;
- SetPlaneSignbits(&frustumPlanes[planeIndex]);
- }
- }
-
- // create plane for intersection
- void CM_GetPlaneVec4Form(const Game::cbrushside_t* sides, const Game::axialPlane_t* axialPlanes, const int index, float* expandedPlane)
- {
- if (index >= 6)
- {
- if (!sides )
- {
- Game::Com_Error(0, Utils::VA("CM_GetPlaneVec4Form L#%d :: sides \n", __LINE__));
- return;
- }
-
- expandedPlane[0] = sides[index - 6].plane->normal[0];
- expandedPlane[1] = sides[index - 6].plane->normal[1];
- expandedPlane[2] = sides[index - 6].plane->normal[2];
- expandedPlane[3] = sides[index - 6].plane->dist;
- }
- else
- {
- /*expandedPlane[0] = axialPlanes[index][0];
- expandedPlane[1] = axialPlanes[index][1];
- expandedPlane[2] = axialPlanes[index][2];
- expandedPlane[3] = axialPlanes[index][3];*/
-
- glm::setFloat3(expandedPlane, axialPlanes[index].plane);
- expandedPlane[3] = axialPlanes[index].dist;
- }
- }
-
- // intersect 3 planes
- int IntersectPlanes(const float *plane0, const float *plane1, const float *plane2, float *xyz)
- {
- float determinant;
-
- determinant = (((plane1[1] * plane2[2]) - (plane2[1] * plane1[2])) * plane0[0])
- + (((plane2[1] * plane0[2]) - (plane0[1] * plane2[2])) * plane1[0])
- + (((plane0[1] * plane1[2]) - (plane1[1] * plane0[2])) * plane2[0]);
-
- if (fabs(determinant) < 0.001f)
- {
- return 0;
- }
-
- determinant = 1.0f / determinant;
-
- xyz[0] = ((((plane1[1] * plane2[2]) - (plane2[1] * plane1[2])) * plane0[3])
- + (((plane2[1] * plane0[2]) - (plane0[1] * plane2[2])) * plane1[3])
- + (((plane0[1] * plane1[2]) - (plane1[1] * plane0[2])) * plane2[3])) * determinant;
-
- xyz[1] = ((((plane1[2] * plane2[0]) - (plane2[2] * plane1[0])) * plane0[3])
- + (((plane2[2] * plane0[0]) - (plane0[2] * plane2[0])) * plane1[3])
- + (((plane0[2] * plane1[0]) - (plane1[2] * plane0[0])) * plane2[3])) * determinant;
-
- xyz[2] = ((((plane1[0] * plane2[1]) - (plane2[0] * plane1[1])) * plane0[3])
- + (((plane2[0] * plane0[1]) - (plane0[0] * plane2[1])) * plane1[3])
- + (((plane0[0] * plane1[1]) - (plane1[0] * plane0[1])) * plane2[3])) * determinant;
-
- return 1;
- }
-
- // cod4map
- bool IsOnGrid(const float *snapped, const float *xyz)
- {
- return xyz[0] == snapped[0] && xyz[1] == snapped[1] && xyz[2] == snapped[2];
- }
-
- // snap points to grid. might prod. some issues
- void SnapPointToIntersectingPlanes(const float *plane0, const float *plane1, const float *plane2, float *xyz, float snapGrid, const float snapEpsilon)
- {
- int axis, planeIndex;
- float rounded, delta, baseError, maxBaseError, snapError, maxSnapError, snapped[3], currentPlane[4];
-
- snapGrid = 1.0f / snapGrid;
-
- // cod4map :
- for (axis = 0; axis < 3; ++axis)
- {
- rounded = round(xyz[axis] * snapGrid) / snapGrid;
- delta = fabs(rounded - xyz[axis]);
-
- if (snapEpsilon <= delta)
- {
- snapped[axis] = xyz[axis];
- }
- else
- {
- snapped[axis] = rounded;
- }
- }
-
- if (!IsOnGrid(snapped, xyz))
- {
- maxSnapError = 0.0f;
- maxBaseError = snapEpsilon;
-
- for (planeIndex = 0; planeIndex < 3; ++planeIndex)
- {
- if (planeIndex == 0)
- memcpy(¤tPlane, plane0, sizeof(currentPlane));
-
- else if (planeIndex == 1)
- memcpy(¤tPlane, plane1, sizeof(currentPlane));
-
- else if (planeIndex == 2)
- memcpy(¤tPlane, plane2, sizeof(currentPlane));
-
-
- snapError = log((currentPlane[0] * snapped[0] + currentPlane[1] * snapped[1] + currentPlane[2] * snapped[2]) - currentPlane[3]);
- if (snapError > maxSnapError)
- {
- maxSnapError = snapError;
- }
-
- baseError = log((currentPlane[0] * xyz[0] + currentPlane[1] * xyz[1] + currentPlane[2] * xyz[2]) - currentPlane[3]);
- if (baseError > maxBaseError)
- {
- maxBaseError = baseError;
- }
- }
-
- if (maxBaseError > maxSnapError)
- {
- xyz[0] = snapped[0];
- xyz[1] = snapped[1];
- xyz[2] = snapped[2];
- }
- }
- }
-
- // add valid vertices from 3 plane intersections
- int CM_AddSimpleBrushPoint(const Game::cbrush_t* brush, const Game::axialPlane_t* axialPlanes, const __int16* sideIndices, const float* xyz, int ptCount, Game::ShowCollisionBrushPt* brushPts)
- {
- unsigned int sideIndex;
- Game::cplane_s *plane;
-
- if (!brush)
- {
- Game::Com_Error(0, Utils::VA("CM_AddSimpleBrushPoint L#%d :: brush \n", __LINE__));
- return 0;
- }
-
- if (!brushPts)
- {
- Game::Com_Error(0, Utils::VA("CM_AddSimpleBrushPoint L#%d :: brushPts \n", __LINE__));
- return 0;
- }
-
- for (sideIndex = 0; sideIndex < 6; ++sideIndex)
- {
- if(( (axialPlanes[sideIndex].plane.x * xyz[0] + axialPlanes[sideIndex].plane.y * xyz[1] + axialPlanes[sideIndex].plane.z * xyz[2])
- - axialPlanes[sideIndex].dist) > 0.1f)
- {
- return ptCount;
- }
- }
-
- for (sideIndex = 0; sideIndex < brush->numsides; ++sideIndex)
- {
- plane = brush->sides[sideIndex].plane;
-
- if ( plane != brush->sides[sideIndices[0] - 6].plane
- && plane != brush->sides[sideIndices[1] - 6].plane
- && plane != brush->sides[sideIndices[2] - 6].plane
- && ((((plane->normal[0] * xyz[0]) + (plane->normal[1] * xyz[1])) + (plane->normal[2] * xyz[2])) - plane->dist) > 0.1f)
- {
- return ptCount;
- }
- }
-
- if (ptCount > CM_MAX_BRUSHPOINTS_FROM_INTERSECTIONS - 2) // T5: 1024 ...
- {
- Game::Com_PrintMessage(0, Utils::VA("CM_AddSimpleBrushPoint :: More than %i points from plane intersections on %i-sided brush\n", ptCount, brush->numsides), 0);
- return ptCount;
- }
-
- brushPts[ptCount].xyz[0] = xyz[0];
- brushPts[ptCount].xyz[1] = xyz[1];
- brushPts[ptCount].xyz[2] = xyz[2];
-
- brushPts[ptCount].sideIndex[0] = sideIndices[0];
- brushPts[ptCount].sideIndex[1] = sideIndices[1];
- brushPts[ptCount].sideIndex[2] = sideIndices[2];
-
- return ptCount + 1;
- }
-
- // intersect 3 planes (for all planes) to reconstruct vertices
- int CM_ForEachBrushPlaneIntersection(const Game::cbrush_t* brush, const Game::axialPlane_t* axialPlanes, Game::ShowCollisionBrushPt* brushPts)
- {
- int ptCount = 0, sideCount;
- __int16 sideIndex[3];
-
- float xyz[3];
- float expandedPlane[3][4];
-
- Game::cbrushside_t *sides;
-
- if (!brush)
- {
- Game::Com_Error(0, Utils::VA("CM_ForEachBrushPlaneIntersection L#%d :: brush \n", __LINE__));
- return 0;
- }
-
- if (!brushPts)
- {
- Game::Com_Error(0, Utils::VA("CM_ForEachBrushPlaneIntersection L#%d :: brushPts \n", __LINE__));
- return 0;
- }
-
- sideCount = brush->numsides + 6;
- sides = brush->sides;
-
- // first loop should only get the axial planes till brush->numsides < 3
- for (sideIndex[0] = 0; sideIndex[0] < sideCount - 2; ++sideIndex[0])
- {
- // sideIndex[0]-[5] are axial planes only; move the current plane into expandedPlane[0]
- CM_GetPlaneVec4Form(sides, axialPlanes, sideIndex[0], (float *)expandedPlane);
-
- // get a plane 1 plane ahead of our first plane
- for (sideIndex[1] = sideIndex[0] + 1; sideIndex[1] < sideCount - 1; ++sideIndex[1])
- {
- // check if we're using an axial plane and 2 different planes
- if (sideIndex[0] < 6 || sideIndex[1] < 6 || sides[sideIndex[0] - 6].plane != sides[sideIndex[1] - 6].plane)
- {
- // move the current plane into expandedPlane[1]
- CM_GetPlaneVec4Form(sides, axialPlanes, sideIndex[1], expandedPlane[1]);
-
- // get a plane 1 plane ahead of our second plane
- for (sideIndex[2] = sideIndex[1] + 1; sideIndex[2] < sideCount - 0; ++sideIndex[2])
- {
- // check if we use axial planes or atleast 3 different sides
- if (( sideIndex[0] < 6 || sideIndex[2] < 6 || sides[sideIndex[0] - 6].plane != sides[sideIndex[2] - 6].plane)
- && (sideIndex[1] < 6 || sideIndex[2] < 6 || sides[sideIndex[1] - 6].plane != sides[sideIndex[2] - 6].plane))
- {
- // move the current plane into expandedPlane[2]
- CM_GetPlaneVec4Form(sides, axialPlanes, sideIndex[2], expandedPlane[2]);
-
- // intersect the 3 planes
- if (IntersectPlanes(expandedPlane[0], expandedPlane[1], expandedPlane[2], xyz))
- {
- // snap our verts in xyz onto the grid
- SnapPointToIntersectingPlanes(expandedPlane[0], expandedPlane[1], expandedPlane[2], xyz, 0.25f, 0.0099999998f);
-
- // if the planes intersected, put verts into brushPts and increase our pointCount
- ptCount = CM_AddSimpleBrushPoint(brush, axialPlanes, sideIndex, xyz, ptCount, brushPts);
-
- if (ptCount >= CM_MAX_BRUSHPOINTS_FROM_INTERSECTIONS - 1)
- {
- return 0;
- }
- }
- }
- }
- }
- }
- }
-
- return ptCount;
- }
-
- // check for float precision errors and check if a point lies within an epsilon
- bool VecNCompareCustomEpsilon(const glm::vec3* xyzList, const int xyzIndex, const float* v1, const float epsilon, const int coordCount)
- {
- for (auto i = 0; i < coordCount; ++i)
- {
- if (((xyzList[xyzIndex][i] - v1[i]) * (xyzList[xyzIndex][i] - v1[i])) > (epsilon * epsilon))
- {
- return false;
- }
- }
-
- return true;
- }
-
- // check if point in list exists
- int CM_PointInList(const float* point, const glm::vec3* xyzList, const int xyzCount)
- {
- int xyzIndex;
-
- for (xyzIndex = 0; xyzIndex < xyzCount; ++xyzIndex)
- {
- if (VecNCompareCustomEpsilon(xyzList, xyzIndex, point, 0.1f, 3)) // larger epsilon decreases quality
- {
- return 1;
- }
- }
-
- return 0;
- }
-
- // create a list of vertex points
- int CM_GetXyzList(const int sideIndex, const Game::ShowCollisionBrushPt* pts, const int ptCount, glm::vec3* xyzList, const int xyzLimit)
- {
- int ptIndex, xyzCount = 0;
-
- if (!pts)
- {
- Game::Com_Error(0, Utils::VA("CM_GetXyzList L#%d :: pts\n", __LINE__));
- return 0;
- }
-
- for (ptIndex = 0; ptIndex < ptCount; ++ptIndex)
- {
- if ((sideIndex == pts[ptIndex].sideIndex[0] || sideIndex == pts[ptIndex].sideIndex[1] || sideIndex == pts[ptIndex].sideIndex[2])
- && !CM_PointInList(pts[ptIndex].xyz, xyzList, xyzCount))
- {
- if (xyzCount == xyzLimit)
- {
- Game::Com_PrintMessage(0, Utils::VA("^1CM_GetXyzList L#%d ^7:: Winding point limit (%i) exceeded on brush face \n", __LINE__, xyzLimit), 0);
- return 0;
- }
-#if DEBUG
- if (Dvars::r_drawCollision_brushDebug->current.enabled)
- {
- Game::Com_PrintMessage(0, Utils::VA("^4CM_GetXyzList L#%d ^7:: Adding X:^2 %.2lf ^7Y:^2 %.2lf ^7Z:^2 %.2lf ^7 \n", __LINE__, xyzList[xyzCount][0], xyzList[xyzCount][1], xyzList[xyzCount][2]), 0);
- }
-#endif
- xyzList[xyzCount] = glm::toVec3(pts[ptIndex].xyz);
- ++xyzCount;
- }
- }
-
-#if DEBUG
- if (Dvars::r_drawCollision_brushDebug->current.enabled)
- {
- Game::Com_PrintMessage(0, Utils::VA("^1CM_GetXyzList L#%d ^7:: Total XYZCOUNT: %d \n", __LINE__, xyzCount), 0);
- }
-#endif
-
- return xyzCount;
- }
-
- // pick the major axis
- void CM_PickProjectionAxes(const float *normal, int *i, int *j)
- {
- int k = 0;
-
- if (fabs(normal[1]) > fabs(normal[0]))
- {
- k = 1;
- }
-
- if (fabs(normal[2]) > fabs(normal[k]))
- {
- k = 2;
- }
-
- *i = ~k & 1;
- *j = ~k & 2;
- }
-
- // cross product
- float CM_SignedAreaForPointsProjected(const float* pt0, const glm::vec3& pt1, const float* pt2, const int i, const int j)
- {
- return (pt2[j] - pt1[j]) * pt0[i] + (pt0[j] - pt2[j]) * pt1[i] + (pt1[j] - pt0[j]) * pt2[i];
- }
-
- // add a point that intersected behind another plane that still is within the bounding box?
- void CM_AddColinearExteriorPointToWindingProjected(Game::winding_t* w, const glm::vec3& pt, int i, int j, int index0, int index1)
- {
- float delta; int axis;
-
-#if DEBUG
- if (w->p[index0][i] == w->p[index1][i] && w->p[index0][j] == w->p[index1][j])
- {
- Game::Com_PrintMessage(0, Utils::VA("^1CM_AddColinearExteriorPointToWindingProjected L#%d ^7:: w->p[%d][%d] %.2lf == w->p[%d][%d] %.2lf && w->p[%d][%d] %.2lf == w->p[%d][%d] %.2lf \n",
- __LINE__, index0, i, w->p[index0][i], index1, i, w->p[index1][i], index0, j, w->p[index0][j], index1, j, w->p[index1][j]), 0);
- }
-#endif
-
- if (fabs(float(uint32_t(w->p[index1][i] - w->p[index0][i]))) < fabs(float(uint32_t(w->p[index1][j] - w->p[index0][j]))))
- {
- axis = j;
- delta = w->p[index1][j] - w->p[index0][j];
- }
- else
- {
- axis = i;
- delta = w->p[index1][i] - w->p[index0][i];
- }
-
- if (delta <= 0.0f)
- {
-
-#if DEBUG
- if (w->p[index0][axis] <= w->p[index1][axis])
- {
- Game::Com_PrintMessage(0, Utils::VA("^1CM_AddColinearExteriorPointToWindingProjected L#%d ^7:: w->p[%d][%d] %.2lf <= w->p[%d][%d] %.2lf \n",
- __LINE__, index0, axis, w->p[index0][axis], index1, axis, w->p[index1][axis]), 0);
- }
-#endif
-
- if (pt[axis] <= w->p[index0][axis])
- {
- if (w->p[index1][axis] > pt[axis])
- {
- glm::setFloat3(w->p[index1], pt);
- }
- }
- else
- {
- glm::setFloat3(w->p[index0], pt);
- }
- }
-
- else
- {
-
-#if DEBUG
- if (w->p[index1][axis] <= w->p[index0][axis])
- {
- Game::Com_PrintMessage(0, Utils::VA("^1CM_AddColinearExteriorPointToWindingProjected L#%d ^7:: w->p[%d][%d] %.2lf < w->p[%d][%d] %.2lf \n",
- __LINE__, index1, axis, w->p[index1][axis], index0, axis, w->p[index0][axis]), 0);
- }
-#endif
-
- if (w->p[index0][axis] <= pt[axis])
- {
- if (pt[axis] > w->p[index1][axis])
- {
- glm::setFloat3(w->p[index1], pt);
- }
- }
- else
- {
- glm::setFloat3(w->p[index0], pt);
- }
- }
- }
-
- // Source :: PolyFromPlane || Q3 :: RemoveColinearPoints ?
- void CM_AddExteriorPointToWindingProjected(Game::winding_t* w, const glm::vec3& pt, int i, int j)
- {
- int index, indexPrev, bestIndex = -1;
- float signedArea, bestSignedArea = FLT_MAX;
-
- indexPrev = w->numpoints - 1;
-
- for (index = 0; index < w->numpoints; ++index)
- {
- signedArea = CM_SignedAreaForPointsProjected(w->p[indexPrev], pt, w->p[index], i, j);
-
- if (bestSignedArea > signedArea)
- {
- bestSignedArea = signedArea;
- bestIndex = index;
- }
-
- indexPrev = index;
- }
-
-#if DEBUG
- if (bestIndex < 0)
- {
- Game::Com_PrintMessage(0, Utils::VA("^1CM_AddExteriorPointToWindingProjected L#%d ^7:: bestIndex < 0 \n", __LINE__), 0);
- }
-#endif
-
- if (bestSignedArea < -0.001f)
- {
- memmove((char *)w->p[bestIndex + 1], (char *)w->p[bestIndex], 12 * (w->numpoints - bestIndex));
-
- glm::setFloat3(w->p[bestIndex], pt);
- ++w->numpoints;
- }
-
- else if (bestSignedArea <= 0.001)
- {
- CM_AddColinearExteriorPointToWindingProjected(w, pt, i, j, (bestIndex + w->numpoints - 1) % w->numpoints, bestIndex);
- }
- }
-
- // create a triangle to check the winding order?
- float CM_RepresentativeTriangleFromWinding(const Game::winding_t *w, const float *normal, int *i0, int *i1, int *i2)
- {
- int i, k; signed int j;
- float testAgainst, areaBest = 0.0f;
- float va[3], vb[3], vc[3];
-
- *i0 = 0; *i1 = 1; *i2 = 2;
-
- for (k = 2; k < w->numpoints; ++k)
- {
- for (j = 1; j < k; ++j)
- {
- vb[0] = w->p[k][0] - w->p[j][0];
- vb[1] = w->p[k][1] - w->p[j][1];
- vb[2] = w->p[k][2] - w->p[j][2];
- for (i = 0; i < j; ++i)
- {
- va[0] = w->p[i][0] - w->p[j][0];
- va[1] = w->p[i][1] - w->p[j][1];
- va[2] = w->p[i][2] - w->p[j][2];
-
- Utils::vector::_Vec3Cross(va, vb, vc);
- testAgainst = fabs(((vc[0] * normal[0]) + (vc[1] * normal[1])) + (vc[2] * normal[2]));
-
- if(testAgainst > 0.0f)
- {
- areaBest = testAgainst;
- *i0 = i;
- *i1 = j;
- *i2 = k;
- }
- }
- }
- }
-
- return areaBest;
- }
-
- // create a plane from points
- bool PlaneFromPoints(float *plane, const float *v0, const float *v1, const float *v2)
- {
- float v2_v0[3], v1_v0[3];
- float length, lengthSq;
-
- v1_v0[0] = v1[0] - v0[0];
- v1_v0[1] = v1[1] - v0[1];
- v1_v0[2] = v1[2] - v0[2];
- v2_v0[0] = v2[0] - v0[0];
- v2_v0[1] = v2[1] - v0[1];
- v2_v0[2] = v2[2] - v0[2];
-
- Utils::vector::_Vec3Cross(v2_v0, v1_v0, plane);
-
- lengthSq = ((plane[0] * plane[0]) + (plane[1] * plane[1])) + (plane[2] * plane[2]);
-
- if (lengthSq >= 2.0f)
- goto WEGOOD;
-
- if (lengthSq == 0.0f)
- return false;
-
- if ((((((v2_v0[0] * v2_v0[0]) + (v2_v0[1] * v2_v0[1])) + (v2_v0[2] * v2_v0[2]))
- * (((v1_v0[0] * v1_v0[0]) + (v1_v0[1] * v1_v0[1])) + (v1_v0[2] * v1_v0[2]))) * 0.0000010000001) >= lengthSq)
- {
- v1_v0[0] = v2[0] - v1[0];
- v1_v0[1] = v2[1] - v1[1];
- v1_v0[2] = v2[2] - v1[2];
- v2_v0[0] = v0[0] - v1[0];
- v2_v0[1] = v0[1] - v1[1];
- v2_v0[2] = v0[2] - v1[2];
-
- Utils::vector::_Vec3Cross(v2_v0, v1_v0, plane);
-
- if ((((((v2_v0[0] * v2_v0[0]) + (v2_v0[1] * v2_v0[1])) + (v2_v0[2] * v2_v0[2]))
- * (((v1_v0[0] * v1_v0[0]) + (v1_v0[1] * v1_v0[1])) + (v1_v0[2] * v1_v0[2]))) * 0.0000010000001) >= lengthSq)
- {
- return false;
- }
- }
-
- WEGOOD:
- length = sqrt(lengthSq);
- plane[0] = plane[0] / length;
- plane[1] = plane[1] / length;
- plane[2] = plane[2] / length;
- plane[3] = ((v0[0] * plane[0]) + (v0[1] * plane[1])) + (v0[2] * plane[2]);
-
- return true;
- }
-
- // reverse clock-wise windings
- void CM_ReverseWinding(Game::winding_t *w)
- {
- int i; float windingSave[3];
-
- for (i = 0; i < w->numpoints / 2; ++i)
- {
- windingSave[0] = w->p[i][0];
- windingSave[1] = w->p[i][1];
- windingSave[2] = w->p[i][2];
-
- w->p[i][0] = w->p[w->numpoints - 1 - i][0];
- w->p[i][1] = w->p[w->numpoints - 1 - i][1];
- w->p[i][2] = w->p[w->numpoints - 1 - i][2];
-
- w->p[w->numpoints - 1 - i][0] = windingSave[0];
- w->p[w->numpoints - 1 - i][1] = windingSave[1];
- w->p[w->numpoints - 1 - i][2] = windingSave[2];
- }
- }
-
- // Map Export - CM_BuildBrushWindingForSide
- bool CM_BuildBrushWindingForSideMapExport(Game::winding_t *winding, const float *planeNormal, const int sideIndex, Game::ShowCollisionBrushPt *pts, int ptCount, Game::map_brushSide_t *bSide)
- {
- int xyzCount, i, i0, i1, i2, j, k;
- Game::vec4_t plane;
-
- Utils::vector::_VectorZero4(plane);
-
- if (!winding)
- {
- Game::Com_Error(0, Utils::VA("CM_BuildBrushWindingForSide L#%d :: winding \n", __LINE__));
- return false;
- }
-
- if (!planeNormal)
- {
- Game::Com_Error(0, Utils::VA("CM_BuildBrushWindingForSide L#%d :: planeNormal \n", __LINE__));
- return false;
- }
-
- if (!pts)
- {
- Game::Com_Error(0, Utils::VA("CM_BuildBrushWindingForSide L#%d :: pts \n", __LINE__));
- return false;
- }
-
- glm::vec3 _xyzList[1024];
- xyzCount = CM_GetXyzList(sideIndex, pts, ptCount, _xyzList, 1024);
-
- if (xyzCount < 3)
- {
- return false;
- }
-
- CM_PickProjectionAxes(planeNormal, &i, &j);
-
- glm::setFloat3(winding->p[0], _xyzList[0]);
- glm::setFloat3(winding->p[1], _xyzList[1]);
-
- winding->numpoints = 2;
-
- for (k = 2; k < xyzCount; ++k)
- {
- CM_AddExteriorPointToWindingProjected(winding, _xyzList[k], i, j);
- }
-
- if (CM_RepresentativeTriangleFromWinding(winding, planeNormal, &i0, &i1, &i2) < 0.001)
- {
- return false;
- }
-
- PlaneFromPoints(&*plane, winding->p[i0], winding->p[i1], winding->p[i2]);
-
- if ((((plane[0] * planeNormal[0]) + (plane[1] * planeNormal[1])) + (plane[2] * planeNormal[2])) < 0.0f)
- {
- CM_ReverseWinding(winding);
- }
-
- // huh
- Game::winding_t *w = winding;
-
- for (auto _i = 0; _i < 3; _i++)
- { for (auto _j = 0; _j < 3; _j++)
- {
- if (fabs(w->p[_i][_j]) < Dvars::mapexport_brushEpsilon1->current.value)
- {
- w->p[_i][_j] = 0;
- }
- else if (fabs((int)w->p[_i][_j] - w->p[_i][_j]) < Dvars::mapexport_brushEpsilon2->current.value)
- {
- w->p[_i][_j] = (float)(int)w->p[_i][_j];
- }
- }
- }
-
- int p1;
- int planenum = 0;
-
- //three non-colinear points to define the plane
- if (planenum & 1) { p1 = 1; }
- else { p1 = 0; }
-
- // *
- // create the brushside
-
- // plane 0
- for (auto idx = 0; idx < 3; idx++)
- {
- bSide->brushPlane[0].point[idx] = w->p[p1][idx];
- }
-
- // plane 1
- for (auto idx = 0; idx < 3; idx++)
- {
- bSide->brushPlane[1].point[idx] = w->p[!p1][idx];
- }
-
- // plane 2
- for (auto idx = 0; idx < 3; idx++)
- {
- bSide->brushPlane[2].point[idx] = w->p[2][idx];
- }
-
- /*if (!Utils::polylib::CheckWinding(w))
- {
- Game::Com_PrintMessage(0, "removed degenerate brushside.\n", 0);
- return false;
- }*/
-
- return true;
- }
-
- // build winding (poly) for side
- bool CM_BuildBrushWindingForSide(Game::winding_t* winding, const float* planeNormal, const int sideIndex, Game::ShowCollisionBrushPt* pts, int ptCount)
- {
- int xyzCount, i, i0, i1, i2, j, k;
- Game::vec4_t plane; Utils::vector::_VectorZero4(plane);
-
- if (!winding)
- {
- Game::Com_Error(0, Utils::VA("CM_BuildBrushWindingForSide L#%d :: winding \n", __LINE__));
- return false;
- }
-
- if (!planeNormal)
- {
- Game::Com_Error(0, Utils::VA("CM_BuildBrushWindingForSide L#%d :: planeNormal \n", __LINE__));
- return false;
- }
-
- if (!pts)
- {
- Game::Com_Error(0, Utils::VA("CM_BuildBrushWindingForSide L#%d :: pts \n", __LINE__));
- return false;
- }
-
- // create a list of vertex points
- glm::vec3 _xyzList[1024];
-
- xyzCount = CM_GetXyzList(sideIndex, pts, ptCount, _xyzList, 1024);
-
- // we need atleast a triangle to create a poly
- if (xyzCount < 3)
- {
- return false;
- }
-
- // direction of camera plane
- glm::vec3 cameraDirectionToPlane = _xyzList[0] - Game::Globals::locPmove_cameraOrigin;
-
- // dot product between line from camera to the plane and the normal
- // if dot > 0 then the plane is facing away from the camera (dot = 1 = plane is facing the same way as the camera; dot = -1 = plane looking directly towards the camera)
- if (glm::dot( glm::vec3(planeNormal[0], planeNormal[1], planeNormal[2]), cameraDirectionToPlane ) > 0.0f && !Dvars::r_drawCollision_polyFace->current.enabled)
- {
- return false;
- }
-
- // find the major axis
- CM_PickProjectionAxes(planeNormal, &i, &j);
-
- glm::setFloat3(winding->p[0], _xyzList[0]);
- glm::setFloat3(winding->p[1], _xyzList[1]);
-
- winding->numpoints = 2;
-
- for (k = 2; k < xyzCount; ++k)
- {
- CM_AddExteriorPointToWindingProjected(winding, _xyzList[k], i, j);
- }
-
- // build a triangle of our winding points so we can check if the winding is clock-wise
- if (CM_RepresentativeTriangleFromWinding(winding, planeNormal, &i0, &i1, &i2) < 0.001)
- {
- // do nothing if it is counter clock-wise
- return false;
- }
-
- // *
- // winding is clock-wise ..
-
- // create a temp plane
- PlaneFromPoints(&*plane, winding->p[i0], winding->p[i1], winding->p[i2]);
-
- // if our winding has a clock-wise winding, reverse it
- if (((plane[0] * planeNormal[0]) + (plane[1] * planeNormal[1]) + (plane[2] * planeNormal[2])) > 0.0f)
- {
- CM_ReverseWinding(winding);
- }
-
- return 1;
- }
-
- // Allocates a single brushside
- Game::map_brushSide_t *Alloc_BrushSide(void)
- {
- Game::map_brushSide_t *bSide;
- bSide = (Game::map_brushSide_t*)malloc(sizeof(*bSide));
-
- if (bSide)
- {
- memset(bSide, 0, sizeof(Game::map_brushSide_t));
- return bSide;
- }
-
- Game::Com_Error(0, "Alloc_BrushSide :: alloc failed!");
- return 0;
- }
-
- bool CM_IsMapBrushSideWithinBounds(const Game::map_brushSide_t* bSide, const glm::vec3& mins, const glm::vec3& maxs)
- {
- if (!bSide)
- {
- return false;
- }
-
- for (auto plane = 0; plane < 3; plane++)
- {
- if (!Utils::polylib::PointWithinBounds(glm::toVec3(bSide->brushPlane[plane].point), mins, maxs, 0.25f))
- {
- return false;
- }
- }
-
- return true;
- }
-
- // rebuild and draw brush from bounding box and dynamic sides
- void CM_ShowSingleBrushCollision(Game::cbrush_t *brush, const float *color, int brushIndex, bool enableExport = true)
- {
- // skip all brush calculations when flicker mode is on and not exporting a map file
- if (Dvars::r_drawCollision_flickerBrushes->current.enabled && !export_inProgress)
- {
- // on-time :: if amount of passed frames > flickerOn + flickerOff
- if (collisionFlickerCounter > Dvars::r_drawCollision_flickerOnTime->current.integer + Dvars::r_drawCollision_flickerOffTime->current.integer)
- {
- collisionFlickerCounter = 0;
- }
-
- // off-time :: skip collision drawing if amount of "on-frames" are larger then our "on-frames-dvar"
- else if (collisionFlickerCounter > Dvars::r_drawCollision_flickerOnTime->current.integer)
- {
- return;
- }
- }
-
- int ptCount, sideIndex;
- Game::ShowCollisionBrushPt brushPts[CM_MAX_BRUSHPOINTS_FROM_INTERSECTIONS]; // T5: 1024 .. wtf
-
- if (!brush)
- {
- Game::Com_Error(0, Utils::VA("CM_ShowSingleBrushCollision L#%d :: brush \n", __LINE__));
- return;
- }
-
- if (!color)
- {
- Game::Com_Error(0, Utils::VA("CM_ShowSingleBrushCollision L#%d :: color\n", __LINE__));
- return;
- }
-
- // Create static sides (CM_BuildAxialPlanes)
- Game::axialPlane_t _axialPlanes[6];
- _axialPlanes[0].plane = glm::vec3(-1.0f, 0.0f, 0.0f);
- _axialPlanes[0].dist = -brush->mins[0];
-
- _axialPlanes[1].plane = glm::vec3(1.0f, 0.0f, 0.0f);
- _axialPlanes[1].dist = brush->maxs[0];
-
- _axialPlanes[2].plane = glm::vec3(0.0f, -1.0f, 0.0f);
- _axialPlanes[2].dist = -brush->mins[1];
-
- _axialPlanes[3].plane = glm::vec3(0.0f, 1.0f, 0.0f);
- _axialPlanes[3].dist = brush->maxs[1];
-
- _axialPlanes[4].plane = glm::vec3(0.0f, 0.0f, -1.0f);
- _axialPlanes[4].dist = -brush->mins[2];
-
- _axialPlanes[5].plane = glm::vec3(0.0f, 0.0f, 1.0f);
- _axialPlanes[5].dist = brush->maxs[2];
-
- // intersect all planes, 3 at a time, to to reconstruct face windings
- ptCount = CM_ForEachBrushPlaneIntersection(brush, _axialPlanes, brushPts);
-
- // we need atleast 4 valid points
- if (ptCount >= 4)
- {
- // list of brushsides we are going to create within "CM_BuildBrushWindingForSideMapExport"
- std::vector mapBrush;
-
- float planeNormal[3];
-
- auto poly_lit = Dvars::r_drawCollision_polyLit->current.enabled;
- auto poly_outlines = Dvars::r_drawCollision->current.integer == 3 ? true : false;
- auto poly_linecolor = Dvars::r_drawCollision_lineColor->current.vector;
- auto poly_depth = Dvars::r_drawCollision_polyDepth->current.enabled;
- auto poly_face = Dvars::r_drawCollision_polyFace->current.enabled;
-
- // -------------------------------
- // brushside [0]-[5] (axialPlanes)
-
- for (sideIndex = 0; (unsigned int)sideIndex < 6; ++sideIndex)
- {
- glm::setFloat3(planeNormal, _axialPlanes[sideIndex].plane);
-
- // build winding for the current brushside and check if it is visible (culling)
- if (CM_BuildBrushWindingForSide((Game::winding_t *)windingPool, planeNormal, sideIndex, brushPts, ptCount))
- {
- if (Dvars::r_drawCollision->current.integer == 1)
- {
- _Debug::RB_AddAndDrawDebugLines(*(DWORD*)windingPool, (float(*)[3])& windingPool[4], Dvars::r_drawCollision_lineColor->current.vector);
- }
- else
- {
- _Debug::RB_DrawPoly(
- /* numPts */ *(DWORD*)windingPool,
- /* points */ (float(*)[3])& windingPool[4],
- /* pColor */ color,
- /* pLit */ poly_lit,
- /* pOutline */ poly_outlines,
- /* pLineCol */ poly_linecolor,
- /* pDepth */ poly_depth,
- /* pFace */ poly_face);
- }
-
- Game::Globals::dbgColl_drawnPlanesAmountTemp++;
- }
-
- // create brushsides from brush bounds (side [0]-[5])
- if (export_inProgress && enableExport)
- {
- // allocate a brushside
- Game::map_brushSide_t *bSide = Alloc_BrushSide();
-
- // create a brushside from windings
- if (CM_BuildBrushWindingForSideMapExport((Game::winding_t *)windingPool, planeNormal, sideIndex, brushPts, ptCount, bSide))
- {
- // brushside is valid
- mapBrush.push_back(bSide);
- }
-
- else
- {
- // not a valid brushside so free it
- free(bSide);
- }
- }
- }
-
- // ---------------------------------
- // brushside [6] and up (additional)
-
- for (sideIndex = 6; sideIndex < (std::int32_t)brush->numsides + 6; ++sideIndex)
- {
- if (CM_BuildBrushWindingForSide((Game::winding_t *)windingPool, brush->sides[sideIndex - 6].plane->normal, sideIndex, brushPts, ptCount))
- {
- if (Dvars::r_drawCollision->current.integer == 1)
- {
- _Debug::RB_AddAndDrawDebugLines(*(DWORD*)windingPool, (float(*)[3]) & windingPool[4], Dvars::r_drawCollision_lineColor->current.vector);
- }
- else
- {
- _Debug::RB_DrawPoly(
- /* numPts */ *(DWORD*)windingPool,
- /* points */ (float(*)[3]) & windingPool[4],
- /* pColor */ color,
- /* pLit */ poly_lit,
- /* pOutline */ poly_outlines,
- /* pLineCol */ poly_linecolor,
- /* pDepth */ poly_depth,
- /* pFace */ poly_face);
- }
-
- Game::Globals::dbgColl_drawnPlanesAmountTemp++;
- }
-
- // create brushsides from cm->brushes->sides (side [6] and up)
- if (export_inProgress && enableExport)
- {
- // allocate a brushside
- Game::map_brushSide_t *bSide = Alloc_BrushSide();
-
- // create a brushside from windings
- if (CM_BuildBrushWindingForSideMapExport((Game::winding_t *)windingPool, brush->sides[sideIndex - 6].plane->normal, sideIndex, brushPts, ptCount, bSide))
- {
- // brushside is valid
- mapBrush.push_back(bSide);
- }
-
- else
- {
- // not a valid brushside so free it
- free(bSide);
- }
- }
- }
-
- // if we are exporting the map
- if (export_inProgress && enableExport)
- {
- bool dirty_hack_5_sides = false;
-
- glm::vec3 bMins = glm::toVec3(brush->mins);
- glm::vec3 bMaxs = glm::toVec3(brush->maxs);
-
- // we need atleast 6 valid brushsides
- if (mapBrush.size() < 6)
- {
- if (mapBrush.size() == 5 && Dvars::mapexport_brush5Sides && Dvars::mapexport_brush5Sides->current.enabled)
- {
- dirty_hack_5_sides = true;
- }
- else
- {
- return;
- }
- }
-
- // check brushes defined by more then their axialplanes
- if(mapBrush.size() > 6)
- {
- if (glm::distance(bMins, bMaxs) < Dvars::mapexport_brushMinSize->current.value)
- {
- return;
- }
- }
-
- for (auto side = 0; side < static_cast(mapBrush.size()); side++)
- {
- if (!CM_IsMapBrushSideWithinBounds(mapBrush[side], bMins, bMaxs))
- {
- return;
- }
- }
-
- // swap brushsides (bottom, top, right, back, left, front)
- if (!dirty_hack_5_sides)
- {
- std::swap(mapBrush[0], mapBrush[5]);
- }
-
- std::swap(mapBrush[3], mapBrush[4]);
- std::swap(mapBrush[1], mapBrush[3]);
- std::swap(mapBrush[0], mapBrush[1]);
-
- // *
- // do not export brushmodels as normal brushes
- // write brushside strings to g_mapBrushModelList instead
-
- if (brush->isSubmodel)
- {
- // clear any existing sides
- g_mapBrushModelList[brush->cmSubmodelIndex].brushSides.clear();
- }
- else
- {
- // start brush
- export_mapFile << Utils::VA("// brush %d\n{", export_currentBrushIndex) << std::endl;
- export_mapFile << "layer \"000_Global/Brushes\"" << std::endl;
-
- // global brush exporting index count
- export_currentBrushIndex++;
-
- // write brush contents
- if (brush->contents & CM_CONTENTS_DETAIL)
- {
- export_mapFile << "contents detail;" << std::endl;
- }
- else if (brush->contents & CM_CONTENTS_CLIPSHOT)
- {
- export_mapFile << "contents weaponClip;" << std::endl;
- }
- }
-
- // print brush sides and material info
- for (auto bs = 0; bs < (int)mapBrush.size(); bs++)
- {
- std::string currBrushSide = Utils::VA(" ( %d %d %d ) ( %d %d %d ) ( %d %d %d ) ",
- (int)mapBrush[bs]->brushPlane[0].point[0], (int)mapBrush[bs]->brushPlane[0].point[1], (int)mapBrush[bs]->brushPlane[0].point[2],
- (int)mapBrush[bs]->brushPlane[1].point[0], (int)mapBrush[bs]->brushPlane[1].point[1], (int)mapBrush[bs]->brushPlane[1].point[2],
- (int)mapBrush[bs]->brushPlane[2].point[0], (int)mapBrush[bs]->brushPlane[2].point[1], (int)mapBrush[bs]->brushPlane[2].point[2]);
-
- if (!brush->isSubmodel)
- {
- export_mapFile << currBrushSide.c_str();
- }
-
- // get material index for the current brush side
- int matIdx = 0;
-
- // for the 6 brush sides created from axialplanes (brush bounds)
- if (bs < 6)
- {
- // get material (brush->axialMaterialNum[array][index]) for the current brush side
- // mapping axialnum order to .map brush side order
- switch (bs)
- {
- case 0: // bottom
- matIdx = (int)brush->axialMaterialNum[0][2];
- break;
- case 1: // top
- matIdx = (int)brush->axialMaterialNum[1][2];
- break;
- case 2: // right
- matIdx = (int)brush->axialMaterialNum[0][1];
- break;
- case 3: // back
- matIdx = (int)brush->axialMaterialNum[1][0];
- break;
- case 4: // left
- matIdx = (int)brush->axialMaterialNum[1][1];
- break;
- case 5: // front
- matIdx = (int)brush->axialMaterialNum[0][0];
- break;
- }
- }
-
- // we have atleast 1 additional brush side
- else
- {
- if (!dirty_hack_5_sides)
- {
- // additional brush sides start at index 0
- matIdx = brush->sides[bs - 6].materialNum;
- }
- }
-
- // *
- // Material Dimensions
-
- // default values if we fail to find the correct texture size
- int texWidth = 128;
- int texHeight = 128;
-
- // texture size scalar (depends on texture quality settings)
- float tex_scalar = 1.0f;
-
- const auto r_picmip = Game::Dvar_FindVar("r_picmip");
- if (r_picmip)
- {
- switch (r_picmip->current.integer)
- {
- case 0: // extra
- tex_scalar = 0.25f;
- break;
- case 1: // high
- tex_scalar = 0.5f;
- break;
- case 2: // normal
- tex_scalar = 1.0f;
- break;
- case 3:
- tex_scalar = 2.0f;
- break;
- default: // should not happen
- tex_scalar = 1.0f;
- }
- }
-
- // get the world material and its size
- std::string material_name_for_brushside = Game::cm->materials[matIdx].material;
- std::string materialForSide = "wc/"s + material_name_for_brushside;
-
- const auto mat = Game::Material_RegisterHandle(materialForSide.c_str(), 3);
- if (mat)
- {
- for (auto tex = 0; tex < mat->textureCount; tex++)
- {
- // 0x2 = color, 0x5 = normal, 0x8 = spec
- if (mat->textureTable[tex].u.image->semantic == 0x2)
- {
- texWidth = static_cast((mat->textureTable[tex].u.image->width * tex_scalar)); // loaded texture sizes vary (texture quality settings)
- texHeight = static_cast((mat->textureTable[tex].u.image->height * tex_scalar)); // so we need to use a scalar to get a 0.25 stretch in radiant
-
- break;
- }
- }
- }
-
- //std::string materialForSide = Game::cm->materials[matIdx].material;
-
- // for each material in memory
- //for (auto matMem = 0; matMem < Game::_gfxWorld->materialMemoryCount; matMem++)
- //{
- // // current material name
- // std::string currMatName = Game::_gfxWorld->materialMemory[matMem].material->info.name; // material with suffix wc/ mc/ ..
-
- // // check if material is our brush side material
- // if (currMatName.find(materialForSide) != std::string::npos)
- // {
- // // find the colormap of the brush side material
- // for (auto texture = 0; texture < Game::_gfxWorld->materialMemory[matMem].material->textureCount; texture++)
- // {
- // // current texture name
- // //std::string currTexName = Game::_gfxWorld->materialMemory[matMem].material->textureTable[texture].u.image->name;
- // // .... //if (Utils::has_suffix(currTexName, std::string("_col")) || Utils::has_suffix(currTexName, std::string("_c")))
-
- // // 0x2 = color, 0x5 = normal, 0x8 = spec
- // if (Game::_gfxWorld->materialMemory[matMem].material->textureTable[texture].u.image->semantic == 0x2)
- // {
- // texWidth = (int)(Game::_gfxWorld->materialMemory[matMem].material->textureTable[texture].u.image->width * 0.25f); // assuming 0.25 horizontal stretch
- // texHeight = (int)(Game::_gfxWorld->materialMemory[matMem].material->textureTable[texture].u.image->height * 0.25f); // assuming 0.25 vertical stretch
-
- // break;
- // }
- // }
-
- // // we found the correct material
- // break;
- // }
- //}
-
- if (!brush->isSubmodel)
- {
- // materialname, width, height, xpos, ypos, rotation, ?, lightmapMat, lmap_sampleSizeX, lmap_sampleSizeY, lmap_xpos, lmap_ypos, lmap_rotation, ?
- export_mapFile << Utils::VA("%s %d %d 0 0 0 0 lightmap_gray 16384 16384 0 0 0 0\n", material_name_for_brushside.c_str(), texWidth, texHeight);
- }
- else
- {
- g_mapBrushModelList[brush->cmSubmodelIndex].brushSides.push_back(currBrushSide + Utils::VA("%s %d %d 0 0 0 0 lightmap_gray 16384 16384 0 0 0 0\n", material_name_for_brushside.c_str(), texWidth, texHeight));
- }
-
- }
-
- if (!brush->isSubmodel)
- {
- // end brush
- export_mapFile << "}" << std::endl;
- }
-
- // single brush for radiant ......
- // Game::ServerCommand cmd;
-
- // Game::Com_PrintMessage(0, "exporting brushsides for selected brush:\n", 0);
-
- // // for all brushsides ::
- // // send index first, then the brushside
- // for (auto bs = 0; bs < (int)mapBrush.size(); bs++)
- // {
- // memset(&cmd, 0, sizeof(Game::ServerCommand));
- // cmd.type = Game::SERVER_EXPORT_SINGLE_BRUSH_FACE_INDEX;
- // sprintf_s(cmd.strCommand, "%d", bs);
-
- // RadiantRemote::RemoteNet_SendPacket(&cmd);
-
- // memset(&cmd, 0, sizeof(Game::ServerCommand));
- // cmd.type = Game::SERVER_EXPORT_SINGLE_BRUSH_FACE;
- // sprintf_s(cmd.strCommand, "%.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f",
- // mapBrush[bs]->brushPlane[0].point[0], mapBrush[bs]->brushPlane[0].point[1], mapBrush[bs]->brushPlane[0].point[2],
- // mapBrush[bs]->brushPlane[1].point[0], mapBrush[bs]->brushPlane[1].point[1], mapBrush[bs]->brushPlane[1].point[2],
- // mapBrush[bs]->brushPlane[2].point[0], mapBrush[bs]->brushPlane[2].point[1], mapBrush[bs]->brushPlane[2].point[2]);
-
- // RadiantRemote::RemoteNet_SendPacket(&cmd);
-
- // Game::Com_PrintMessage(0, Utils::VA("< ( %.2f %.2f %.2f ) ( %.2f %.2f %.2f ) ( %.2f %.2f %.2f ) >\n",
- // mapBrush[bs]->brushPlane[0].point[0], mapBrush[bs]->brushPlane[0].point[1], mapBrush[bs]->brushPlane[0].point[2],
- // mapBrush[bs]->brushPlane[1].point[0], mapBrush[bs]->brushPlane[1].point[1], mapBrush[bs]->brushPlane[1].point[2],
- // mapBrush[bs]->brushPlane[2].point[0], mapBrush[bs]->brushPlane[2].point[1], mapBrush[bs]->brushPlane[2].point[2]), 0);
- // }
- //}
- }
- }
- }
-
- // Brush View Culling
- int BoxOnPlaneSide(const float *emins, const float *emaxs, const Game::cplane_s *p)
- {
- float dist1, dist2;
- int sides = 0;
-
- // fast axial cases
- if (p->type < 3)
- {
- if (p->dist <= emins[p->type]) { return 1; }
- if (p->dist >= emaxs[p->type]) { return 2; }
-
- return 3;
- }
-
- // general case
- switch (p->signbits)
- {
- case 0:
- dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2];
- dist2 = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2];
- break;
-
- case 1:
- dist1 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2];
- dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2];
- break;
-
- case 2:
- dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2];
- dist2 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2];
- break;
-
- case 3:
- dist1 = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2];
- dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2];
- break;
-
- case 4:
- dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2];
- dist2 = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2];
- break;
-
- case 5:
- dist1 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2];
- dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2];
- break;
-
- case 6:
- dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2];
- dist2 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2];
- break;
-
- case 7:
- dist1 = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2];
- dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2];
- break;
-
- default:
- dist1 = dist2 = 0; // shut up compiler
- break;
- }
-
- if (dist1 >= p->dist)
- {
- sides = 1;
- }
-
- if (dist2 < p->dist)
- {
- sides |= 2;
- }
-
- return sides;
- }
-
- // check if any side of a brush is within the view frustum
- bool CM_BrushInView(Game::cbrush_t *brush, Game::cplane_s *frustumPlanes, int frustumPlaneCount)
- {
- int frustumPlaneIndex;
-
- if (!frustumPlanes)
- {
- Game::Com_Error(0, Utils::VA("CM_BrushInView L#%d :: frustumPlanes\n", __LINE__));
- return false;
- }
-
- for (frustumPlaneIndex = 0; frustumPlaneIndex < frustumPlaneCount; ++frustumPlaneIndex)
- {
- if (!(BoxOnPlaneSide(brush->mins, brush->maxs, &frustumPlanes[frustumPlaneIndex]) & 1))
- {
- return 0;
- }
- }
-
- return 1;
- }
-
- // check if the material selected via dvar equals the current brush material or one of its sides if the first side uses caulk
- bool CM_ValidBrushMaterialSelection(Game::cbrush_t *brush, int materialNumFromDvar)
- {
- // we can also filter materials by substrings with "r_drawCollision_materialInclude" ( "clip_player" returns true when using "clip" )
- std::string includeString = "";
-
- // workaround till we get dvar strings to register - actually, cba. to implement that now
- switch (Dvars::r_drawCollision_materialInclude->current.integer)
- {
- case 1:
- includeString = "clip";
- break;
-
- case 2:
- includeString = "mantle";
- break;
-
- case 3:
- includeString = "trigger";
- break;
-
- case 4:
- includeString = "all";
- return true; // no need to check any material so return true
- //break; // removing brushes using a trigger texture
-
- case 5:
- includeString = "all-no-tools";
- break;
- case 6:
- includeString = "all-no-tools-clip";
- break;
- }
-
- // check if we are within array bounds
- if (static_cast(brush->axialMaterialNum[0][0]) >= g_mapMaterialListDuplicates.size())
- {
- return false;
- }
-
- if (static_cast(materialNumFromDvar) >= g_mapMaterialListSingle.size())
- {
- return false;
- }
-
- // bridge dupe materials list with the cleaned list
- std::string materialNameFromDuplicates = g_mapMaterialListDuplicates[(int)brush->axialMaterialNum[0][0]];
- std::string materialNameFromSingle = g_mapMaterialListSingle[materialNumFromDvar];
-
- // instantly found our material
- if (materialNameFromDuplicates == materialNameFromSingle)
- {
- return true;
- }
-
- // if filter is not empty
- if (!includeString.empty())
- {
- // if we found a matching substring
- if (materialNameFromDuplicates.find(includeString) != std::string::npos)
- {
- return true;
- }
-
- // draw all materials (mainly for map exporting)
- else if (includeString == "all")
- {
- // no longer skip triggers
- /*if (materialNameFromDuplicates.find("trigger") != std::string::npos)
- {
- return false;
- }*/
-
- return true;
- }
-
- // draw all materials without info volumes
- else if (includeString == "all-no-tools")
- {
- if(materialNameFromDuplicates.find("portal") != std::string::npos)
- return false;
-
- if (materialNameFromDuplicates.find("hint") != std::string::npos)
- return false;
-
- if (materialNameFromDuplicates.find("volume") != std::string::npos)
- return false;
-
- if (materialNameFromDuplicates.find("mantle") != std::string::npos)
- return false;
-
- if (materialNameFromDuplicates.find("trigger") != std::string::npos)
- return false;
-
- if (materialNameFromDuplicates.find("sky") != std::string::npos)
- return false;
-
- // if no excluded materials were found, return true
- return true;
- }
-
- else if (includeString == "all-no-tools-clip")
- {
- if (materialNameFromDuplicates.find("portal") != std::string::npos)
- return false;
-
- if (materialNameFromDuplicates.find("hint") != std::string::npos)
- return false;
-
- if (materialNameFromDuplicates.find("volume") != std::string::npos)
- return false;
-
- if (materialNameFromDuplicates.find("mantle") != std::string::npos)
- return false;
-
- if (materialNameFromDuplicates.find("trigger") != std::string::npos)
- return false;
-
- if (materialNameFromDuplicates.find("sky") != std::string::npos)
- return false;
-
- if (materialNameFromDuplicates.find("clip") != std::string::npos)
- return false;
-
- // if no excluded materials were found, return true
- return true;
- }
- }
-
- // if the brush has caulk as its first side, check the other sides for our material/substring
- else if (materialNameFromDuplicates == "caulk")
- {
- int currentMatArray;
-
- // check the first and second material array
- for (currentMatArray = 0; currentMatArray < 2; currentMatArray++)
- {
- // check its 3 elements
- for (int matIndex = 0; matIndex < 3; matIndex++)
- {
- // if one of the sides uses our material
- if (g_mapMaterialListDuplicates[(int)brush->axialMaterialNum[currentMatArray][matIndex]] == materialNameFromSingle)
- {
- return true;
- }
-
- // if we have a filter set
- if (Dvars::r_drawCollision_materialInclude->current.integer != 0)
- {
- // if one of the sides matches our substring
- if (g_mapMaterialListDuplicates[(int)brush->axialMaterialNum[currentMatArray][matIndex]].find(includeString) != std::string::npos)
- {
- return true;
- }
- }
- }
- }
- }
-
- // brush does not contain our material/substring
- return false;
- }
-
- // resets a selection bounding box
- void CM_ResetSelectionBox(Game::boundingBox_t* box)
- {
- memset(box, 0, sizeof(Game::boundingBox_t));
-
- box->mins = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
- box->maxs = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
- box->wasReset = true;
- }
-
- // get mins/maxs from points
- void CM_BoundsFromPoints(const int& numPoints, const glm::vec3* points, glm::vec3& mins, glm::vec3& maxs)
- {
- mins = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
- maxs = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
-
- for (auto sPoint = 0; sPoint < numPoints; sPoint++)
- {
- for (auto pAxis = 0; pAxis < 3; pAxis++)
- {
- // mins :: find the closest point on each axis
- if (mins[pAxis] > points[sPoint][pAxis])
- mins[pAxis] = points[sPoint][pAxis];
-
- // maxs :: find the furthest point on each axis
- if (maxs[pAxis] < points[sPoint][pAxis])
- maxs[pAxis] = points[sPoint][pAxis];
- }
- }
- }
-
- // calculate brush midpoint
- glm::vec3 CM_GetBrushMidpoint(const Game::cbrush_t* brush, bool xyzMidpoint = false)
- {
- if (!xyzMidpoint)
- {
- return (glm::vec3(brush->mins[0], brush->mins[1], 0.0f)
- + glm::vec3(brush->maxs[0], brush->maxs[1], 0.0f)) * 0.5f;
- }
-
- else
- {
- return (glm::vec3(brush->mins[0], brush->mins[1], brush->mins[2])
- + glm::vec3(brush->maxs[0], brush->maxs[1], brush->maxs[2])) * 0.5f;
- }
- }
-
- // check if a brushes midpoint is within a selection box
- bool CM_IsBrushWithinSelectionBox(const Game::cbrush_t* brush, const Game::boundingBox_t* selectionBox)
- {
- if (brush && selectionBox->isBoxValid)
- {
- return Utils::polylib::PointWithinBounds(CM_GetBrushMidpoint(brush, true), selectionBox->mins, selectionBox->maxs, 0.25f);
- }
-
- return false;
- }
-
- // check if a triangles midpoint is within a selection box
- bool CM_IsTriangleWithinSelectionBox(const Game::map_patchTris_t* tris, Game::boundingBox_t* selectionBox)
- {
- for (auto triCoord = 0; triCoord < 3; triCoord++)
- {
- if(!Utils::polylib::PointWithinBounds(glm::toVec3(tris->coords[triCoord].xyz), selectionBox->mins, selectionBox->maxs, 0.25f))
- {
- return false;
- }
- }
-
- return true;
- }
-
- // -------------
- // CM_OnceOnInit
-
- // :: create "g_mapMaterialListDuplicates" and write all materials without \0
- // :: create "g_mapMaterialListSingle" add all material from matListDuplicates without creating duplicates
- // :: create a dvar string from "g_mapMaterialListSingle"
- // :: compare axialMaterialNum with "g_mapMaterialListDuplicates" to get the original materialName
- // :: compare "g_mapMaterialListDuplicates" materialName with the choosen dvar Material name
- // :: draw the brush if they match
-
- // stuff we only do once per map after r_drawcollision was turned on
- void CM_OnceOnInit()
- {
- // assign a color to each brush (modified cbrush_t struct) so we use persistent colors even when sorting brushes
- int colorCounter = 0;
-
- // only once per map
- //if (g_mapNameCm != Game::cm->name)
- if(!Game::Globals::dbgColl_initialized || Utils::Q_stricmp(g_mapNameCm, Game::cm->name))
- {
- Game::Com_PrintMessage(0, "[Debug Collision] : initializing ...\n", 0);
-
- // list of all brushmodels within the current map mapped to their respective brushes in cm->brushes
- Utils::Entities mapEnts(Game::cm->mapEnts->entityString);
- g_mapBrushModelList = mapEnts.getBrushModels();
-
- Game::Com_PrintMessage(0, Utils::VA("|-> found %d submodels\n", static_cast(g_mapBrushModelList.size())), 0);
-
- // hacky string dvar
- auto dvarName = "r_drawCollision_brushIndexFilter";
- Game::Dvar_RegisterString_hacky(dvarName, "null", "Specifies which brushes to draw. Ignores all other filters and will disable brush sorting.\nInput example: \"101 99 2\" :: \"^1null^7\" disables this filter");
-
- // assign hacky dvar to the global dvar
- Dvars::r_drawCollision_brushIndexFilter = Game::Dvar_FindVar(dvarName);
-
-#if DEBUG
- // change dvar to only draw 1 brush when using a debug build (debug build has really bad performance)
- Game::Dvar_SetValue(Dvars::r_drawCollision_brushAmount, 1);
-#endif
- // cap brushAmount
- Dvars::r_drawCollision_brushAmount->domain.integer.max = Game::cm->numBrushes - 1;
-
- // reset brush distance filter
- Game::Dvar_SetValue(Dvars::r_drawCollision_brushDist, 800.0f);
-
- // clear global vectors
- g_mapBrushList.clear();
- g_mapMaterialList.clear();
- g_mapMaterialListDuplicates.clear();
- g_mapMaterialListSingle.clear();
- //g_dvarMaterialList_str.clear();
- Game::Globals::r_drawCollision_materialList_string.clear();
-
- // init/reset the selection box
- CM_ResetSelectionBox(&export_selectionBox);
-
- // create a dmaterial_t vector
- std::int32_t cmMaterialNum = Game::cm->numMaterials;
- std::vector currMapMaterials(cmMaterialNum);
-
- // assign clipMap material pointers
- for (int num = 0; num < cmMaterialNum; num++)
- {
- currMapMaterials[num] = &Game::cm->materials[num];
- }
-
- // create a cbrush_t vector
- std::vector currBrushList(Game::cm->numBrushes);
-
- // assign clipMap brush pointers
- for (int num = 0; num < Game::cm->numBrushes; num++)
- {
- currBrushList[num] = &Game::cm->brushes[num];
- currBrushList[num]->colorCounter = (short)colorCounter++;
- currBrushList[num]->cmBrushIndex = (short)num;
-
- colorCounter %= 8;
- }
-
- // set globals
- Game::Globals::dbgColl_initialized = true;
- g_mapNameCm = Game::cm->name;
- g_mapMaterialList = currMapMaterials;
- g_mapBrushList = currBrushList;
-
- // create a string vector of all material (contains duplicates)
- for (std::uint32_t num = 0; num < g_mapMaterialList.size(); num++)
- {
- std::string materialName = Utils::convertToString(g_mapMaterialList[num]->material, sizeof(g_mapMaterialList[num]->material));
- materialName.erase(std::remove(materialName.begin(), materialName.end(), '\0'), materialName.end());
-
- g_mapMaterialListDuplicates.push_back(materialName);
- }
-
- // create a string vector that without duplicate materials (used for dvar description)
- for (std::uint32_t num = 0; num < g_mapMaterialListDuplicates.size(); num++)
- {
- std::string materialName = g_mapMaterialListDuplicates[num];
-
- // if the vector is empty or the materialName does not exist within the array
- if (g_mapMaterialListSingle.empty() || std::find(g_mapMaterialListSingle.begin(), g_mapMaterialListSingle.end(), materialName) == g_mapMaterialListSingle.end())
- {
- g_mapMaterialListSingle.push_back(materialName);
- }
- }
-
- // create the dvar string
- auto singleMaterialCount = g_mapMaterialListSingle.size();
-
- for (std::uint32_t num = 0; num < singleMaterialCount; num++)
- {
- // do not print "empty" materials
- if (!Utils::StartsWith(g_mapMaterialListSingle[num], "*"))
- {
- /*g_dvarMaterialList_str*/
- Game::Globals::r_drawCollision_materialList_string += std::to_string(num) + ": " + g_mapMaterialListSingle[num] + "\n";
- }
- }
-
- Game::Com_PrintMessage(0, Utils::VA("|-> found %d materials\n", static_cast(singleMaterialCount)), 0);
-
- // Set material dvar back to 0, update description and max value
- Game::Dvar_SetValue(Dvars::r_drawCollision_material, 0);
- Dvars::r_drawCollision_material->domain.integer.max = singleMaterialCount - 1;
-
- //print the material list to the large console if the list has more then 20 materials
- if (singleMaterialCount > 20)
- {
- Dvars::r_drawCollision_material->description = "Too many materials to show here! Use ^1\"r_drawCollision_materialList\" ^7to print a list of all materials.\nOnly works with ^1\"r_drawCollision\" ^7enabled!";
- }
- else
- {
- Dvars::r_drawCollision_material->description = Game::Globals::r_drawCollision_materialList_string.c_str(); //g_dvarMaterialList_str.c_str();
- }
-
- Game::Com_PrintMessage(0, "|-> done\n", 0);
- }
- }
-
- // sort globBrushList depending on distance from brush to camera
- void CM_SortBrushListOnDistanceCamera(bool farToNear = true, bool useCurrentBrushesDrawnList = false, bool updateAlways = false)
- {
- if (!useCurrentBrushesDrawnList)
- {
- // sort brushes by distance from brush midpoint(xy) to camera origin :: only sort if the players origin has changed
- if (g_oldSortingOrigin[0] != Game::Globals::locPmove_playerOrigin[0] || g_oldSortingOrigin[1] != Game::Globals::locPmove_playerOrigin[1] || updateAlways)
- {
- // sort from far to near (used for rendering)
- if (farToNear)
- {
- std::sort(g_mapBrushList.begin(), g_mapBrushList.end(), [](Game::cbrush_t *brush1, Game::cbrush_t *brush2)
- {
- glm::vec3 b1_mid = (glm::vec3(brush1->mins[0], brush1->mins[1], 0.0f)
- + glm::vec3(brush1->maxs[0], brush1->maxs[1], 0.0f)) * 0.5f;
-
- glm::vec3 b2_mid = (glm::vec3(brush2->mins[0], brush2->mins[1], 0.0f)
- + glm::vec3(brush2->maxs[0], brush2->maxs[1], 0.0f)) * 0.5f;
-
- float dist1 = glm::distance(b1_mid, Game::Globals::locPmove_playerOrigin);
- float dist2 = glm::distance(b2_mid, Game::Globals::locPmove_playerOrigin);
-
- return dist1 > dist2;
- });
- }
-
- // sort from near to far (get closest brush)
- else
- {
- std::sort(g_mapBrushList.begin(), g_mapBrushList.end(), [](Game::cbrush_t *brush1, Game::cbrush_t *brush2)
- {
- glm::vec3 b1_mid = (glm::vec3(brush1->mins[0], brush1->mins[1], brush1->mins[2])
- + glm::vec3(brush1->maxs[0], brush1->maxs[1], brush1->maxs[2])) * 0.5f;
-
- glm::vec3 b2_mid = (glm::vec3(brush2->mins[0], brush2->mins[1], brush2->mins[2])
- + glm::vec3(brush2->maxs[0], brush2->maxs[1], brush2->maxs[2])) * 0.5f;
-
- float dist1 = glm::distance(b1_mid, Game::Globals::locPmove_playerOrigin);
- float dist2 = glm::distance(b2_mid, Game::Globals::locPmove_playerOrigin);
-
- return dist1 < dist2;
- });
- }
-
- //memcpy(prev_Origin, Game::Globals::locPmove_playerOrigin, sizeof(prev_Origin));
- g_oldSortingOrigin = Game::Globals::locPmove_playerOrigin;
- }
- }
-
- // use drawn brushes list if not empty
- else if(!g_mapBrushListForIndexFiltering.empty())
- {
- // sort brushes by distance from brush midpoint(xy) to camera origin :: only sort if the players origin has changed
- if (g_oldSortingOrigin[0] != Game::Globals::locPmove_playerOrigin[0] || g_oldSortingOrigin[1] != Game::Globals::locPmove_playerOrigin[1] || updateAlways)
- {
- // sort from far to near (used for rendering)
- if (farToNear)
- {
- std::sort(g_mapBrushListForIndexFiltering.begin(), g_mapBrushListForIndexFiltering.end(), [](Game::cbrush_t *brush1, Game::cbrush_t *brush2)
- {
- glm::vec3 b1_mid = (glm::vec3(brush1->mins[0], brush1->mins[1], 0.0f)
- + glm::vec3(brush1->maxs[0], brush1->maxs[1], 0.0f)) * 0.5f;
-
- glm::vec3 b2_mid = (glm::vec3(brush2->mins[0], brush2->mins[1], 0.0f)
- + glm::vec3(brush2->maxs[0], brush2->maxs[1], 0.0f)) * 0.5f;
-
- float dist1 = glm::distance(b1_mid, Game::Globals::locPmove_playerOrigin);
- float dist2 = glm::distance(b2_mid, Game::Globals::locPmove_playerOrigin);
-
- return dist1 > dist2;
- });
- }
-
- // sort from near to far (get closest brush)
- else
- {
- std::sort(g_mapBrushListForIndexFiltering.begin(), g_mapBrushListForIndexFiltering.end(), [](Game::cbrush_t *brush1, Game::cbrush_t *brush2)
- {
- glm::vec3 b1_mid = (glm::vec3(brush1->mins[0], brush1->mins[1], 0.0f)
- + glm::vec3(brush1->maxs[0], brush1->maxs[1], 0.0f)) * 0.5f;
-
- glm::vec3 b2_mid = (glm::vec3(brush2->mins[0], brush2->mins[1], 0.0f)
- + glm::vec3(brush2->maxs[0], brush2->maxs[1], 0.0f)) * 0.5f;
-
- float dist1 = glm::distance(b1_mid, Game::Globals::locPmove_playerOrigin);
- float dist2 = glm::distance(b2_mid, Game::Globals::locPmove_playerOrigin);
-
- return dist1 < dist2;
- });
- }
-
- g_oldSortingOrigin = Game::Globals::locPmove_playerOrigin;
- }
- }
- }
-
- // color brushes depending on their index within cm->brushes
- void CM_GetShowCollisionColor(float *colorFloat, const int colorCounter)
- {
- if (!colorFloat)
- {
- Game::Com_Error(0, Utils::VA("CM_GetShowCollisionColor L#%d :: colorFloat\n", __LINE__));
- return;
- }
-
- if (colorCounter & 1)
- { colorFloat[0] = 1.0f; }
- else
- { colorFloat[0] = 0.0f; }
-
- if (colorCounter & 2)
- { colorFloat[1] = 1.0f; }
- else
- { colorFloat[1] = 0.0f; }
-
- if (colorCounter & 4)
- { colorFloat[2] = 1.0f; }
- else
- { colorFloat[2] = 0.0f; }
-
- colorFloat[3] = Dvars::r_drawCollision_polyAlpha->current.value;
- }
-
- // check if 2 triangles share an edge
- bool PatchTriangle_SharesEdge(const Game::map_patchTris_t *pTri1, const Game::map_patchTris_t *pTri2)
- {
- int matchedPoints = 0;
-
- // for each edge of triangle 1
- for (auto edgeTri1 = 0; edgeTri1 < 3; edgeTri1++)
- {
- // for each edge of triangle 2
- for (auto edgeTri2 = 0; edgeTri2 < 3; edgeTri2++)
- {
- matchedPoints += Utils::vector::_VectorCompare(pTri1->coords[edgeTri1].xyz, pTri2->coords[edgeTri2].xyz);
- }
-
- // shouldnt happen
- if (matchedPoints > 2)
- {
- Game::Com_PrintMessage(0, "^1[MAP-EXPORT]: ^7 PatchTriangle_SharesEdge matchedPoints > 2", 0);
- }
-
- if (matchedPoints == 2)
- {
- return true;
- }
- }
-
- return false;
- }
-
- // if (k < 0.001f) = counter clock wise
- bool PatchTriangle_ClockwiseWinding(const float *pt0, const float *pt1, const float *pt2)
- {
- float k = (pt1[1] - pt0[1]) * (pt2[0] - pt1[0]) - (pt1[0] - pt0[0]) * (pt2[1] - pt1[1]);
- return k > 0.001f;
- }
-
- // not in map format order
- void PatchTriangle_FromIncides(Game::map_patchTris_t *pTris, const unsigned short *incides)
- {
- bool foundFirst = false, foundSecond = false, foundThird = false, secondIteration = false;
-
- // find the first match
- for (auto gfxVert = 0; gfxVert < static_cast(Game::_gfxWorld->vertexCount); gfxVert++)
- {
- // try to match our first clipmap vertex to a gfxworld vertex
- if (!foundFirst && Utils::vector::_VectorCompare(Game::cm->verts[incides[0]], Game::_gfxWorld->vd.vertices[gfxVert].xyz))
- {
- // found the first corrosponding gfx vertex :: copy gfx vertex data to our temp vertex
- memcpy(&pTris->coords[0], &Game::_gfxWorld->vd.vertices[gfxVert], sizeof(Game::GfxWorldVertex));
-
- // go back 6 verts in the gfxworld vertex array and start searching for our second point
- // :: assuming that our verts are close to each other
- if (gfxVert - 6 > 0)
- {
- gfxVert -= 6;
- }
-
- else
- {
- gfxVert = 0;
- }
-
- // do not match first vert again
- foundFirst = true;
- }
-
- // try to match our second clipmap vertex to a gfxworld vertex
- if (!foundSecond && Utils::vector::_VectorCompare(Game::cm->verts[incides[1]], Game::_gfxWorld->vd.vertices[gfxVert].xyz))
- {
- // found the second corrosponding gfx vertex :: copy gfx vertex data to our temp vertex
- memcpy(&pTris->coords[1], &Game::_gfxWorld->vd.vertices[gfxVert], sizeof(Game::GfxWorldVertex));
-
- // go back 6 verts in the gfxworld vertex array
- // :: assuming that our verts are close to each other
- if (gfxVert - 6 > 0)
- {
- gfxVert -= 6;
- }
-
- else
- {
- gfxVert = 0;
- }
-
- // do not match second vert again
- foundSecond = true;
- }
-
- // try to match our third clipmap vertex to a gfxworld vertex
- if (!foundThird && Utils::vector::_VectorCompare(Game::cm->verts[incides[2]], Game::_gfxWorld->vd.vertices[gfxVert].xyz))
- {
- // found the third corrosponding gfx vertex :: copy gfx vertex data to our temp vertex
- memcpy(&pTris->coords[2], &Game::_gfxWorld->vd.vertices[gfxVert], sizeof(Game::GfxWorldVertex));
-
- // go back 6 verts in the gfxworld vertex array
- // :: assuming that our verts are close to each other
- if (gfxVert - 6 > 0)
- {
- gfxVert -= 6;
- }
-
- else
- {
- gfxVert = 0;
- }
-
- // do not match third vert again
- foundThird = true;
- }
-
- if (foundFirst && foundSecond && foundThird)
- {
- break;
- }
- // if we did not match all 3 and looped all of the gfxworld vertices
- else if (gfxVert + 1 > (int)Game::_gfxWorld->vertexCount && !secondIteration)
- {
- // check the whole array once again (as we only gone back 6 verts each time we found a corrosponding vert)
- gfxVert = 0;
-
- // break out of the loop if we failed to match all 3 verts within the second iteration
- secondIteration = true;
- }
- }
-
- // if we failed to match all points, write clipmap vert data
- if (!foundFirst)
- {
- pTris->coords[0].xyz[0] = Game::cm->verts[incides[0]][0];
- pTris->coords[0].xyz[1] = Game::cm->verts[incides[0]][1];
- pTris->coords[0].xyz[2] = Game::cm->verts[incides[0]][2];
- }
-
- if (!foundSecond)
- {
- pTris->coords[1].xyz[0] = Game::cm->verts[incides[1]][0];
- pTris->coords[1].xyz[1] = Game::cm->verts[incides[1]][1];
- pTris->coords[1].xyz[2] = Game::cm->verts[incides[1]][2];
- }
-
- if (!foundThird)
- {
- pTris->coords[2].xyz[0] = Game::cm->verts[incides[2]][0];
- pTris->coords[2].xyz[1] = Game::cm->verts[incides[2]][1];
- pTris->coords[2].xyz[2] = Game::cm->verts[incides[2]][2];
- }
- }
-
- // Allocates a single patch triangle
- Game::map_patchTris_t *Alloc_PatchTriangle(void)
- {
- Game::map_patchTris_t *pTris;
- pTris = (Game::map_patchTris_t*)malloc(sizeof(*pTris));
-
- if (pTris)
- {
- memset(pTris, 0, sizeof(Game::map_patchTris_t));
- return pTris;
- }
-
- Game::Com_Error(0, "Alloc_PatchTriangle :: alloc failed!");
- return 0;
- }
-
- // Allocates a single patch quad
- Game::map_patchQuads_t *Alloc_PatchQuad(void)
- {
- Game::map_patchQuads_t *pQuad;
- pQuad = (Game::map_patchQuads_t*)malloc(sizeof(*pQuad));
-
- if (pQuad)
- {
- memset(pQuad, 0, sizeof(Game::map_patchQuads_t));
- return pQuad;
- }
-
- Game::Com_Error(0, "Alloc_PatchQuad :: alloc failed!");
- return 0;
- }
-
- // coord to check = xy; bounds = xywh
- bool IsCoordWithinBoundsXY(float *coordToCheck, float *bounds)
- {
- // if pt to right of left border
- if (coordToCheck[0] >= bounds[0]) // bounds x
- {
- // if pt to left of right border
- if (coordToCheck[0] <= (bounds[0] + bounds[2])) // bounds x + w
- {
- // if pt above bottom border
- if (coordToCheck[1] >= bounds[1]) // bounds y
- {
- // if pt below top border
- if (coordToCheck[1] <= (bounds[1] + bounds[3])) // bounds y + h
- {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- // quad in map format order (tiangles need to share an edge or it will fail)
- bool PatchQuad_SingleFromTris(Game::map_patchQuads_t *pQuad, const Game::map_patchTris_t *pTri1, const Game::map_patchTris_t *pTri2, const bool checkSkewness = false)
- {
- // temp quad points
- std::vectorcoord(3, 0.0f);
- std::vector> t_quadPts(4, coord);
-
- int uniquePtCount = 0;
-
- // for each coord of triangle 1
- for (auto crdTri1 = 0; crdTri1 < 3; crdTri1++)
- {
- int sharedPtCount = 0;
-
- // for each coord of triangle 2
- for (auto crdTri2 = 0; crdTri2 < 3; crdTri2++)
- {
- // check if the current coord from tri 1 is unique and not part of tri 2
- if (Utils::vector::_VectorCompare(pTri1->coords[crdTri1].xyz, pTri2->coords[crdTri2].xyz))
- {
- // found shared point
- sharedPtCount++;
- break;
- }
- }
-
- // check if we found the unique coord of triangle 1
- if (!sharedPtCount)
- {
- // add unique point that isnt on the shared edge
- t_quadPts[0][0] = pTri1->coords[crdTri1].xyz[0];
- t_quadPts[0][1] = pTri1->coords[crdTri1].xyz[1];
- t_quadPts[0][2] = pTri1->coords[crdTri1].xyz[2];
-
- uniquePtCount++;
- break;
- }
- }
-
- // if we found the unique point
- if (uniquePtCount)
- {
- // we should only have 1 point from the first tiangle in our list, otherwise, throw an error?
- if (uniquePtCount > 1)
- {
- Game::Com_PrintMessage(0, "^1[MAP-EXPORT]: ^7 PatchQuad_SingleFromTris t_quadPts.size() > 1", 0);
- return false;
- }
-
- for (auto secondTriCoords = 0; secondTriCoords < 3; secondTriCoords++)
- {
- // add point that isnt on the shared edge
- t_quadPts[secondTriCoords + 1][0] = pTri2->coords[secondTriCoords].xyz[0];
- t_quadPts[secondTriCoords + 1][1] = pTri2->coords[secondTriCoords].xyz[1];
- t_quadPts[secondTriCoords + 1][2] = pTri2->coords[secondTriCoords].xyz[2];
- }
-
- // unpack the normal .. might be needed for texture info later down the line
- Game::vec3_t normal;
- Utils::vector::_Vec3UnpackUnitVec(pTri2->coords[1].normal, normal);
-
- // sort x accending
- std::sort(t_quadPts.begin(), t_quadPts.end());
-
- // x was sorted, now sort by y
- std::sort(t_quadPts.begin(), t_quadPts.end());
-
- // always check clockwise ordering?
- float pt0[3], pt1[3], pt2[3], pt3[3];
-
- // why did i do that
- memcpy(&pt0, &t_quadPts[0][0], sizeof(float[3]));
- memcpy(&pt1, &t_quadPts[1][0], sizeof(float[3]));
- memcpy(&pt2, &t_quadPts[2][0], sizeof(float[3]));
- memcpy(&pt3, &t_quadPts[3][0], sizeof(float[3]));
-
- // cross :: triangle clockwise (k > 0.001f) | counter clockwise (k < 0.001f)
- float k = (pt1[1] - pt0[1]) * (pt2[0] - pt1[0]) - (pt1[0] - pt0[0]) * (pt2[1] - pt1[1]);
-
- if (k < 0.001f)
- {
- // triangle is counter clockwise, discard
- return false;
- }
-
- if (checkSkewness)
- {
- /*Game::vec3_t dirVec1, dirVec2;
- Utils::vector::_VectorZero(dirVec1); Utils::vector::_VectorZero(dirVec2);
-
- Utils::vector::_VectorSubtract(pt0, pt1, dirVec1);
- Utils::vector::_VectorSubtract(pt2, pt1, dirVec2);
-
- Utils::vector::_VectorNormalize(dirVec1);
- Utils::vector::_VectorNormalize(dirVec2);
-
- float dot = Utils::vector::_DotProduct(dirVec1, dirVec2);
- float angle = acosf(dot) * 180.0f / 3.141592f;*/
-
- //if(angle > 70.0f || angle < 10.0f )
- // return false;
-
- // triangle can be in clockwise order and still be using a point of a neighboring triangle
- // project a quad from the 3 coords >> skip the triangle if the 4th point is not within the bounds
-
- // horizontal check from left to right
- float ptToCheck[2] =
- { pt3[0], pt3[1] };
-
- float bounds[4] =
- {
- /*x*/ pt0[0],
- /*y*/ pt0[1],
- /*w*/ fabs(pt2[0] - pt0[0]) + export_quadEpsilon,
- /*h*/ fabs(pt1[1] - pt0[1]) + export_quadEpsilon
- };
-
- // discard point if not within bounds
- if (!IsCoordWithinBoundsXY(ptToCheck, bounds))
- {
- return false;
- }
-
- // vertical check from top to bottom
- float ptToCheck2[2] =
- { pt2[0], pt2[1] };
-
- float bounds2[4] =
- {
- /*x*/ pt0[0],
- /*y*/ pt0[1],
- /*w*/ fabs(pt3[0] - pt0[0]) + export_quadEpsilon,
- /*h*/ fabs(pt1[1] - pt0[1]) + export_quadEpsilon
- };
-
- // discard point if not within bounds
- if (!IsCoordWithinBoundsXY(ptToCheck2, bounds2))
- {
- return false;
- }
- }
-
- // build the quad from the 4 sorted coordinates
- for (auto quadCrd = 0; quadCrd < 4; quadCrd++)
- {
- for (auto quadCrdIdx = 0; quadCrdIdx < 3; quadCrdIdx++)
- {
- pQuad->coords[quadCrd][quadCrdIdx] = t_quadPts[quadCrd][quadCrdIdx];
- }
- }
-
- return true;
- }
-
- return false;
- }
-
- // handle filters
- void CM_BrushFilters(int &filter_BrushAmount, bool &filter_BrushIndex, bool &filter_BrushSorting, bool &filter_BrushSelection)
- {
- int cmTotalBrushAmount = static_cast(Game::cm->numBrushes);
-
- // brush bounding box filter
- if (Dvars::mapexport_selectionMode && Dvars::mapexport_selectionMode->current.integer != 0)
- {
- if(Dvars::r_drawCollision_brushAmount && Dvars::r_drawCollision_brushAmount->current.integer != cmTotalBrushAmount)
- Game::Dvar_SetValue(Dvars::r_drawCollision_brushAmount, cmTotalBrushAmount);
-
- if (Dvars::r_drawCollision_brushSorting && Dvars::r_drawCollision_brushSorting->current.integer != 0)
- Game::Dvar_SetValue(Dvars::r_drawCollision_brushSorting, 0);
-
- if (Dvars::r_drawCollision_brushIndexFilter && (std::string)Dvars::r_drawCollision_brushIndexFilter->current.string != "null")
- Game::Dvar_SetString("null", Dvars::r_drawCollision_brushIndexFilter);
-
- filter_BrushAmount = cmTotalBrushAmount;
- filter_BrushSelection = true;
-
- return;
- }
-
- // brush index filter & none of the above
- else if(Dvars::r_drawCollision_brushIndexFilter && (std::string)Dvars::r_drawCollision_brushIndexFilter->current.string != "null")
- {
- if (Dvars::r_drawCollision_brushAmount && Dvars::r_drawCollision_brushAmount->current.integer != cmTotalBrushAmount)
- Game::Dvar_SetValue(Dvars::r_drawCollision_brushAmount, cmTotalBrushAmount);
-
- if (Dvars::r_drawCollision_brushSorting && Dvars::r_drawCollision_brushSorting->current.integer != 0)
- Game::Dvar_SetValue(Dvars::r_drawCollision_brushSorting, 0);
-
- filter_BrushAmount = cmTotalBrushAmount;
- filter_BrushIndex = true;
-
- return;
- }
-
- // brush sorting & none of the above
- else if(Dvars::r_drawCollision_brushSorting && Dvars::r_drawCollision_brushSorting->current.integer != 0)
- {
- filter_BrushSorting = true;
- }
-
- // brush amount filter & none of the above
- if (Dvars::r_drawCollision_brushAmount && Dvars::r_drawCollision_brushAmount->current.integer != 0 && Dvars::r_drawCollision_brushAmount->current.integer <= cmTotalBrushAmount)
- {
- filter_BrushAmount = Dvars::r_drawCollision_brushAmount->current.integer;
- return;
- }
- else
- {
- if (Dvars::r_drawCollision_brushAmount && Dvars::r_drawCollision_brushAmount->current.integer != cmTotalBrushAmount)
- Game::Dvar_SetValue(Dvars::r_drawCollision_brushAmount, cmTotalBrushAmount);
-
- filter_BrushAmount = cmTotalBrushAmount;
- }
- }
-
- // handle brush dvar commands
- void CM_BrushCommands()
- {
- // cmd :: print material list to console
- if (Dvars::r_drawCollision_materialList && Dvars::r_drawCollision_materialList->current.enabled)
- {
- Game::Cmd_ExecuteSingleCommand(0, 0, "clear\n");
-
- // add spaces to the console so we can scroll the mini console
- Game::Com_PrintMessage(0, Utils::VA(" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n"), 0);
-
- // reset the dvar and print our material list
- Game::Dvar_SetValue(Dvars::r_drawCollision_materialList, false);
- Game::Com_PrintMessage(0, Utils::VA("%s\n", /*g_dvarMaterialList_str.c_str()*/ Game::Globals::r_drawCollision_materialList_string.c_str()), 0);
- }
- }
-
- // create a bounding box to select things that should be exported
- void CM_BrushSelectionBox(bool isActive)
- {
- if (isActive)
- {
- float selectionBoxColor[4] = { 0.0f, 1.0f, 0.0f, 0.6f };
- export_selectionBox.wasReset = false;
-
- if (!export_selectionBox.isBoxValid)
- {
- // we need atleast 2 valid points to define the selection box
- if (export_selectionBox.numPoints < 2)
- {
- if(export_selectionAdd)
- {
- export_selectionBox.points[export_selectionBox.numPoints] = Game::Globals::locPmove_playerOrigin;
- export_selectionBox.numPoints++;
-
- export_selectionAdd = false;
- }
-
- // use the players origin as a temporary second point till a second point was defined
- if (export_selectionBox.numPoints == 1)
- {
- export_selectionBox.points[1] = Game::Globals::locPmove_playerOrigin;
-
- CM_BoundsFromPoints(2, export_selectionBox.points, export_selectionBox.mins, export_selectionBox.maxs);
- glm::setFloat3(export_selectionBox.box.mins, export_selectionBox.mins);
- glm::setFloat3(export_selectionBox.box.maxs, export_selectionBox.maxs);
-
- CM_ShowSingleBrushCollision(&export_selectionBox.box, selectionBoxColor, 0, false);
- }
- }
- else
- {
- // calculate mins and maxs for our selection box once
- if (export_selectionBox.numPoints == 2 && !export_selectionBox.isBoxValid)
- {
- CM_BoundsFromPoints(export_selectionBox.numPoints, export_selectionBox.points, export_selectionBox.mins, export_selectionBox.maxs);
- glm::setFloat3(export_selectionBox.box.mins, export_selectionBox.mins);
- glm::setFloat3(export_selectionBox.box.maxs, export_selectionBox.maxs);
-
- export_selectionBox.isBoxValid = true;
- }
- }
- }
- else
- {
- CM_ShowSingleBrushCollision(&export_selectionBox.box, selectionBoxColor, 0, false);
- }
- }
- else
- {
- if (!export_selectionBox.wasReset)
- {
- CM_ResetSelectionBox(&export_selectionBox);
- }
- }
- }
-
- // main logic for brush drawing
- void CM_ShowBrushCollision(Game::GfxViewParms *viewParms, Game::cplane_s *frustumPlanes, int frustumPlaneCount)
- {
- int debugPrints, colorCounter = 0, lastDrawnBrushAmount = 0, filter_BrushAmount = 0;
- bool filter_BrushIndex = false, filter_BrushSorting = false, filter_ExportSelection = false;
-
- float colorFloat[4];
- Game::cbrush_t *brush;
-
- if (!frustumPlanes)
- {
- Game::Com_Error(0, Utils::VA("CM_ShowBrushCollision L#%d :: frustumPlanes \n", __LINE__));
- return;
- }
-
- // One time init per map when r_drawcollison was enabled
- CM_OnceOnInit();
-
- // Handle filter dvars
- CM_BrushFilters(filter_BrushAmount, filter_BrushIndex, filter_BrushSorting, filter_ExportSelection);
-
- // Handle commands
- CM_BrushCommands();
-
- // *
- // Map Export
-
- export_inProgress = false; // reset after exporting
- export_currentBrushIndex = 0; // brush index for .map file brushes
-
- // cmd :: export current map
- if (export_mapExportCmd)
- {
- // reset the the command bool
- export_mapExportCmd = false;
-
- // let our code know that we are about to export a map
- export_inProgress = true;
-
- // map file name
- std::string mapName = Game::cm->name;
- Utils::replaceAll(mapName, std::string("maps/mp/"), std::string(""));
- Utils::replaceAll(mapName, std::string(".d3dbsp"), std::string(".map"));
-
- // export to root/map_export
- std::string basePath = Game::Dvar_FindVar("fs_basepath")->current.string;
- basePath += "\\iw3xo\\map_export\\";
-
- std::string filePath = basePath + mapName;
-
- Game::Com_PrintMessage(0, "\n------------------------------------------------------\n", 0);
- export_time_exportStart = Utils::Clock_StartTimerPrint(Utils::VA("[MAP-EXPORT]: Starting to export %s to %s ...\n", Game::cm->name, filePath.c_str()));
-
- // create directory root/map_export if it doesnt exist
- // client is only able to export to a sub-directory of "menu_export"
-
- if (std::filesystem::create_directories(basePath))
- {
- Game::Com_PrintMessage(0, "|- Created directory \"root/iw3xo/map_export\"\n", 0);
- }
-
- // steam to .map file
- export_mapFile.open(filePath.c_str());
-
- // build entity list
- export_mapEntities = Utils::Entities(Game::cm->mapEnts->entityString);
- Game::Com_PrintMessage(0, "|- Writing header and world entity ...\n\n", 0);
-
- // write header
- export_mapFile << "iwmap 4\n"
- "\"000_Global\" flags expanded active\n"
- "\"000_Global/Brushes\" flags\n"
- "\"000_Global/SingleQuads\" flags\n"
- "\"000_Global/Triangles\" flags\n"
- "\"000_Global/Models\" flags\n"
- "\"The Map\" flags" << std::endl; // header
-
- // write worldspawn
- export_mapFile << "// entity 0\n"
- "{\n"
- + export_mapEntities.buildWorldspawnKeys();
-
- // Use debug collision methods to create our brushes ...
- export_time_brushGenerationStart = Utils::Clock_StartTimerPrint("[MAP-EXPORT]: Creating brushes ...\n");
- }
-
- // Handle box selection
- CM_BrushSelectionBox(filter_ExportSelection);
-
- // do not draw brushes when using the selection box
- if (!export_inProgress && filter_ExportSelection)
- {
- return;
- }
-
- // --------
-
- bool brushIndexVisible = Dvars::r_drawCollision_brushIndexVisible && Dvars::r_drawCollision_brushIndexVisible->current.enabled;
-
- if (filter_BrushSorting)
- {
- // sort brushes far to near
- if (Dvars::r_drawCollision_brushSorting->current.integer == 1)
- {
- CM_SortBrushListOnDistanceCamera();
- }
- // sort brushes near to far
- else
- {
- CM_SortBrushListOnDistanceCamera(false);
- }
-
- // disable sorting if sorted brushList is empty .. should not happen
- if (g_mapBrushList.empty())
- {
- Game::Dvar_SetValue(Dvars::r_drawCollision_brushSorting, 0);
- filter_BrushSorting = false;
- }
- }
-
- // clear drawn brushes vector
- g_mapBrushListForIndexFiltering.clear();
-
- // reset hud element
- Game::Globals::dbgColl_drawnPlanesAmountTemp = 0;
-
- int BRUSH_INDEX, BRUSH_COUNT;
- std::vector Integers;
-
- // brush index filtering
- if (filter_BrushIndex)
- {
- Utils::extractIntegerWords(Dvars::r_drawCollision_brushIndexFilter->current.string, Integers, true);
- BRUSH_COUNT = static_cast(Integers.size());
- }
-
- // sorted / unsorted
- else
- {
- BRUSH_COUNT = Game::cm->numBrushes;
- }
-
- for (BRUSH_INDEX = 0; BRUSH_INDEX < BRUSH_COUNT; ++BRUSH_INDEX)
- {
- int brushIndex;
-
- // break after we drew the amount of brushes we set (works best with brushSorting)
- if (lastDrawnBrushAmount >= filter_BrushAmount)
- {
- break;
- }
-
- // brush index filtering
- if (filter_BrushIndex)
- {
- brushIndex = Integers[BRUSH_INDEX];
-
- // check if the index is within bounds
- if (brushIndex < 0 || brushIndex >= Game::cm->numBrushes)
- {
- // find and remove the index
- Integers.erase(std::remove(Integers.begin(), Integers.end(), brushIndex), Integers.end());
-
- // vector to string
- std::string vecToString = "";
- for (int num = 0; num < (int)Integers.size(); num++)
- {
- vecToString += std::to_string(Integers[num]) + " ";
- }
-
- Game::Dvar_SetString(vecToString.c_str(), Dvars::r_drawCollision_brushIndexFilter);
- Game::Com_PrintMessage(0, Utils::VA("^1-> r_drawCollision_brushIndexFilter ^7:: found and removed index: %d \n", brushIndex), 0);
-
- return;
- }
- }
-
- // sorted / unsorted
- else
- {
- brushIndex = BRUSH_INDEX;
- }
-
- if (filter_BrushSorting)
- {
- brush = g_mapBrushList[brushIndex];
- }
- else
- {
- brush = &Game::cm->brushes[brushIndex];
- }
-
- // if brush is part of a submodel, translate brushmodel bounds by the submodel origin
- if (brush->isSubmodel)
- {
- Game::cbrush_t dupe = Game::cbrush_t();
- memcpy(&dupe, brush, sizeof(Game::cbrush_t));
-
- Utils::vector::_VectorAdd(g_mapBrushModelList[dupe.cmSubmodelIndex].cmSubmodelOrigin, dupe.mins, dupe.mins);
- Utils::vector::_VectorAdd(g_mapBrushModelList[dupe.cmSubmodelIndex].cmSubmodelOrigin, dupe.maxs, dupe.maxs);
-
- brush = &dupe;
- }
-
- // when not exporting a map
- if (!export_inProgress)
- {
- // always cull if not exporting
- if (!CM_BrushInView(brush, frustumPlanes, frustumPlaneCount)) {
- continue;
- }
-
- // disable material filter when using index filter
- if (!filter_BrushIndex)
- {
- // check if its a material we selected otherwise
- if (!CM_ValidBrushMaterialSelection(brush, Dvars::r_drawCollision_material->current.integer)) {
- continue;
- }
- }
- }
-
- // on exporting
- else
- {
- // exclude current brush if its not part of the selection box (when using selectionMode)
- if (filter_ExportSelection && !CM_IsBrushWithinSelectionBox(brush, &export_selectionBox)) {
- continue;
- }
-
- // skip material check if using index filtering or selectionMode
- if (!filter_BrushIndex && !filter_ExportSelection)
- {
- // check if its a material we selected otherwise
- if (!CM_ValidBrushMaterialSelection(brush, Dvars::r_drawCollision_material->current.integer)) {
- continue;
- }
- }
- }
-
- // always use the brush index within clipmap->brushes to define its color
- CM_GetShowCollisionColor(colorFloat, brush->colorCounter);
-
- // draw the current brush
- CM_ShowSingleBrushCollision(brush, colorFloat, brushIndex);
-
- if (brushIndexVisible)
- {
- g_mapBrushListForIndexFiltering.push_back(brush);
- }
-
- lastDrawnBrushAmount++;
- }
-
- // *
- // draw brush indices as 3D text (only when: unsorted brushes / index filtering)
-
- if (brushIndexVisible && !g_mapBrushListForIndexFiltering.empty())
- {
- // debug strings are normally handled within G_RunFrame (server running @ 20fps) so we have to skip drawing them -> ((renderfps / sv_fps)) times
-
- // if new loop allowed
- //if (!SvFramerateToRendertime_Counter)
- //{
- // SvFramerateToRendertime_CurrentDelta = (1000 / Game::Globals::pmlFrameTime) / Game::Dvar_FindVar("sv_fps")->current.integer;
- // SvFramerateToRendertime_CurrentDelta = static_cast(floor(SvFramerateToRendertime_CurrentDelta));
- //}
-
- //// increase counter
- //SvFramerateToRendertime_Counter++;
-
- //// if we reached the delta, we can draw strings again
- //if (SvFramerateToRendertime_Counter >= SvFramerateToRendertime_CurrentDelta)
- //{
- // // reset counter
- // SvFramerateToRendertime_Counter = 0;
-
- // sort brushes from near to far
- CM_SortBrushListOnDistanceCamera(false, true, true);
-
- // should not happen
- if (g_mapBrushListForIndexFiltering.empty())
- {
- Game::Dvar_SetValue(Dvars::r_drawCollision_brushIndexVisible, false);
- Game::Com_PrintMessage(0, Utils::VA("^1CM_ShowBrushCollision L#%d ^7:: GlobalBrushList was empty. Disabled r_drawCollision_brushIndexVisible! \n", __LINE__), 0);
- }
-
- // maximum amount of brush indices to draw
- int debugPrintsMax = 64;
-
- // only draw as many indices as the map has brushes
- if (static_cast(g_mapBrushListForIndexFiltering.size()) < debugPrintsMax)
- {
- debugPrintsMax = g_mapBrushListForIndexFiltering.size();
- }
-
- // draw strings near - far
- for (debugPrints = 0; debugPrints < debugPrintsMax; ++debugPrints)
- {
- // get distance-sorted brush
- brush = g_mapBrushListForIndexFiltering[debugPrints];
-
- // get midpoint of brush bounds (xyz)
- glm::vec3 printOrigin = CM_GetBrushMidpoint(brush, true);
-
- // draw original brush index in the middle of the collision poly
- CM_ShowBrushIndexNumbers(viewParms, brush->cmBrushIndex, printOrigin, debugPrints, debugPrintsMax);
- }
- //}
- }
- else
- {
- // if not drawing debug strings
- SvFramerateToRendertime_Counter = 0;
- }
-
- // *
- // Map Export
-
- if (export_inProgress)
- {
- const char* brush_str = Utils::VA("|- Building (%d) brushes took", export_currentBrushIndex);
- std::string timefmt = " took (%.4f)\n\n";
- timefmt = brush_str + timefmt;
-
- Utils::Clock_EndTimerPrintSeconds(export_time_brushGenerationStart, timefmt.c_str());
-
- // *
- // Create Tris
-
- std::vector singleTriangles;
- bool export_needsTriangles = false;
-
- // only generate triangles if exporting triangles/quads or both
- if (Dvars::mapexport_writeQuads->current.enabled || Dvars::mapexport_writeTriangles->current.enabled)
- {
- export_needsTriangles = true;
-
- auto export_time_createTrisStart = Utils::Clock_StartTimerPrint("[MAP-EXPORT]: Building triangles ...\n");
-
- for (auto triNum = 0; triNum < static_cast(Game::cm->triCount); triNum++)
- {
- // get incides that define the triangle
- unsigned short triIncides1[3] =
- {
- Game::cm->triIndices[triNum * 3 + 0],
- Game::cm->triIndices[triNum * 3 + 1],
- Game::cm->triIndices[triNum * 3 + 2]
- };
-
- Game::map_patchTris_t *pTris = Alloc_PatchTriangle();
- pTris->triIndex = triNum;
-
- PatchTriangle_FromIncides(pTris, triIncides1);
-
- if (filter_ExportSelection)
- {
- if (CM_IsTriangleWithinSelectionBox(pTris, &export_selectionBox))
- {
- singleTriangles.push_back(pTris);
- }
- }
- else
- {
- singleTriangles.push_back(pTris);
- }
- }
-
- Utils::Clock_EndTimerPrintSeconds(export_time_createTrisStart, "|- Building triangles took (%.4f) seconds!\n");
- Game::Com_PrintMessage(0, Utils::VA("|- Initial amount of triangles = (%d)\n\n", static_cast(singleTriangles.size())), 0);
- }
-
- // *
- // Merge Tris to Quads
-
- // fix me daddy
-
- if (Dvars::mapexport_writeQuads->current.enabled)
- {
- auto export_time_createQuadsStart = Utils::Clock_StartTimerPrint("[MAP-EXPORT]: Merging triangles to quads ...\n");
-
- // create a list of merged patch triangles (quads)
- std::vector singleQuads;
-
- // reset quad epsilon
- export_quadEpsilon = 0.0f;
-
- // count the amount of iterations till we are no longer able to merge any triangles to quads
- int quadFromTrisLoopCount = 0;
-
- // triangle index offset when we are no longer able to merge triangles (eg. if triangle 1-4 and 2-3 share an edge)
- int triNumOffset = 0;
-
- // amount of "failed" iterations with triangle index offset included
- int mergeIterationFailCount = 0;
-
- // defines how many triangles we look ahead from the current triangle to check for a shared edge
- int triFowardOffset = 1;
-
- // try to find as many "easy" quads as possible till we no longer merge triangles
- bool easyQuadsFirst = true;
-
- // build single quads from triangles; iterate for as long as we have enough triangles & if we actually decreased the amount of triangles after merging
- for (quadFromTrisLoopCount = 0; static_cast(singleTriangles.size()) > 1; quadFromTrisLoopCount++)
- {
- // get amount of triangles before merging
- int startTris = (int)singleTriangles.size();
-
- // merge triangles if they share an edge to create quads ... we will decrease the size of singlePatchTriangles, so do this for x amount of times ^
- for (auto triNum = 0 + triNumOffset; triNum < (int)singleTriangles.size(); triNum++)
- {
- // if we have atleast 2 triangles to work with
- if (triNum + triFowardOffset < (int)singleTriangles.size())
- {
- if (PatchTriangle_SharesEdge(singleTriangles[triNum], singleTriangles[triNum + triFowardOffset]))
- {
- Game::map_patchQuads_t *pQuad = Alloc_PatchQuad();
- pQuad->quadIndex = triNum; // for debugging :)
-
- // try to merge both triangles to create a quad
- if (PatchQuad_SingleFromTris(pQuad, singleTriangles[triNum], singleTriangles[triNum + triFowardOffset], easyQuadsFirst))
- {
- singleQuads.push_back(pQuad);
-
- // free triangles and erase them from singlePatchTriangles
- free(singleTriangles[triNum]);
- free(singleTriangles[triNum + triFowardOffset]);
-
- singleTriangles.erase(singleTriangles.begin() + triNum + triFowardOffset);
- singleTriangles.erase(singleTriangles.begin() + triNum);
-
- // decrease triNum by one
- if (triNum - 1 > 0)
- {
- triNum--;
- }
- }
- }
- }
- }
-
- // check if we merged any triangles in this iteration
- // stop trying to merge triangles if we didnt decrease the amount of triangles
- if (startTris - (int)singleTriangles.size() == 0)
- {
- // catch :: tri 1-4 / 2-3 => offset triangle index on first fail so we catch 2-3
- triNumOffset = 1;
-
- // still no merge? catch :: tri 1-3 / 2-4 => reset triNumOffset and add 1 to triFowardOffset
- if (mergeIterationFailCount == 1) {
- triNumOffset = 0;
- triFowardOffset = 2;
- }
-
- // reset triFowardOffset after we swizzeled the triangles once
- if (mergeIterationFailCount == 2) {
- triFowardOffset = 1;
- }
-
- // still unable to merge? .. (following doesnt seem to help)
- if (mergeIterationFailCount == 3) {
- triFowardOffset = 3;
- }
-
- if (mergeIterationFailCount == 4) {
- triFowardOffset = 4;
- }
-
- if (mergeIterationFailCount == 5) {
- triFowardOffset = 1;
- }
-
- // start increasing the bounding box that has to encapsule the 4th coordinate
- if (mergeIterationFailCount >= 6 && mergeIterationFailCount <= 13) {
- export_quadEpsilon += 5.0f;
- }
-
- // now try to merge skewed quads (might still be valid ones)
- if (mergeIterationFailCount == 14) {
- easyQuadsFirst = false;
- }
-
- mergeIterationFailCount++;
-
- if (mergeIterationFailCount > 16) {
- break;
- }
- }
- }
-
- Utils::Clock_EndTimerPrintSeconds(export_time_createQuadsStart, "|- Merging triangles to quads took (%.4f) seconds!\n");
- Game::Com_PrintMessage(0, Utils::VA("|- (%d) triangles couldn't be be merged.\n", (int)singleTriangles.size()), 0);
- Game::Com_PrintMessage(0, Utils::VA("|- (%d) quads after (%d) iterations.\n", (int)singleQuads.size(), quadFromTrisLoopCount), 0);
-
- // *
- // Write Quad Patches
-
- // coord x y z .... t .... (tc.x * 1024) (tc.y * 1024) (st.x * 1024 + 2) -(st.y * 1024 + 2)
- // v -108 -102 128 t -1728 5012.7217 -6.75 6.375
-
- for (auto quadNum = 0; quadNum < (int)singleQuads.size(); quadNum++)
- {
- // start patch
- export_mapFile << Utils::VA("// brush %d", export_currentBrushIndex) << std::endl;
-
- // global brush exporting index count
- export_currentBrushIndex++;
-
- export_mapFile << " {" << std::endl;
- export_mapFile << " mesh" << std::endl;
- export_mapFile << " {" << std::endl;
- export_mapFile << " layer \"000_Global/SingleQuads\"" << std::endl;
- export_mapFile << " toolFlags splitGeo;" << std::endl;
- export_mapFile << " caulk" << std::endl;
- export_mapFile << " lightmap_gray" << std::endl;
- export_mapFile << " 2 2 16 8" << std::endl;
-
- export_mapFile << " (" << std::endl;
- export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleQuads[quadNum]->coords[0][0], singleQuads[quadNum]->coords[0][1], singleQuads[quadNum]->coords[0][2]) << std::endl;
- export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleQuads[quadNum]->coords[1][0], singleQuads[quadNum]->coords[1][1], singleQuads[quadNum]->coords[1][2]) << std::endl;
- export_mapFile << " )" << std::endl;
- export_mapFile << " (" << std::endl;
- export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleQuads[quadNum]->coords[2][0], singleQuads[quadNum]->coords[2][1], singleQuads[quadNum]->coords[2][2]) << std::endl;
- export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleQuads[quadNum]->coords[3][0], singleQuads[quadNum]->coords[3][1], singleQuads[quadNum]->coords[3][2]) << std::endl;
- export_mapFile << " )" << std::endl;
- export_mapFile << " }" << std::endl;
- export_mapFile << " }" << std::endl;
- }
-
- // free quads
- for (auto quad = 0; quad < (int)singleQuads.size(); quad++)
- {
- free(singleQuads[quad]);
- }
-
- Game::Com_PrintMessage(0, "|- Wrote quads to layer \"000_Global/SingleQuads\"\n", 0);
- }
-
- // *
- // Write Single Triangles
-
- if (Dvars::mapexport_writeTriangles->current.enabled)
- {
- for (auto tri = 0; tri < (int)singleTriangles.size(); tri++)
- {
- // start patch
- export_mapFile << Utils::VA("// brush %d", export_currentBrushIndex) << std::endl;
-
- // global brush exporting index count
- export_currentBrushIndex++;
-
- export_mapFile << " {" << std::endl;
- export_mapFile << " mesh" << std::endl;
- export_mapFile << " {" << std::endl;
- export_mapFile << " layer \"000_Global/Triangles\"" << std::endl;
- export_mapFile << " contents nonColliding;" << std::endl;
- export_mapFile << " toolFlags splitGeo;" << std::endl;
- export_mapFile << " caulk" << std::endl;
- export_mapFile << " lightmap_gray" << std::endl;
- export_mapFile << " 2 2 16 8" << std::endl;
-
- export_mapFile << " (" << std::endl;
- export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleTriangles[tri]->coords[2].xyz[0], singleTriangles[tri]->coords[2].xyz[1], singleTriangles[tri]->coords[2].xyz[2]) << std::endl;
- export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleTriangles[tri]->coords[0].xyz[0], singleTriangles[tri]->coords[0].xyz[1], singleTriangles[tri]->coords[0].xyz[2]) << std::endl;
- export_mapFile << " )" << std::endl;
- export_mapFile << " (" << std::endl;
- export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleTriangles[tri]->coords[1].xyz[0], singleTriangles[tri]->coords[1].xyz[1], singleTriangles[tri]->coords[1].xyz[2]) << std::endl;
- export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleTriangles[tri]->coords[1].xyz[0], singleTriangles[tri]->coords[1].xyz[1], singleTriangles[tri]->coords[1].xyz[2]) << std::endl;
- export_mapFile << " )" << std::endl;
- export_mapFile << " }" << std::endl;
- export_mapFile << " }" << std::endl;
- }
-
- Game::Com_PrintMessage(0, "|- Wrote triangles to layer \"000_Global/Triangles\"\n\n", 0);
- }
-
- // free triangles
- if (export_needsTriangles)
- {
- for (auto tri = 0; tri < (int)singleTriangles.size(); tri++)
- {
- free(singleTriangles[tri]);
- }
- }
-
- // close the worldspawn entity with all its brushes
- export_mapFile << "}" << std::endl;
- Game::Com_PrintMessage(0, "|- Finished writing the world entity\n\n", 0);
-
- // *
- // Map Entities + Submodels/Brushmodels + Reflection Probes
-
- if (Dvars::mapexport_writeEntities->current.enabled)
- {
- Game::Com_PrintMessage(0, "[MAP-EXPORT]: Building entities ...\n", 0);
-
- // already exported the worldspawn entity, so delete it from the list
- export_mapEntities.deleteWorldspawn();
-
- // *
- // map entities and brushmodels
-
- if (filter_ExportSelection) // only exporting entities within the selection box
- {
- export_mapFile << export_mapEntities.buildSelection_FixBrushmodels(&export_selectionBox, g_mapBrushModelList);
- }
- else // build all other entities and fix up brushmodels
- {
- export_mapFile << export_mapEntities.buildAll_FixBrushmodels(g_mapBrushModelList);
- }
-
- // *
- // reflection probes (always skip the first one (not defined within the map file))
-
- int exportedProbes = 0;
-
- for (auto probe = 1; probe < (int)Game::_gfxWorld->reflectionProbeCount; probe++)
- {
- if (filter_ExportSelection &&
- !Utils::polylib::PointWithinBounds(glm::vec3(Game::_gfxWorld->reflectionProbes[probe].origin[0],
- Game::_gfxWorld->reflectionProbes[probe].origin[1],
- Game::_gfxWorld->reflectionProbes[probe].origin[2]),
- export_selectionBox.mins, export_selectionBox.maxs, 0.25f))
- {
- // skip probe if not in selection box
- continue;
- }
- else
- {
- export_mapFile << Utils::VA("// reflection probe %d", exportedProbes) << std::endl;
- export_mapFile << "{" << std::endl;
- export_mapFile << "\"angles\" \"0 0 0\"" << std::endl;
- export_mapFile << Utils::VA("\"origin\" \"%.1f %.1f %.1f\"", Game::_gfxWorld->reflectionProbes[probe].origin[0], Game::_gfxWorld->reflectionProbes[probe].origin[1], Game::_gfxWorld->reflectionProbes[probe].origin[2]) << std::endl;
- export_mapFile << "\"classname\" \"reflection_probe\"" << std::endl;
- export_mapFile << "}" << std::endl;
-
- exportedProbes++;
- }
- }
-
- Game::Com_PrintMessage(0, Utils::VA("|- (%d) reflection probes.\n", exportedProbes), 0);
- }
-
- // *
- // Static Models
-
- if (Dvars::mapexport_writeModels->current.enabled)
- {
- auto map_mapModelsStart = Utils::Clock_StartTimer();
- int exportedSModels = 0;
-
- for (auto sModel = 0u; sModel < Game::_gfxWorld->dpvs.smodelCount; sModel++)
- {
- // only export static models within the selection box
- if (filter_ExportSelection &&
- !Utils::polylib::PointWithinBounds(glm::vec3(Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.origin[0],
- Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.origin[1],
- Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.origin[2]),
- export_selectionBox.mins, export_selectionBox.maxs, 0.25f))
- {
- // skip static model if not in selection box
- continue;
- }
- else
- {
- // copy model rotation axis
- Game::vec4_t matrix[4];
-
- // X
- matrix[0][0] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[0][0];
- matrix[0][1] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[0][1];
- matrix[0][2] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[0][2];
-
- // Y
- matrix[1][0] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[1][0];
- matrix[1][1] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[1][1];
- matrix[1][2] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[1][2];
-
- // Z
- matrix[2][0] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[2][0];
- matrix[2][1] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[2][1];
- matrix[2][2] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[2][2];
-
- // calculate model angles
- Game::vec3_t angles;
- Utils::vector::_ToEulerAnglesDegrees(matrix, angles);
-
- export_mapFile << Utils::VA("// static model %d\n{", exportedSModels) << std::endl;
- export_mapFile << "layer \"000_Global/Models\"" << std::endl;
- export_mapFile << Utils::VA("\"modelscale\" \"%.1f\"", Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.scale) << std::endl;
- export_mapFile << Utils::VA("\"origin\" \"%.1f %.1f %.1f\"", Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.origin[0], Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.origin[1], Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.origin[2]) << std::endl;
- export_mapFile << Utils::VA("\"angles\" \"%.1f %.1f %.1f\"", angles[1], angles[2], angles[0]) << std::endl;
- export_mapFile << Utils::VA("\"model\" \"%s\"", Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].model->name) << std::endl;
- export_mapFile << "\"classname\" \"misc_model\"" << std::endl;
- export_mapFile << "}" << std::endl;
- }
- }
-
- Utils::Clock_EndTimerPrintSeconds(map_mapModelsStart, "|- Building static models took (%.4f) seconds!\n\n");
- }
-
- // *
- // Map Export End
-
- export_mapFile.close();
- export_currentBrushIndex = 0;
-
- Utils::Clock_EndTimerPrintSeconds(export_time_exportStart, ">> DONE! Map export took (%.4f) seconds!\n");
- Game::Com_PrintMessage(0, "------------------------------------------------------\n\n", 0);
- }
-
- // ------------
-
- // update hud elements after we drew all brushes / planes
- if (Game::Globals::dbgColl_drawnPlanesAmount != Game::Globals::dbgColl_drawnPlanesAmountTemp) {
- Game::Globals::dbgColl_drawnPlanesAmount = Game::Globals::dbgColl_drawnPlanesAmountTemp;
- }
-
- if (Game::Globals::dbgColl_drawnBrushAmount != lastDrawnBrushAmount) {
- Game::Globals::dbgColl_drawnBrushAmount = lastDrawnBrushAmount;
- }
- }
-
- // *
- // _Debug::RB_AdditionalDebug :: entry for collision drawing (create view frustum)
- void RB_DrawCollision::RB_ShowCollision(Game::GfxViewParms *viewParms)
- {
- char frustumType;
- float drawDistance;
-
- Game::cplane_s frustumPlanes[6];
-
- if (!viewParms)
- {
- Game::Com_Error(0, Utils::VA("RB_ShowCollision L#%d :: viewparams\n", __LINE__));
- return;
- }
-
- // enable drawcollision on mapexport_selectionMode
- if (Dvars::mapexport_selectionMode && Dvars::mapexport_selectionMode->current.integer != 0 && Dvars::r_drawCollision && Dvars::r_drawCollision->current.integer == 0)
- {
- Game::Cmd_ExecuteSingleCommand(0, 0, "r_drawcollision 3\n");
- }
-
- if (Dvars::r_drawCollision && Dvars::r_drawCollision->current.integer > 0)
- {
- // turn off brush sorting if displaying brushIndices
- if (Dvars::r_drawCollision_brushIndexVisible->current.enabled && Dvars::r_drawCollision_brushSorting->current.integer != 0)
- {
- Game::Dvar_SetValue(Dvars::r_drawCollision_brushSorting, 0);
- Game::Com_PrintMessage(0, Utils::VA("^1-> r_drawCollision_brushSorting ^7:: disabled due to r_drawCollision_brushIndexVisible \n"), 0);
- }
-
- // Disable r_drawCollision when using r_fullbright
- if (Game::Dvar_FindVar("r_fullbright")->current.enabled)
- {
- Game::Com_PrintMessage(0, Utils::VA("^1-> r_drawCollision ^7:: disabled due to r_fullbright \n"), 0);
- Game::Dvar_SetValue(Dvars::r_drawCollision, 0);
-
- return;
- }
-
- // Disable r_drawCollision when using r_debugShader
- if (Game::Dvar_FindVar("r_debugShader")->current.integer > 0)
- {
- Game::Com_PrintMessage(0, Utils::VA("^1-> r_drawCollision ^7:: disabled due to r_debugShader \n"), 0);
- Game::Dvar_SetValue(Dvars::r_drawCollision, 0);
-
- return;
- }
-
- // increment our frame counter if we use brush flickering mode
- if (Dvars::r_drawCollision_flickerBrushes->current.enabled)
- {
- collisionFlickerCounter++;
- }
-
- // *
- // Build culling frustum
-
- BuildFrustumPlanes(viewParms, frustumPlanes);
-
- frustumPlanes[5].normal[0] = -frustumPlanes[4].normal[0];
- frustumPlanes[5].normal[1] = -frustumPlanes[4].normal[1];
- frustumPlanes[5].normal[2] = -frustumPlanes[4].normal[2];
-
- // max draw distance when brushDist is set to 0
- drawDistance = Dvars::r_drawCollision_brushDist->current.value;
-
- if (drawDistance == 0)
- {
- drawDistance = 999999.0f;
- }
-
- frustumPlanes[5].dist = -frustumPlanes[4].dist - drawDistance;
-
- if (frustumPlanes[5].normal[0] == 1.0f)
- {
- frustumType = 0;
- }
-
- else
- {
- if (frustumPlanes[5].normal[1] == 1.0)
- {
- frustumType = 1;
- }
-
- else
- {
- frustumType = 2;
- if (frustumPlanes[5].normal[2] != 1.0)
- {
- frustumType = 3;
- }
- }
- }
-
- frustumPlanes[5].type = frustumType;
- SetPlaneSignbits(&frustumPlanes[5]);
-
- // *
- // Main brush drawing logic
-
- CM_ShowBrushCollision(viewParms, frustumPlanes, 6);
-
- // draw added polys / lines
- if (Game::tess->indexCount)
- {
- Game::RB_EndTessSurface();
- }
- }
- }
-
- RB_DrawCollision::RB_DrawCollision()
- {
- // -----
- // Dvars
-
- Dvars::r_drawCollision = Game::Dvar_RegisterInt(
- /* name */ "r_drawCollision",
- /* desc */ "Enable collision drawing.\n0: Off\n1: Outlines\n2: Polys\n3: Both",
- /* default */ 0,
- /* minVal */ 0,
- /* maxVal */ 3,
- /* flags */ Game::dvar_flags::none);
-
- Dvars::r_drawCollision_brushAmount = Game::Dvar_RegisterInt(
- /* name */ "r_drawCollision_brushAmount",
- /* desc */ "Draw x amount of brushes, starting at brush index 0 and will limit itself to the total amount of brushes within the clipMap.\n0: disables this filter.",
- /* default */ 0,
- /* minVal */ 0,
- /* maxVal */ INT_MAX / 2 - 1,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::r_drawCollision_brushDist = Game::Dvar_RegisterFloat(
- /* name */ "r_drawCollision_brushDist",
- /* desc */ "Max distance to draw collision.\n0: disables this filter.\nWill reset itself on map load.",
- /* default */ 800.0f,
- /* minVal */ 0.0f,
- /* maxVal */ 10000.0f,
- /* flags */ Game::dvar_flags::none);
-
- // r_drawCollision_brushIndexFilter @ (CM_BuildMaterialListForMapOnce)
-
- Dvars::r_drawCollision_brushIndexVisible = Game::Dvar_RegisterBool(
- /* name */ "r_drawCollision_brushIndexVisible",
- /* desc */ "Draw brush index numbers for use with ^1r_drawCollision_brushFilter^7. Unstable fps will cause flickering.",
- /* default */ false,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::r_drawCollision_brushSorting = Game::Dvar_RegisterInt(
- /* name */ "r_drawCollision_brushSorting",
- /* desc */ "Sort brushes based on distance from the camera.\n0: Off\n1: Far to near\n2: Near to far",
- /* default */ 0,
- /* minVal */ 0,
- /* maxVal */ 2,
- /* flags */ Game::dvar_flags::saved);
-
-#if DEBUG
- Dvars::r_drawCollision_brushDebug = Game::Dvar_RegisterBool(
- /* name */ "r_drawCollision_brushDebug",
- /* desc */ "Draw debug prints. Only enabled when r_drawCollision_brushAmount = 1",
- /* default */ false,
- /* flags */ Game::dvar_flags::none);
-#endif
-
- Dvars::r_drawCollision_lineWidth = Game::Dvar_RegisterInt(
- /* name */ "r_drawCollision_lineWidth",
- /* desc */ "Width of debug lines. (Only if using r_drawCollision 1)",
- /* default */ 1,
- /* minVal */ 0,
- /* maxVal */ 12,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::r_drawCollision_lineColor = Game::Dvar_RegisterVec4(
- /* name */ "r_drawCollision_lineColor",
- /* desc */ "Color of debug lines.",
- /* x */ 0.2f,
- /* y */ 1.0f,
- /* z */ 0.2f,
- /* w */ 1.0f,
- /* minValue */ 0.0f,
- /* maxValue */ 1.0f,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::r_drawCollision_polyAlpha = Game::Dvar_RegisterFloat(
- /* name */ "r_drawCollision_polyAlpha",
- /* desc */ "Transparency of polygons.",
- /* default */ 0.8f,
- /* minVal */ 0.0f,
- /* maxVal */ 1.0f,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::r_drawCollision_polyDepth = Game::Dvar_RegisterBool(
- /* name */ "r_drawCollision_polyDepth",
- /* desc */ "Enable depth test for polygons.",
- /* default */ true,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::r_drawCollision_polyFace = Game::Dvar_RegisterBool(
- /* name */ "r_drawCollision_polyFace",
- /* desc */ "0: Back(only draw the front facing side)\n1: None(draw both sides)",
- /* default */ false,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::r_drawCollision_polyLit = Game::Dvar_RegisterBool(
- /* name */ "r_drawCollision_polyLit",
- /* desc */ "Enable fake lighting for polygons.",
- /* default */ false,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::r_drawCollision_material = Game::Dvar_RegisterInt(
- /* name */ "r_drawCollision_material",
- /* desc */ "Will be populated when a map is loaded and r_drawCollision is enabled.",
- /* default */ 0,
- /* minVal */ 0,
- /* maxVal */ 1,
- /* flags */ Game::dvar_flags::none);
-
- static std::vector r_drawCollisionMatFilter =
- {
- "none",
- "clip",
- "mantle",
- "trigger",
- "all",
- "all-no-tools",
- "all-no-tools-clip",
- };
-
- Dvars::r_drawCollision_materialInclude = Game::Dvar_RegisterEnum(
- /* name */ "r_drawCollision_materialInclude",
- /* desc */ "Filter by type. \nExample: will show \"clip_player\" / \"clip_metal\" etc. \n draws everything but portal, trigger, hint, mantle, sky and volumes materials.\n draws everything but clip and previous mentioned materials.",
- /* default */ 0,
- /* enumSize */ r_drawCollisionMatFilter.size(),
- /* enumData */ r_drawCollisionMatFilter.data(),
- /* flags */ Game::dvar_flags::none);
-
- Dvars::r_drawCollision_materialList = Game::Dvar_RegisterBool(
- /* name */ "r_drawCollision_materialList",
- /* desc */ "CMD: Prints a list of materials in use by the current map (to the console).\nOnly works when a map is loaded and r_drawCollision is enabled!",
- /* default */ false,
- /* flags */ Game::dvar_flags::none);
-
- Dvars::r_drawCollision_flickerBrushes = Game::Dvar_RegisterBool(
- /* name */ "r_drawCollision_flickerBrushes",
- /* desc */ "[VIS] Enable debug collision flicker mode. Remove me.",
- /* default */ false,
- /* flags */ Game::dvar_flags::none);
-
- Dvars::r_drawCollision_flickerOnTime = Game::Dvar_RegisterInt(
- /* name */ "r_drawCollision_flickerOnTime",
- /* desc */ "[VIS] Amount of frames to show brush collision.",
- /* default */ 60,
- /* minVal */ 0,
- /* maxVal */ INT_MAX / 2 - 1,
- /* flags */ Game::dvar_flags::none);
-
- Dvars::r_drawCollision_flickerOffTime = Game::Dvar_RegisterInt(
- /* name */ "r_drawCollision_flickerOffTime",
- /* desc */ "[VIS] Amount of frames to hide brush collision.",
- /* default */ 500,
- /* minVal */ 0,
- /* maxVal */ INT_MAX/2 - 1,
- /* flags */ Game::dvar_flags::none);
-
- Dvars::r_drawCollision_hud = Game::Dvar_RegisterBool(
- /* name */ "r_drawCollision_hud",
- /* desc */ "Display debug hud.",
- /* default */ true,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::r_drawCollision_hud_position = Game::Dvar_RegisterVec2(
- /* name */ "r_drawCollision_hud_position",
- /* desc */ "hud position offset",
- /* def x */ 10.0f,
- /* def y */ 250.0f,
- /* minVal */ -1000.0f,
- /* maxVal */ 1000.0f,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::r_drawCollision_hud_fontScale = Game::Dvar_RegisterFloat(
- /* name */ "r_drawCollision_hud_fontScale",
- /* desc */ "font scale",
- /* default */ 0.75f,
- /* minVal */ 0.0f,
- /* maxVal */ 100.0f,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::r_drawCollision_hud_fontStyle = Game::Dvar_RegisterInt(
- /* name */ "r_drawCollision_hud_fontStyle",
- /* desc */ "font style",
- /* default */ 1,
- /* minVal */ 0,
- /* maxVal */ 8,
- /* flags */ Game::dvar_flags::none);
-
- Dvars::r_drawCollision_hud_fontColor = Game::Dvar_RegisterVec4(
- /* name */ "r_drawCollision_hud_fontColor",
- /* desc */ "font color",
- /* x */ 1.0f,
- /* y */ 0.55f,
- /* z */ 0.4f,
- /* w */ 1.0f,
- /* minValue */ 0.0f,
- /* maxValue */ 1.0f,
- /* flags */ Game::dvar_flags::saved);
-
- // ---------------
-
- Dvars::mapexport_brushEpsilon1 = Game::Dvar_RegisterFloat(
- /* name */ "mapexport_brushEpsilon1",
- /* desc */ "brushside epsilon 1 (debug)",
- /* default */ 0.4f,
- /* minVal */ 0.0f,
- /* maxVal */ 1.0f,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::mapexport_brushEpsilon2 = Game::Dvar_RegisterFloat(
- /* name */ "mapexport_brushEpsilon2",
- /* desc */ "brushside epsilon 2 (debug)",
- /* default */ 1.0f,
- /* minVal */ 0.0f,
- /* maxVal */ 1.0f,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::mapexport_brushMinSize = Game::Dvar_RegisterFloat(
- /* name */ "mapexport_brushMinSize",
- /* desc */ "only export brushes (with more then 6 sides) if their diagonal length is greater then ",
- /* default */ 64.0f,
- /* minVal */ 0.0f,
- /* maxVal */ 1000.0f,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::mapexport_brush5Sides = Game::Dvar_RegisterBool(
- /* name */ "mapexport_brush5Sides",
- /* desc */ "enable exp. export of brushes with only 5 sides",
- /* default */ true,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::mapexport_selectionMode = Game::Dvar_RegisterInt(
- /* name */ "mapexport_selectionMode",
- /* desc */ "Only export selected things. Use \"mapexport_selectionAdd\" and \"mapexport_selectionClear\" \n0: Off\n1: Bounding box (needs 2 defined points)",
- /* default */ 0,
- /* minVal */ 0,
- /* maxVal */ 1,
- /* flags */ Game::dvar_flags::none);
-
- Dvars::mapexport_writeTriangles = Game::Dvar_RegisterBool(
- /* name */ "mapexport_writeTriangles",
- /* desc */ "[MAP-EXPORT-OPTION] Export leftover unmerged triangles if enabled.",
- /* default */ true,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::mapexport_writeQuads = Game::Dvar_RegisterBool(
- /* name */ "mapexport_writeQuads",
- /* desc */ "[MAP-EXPORT-OPTION] Export resulting quads after triangle merging if enabled.",
- /* default */ true,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::mapexport_writeEntities = Game::Dvar_RegisterBool(
- /* name */ "mapexport_writeEntities",
- /* desc */ "[MAP-EXPORT-OPTION] Export map entities if enabled (no brushmodel support).",
- /* default */ true,
- /* flags */ Game::dvar_flags::saved);
-
- Dvars::mapexport_writeModels = Game::Dvar_RegisterBool(
- /* name */ "mapexport_writeModels",
- /* desc */ "[MAP-EXPORT-OPTION] Export all static models if enabled.",
- /* default */ true,
- /* flags */ Game::dvar_flags::saved);
-
- // --------
- // Commands
-
- Command::Add("mapexport", [](Command::Params)
- {
- if (Dvars::r_drawCollision && Dvars::r_drawCollision->current.integer == 0)
- {
- Game::Com_PrintMessage(0, "Please enable \"r_drawCollision\" and re-run your command.\n", 0);
- return;
- }
-
- export_mapExportCmd = true;
-
- Game::Cmd_ExecuteSingleCommand(0, 0, "pm_hud_enable 0\n");
- Game::Cmd_ExecuteSingleCommand(0, 0, "say \"Export Done!\"\n");
- });
-
- Command::Add("mapexport_selectionAdd", [](Command::Params)
- {
- if (Dvars::mapexport_selectionMode && Dvars::mapexport_selectionMode->current.integer != 0)
- {
- export_selectionAdd = true;
- }
- else
- {
- Game::Com_PrintMessage(0, "mapexport_selectionMode needs to be enabled for this to work.\n", 0);
- }
- });
-
- Command::Add("mapexport_selectionClear", [](Command::Params)
- {
- if (Dvars::mapexport_selectionMode && Dvars::mapexport_selectionMode->current.integer != 0)
- {
- CM_ResetSelectionBox(&export_selectionBox);
- }
- else
- {
- Game::Com_PrintMessage(0, "mapexport_selectionMode needs to be enabled for this to work.\n", 0);
- }
- });
- }
-
- RB_DrawCollision::~RB_DrawCollision()
- { }
+#include "STDInclude.hpp"
+
+#define CM_CONTENTS_SOLID 0x1
+#define CM_CONTENTS_CLIPSHOT 0x2000 // weapon clip
+#define CM_CONTENTS_DETAIL 0x8000000 // detail
+
+#define CM_MAX_BRUSHPOINTS_FROM_INTERSECTIONS 128
+
+std::chrono::time_point export_time_exportStart;
+std::chrono::time_point export_time_brushGenerationStart;
+
+bool export_mapExportCmd = false;
+bool export_inProgress = false;
+bool export_selectionAdd;
+int export_currentBrushIndex = 0;
+float export_quadEpsilon = 0.0f;
+std::ofstream export_mapFile;
+std::ofstream export_mapFile_addon;
+Utils::Entities export_mapEntities;
+Game::boundingBox_t export_selectionBox;
+
+int collisionFlickerCounter;
+const char *g_mapNameCm = "";
+
+// last origin we sorted brushes at
+glm::vec3 g_oldSortingOrigin = glm::vec3(0.0f);
+
+// winding pool
+char windingPool[12292];
+
+// dynamic dvar strings
+//std::string g_dvarMaterialList_str;
+
+// material lists
+std::vector g_mapMaterialList;
+std::vector g_mapMaterialListDuplicates;
+std::vector g_mapMaterialListSingle;
+
+// brush lists
+std::vector g_mapBrushList;
+std::vector g_mapBrushListForIndexFiltering;
+std::vector g_mapBrushModelList;
+
+// used to calculate the delta between sv_fps and client fps
+int SvFramerateToRendertime_Counter = 0;
+int SvFramerateToRendertime_CurrentDelta = 0;
+
+// view-frustum planes
+const Game::vec4_t frustumSidePlanes[5] =
+{
+ {-1.0f, 0.0f, 0.0f, 1.0f}, // left?
+ { 1.0f, 0.0f, 0.0f, 1.0f}, // right?
+ { 0.0f, -1.0f, 0.0f, 1.0f}, // bottom?
+ { 0.0f, 1.0f, 0.0f, 1.0f}, // top?
+ { 0.0f, 0.0f, 1.0f, 1.0f} // back?
+};
+
+namespace Components
+{
+ // scale strings with distance :: when looking directly at them
+ float StringScaleDot(Game::GfxViewParms *viewParms, const glm::vec3 origin)
+ {
+ glm::vec3 delta = glm::vec3(origin.x, origin.y, origin.z) - glm::vec3(viewParms->origin[0], viewParms->origin[1], viewParms->origin[2]);
+
+ float scale = Utils::vector::_GLM_VectorNormalize(delta);
+ float dot = glm::dot(delta, glm::vec3(viewParms->axis[0][0], viewParms->axis[0][1], viewParms->axis[0][2]));
+
+ scale = (dot - 0.995f) * scale;
+
+ if (scale < 1.0f)
+ {
+ scale = 1.0f;
+ }
+
+ return scale;
+ }
+
+ // show brush index numbers (used for r_drawCollision_brushIndexFilter) ---- adding strings will cause flicker or duplicates because renderthread runs at diff. framerate?
+ void CM_ShowBrushIndexNumbers(Game::GfxViewParms *viewParms, int brushIndex, const glm::vec3 origin, int sortedBrushIndex, int maxStringAmount)
+ {
+ float scale;
+ glm::vec4 color;
+
+ // we need the viewAxis to scale strings, calling Set3D populates our struct
+ Game::R_Set3D();
+
+ // scale strings with distance when looking directly at them
+ scale = StringScaleDot(viewParms, origin);
+ color = glm::vec4(1.0f);
+
+ const char *brushIndexStr = Utils::VA("Index: %d", brushIndex);
+
+ //char color_packed[4];
+ //float color_test[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
+ //Game::R_ConvertColorToBytes(color_test, color_packed);
+
+ //float pixel_x[3], pixel_y[3], org[3];
+ //Utils::vector::_VectorScale(viewParms->axis[1], -scale, pixel_x);
+ //Utils::vector::_VectorScale(viewParms->axis[2], -scale, pixel_y);
+
+ ////auto font = Game::R_RegisterFont(FONT_NORMAL, sizeof(FONT_NORMAL));
+ //Game::Font_s* font = (Game::Font_s*) *(DWORD*)(0xD070C74);
+
+ //glm::setFloat3(org, origin);
+
+ //Game::RB_DrawTextInSpace(pixel_x, pixel_y, brushIndexStr, font, org, color_packed); // no idea why thats not working
+
+ _Debug::AddDebugStringClient(origin, color, scale, brushIndexStr, 2); // not propper but will do for now
+ }
+
+ // some kind of wizardry
+ void SetPlaneSignbits(Game::cplane_s *out)
+ {
+ int j; char bits;
+
+ // for fast box on planeside test
+ bits = 0;
+ for (j = 0; j < 3; j++)
+ {
+ if (out->normal[j] < 0.0f)
+ {
+ bits |= 1 << j;
+ }
+ }
+
+ out->signbits = bits;
+ }
+
+ // build view frustum
+ void R_SetDpvsPlaneSides(Game::DpvsPlane *plane)
+ {
+ plane->side[0] = char(plane->coeffs[0]) <= 0 ? 0 : 12;
+ plane->side[1] = char(plane->coeffs[1]) <= 0 ? 4 : 16;
+ plane->side[2] = char(plane->coeffs[2]) <= 0 ? 8 : 20;
+ }
+
+ // build view frustum
+ void R_FrustumClipPlanes(const Game::GfxMatrix *viewProjMtx, const float(*sidePlanes)[4], const int sidePlaneCount, Game::DpvsPlane *frustumPlanes)
+ {
+ int term, planeIndex;
+ float length;
+
+ Game::DpvsPlane *plane;
+
+ for (planeIndex = 0; planeIndex < sidePlaneCount; ++planeIndex)
+ {
+ for (term = 0; term < 4; ++term)
+ {
+ frustumPlanes[planeIndex].coeffs[term] = ((((*sidePlanes)[4 * planeIndex + 0] * viewProjMtx->m[term][0])
+ + ((*sidePlanes)[4 * planeIndex + 1] * viewProjMtx->m[term][1]))
+ + ((*sidePlanes)[4 * planeIndex + 2] * viewProjMtx->m[term][2]))
+ + ((*sidePlanes)[4 * planeIndex + 3] * viewProjMtx->m[term][3]);
+ }
+
+ length = Utils::vector::_VectorLength(frustumPlanes[planeIndex].coeffs);
+
+ if (length <= 0.0f)
+ {
+ Game::Com_PrintMessage(0, Utils::VA("^1R_FrustumClipPlanes L#%d ^7:: length <= 0 \n", __LINE__), 0);
+ }
+
+ plane = &frustumPlanes[planeIndex];
+
+ plane->coeffs[0] = (1.0f / length) * plane->coeffs[0];
+ plane->coeffs[1] = (1.0f / length) * plane->coeffs[1];
+ plane->coeffs[2] = (1.0f / length) * plane->coeffs[2];
+ plane->coeffs[3] = (1.0f / length) * plane->coeffs[3];
+
+ R_SetDpvsPlaneSides(&frustumPlanes[planeIndex]);
+ }
+ }
+
+ // build view frustum
+ void BuildFrustumPlanes(Game::GfxViewParms *viewParms, Game::cplane_s *frustumPlanes)
+ {
+ char frustumType;
+ unsigned int planeIndex;
+
+ Game::DpvsPlane dpvsFrustumPlanes[5];
+ Game::DpvsPlane *dpvsPlane;
+ Game::cplane_s *cplane;
+
+ if (!viewParms)
+ {
+ Game::Com_Error(0, Utils::VA("BuildFrustumPlanes L#%d :: viewparams \n", __LINE__));
+ return;
+ }
+
+ if (!frustumPlanes)
+ {
+ Game::Com_Error(0, Utils::VA("BuildFrustumPlanes L#%d :: frustumPlanes \n", __LINE__));
+ return;
+ }
+
+ R_FrustumClipPlanes(&viewParms->viewProjectionMatrix, frustumSidePlanes, 5, dpvsFrustumPlanes);
+
+ for (planeIndex = 0; planeIndex < 5; ++planeIndex)
+ {
+ cplane = &frustumPlanes[planeIndex];
+ dpvsPlane = &dpvsFrustumPlanes[planeIndex];
+
+ cplane->normal[0] = dpvsPlane->coeffs[0];
+ cplane->normal[1] = dpvsPlane->coeffs[1];
+ cplane->normal[2] = dpvsPlane->coeffs[2];
+ cplane->dist = dpvsPlane->coeffs[3];
+
+ frustumPlanes[planeIndex].dist = frustumPlanes[planeIndex].dist * -1.0f;
+
+ if (frustumPlanes[planeIndex].normal[0] == 1.0f)
+ {
+ frustumType = 0;
+ }
+ else
+ {
+ if (frustumPlanes[planeIndex].normal[1] == 1.0f)
+ {
+ frustumType = 1;
+ }
+ else
+ {
+ if (frustumPlanes[planeIndex].normal[2] == 1.0f)
+ {
+ frustumType = 2;
+ }
+ else
+ {
+ frustumType = 3;
+ }
+ }
+ }
+
+ frustumPlanes[planeIndex].type = frustumType;
+ SetPlaneSignbits(&frustumPlanes[planeIndex]);
+ }
+ }
+
+ // create plane for intersection
+ void CM_GetPlaneVec4Form(const Game::cbrushside_t* sides, const Game::axialPlane_t* axialPlanes, const int index, float* expandedPlane)
+ {
+ if (index >= 6)
+ {
+ if (!sides )
+ {
+ Game::Com_Error(0, Utils::VA("CM_GetPlaneVec4Form L#%d :: sides \n", __LINE__));
+ return;
+ }
+
+ expandedPlane[0] = sides[index - 6].plane->normal[0];
+ expandedPlane[1] = sides[index - 6].plane->normal[1];
+ expandedPlane[2] = sides[index - 6].plane->normal[2];
+ expandedPlane[3] = sides[index - 6].plane->dist;
+ }
+ else
+ {
+ /*expandedPlane[0] = axialPlanes[index][0];
+ expandedPlane[1] = axialPlanes[index][1];
+ expandedPlane[2] = axialPlanes[index][2];
+ expandedPlane[3] = axialPlanes[index][3];*/
+
+ glm::setFloat3(expandedPlane, axialPlanes[index].plane);
+ expandedPlane[3] = axialPlanes[index].dist;
+ }
+ }
+
+ // intersect 3 planes
+ int IntersectPlanes(const float *plane0, const float *plane1, const float *plane2, float *xyz)
+ {
+ float determinant;
+
+ determinant = (((plane1[1] * plane2[2]) - (plane2[1] * plane1[2])) * plane0[0])
+ + (((plane2[1] * plane0[2]) - (plane0[1] * plane2[2])) * plane1[0])
+ + (((plane0[1] * plane1[2]) - (plane1[1] * plane0[2])) * plane2[0]);
+
+ if (fabs(determinant) < 0.001f)
+ {
+ return 0;
+ }
+
+ determinant = 1.0f / determinant;
+
+ xyz[0] = ((((plane1[1] * plane2[2]) - (plane2[1] * plane1[2])) * plane0[3])
+ + (((plane2[1] * plane0[2]) - (plane0[1] * plane2[2])) * plane1[3])
+ + (((plane0[1] * plane1[2]) - (plane1[1] * plane0[2])) * plane2[3])) * determinant;
+
+ xyz[1] = ((((plane1[2] * plane2[0]) - (plane2[2] * plane1[0])) * plane0[3])
+ + (((plane2[2] * plane0[0]) - (plane0[2] * plane2[0])) * plane1[3])
+ + (((plane0[2] * plane1[0]) - (plane1[2] * plane0[0])) * plane2[3])) * determinant;
+
+ xyz[2] = ((((plane1[0] * plane2[1]) - (plane2[0] * plane1[1])) * plane0[3])
+ + (((plane2[0] * plane0[1]) - (plane0[0] * plane2[1])) * plane1[3])
+ + (((plane0[0] * plane1[1]) - (plane1[0] * plane0[1])) * plane2[3])) * determinant;
+
+ return 1;
+ }
+
+ // cod4map
+ bool IsOnGrid(const float *snapped, const float *xyz)
+ {
+ return xyz[0] == snapped[0] && xyz[1] == snapped[1] && xyz[2] == snapped[2];
+ }
+
+ // snap points to grid. might prod. some issues
+ void SnapPointToIntersectingPlanes(const float *plane0, const float *plane1, const float *plane2, float *xyz, float snapGrid, const float snapEpsilon)
+ {
+ int axis, planeIndex;
+ float rounded, delta, baseError, maxBaseError, snapError, maxSnapError, snapped[3], currentPlane[4];
+
+ snapGrid = 1.0f / snapGrid;
+
+ // cod4map :
+ for (axis = 0; axis < 3; ++axis)
+ {
+ rounded = round(xyz[axis] * snapGrid) / snapGrid;
+ delta = fabs(rounded - xyz[axis]);
+
+ if (snapEpsilon <= delta)
+ {
+ snapped[axis] = xyz[axis];
+ }
+ else
+ {
+ snapped[axis] = rounded;
+ }
+ }
+
+ if (!IsOnGrid(snapped, xyz))
+ {
+ maxSnapError = 0.0f;
+ maxBaseError = snapEpsilon;
+
+ for (planeIndex = 0; planeIndex < 3; ++planeIndex)
+ {
+ if (planeIndex == 0)
+ memcpy(¤tPlane, plane0, sizeof(currentPlane));
+
+ else if (planeIndex == 1)
+ memcpy(¤tPlane, plane1, sizeof(currentPlane));
+
+ else if (planeIndex == 2)
+ memcpy(¤tPlane, plane2, sizeof(currentPlane));
+
+
+ snapError = log((currentPlane[0] * snapped[0] + currentPlane[1] * snapped[1] + currentPlane[2] * snapped[2]) - currentPlane[3]);
+ if (snapError > maxSnapError)
+ {
+ maxSnapError = snapError;
+ }
+
+ baseError = log((currentPlane[0] * xyz[0] + currentPlane[1] * xyz[1] + currentPlane[2] * xyz[2]) - currentPlane[3]);
+ if (baseError > maxBaseError)
+ {
+ maxBaseError = baseError;
+ }
+ }
+
+ if (maxBaseError > maxSnapError)
+ {
+ xyz[0] = snapped[0];
+ xyz[1] = snapped[1];
+ xyz[2] = snapped[2];
+ }
+ }
+ }
+
+ // add valid vertices from 3 plane intersections
+ int CM_AddSimpleBrushPoint(const Game::cbrush_t* brush, const Game::axialPlane_t* axialPlanes, const __int16* sideIndices, const float* xyz, int ptCount, Game::ShowCollisionBrushPt* brushPts)
+ {
+ unsigned int sideIndex;
+ Game::cplane_s *plane;
+
+ if (!brush)
+ {
+ Game::Com_Error(0, Utils::VA("CM_AddSimpleBrushPoint L#%d :: brush \n", __LINE__));
+ return 0;
+ }
+
+ if (!brushPts)
+ {
+ Game::Com_Error(0, Utils::VA("CM_AddSimpleBrushPoint L#%d :: brushPts \n", __LINE__));
+ return 0;
+ }
+
+ for (sideIndex = 0; sideIndex < 6; ++sideIndex)
+ {
+ if(( (axialPlanes[sideIndex].plane.x * xyz[0] + axialPlanes[sideIndex].plane.y * xyz[1] + axialPlanes[sideIndex].plane.z * xyz[2])
+ - axialPlanes[sideIndex].dist) > 0.1f)
+ {
+ return ptCount;
+ }
+ }
+
+ for (sideIndex = 0; sideIndex < brush->numsides; ++sideIndex)
+ {
+ plane = brush->sides[sideIndex].plane;
+
+ if ( plane != brush->sides[sideIndices[0] - 6].plane
+ && plane != brush->sides[sideIndices[1] - 6].plane
+ && plane != brush->sides[sideIndices[2] - 6].plane
+ && ((((plane->normal[0] * xyz[0]) + (plane->normal[1] * xyz[1])) + (plane->normal[2] * xyz[2])) - plane->dist) > 0.1f)
+ {
+ return ptCount;
+ }
+ }
+
+ if (ptCount > CM_MAX_BRUSHPOINTS_FROM_INTERSECTIONS - 2) // T5: 1024 ...
+ {
+ Game::Com_PrintMessage(0, Utils::VA("CM_AddSimpleBrushPoint :: More than %i points from plane intersections on %i-sided brush\n", ptCount, brush->numsides), 0);
+ return ptCount;
+ }
+
+ brushPts[ptCount].xyz[0] = xyz[0];
+ brushPts[ptCount].xyz[1] = xyz[1];
+ brushPts[ptCount].xyz[2] = xyz[2];
+
+ brushPts[ptCount].sideIndex[0] = sideIndices[0];
+ brushPts[ptCount].sideIndex[1] = sideIndices[1];
+ brushPts[ptCount].sideIndex[2] = sideIndices[2];
+
+ return ptCount + 1;
+ }
+
+ // intersect 3 planes (for all planes) to reconstruct vertices
+ int CM_ForEachBrushPlaneIntersection(const Game::cbrush_t* brush, const Game::axialPlane_t* axialPlanes, Game::ShowCollisionBrushPt* brushPts)
+ {
+ int ptCount = 0, sideCount;
+ __int16 sideIndex[3];
+
+ float xyz[3];
+ float expandedPlane[3][4];
+
+ Game::cbrushside_t *sides;
+
+ if (!brush)
+ {
+ Game::Com_Error(0, Utils::VA("CM_ForEachBrushPlaneIntersection L#%d :: brush \n", __LINE__));
+ return 0;
+ }
+
+ if (!brushPts)
+ {
+ Game::Com_Error(0, Utils::VA("CM_ForEachBrushPlaneIntersection L#%d :: brushPts \n", __LINE__));
+ return 0;
+ }
+
+ sideCount = brush->numsides + 6;
+ sides = brush->sides;
+
+ // first loop should only get the axial planes till brush->numsides < 3
+ for (sideIndex[0] = 0; sideIndex[0] < sideCount - 2; ++sideIndex[0])
+ {
+ // sideIndex[0]-[5] are axial planes only; move the current plane into expandedPlane[0]
+ CM_GetPlaneVec4Form(sides, axialPlanes, sideIndex[0], (float *)expandedPlane);
+
+ // get a plane 1 plane ahead of our first plane
+ for (sideIndex[1] = sideIndex[0] + 1; sideIndex[1] < sideCount - 1; ++sideIndex[1])
+ {
+ // check if we're using an axial plane and 2 different planes
+ if (sideIndex[0] < 6 || sideIndex[1] < 6 || sides[sideIndex[0] - 6].plane != sides[sideIndex[1] - 6].plane)
+ {
+ // move the current plane into expandedPlane[1]
+ CM_GetPlaneVec4Form(sides, axialPlanes, sideIndex[1], expandedPlane[1]);
+
+ // get a plane 1 plane ahead of our second plane
+ for (sideIndex[2] = sideIndex[1] + 1; sideIndex[2] < sideCount - 0; ++sideIndex[2])
+ {
+ // check if we use axial planes or atleast 3 different sides
+ if (( sideIndex[0] < 6 || sideIndex[2] < 6 || sides[sideIndex[0] - 6].plane != sides[sideIndex[2] - 6].plane)
+ && (sideIndex[1] < 6 || sideIndex[2] < 6 || sides[sideIndex[1] - 6].plane != sides[sideIndex[2] - 6].plane))
+ {
+ // move the current plane into expandedPlane[2]
+ CM_GetPlaneVec4Form(sides, axialPlanes, sideIndex[2], expandedPlane[2]);
+
+ // intersect the 3 planes
+ if (IntersectPlanes(expandedPlane[0], expandedPlane[1], expandedPlane[2], xyz))
+ {
+ // snap our verts in xyz onto the grid
+ SnapPointToIntersectingPlanes(expandedPlane[0], expandedPlane[1], expandedPlane[2], xyz, 0.25f, 0.0099999998f);
+
+ // if the planes intersected, put verts into brushPts and increase our pointCount
+ ptCount = CM_AddSimpleBrushPoint(brush, axialPlanes, sideIndex, xyz, ptCount, brushPts);
+
+ if (ptCount >= CM_MAX_BRUSHPOINTS_FROM_INTERSECTIONS - 1)
+ {
+ return 0;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return ptCount;
+ }
+
+ // check for float precision errors and check if a point lies within an epsilon
+ bool VecNCompareCustomEpsilon(const glm::vec3* xyzList, const int xyzIndex, const float* v1, const float epsilon, const int coordCount)
+ {
+ for (auto i = 0; i < coordCount; ++i)
+ {
+ if (((xyzList[xyzIndex][i] - v1[i]) * (xyzList[xyzIndex][i] - v1[i])) > (epsilon * epsilon))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // check if point in list exists
+ int CM_PointInList(const float* point, const glm::vec3* xyzList, const int xyzCount)
+ {
+ int xyzIndex;
+
+ for (xyzIndex = 0; xyzIndex < xyzCount; ++xyzIndex)
+ {
+ if (VecNCompareCustomEpsilon(xyzList, xyzIndex, point, 0.1f, 3)) // larger epsilon decreases quality
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+ // create a list of vertex points
+ int CM_GetXyzList(const int sideIndex, const Game::ShowCollisionBrushPt* pts, const int ptCount, glm::vec3* xyzList, const int xyzLimit)
+ {
+ int ptIndex, xyzCount = 0;
+
+ if (!pts)
+ {
+ Game::Com_Error(0, Utils::VA("CM_GetXyzList L#%d :: pts\n", __LINE__));
+ return 0;
+ }
+
+ for (ptIndex = 0; ptIndex < ptCount; ++ptIndex)
+ {
+ if ((sideIndex == pts[ptIndex].sideIndex[0] || sideIndex == pts[ptIndex].sideIndex[1] || sideIndex == pts[ptIndex].sideIndex[2])
+ && !CM_PointInList(pts[ptIndex].xyz, xyzList, xyzCount))
+ {
+ if (xyzCount == xyzLimit)
+ {
+ Game::Com_PrintMessage(0, Utils::VA("^1CM_GetXyzList L#%d ^7:: Winding point limit (%i) exceeded on brush face \n", __LINE__, xyzLimit), 0);
+ return 0;
+ }
+#if DEBUG
+ if (Dvars::r_drawCollision_brushDebug->current.enabled)
+ {
+ Game::Com_PrintMessage(0, Utils::VA("^4CM_GetXyzList L#%d ^7:: Adding X:^2 %.2lf ^7Y:^2 %.2lf ^7Z:^2 %.2lf ^7 \n", __LINE__, xyzList[xyzCount][0], xyzList[xyzCount][1], xyzList[xyzCount][2]), 0);
+ }
+#endif
+ xyzList[xyzCount] = glm::toVec3(pts[ptIndex].xyz);
+ ++xyzCount;
+ }
+ }
+
+#if DEBUG
+ if (Dvars::r_drawCollision_brushDebug->current.enabled)
+ {
+ Game::Com_PrintMessage(0, Utils::VA("^1CM_GetXyzList L#%d ^7:: Total XYZCOUNT: %d \n", __LINE__, xyzCount), 0);
+ }
+#endif
+
+ return xyzCount;
+ }
+
+ // pick the major axis
+ void CM_PickProjectionAxes(const float *normal, int *i, int *j)
+ {
+ int k = 0;
+
+ if (fabs(normal[1]) > fabs(normal[0]))
+ {
+ k = 1;
+ }
+
+ if (fabs(normal[2]) > fabs(normal[k]))
+ {
+ k = 2;
+ }
+
+ *i = ~k & 1;
+ *j = ~k & 2;
+ }
+
+ // cross product
+ float CM_SignedAreaForPointsProjected(const float* pt0, const glm::vec3& pt1, const float* pt2, const int i, const int j)
+ {
+ return (pt2[j] - pt1[j]) * pt0[i] + (pt0[j] - pt2[j]) * pt1[i] + (pt1[j] - pt0[j]) * pt2[i];
+ }
+
+ // add a point that intersected behind another plane that still is within the bounding box?
+ void CM_AddColinearExteriorPointToWindingProjected(Game::winding_t* w, const glm::vec3& pt, int i, int j, int index0, int index1)
+ {
+ float delta; int axis;
+
+#if DEBUG
+ if (w->p[index0][i] == w->p[index1][i] && w->p[index0][j] == w->p[index1][j])
+ {
+ Game::Com_PrintMessage(0, Utils::VA("^1CM_AddColinearExteriorPointToWindingProjected L#%d ^7:: w->p[%d][%d] %.2lf == w->p[%d][%d] %.2lf && w->p[%d][%d] %.2lf == w->p[%d][%d] %.2lf \n",
+ __LINE__, index0, i, w->p[index0][i], index1, i, w->p[index1][i], index0, j, w->p[index0][j], index1, j, w->p[index1][j]), 0);
+ }
+#endif
+
+ if (fabs(float(uint32_t(w->p[index1][i] - w->p[index0][i]))) < fabs(float(uint32_t(w->p[index1][j] - w->p[index0][j]))))
+ {
+ axis = j;
+ delta = w->p[index1][j] - w->p[index0][j];
+ }
+ else
+ {
+ axis = i;
+ delta = w->p[index1][i] - w->p[index0][i];
+ }
+
+ if (delta <= 0.0f)
+ {
+
+#if DEBUG
+ if (w->p[index0][axis] <= w->p[index1][axis])
+ {
+ Game::Com_PrintMessage(0, Utils::VA("^1CM_AddColinearExteriorPointToWindingProjected L#%d ^7:: w->p[%d][%d] %.2lf <= w->p[%d][%d] %.2lf \n",
+ __LINE__, index0, axis, w->p[index0][axis], index1, axis, w->p[index1][axis]), 0);
+ }
+#endif
+
+ if (pt[axis] <= w->p[index0][axis])
+ {
+ if (w->p[index1][axis] > pt[axis])
+ {
+ glm::setFloat3(w->p[index1], pt);
+ }
+ }
+ else
+ {
+ glm::setFloat3(w->p[index0], pt);
+ }
+ }
+
+ else
+ {
+
+#if DEBUG
+ if (w->p[index1][axis] <= w->p[index0][axis])
+ {
+ Game::Com_PrintMessage(0, Utils::VA("^1CM_AddColinearExteriorPointToWindingProjected L#%d ^7:: w->p[%d][%d] %.2lf < w->p[%d][%d] %.2lf \n",
+ __LINE__, index1, axis, w->p[index1][axis], index0, axis, w->p[index0][axis]), 0);
+ }
+#endif
+
+ if (w->p[index0][axis] <= pt[axis])
+ {
+ if (pt[axis] > w->p[index1][axis])
+ {
+ glm::setFloat3(w->p[index1], pt);
+ }
+ }
+ else
+ {
+ glm::setFloat3(w->p[index0], pt);
+ }
+ }
+ }
+
+ // Source :: PolyFromPlane || Q3 :: RemoveColinearPoints ?
+ void CM_AddExteriorPointToWindingProjected(Game::winding_t* w, const glm::vec3& pt, int i, int j)
+ {
+ int index, indexPrev, bestIndex = -1;
+ float signedArea, bestSignedArea = FLT_MAX;
+
+ indexPrev = w->numpoints - 1;
+
+ for (index = 0; index < w->numpoints; ++index)
+ {
+ signedArea = CM_SignedAreaForPointsProjected(w->p[indexPrev], pt, w->p[index], i, j);
+
+ if (bestSignedArea > signedArea)
+ {
+ bestSignedArea = signedArea;
+ bestIndex = index;
+ }
+
+ indexPrev = index;
+ }
+
+#if DEBUG
+ if (bestIndex < 0)
+ {
+ Game::Com_PrintMessage(0, Utils::VA("^1CM_AddExteriorPointToWindingProjected L#%d ^7:: bestIndex < 0 \n", __LINE__), 0);
+ }
+#endif
+
+ if (bestSignedArea < -0.001f)
+ {
+ memmove((char *)w->p[bestIndex + 1], (char *)w->p[bestIndex], 12 * (w->numpoints - bestIndex));
+
+ glm::setFloat3(w->p[bestIndex], pt);
+ ++w->numpoints;
+ }
+
+ else if (bestSignedArea <= 0.001)
+ {
+ CM_AddColinearExteriorPointToWindingProjected(w, pt, i, j, (bestIndex + w->numpoints - 1) % w->numpoints, bestIndex);
+ }
+ }
+
+ // create a triangle to check the winding order?
+ float CM_RepresentativeTriangleFromWinding(const Game::winding_t *w, const float *normal, int *i0, int *i1, int *i2)
+ {
+ int i, k; signed int j;
+ float testAgainst, areaBest = 0.0f;
+ float va[3], vb[3], vc[3];
+
+ *i0 = 0; *i1 = 1; *i2 = 2;
+
+ for (k = 2; k < w->numpoints; ++k)
+ {
+ for (j = 1; j < k; ++j)
+ {
+ vb[0] = w->p[k][0] - w->p[j][0];
+ vb[1] = w->p[k][1] - w->p[j][1];
+ vb[2] = w->p[k][2] - w->p[j][2];
+ for (i = 0; i < j; ++i)
+ {
+ va[0] = w->p[i][0] - w->p[j][0];
+ va[1] = w->p[i][1] - w->p[j][1];
+ va[2] = w->p[i][2] - w->p[j][2];
+
+ Utils::vector::_Vec3Cross(va, vb, vc);
+ testAgainst = fabs(((vc[0] * normal[0]) + (vc[1] * normal[1])) + (vc[2] * normal[2]));
+
+ if(testAgainst > 0.0f)
+ {
+ areaBest = testAgainst;
+ *i0 = i;
+ *i1 = j;
+ *i2 = k;
+ }
+ }
+ }
+ }
+
+ return areaBest;
+ }
+
+ // create a plane from points
+ bool PlaneFromPoints(float *plane, const float *v0, const float *v1, const float *v2)
+ {
+ float v2_v0[3], v1_v0[3];
+ float length, lengthSq;
+
+ v1_v0[0] = v1[0] - v0[0];
+ v1_v0[1] = v1[1] - v0[1];
+ v1_v0[2] = v1[2] - v0[2];
+ v2_v0[0] = v2[0] - v0[0];
+ v2_v0[1] = v2[1] - v0[1];
+ v2_v0[2] = v2[2] - v0[2];
+
+ Utils::vector::_Vec3Cross(v2_v0, v1_v0, plane);
+
+ lengthSq = ((plane[0] * plane[0]) + (plane[1] * plane[1])) + (plane[2] * plane[2]);
+
+ if (lengthSq >= 2.0f)
+ goto WEGOOD;
+
+ if (lengthSq == 0.0f)
+ return false;
+
+ if ((((((v2_v0[0] * v2_v0[0]) + (v2_v0[1] * v2_v0[1])) + (v2_v0[2] * v2_v0[2]))
+ * (((v1_v0[0] * v1_v0[0]) + (v1_v0[1] * v1_v0[1])) + (v1_v0[2] * v1_v0[2]))) * 0.0000010000001) >= lengthSq)
+ {
+ v1_v0[0] = v2[0] - v1[0];
+ v1_v0[1] = v2[1] - v1[1];
+ v1_v0[2] = v2[2] - v1[2];
+ v2_v0[0] = v0[0] - v1[0];
+ v2_v0[1] = v0[1] - v1[1];
+ v2_v0[2] = v0[2] - v1[2];
+
+ Utils::vector::_Vec3Cross(v2_v0, v1_v0, plane);
+
+ if ((((((v2_v0[0] * v2_v0[0]) + (v2_v0[1] * v2_v0[1])) + (v2_v0[2] * v2_v0[2]))
+ * (((v1_v0[0] * v1_v0[0]) + (v1_v0[1] * v1_v0[1])) + (v1_v0[2] * v1_v0[2]))) * 0.0000010000001) >= lengthSq)
+ {
+ return false;
+ }
+ }
+
+ WEGOOD:
+ length = sqrt(lengthSq);
+ plane[0] = plane[0] / length;
+ plane[1] = plane[1] / length;
+ plane[2] = plane[2] / length;
+ plane[3] = ((v0[0] * plane[0]) + (v0[1] * plane[1])) + (v0[2] * plane[2]);
+
+ return true;
+ }
+
+ // reverse clock-wise windings
+ void CM_ReverseWinding(Game::winding_t *w)
+ {
+ int i; float windingSave[3];
+
+ for (i = 0; i < w->numpoints / 2; ++i)
+ {
+ windingSave[0] = w->p[i][0];
+ windingSave[1] = w->p[i][1];
+ windingSave[2] = w->p[i][2];
+
+ w->p[i][0] = w->p[w->numpoints - 1 - i][0];
+ w->p[i][1] = w->p[w->numpoints - 1 - i][1];
+ w->p[i][2] = w->p[w->numpoints - 1 - i][2];
+
+ w->p[w->numpoints - 1 - i][0] = windingSave[0];
+ w->p[w->numpoints - 1 - i][1] = windingSave[1];
+ w->p[w->numpoints - 1 - i][2] = windingSave[2];
+ }
+ }
+
+ // Map Export - CM_BuildBrushWindingForSide
+ bool CM_BuildBrushWindingForSideMapExport(Game::winding_t *winding, const float *planeNormal, const int sideIndex, Game::ShowCollisionBrushPt *pts, int ptCount, Game::map_brushSide_t *bSide)
+ {
+ int xyzCount, i, i0, i1, i2, j, k;
+ Game::vec4_t plane;
+
+ Utils::vector::_VectorZero4(plane);
+
+ if (!winding)
+ {
+ Game::Com_Error(0, Utils::VA("CM_BuildBrushWindingForSide L#%d :: winding \n", __LINE__));
+ return false;
+ }
+
+ if (!planeNormal)
+ {
+ Game::Com_Error(0, Utils::VA("CM_BuildBrushWindingForSide L#%d :: planeNormal \n", __LINE__));
+ return false;
+ }
+
+ if (!pts)
+ {
+ Game::Com_Error(0, Utils::VA("CM_BuildBrushWindingForSide L#%d :: pts \n", __LINE__));
+ return false;
+ }
+
+ glm::vec3 _xyzList[1024];
+ xyzCount = CM_GetXyzList(sideIndex, pts, ptCount, _xyzList, 1024);
+
+ if (xyzCount < 3)
+ {
+ return false;
+ }
+
+ CM_PickProjectionAxes(planeNormal, &i, &j);
+
+ glm::setFloat3(winding->p[0], _xyzList[0]);
+ glm::setFloat3(winding->p[1], _xyzList[1]);
+
+ winding->numpoints = 2;
+
+ for (k = 2; k < xyzCount; ++k)
+ {
+ CM_AddExteriorPointToWindingProjected(winding, _xyzList[k], i, j);
+ }
+
+ if (CM_RepresentativeTriangleFromWinding(winding, planeNormal, &i0, &i1, &i2) < 0.001)
+ {
+ return false;
+ }
+
+ PlaneFromPoints(&*plane, winding->p[i0], winding->p[i1], winding->p[i2]);
+
+ if ((((plane[0] * planeNormal[0]) + (plane[1] * planeNormal[1])) + (plane[2] * planeNormal[2])) < 0.0f)
+ {
+ CM_ReverseWinding(winding);
+ }
+
+ // huh
+ Game::winding_t *w = winding;
+
+ for (auto _i = 0; _i < 3; _i++)
+ { for (auto _j = 0; _j < 3; _j++)
+ {
+ if (fabs(w->p[_i][_j]) < Dvars::mapexport_brushEpsilon1->current.value)
+ {
+ w->p[_i][_j] = 0;
+ }
+ else if (fabs((int)w->p[_i][_j] - w->p[_i][_j]) < Dvars::mapexport_brushEpsilon2->current.value)
+ {
+ w->p[_i][_j] = (float)(int)w->p[_i][_j];
+ }
+ }
+ }
+
+ int p1;
+ int planenum = 0;
+
+ //three non-colinear points to define the plane
+ if (planenum & 1) { p1 = 1; }
+ else { p1 = 0; }
+
+ // *
+ // create the brushside
+
+ // plane 0
+ for (auto idx = 0; idx < 3; idx++)
+ {
+ bSide->brushPlane[0].point[idx] = w->p[p1][idx];
+ }
+
+ // plane 1
+ for (auto idx = 0; idx < 3; idx++)
+ {
+ bSide->brushPlane[1].point[idx] = w->p[!p1][idx];
+ }
+
+ // plane 2
+ for (auto idx = 0; idx < 3; idx++)
+ {
+ bSide->brushPlane[2].point[idx] = w->p[2][idx];
+ }
+
+ /*if (!Utils::polylib::CheckWinding(w))
+ {
+ Game::Com_PrintMessage(0, "removed degenerate brushside.\n", 0);
+ return false;
+ }*/
+
+ return true;
+ }
+
+ // build winding (poly) for side
+ bool CM_BuildBrushWindingForSide(Game::winding_t* winding, const float* planeNormal, const int sideIndex, Game::ShowCollisionBrushPt* pts, int ptCount)
+ {
+ int xyzCount, i, i0, i1, i2, j, k;
+ Game::vec4_t plane; Utils::vector::_VectorZero4(plane);
+
+ if (!winding)
+ {
+ Game::Com_Error(0, Utils::VA("CM_BuildBrushWindingForSide L#%d :: winding \n", __LINE__));
+ return false;
+ }
+
+ if (!planeNormal)
+ {
+ Game::Com_Error(0, Utils::VA("CM_BuildBrushWindingForSide L#%d :: planeNormal \n", __LINE__));
+ return false;
+ }
+
+ if (!pts)
+ {
+ Game::Com_Error(0, Utils::VA("CM_BuildBrushWindingForSide L#%d :: pts \n", __LINE__));
+ return false;
+ }
+
+ // create a list of vertex points
+ glm::vec3 _xyzList[1024];
+
+ xyzCount = CM_GetXyzList(sideIndex, pts, ptCount, _xyzList, 1024);
+
+ // we need atleast a triangle to create a poly
+ if (xyzCount < 3)
+ {
+ return false;
+ }
+
+ // direction of camera plane
+ glm::vec3 cameraDirectionToPlane = _xyzList[0] - Game::Globals::locPmove_cameraOrigin;
+
+ // dot product between line from camera to the plane and the normal
+ // if dot > 0 then the plane is facing away from the camera (dot = 1 = plane is facing the same way as the camera; dot = -1 = plane looking directly towards the camera)
+ if (glm::dot( glm::vec3(planeNormal[0], planeNormal[1], planeNormal[2]), cameraDirectionToPlane ) > 0.0f && !Dvars::r_drawCollision_polyFace->current.enabled)
+ {
+ return false;
+ }
+
+ // find the major axis
+ CM_PickProjectionAxes(planeNormal, &i, &j);
+
+ glm::setFloat3(winding->p[0], _xyzList[0]);
+ glm::setFloat3(winding->p[1], _xyzList[1]);
+
+ winding->numpoints = 2;
+
+ for (k = 2; k < xyzCount; ++k)
+ {
+ CM_AddExteriorPointToWindingProjected(winding, _xyzList[k], i, j);
+ }
+
+ // build a triangle of our winding points so we can check if the winding is clock-wise
+ if (CM_RepresentativeTriangleFromWinding(winding, planeNormal, &i0, &i1, &i2) < 0.001)
+ {
+ // do nothing if it is counter clock-wise
+ return false;
+ }
+
+ // *
+ // winding is clock-wise ..
+
+ // create a temp plane
+ PlaneFromPoints(&*plane, winding->p[i0], winding->p[i1], winding->p[i2]);
+
+ // if our winding has a clock-wise winding, reverse it
+ if (((plane[0] * planeNormal[0]) + (plane[1] * planeNormal[1]) + (plane[2] * planeNormal[2])) > 0.0f)
+ {
+ CM_ReverseWinding(winding);
+ }
+
+ return 1;
+ }
+
+ // Allocates a single brushside
+ Game::map_brushSide_t *Alloc_BrushSide(void)
+ {
+ Game::map_brushSide_t *bSide;
+ bSide = (Game::map_brushSide_t*)malloc(sizeof(*bSide));
+
+ if (bSide)
+ {
+ memset(bSide, 0, sizeof(Game::map_brushSide_t));
+ return bSide;
+ }
+
+ Game::Com_Error(0, "Alloc_BrushSide :: alloc failed!");
+ return 0;
+ }
+
+ bool CM_IsMapBrushSideWithinBounds(const Game::map_brushSide_t* bSide, const glm::vec3& mins, const glm::vec3& maxs)
+ {
+ if (!bSide)
+ {
+ return false;
+ }
+
+ for (auto plane = 0; plane < 3; plane++)
+ {
+ if (!Utils::polylib::PointWithinBounds(glm::toVec3(bSide->brushPlane[plane].point), mins, maxs, 0.25f))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // rebuild and draw brush from bounding box and dynamic sides
+ void CM_ShowSingleBrushCollision(Game::cbrush_t *brush, const float *color, int brushIndex, bool enableExport = true)
+ {
+ // skip all brush calculations when flicker mode is on and not exporting a map file
+ if (Dvars::r_drawCollision_flickerBrushes->current.enabled && !export_inProgress)
+ {
+ // on-time :: if amount of passed frames > flickerOn + flickerOff
+ if (collisionFlickerCounter > Dvars::r_drawCollision_flickerOnTime->current.integer + Dvars::r_drawCollision_flickerOffTime->current.integer)
+ {
+ collisionFlickerCounter = 0;
+ }
+
+ // off-time :: skip collision drawing if amount of "on-frames" are larger then our "on-frames-dvar"
+ else if (collisionFlickerCounter > Dvars::r_drawCollision_flickerOnTime->current.integer)
+ {
+ return;
+ }
+ }
+
+ int ptCount, sideIndex;
+ Game::ShowCollisionBrushPt brushPts[CM_MAX_BRUSHPOINTS_FROM_INTERSECTIONS]; // T5: 1024 .. wtf
+
+ if (!brush)
+ {
+ Game::Com_Error(0, Utils::VA("CM_ShowSingleBrushCollision L#%d :: brush \n", __LINE__));
+ return;
+ }
+
+ if (!color)
+ {
+ Game::Com_Error(0, Utils::VA("CM_ShowSingleBrushCollision L#%d :: color\n", __LINE__));
+ return;
+ }
+
+ // Create static sides (CM_BuildAxialPlanes)
+ Game::axialPlane_t _axialPlanes[6];
+ _axialPlanes[0].plane = glm::vec3(-1.0f, 0.0f, 0.0f);
+ _axialPlanes[0].dist = -brush->mins[0];
+
+ _axialPlanes[1].plane = glm::vec3(1.0f, 0.0f, 0.0f);
+ _axialPlanes[1].dist = brush->maxs[0];
+
+ _axialPlanes[2].plane = glm::vec3(0.0f, -1.0f, 0.0f);
+ _axialPlanes[2].dist = -brush->mins[1];
+
+ _axialPlanes[3].plane = glm::vec3(0.0f, 1.0f, 0.0f);
+ _axialPlanes[3].dist = brush->maxs[1];
+
+ _axialPlanes[4].plane = glm::vec3(0.0f, 0.0f, -1.0f);
+ _axialPlanes[4].dist = -brush->mins[2];
+
+ _axialPlanes[5].plane = glm::vec3(0.0f, 0.0f, 1.0f);
+ _axialPlanes[5].dist = brush->maxs[2];
+
+ // intersect all planes, 3 at a time, to to reconstruct face windings
+ ptCount = CM_ForEachBrushPlaneIntersection(brush, _axialPlanes, brushPts);
+
+ // we need atleast 4 valid points
+ if (ptCount >= 4)
+ {
+ // list of brushsides we are going to create within "CM_BuildBrushWindingForSideMapExport"
+ std::vector mapBrush;
+
+ float planeNormal[3];
+
+ auto poly_lit = Dvars::r_drawCollision_polyLit->current.enabled;
+ auto poly_outlines = Dvars::r_drawCollision->current.integer == 3 ? true : false;
+ auto poly_linecolor = Dvars::r_drawCollision_lineColor->current.vector;
+ auto poly_depth = Dvars::r_drawCollision_polyDepth->current.enabled;
+ auto poly_face = Dvars::r_drawCollision_polyFace->current.enabled;
+
+ // -------------------------------
+ // brushside [0]-[5] (axialPlanes)
+
+ for (sideIndex = 0; (unsigned int)sideIndex < 6; ++sideIndex)
+ {
+ glm::setFloat3(planeNormal, _axialPlanes[sideIndex].plane);
+
+ // build winding for the current brushside and check if it is visible (culling)
+ if (CM_BuildBrushWindingForSide((Game::winding_t *)windingPool, planeNormal, sideIndex, brushPts, ptCount))
+ {
+ if (Dvars::r_drawCollision->current.integer == 1)
+ {
+ _Debug::RB_AddAndDrawDebugLines(*(DWORD*)windingPool, (float(*)[3])& windingPool[4], Dvars::r_drawCollision_lineColor->current.vector);
+ }
+ else
+ {
+ _Debug::RB_DrawPoly(
+ /* numPts */ *(DWORD*)windingPool,
+ /* points */ (float(*)[3])& windingPool[4],
+ /* pColor */ color,
+ /* pLit */ poly_lit,
+ /* pOutline */ poly_outlines,
+ /* pLineCol */ poly_linecolor,
+ /* pDepth */ poly_depth,
+ /* pFace */ poly_face);
+ }
+
+ Game::Globals::dbgColl_drawnPlanesAmountTemp++;
+ }
+
+ // create brushsides from brush bounds (side [0]-[5])
+ if (export_inProgress && enableExport)
+ {
+ // allocate a brushside
+ Game::map_brushSide_t *bSide = Alloc_BrushSide();
+
+ // create a brushside from windings
+ if (CM_BuildBrushWindingForSideMapExport((Game::winding_t *)windingPool, planeNormal, sideIndex, brushPts, ptCount, bSide))
+ {
+ // brushside is valid
+ mapBrush.push_back(bSide);
+ }
+
+ else
+ {
+ // not a valid brushside so free it
+ free(bSide);
+ }
+ }
+ }
+
+ // ---------------------------------
+ // brushside [6] and up (additional)
+
+ for (sideIndex = 6; sideIndex < (std::int32_t)brush->numsides + 6; ++sideIndex)
+ {
+ if (CM_BuildBrushWindingForSide((Game::winding_t *)windingPool, brush->sides[sideIndex - 6].plane->normal, sideIndex, brushPts, ptCount))
+ {
+ if (Dvars::r_drawCollision->current.integer == 1)
+ {
+ _Debug::RB_AddAndDrawDebugLines(*(DWORD*)windingPool, (float(*)[3]) & windingPool[4], Dvars::r_drawCollision_lineColor->current.vector);
+ }
+ else
+ {
+ _Debug::RB_DrawPoly(
+ /* numPts */ *(DWORD*)windingPool,
+ /* points */ (float(*)[3]) & windingPool[4],
+ /* pColor */ color,
+ /* pLit */ poly_lit,
+ /* pOutline */ poly_outlines,
+ /* pLineCol */ poly_linecolor,
+ /* pDepth */ poly_depth,
+ /* pFace */ poly_face);
+ }
+
+ Game::Globals::dbgColl_drawnPlanesAmountTemp++;
+ }
+
+ // create brushsides from cm->brushes->sides (side [6] and up)
+ if (export_inProgress && enableExport)
+ {
+ // allocate a brushside
+ Game::map_brushSide_t *bSide = Alloc_BrushSide();
+
+ // create a brushside from windings
+ if (CM_BuildBrushWindingForSideMapExport((Game::winding_t *)windingPool, brush->sides[sideIndex - 6].plane->normal, sideIndex, brushPts, ptCount, bSide))
+ {
+ // brushside is valid
+ mapBrush.push_back(bSide);
+ }
+
+ else
+ {
+ // not a valid brushside so free it
+ free(bSide);
+ }
+ }
+ }
+
+ // if we are exporting the map
+ if (export_inProgress && enableExport)
+ {
+ bool dirty_hack_5_sides = false;
+
+ glm::vec3 bMins = glm::toVec3(brush->mins);
+ glm::vec3 bMaxs = glm::toVec3(brush->maxs);
+
+ // we need atleast 6 valid brushsides
+ if (mapBrush.size() < 6)
+ {
+ if (mapBrush.size() == 5 && Dvars::mapexport_brush5Sides && Dvars::mapexport_brush5Sides->current.enabled)
+ {
+ dirty_hack_5_sides = true;
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ // check brushes defined by more then their axialplanes
+ if(mapBrush.size() > 6)
+ {
+ if (glm::distance(bMins, bMaxs) < Dvars::mapexport_brushMinSize->current.value)
+ {
+ return;
+ }
+ }
+
+ for (auto side = 0; side < static_cast(mapBrush.size()); side++)
+ {
+ if (!CM_IsMapBrushSideWithinBounds(mapBrush[side], bMins, bMaxs))
+ {
+ return;
+ }
+ }
+
+ // swap brushsides (bottom, top, right, back, left, front)
+ if (!dirty_hack_5_sides)
+ {
+ std::swap(mapBrush[0], mapBrush[5]);
+ }
+
+ std::swap(mapBrush[3], mapBrush[4]);
+ std::swap(mapBrush[1], mapBrush[3]);
+ std::swap(mapBrush[0], mapBrush[1]);
+
+ // *
+ // do not export brushmodels as normal brushes
+ // write brushside strings to g_mapBrushModelList instead
+
+ if (brush->isSubmodel)
+ {
+ // clear any existing sides
+ g_mapBrushModelList[brush->cmSubmodelIndex].brushSides.clear();
+ }
+ else
+ {
+ // start brush
+ export_mapFile << Utils::VA("// brush %d\n{", export_currentBrushIndex) << std::endl;
+ export_mapFile << "layer \"000_Global/Brushes\"" << std::endl;
+
+ // global brush exporting index count
+ export_currentBrushIndex++;
+
+ // write brush contents
+ if (brush->contents & CM_CONTENTS_DETAIL)
+ {
+ export_mapFile << "contents detail;" << std::endl;
+ }
+ else if (brush->contents & CM_CONTENTS_CLIPSHOT)
+ {
+ export_mapFile << "contents weaponClip;" << std::endl;
+ }
+ }
+
+ // print brush sides and material info
+ for (auto bs = 0; bs < (int)mapBrush.size(); bs++)
+ {
+ std::string currBrushSide = Utils::VA(" ( %d %d %d ) ( %d %d %d ) ( %d %d %d ) ",
+ (int)mapBrush[bs]->brushPlane[0].point[0], (int)mapBrush[bs]->brushPlane[0].point[1], (int)mapBrush[bs]->brushPlane[0].point[2],
+ (int)mapBrush[bs]->brushPlane[1].point[0], (int)mapBrush[bs]->brushPlane[1].point[1], (int)mapBrush[bs]->brushPlane[1].point[2],
+ (int)mapBrush[bs]->brushPlane[2].point[0], (int)mapBrush[bs]->brushPlane[2].point[1], (int)mapBrush[bs]->brushPlane[2].point[2]);
+
+ if (!brush->isSubmodel)
+ {
+ export_mapFile << currBrushSide.c_str();
+ }
+
+ // get material index for the current brush side
+ int matIdx = 0;
+
+ // for the 6 brush sides created from axialplanes (brush bounds)
+ if (bs < 6)
+ {
+ // get material (brush->axialMaterialNum[array][index]) for the current brush side
+ // mapping axialnum order to .map brush side order
+ switch (bs)
+ {
+ case 0: // bottom
+ matIdx = (int)brush->axialMaterialNum[0][2];
+ break;
+ case 1: // top
+ matIdx = (int)brush->axialMaterialNum[1][2];
+ break;
+ case 2: // right
+ matIdx = (int)brush->axialMaterialNum[0][1];
+ break;
+ case 3: // back
+ matIdx = (int)brush->axialMaterialNum[1][0];
+ break;
+ case 4: // left
+ matIdx = (int)brush->axialMaterialNum[1][1];
+ break;
+ case 5: // front
+ matIdx = (int)brush->axialMaterialNum[0][0];
+ break;
+ }
+ }
+
+ // we have atleast 1 additional brush side
+ else
+ {
+ if (!dirty_hack_5_sides)
+ {
+ // additional brush sides start at index 0
+ matIdx = brush->sides[bs - 6].materialNum;
+ }
+ }
+
+ // *
+ // Material Dimensions
+
+ // default values if we fail to find the correct texture size
+ int texWidth = 128;
+ int texHeight = 128;
+
+ // texture size scalar (depends on texture quality settings)
+ float tex_scalar = 1.0f;
+
+ const auto r_picmip = Game::Dvar_FindVar("r_picmip");
+ if (r_picmip)
+ {
+ switch (r_picmip->current.integer)
+ {
+ case 0: // extra
+ tex_scalar = 0.25f;
+ break;
+ case 1: // high
+ tex_scalar = 0.5f;
+ break;
+ case 2: // normal
+ tex_scalar = 1.0f;
+ break;
+ case 3:
+ tex_scalar = 2.0f;
+ break;
+ default: // should not happen
+ tex_scalar = 1.0f;
+ }
+ }
+
+ // get the world material and its size
+ std::string material_name_for_brushside = Game::cm->materials[matIdx].material;
+ std::string materialForSide = "wc/"s + material_name_for_brushside;
+
+ const auto mat = Game::Material_RegisterHandle(materialForSide.c_str(), 3);
+ if (mat)
+ {
+ for (auto tex = 0; tex < mat->textureCount; tex++)
+ {
+ // 0x2 = color, 0x5 = normal, 0x8 = spec
+ if (mat->textureTable[tex].u.image->semantic == 0x2)
+ {
+ texWidth = static_cast((mat->textureTable[tex].u.image->width * tex_scalar)); // loaded texture sizes vary (texture quality settings)
+ texHeight = static_cast((mat->textureTable[tex].u.image->height * tex_scalar)); // so we need to use a scalar to get a 0.25 stretch in radiant
+
+ break;
+ }
+ }
+ }
+
+ //std::string materialForSide = Game::cm->materials[matIdx].material;
+
+ // for each material in memory
+ //for (auto matMem = 0; matMem < Game::_gfxWorld->materialMemoryCount; matMem++)
+ //{
+ // // current material name
+ // std::string currMatName = Game::_gfxWorld->materialMemory[matMem].material->info.name; // material with suffix wc/ mc/ ..
+
+ // // check if material is our brush side material
+ // if (currMatName.find(materialForSide) != std::string::npos)
+ // {
+ // // find the colormap of the brush side material
+ // for (auto texture = 0; texture < Game::_gfxWorld->materialMemory[matMem].material->textureCount; texture++)
+ // {
+ // // current texture name
+ // //std::string currTexName = Game::_gfxWorld->materialMemory[matMem].material->textureTable[texture].u.image->name;
+ // // .... //if (Utils::has_suffix(currTexName, std::string("_col")) || Utils::has_suffix(currTexName, std::string("_c")))
+
+ // // 0x2 = color, 0x5 = normal, 0x8 = spec
+ // if (Game::_gfxWorld->materialMemory[matMem].material->textureTable[texture].u.image->semantic == 0x2)
+ // {
+ // texWidth = (int)(Game::_gfxWorld->materialMemory[matMem].material->textureTable[texture].u.image->width * 0.25f); // assuming 0.25 horizontal stretch
+ // texHeight = (int)(Game::_gfxWorld->materialMemory[matMem].material->textureTable[texture].u.image->height * 0.25f); // assuming 0.25 vertical stretch
+
+ // break;
+ // }
+ // }
+
+ // // we found the correct material
+ // break;
+ // }
+ //}
+
+ if (!brush->isSubmodel)
+ {
+ // materialname, width, height, xpos, ypos, rotation, ?, lightmapMat, lmap_sampleSizeX, lmap_sampleSizeY, lmap_xpos, lmap_ypos, lmap_rotation, ?
+ export_mapFile << Utils::VA("%s %d %d 0 0 0 0 lightmap_gray 16384 16384 0 0 0 0\n", material_name_for_brushside.c_str(), texWidth, texHeight);
+ }
+ else
+ {
+ g_mapBrushModelList[brush->cmSubmodelIndex].brushSides.push_back(currBrushSide + Utils::VA("%s %d %d 0 0 0 0 lightmap_gray 16384 16384 0 0 0 0\n", material_name_for_brushside.c_str(), texWidth, texHeight));
+ }
+
+ }
+
+ if (!brush->isSubmodel)
+ {
+ // end brush
+ export_mapFile << "}" << std::endl;
+ }
+
+ // single brush for radiant ......
+ // Game::ServerCommand cmd;
+
+ // Game::Com_PrintMessage(0, "exporting brushsides for selected brush:\n", 0);
+
+ // // for all brushsides ::
+ // // send index first, then the brushside
+ // for (auto bs = 0; bs < (int)mapBrush.size(); bs++)
+ // {
+ // memset(&cmd, 0, sizeof(Game::ServerCommand));
+ // cmd.type = Game::SERVER_EXPORT_SINGLE_BRUSH_FACE_INDEX;
+ // sprintf_s(cmd.strCommand, "%d", bs);
+
+ // RadiantRemote::RemoteNet_SendPacket(&cmd);
+
+ // memset(&cmd, 0, sizeof(Game::ServerCommand));
+ // cmd.type = Game::SERVER_EXPORT_SINGLE_BRUSH_FACE;
+ // sprintf_s(cmd.strCommand, "%.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f",
+ // mapBrush[bs]->brushPlane[0].point[0], mapBrush[bs]->brushPlane[0].point[1], mapBrush[bs]->brushPlane[0].point[2],
+ // mapBrush[bs]->brushPlane[1].point[0], mapBrush[bs]->brushPlane[1].point[1], mapBrush[bs]->brushPlane[1].point[2],
+ // mapBrush[bs]->brushPlane[2].point[0], mapBrush[bs]->brushPlane[2].point[1], mapBrush[bs]->brushPlane[2].point[2]);
+
+ // RadiantRemote::RemoteNet_SendPacket(&cmd);
+
+ // Game::Com_PrintMessage(0, Utils::VA("< ( %.2f %.2f %.2f ) ( %.2f %.2f %.2f ) ( %.2f %.2f %.2f ) >\n",
+ // mapBrush[bs]->brushPlane[0].point[0], mapBrush[bs]->brushPlane[0].point[1], mapBrush[bs]->brushPlane[0].point[2],
+ // mapBrush[bs]->brushPlane[1].point[0], mapBrush[bs]->brushPlane[1].point[1], mapBrush[bs]->brushPlane[1].point[2],
+ // mapBrush[bs]->brushPlane[2].point[0], mapBrush[bs]->brushPlane[2].point[1], mapBrush[bs]->brushPlane[2].point[2]), 0);
+ // }
+ //}
+ }
+ }
+ }
+
+ // Brush View Culling
+ int BoxOnPlaneSide(const float *emins, const float *emaxs, const Game::cplane_s *p)
+ {
+ float dist1, dist2;
+ int sides = 0;
+
+ // fast axial cases
+ if (p->type < 3)
+ {
+ if (p->dist <= emins[p->type]) { return 1; }
+ if (p->dist >= emaxs[p->type]) { return 2; }
+
+ return 3;
+ }
+
+ // general case
+ switch (p->signbits)
+ {
+ case 0:
+ dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2];
+ dist2 = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2];
+ break;
+
+ case 1:
+ dist1 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2];
+ dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2];
+ break;
+
+ case 2:
+ dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2];
+ dist2 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2];
+ break;
+
+ case 3:
+ dist1 = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2];
+ dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2];
+ break;
+
+ case 4:
+ dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2];
+ dist2 = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2];
+ break;
+
+ case 5:
+ dist1 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2];
+ dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2];
+ break;
+
+ case 6:
+ dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2];
+ dist2 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2];
+ break;
+
+ case 7:
+ dist1 = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2];
+ dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2];
+ break;
+
+ default:
+ dist1 = dist2 = 0; // shut up compiler
+ break;
+ }
+
+ if (dist1 >= p->dist)
+ {
+ sides = 1;
+ }
+
+ if (dist2 < p->dist)
+ {
+ sides |= 2;
+ }
+
+ return sides;
+ }
+
+ // check if any side of a brush is within the view frustum
+ bool CM_BrushInView(Game::cbrush_t *brush, Game::cplane_s *frustumPlanes, int frustumPlaneCount)
+ {
+ int frustumPlaneIndex;
+
+ if (!frustumPlanes)
+ {
+ Game::Com_Error(0, Utils::VA("CM_BrushInView L#%d :: frustumPlanes\n", __LINE__));
+ return false;
+ }
+
+ for (frustumPlaneIndex = 0; frustumPlaneIndex < frustumPlaneCount; ++frustumPlaneIndex)
+ {
+ if (!(BoxOnPlaneSide(brush->mins, brush->maxs, &frustumPlanes[frustumPlaneIndex]) & 1))
+ {
+ return 0;
+ }
+ }
+
+ return 1;
+ }
+
+ // check if the material selected via dvar equals the current brush material or one of its sides if the first side uses caulk
+ bool CM_ValidBrushMaterialSelection(Game::cbrush_t *brush, int materialNumFromDvar)
+ {
+ // we can also filter materials by substrings with "r_drawCollision_materialInclude" ( "clip_player" returns true when using "clip" )
+ std::string includeString = "";
+
+ // workaround till we get dvar strings to register - actually, cba. to implement that now
+ switch (Dvars::r_drawCollision_materialInclude->current.integer)
+ {
+ case 1:
+ includeString = "clip";
+ break;
+
+ case 2:
+ includeString = "mantle";
+ break;
+
+ case 3:
+ includeString = "trigger";
+ break;
+
+ case 4:
+ includeString = "all";
+ return true; // no need to check any material so return true
+ //break; // removing brushes using a trigger texture
+
+ case 5:
+ includeString = "all-no-tools";
+ break;
+ case 6:
+ includeString = "all-no-tools-clip";
+ break;
+ }
+
+ // check if we are within array bounds
+ if (static_cast(brush->axialMaterialNum[0][0]) >= g_mapMaterialListDuplicates.size())
+ {
+ return false;
+ }
+
+ if (static_cast(materialNumFromDvar) >= g_mapMaterialListSingle.size())
+ {
+ return false;
+ }
+
+ // bridge dupe materials list with the cleaned list
+ std::string materialNameFromDuplicates = g_mapMaterialListDuplicates[(int)brush->axialMaterialNum[0][0]];
+ std::string materialNameFromSingle = g_mapMaterialListSingle[materialNumFromDvar];
+
+ // instantly found our material
+ if (materialNameFromDuplicates == materialNameFromSingle)
+ {
+ return true;
+ }
+
+ // if filter is not empty
+ if (!includeString.empty())
+ {
+ // if we found a matching substring
+ if (materialNameFromDuplicates.find(includeString) != std::string::npos)
+ {
+ return true;
+ }
+
+ // draw all materials (mainly for map exporting)
+ else if (includeString == "all")
+ {
+ // no longer skip triggers
+ /*if (materialNameFromDuplicates.find("trigger") != std::string::npos)
+ {
+ return false;
+ }*/
+
+ return true;
+ }
+
+ // draw all materials without info volumes
+ else if (includeString == "all-no-tools")
+ {
+ if(materialNameFromDuplicates.find("portal") != std::string::npos)
+ return false;
+
+ if (materialNameFromDuplicates.find("hint") != std::string::npos)
+ return false;
+
+ if (materialNameFromDuplicates.find("volume") != std::string::npos)
+ return false;
+
+ if (materialNameFromDuplicates.find("mantle") != std::string::npos)
+ return false;
+
+ if (materialNameFromDuplicates.find("trigger") != std::string::npos)
+ return false;
+
+ if (materialNameFromDuplicates.find("sky") != std::string::npos)
+ return false;
+
+ // if no excluded materials were found, return true
+ return true;
+ }
+
+ else if (includeString == "all-no-tools-clip")
+ {
+ if (materialNameFromDuplicates.find("portal") != std::string::npos)
+ return false;
+
+ if (materialNameFromDuplicates.find("hint") != std::string::npos)
+ return false;
+
+ if (materialNameFromDuplicates.find("volume") != std::string::npos)
+ return false;
+
+ if (materialNameFromDuplicates.find("mantle") != std::string::npos)
+ return false;
+
+ if (materialNameFromDuplicates.find("trigger") != std::string::npos)
+ return false;
+
+ if (materialNameFromDuplicates.find("sky") != std::string::npos)
+ return false;
+
+ if (materialNameFromDuplicates.find("clip") != std::string::npos)
+ return false;
+
+ // if no excluded materials were found, return true
+ return true;
+ }
+ }
+
+ // if the brush has caulk as its first side, check the other sides for our material/substring
+ else if (materialNameFromDuplicates == "caulk")
+ {
+ int currentMatArray;
+
+ // check the first and second material array
+ for (currentMatArray = 0; currentMatArray < 2; currentMatArray++)
+ {
+ // check its 3 elements
+ for (int matIndex = 0; matIndex < 3; matIndex++)
+ {
+ // if one of the sides uses our material
+ if (g_mapMaterialListDuplicates[(int)brush->axialMaterialNum[currentMatArray][matIndex]] == materialNameFromSingle)
+ {
+ return true;
+ }
+
+ // if we have a filter set
+ if (Dvars::r_drawCollision_materialInclude->current.integer != 0)
+ {
+ // if one of the sides matches our substring
+ if (g_mapMaterialListDuplicates[(int)brush->axialMaterialNum[currentMatArray][matIndex]].find(includeString) != std::string::npos)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ // brush does not contain our material/substring
+ return false;
+ }
+
+ // resets a selection bounding box
+ void CM_ResetSelectionBox(Game::boundingBox_t* box)
+ {
+ memset(box, 0, sizeof(Game::boundingBox_t));
+
+ box->mins = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
+ box->maxs = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
+ box->wasReset = true;
+ }
+
+ // get mins/maxs from points
+ void CM_BoundsFromPoints(const int& numPoints, const glm::vec3* points, glm::vec3& mins, glm::vec3& maxs)
+ {
+ mins = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);
+ maxs = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
+
+ for (auto sPoint = 0; sPoint < numPoints; sPoint++)
+ {
+ for (auto pAxis = 0; pAxis < 3; pAxis++)
+ {
+ // mins :: find the closest point on each axis
+ if (mins[pAxis] > points[sPoint][pAxis])
+ mins[pAxis] = points[sPoint][pAxis];
+
+ // maxs :: find the furthest point on each axis
+ if (maxs[pAxis] < points[sPoint][pAxis])
+ maxs[pAxis] = points[sPoint][pAxis];
+ }
+ }
+ }
+
+ // calculate brush midpoint
+ glm::vec3 CM_GetBrushMidpoint(const Game::cbrush_t* brush, bool xyzMidpoint = false)
+ {
+ if (!xyzMidpoint)
+ {
+ return (glm::vec3(brush->mins[0], brush->mins[1], 0.0f)
+ + glm::vec3(brush->maxs[0], brush->maxs[1], 0.0f)) * 0.5f;
+ }
+
+ else
+ {
+ return (glm::vec3(brush->mins[0], brush->mins[1], brush->mins[2])
+ + glm::vec3(brush->maxs[0], brush->maxs[1], brush->maxs[2])) * 0.5f;
+ }
+ }
+
+ // check if a brushes midpoint is within a selection box
+ bool CM_IsBrushWithinSelectionBox(const Game::cbrush_t* brush, const Game::boundingBox_t* selectionBox)
+ {
+ if (brush && selectionBox->isBoxValid)
+ {
+ return Utils::polylib::PointWithinBounds(CM_GetBrushMidpoint(brush, true), selectionBox->mins, selectionBox->maxs, 0.25f);
+ }
+
+ return false;
+ }
+
+ // check if a triangles midpoint is within a selection box
+ bool CM_IsTriangleWithinSelectionBox(const Game::map_patchTris_t* tris, Game::boundingBox_t* selectionBox)
+ {
+ for (auto triCoord = 0; triCoord < 3; triCoord++)
+ {
+ if(!Utils::polylib::PointWithinBounds(glm::toVec3(tris->coords[triCoord].xyz), selectionBox->mins, selectionBox->maxs, 0.25f))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // -------------
+ // CM_OnceOnInit
+
+ // :: create "g_mapMaterialListDuplicates" and write all materials without \0
+ // :: create "g_mapMaterialListSingle" add all material from matListDuplicates without creating duplicates
+ // :: create a dvar string from "g_mapMaterialListSingle"
+ // :: compare axialMaterialNum with "g_mapMaterialListDuplicates" to get the original materialName
+ // :: compare "g_mapMaterialListDuplicates" materialName with the choosen dvar Material name
+ // :: draw the brush if they match
+
+ // stuff we only do once per map after r_drawcollision was turned on
+ void CM_OnceOnInit()
+ {
+ // assign a color to each brush (modified cbrush_t struct) so we use persistent colors even when sorting brushes
+ int colorCounter = 0;
+
+ // only once per map
+ //if (g_mapNameCm != Game::cm->name)
+ if(!Game::Globals::dbgColl_initialized || Utils::Q_stricmp(g_mapNameCm, Game::cm->name))
+ {
+ Game::Com_PrintMessage(0, "[Debug Collision] : initializing ...\n", 0);
+
+ // list of all brushmodels within the current map mapped to their respective brushes in cm->brushes
+
+ char* mapents_ptr = Game::cm->mapEnts->entityString;
+
+ if(_Map::mpsp_is_sp_map)
+ {
+ mapents_ptr = _Map::mpsp_mapents_original;
+ }
+
+ Utils::Entities mapEnts(mapents_ptr);
+ g_mapBrushModelList = mapEnts.getBrushModels();
+
+ Game::Com_PrintMessage(0, Utils::VA("|-> found %d submodels\n", static_cast(g_mapBrushModelList.size())), 0);
+
+ // hacky string dvar
+ auto dvarName = "r_drawCollision_brushIndexFilter";
+ Game::Dvar_RegisterString_hacky(dvarName, "null", "Specifies which brushes to draw. Ignores all other filters and will disable brush sorting.\nInput example: \"101 99 2\" :: \"^1null^7\" disables this filter");
+
+ // assign hacky dvar to the global dvar
+ Dvars::r_drawCollision_brushIndexFilter = Game::Dvar_FindVar(dvarName);
+
+#if DEBUG
+ // change dvar to only draw 1 brush when using a debug build (debug build has really bad performance)
+ Game::Dvar_SetValue(Dvars::r_drawCollision_brushAmount, 1);
+#endif
+ // cap brushAmount
+ Dvars::r_drawCollision_brushAmount->domain.integer.max = Game::cm->numBrushes - 1;
+
+ // reset brush distance filter
+ Game::Dvar_SetValue(Dvars::r_drawCollision_brushDist, 800.0f);
+
+ // clear global vectors
+ g_mapBrushList.clear();
+ g_mapMaterialList.clear();
+ g_mapMaterialListDuplicates.clear();
+ g_mapMaterialListSingle.clear();
+ //g_dvarMaterialList_str.clear();
+ Game::Globals::r_drawCollision_materialList_string.clear();
+
+ // init/reset the selection box
+ CM_ResetSelectionBox(&export_selectionBox);
+
+ // create a dmaterial_t vector
+ std::int32_t cmMaterialNum = Game::cm->numMaterials;
+ std::vector currMapMaterials(cmMaterialNum);
+
+ // assign clipMap material pointers
+ for (int num = 0; num < cmMaterialNum; num++)
+ {
+ currMapMaterials[num] = &Game::cm->materials[num];
+ }
+
+ // create a cbrush_t vector
+ std::vector currBrushList(Game::cm->numBrushes);
+
+ // assign clipMap brush pointers
+ for (int num = 0; num < Game::cm->numBrushes; num++)
+ {
+ currBrushList[num] = &Game::cm->brushes[num];
+ currBrushList[num]->colorCounter = (short)colorCounter++;
+ currBrushList[num]->cmBrushIndex = (short)num;
+
+ colorCounter %= 8;
+ }
+
+ // set globals
+ Game::Globals::dbgColl_initialized = true;
+ g_mapNameCm = Game::cm->name;
+ g_mapMaterialList = currMapMaterials;
+ g_mapBrushList = currBrushList;
+
+ // create a string vector of all material (contains duplicates)
+ for (std::uint32_t num = 0; num < g_mapMaterialList.size(); num++)
+ {
+ std::string materialName = Utils::convertToString(g_mapMaterialList[num]->material, sizeof(g_mapMaterialList[num]->material));
+ materialName.erase(std::remove(materialName.begin(), materialName.end(), '\0'), materialName.end());
+
+ g_mapMaterialListDuplicates.push_back(materialName);
+ }
+
+ // create a string vector that without duplicate materials (used for dvar description)
+ for (std::uint32_t num = 0; num < g_mapMaterialListDuplicates.size(); num++)
+ {
+ std::string materialName = g_mapMaterialListDuplicates[num];
+
+ // if the vector is empty or the materialName does not exist within the array
+ if (g_mapMaterialListSingle.empty() || std::find(g_mapMaterialListSingle.begin(), g_mapMaterialListSingle.end(), materialName) == g_mapMaterialListSingle.end())
+ {
+ g_mapMaterialListSingle.push_back(materialName);
+ }
+ }
+
+ // create the dvar string
+ auto singleMaterialCount = g_mapMaterialListSingle.size();
+
+ for (std::uint32_t num = 0; num < singleMaterialCount; num++)
+ {
+ // do not print "empty" materials
+ if (!Utils::StartsWith(g_mapMaterialListSingle[num], "*"))
+ {
+ /*g_dvarMaterialList_str*/
+ Game::Globals::r_drawCollision_materialList_string += std::to_string(num) + ": " + g_mapMaterialListSingle[num] + "\n";
+ }
+ }
+
+ Game::Com_PrintMessage(0, Utils::VA("|-> found %d materials\n", static_cast(singleMaterialCount)), 0);
+
+ // Set material dvar back to 0, update description and max value
+ Game::Dvar_SetValue(Dvars::r_drawCollision_material, 0);
+ Dvars::r_drawCollision_material->domain.integer.max = singleMaterialCount - 1;
+
+ //print the material list to the large console if the list has more then 20 materials
+ if (singleMaterialCount > 20)
+ {
+ Dvars::r_drawCollision_material->description = "Too many materials to show here! Use ^1\"r_drawCollision_materialList\" ^7to print a list of all materials.\nOnly works with ^1\"r_drawCollision\" ^7enabled!";
+ }
+ else
+ {
+ Dvars::r_drawCollision_material->description = Game::Globals::r_drawCollision_materialList_string.c_str(); //g_dvarMaterialList_str.c_str();
+ }
+
+ Game::Com_PrintMessage(0, "|-> done\n", 0);
+ }
+ }
+
+ // sort globBrushList depending on distance from brush to camera
+ void CM_SortBrushListOnDistanceCamera(bool farToNear = true, bool useCurrentBrushesDrawnList = false, bool updateAlways = false)
+ {
+ if (!useCurrentBrushesDrawnList)
+ {
+ // sort brushes by distance from brush midpoint(xy) to camera origin :: only sort if the players origin has changed
+ if (g_oldSortingOrigin[0] != Game::Globals::locPmove_playerOrigin[0] || g_oldSortingOrigin[1] != Game::Globals::locPmove_playerOrigin[1] || updateAlways)
+ {
+ // sort from far to near (used for rendering)
+ if (farToNear)
+ {
+ std::sort(g_mapBrushList.begin(), g_mapBrushList.end(), [](Game::cbrush_t *brush1, Game::cbrush_t *brush2)
+ {
+ glm::vec3 b1_mid = (glm::vec3(brush1->mins[0], brush1->mins[1], 0.0f)
+ + glm::vec3(brush1->maxs[0], brush1->maxs[1], 0.0f)) * 0.5f;
+
+ glm::vec3 b2_mid = (glm::vec3(brush2->mins[0], brush2->mins[1], 0.0f)
+ + glm::vec3(brush2->maxs[0], brush2->maxs[1], 0.0f)) * 0.5f;
+
+ float dist1 = glm::distance(b1_mid, Game::Globals::locPmove_playerOrigin);
+ float dist2 = glm::distance(b2_mid, Game::Globals::locPmove_playerOrigin);
+
+ return dist1 > dist2;
+ });
+ }
+
+ // sort from near to far (get closest brush)
+ else
+ {
+ std::sort(g_mapBrushList.begin(), g_mapBrushList.end(), [](Game::cbrush_t *brush1, Game::cbrush_t *brush2)
+ {
+ glm::vec3 b1_mid = (glm::vec3(brush1->mins[0], brush1->mins[1], brush1->mins[2])
+ + glm::vec3(brush1->maxs[0], brush1->maxs[1], brush1->maxs[2])) * 0.5f;
+
+ glm::vec3 b2_mid = (glm::vec3(brush2->mins[0], brush2->mins[1], brush2->mins[2])
+ + glm::vec3(brush2->maxs[0], brush2->maxs[1], brush2->maxs[2])) * 0.5f;
+
+ float dist1 = glm::distance(b1_mid, Game::Globals::locPmove_playerOrigin);
+ float dist2 = glm::distance(b2_mid, Game::Globals::locPmove_playerOrigin);
+
+ return dist1 < dist2;
+ });
+ }
+
+ //memcpy(prev_Origin, Game::Globals::locPmove_playerOrigin, sizeof(prev_Origin));
+ g_oldSortingOrigin = Game::Globals::locPmove_playerOrigin;
+ }
+ }
+
+ // use drawn brushes list if not empty
+ else if(!g_mapBrushListForIndexFiltering.empty())
+ {
+ // sort brushes by distance from brush midpoint(xy) to camera origin :: only sort if the players origin has changed
+ if (g_oldSortingOrigin[0] != Game::Globals::locPmove_playerOrigin[0] || g_oldSortingOrigin[1] != Game::Globals::locPmove_playerOrigin[1] || updateAlways)
+ {
+ // sort from far to near (used for rendering)
+ if (farToNear)
+ {
+ std::sort(g_mapBrushListForIndexFiltering.begin(), g_mapBrushListForIndexFiltering.end(), [](Game::cbrush_t *brush1, Game::cbrush_t *brush2)
+ {
+ glm::vec3 b1_mid = (glm::vec3(brush1->mins[0], brush1->mins[1], 0.0f)
+ + glm::vec3(brush1->maxs[0], brush1->maxs[1], 0.0f)) * 0.5f;
+
+ glm::vec3 b2_mid = (glm::vec3(brush2->mins[0], brush2->mins[1], 0.0f)
+ + glm::vec3(brush2->maxs[0], brush2->maxs[1], 0.0f)) * 0.5f;
+
+ float dist1 = glm::distance(b1_mid, Game::Globals::locPmove_playerOrigin);
+ float dist2 = glm::distance(b2_mid, Game::Globals::locPmove_playerOrigin);
+
+ return dist1 > dist2;
+ });
+ }
+
+ // sort from near to far (get closest brush)
+ else
+ {
+ std::sort(g_mapBrushListForIndexFiltering.begin(), g_mapBrushListForIndexFiltering.end(), [](Game::cbrush_t *brush1, Game::cbrush_t *brush2)
+ {
+ glm::vec3 b1_mid = (glm::vec3(brush1->mins[0], brush1->mins[1], 0.0f)
+ + glm::vec3(brush1->maxs[0], brush1->maxs[1], 0.0f)) * 0.5f;
+
+ glm::vec3 b2_mid = (glm::vec3(brush2->mins[0], brush2->mins[1], 0.0f)
+ + glm::vec3(brush2->maxs[0], brush2->maxs[1], 0.0f)) * 0.5f;
+
+ float dist1 = glm::distance(b1_mid, Game::Globals::locPmove_playerOrigin);
+ float dist2 = glm::distance(b2_mid, Game::Globals::locPmove_playerOrigin);
+
+ return dist1 < dist2;
+ });
+ }
+
+ g_oldSortingOrigin = Game::Globals::locPmove_playerOrigin;
+ }
+ }
+ }
+
+ // color brushes depending on their index within cm->brushes
+ void CM_GetShowCollisionColor(float *colorFloat, const int colorCounter)
+ {
+ if (!colorFloat)
+ {
+ Game::Com_Error(0, Utils::VA("CM_GetShowCollisionColor L#%d :: colorFloat\n", __LINE__));
+ return;
+ }
+
+ if (colorCounter & 1)
+ { colorFloat[0] = 1.0f; }
+ else
+ { colorFloat[0] = 0.0f; }
+
+ if (colorCounter & 2)
+ { colorFloat[1] = 1.0f; }
+ else
+ { colorFloat[1] = 0.0f; }
+
+ if (colorCounter & 4)
+ { colorFloat[2] = 1.0f; }
+ else
+ { colorFloat[2] = 0.0f; }
+
+ colorFloat[3] = Dvars::r_drawCollision_polyAlpha->current.value;
+ }
+
+ // check if 2 triangles share an edge
+ bool PatchTriangle_SharesEdge(const Game::map_patchTris_t *pTri1, const Game::map_patchTris_t *pTri2)
+ {
+ int matchedPoints = 0;
+
+ // for each edge of triangle 1
+ for (auto edgeTri1 = 0; edgeTri1 < 3; edgeTri1++)
+ {
+ // for each edge of triangle 2
+ for (auto edgeTri2 = 0; edgeTri2 < 3; edgeTri2++)
+ {
+ matchedPoints += Utils::vector::_VectorCompare(pTri1->coords[edgeTri1].xyz, pTri2->coords[edgeTri2].xyz);
+ }
+
+ // shouldnt happen
+ if (matchedPoints > 2)
+ {
+ Game::Com_PrintMessage(0, "^1[MAP-EXPORT]: ^7 PatchTriangle_SharesEdge matchedPoints > 2", 0);
+ }
+
+ if (matchedPoints == 2)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // if (k < 0.001f) = counter clock wise
+ bool PatchTriangle_ClockwiseWinding(const float *pt0, const float *pt1, const float *pt2)
+ {
+ float k = (pt1[1] - pt0[1]) * (pt2[0] - pt1[0]) - (pt1[0] - pt0[0]) * (pt2[1] - pt1[1]);
+ return k > 0.001f;
+ }
+
+ // not in map format order
+ void PatchTriangle_FromIncides(Game::map_patchTris_t *pTris, const unsigned short *incides)
+ {
+ bool foundFirst = false, foundSecond = false, foundThird = false, secondIteration = false;
+
+ // find the first match
+ for (auto gfxVert = 0; gfxVert < static_cast(Game::_gfxWorld->vertexCount); gfxVert++)
+ {
+ // try to match our first clipmap vertex to a gfxworld vertex
+ if (!foundFirst && Utils::vector::_VectorCompare(Game::cm->verts[incides[0]], Game::_gfxWorld->vd.vertices[gfxVert].xyz))
+ {
+ // found the first corrosponding gfx vertex :: copy gfx vertex data to our temp vertex
+ memcpy(&pTris->coords[0], &Game::_gfxWorld->vd.vertices[gfxVert], sizeof(Game::GfxWorldVertex));
+
+ // go back 6 verts in the gfxworld vertex array and start searching for our second point
+ // :: assuming that our verts are close to each other
+ if (gfxVert - 6 > 0)
+ {
+ gfxVert -= 6;
+ }
+
+ else
+ {
+ gfxVert = 0;
+ }
+
+ // do not match first vert again
+ foundFirst = true;
+ }
+
+ // try to match our second clipmap vertex to a gfxworld vertex
+ if (!foundSecond && Utils::vector::_VectorCompare(Game::cm->verts[incides[1]], Game::_gfxWorld->vd.vertices[gfxVert].xyz))
+ {
+ // found the second corrosponding gfx vertex :: copy gfx vertex data to our temp vertex
+ memcpy(&pTris->coords[1], &Game::_gfxWorld->vd.vertices[gfxVert], sizeof(Game::GfxWorldVertex));
+
+ // go back 6 verts in the gfxworld vertex array
+ // :: assuming that our verts are close to each other
+ if (gfxVert - 6 > 0)
+ {
+ gfxVert -= 6;
+ }
+
+ else
+ {
+ gfxVert = 0;
+ }
+
+ // do not match second vert again
+ foundSecond = true;
+ }
+
+ // try to match our third clipmap vertex to a gfxworld vertex
+ if (!foundThird && Utils::vector::_VectorCompare(Game::cm->verts[incides[2]], Game::_gfxWorld->vd.vertices[gfxVert].xyz))
+ {
+ // found the third corrosponding gfx vertex :: copy gfx vertex data to our temp vertex
+ memcpy(&pTris->coords[2], &Game::_gfxWorld->vd.vertices[gfxVert], sizeof(Game::GfxWorldVertex));
+
+ // go back 6 verts in the gfxworld vertex array
+ // :: assuming that our verts are close to each other
+ if (gfxVert - 6 > 0)
+ {
+ gfxVert -= 6;
+ }
+
+ else
+ {
+ gfxVert = 0;
+ }
+
+ // do not match third vert again
+ foundThird = true;
+ }
+
+ if (foundFirst && foundSecond && foundThird)
+ {
+ break;
+ }
+ // if we did not match all 3 and looped all of the gfxworld vertices
+ else if (gfxVert + 1 > (int)Game::_gfxWorld->vertexCount && !secondIteration)
+ {
+ // check the whole array once again (as we only gone back 6 verts each time we found a corrosponding vert)
+ gfxVert = 0;
+
+ // break out of the loop if we failed to match all 3 verts within the second iteration
+ secondIteration = true;
+ }
+ }
+
+ // if we failed to match all points, write clipmap vert data
+ if (!foundFirst)
+ {
+ pTris->coords[0].xyz[0] = Game::cm->verts[incides[0]][0];
+ pTris->coords[0].xyz[1] = Game::cm->verts[incides[0]][1];
+ pTris->coords[0].xyz[2] = Game::cm->verts[incides[0]][2];
+ }
+
+ if (!foundSecond)
+ {
+ pTris->coords[1].xyz[0] = Game::cm->verts[incides[1]][0];
+ pTris->coords[1].xyz[1] = Game::cm->verts[incides[1]][1];
+ pTris->coords[1].xyz[2] = Game::cm->verts[incides[1]][2];
+ }
+
+ if (!foundThird)
+ {
+ pTris->coords[2].xyz[0] = Game::cm->verts[incides[2]][0];
+ pTris->coords[2].xyz[1] = Game::cm->verts[incides[2]][1];
+ pTris->coords[2].xyz[2] = Game::cm->verts[incides[2]][2];
+ }
+ }
+
+ // Allocates a single patch triangle
+ Game::map_patchTris_t *Alloc_PatchTriangle(void)
+ {
+ Game::map_patchTris_t *pTris;
+ pTris = (Game::map_patchTris_t*)malloc(sizeof(*pTris));
+
+ if (pTris)
+ {
+ memset(pTris, 0, sizeof(Game::map_patchTris_t));
+ return pTris;
+ }
+
+ Game::Com_Error(0, "Alloc_PatchTriangle :: alloc failed!");
+ return 0;
+ }
+
+ // Allocates a single patch quad
+ Game::map_patchQuads_t *Alloc_PatchQuad(void)
+ {
+ Game::map_patchQuads_t *pQuad;
+ pQuad = (Game::map_patchQuads_t*)malloc(sizeof(*pQuad));
+
+ if (pQuad)
+ {
+ memset(pQuad, 0, sizeof(Game::map_patchQuads_t));
+ return pQuad;
+ }
+
+ Game::Com_Error(0, "Alloc_PatchQuad :: alloc failed!");
+ return 0;
+ }
+
+ // coord to check = xy; bounds = xywh
+ bool IsCoordWithinBoundsXY(float *coordToCheck, float *bounds)
+ {
+ // if pt to right of left border
+ if (coordToCheck[0] >= bounds[0]) // bounds x
+ {
+ // if pt to left of right border
+ if (coordToCheck[0] <= (bounds[0] + bounds[2])) // bounds x + w
+ {
+ // if pt above bottom border
+ if (coordToCheck[1] >= bounds[1]) // bounds y
+ {
+ // if pt below top border
+ if (coordToCheck[1] <= (bounds[1] + bounds[3])) // bounds y + h
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ // quad in map format order (tiangles need to share an edge or it will fail)
+ bool PatchQuad_SingleFromTris(Game::map_patchQuads_t *pQuad, const Game::map_patchTris_t *pTri1, const Game::map_patchTris_t *pTri2, const bool checkSkewness = false)
+ {
+ // temp quad points
+ std::vectorcoord(3, 0.0f);
+ std::vector> t_quadPts(4, coord);
+
+ int uniquePtCount = 0;
+
+ // for each coord of triangle 1
+ for (auto crdTri1 = 0; crdTri1 < 3; crdTri1++)
+ {
+ int sharedPtCount = 0;
+
+ // for each coord of triangle 2
+ for (auto crdTri2 = 0; crdTri2 < 3; crdTri2++)
+ {
+ // check if the current coord from tri 1 is unique and not part of tri 2
+ if (Utils::vector::_VectorCompare(pTri1->coords[crdTri1].xyz, pTri2->coords[crdTri2].xyz))
+ {
+ // found shared point
+ sharedPtCount++;
+ break;
+ }
+ }
+
+ // check if we found the unique coord of triangle 1
+ if (!sharedPtCount)
+ {
+ // add unique point that isnt on the shared edge
+ t_quadPts[0][0] = pTri1->coords[crdTri1].xyz[0];
+ t_quadPts[0][1] = pTri1->coords[crdTri1].xyz[1];
+ t_quadPts[0][2] = pTri1->coords[crdTri1].xyz[2];
+
+ uniquePtCount++;
+ break;
+ }
+ }
+
+ // if we found the unique point
+ if (uniquePtCount)
+ {
+ // we should only have 1 point from the first tiangle in our list, otherwise, throw an error?
+ if (uniquePtCount > 1)
+ {
+ Game::Com_PrintMessage(0, "^1[MAP-EXPORT]: ^7 PatchQuad_SingleFromTris t_quadPts.size() > 1", 0);
+ return false;
+ }
+
+ for (auto secondTriCoords = 0; secondTriCoords < 3; secondTriCoords++)
+ {
+ // add point that isnt on the shared edge
+ t_quadPts[secondTriCoords + 1][0] = pTri2->coords[secondTriCoords].xyz[0];
+ t_quadPts[secondTriCoords + 1][1] = pTri2->coords[secondTriCoords].xyz[1];
+ t_quadPts[secondTriCoords + 1][2] = pTri2->coords[secondTriCoords].xyz[2];
+ }
+
+ // unpack the normal .. might be needed for texture info later down the line
+ Game::vec3_t normal;
+ Utils::vector::_Vec3UnpackUnitVec(pTri2->coords[1].normal, normal);
+
+ // sort x accending
+ std::sort(t_quadPts.begin(), t_quadPts.end());
+
+ // x was sorted, now sort by y
+ std::sort(t_quadPts.begin(), t_quadPts.end());
+
+ // always check clockwise ordering?
+ float pt0[3], pt1[3], pt2[3], pt3[3];
+
+ // why did i do that
+ memcpy(&pt0, &t_quadPts[0][0], sizeof(float[3]));
+ memcpy(&pt1, &t_quadPts[1][0], sizeof(float[3]));
+ memcpy(&pt2, &t_quadPts[2][0], sizeof(float[3]));
+ memcpy(&pt3, &t_quadPts[3][0], sizeof(float[3]));
+
+ // cross :: triangle clockwise (k > 0.001f) | counter clockwise (k < 0.001f)
+ float k = (pt1[1] - pt0[1]) * (pt2[0] - pt1[0]) - (pt1[0] - pt0[0]) * (pt2[1] - pt1[1]);
+
+ if (k < 0.001f)
+ {
+ // triangle is counter clockwise, discard
+ return false;
+ }
+
+ if (checkSkewness)
+ {
+ /*Game::vec3_t dirVec1, dirVec2;
+ Utils::vector::_VectorZero(dirVec1); Utils::vector::_VectorZero(dirVec2);
+
+ Utils::vector::_VectorSubtract(pt0, pt1, dirVec1);
+ Utils::vector::_VectorSubtract(pt2, pt1, dirVec2);
+
+ Utils::vector::_VectorNormalize(dirVec1);
+ Utils::vector::_VectorNormalize(dirVec2);
+
+ float dot = Utils::vector::_DotProduct(dirVec1, dirVec2);
+ float angle = acosf(dot) * 180.0f / 3.141592f;*/
+
+ //if(angle > 70.0f || angle < 10.0f )
+ // return false;
+
+ // triangle can be in clockwise order and still be using a point of a neighboring triangle
+ // project a quad from the 3 coords >> skip the triangle if the 4th point is not within the bounds
+
+ // horizontal check from left to right
+ float ptToCheck[2] =
+ { pt3[0], pt3[1] };
+
+ float bounds[4] =
+ {
+ /*x*/ pt0[0],
+ /*y*/ pt0[1],
+ /*w*/ fabs(pt2[0] - pt0[0]) + export_quadEpsilon,
+ /*h*/ fabs(pt1[1] - pt0[1]) + export_quadEpsilon
+ };
+
+ // discard point if not within bounds
+ if (!IsCoordWithinBoundsXY(ptToCheck, bounds))
+ {
+ return false;
+ }
+
+ // vertical check from top to bottom
+ float ptToCheck2[2] =
+ { pt2[0], pt2[1] };
+
+ float bounds2[4] =
+ {
+ /*x*/ pt0[0],
+ /*y*/ pt0[1],
+ /*w*/ fabs(pt3[0] - pt0[0]) + export_quadEpsilon,
+ /*h*/ fabs(pt1[1] - pt0[1]) + export_quadEpsilon
+ };
+
+ // discard point if not within bounds
+ if (!IsCoordWithinBoundsXY(ptToCheck2, bounds2))
+ {
+ return false;
+ }
+ }
+
+ // build the quad from the 4 sorted coordinates
+ for (auto quadCrd = 0; quadCrd < 4; quadCrd++)
+ {
+ for (auto quadCrdIdx = 0; quadCrdIdx < 3; quadCrdIdx++)
+ {
+ pQuad->coords[quadCrd][quadCrdIdx] = t_quadPts[quadCrd][quadCrdIdx];
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ // handle filters
+ void CM_BrushFilters(int &filter_BrushAmount, bool &filter_BrushIndex, bool &filter_BrushSorting, bool &filter_BrushSelection)
+ {
+ int cmTotalBrushAmount = static_cast(Game::cm->numBrushes);
+
+ // brush bounding box filter
+ if (Dvars::mapexport_selectionMode && Dvars::mapexport_selectionMode->current.integer != 0)
+ {
+ if(Dvars::r_drawCollision_brushAmount && Dvars::r_drawCollision_brushAmount->current.integer != cmTotalBrushAmount)
+ Game::Dvar_SetValue(Dvars::r_drawCollision_brushAmount, cmTotalBrushAmount);
+
+ if (Dvars::r_drawCollision_brushSorting && Dvars::r_drawCollision_brushSorting->current.integer != 0)
+ Game::Dvar_SetValue(Dvars::r_drawCollision_brushSorting, 0);
+
+ if (Dvars::r_drawCollision_brushIndexFilter && (std::string)Dvars::r_drawCollision_brushIndexFilter->current.string != "null")
+ Game::Dvar_SetString("null", Dvars::r_drawCollision_brushIndexFilter);
+
+ filter_BrushAmount = cmTotalBrushAmount;
+ filter_BrushSelection = true;
+
+ return;
+ }
+
+ // brush index filter & none of the above
+ else if(Dvars::r_drawCollision_brushIndexFilter && (std::string)Dvars::r_drawCollision_brushIndexFilter->current.string != "null")
+ {
+ if (Dvars::r_drawCollision_brushAmount && Dvars::r_drawCollision_brushAmount->current.integer != cmTotalBrushAmount)
+ Game::Dvar_SetValue(Dvars::r_drawCollision_brushAmount, cmTotalBrushAmount);
+
+ if (Dvars::r_drawCollision_brushSorting && Dvars::r_drawCollision_brushSorting->current.integer != 0)
+ Game::Dvar_SetValue(Dvars::r_drawCollision_brushSorting, 0);
+
+ filter_BrushAmount = cmTotalBrushAmount;
+ filter_BrushIndex = true;
+
+ return;
+ }
+
+ // brush sorting & none of the above
+ else if(Dvars::r_drawCollision_brushSorting && Dvars::r_drawCollision_brushSorting->current.integer != 0)
+ {
+ filter_BrushSorting = true;
+ }
+
+ // brush amount filter & none of the above
+ if (Dvars::r_drawCollision_brushAmount && Dvars::r_drawCollision_brushAmount->current.integer != 0 && Dvars::r_drawCollision_brushAmount->current.integer <= cmTotalBrushAmount)
+ {
+ filter_BrushAmount = Dvars::r_drawCollision_brushAmount->current.integer;
+ return;
+ }
+ else
+ {
+ if (Dvars::r_drawCollision_brushAmount && Dvars::r_drawCollision_brushAmount->current.integer != cmTotalBrushAmount)
+ Game::Dvar_SetValue(Dvars::r_drawCollision_brushAmount, cmTotalBrushAmount);
+
+ filter_BrushAmount = cmTotalBrushAmount;
+ }
+ }
+
+ // handle brush dvar commands
+ void CM_BrushCommands()
+ {
+ // cmd :: print material list to console
+ if (Dvars::r_drawCollision_materialList && Dvars::r_drawCollision_materialList->current.enabled)
+ {
+ Game::Cmd_ExecuteSingleCommand(0, 0, "clear\n");
+
+ // add spaces to the console so we can scroll the mini console
+ Game::Com_PrintMessage(0, Utils::VA(" \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n"), 0);
+
+ // reset the dvar and print our material list
+ Game::Dvar_SetValue(Dvars::r_drawCollision_materialList, false);
+ Game::Com_PrintMessage(0, Utils::VA("%s\n", /*g_dvarMaterialList_str.c_str()*/ Game::Globals::r_drawCollision_materialList_string.c_str()), 0);
+ }
+ }
+
+ // create a bounding box to select things that should be exported
+ void CM_BrushSelectionBox(bool isActive)
+ {
+ if (isActive)
+ {
+ float selectionBoxColor[4] = { 0.0f, 1.0f, 0.0f, 0.6f };
+ export_selectionBox.wasReset = false;
+
+ if (!export_selectionBox.isBoxValid)
+ {
+ // we need atleast 2 valid points to define the selection box
+ if (export_selectionBox.numPoints < 2)
+ {
+ if(export_selectionAdd)
+ {
+ export_selectionBox.points[export_selectionBox.numPoints] = Game::Globals::locPmove_playerOrigin;
+ export_selectionBox.numPoints++;
+
+ export_selectionAdd = false;
+ }
+
+ // use the players origin as a temporary second point till a second point was defined
+ if (export_selectionBox.numPoints == 1)
+ {
+ export_selectionBox.points[1] = Game::Globals::locPmove_playerOrigin;
+
+ CM_BoundsFromPoints(2, export_selectionBox.points, export_selectionBox.mins, export_selectionBox.maxs);
+ glm::setFloat3(export_selectionBox.box.mins, export_selectionBox.mins);
+ glm::setFloat3(export_selectionBox.box.maxs, export_selectionBox.maxs);
+
+ CM_ShowSingleBrushCollision(&export_selectionBox.box, selectionBoxColor, 0, false);
+ }
+ }
+ else
+ {
+ // calculate mins and maxs for our selection box once
+ if (export_selectionBox.numPoints == 2 && !export_selectionBox.isBoxValid)
+ {
+ CM_BoundsFromPoints(export_selectionBox.numPoints, export_selectionBox.points, export_selectionBox.mins, export_selectionBox.maxs);
+ glm::setFloat3(export_selectionBox.box.mins, export_selectionBox.mins);
+ glm::setFloat3(export_selectionBox.box.maxs, export_selectionBox.maxs);
+
+ export_selectionBox.isBoxValid = true;
+ }
+ }
+ }
+ else
+ {
+ CM_ShowSingleBrushCollision(&export_selectionBox.box, selectionBoxColor, 0, false);
+ }
+ }
+ else
+ {
+ if (!export_selectionBox.wasReset)
+ {
+ CM_ResetSelectionBox(&export_selectionBox);
+ }
+ }
+ }
+
+ // main logic for brush drawing
+ void CM_ShowBrushCollision(Game::GfxViewParms *viewParms, Game::cplane_s *frustumPlanes, int frustumPlaneCount)
+ {
+ int debugPrints, colorCounter = 0, lastDrawnBrushAmount = 0, filter_BrushAmount = 0;
+ bool filter_BrushIndex = false, filter_BrushSorting = false, filter_ExportSelection = false;
+
+ float colorFloat[4];
+ Game::cbrush_t *brush;
+
+ if (!frustumPlanes)
+ {
+ Game::Com_Error(0, Utils::VA("CM_ShowBrushCollision L#%d :: frustumPlanes \n", __LINE__));
+ return;
+ }
+
+ // One time init per map when r_drawcollison was enabled
+ CM_OnceOnInit();
+
+ // Handle filter dvars
+ CM_BrushFilters(filter_BrushAmount, filter_BrushIndex, filter_BrushSorting, filter_ExportSelection);
+
+ // Handle commands
+ CM_BrushCommands();
+
+ // *
+ // Map Export
+
+ export_inProgress = false; // reset after exporting
+ export_currentBrushIndex = 0; // brush index for .map file brushes
+
+ // cmd :: export current map
+ if (export_mapExportCmd)
+ {
+ // reset the the command bool
+ export_mapExportCmd = false;
+
+ // let our code know that we are about to export a map
+ export_inProgress = true;
+
+ // map file name
+ std::string mapName = Game::cm->name;
+ Utils::replaceAll(mapName, std::string("maps/mp/"), std::string(""));
+
+ // if sp map
+ Utils::replaceAll(mapName, std::string("maps/"), std::string(""));
+
+ Utils::replaceAll(mapName, std::string(".d3dbsp"), std::string(".map"));
+
+ // export to root/map_export
+ std::string basePath = Game::Dvar_FindVar("fs_basepath")->current.string;
+ basePath += "\\iw3xo\\map_export\\";
+
+ std::string filePath = basePath + mapName;
+
+ Game::Com_PrintMessage(0, "\n------------------------------------------------------\n", 0);
+ export_time_exportStart = Utils::Clock_StartTimerPrint(Utils::VA("[MAP-EXPORT]: Starting to export %s to %s ...\n", Game::cm->name, filePath.c_str()));
+
+ // create directory root/map_export if it doesnt exist
+ // client is only able to export to a sub-directory of "menu_export"
+
+ if (std::filesystem::create_directories(basePath))
+ {
+ Game::Com_PrintMessage(0, "|- Created directory \"root/iw3xo/map_export\"\n", 0);
+ }
+
+ // steam to .map file
+ export_mapFile.open(filePath.c_str());
+ //export_mapFile_addon.open(filePath + ".ents"s);
+
+ // build entity list
+ char* mapents_ptr = Game::cm->mapEnts->entityString;
+
+ if (_Map::mpsp_is_sp_map)
+ {
+ mapents_ptr = _Map::mpsp_mapents_original;
+ }
+
+ export_mapEntities = Utils::Entities(mapents_ptr);
+ Game::Com_PrintMessage(0, "|- Writing header and world entity ...\n\n", 0);
+
+ // write header
+ export_mapFile << "iwmap 4\n"
+ "\"000_Global\" flags expanded active\n"
+ "\"000_Global/Brushes\" flags\n"
+ "\"000_Global/SingleQuads\" flags\n"
+ "\"000_Global/Triangles\" flags\n"
+ "\"000_Global/Models\" flags\n"
+ "\"The Map\" flags" << std::endl; // header
+
+ // write worldspawn
+ export_mapFile << "// entity 0\n"
+ "{\n"
+ + export_mapEntities.buildWorldspawnKeys();
+
+ // Use debug collision methods to create our brushes ...
+ export_time_brushGenerationStart = Utils::Clock_StartTimerPrint("[MAP-EXPORT]: Creating brushes ...\n");
+ }
+
+ // Handle box selection
+ CM_BrushSelectionBox(filter_ExportSelection);
+
+ // do not draw brushes when using the selection box
+ if (!export_inProgress && filter_ExportSelection)
+ {
+ return;
+ }
+
+ // --------
+
+ bool brushIndexVisible = Dvars::r_drawCollision_brushIndexVisible && Dvars::r_drawCollision_brushIndexVisible->current.enabled;
+
+ if (filter_BrushSorting)
+ {
+ // sort brushes far to near
+ if (Dvars::r_drawCollision_brushSorting->current.integer == 1)
+ {
+ CM_SortBrushListOnDistanceCamera();
+ }
+ // sort brushes near to far
+ else
+ {
+ CM_SortBrushListOnDistanceCamera(false);
+ }
+
+ // disable sorting if sorted brushList is empty .. should not happen
+ if (g_mapBrushList.empty())
+ {
+ Game::Dvar_SetValue(Dvars::r_drawCollision_brushSorting, 0);
+ filter_BrushSorting = false;
+ }
+ }
+
+ // clear drawn brushes vector
+ g_mapBrushListForIndexFiltering.clear();
+
+ // reset hud element
+ Game::Globals::dbgColl_drawnPlanesAmountTemp = 0;
+
+ int BRUSH_INDEX, BRUSH_COUNT;
+ std::vector Integers;
+
+ // brush index filtering
+ if (filter_BrushIndex)
+ {
+ Utils::extractIntegerWords(Dvars::r_drawCollision_brushIndexFilter->current.string, Integers, true);
+ BRUSH_COUNT = static_cast(Integers.size());
+ }
+
+ // sorted / unsorted
+ else
+ {
+ BRUSH_COUNT = Game::cm->numBrushes;
+ }
+
+ for (BRUSH_INDEX = 0; BRUSH_INDEX < BRUSH_COUNT; ++BRUSH_INDEX)
+ {
+ int brushIndex;
+
+ // break after we drew the amount of brushes we set (works best with brushSorting)
+ if (lastDrawnBrushAmount >= filter_BrushAmount)
+ {
+ break;
+ }
+
+ // brush index filtering
+ if (filter_BrushIndex)
+ {
+ brushIndex = Integers[BRUSH_INDEX];
+
+ // check if the index is within bounds
+ if (brushIndex < 0 || brushIndex >= Game::cm->numBrushes)
+ {
+ // find and remove the index
+ Integers.erase(std::remove(Integers.begin(), Integers.end(), brushIndex), Integers.end());
+
+ // vector to string
+ std::string vecToString = "";
+ for (int num = 0; num < (int)Integers.size(); num++)
+ {
+ vecToString += std::to_string(Integers[num]) + " ";
+ }
+
+ Game::Dvar_SetString(vecToString.c_str(), Dvars::r_drawCollision_brushIndexFilter);
+ Game::Com_PrintMessage(0, Utils::VA("^1-> r_drawCollision_brushIndexFilter ^7:: found and removed index: %d \n", brushIndex), 0);
+
+ return;
+ }
+ }
+
+ // sorted / unsorted
+ else
+ {
+ brushIndex = BRUSH_INDEX;
+ }
+
+ if (filter_BrushSorting)
+ {
+ brush = g_mapBrushList[brushIndex];
+ }
+ else
+ {
+ brush = &Game::cm->brushes[brushIndex];
+ }
+
+ // if brush is part of a submodel, translate brushmodel bounds by the submodel origin
+ if (brush->isSubmodel)
+ {
+ Game::cbrush_t dupe = Game::cbrush_t();
+ memcpy(&dupe, brush, sizeof(Game::cbrush_t));
+
+ Utils::vector::_VectorAdd(g_mapBrushModelList[dupe.cmSubmodelIndex].cmSubmodelOrigin, dupe.mins, dupe.mins);
+ Utils::vector::_VectorAdd(g_mapBrushModelList[dupe.cmSubmodelIndex].cmSubmodelOrigin, dupe.maxs, dupe.maxs);
+
+ brush = &dupe;
+ }
+
+ // when not exporting a map
+ if (!export_inProgress)
+ {
+ // always cull if not exporting
+ if (!CM_BrushInView(brush, frustumPlanes, frustumPlaneCount)) {
+ continue;
+ }
+
+ // disable material filter when using index filter
+ if (!filter_BrushIndex)
+ {
+ // check if its a material we selected otherwise
+ if (!CM_ValidBrushMaterialSelection(brush, Dvars::r_drawCollision_material->current.integer)) {
+ continue;
+ }
+ }
+ }
+
+ // on exporting
+ else
+ {
+ // exclude current brush if its not part of the selection box (when using selectionMode)
+ if (filter_ExportSelection && !CM_IsBrushWithinSelectionBox(brush, &export_selectionBox)) {
+ continue;
+ }
+
+ // skip material check if using index filtering or selectionMode
+ if (!filter_BrushIndex && !filter_ExportSelection)
+ {
+ // check if its a material we selected otherwise
+ if (!CM_ValidBrushMaterialSelection(brush, Dvars::r_drawCollision_material->current.integer)) {
+ continue;
+ }
+ }
+ }
+
+ // always use the brush index within clipmap->brushes to define its color
+ CM_GetShowCollisionColor(colorFloat, brush->colorCounter);
+
+ // draw the current brush
+ CM_ShowSingleBrushCollision(brush, colorFloat, brushIndex);
+
+ if (brushIndexVisible)
+ {
+ g_mapBrushListForIndexFiltering.push_back(brush);
+ }
+
+ lastDrawnBrushAmount++;
+ }
+
+ // *
+ // draw brush indices as 3D text (only when: unsorted brushes / index filtering)
+
+ if (brushIndexVisible && !g_mapBrushListForIndexFiltering.empty())
+ {
+ // debug strings are normally handled within G_RunFrame (server running @ 20fps) so we have to skip drawing them -> ((renderfps / sv_fps)) times
+
+ // if new loop allowed
+ //if (!SvFramerateToRendertime_Counter)
+ //{
+ // SvFramerateToRendertime_CurrentDelta = (1000 / Game::Globals::pmlFrameTime) / Game::Dvar_FindVar("sv_fps")->current.integer;
+ // SvFramerateToRendertime_CurrentDelta = static_cast(floor(SvFramerateToRendertime_CurrentDelta));
+ //}
+
+ //// increase counter
+ //SvFramerateToRendertime_Counter++;
+
+ //// if we reached the delta, we can draw strings again
+ //if (SvFramerateToRendertime_Counter >= SvFramerateToRendertime_CurrentDelta)
+ //{
+ // // reset counter
+ // SvFramerateToRendertime_Counter = 0;
+
+ // sort brushes from near to far
+ CM_SortBrushListOnDistanceCamera(false, true, true);
+
+ // should not happen
+ if (g_mapBrushListForIndexFiltering.empty())
+ {
+ Game::Dvar_SetValue(Dvars::r_drawCollision_brushIndexVisible, false);
+ Game::Com_PrintMessage(0, Utils::VA("^1CM_ShowBrushCollision L#%d ^7:: GlobalBrushList was empty. Disabled r_drawCollision_brushIndexVisible! \n", __LINE__), 0);
+ }
+
+ // maximum amount of brush indices to draw
+ int debugPrintsMax = 64;
+
+ // only draw as many indices as the map has brushes
+ if (static_cast(g_mapBrushListForIndexFiltering.size()) < debugPrintsMax)
+ {
+ debugPrintsMax = g_mapBrushListForIndexFiltering.size();
+ }
+
+ // draw strings near - far
+ for (debugPrints = 0; debugPrints < debugPrintsMax; ++debugPrints)
+ {
+ // get distance-sorted brush
+ brush = g_mapBrushListForIndexFiltering[debugPrints];
+
+ // get midpoint of brush bounds (xyz)
+ glm::vec3 printOrigin = CM_GetBrushMidpoint(brush, true);
+
+ // draw original brush index in the middle of the collision poly
+ CM_ShowBrushIndexNumbers(viewParms, brush->cmBrushIndex, printOrigin, debugPrints, debugPrintsMax);
+ }
+ //}
+ }
+ else
+ {
+ // if not drawing debug strings
+ SvFramerateToRendertime_Counter = 0;
+ }
+
+ // *
+ // Map Export
+
+ if (export_inProgress)
+ {
+ const char* brush_str = Utils::VA("|- Building (%d) brushes took", export_currentBrushIndex);
+ std::string timefmt = " took (%.4f)\n\n";
+ timefmt = brush_str + timefmt;
+
+ Utils::Clock_EndTimerPrintSeconds(export_time_brushGenerationStart, timefmt.c_str());
+
+ // *
+ // Create Tris
+
+ std::vector singleTriangles;
+ bool export_needsTriangles = false;
+
+ // only generate triangles if exporting triangles/quads or both
+ if (Dvars::mapexport_writeQuads->current.enabled || Dvars::mapexport_writeTriangles->current.enabled)
+ {
+ export_needsTriangles = true;
+
+ auto export_time_createTrisStart = Utils::Clock_StartTimerPrint("[MAP-EXPORT]: Building triangles ...\n");
+
+ for (auto triNum = 0; triNum < static_cast(Game::cm->triCount); triNum++)
+ {
+ // get incides that define the triangle
+ unsigned short triIncides1[3] =
+ {
+ Game::cm->triIndices[triNum * 3 + 0],
+ Game::cm->triIndices[triNum * 3 + 1],
+ Game::cm->triIndices[triNum * 3 + 2]
+ };
+
+ Game::map_patchTris_t *pTris = Alloc_PatchTriangle();
+ pTris->triIndex = triNum;
+
+ PatchTriangle_FromIncides(pTris, triIncides1);
+
+ if (filter_ExportSelection)
+ {
+ if (CM_IsTriangleWithinSelectionBox(pTris, &export_selectionBox))
+ {
+ singleTriangles.push_back(pTris);
+ }
+ }
+ else
+ {
+ singleTriangles.push_back(pTris);
+ }
+ }
+
+ Utils::Clock_EndTimerPrintSeconds(export_time_createTrisStart, "|- Building triangles took (%.4f) seconds!\n");
+ Game::Com_PrintMessage(0, Utils::VA("|- Initial amount of triangles = (%d)\n\n", static_cast(singleTriangles.size())), 0);
+ }
+
+ // *
+ // Merge Tris to Quads
+
+ // fix me daddy
+
+ if (Dvars::mapexport_writeQuads->current.enabled)
+ {
+ auto export_time_createQuadsStart = Utils::Clock_StartTimerPrint("[MAP-EXPORT]: Merging triangles to quads ...\n");
+
+ // create a list of merged patch triangles (quads)
+ std::vector singleQuads;
+
+ // reset quad epsilon
+ export_quadEpsilon = 0.0f;
+
+ // count the amount of iterations till we are no longer able to merge any triangles to quads
+ int quadFromTrisLoopCount = 0;
+
+ // triangle index offset when we are no longer able to merge triangles (eg. if triangle 1-4 and 2-3 share an edge)
+ int triNumOffset = 0;
+
+ // amount of "failed" iterations with triangle index offset included
+ int mergeIterationFailCount = 0;
+
+ // defines how many triangles we look ahead from the current triangle to check for a shared edge
+ int triFowardOffset = 1;
+
+ // try to find as many "easy" quads as possible till we no longer merge triangles
+ bool easyQuadsFirst = true;
+
+ // build single quads from triangles; iterate for as long as we have enough triangles & if we actually decreased the amount of triangles after merging
+ for (quadFromTrisLoopCount = 0; static_cast(singleTriangles.size()) > 1; quadFromTrisLoopCount++)
+ {
+ // get amount of triangles before merging
+ int startTris = (int)singleTriangles.size();
+
+ // merge triangles if they share an edge to create quads ... we will decrease the size of singlePatchTriangles, so do this for x amount of times ^
+ for (auto triNum = 0 + triNumOffset; triNum < (int)singleTriangles.size(); triNum++)
+ {
+ // if we have atleast 2 triangles to work with
+ if (triNum + triFowardOffset < (int)singleTriangles.size())
+ {
+ if (PatchTriangle_SharesEdge(singleTriangles[triNum], singleTriangles[triNum + triFowardOffset]))
+ {
+ Game::map_patchQuads_t *pQuad = Alloc_PatchQuad();
+ pQuad->quadIndex = triNum; // for debugging :)
+
+ // try to merge both triangles to create a quad
+ if (PatchQuad_SingleFromTris(pQuad, singleTriangles[triNum], singleTriangles[triNum + triFowardOffset], easyQuadsFirst))
+ {
+ singleQuads.push_back(pQuad);
+
+ // free triangles and erase them from singlePatchTriangles
+ free(singleTriangles[triNum]);
+ free(singleTriangles[triNum + triFowardOffset]);
+
+ singleTriangles.erase(singleTriangles.begin() + triNum + triFowardOffset);
+ singleTriangles.erase(singleTriangles.begin() + triNum);
+
+ // decrease triNum by one
+ if (triNum - 1 > 0)
+ {
+ triNum--;
+ }
+ }
+ }
+ }
+ }
+
+ // check if we merged any triangles in this iteration
+ // stop trying to merge triangles if we didnt decrease the amount of triangles
+ if (startTris - (int)singleTriangles.size() == 0)
+ {
+ // catch :: tri 1-4 / 2-3 => offset triangle index on first fail so we catch 2-3
+ triNumOffset = 1;
+
+ // still no merge? catch :: tri 1-3 / 2-4 => reset triNumOffset and add 1 to triFowardOffset
+ if (mergeIterationFailCount == 1) {
+ triNumOffset = 0;
+ triFowardOffset = 2;
+ }
+
+ // reset triFowardOffset after we swizzeled the triangles once
+ if (mergeIterationFailCount == 2) {
+ triFowardOffset = 1;
+ }
+
+ // still unable to merge? .. (following doesnt seem to help)
+ if (mergeIterationFailCount == 3) {
+ triFowardOffset = 3;
+ }
+
+ if (mergeIterationFailCount == 4) {
+ triFowardOffset = 4;
+ }
+
+ if (mergeIterationFailCount == 5) {
+ triFowardOffset = 1;
+ }
+
+ // start increasing the bounding box that has to encapsule the 4th coordinate
+ if (mergeIterationFailCount >= 6 && mergeIterationFailCount <= 13) {
+ export_quadEpsilon += 5.0f;
+ }
+
+ // now try to merge skewed quads (might still be valid ones)
+ if (mergeIterationFailCount == 14) {
+ easyQuadsFirst = false;
+ }
+
+ mergeIterationFailCount++;
+
+ if (mergeIterationFailCount > 16) {
+ break;
+ }
+ }
+ }
+
+ Utils::Clock_EndTimerPrintSeconds(export_time_createQuadsStart, "|- Merging triangles to quads took (%.4f) seconds!\n");
+ Game::Com_PrintMessage(0, Utils::VA("|- (%d) triangles couldn't be be merged.\n", (int)singleTriangles.size()), 0);
+ Game::Com_PrintMessage(0, Utils::VA("|- (%d) quads after (%d) iterations.\n", (int)singleQuads.size(), quadFromTrisLoopCount), 0);
+
+ // *
+ // Write Quad Patches
+
+ // coord x y z .... t .... (tc.x * 1024) (tc.y * 1024) (st.x * 1024 + 2) -(st.y * 1024 + 2)
+ // v -108 -102 128 t -1728 5012.7217 -6.75 6.375
+
+ for (auto quadNum = 0; quadNum < (int)singleQuads.size(); quadNum++)
+ {
+ // start patch
+ export_mapFile << Utils::VA("// brush %d", export_currentBrushIndex) << std::endl;
+
+ // global brush exporting index count
+ export_currentBrushIndex++;
+
+ export_mapFile << " {" << std::endl;
+ export_mapFile << " mesh" << std::endl;
+ export_mapFile << " {" << std::endl;
+ export_mapFile << " layer \"000_Global/SingleQuads\"" << std::endl;
+ export_mapFile << " toolFlags splitGeo;" << std::endl;
+ export_mapFile << " caulk" << std::endl;
+ export_mapFile << " lightmap_gray" << std::endl;
+ export_mapFile << " 2 2 16 8" << std::endl;
+
+ export_mapFile << " (" << std::endl;
+ export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleQuads[quadNum]->coords[0][0], singleQuads[quadNum]->coords[0][1], singleQuads[quadNum]->coords[0][2]) << std::endl;
+ export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleQuads[quadNum]->coords[1][0], singleQuads[quadNum]->coords[1][1], singleQuads[quadNum]->coords[1][2]) << std::endl;
+ export_mapFile << " )" << std::endl;
+ export_mapFile << " (" << std::endl;
+ export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleQuads[quadNum]->coords[2][0], singleQuads[quadNum]->coords[2][1], singleQuads[quadNum]->coords[2][2]) << std::endl;
+ export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleQuads[quadNum]->coords[3][0], singleQuads[quadNum]->coords[3][1], singleQuads[quadNum]->coords[3][2]) << std::endl;
+ export_mapFile << " )" << std::endl;
+ export_mapFile << " }" << std::endl;
+ export_mapFile << " }" << std::endl;
+ }
+
+ // free quads
+ for (auto quad = 0; quad < (int)singleQuads.size(); quad++)
+ {
+ free(singleQuads[quad]);
+ }
+
+ Game::Com_PrintMessage(0, "|- Wrote quads to layer \"000_Global/SingleQuads\"\n", 0);
+ }
+
+ // *
+ // Write Single Triangles
+
+ if (Dvars::mapexport_writeTriangles->current.enabled)
+ {
+ for (auto tri = 0; tri < (int)singleTriangles.size(); tri++)
+ {
+ // start patch
+ export_mapFile << Utils::VA("// brush %d", export_currentBrushIndex) << std::endl;
+
+ // global brush exporting index count
+ export_currentBrushIndex++;
+
+ export_mapFile << " {" << std::endl;
+ export_mapFile << " mesh" << std::endl;
+ export_mapFile << " {" << std::endl;
+ export_mapFile << " layer \"000_Global/Triangles\"" << std::endl;
+ export_mapFile << " contents nonColliding;" << std::endl;
+ export_mapFile << " toolFlags splitGeo;" << std::endl;
+ export_mapFile << " caulk" << std::endl;
+ export_mapFile << " lightmap_gray" << std::endl;
+ export_mapFile << " 2 2 16 8" << std::endl;
+
+ export_mapFile << " (" << std::endl;
+ export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleTriangles[tri]->coords[2].xyz[0], singleTriangles[tri]->coords[2].xyz[1], singleTriangles[tri]->coords[2].xyz[2]) << std::endl;
+ export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleTriangles[tri]->coords[0].xyz[0], singleTriangles[tri]->coords[0].xyz[1], singleTriangles[tri]->coords[0].xyz[2]) << std::endl;
+ export_mapFile << " )" << std::endl;
+ export_mapFile << " (" << std::endl;
+ export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleTriangles[tri]->coords[1].xyz[0], singleTriangles[tri]->coords[1].xyz[1], singleTriangles[tri]->coords[1].xyz[2]) << std::endl;
+ export_mapFile << Utils::VA(" v %.1f %.1f %.1f t 0 0 0", singleTriangles[tri]->coords[1].xyz[0], singleTriangles[tri]->coords[1].xyz[1], singleTriangles[tri]->coords[1].xyz[2]) << std::endl;
+ export_mapFile << " )" << std::endl;
+ export_mapFile << " }" << std::endl;
+ export_mapFile << " }" << std::endl;
+ }
+
+ Game::Com_PrintMessage(0, "|- Wrote triangles to layer \"000_Global/Triangles\"\n\n", 0);
+ }
+
+ // free triangles
+ if (export_needsTriangles)
+ {
+ for (auto tri = 0; tri < (int)singleTriangles.size(); tri++)
+ {
+ free(singleTriangles[tri]);
+ }
+ }
+
+ // close the worldspawn entity with all its brushes
+ export_mapFile << "}" << std::endl;
+ Game::Com_PrintMessage(0, "|- Finished writing the world entity\n\n", 0);
+
+ // *
+ // Map Entities + Submodels/Brushmodels + Reflection Probes
+
+ if (Dvars::mapexport_writeEntities->current.enabled)
+ {
+ Game::Com_PrintMessage(0, "[MAP-EXPORT]: Building entities ...\n", 0);
+
+ // already exported the worldspawn entity, so delete it from the list
+ export_mapEntities.deleteWorldspawn();
+
+ // *
+ // map entities and brushmodels
+
+ if (filter_ExportSelection) // only exporting entities within the selection box
+ {
+ export_mapFile << export_mapEntities.buildSelection_FixBrushmodels(&export_selectionBox, g_mapBrushModelList);
+ }
+ else // build all other entities and fix up brushmodels
+ {
+ export_mapFile << export_mapEntities.buildAll_FixBrushmodels(g_mapBrushModelList);
+ }
+
+ //export_mapFile_addon << export_mapEntities.buildAll_script_structs();
+
+ // *
+ // reflection probes (always skip the first one (not defined within the map file))
+
+ int exportedProbes = 0;
+
+ for (auto probe = 1; probe < (int)Game::_gfxWorld->reflectionProbeCount; probe++)
+ {
+ if (filter_ExportSelection &&
+ !Utils::polylib::PointWithinBounds(glm::vec3(Game::_gfxWorld->reflectionProbes[probe].origin[0],
+ Game::_gfxWorld->reflectionProbes[probe].origin[1],
+ Game::_gfxWorld->reflectionProbes[probe].origin[2]),
+ export_selectionBox.mins, export_selectionBox.maxs, 0.25f))
+ {
+ // skip probe if not in selection box
+ continue;
+ }
+ else
+ {
+ export_mapFile << Utils::VA("// reflection probe %d", exportedProbes) << std::endl;
+ export_mapFile << "{" << std::endl;
+ export_mapFile << "\"angles\" \"0 0 0\"" << std::endl;
+ export_mapFile << Utils::VA("\"origin\" \"%.1f %.1f %.1f\"", Game::_gfxWorld->reflectionProbes[probe].origin[0], Game::_gfxWorld->reflectionProbes[probe].origin[1], Game::_gfxWorld->reflectionProbes[probe].origin[2]) << std::endl;
+ export_mapFile << "\"classname\" \"reflection_probe\"" << std::endl;
+ export_mapFile << "}" << std::endl;
+
+ exportedProbes++;
+ }
+ }
+
+ Game::Com_PrintMessage(0, Utils::VA("|- (%d) reflection probes.\n", exportedProbes), 0);
+ }
+
+ // *
+ // Static Models
+
+ if (Dvars::mapexport_writeModels->current.enabled)
+ {
+ auto map_mapModelsStart = Utils::Clock_StartTimer();
+ int exportedSModels = 0;
+
+ for (auto sModel = 0u; sModel < Game::_gfxWorld->dpvs.smodelCount; sModel++)
+ {
+ // only export static models within the selection box
+ if (filter_ExportSelection &&
+ !Utils::polylib::PointWithinBounds(glm::vec3(Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.origin[0],
+ Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.origin[1],
+ Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.origin[2]),
+ export_selectionBox.mins, export_selectionBox.maxs, 0.25f))
+ {
+ // skip static model if not in selection box
+ continue;
+ }
+ else
+ {
+ // copy model rotation axis
+ Game::vec4_t matrix[4];
+
+ // X
+ matrix[0][0] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[0][0];
+ matrix[0][1] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[0][1];
+ matrix[0][2] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[0][2];
+
+ // Y
+ matrix[1][0] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[1][0];
+ matrix[1][1] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[1][1];
+ matrix[1][2] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[1][2];
+
+ // Z
+ matrix[2][0] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[2][0];
+ matrix[2][1] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[2][1];
+ matrix[2][2] = Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.axis[2][2];
+
+ // calculate model angles
+ Game::vec3_t angles;
+ Utils::vector::_ToEulerAnglesDegrees(matrix, angles);
+
+ export_mapFile << Utils::VA("// static model %d\n{", exportedSModels) << std::endl;
+ export_mapFile << "layer \"000_Global/Models\"" << std::endl;
+ export_mapFile << Utils::VA("\"modelscale\" \"%.1f\"", Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.scale) << std::endl;
+ export_mapFile << Utils::VA("\"origin\" \"%.1f %.1f %.1f\"", Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.origin[0], Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.origin[1], Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].placement.origin[2]) << std::endl;
+ export_mapFile << Utils::VA("\"angles\" \"%.1f %.1f %.1f\"", angles[1], angles[2], angles[0]) << std::endl;
+ export_mapFile << Utils::VA("\"model\" \"%s\"", Game::_gfxWorld->dpvs.smodelDrawInsts[sModel].model->name) << std::endl;
+ export_mapFile << "\"classname\" \"misc_model\"" << std::endl;
+ export_mapFile << "}" << std::endl;
+ }
+ }
+
+ Utils::Clock_EndTimerPrintSeconds(map_mapModelsStart, "|- Building static models took (%.4f) seconds!\n\n");
+ }
+
+ // *
+ // Map Export End
+
+ export_mapFile.close();
+ //export_mapFile_addon.close();
+ export_currentBrushIndex = 0;
+
+ Utils::Clock_EndTimerPrintSeconds(export_time_exportStart, ">> DONE! Map export took (%.4f) seconds!\n");
+ Game::Com_PrintMessage(0, "------------------------------------------------------\n\n", 0);
+ }
+
+ // ------------
+
+ // update hud elements after we drew all brushes / planes
+ if (Game::Globals::dbgColl_drawnPlanesAmount != Game::Globals::dbgColl_drawnPlanesAmountTemp) {
+ Game::Globals::dbgColl_drawnPlanesAmount = Game::Globals::dbgColl_drawnPlanesAmountTemp;
+ }
+
+ if (Game::Globals::dbgColl_drawnBrushAmount != lastDrawnBrushAmount) {
+ Game::Globals::dbgColl_drawnBrushAmount = lastDrawnBrushAmount;
+ }
+ }
+
+ // *
+ // _Debug::RB_AdditionalDebug :: entry for collision drawing (create view frustum)
+ void RB_DrawCollision::RB_ShowCollision(Game::GfxViewParms *viewParms)
+ {
+ char frustumType;
+ float drawDistance;
+
+ Game::cplane_s frustumPlanes[6];
+
+ if (!viewParms)
+ {
+ Game::Com_Error(0, Utils::VA("RB_ShowCollision L#%d :: viewparams\n", __LINE__));
+ return;
+ }
+
+ // enable drawcollision on mapexport_selectionMode
+ if (Dvars::mapexport_selectionMode && Dvars::mapexport_selectionMode->current.integer != 0 && Dvars::r_drawCollision && Dvars::r_drawCollision->current.integer == 0)
+ {
+ Game::Cmd_ExecuteSingleCommand(0, 0, "r_drawcollision 3\n");
+ }
+
+ if (Dvars::r_drawCollision && Dvars::r_drawCollision->current.integer > 0)
+ {
+ // turn off brush sorting if displaying brushIndices
+ if (Dvars::r_drawCollision_brushIndexVisible->current.enabled && Dvars::r_drawCollision_brushSorting->current.integer != 0)
+ {
+ Game::Dvar_SetValue(Dvars::r_drawCollision_brushSorting, 0);
+ Game::Com_PrintMessage(0, Utils::VA("^1-> r_drawCollision_brushSorting ^7:: disabled due to r_drawCollision_brushIndexVisible \n"), 0);
+ }
+
+ // Disable r_drawCollision when using r_fullbright
+ if (Game::Dvar_FindVar("r_fullbright")->current.enabled)
+ {
+ Game::Com_PrintMessage(0, Utils::VA("^1-> r_drawCollision ^7:: disabled due to r_fullbright \n"), 0);
+ Game::Dvar_SetValue(Dvars::r_drawCollision, 0);
+
+ return;
+ }
+
+ // Disable r_drawCollision when using r_debugShader
+ if (Game::Dvar_FindVar("r_debugShader")->current.integer > 0)
+ {
+ Game::Com_PrintMessage(0, Utils::VA("^1-> r_drawCollision ^7:: disabled due to r_debugShader \n"), 0);
+ Game::Dvar_SetValue(Dvars::r_drawCollision, 0);
+
+ return;
+ }
+
+ // increment our frame counter if we use brush flickering mode
+ if (Dvars::r_drawCollision_flickerBrushes->current.enabled)
+ {
+ collisionFlickerCounter++;
+ }
+
+ // *
+ // Build culling frustum
+
+ BuildFrustumPlanes(viewParms, frustumPlanes);
+
+ frustumPlanes[5].normal[0] = -frustumPlanes[4].normal[0];
+ frustumPlanes[5].normal[1] = -frustumPlanes[4].normal[1];
+ frustumPlanes[5].normal[2] = -frustumPlanes[4].normal[2];
+
+ // max draw distance when brushDist is set to 0
+ drawDistance = Dvars::r_drawCollision_brushDist->current.value;
+
+ if (drawDistance == 0)
+ {
+ drawDistance = 999999.0f;
+ }
+
+ frustumPlanes[5].dist = -frustumPlanes[4].dist - drawDistance;
+
+ if (frustumPlanes[5].normal[0] == 1.0f)
+ {
+ frustumType = 0;
+ }
+
+ else
+ {
+ if (frustumPlanes[5].normal[1] == 1.0)
+ {
+ frustumType = 1;
+ }
+
+ else
+ {
+ frustumType = 2;
+ if (frustumPlanes[5].normal[2] != 1.0)
+ {
+ frustumType = 3;
+ }
+ }
+ }
+
+ frustumPlanes[5].type = frustumType;
+ SetPlaneSignbits(&frustumPlanes[5]);
+
+ // *
+ // Main brush drawing logic
+
+ CM_ShowBrushCollision(viewParms, frustumPlanes, 6);
+
+ // draw added polys / lines
+ if (Game::tess->indexCount)
+ {
+ Game::RB_EndTessSurface();
+ }
+ }
+ }
+
+ RB_DrawCollision::RB_DrawCollision()
+ {
+ // -----
+ // Dvars
+
+ Dvars::r_drawCollision = Game::Dvar_RegisterInt(
+ /* name */ "r_drawCollision",
+ /* desc */ "Enable collision drawing.\n0: Off\n1: Outlines\n2: Polys\n3: Both",
+ /* default */ 0,
+ /* minVal */ 0,
+ /* maxVal */ 3,
+ /* flags */ Game::dvar_flags::none);
+
+ Dvars::r_drawCollision_brushAmount = Game::Dvar_RegisterInt(
+ /* name */ "r_drawCollision_brushAmount",
+ /* desc */ "Draw x amount of brushes, starting at brush index 0 and will limit itself to the total amount of brushes within the clipMap.\n0: disables this filter.",
+ /* default */ 0,
+ /* minVal */ 0,
+ /* maxVal */ INT_MAX / 2 - 1,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::r_drawCollision_brushDist = Game::Dvar_RegisterFloat(
+ /* name */ "r_drawCollision_brushDist",
+ /* desc */ "Max distance to draw collision.\n0: disables this filter.\nWill reset itself on map load.",
+ /* default */ 800.0f,
+ /* minVal */ 0.0f,
+ /* maxVal */ 10000.0f,
+ /* flags */ Game::dvar_flags::none);
+
+ // r_drawCollision_brushIndexFilter @ (CM_BuildMaterialListForMapOnce)
+
+ Dvars::r_drawCollision_brushIndexVisible = Game::Dvar_RegisterBool(
+ /* name */ "r_drawCollision_brushIndexVisible",
+ /* desc */ "Draw brush index numbers for use with ^1r_drawCollision_brushFilter^7. Unstable fps will cause flickering.",
+ /* default */ false,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::r_drawCollision_brushSorting = Game::Dvar_RegisterInt(
+ /* name */ "r_drawCollision_brushSorting",
+ /* desc */ "Sort brushes based on distance from the camera.\n0: Off\n1: Far to near\n2: Near to far",
+ /* default */ 0,
+ /* minVal */ 0,
+ /* maxVal */ 2,
+ /* flags */ Game::dvar_flags::saved);
+
+#if DEBUG
+ Dvars::r_drawCollision_brushDebug = Game::Dvar_RegisterBool(
+ /* name */ "r_drawCollision_brushDebug",
+ /* desc */ "Draw debug prints. Only enabled when r_drawCollision_brushAmount = 1",
+ /* default */ false,
+ /* flags */ Game::dvar_flags::none);
+#endif
+
+ Dvars::r_drawCollision_lineWidth = Game::Dvar_RegisterInt(
+ /* name */ "r_drawCollision_lineWidth",
+ /* desc */ "Width of debug lines. (Only if using r_drawCollision 1)",
+ /* default */ 1,
+ /* minVal */ 0,
+ /* maxVal */ 12,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::r_drawCollision_lineColor = Game::Dvar_RegisterVec4(
+ /* name */ "r_drawCollision_lineColor",
+ /* desc */ "Color of debug lines.",
+ /* x */ 0.2f,
+ /* y */ 1.0f,
+ /* z */ 0.2f,
+ /* w */ 1.0f,
+ /* minValue */ 0.0f,
+ /* maxValue */ 1.0f,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::r_drawCollision_polyAlpha = Game::Dvar_RegisterFloat(
+ /* name */ "r_drawCollision_polyAlpha",
+ /* desc */ "Transparency of polygons.",
+ /* default */ 0.8f,
+ /* minVal */ 0.0f,
+ /* maxVal */ 1.0f,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::r_drawCollision_polyDepth = Game::Dvar_RegisterBool(
+ /* name */ "r_drawCollision_polyDepth",
+ /* desc */ "Enable depth test for polygons.",
+ /* default */ true,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::r_drawCollision_polyFace = Game::Dvar_RegisterBool(
+ /* name */ "r_drawCollision_polyFace",
+ /* desc */ "0: Back(only draw the front facing side)\n1: None(draw both sides)",
+ /* default */ false,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::r_drawCollision_polyLit = Game::Dvar_RegisterBool(
+ /* name */ "r_drawCollision_polyLit",
+ /* desc */ "Enable fake lighting for polygons.",
+ /* default */ false,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::r_drawCollision_material = Game::Dvar_RegisterInt(
+ /* name */ "r_drawCollision_material",
+ /* desc */ "Will be populated when a map is loaded and r_drawCollision is enabled.",
+ /* default */ 0,
+ /* minVal */ 0,
+ /* maxVal */ 1,
+ /* flags */ Game::dvar_flags::none);
+
+ static std::vector r_drawCollisionMatFilter =
+ {
+ "none",
+ "clip",
+ "mantle",
+ "trigger",
+ "all",
+ "all-no-tools",
+ "all-no-tools-clip",
+ };
+
+ Dvars::r_drawCollision_materialInclude = Game::Dvar_RegisterEnum(
+ /* name */ "r_drawCollision_materialInclude",
+ /* desc */ "Filter by type. \nExample: will show \"clip_player\" / \"clip_metal\" etc. \n draws everything but portal, trigger, hint, mantle, sky and volumes materials.\n draws everything but clip and previous mentioned materials.",
+ /* default */ 0,
+ /* enumSize */ r_drawCollisionMatFilter.size(),
+ /* enumData */ r_drawCollisionMatFilter.data(),
+ /* flags */ Game::dvar_flags::none);
+
+ Dvars::r_drawCollision_materialList = Game::Dvar_RegisterBool(
+ /* name */ "r_drawCollision_materialList",
+ /* desc */ "CMD: Prints a list of materials in use by the current map (to the console).\nOnly works when a map is loaded and r_drawCollision is enabled!",
+ /* default */ false,
+ /* flags */ Game::dvar_flags::none);
+
+ Dvars::r_drawCollision_flickerBrushes = Game::Dvar_RegisterBool(
+ /* name */ "r_drawCollision_flickerBrushes",
+ /* desc */ "[VIS] Enable debug collision flicker mode. Remove me.",
+ /* default */ false,
+ /* flags */ Game::dvar_flags::none);
+
+ Dvars::r_drawCollision_flickerOnTime = Game::Dvar_RegisterInt(
+ /* name */ "r_drawCollision_flickerOnTime",
+ /* desc */ "[VIS] Amount of frames to show brush collision.",
+ /* default */ 60,
+ /* minVal */ 0,
+ /* maxVal */ INT_MAX / 2 - 1,
+ /* flags */ Game::dvar_flags::none);
+
+ Dvars::r_drawCollision_flickerOffTime = Game::Dvar_RegisterInt(
+ /* name */ "r_drawCollision_flickerOffTime",
+ /* desc */ "[VIS] Amount of frames to hide brush collision.",
+ /* default */ 500,
+ /* minVal */ 0,
+ /* maxVal */ INT_MAX/2 - 1,
+ /* flags */ Game::dvar_flags::none);
+
+ Dvars::r_drawCollision_hud = Game::Dvar_RegisterBool(
+ /* name */ "r_drawCollision_hud",
+ /* desc */ "Display debug hud.",
+ /* default */ true,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::r_drawCollision_hud_position = Game::Dvar_RegisterVec2(
+ /* name */ "r_drawCollision_hud_position",
+ /* desc */ "hud position offset",
+ /* def x */ 10.0f,
+ /* def y */ 250.0f,
+ /* minVal */ -1000.0f,
+ /* maxVal */ 1000.0f,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::r_drawCollision_hud_fontScale = Game::Dvar_RegisterFloat(
+ /* name */ "r_drawCollision_hud_fontScale",
+ /* desc */ "font scale",
+ /* default */ 0.75f,
+ /* minVal */ 0.0f,
+ /* maxVal */ 100.0f,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::r_drawCollision_hud_fontStyle = Game::Dvar_RegisterInt(
+ /* name */ "r_drawCollision_hud_fontStyle",
+ /* desc */ "font style",
+ /* default */ 1,
+ /* minVal */ 0,
+ /* maxVal */ 8,
+ /* flags */ Game::dvar_flags::none);
+
+ Dvars::r_drawCollision_hud_fontColor = Game::Dvar_RegisterVec4(
+ /* name */ "r_drawCollision_hud_fontColor",
+ /* desc */ "font color",
+ /* x */ 1.0f,
+ /* y */ 0.55f,
+ /* z */ 0.4f,
+ /* w */ 1.0f,
+ /* minValue */ 0.0f,
+ /* maxValue */ 1.0f,
+ /* flags */ Game::dvar_flags::saved);
+
+ // ---------------
+
+ Dvars::mapexport_brushEpsilon1 = Game::Dvar_RegisterFloat(
+ /* name */ "mapexport_brushEpsilon1",
+ /* desc */ "brushside epsilon 1 (debug)",
+ /* default */ 0.4f,
+ /* minVal */ 0.0f,
+ /* maxVal */ 1.0f,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::mapexport_brushEpsilon2 = Game::Dvar_RegisterFloat(
+ /* name */ "mapexport_brushEpsilon2",
+ /* desc */ "brushside epsilon 2 (debug)",
+ /* default */ 1.0f,
+ /* minVal */ 0.0f,
+ /* maxVal */ 1.0f,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::mapexport_brushMinSize = Game::Dvar_RegisterFloat(
+ /* name */ "mapexport_brushMinSize",
+ /* desc */ "only export brushes (with more then 6 sides) if their diagonal length is greater then ",
+ /* default */ 64.0f,
+ /* minVal */ 0.0f,
+ /* maxVal */ 1000.0f,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::mapexport_brush5Sides = Game::Dvar_RegisterBool(
+ /* name */ "mapexport_brush5Sides",
+ /* desc */ "enable exp. export of brushes with only 5 sides",
+ /* default */ true,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::mapexport_selectionMode = Game::Dvar_RegisterInt(
+ /* name */ "mapexport_selectionMode",
+ /* desc */ "Only export selected things. Use \"mapexport_selectionAdd\" and \"mapexport_selectionClear\" \n0: Off\n1: Bounding box (needs 2 defined points)",
+ /* default */ 0,
+ /* minVal */ 0,
+ /* maxVal */ 1,
+ /* flags */ Game::dvar_flags::none);
+
+ Dvars::mapexport_writeTriangles = Game::Dvar_RegisterBool(
+ /* name */ "mapexport_writeTriangles",
+ /* desc */ "[MAP-EXPORT-OPTION] Export leftover unmerged triangles if enabled.",
+ /* default */ true,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::mapexport_writeQuads = Game::Dvar_RegisterBool(
+ /* name */ "mapexport_writeQuads",
+ /* desc */ "[MAP-EXPORT-OPTION] Export resulting quads after triangle merging if enabled.",
+ /* default */ true,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::mapexport_writeEntities = Game::Dvar_RegisterBool(
+ /* name */ "mapexport_writeEntities",
+ /* desc */ "[MAP-EXPORT-OPTION] Export map entities if enabled (no brushmodel support).",
+ /* default */ true,
+ /* flags */ Game::dvar_flags::saved);
+
+ Dvars::mapexport_writeModels = Game::Dvar_RegisterBool(
+ /* name */ "mapexport_writeModels",
+ /* desc */ "[MAP-EXPORT-OPTION] Export all static models if enabled.",
+ /* default */ true,
+ /* flags */ Game::dvar_flags::saved);
+
+ // --------
+ // Commands
+
+ Command::Add("mapexport", [](Command::Params)
+ {
+ if (Dvars::r_drawCollision && Dvars::r_drawCollision->current.integer == 0)
+ {
+ Game::Com_PrintMessage(0, "Please enable \"r_drawCollision\" and re-run your command.\n", 0);
+ return;
+ }
+
+ export_mapExportCmd = true;
+
+ Game::Cmd_ExecuteSingleCommand(0, 0, "pm_hud_enable 0\n");
+ Game::Cmd_ExecuteSingleCommand(0, 0, "say \"Export Done!\"\n");
+ });
+
+ Command::Add("mapexport_selectionAdd", [](Command::Params)
+ {
+ if (Dvars::mapexport_selectionMode && Dvars::mapexport_selectionMode->current.integer != 0)
+ {
+ export_selectionAdd = true;
+ }
+ else
+ {
+ Game::Com_PrintMessage(0, "mapexport_selectionMode needs to be enabled for this to work.\n", 0);
+ }
+ });
+
+ Command::Add("mapexport_selectionClear", [](Command::Params)
+ {
+ if (Dvars::mapexport_selectionMode && Dvars::mapexport_selectionMode->current.integer != 0)
+ {
+ CM_ResetSelectionBox(&export_selectionBox);
+ }
+ else
+ {
+ Game::Com_PrintMessage(0, "mapexport_selectionMode needs to be enabled for this to work.\n", 0);
+ }
+ });
+ }
+
+ RB_DrawCollision::~RB_DrawCollision()
+ { }
}
\ No newline at end of file
diff --git a/src/Components/Modules/_Common.cpp b/src/Components/Modules/_Common.cpp
index f5996ee..f454f00 100644
--- a/src/Components/Modules/_Common.cpp
+++ b/src/Components/Modules/_Common.cpp
@@ -404,169 +404,6 @@ namespace Components
}
}
- // *
- // MP-SP
-
- bool mpsp_is_sp_map = false;
- int mpsp_ignore_entities(char* entity, [[maybe_unused]] const char* unused_str, [[maybe_unused]] int unused_int)
- {
- if (Utils::StartsWith(entity, "dyn_"))
- {
- return false;
- }
-
- if (mpsp_is_sp_map)
- {
- if (Utils::StartsWith(entity, "info_player_start"))
- {
- strcpy(entity, "mp_dm_spawn"); // kek works
- return true;
- }
-
- if (Utils::StartsWith(entity, "worldspawn"))
- {
- return true;
- }
-
- if (Utils::StartsWith(entity, "script_struct"))
- {
- return true;
- }
-
- // ignore all other entities
- return false;
- }
-
- return true;
- }
-
- void __declspec(naked) mpsp_ignore_entities_stub()
- {
- const static uint32_t retn_addr = 0x4DFF25;
- __asm
- {
- // already pushed: entity str
- call mpsp_ignore_entities;
- jmp retn_addr;
- }
- }
-
- void mpsp_get_bsp_name(char* filename, int size, [[maybe_unused]] const char* original_format, const char* mapname)
- {
- mpsp_is_sp_map = !strstr(mapname, "mp_");
- sprintf_s(filename, size, "maps/%s%s.d3dbsp", strstr(mapname, "mp_") ? "mp/" : "", mapname);
- }
-
- void __declspec(naked) mpsp_bsp_name_stub01()
- {
- const static uint32_t retn_addr = 0x44AA1C;
- __asm
- {
- // already pushed: mapname
- // already pushed: "maps/mp/%s.d3dbsp"
-
- push esi; // size
- push edi; // dest
- call mpsp_get_bsp_name;
- add esp, 8;
-
- jmp retn_addr;
- }
- }
-
- void __declspec(naked) mpsp_bsp_name_stub02()
- {
- const static uint32_t retn_addr = 0x45BF91;
- __asm
- {
- // already pushed: mapname
- // already pushed: "maps/mp/%s.d3dbsp"
-
- push esi; // size
- push edi; // dest
- call mpsp_get_bsp_name;
- add esp, 8;
-
- jmp retn_addr;
- }
- }
-
- void __declspec(naked) mpsp_bsp_name_stub03()
- {
- const static uint32_t retn_addr = 0x52F72C;
- __asm
- {
- // already pushed: mapname
- // already pushed: "maps/mp/%s.d3dbsp"
-
- push esi; // size
- push edi; // dest
- call mpsp_get_bsp_name;
- add esp, 8;
-
- jmp retn_addr;
- }
- }
-
- void mpsp_get_fx_def(char* filename, int size, [[maybe_unused]] const char* original_format, const char* mapname)
- {
- if (Dvars::mpsp_require_gsc->current.enabled)
- {
- sprintf_s(filename, size, "maps/%s%s_fx.gsc", strstr(mapname, "mp_") ? "mp/" : "", mapname);
- }
- else
- {
- sprintf_s(filename, size, "maps/mp/%s_fx.gsc", mapname);
- }
-
- }
-
- void __declspec(naked) mpsp_fx_def_stub()
- {
- const static uint32_t retn_addr = 0x424C84;
- __asm
- {
- // already pushed: mapname
- // already pushed: "maps/mp/%s.d3dbsp"
-
- push esi; // size
- push edi; // dest
- call mpsp_get_fx_def;
- add esp, 8;
-
- jmp retn_addr;
- }
- }
-
- void mpsp_get_map_gsc(char* filename, int size, [[maybe_unused]] const char* original_format, const char* mapname)
- {
- if (Dvars::mpsp_require_gsc->current.enabled)
- {
- sprintf_s(filename, size, "maps/%s%s", strstr(mapname, "mp_") ? "mp/" : "", mapname);
- }
- else
- {
- sprintf_s(filename, size, "maps/mp/%s", mapname);
- }
- }
-
- void __declspec(naked) mpsp_map_gsc_stub()
- {
- const static uint32_t retn_addr = 0x4CB01A;
- __asm
- {
- // already pushed: mapname
- // already pushed: "maps/mp/%s.d3dbsp"
-
- push esi; // size
- push edi; // dest
- call mpsp_get_map_gsc;
- add esp, 8;
-
- jmp retn_addr;
- }
- }
-
// *
// DB
@@ -643,6 +480,7 @@ namespace Components
Utils::Hook(0x56B335, disable_dvar_cheats_stub, HOOK_JUMP).install()->quick();
Utils::Hook::Nop(0x56B339 + 1, 1);
+
// *
// FastFiles
@@ -652,6 +490,7 @@ namespace Components
// ^ Com_StartHunkUsers Mid-hook (realloc files that were unloaded on map load)
Utils::Hook::Nop(0x50020F, 6); Utils::Hook(0x50020F, Com_StartHunkUsers_stub, HOOK_JUMP).install()->quick();
+
// *
// IWDs
@@ -661,21 +500,6 @@ namespace Components
// Load "iw_" and "xcommon_" iwds as localized (works in all situations + elements in xcommon files can overwrite prior files)
Utils::Hook(0x55DBB4, FS_MakeIWDsLocalized, HOOK_JUMP).install()->quick();
- // *
- // MP-SP
-
- Utils::Hook(0x44AA17, mpsp_bsp_name_stub01, HOOK_JUMP).install()->quick();
- Utils::Hook(0x45BF8C, mpsp_bsp_name_stub02, HOOK_JUMP).install()->quick();
- Utils::Hook(0x52F727, mpsp_bsp_name_stub03, HOOK_JUMP).install()->quick();
- Utils::Hook(0x424C7F, mpsp_fx_def_stub, HOOK_JUMP).install()->quick();
- Utils::Hook(0x4CB015, mpsp_map_gsc_stub, HOOK_JUMP).install()->quick();
- Utils::Hook(0x4DFF20, mpsp_ignore_entities_stub, HOOK_JUMP).install()->quick();
-
- Dvars::mpsp_require_gsc = Game::Dvar_RegisterBool(
- /* name */ "mpsp_require_gsc",
- /* desc */ "enabled: load spmod map gsc's (mostly fx)",
- /* default */ true,
- /* flags */ Game::dvar_flags::saved);
// *
// Commands
diff --git a/src/Components/Modules/_Common.hpp b/src/Components/Modules/_Common.hpp
index 1fcbe78..8445915 100644
--- a/src/Components/Modules/_Common.hpp
+++ b/src/Components/Modules/_Common.hpp
@@ -1,15 +1,15 @@
-#pragma once
-
-namespace Components
-{
- class _Common : public Component
- {
- public:
- _Common();
- ~_Common();
- const char* getName() override { return "_Common"; };
-
- private:
- void db_realloc_entry_pool();
- };
-}
+#pragma once
+
+namespace Components
+{
+ class _Common : public Component
+ {
+ public:
+ _Common();
+ ~_Common();
+ const char* getName() override { return "_Common"; };
+
+ private:
+ void db_realloc_entry_pool();
+ };
+}
diff --git a/src/Components/Modules/_Map.cpp b/src/Components/Modules/_Map.cpp
index d03f70f..845324b 100644
--- a/src/Components/Modules/_Map.cpp
+++ b/src/Components/Modules/_Map.cpp
@@ -1,70 +1,373 @@
-#include "STDInclude.hpp"
-
-namespace Components
-{
- void _Map::OnLoad()
- {
- memset(&Game::Globals::cgsAddons, 0, sizeof(Game::cgsAddon));
-
- if (Components::active.RadiantRemote)
- {
- memset(&Game::Globals::dynBrushModels, 0, sizeof(Game::dynBrushModelsArray_t));
- memset(&Game::Globals::rad_savedBrushes, 0, sizeof(Game::savedRadiantBrushes));
-
- RadiantRemote::CM_FindDynamicBrushModels();
- }
-
- if (Components::active.RB_DrawCollision)
- {
- Game::Globals::dbgColl_initialized = false;
- }
- }
-
- void _Map::OnUnload()
- {
- if (Components::active.RadiantRemote)
- {
- RadiantRemote::SV_Shutdown();
- }
- }
-
- // --------
-
- __declspec(naked) void sv_spawnserver_stub()
- {
- const static uint32_t retnPt = 0x52F8A7;
- __asm
- {
- // overwritten op's
- add esp, 8;
- and esi, 0FFFFFFF0h;
-
- call _Map::OnLoad;
- jmp retnPt;
- }
- }
-
- __declspec(naked) void com_shutdowninternal_stub()
- {
- const static uint32_t Com_Restart_Jmp = 0x5004C0;
- __asm
- {
- call _Map::OnUnload;
- jmp Com_Restart_Jmp;
- }
- }
-
- // --------
-
- _Map::_Map()
- {
- // On map load
- Utils::Hook::Nop(0x52F8A1, 6); Utils::Hook(0x52F8A1, sv_spawnserver_stub, HOOK_JUMP).install()->quick(); // after SV_InitGameProgs before G_RunFrame
-
- // On map unload
- Utils::Hook(0x4FCDF8, com_shutdowninternal_stub, HOOK_JUMP).install()->quick(); // before Com_Restart
- }
-
- _Map::~_Map()
- { }
+#include "STDInclude.hpp"
+
+namespace Components
+{
+ void _Map::OnLoad()
+ {
+ memset(&Game::Globals::cgsAddons, 0, sizeof(Game::cgsAddon));
+
+ if (Components::active.RadiantRemote)
+ {
+ memset(&Game::Globals::dynBrushModels, 0, sizeof(Game::dynBrushModelsArray_t));
+ memset(&Game::Globals::rad_savedBrushes, 0, sizeof(Game::savedRadiantBrushes));
+
+ RadiantRemote::CM_FindDynamicBrushModels();
+ }
+
+ if (Components::active.RB_DrawCollision)
+ {
+ Game::Globals::dbgColl_initialized = false;
+ }
+ }
+
+ void _Map::OnUnload()
+ {
+ if (Components::active.RadiantRemote)
+ {
+ RadiantRemote::SV_Shutdown();
+ }
+ }
+
+ // --------
+
+ __declspec(naked) void sv_spawnserver_stub()
+ {
+ const static uint32_t retnPt = 0x52F8A7;
+ __asm
+ {
+ // overwritten op's
+ add esp, 8;
+ and esi, 0FFFFFFF0h;
+
+ call _Map::OnLoad;
+ jmp retnPt;
+ }
+ }
+
+ __declspec(naked) void com_shutdowninternal_stub()
+ {
+ const static uint32_t Com_Restart_Jmp = 0x5004C0;
+ __asm
+ {
+ call _Map::OnUnload;
+ jmp Com_Restart_Jmp;
+ }
+ }
+
+
+ // *
+ // MP-SP
+
+ std::string mpsp_map_name = "";
+ char* mpsp_mapents_buffer = nullptr;
+
+#if 0
+ int mpsp_ignore_entities(char* entity, [[maybe_unused]] const char* unused_str, [[maybe_unused]] int unused_int)
+ {
+ if (Utils::StartsWith(entity, "dyn_"))
+ {
+ return false;
+ }
+
+ //if (mpsp_is_sp_map)
+ //{
+ // //strcpy(Game::cm->mapEnts->entityString, test);
+
+ // if (Utils::StartsWith(entity, "info_player_start"))
+ // {
+ // strcpy(entity, "mp_dm_spawn"); // kek works
+ // return true;
+ // }
+
+ // if (Utils::StartsWith(entity, "worldspawn"))
+ // {
+ // return true;
+ // }
+
+ // if (Utils::StartsWith(entity, "script_struct"))
+ // {
+ // return true;
+ // }
+
+ // // ignore all other entities
+ // return false;
+ //}
+
+ return true;
+ }
+
+ void __declspec(naked) mpsp_ignore_entities_stub()
+ {
+ const static uint32_t retn_addr = 0x4DFF25;
+ __asm
+ {
+ // already pushed: entity str
+ call mpsp_ignore_entities;
+ jmp retn_addr;
+ }
+ }
+#endif
+
+ void mpsp_replace_mapents(Game::clipMap_t* cm)
+ {
+ if (_Map::mpsp_is_sp_map)
+ {
+ const auto& fs_basepath = Game::Dvar_FindVar("fs_basepath");
+ const auto& fs_game = Game::Dvar_FindVar("fs_game");
+
+ if (fs_basepath && fs_game)
+ {
+ std::string mod = fs_game->current.string;
+ Utils::Replace(mod, "/", "\\"s);
+
+ std::string base_path;
+ base_path += fs_basepath->current.string + "\\"s;
+ base_path += mod + "\\mapents\\"s;
+
+ if (std::filesystem::exists(base_path))
+ {
+ std::ifstream mapents;
+ mapents.open(base_path + mpsp_map_name + ".ents", std::ios::in | std::ios::binary);
+
+ if (!mapents.is_open())
+ {
+ Game::Com_PrintMessage(0, Utils::VA("[!] Failed to open mapents file [%s]", (mpsp_map_name + ".ents"s).c_str()), 0);
+ }
+
+ mapents.ignore(std::numeric_limits::max());
+ const size_t length = static_cast(mapents.gcount());
+ mapents.clear(); // Since ignore will have set eof.
+ mapents.seekg(0, std::ios_base::beg);
+
+ if (mpsp_mapents_buffer)
+ {
+ mpsp_mapents_buffer = (char*)realloc(mpsp_mapents_buffer, length + 1);
+ }
+ else
+ {
+ mpsp_mapents_buffer = (char*)malloc(length + 1); //new char[length];
+ }
+
+ mapents.read(mpsp_mapents_buffer, length);
+ mapents.close();
+
+ mpsp_mapents_buffer[length] = 0;
+
+ // save original ptr
+ _Map::mpsp_mapents_original = cm->mapEnts->entityString;
+
+ // replace ptr
+ cm->mapEnts->entityString = mpsp_mapents_buffer;
+ }
+ }
+ }
+ }
+
+ void __declspec(naked) mpsp_replace_mapents_stub()
+ {
+ const static uint32_t retn_addr = 0x480898;
+ __asm
+ {
+ pushad;
+ mov eax, [eax]; // ptr to clipmap ptr
+ push eax; // clipmap ptr
+ call mpsp_replace_mapents;
+ add esp, 4;
+ popad;
+
+ mov esi, eax; // og
+ mov eax, [eax]; // og
+ push eax; // og
+ jmp retn_addr;
+ }
+ }
+
+ bool skip_visible_from_point_check()
+ {
+ return _Map::mpsp_is_sp_map;
+ }
+
+ void __declspec(naked) mpsp_add_entities_visible_from_point_stub()
+ {
+ const static uint32_t func_addr_mp_map = 0x52D540;
+ const static uint32_t retn_addr_mp_map = 0x5346DB;
+ const static uint32_t retn_addr_sp_map = 0x5347B2;
+ __asm
+ {
+ pushad;
+ call skip_visible_from_point_check;
+ test al, al;
+ jne SKIP;
+
+ popad;
+ call func_addr_mp_map; // og
+ jmp retn_addr_mp_map; // og
+
+ SKIP:
+ popad;
+ jmp retn_addr_sp_map;
+ }
+ }
+
+ void mpsp_get_bsp_name(char* filename, int size, [[maybe_unused]] const char* original_format, const char* mapname)
+ {
+ _Map::mpsp_is_sp_map = !strstr(mapname, "mp_");
+ mpsp_map_name = mapname;
+
+ sprintf_s(filename, size, "maps/%s%s.d3dbsp", strstr(mapname, "mp_") ? "mp/" : "", mapname);
+ }
+
+ void __declspec(naked) mpsp_bsp_name_stub01()
+ {
+ const static uint32_t retn_addr = 0x44AA1C;
+ __asm
+ {
+ // already pushed: mapname
+ // already pushed: "maps/mp/%s.d3dbsp"
+
+ push esi; // size
+ push edi; // dest
+ call mpsp_get_bsp_name;
+ add esp, 8;
+
+ jmp retn_addr;
+ }
+ }
+
+ void __declspec(naked) mpsp_bsp_name_stub02()
+ {
+ const static uint32_t retn_addr = 0x45BF91;
+ __asm
+ {
+ // already pushed: mapname
+ // already pushed: "maps/mp/%s.d3dbsp"
+
+ push esi; // size
+ push edi; // dest
+ call mpsp_get_bsp_name;
+ add esp, 8;
+
+ jmp retn_addr;
+ }
+ }
+
+ void __declspec(naked) mpsp_bsp_name_stub03()
+ {
+ const static uint32_t retn_addr = 0x52F72C;
+ __asm
+ {
+ // already pushed: mapname
+ // already pushed: "maps/mp/%s.d3dbsp"
+
+ push esi; // size
+ push edi; // dest
+ call mpsp_get_bsp_name;
+ add esp, 8;
+
+ jmp retn_addr;
+ }
+ }
+
+ void mpsp_get_fx_def(char* filename, int size, [[maybe_unused]] const char* original_format, const char* mapname)
+ {
+ if (Dvars::mpsp_require_gsc->current.enabled)
+ {
+ sprintf_s(filename, size, "maps/%s%s_fx.gsc", strstr(mapname, "mp_") ? "mp/" : "", mapname);
+ }
+ else
+ {
+ sprintf_s(filename, size, "maps/mp/%s_fx.gsc", mapname);
+ }
+
+ }
+
+ void __declspec(naked) mpsp_fx_def_stub()
+ {
+ const static uint32_t retn_addr = 0x424C84;
+ __asm
+ {
+ // already pushed: mapname
+ // already pushed: "maps/mp/%s.d3dbsp"
+
+ push esi; // size
+ push edi; // dest
+ call mpsp_get_fx_def;
+ add esp, 8;
+
+ jmp retn_addr;
+ }
+ }
+
+ void mpsp_get_map_gsc(char* filename, int size, [[maybe_unused]] const char* original_format, const char* mapname)
+ {
+ if (Dvars::mpsp_require_gsc->current.enabled)
+ {
+ sprintf_s(filename, size, "maps/%s%s", strstr(mapname, "mp_") ? "mp/" : "", mapname);
+ }
+ else
+ {
+ sprintf_s(filename, size, "maps/mp/%s", mapname);
+ }
+ }
+
+ void __declspec(naked) mpsp_map_gsc_stub()
+ {
+ const static uint32_t retn_addr = 0x4CB01A;
+ __asm
+ {
+ // already pushed: mapname
+ // already pushed: "maps/mp/%s.d3dbsp"
+
+ push esi; // size
+ push edi; // dest
+ call mpsp_get_map_gsc;
+ add esp, 8;
+
+ jmp retn_addr;
+ }
+ }
+
+
+ // --------
+
+ _Map::_Map()
+ {
+ // On map load
+ Utils::Hook::Nop(0x52F8A1, 6); Utils::Hook(0x52F8A1, sv_spawnserver_stub, HOOK_JUMP).install()->quick(); // after SV_InitGameProgs before G_RunFrame
+
+ // On map unload
+ Utils::Hook(0x4FCDF8, com_shutdowninternal_stub, HOOK_JUMP).install()->quick(); // before Com_Restart
+
+ // *
+ // MP-SP
+
+ // fix bsp path
+ Utils::Hook(0x44AA17, mpsp_bsp_name_stub01, HOOK_JUMP).install()->quick();
+ Utils::Hook(0x45BF8C, mpsp_bsp_name_stub02, HOOK_JUMP).install()->quick();
+ Utils::Hook(0x52F727, mpsp_bsp_name_stub03, HOOK_JUMP).install()->quick();
+
+ // fix gsc path
+ Utils::Hook(0x424C7F, mpsp_fx_def_stub, HOOK_JUMP).install()->quick();
+ Utils::Hook(0x4CB015, mpsp_map_gsc_stub, HOOK_JUMP).install()->quick();
+
+ // custom mapents
+ Utils::Hook(0x480893, mpsp_replace_mapents_stub, HOOK_JUMP).install()->quick();
+
+ // skip sv_ent visibility check on sp maps
+ Utils::Hook(0x5346D6, mpsp_add_entities_visible_from_point_stub, HOOK_JUMP).install()->quick();
+
+ // no longer needed because of custom mapents
+ //Utils::Hook(0x4DFF20, mpsp_ignore_entities_stub, HOOK_JUMP).install()->quick();
+
+ Dvars::mpsp_require_gsc = Game::Dvar_RegisterBool(
+ /* name */ "mpsp_require_gsc",
+ /* desc */ "enabled: load spmod map gsc's (mostly fx)",
+ /* default */ true,
+ /* flags */ Game::dvar_flags::saved);
+ }
+
+ _Map::~_Map()
+ {
+ free(mpsp_mapents_buffer);
+ }
}
\ No newline at end of file
diff --git a/src/Components/Modules/_Map.hpp b/src/Components/Modules/_Map.hpp
index 0569416..06beef5 100644
--- a/src/Components/Modules/_Map.hpp
+++ b/src/Components/Modules/_Map.hpp
@@ -1,17 +1,20 @@
-#pragma once
-
-namespace Components
-{
- class _Map : public Component
- {
- public:
- _Map();
- ~_Map();
- const char* getName() override { return "_Map"; };
-
- static void OnLoad();
- static void OnUnload();
-
- private:
- };
-}
+#pragma once
+
+namespace Components
+{
+ class _Map : public Component
+ {
+ public:
+ _Map();
+ ~_Map();
+ const char* getName() override { return "_Map"; };
+
+ static void OnLoad();
+ static void OnUnload();
+
+ inline static bool mpsp_is_sp_map = false;
+ inline static char* mpsp_mapents_original = nullptr;
+
+ private:
+ };
+}
diff --git a/src/Components/Modules/_UI.cpp b/src/Components/Modules/_UI.cpp
index f032e19..8d8a4c0 100644
--- a/src/Components/Modules/_UI.cpp
+++ b/src/Components/Modules/_UI.cpp
@@ -574,7 +574,7 @@ R"(
Dvars::ui_main_title = Game::Dvar_RegisterString(
/* name */ "ui_changelog_title",
/* desc */ "menu helper",
- /* value */ Utils::VA(IW3XO_CHANGELOG_TITLE_FMT, IW3X_BUILDNUMBER, __TIMESTAMP__),
+ /* value */ Utils::VA(IW3XO_CHANGELOG_TITLE_FMT, IW3X_BUILDNUMBER, IW3XO_BUILDVERSION_DATE),
/* flags */ Game::dvar_flags::read_only);
}
diff --git a/src/Utils/Entities.cpp b/src/Utils/Entities.cpp
index 6c681ff..19c45d1 100644
--- a/src/Utils/Entities.cpp
+++ b/src/Utils/Entities.cpp
@@ -1,480 +1,513 @@
-#include "STDInclude.hpp"
-
-namespace Utils
-{
- // build all entities (includes brushmodel pointers "*")
- std::string Entities::buildAll()
- {
- std::string entityString;
-
- for (auto& entity : this->entities)
- {
- entityString.append("{\n");
-
- for (auto& property : entity)
- {
- entityString.push_back('"');
- entityString.append(property.first);
- entityString.append("\" \"");
- entityString.append(property.second);
- entityString.append("\"\n");
- }
-
- entityString.append("}\n");
- }
-
- return entityString;
- }
-
- // build all entities and fix brushmodels
- std::string Entities::buildAll_FixBrushmodels(const std::vector &bModelList)
- {
- int entityNum = 1; // worldspawn is 0
- int submodelNum = 0;
- std::string entityString;
-
- for (auto& entity : this->entities)
- {
- std::string model = entity["model"];
-
- // if ent is a brushmodel/submodel
- if (!model.empty() && model[0] == '*')
- {
- // get the submodel index
- auto p_index = std::stoi(model.erase(0, 1));
-
- if (p_index < static_cast(bModelList.size()))
- {
- auto bModelSideCount = static_cast(bModelList[p_index].brushSides.size());
-
- // skip submodel entity if we have less then 6 brushsides (we could also create a temp. cube at its origin)
- if (bModelSideCount < 6)
- {
- continue;
- }
-
- // start submodel
- entityString.append(Utils::VA("// submodel %d\n", submodelNum));
- entityString.append("{\n");
-
- // write submodel keys
- for (auto& property : entity)
- {
- // do not write model/origin keys
- if (property.first == "model" || property.first == "origin")
- {
- continue;
- }
-
- entityString.push_back('"');
- entityString.append(property.first);
- entityString.append("\" \"");
- entityString.append(property.second);
- entityString.append("\"\n");
- }
-
- // start submodel brush
- entityString.append("{\n");
-
- for (auto bSide = 0; bSide < bModelSideCount; bSide++)
- {
- entityString.append(bModelList[p_index].brushSides[bSide]);
- }
-
- // close submodel brush
- entityString.append("}\n");
-
- // close submodel
- entityString.append("}\n");
-
- submodelNum++;
- }
- }
-
- else
- {
- // start entity
- entityString.append(Utils::VA("// entity %d\n", entityNum));
- entityString.append("{\n");
-
- for (auto& property : entity)
- {
- entityString.push_back('"');
- entityString.append(property.first);
- entityString.append("\" \"");
- entityString.append(property.second);
- entityString.append("\"\n");
- }
-
- // close entity
- entityString.append("}\n");
-
- entityNum++;
- }
- }
-
- return entityString;
- }
-
- // build all selected entities and fix brushmodels
- std::string Entities::buildSelection_FixBrushmodels(const Game::boundingBox_t* box, const std::vector& bModelList)
- {
- int entityNum = 1; // worldspawn is 0
- int submodelNum = 0;
- std::string entityString;
-
- for (auto& entity : this->entities)
- {
- std::string model = entity["model"];
- std::string origin = entity["origin"];
-
- // if ent is a brushmodel/submodel
- if (!model.empty() && model[0] == '*')
- {
- // get the submodel index
- auto p_index = std::stoi(model.erase(0, 1));
-
- if (p_index < static_cast(bModelList.size()))
- {
- auto bModelSideCount = static_cast(bModelList[p_index].brushSides.size());
-
- // skip submodel entity if we have less then 6 brushsides (we could also create a temp. cube at its origin)
- if (bModelSideCount < 6)
- {
- continue;
- }
-
- // start submodel
- entityString.append(Utils::VA("// submodel %d\n", submodelNum));
- entityString.append("{\n");
-
- // write submodel keys
- for (auto& property : entity)
- {
- // do not write model/origin keys
- if (property.first == "model" || property.first == "origin")
- {
- continue;
- }
-
- entityString.push_back('"');
- entityString.append(property.first);
- entityString.append("\" \"");
- entityString.append(property.second);
- entityString.append("\"\n");
- }
-
- // start submodel brush
- entityString.append("{\n");
-
- for (auto bSide = 0; bSide < bModelSideCount; bSide++)
- {
- entityString.append(bModelList[p_index].brushSides[bSide]);
- }
-
- // close submodel brush
- entityString.append("}\n");
-
- // close submodel
- entityString.append("}\n");
-
- submodelNum++;
- }
- }
-
- else
- {
- float tempOrigin[3] = {0.0f, 0.0f, 0.0f};
-
- if (!sscanf_s(origin.c_str(), "%f %f %f", &tempOrigin[0], &tempOrigin[1], &tempOrigin[2]))
- {
- Game::Com_PrintMessage(0, Utils::VA("[!]: sscanf failed for entity %d", entityNum), 0);
- }
-
- if (Utils::polylib::PointWithinBounds(glm::toVec3(tempOrigin), box->mins, box->maxs, 0.25f))
- {
- // start entity
- entityString.append(Utils::VA("// entity %d\n", entityNum));
- entityString.append("{\n");
-
- for (auto& property : entity)
- {
- entityString.push_back('"');
- entityString.append(property.first);
- entityString.append("\" \"");
- entityString.append(property.second);
- entityString.append("\"\n");
- }
-
- // close entity
- entityString.append("}\n");
- entityNum++;
- }
- }
- }
-
- return entityString;
- }
-
- // only build worldspawn keys/values without opening/closing brackets
- std::string Entities::buildWorldspawnKeys()
- {
- std::string entityString;
-
- for (auto& entity : this->entities)
- {
- if (entity.find("classname") != entity.end())
- {
- if (entity["classname"] == "worldspawn"s)
- {
- for (auto& property : entity)
- {
- entityString.push_back('"');
- entityString.append(property.first);
- entityString.append("\" \"");
- entityString.append(property.second);
- entityString.append("\"\n");
- }
-
- break;
- }
- }
- }
-
- return entityString;
- }
-
- std::vector Entities::getModels()
- {
- std::vector models;
-
- for (auto& entity : this->entities)
- {
- if (entity.find("model") != entity.end())
- {
- std::string model = entity["model"];
-
- if (!model.empty() && model[0] != '*' && model[0] != '?') // Skip brushmodels
- {
- if (std::find(models.begin(), models.end(), model) == models.end())
- {
- models.push_back(model);
- }
- }
- }
- }
-
- return models;
- }
-
- std::vector Entities::getBrushModels()
- {
- std::vector bModels;
-
- // geting the total clipmap size would prob. be better
- uintptr_t leafBrushesStart = reinterpret_cast(&*Game::cm->leafbrushNodes);
- uintptr_t leafBrushesEnd = leafBrushesStart + sizeof(Game::cLeafBrushNode_s) * (Game::cm->leafbrushNodesCount + Game::cm->numLeafBrushes); // wrong
-
- // first element is always empty because
- // the first submodel within the entsMap starts at 1 and we want to avoid subtracting - 1 everywhere
- bModels.push_back(Game::brushmodelEnt_t());
-
- for (auto& entity : this->entities)
- {
- if (entity.find("model") != entity.end())
- {
- std::string model = entity["model"];
- std::string origin = entity["origin"];
-
- // if ent is a brushmodel/submodel
- if (!model.empty() && model[0] == '*' && !origin.empty())
- {
- auto currBModel = Game::brushmodelEnt_t();
-
- // get the submodel index
- auto p_index = std::stoi(model.erase(0, 1));
-
- // the index should always match the size of our vector or we did something wrong
- if (p_index != (int)bModels.size())
- {
- Game::Com_PrintMessage(0, Utils::VA("[Entities::getBrushModels]: Something went wrong while parsing submodels. (%d != %d)", p_index, bModels.size()), 0);
- }
-
- if (p_index >= static_cast(Game::cm->numSubModels))
- {
- Game::Com_PrintMessage(0, Utils::VA("[Entities::getBrushModels]: Something went wrong while parsing submodels. (%d >= %d numSubModels)", p_index, Game::cm->numSubModels), 0);
- break;
- }
-
- // assign indices and pointers to both the brush and the submodel
- currBModel.cmSubmodelIndex = p_index;
-
- if (&Game::cm->cmodels[p_index])
- {
- currBModel.cmSubmodel = &Game::cm->cmodels[p_index];
- }
-
- // fix me daddy
- auto brushIdx_ptr = Game::cm->leafbrushNodes[Game::cm->cmodels[p_index].leaf.leafBrushNode].data.leaf.brushes;
- currBModel.cmBrushIndex = 0;
-
- // this is giving me cancer
- if (Game::cm->cmodels[p_index].leaf.leafBrushNode != 0 && brushIdx_ptr)
- {
- if ((uintptr_t)&*brushIdx_ptr >= leafBrushesStart && (uintptr_t) & *brushIdx_ptr < leafBrushesEnd)
- {
- currBModel.cmBrushIndex = static_cast(*Game::cm->leafbrushNodes[Game::cm->cmodels[p_index].leaf.leafBrushNode].data.leaf.brushes);
- }
- else
- {
- Game::Com_PrintMessage(0, Utils::VA("[Entities::getBrushModels]: Skipping faulty brush-index pointer at leafbrushNodes[%d].data.leaf.brushes ...\n", p_index), 0);
- }
-
- //currBModel.cmBrush = &Game::cm->brushes[*Game::cm->leafbrushNodes[Game::cm->cmodels[p_index].leaf.leafBrushNode].data.leaf.brushes];
- currBModel.cmBrush = &Game::cm->brushes[currBModel.cmBrushIndex];
-
- // add the submodel index to the clipmap brush
- currBModel.cmBrush->isSubmodel = true;
- currBModel.cmBrush->cmSubmodelIndex = static_cast<__int16>(p_index);
- }
-
-
- // save entity origin
- if (!sscanf_s(origin.c_str(), "%f %f %f", &currBModel.cmSubmodelOrigin[0], &currBModel.cmSubmodelOrigin[1], &currBModel.cmSubmodelOrigin[2]))
- {
- Game::Com_PrintMessage(0, Utils::VA("[!]: sscanf failed for submodel %d", p_index), 0);
- currBModel.cmSubmodelOrigin[0] = 0.0f;
- currBModel.cmSubmodelOrigin[1] = 0.0f;
- currBModel.cmSubmodelOrigin[2] = 0.0f;
- }
-
- bModels.push_back(currBModel);
- }
- }
- }
-
- return bModels;
- }
-
- void Entities::deleteWorldspawn()
- {
- for (auto i = this->entities.begin(); i != this->entities.end();)
- {
- if (i->find("classname") != i->end())
- {
- std::string classname = (*i)["classname"];
- if (Utils::StartsWith(classname, "worldspawn"))
- {
- i = this->entities.erase(i);
- continue;
- }
- }
-
- ++i;
- }
- }
-
- void Entities::deleteTriggers()
- {
- for (auto i = this->entities.begin(); i != this->entities.end();)
- {
- if (i->find("classname") != i->end())
- {
- std::string classname = (*i)["classname"];
- if (Utils::StartsWith(classname, "trigger_"))
- {
- i = this->entities.erase(i);
- continue;
- }
- }
-
- ++i;
- }
- }
-
- void Entities::deleteWeapons(bool keepTurrets)
- {
- for (auto i = this->entities.begin(); i != this->entities.end();)
- {
- if (i->find("weaponinfo") != i->end() || (i->find("targetname") != i->end() && (*i)["targetname"] == "oldschool_pickup"s))
- {
- if (!keepTurrets || i->find("classname") == i->end() || (*i)["classname"] != "misc_turret"s)
- {
- i = this->entities.erase(i);
- continue;
- }
- }
-
- ++i;
- }
- }
-
- void Entities::parse(std::string buffer)
- {
- int parseState = 0;
- std::string key;
- std::string value;
- std::unordered_map entity;
-
- for (unsigned int i = 0; i < buffer.size(); ++i)
- {
- char character = buffer[i];
- if (character == '{')
- {
- entity.clear();
- }
-
- switch (character)
- {
- case '{':
- {
- entity.clear();
- break;
- }
-
- case '}':
- {
- this->entities.push_back(entity);
- entity.clear();
- break;
- }
-
- case '"':
- {
- if (parseState == PARSE_AWAIT_KEY)
- {
- key.clear();
- parseState = PARSE_READ_KEY;
- }
- else if (parseState == PARSE_READ_KEY)
- {
- parseState = PARSE_AWAIT_VALUE;
- }
- else if (parseState == PARSE_AWAIT_VALUE)
- {
- value.clear();
- parseState = PARSE_READ_VALUE;
- }
- else if (parseState == PARSE_READ_VALUE)
- {
- entity[Utils::StrToLower(key)] = value;
- parseState = PARSE_AWAIT_KEY;
- }
- else
- {
- throw std::runtime_error("Parsing error!");
- }
- break;
- }
-
- default:
- {
- if (parseState == PARSE_READ_KEY) key.push_back(character);
- else if (parseState == PARSE_READ_VALUE) value.push_back(character);
-
- break;
- }
- }
- }
- }
-}
+#include "STDInclude.hpp"
+
+namespace Utils
+{
+ // build all entities (includes brushmodel pointers "*")
+ std::string Entities::buildAll()
+ {
+ std::string entityString;
+
+ for (auto& entity : this->entities)
+ {
+ entityString.append("{\n");
+
+ for (auto& property : entity)
+ {
+ entityString.push_back('"');
+ entityString.append(property.first);
+ entityString.append("\" \"");
+ entityString.append(property.second);
+ entityString.append("\"\n");
+ }
+
+ entityString.append("}\n");
+ }
+
+ return entityString;
+ }
+
+ // build all entities and fix brushmodels
+ std::string Entities::buildAll_script_structs()
+ {
+ std::string entityString;
+
+ for (auto& entity : this->entities)
+ {
+ std::string classname = entity["classname"];
+
+ // if ent is a brushmodel/submodel
+ if (!classname.empty() && Utils::Contains(classname, "script_struct"))
+ {
+ // start entity
+ //entityString.append(Utils::VA("// entity %d\n", entityNum));
+ entityString.append("{\n");
+
+ for (auto& property : entity)
+ {
+ entityString.push_back('"');
+ entityString.append(property.first);
+ entityString.append("\" \"");
+ entityString.append(property.second);
+ entityString.append("\"\n");
+ }
+
+ // close entity
+ entityString.append("}\n");
+ }
+ }
+
+ return entityString;
+ }
+
+ // build all entities and fix brushmodels
+ std::string Entities::buildAll_FixBrushmodels(const std::vector &bModelList)
+ {
+ int entityNum = 1; // worldspawn is 0
+ int submodelNum = 0;
+ std::string entityString;
+
+ for (auto& entity : this->entities)
+ {
+ std::string model = entity["model"];
+
+ // if ent is a brushmodel/submodel
+ if (!model.empty() && model[0] == '*')
+ {
+ // get the submodel index
+ auto p_index = std::stoi(model.erase(0, 1));
+
+ if (p_index < static_cast(bModelList.size()))
+ {
+ auto bModelSideCount = static_cast(bModelList[p_index].brushSides.size());
+
+ // skip submodel entity if we have less then 6 brushsides (we could also create a temp. cube at its origin)
+ if (bModelSideCount < 6)
+ {
+ continue;
+ }
+
+ // start submodel
+ entityString.append(Utils::VA("// submodel %d\n", submodelNum));
+ entityString.append("{\n");
+
+ // write submodel keys
+ for (auto& property : entity)
+ {
+ // do not write model/origin keys
+ if (property.first == "model" || property.first == "origin")
+ {
+ continue;
+ }
+
+ entityString.push_back('"');
+ entityString.append(property.first);
+ entityString.append("\" \"");
+ entityString.append(property.second);
+ entityString.append("\"\n");
+ }
+
+ // start submodel brush
+ entityString.append("{\n");
+
+ for (auto bSide = 0; bSide < bModelSideCount; bSide++)
+ {
+ entityString.append(bModelList[p_index].brushSides[bSide]);
+ }
+
+ // close submodel brush
+ entityString.append("}\n");
+
+ // close submodel
+ entityString.append("}\n");
+
+ submodelNum++;
+ }
+ }
+
+ else
+ {
+ // start entity
+ entityString.append(Utils::VA("// entity %d\n", entityNum));
+ entityString.append("{\n");
+
+ for (auto& property : entity)
+ {
+ entityString.push_back('"');
+ entityString.append(property.first);
+ entityString.append("\" \"");
+ entityString.append(property.second);
+ entityString.append("\"\n");
+ }
+
+ // close entity
+ entityString.append("}\n");
+
+ entityNum++;
+ }
+ }
+
+ return entityString;
+ }
+
+ // build all selected entities and fix brushmodels
+ std::string Entities::buildSelection_FixBrushmodels(const Game::boundingBox_t* box, const std::vector& bModelList)
+ {
+ int entityNum = 1; // worldspawn is 0
+ int submodelNum = 0;
+ std::string entityString;
+
+ for (auto& entity : this->entities)
+ {
+ std::string model = entity["model"];
+ std::string origin = entity["origin"];
+
+ // if ent is a brushmodel/submodel
+ if (!model.empty() && model[0] == '*')
+ {
+ // get the submodel index
+ auto p_index = std::stoi(model.erase(0, 1));
+
+ if (p_index < static_cast(bModelList.size()))
+ {
+ auto bModelSideCount = static_cast(bModelList[p_index].brushSides.size());
+
+ // skip submodel entity if we have less then 6 brushsides (we could also create a temp. cube at its origin)
+ if (bModelSideCount < 6)
+ {
+ continue;
+ }
+
+ // start submodel
+ entityString.append(Utils::VA("// submodel %d\n", submodelNum));
+ entityString.append("{\n");
+
+ // write submodel keys
+ for (auto& property : entity)
+ {
+ // do not write model/origin keys
+ if (property.first == "model" || property.first == "origin")
+ {
+ continue;
+ }
+
+ entityString.push_back('"');
+ entityString.append(property.first);
+ entityString.append("\" \"");
+ entityString.append(property.second);
+ entityString.append("\"\n");
+ }
+
+ // start submodel brush
+ entityString.append("{\n");
+
+ for (auto bSide = 0; bSide < bModelSideCount; bSide++)
+ {
+ entityString.append(bModelList[p_index].brushSides[bSide]);
+ }
+
+ // close submodel brush
+ entityString.append("}\n");
+
+ // close submodel
+ entityString.append("}\n");
+
+ submodelNum++;
+ }
+ }
+
+ else
+ {
+ float tempOrigin[3] = {0.0f, 0.0f, 0.0f};
+
+ if (!sscanf_s(origin.c_str(), "%f %f %f", &tempOrigin[0], &tempOrigin[1], &tempOrigin[2]))
+ {
+ Game::Com_PrintMessage(0, Utils::VA("[!]: sscanf failed for entity %d", entityNum), 0);
+ }
+
+ if (Utils::polylib::PointWithinBounds(glm::toVec3(tempOrigin), box->mins, box->maxs, 0.25f))
+ {
+ // start entity
+ entityString.append(Utils::VA("// entity %d\n", entityNum));
+ entityString.append("{\n");
+
+ for (auto& property : entity)
+ {
+ entityString.push_back('"');
+ entityString.append(property.first);
+ entityString.append("\" \"");
+ entityString.append(property.second);
+ entityString.append("\"\n");
+ }
+
+ // close entity
+ entityString.append("}\n");
+ entityNum++;
+ }
+ }
+ }
+
+ return entityString;
+ }
+
+ // only build worldspawn keys/values without opening/closing brackets
+ std::string Entities::buildWorldspawnKeys()
+ {
+ std::string entityString;
+
+ for (auto& entity : this->entities)
+ {
+ if (entity.find("classname") != entity.end())
+ {
+ if (entity["classname"] == "worldspawn"s)
+ {
+ for (auto& property : entity)
+ {
+ entityString.push_back('"');
+ entityString.append(property.first);
+ entityString.append("\" \"");
+ entityString.append(property.second);
+ entityString.append("\"\n");
+ }
+
+ break;
+ }
+ }
+ }
+
+ return entityString;
+ }
+
+ std::vector Entities::getModels()
+ {
+ std::vector models;
+
+ for (auto& entity : this->entities)
+ {
+ if (entity.find("model") != entity.end())
+ {
+ std::string model = entity["model"];
+
+ if (!model.empty() && model[0] != '*' && model[0] != '?') // Skip brushmodels
+ {
+ if (std::find(models.begin(), models.end(), model) == models.end())
+ {
+ models.push_back(model);
+ }
+ }
+ }
+ }
+
+ return models;
+ }
+
+ std::vector Entities::getBrushModels()
+ {
+ std::vector bModels;
+
+ // geting the total clipmap size would prob. be better
+ uintptr_t leafBrushesStart = reinterpret_cast(&*Game::cm->leafbrushNodes);
+ uintptr_t leafBrushesEnd = leafBrushesStart + sizeof(Game::cLeafBrushNode_s) * (Game::cm->leafbrushNodesCount + Game::cm->numLeafBrushes); // wrong
+
+ // first element is always empty because
+ // the first submodel within the entsMap starts at 1 and we want to avoid subtracting - 1 everywhere
+ bModels.push_back(Game::brushmodelEnt_t());
+
+ for (auto& entity : this->entities)
+ {
+ if (entity.find("model") != entity.end())
+ {
+ std::string model = entity["model"];
+ std::string origin = entity["origin"];
+
+ // if ent is a brushmodel/submodel
+ if (!model.empty() && model[0] == '*' && !origin.empty())
+ {
+ auto currBModel = Game::brushmodelEnt_t();
+
+ // get the submodel index
+ auto p_index = std::stoi(model.erase(0, 1));
+
+ // the index should always match the size of our vector or we did something wrong
+ if (p_index != (int)bModels.size())
+ {
+ Game::Com_PrintMessage(0, Utils::VA("[Entities::getBrushModels]: Something went wrong while parsing submodels. (%d != %d)", p_index, bModels.size()), 0);
+ }
+
+ if (p_index >= static_cast(Game::cm->numSubModels))
+ {
+ Game::Com_PrintMessage(0, Utils::VA("[Entities::getBrushModels]: Something went wrong while parsing submodels. (%d >= %d numSubModels)", p_index, Game::cm->numSubModels), 0);
+ break;
+ }
+
+ // assign indices and pointers to both the brush and the submodel
+ currBModel.cmSubmodelIndex = p_index;
+
+ if (&Game::cm->cmodels[p_index])
+ {
+ currBModel.cmSubmodel = &Game::cm->cmodels[p_index];
+ }
+
+ // fix me daddy
+ auto brushIdx_ptr = Game::cm->leafbrushNodes[Game::cm->cmodels[p_index].leaf.leafBrushNode].data.leaf.brushes;
+ currBModel.cmBrushIndex = 0;
+
+ // this is giving me cancer
+ if (Game::cm->cmodels[p_index].leaf.leafBrushNode != 0 && brushIdx_ptr)
+ {
+ if ((uintptr_t)&*brushIdx_ptr >= leafBrushesStart && (uintptr_t) & *brushIdx_ptr < leafBrushesEnd)
+ {
+ currBModel.cmBrushIndex = static_cast(*Game::cm->leafbrushNodes[Game::cm->cmodels[p_index].leaf.leafBrushNode].data.leaf.brushes);
+ }
+ else
+ {
+ Game::Com_PrintMessage(0, Utils::VA("[Entities::getBrushModels]: Skipping faulty brush-index pointer at leafbrushNodes[%d].data.leaf.brushes ...\n", p_index), 0);
+ }
+
+ //currBModel.cmBrush = &Game::cm->brushes[*Game::cm->leafbrushNodes[Game::cm->cmodels[p_index].leaf.leafBrushNode].data.leaf.brushes];
+ currBModel.cmBrush = &Game::cm->brushes[currBModel.cmBrushIndex];
+
+ // add the submodel index to the clipmap brush
+ currBModel.cmBrush->isSubmodel = true;
+ currBModel.cmBrush->cmSubmodelIndex = static_cast<__int16>(p_index);
+ }
+
+
+ // save entity origin
+ if (!sscanf_s(origin.c_str(), "%f %f %f", &currBModel.cmSubmodelOrigin[0], &currBModel.cmSubmodelOrigin[1], &currBModel.cmSubmodelOrigin[2]))
+ {
+ Game::Com_PrintMessage(0, Utils::VA("[!]: sscanf failed for submodel %d", p_index), 0);
+ currBModel.cmSubmodelOrigin[0] = 0.0f;
+ currBModel.cmSubmodelOrigin[1] = 0.0f;
+ currBModel.cmSubmodelOrigin[2] = 0.0f;
+ }
+
+ bModels.push_back(currBModel);
+ }
+ }
+ }
+
+ return bModels;
+ }
+
+ void Entities::deleteWorldspawn()
+ {
+ for (auto i = this->entities.begin(); i != this->entities.end();)
+ {
+ if (i->find("classname") != i->end())
+ {
+ std::string classname = (*i)["classname"];
+ if (Utils::StartsWith(classname, "worldspawn"))
+ {
+ i = this->entities.erase(i);
+ continue;
+ }
+ }
+
+ ++i;
+ }
+ }
+
+ void Entities::deleteTriggers()
+ {
+ for (auto i = this->entities.begin(); i != this->entities.end();)
+ {
+ if (i->find("classname") != i->end())
+ {
+ std::string classname = (*i)["classname"];
+ if (Utils::StartsWith(classname, "trigger_"))
+ {
+ i = this->entities.erase(i);
+ continue;
+ }
+ }
+
+ ++i;
+ }
+ }
+
+ void Entities::deleteWeapons(bool keepTurrets)
+ {
+ for (auto i = this->entities.begin(); i != this->entities.end();)
+ {
+ if (i->find("weaponinfo") != i->end() || (i->find("targetname") != i->end() && (*i)["targetname"] == "oldschool_pickup"s))
+ {
+ if (!keepTurrets || i->find("classname") == i->end() || (*i)["classname"] != "misc_turret"s)
+ {
+ i = this->entities.erase(i);
+ continue;
+ }
+ }
+
+ ++i;
+ }
+ }
+
+ void Entities::parse(std::string buffer)
+ {
+ int parseState = 0;
+ std::string key;
+ std::string value;
+ std::unordered_map entity;
+
+ for (unsigned int i = 0; i < buffer.size(); ++i)
+ {
+ char character = buffer[i];
+ if (character == '{')
+ {
+ entity.clear();
+ }
+
+ switch (character)
+ {
+ case '{':
+ {
+ entity.clear();
+ break;
+ }
+
+ case '}':
+ {
+ this->entities.push_back(entity);
+ entity.clear();
+ break;
+ }
+
+ case '"':
+ {
+ if (parseState == PARSE_AWAIT_KEY)
+ {
+ key.clear();
+ parseState = PARSE_READ_KEY;
+ }
+ else if (parseState == PARSE_READ_KEY)
+ {
+ parseState = PARSE_AWAIT_VALUE;
+ }
+ else if (parseState == PARSE_AWAIT_VALUE)
+ {
+ value.clear();
+ parseState = PARSE_READ_VALUE;
+ }
+ else if (parseState == PARSE_READ_VALUE)
+ {
+ entity[Utils::StrToLower(key)] = value;
+ parseState = PARSE_AWAIT_KEY;
+ }
+ else
+ {
+ throw std::runtime_error("Parsing error!");
+ }
+ break;
+ }
+
+ default:
+ {
+ if (parseState == PARSE_READ_KEY) key.push_back(character);
+ else if (parseState == PARSE_READ_VALUE) value.push_back(character);
+
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Utils/Entities.hpp b/src/Utils/Entities.hpp
index 50a7c0d..8861fd7 100644
--- a/src/Utils/Entities.hpp
+++ b/src/Utils/Entities.hpp
@@ -1,37 +1,38 @@
-#pragma once
-
-namespace Utils
-{
- class Entities
- {
- public:
- Entities() {};
- Entities(const char* string, size_t lenPlusOne) : Entities(std::string(string, lenPlusOne - 1)) {}
- Entities(std::string buffer) : Entities() { this->parse(buffer); };
- Entities(const Entities &obj) : entities(obj.entities) {};
-
- std::string buildAll();
- std::string buildAll_FixBrushmodels(const std::vector& bModelList);
- std::string buildSelection_FixBrushmodels(const Game::boundingBox_t* box, const std::vector& bModelList);
- std::string buildWorldspawnKeys();
-
- std::vector getModels();
- std::vector getBrushModels();
-
- void deleteWorldspawn();
- void deleteTriggers();
- void deleteWeapons(bool keepTurrets);
-
- private:
- enum
- {
- PARSE_AWAIT_KEY,
- PARSE_READ_KEY,
- PARSE_AWAIT_VALUE,
- PARSE_READ_VALUE,
- };
-
- std::vector> entities;
- void parse(std::string buffer);
- };
-}
+#pragma once
+
+namespace Utils
+{
+ class Entities
+ {
+ public:
+ Entities() {};
+ Entities(const char* string, size_t lenPlusOne) : Entities(std::string(string, lenPlusOne - 1)) {}
+ Entities(std::string buffer) : Entities() { this->parse(buffer); };
+ Entities(const Entities &obj) : entities(obj.entities) {};
+
+ std::string buildAll();
+ std::string buildAll_FixBrushmodels(const std::vector& bModelList);
+ std::string buildSelection_FixBrushmodels(const Game::boundingBox_t* box, const std::vector& bModelList);
+ std::string buildAll_script_structs();
+ std::string buildWorldspawnKeys();
+
+ std::vector getModels();
+ std::vector getBrushModels();
+
+ void deleteWorldspawn();
+ void deleteTriggers();
+ void deleteWeapons(bool keepTurrets);
+
+ private:
+ enum
+ {
+ PARSE_AWAIT_KEY,
+ PARSE_READ_KEY,
+ PARSE_AWAIT_VALUE,
+ PARSE_READ_VALUE,
+ };
+
+ std::vector> entities;
+ void parse(std::string buffer);
+ };
+}
diff --git a/src/defines.hpp b/src/defines.hpp
index 9b2c729..3f5a08b 100644
--- a/src/defines.hpp
+++ b/src/defines.hpp
@@ -17,9 +17,9 @@
// *
// Version
-#define IW3XO_BUILDVERSION_DATE __TIMESTAMP__
-#define IW3XO_BUILDSTRING "IW3xo :: build %.lf :: %s", IW3X_BUILDNUMBER, __TIMESTAMP__
-#define IW3XO_BUILDSTRING_CHANGELOG "IW3xo :: build %.lf :: %s %s", IW3X_BUILDNUMBER, __TIMESTAMP__
+#define IW3XO_BUILDVERSION_DATE __DATE__
+#define IW3XO_BUILDSTRING "IW3xo :: build %.lf :: %s", IW3X_BUILDNUMBER, __DATE__
+#define IW3XO_BUILDSTRING_CHANGELOG "IW3xo :: build %.lf :: %s %s", IW3X_BUILDNUMBER, __DATE__
// *
// Gui
diff --git a/src/version.hpp b/src/version.hpp
index 7427626..a08d422 100644
--- a/src/version.hpp
+++ b/src/version.hpp
@@ -1,3 +1,3 @@
/* Automatically generated by premake5. */
-#define IW3X_BUILDNUMBER 3427.0
+#define IW3X_BUILDNUMBER 3428.0