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