diff --git a/game/neo/resource/ClientScheme.res b/game/neo/resource/ClientScheme.res index c3f9b7d57..73f97f7de 100644 --- a/game/neo/resource/ClientScheme.res +++ b/game/neo/resource/ClientScheme.res @@ -282,13 +282,31 @@ Scheme "5" { "name" "Verdana" - "tall" "16" [!$LINUX] //"24" - "tall" "20" [$LINUX] + "tall" "18" [!$LINUX] //"24" + "tall" "22" [$LINUX] "weight" "900" "antialias" "1" - "yres" "1200 10000" + "yres" "1200 1599" } "6" + { + "name" "Verdana" + "tall" "22" [!$LINUX] //"24" + "tall" "26" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "1600 2099" + } + "7" + { + "name" "Verdana" + "tall" "26" [!$LINUX] //"24" + "tall" "32" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "2100 10000" + } + "8" { "name" "Verdana" "tall" "12" [!$LINUX] @@ -296,7 +314,7 @@ Scheme "range" "0x0000 0x00FF" "weight" "900" } - "7" + "9" { "name" "Arial" "tall" "12" @@ -403,194 +421,872 @@ Scheme "3" { "name" "Verdana" - "tall" "14" [!$LINUX] - "tall" "16" [$LINUX] - "weight" "0" - "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "tall" "14" [!$LINUX] + "tall" "16" [$LINUX] + "weight" "0" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "768 1023" + "antialias" "1" + } + "4" + { + "name" "Verdana" + "tall" "16" [!$LINUX] //"20" + "tall" "18" [$LINUX] + "weight" "0" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "1024 1199" + "antialias" "1" + } + "5" + { + "name" "Verdana" + "tall" "16" [!$LINUX] //"24" + "tall" "18" [$LINUX] + "weight" "0" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "1200 6000" + "antialias" "1" + } + "6" + { + "name" "Verdana" + "tall" "12" [!$LINUX] + "tall" "13" [$LINUX] + "range" "0x0000 0x00FF" + "weight" "0" + } + "7" + { + "name" "Arial" + "tall" "11" + "range" "0x0000 0x00FF" + "weight" "0" + } + } + // For the class, loadout, and team menu to use how the Default font used to be + // because those menu are very small for HiDPI res like 1440/4k + "CLTDefault" + { + "1" + { + "name" "Verdana" + "tall" "12" [!$LINUX] + "tall" "15" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "480 599" + } + "2" + { + "name" "Verdana" + "tall" "13" [!$LINUX] + "tall" "16" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "600 767" + } + "3" + { + "name" "Verdana" + "tall" "14" [!$LINUX] + "tall" "18" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "768 1023" + } + "4" + { + "name" "Verdana" + "tall" "16" [!$LINUX] //"20" + "tall" "20" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "1024 1199" + } + "5" + { + "name" "Verdana" + "tall" "18" [!$LINUX] //"24" + "tall" "22" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "1200 10000" + } + "6" + { + "name" "Verdana" + "tall" "12" [!$LINUX] + "tall" "15" [$LINUX] + "range" "0x0000 0x00FF" + "weight" "900" + } + "7" + { + "name" "Arial" + "tall" "12" + "range" "0x0000 0x00FF" + "weight" "800" + } + } + "CLTDefaultBold" + { + "1" + { + "name" "Verdana" + "tall" "12" [!$LINUX] + "tall" "15" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "480 599" + } + "2" + { + "name" "Verdana" + "tall" "13" [!$LINUX] + "tall" "16" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "600 767" + } + "3" + { + "name" "Verdana" + "tall" "14" [!$LINUX] + "tall" "18" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "768 1023" + } + "4" + { + "name" "Verdana" + "tall" "16" [!$LINUX] //"20" + "tall" "20" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "1024 1199" + } + "5" + { + "name" "Verdana" + "tall" "18" [!$LINUX] //"24" + "tall" "22" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "1200 10000" + } + "6" + { + "name" "Verdana" + "tall" "12" [!$LINUX] + "tall" "15" [$LINUX] + "range" "0x0000 0x00FF" + "weight" "900" + } + "7" + { + "name" "Arial" + "tall" "12" + "range" "0x0000 0x00FF" + "weight" "800" + } + } + HudHintText + { + "1" + { + "name" "Verdana" + "tall" "12" [!$LINUX] + "tall" "15" [$LINUX] + "weight" "700" + "yres" "480 599" + } + "2" + { + "name" "Verdana" + "tall" "13" [!$LINUX] + "tall" "16" [$LINUX] + "weight" "700" + "yres" "600 767" + } + "3" + { + "name" "Verdana" + "tall" "14" [!$LINUX] + "tall" "18" [$LINUX] + "weight" "700" + "yres" "768 1023" + } + "4" + { + "name" "Verdana" + "tall" "20" [!$LINUX] + "tall" "26" [$LINUX] + "weight" "700" + "yres" "1024 1199" + } + "5" + { + "name" "Verdana" + "tall" "24" [!$LINUX] + "tall" "32" [$LINUX] + "weight" "700" + "yres" "1200 10000" + } + } + HudHintTextLarge + { + "1" + { + "name" "xscale" + "tall" "14" + "weight" "1000" + "antialias" "1" + "additive" "1" + } + } + HudHintTextSmall + { + "1" + { + "name" "xscale" + "tall" "14" + "weight" "0" + "antialias" "1" + "additive" "1" + } + } + HudNumbersSmall + { + "1" + { + "name" "Arial" + "tall" "16" + "weight" "1000" + "additive" "1" + "antialias" "1" + "range" "0x0000 0x017F" + } + } + + HudSelectionNumbers + { + "1" + { + "name" "Verdana" + "tall" "11" [!$LINUX] + "tall" "14" [$LINUX] + "weight" "700" + "antialias" "1" + "additive" "1" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + } + } + + HudSelectionText + { + "1" + { + "name" "Verdana" + "tall" "8" [!$LINUX] + "tall" "10" [$LINUX] + "weight" "700" + "antialias" "1" + "yres" "1 599" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "additive" "1" + } + "2" + { + "name" "Verdana" + "tall" "10" [!$LINUX] + "tall" "13" [$LINUX] + "weight" "700" + "antialias" "1" + "yres" "600 767" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "additive" "1" + } + "3" + { + "name" "Verdana" + "tall" "12" [!$LINUX] + "tall" "15" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "768 1023" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + } + "4" + { + "name" "Verdana" + "tall" "16" [!$LINUX] + "tall" "20" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "1024 1199" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + } + "5" + { + "name" "Verdana" + "tall" "17" [!$LINUX] + "tall" "22" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "1200 1599" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + } + "6" + { + "name" "Verdana" + "tall" "20" [!$LINUX] + "tall" "24" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "1600 2099" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + } + "7" + { + "name" "Verdana" + "tall" "24" [!$LINUX] + "tall" "28" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "2100 10000" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + } + } + + NHudSpectatorOverlayName + { + "1" + { + "name" "Verdana" + "tall" "9" [!$LINUX] + "tall" "12" [$LINUX] + "weight" "700" + "antialias" "1" + "yres" "1 599" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "2" + { + "name" "Verdana" + "tall" "11" [!$LINUX] + "tall" "14" [$LINUX] + "weight" "700" + "antialias" "1" + "yres" "600 767" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "3" + { + "name" "Verdana" + "tall" "14" [!$LINUX] + "tall" "18" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "768 1023" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "4" + { + "name" "Verdana" + "tall" "16" [!$LINUX] + "tall" "20" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "1024 1199" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "5" + { + "name" "Verdana" + "tall" "18" [!$LINUX] + "tall" "24" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "1200 1599" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "6" + { + "name" "Verdana" + "tall" "26" [!$LINUX] + "tall" "32" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "1600 10000" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + } + + NHudSpectatorOverlayInfo + { + "1" + { + "name" "Verdana" + "tall" "8" [!$LINUX] + "tall" "10" [$LINUX] + "weight" "700" + "antialias" "1" + "yres" "1 599" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "additive" "1" + } + "2" + { + "name" "Verdana" + "tall" "10" [!$LINUX] + "tall" "13" [$LINUX] + "weight" "700" + "antialias" "1" + "yres" "600 767" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "additive" "1" + } + "3" + { + "name" "Verdana" + "tall" "12" [!$LINUX] + "tall" "15" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "768 1023" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + } + "4" + { + "name" "Verdana" + "tall" "16" [!$LINUX] + "tall" "20" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "1024 1199" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + } + "5" + { + "name" "Verdana" + "tall" "17" [!$LINUX] + "tall" "22" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "1200 1599" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + } + "6" + { + "name" "Verdana" + "tall" "24" [!$LINUX] + "tall" "28" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "1600 10000" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + } + } + + NHudSpectatorOverlayClass + { + "1" + { + "name" "Verdana" + "tall" "8" [!$LINUX] + "tall" "10" [$LINUX] + "weight" "500" + "antialias" "1" + "yres" "1 599" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "2" + { + "name" "Verdana" + "tall" "9" [!$LINUX] + "tall" "12" [$LINUX] + "weight" "500" + "antialias" "1" + "yres" "600 767" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "3" + { + "name" "Verdana" + "tall" "11" [!$LINUX] + "tall" "14" [$LINUX] + "weight" "700" + "antialias" "1" + "yres" "768 1023" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "4" + { + "name" "Verdana" + "tall" "12" [!$LINUX] + "tall" "16" [$LINUX] + "weight" "700" + "antialias" "1" + "yres" "1024 1199" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "5" + { + "name" "Verdana" + "tall" "16" [!$LINUX] + "tall" "20" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "1200 1599" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "6" + { + "name" "Verdana" + "tall" "24" [!$LINUX] + "tall" "28" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "1600 10000" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + } + + NHudSpectatorOverlayRoundKillHPFront + { + "1" + { + "name" "Verdana" + "tall" "12" [!$LINUX] + "tall" "14" [$LINUX] + "weight" "700" + "antialias" "1" + "yres" "1 599" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "2" + { + "name" "Verdana" + "tall" "14" [!$LINUX] + "tall" "16" [$LINUX] + "weight" "700" + "antialias" "1" + "yres" "600 767" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "3" + { + "name" "Verdana" + "tall" "16" [!$LINUX] + "tall" "20" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "768 1023" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "4" + { + "name" "Verdana" + "tall" "20" [!$LINUX] + "tall" "24" [$LINUX] + "weight" "900" + "antialias" "1" + "yres" "1024 1199" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "5" + { + "name" "Verdana" + "tall" "22" [!$LINUX] + "tall" "26" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "1200 1599" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "6" + { + "name" "Verdana" + "tall" "26" [!$LINUX] + "tall" "32" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "1600 2099" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + "7" + { + "name" "Verdana" + "tall" "36" [!$LINUX] + "tall" "42" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "2100 10000" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "dropshadow" "1" + } + } + + NHudSpectatorOverlayRoundKillHPBack + { + "1" + { + "name" "Verdana" + "tall" "12" [!$LINUX] + "tall" "14" [$LINUX] + "weight" "700" + "antialias" "1" + "yres" "1 599" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "outline" "1" + "blur" "1" + } + "2" + { + "name" "Verdana" + "tall" "14" [!$LINUX] + "tall" "16" [$LINUX] + "weight" "700" + "antialias" "1" + "yres" "600 767" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "outline" "1" + "blur" "1" + } + "3" + { + "name" "Verdana" + "tall" "16" [!$LINUX] + "tall" "20" [$LINUX] + "weight" "900" + "antialias" "1" "yres" "768 1023" - "antialias" "1" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "outline" "1" + "blur" "1" } "4" { "name" "Verdana" - "tall" "16" [!$LINUX] //"20" - "tall" "18" [$LINUX] - "weight" "0" - "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "tall" "20" [!$LINUX] + "tall" "24" [$LINUX] + "weight" "900" + "antialias" "1" "yres" "1024 1199" - "antialias" "1" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "outline" "1" + "blur" "1" } "5" { "name" "Verdana" - "tall" "16" [!$LINUX] //"24" - "tall" "18" [$LINUX] - "weight" "0" + "tall" "22" [!$LINUX] + "tall" "26" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "1200 1599" "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A - "yres" "1200 6000" - "antialias" "1" + "outline" "1" + "blur" "1" } "6" { "name" "Verdana" - "tall" "12" [!$LINUX] - "tall" "13" [$LINUX] - "range" "0x0000 0x00FF" - "weight" "0" + "tall" "26" [!$LINUX] + "tall" "32" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "1600 2099" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "outline" "1" + "blur" "1" } "7" { - "name" "Arial" - "tall" "11" - "range" "0x0000 0x00FF" - "weight" "0" + "name" "Verdana" + "tall" "36" [!$LINUX] + "tall" "42" [$LINUX] + "weight" "1000" + "antialias" "1" + "yres" "2100 10000" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "outline" "1" + "blur" "1" } } - HudHintText + + + NHudSpectatorOverlayGhost { "1" { - "name" "Verdana" - "tall" "12" [!$LINUX] - "tall" "15" [$LINUX] - "weight" "700" + "name" "killfeedicons" + "tall" "28" + "weight" "600" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A "yres" "480 599" + "additive" "0" } "2" { - "name" "Verdana" - "tall" "13" [!$LINUX] - "tall" "16" [$LINUX] - "weight" "700" + "name" "killfeedicons" + "tall" "32" + "weight" "600" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A "yres" "600 767" + "additive" "0" } "3" { - "name" "Verdana" - "tall" "14" [!$LINUX] - "tall" "18" [$LINUX] - "weight" "700" + "name" "killfeedicons" + "tall" "36" + "weight" "600" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A "yres" "768 1023" + "antialias" "1" + "additive" "0" } "4" { - "name" "Verdana" - "tall" "20" [!$LINUX] - "tall" "26" [$LINUX] - "weight" "700" + "name" "killfeedicons" + "tall" "40" + "weight" "600" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A "yres" "1024 1199" + "antialias" "1" + "additive" "0" } "5" { - "name" "Verdana" - "tall" "24" [!$LINUX] - "tall" "32" [$LINUX] - "weight" "700" - "yres" "1200 10000" - } - } - HudHintTextLarge - { - "1" - { - "name" "xscale" - "tall" "14" - "weight" "1000" - "antialias" "1" - "additive" "1" + "name" "killfeedicons" + "tall" "44" + "weight" "600" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "1200 1440" + "antialias" "1" + "additive" "0" } - } - HudHintTextSmall - { - "1" + "6" { - "name" "xscale" - "tall" "14" - "weight" "0" - "antialias" "1" - "additive" "1" + "name" "killfeedicons" + "tall" "48" + "weight" "600" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "1441 1599" + "antialias" "1" + "additive" "0" } - } - HudNumbersSmall - { - "1" + "7" { - "name" "Arial" - "tall" "16" - "weight" "1000" - "additive" "1" - "antialias" "1" - "range" "0x0000 0x017F" + "name" "killfeedicons" + "tall" "52" + "weight" "600" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "1600 2099" + "antialias" "1" + "additive" "0" } - } - - HudSelectionNumbers - { - "1" + "8" { - "name" "Verdana" - "tall" "11" [!$LINUX] - "tall" "14" [$LINUX] - "weight" "700" - "antialias" "1" - "additive" "1" + "name" "killfeedicons" + "tall" "62" + "weight" "600" "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "2100 6000" + "antialias" "1" + "additive" "0" } } - HudSelectionText + NHudSpectatorOverlaySmallWeapons { "1" { - "name" "Verdana" - "tall" "8" [!$LINUX] - "tall" "10" [$LINUX] - "weight" "700" - "antialias" "1" - "yres" "1 599" + "name" "killfeedicons" + "tall" "9" + "weight" "600" "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A - "additive" "1" + "yres" "480 599" + "additive" "0" } "2" { - "name" "Verdana" - "tall" "10" [!$LINUX] - "tall" "13" [$LINUX] - "weight" "700" - "antialias" "1" - "yres" "600 767" + "name" "killfeedicons" + "tall" "10" + "weight" "600" "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A - "additive" "1" + "yres" "600 767" + "additive" "0" } "3" { - "name" "Verdana" - "tall" "12" [!$LINUX] - "tall" "15" [$LINUX] - "weight" "900" - "antialias" "1" - "yres" "768 1023" + "name" "killfeedicons" + "tall" "12" + "weight" "600" "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "768 1023" + "antialias" "1" + "additive" "0" } "4" { - "name" "Verdana" - "tall" "16" [!$LINUX] - "tall" "20" [$LINUX] - "weight" "900" - "antialias" "1" - "yres" "1024 1199" + "name" "killfeedicons" + "tall" "14" + "weight" "600" "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "1024 1199" + "antialias" "1" + "additive" "0" } "5" { - "name" "Verdana" - "tall" "17" [!$LINUX] - "tall" "22" [$LINUX] - "weight" "1000" - "antialias" "1" - "yres" "1200 10000" + "name" "killfeedicons" + "tall" "16" + "weight" "600" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "1200 1440" + "antialias" "1" + "additive" "0" + } + "6" + { + "name" "killfeedicons" + "tall" "22" + "weight" "600" "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "1441 1599" + "antialias" "1" + "additive" "0" + } + "7" + { + "name" "killfeedicons" + "tall" "26" + "weight" "600" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "1600 6000" + "antialias" "1" + "additive" "0" } } @@ -1385,7 +2081,7 @@ Scheme "7" { "name" "killfeedicons" - "tall" "28" + "tall" "32" "weight" "600" "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A "yres" "1600 6000" @@ -2507,7 +3203,25 @@ Scheme "tall" "15" "weight" "0" "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A - "yres" "1200 6000" + "yres" "1200 1599" + "antialias" "1" + } + "4" + { + "name" "Verdana" + "tall" "18" + "weight" "0" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "1600 2099" + "antialias" "1" + } + "5" + { + "name" "Verdana" + "tall" "24" + "weight" "0" + "range" "0x0000 0x017F" // Basic Latin, Latin-1 Supplement, Latin Extended-A + "yres" "2100 6000" "antialias" "1" } } diff --git a/game/neo/resource/neo_ui/Neo_ClassMenu.res b/game/neo/resource/neo_ui/Neo_ClassMenu.res index 5d7c9e3ac..1c29c350c 100644 --- a/game/neo/resource/neo_ui/Neo_ClassMenu.res +++ b/game/neo/resource/neo_ui/Neo_ClassMenu.res @@ -133,7 +133,7 @@ "textAlignment" "center" "dulltext" "0" "brighttext" "1" - "font" "Default" + "font" "CLTDefault" "wrap" "0" } "Scout_Button" @@ -153,7 +153,7 @@ "textAlignment" "center" "dulltext" "0" "brighttext" "0" - "font" "Default" + "font" "CLTDefault" "wrap" "0" "Command" "setclass 1" "Default" "0" @@ -175,7 +175,7 @@ "textAlignment" "center" "dulltext" "0" "brighttext" "1" - "font" "Default" + "font" "CLTDefault" "wrap" "0" } "Assault_Button" @@ -216,7 +216,7 @@ "textAlignment" "center" "dulltext" "0" "brighttext" "1" - "font" "Default" + "font" "CLTDefault" "wrap" "0" } "Heavy_Button" @@ -308,7 +308,7 @@ "textAlignment" "center" "dulltext" "0" "brighttext" "0" - "font" "Default" + "font" "CLTDefault" "wrap" "0" "Command" "playerstate_reverse" "Default" "0" diff --git a/game/neo/resource/neo_ui/Neo_LoadoutMenu_Other.res b/game/neo/resource/neo_ui/Neo_LoadoutMenu_Other.res index 1763ff630..e2e022013 100644 --- a/game/neo/resource/neo_ui/Neo_LoadoutMenu_Other.res +++ b/game/neo/resource/neo_ui/Neo_LoadoutMenu_Other.res @@ -546,7 +546,7 @@ "textAlignment" "center" "dulltext" "0" "brighttext" "0" - "font" "Default" + "font" "CLTDefault" "wrap" "0" "Command" "playerstate_reverse" "Default" "0" diff --git a/game/neo/resource/neo_ui/Neo_TeamMenu.res b/game/neo/resource/neo_ui/Neo_TeamMenu.res index 3679f0a9c..6ec0293a8 100644 --- a/game/neo/resource/neo_ui/Neo_TeamMenu.res +++ b/game/neo/resource/neo_ui/Neo_TeamMenu.res @@ -114,7 +114,7 @@ "textAlignment" "east" "dulltext" "0" "brighttext" "0" - "font" "DefaultBold" + "font" "CLTDefaultBold" "wrap" "0" } "neo_IconPanel4" @@ -151,7 +151,7 @@ "textAlignment" "west" "dulltext" "0" "brighttext" "0" - "font" "DefaultBold" + "font" "CLTDefaultBold" "wrap" "0" } "neo_jscorelabel" @@ -171,7 +171,7 @@ "textAlignment" "west" "dulltext" "0" "brighttext" "0" - "font" "DefaultBold" + "font" "CLTDefaultBold" "wrap" "0" } "neo_Divider1" @@ -205,7 +205,7 @@ "textAlignment" "west" "dulltext" "0" "brighttext" "0" - "font" "DefaultBold" + "font" "CLTDefaultBold" "wrap" "0" } "neo_nscorelabel" @@ -225,7 +225,7 @@ "textAlignment" "west" "dulltext" "0" "brighttext" "0" - "font" "DefaultBold" + "font" "CLTDefaultBold" "wrap" "0" } "neo_jinraibutton" @@ -245,7 +245,7 @@ "textAlignment" "center" "dulltext" "0" "brighttext" "0" - "font" "DefaultBold" + "font" "CLTDefaultBold" "wrap" "0" "command" "jointeam 2" "Default" "0" @@ -267,7 +267,7 @@ "textAlignment" "center" "dulltext" "0" "brighttext" "0" - "font" "DefaultBold" + "font" "CLTDefaultBold" "wrap" "0" "command" "jointeam 3" "Default" "0" @@ -289,7 +289,7 @@ "textAlignment" "center" "dulltext" "0" "brighttext" "0" - "font" "DefaultBold" + "font" "CLTDefaultBold" "wrap" "0" "command" "jointeam a" "Default" "0" @@ -311,7 +311,7 @@ "textAlignment" "center" "dulltext" "0" "brighttext" "0" - "font" "DefaultBold" + "font" "CLTDefaultBold" "wrap" "0" "command" "jointeam 1" "Default" "0" @@ -333,7 +333,7 @@ "textAlignment" "center" "dulltext" "0" "brighttext" "0" - "font" "DefaultBold" + "font" "CLTDefaultBold" "wrap" "0" "command" "vguicancel" "Default" "1" diff --git a/game/neo/scripts/HudLayout.res b/game/neo/scripts/HudLayout.res index d692ff1c0..363158d81 100644 --- a/game/neo/scripts/HudLayout.res +++ b/game/neo/scripts/HudLayout.res @@ -1013,4 +1013,17 @@ "box_color" "20 20 20 0" "text_color" "255 255 255 255" } + + neo_spectator_overlay + { + "fieldName" "neo_spectator_overlay" + "NameFont" "NHudSpectatorOverlayName" + "InfoFont" "NHudSpectatorOverlayInfo" + "ClassFont" "NHudSpectatorOverlayClass" + "RKHPFont" "NHudSpectatorOverlayRoundKillHPFront" + "RKHPBackFont" "NHudSpectatorOverlayRoundKillHPBack" + "GhostFont" "NHudSpectatorOverlayGhost" + "SmallWeaponsFont" "NHudSpectatorOverlaySmallWeapons" + "DeadTexture" "vgui/hud/kill_kill" + } } diff --git a/src/game/client/CMakeLists.txt b/src/game/client/CMakeLists.txt index 7fbb698e4..317cbb87a 100644 --- a/src/game/client/CMakeLists.txt +++ b/src/game/client/CMakeLists.txt @@ -1667,11 +1667,13 @@ set(UNITY_SOURCE_NEO_UI neo/ui/neo_hud_message.cpp neo/ui/neo_hud_player_ping.cpp neo/ui/neo_hud_round_state.cpp + neo/ui/neo_hud_spectator_overlay.cpp neo/ui/neo_hud_startup_sequence.cpp neo/ui/neo_hud_worldpos_marker.cpp neo/ui/neo_hud_worldpos_marker_generic.cpp neo/ui/neo_scoreboard.cpp neo/ui/neo_hud_context_hint.cpp + neo/ui/neo_spec_player_by_hud.cpp ) set_source_files_properties( @@ -1694,6 +1696,7 @@ target_sources_grouped( neo/ui/neo_hud_ammo.h neo/ui/neo_hud_childelement.h neo/ui/neo_hud_compass.h + neo/ui/neo_hud_deathnotice.h neo/ui/neo_hud_friendly_marker.h neo/ui/neo_hud_game_event.h neo/ui/neo_hud_ghost_beacons.h @@ -1706,6 +1709,7 @@ target_sources_grouped( neo/ui/neo_hud_message.h neo/ui/neo_hud_player_ping.h neo/ui/neo_hud_round_state.h + neo/ui/neo_hud_spectator_overlay.h neo/ui/neo_hud_startup_sequence.h neo/ui/neo_hud_worldpos_marker.h neo/ui/neo_hud_worldpos_marker_generic.h diff --git a/src/game/client/hud.cpp b/src/game/client/hud.cpp index 6269bd848..824b35c2a 100644 --- a/src/game/client/hud.cpp +++ b/src/game/client/hud.cpp @@ -500,7 +500,7 @@ void CHud::InitFonts() #ifdef NEO g_hFontKillfeed = pScheme->GetFont("NHudOCRSmallNoAdditive", true); - g_hFontKillfeedIcons = pScheme->GetFont("NHudKillFeedIcons", true); + g_hFontKillfeedIcons = pScheme->GetFont("NHudKillfeedIcons", true); if (g_hFontKillfeed == vgui::INVALID_FONT) { diff --git a/src/game/client/neo/c_neo_killer_damage_infos.h b/src/game/client/neo/c_neo_killer_damage_infos.h index cbabf40de..6bd7a984b 100644 --- a/src/game/client/neo/c_neo_killer_damage_infos.h +++ b/src/game/client/neo/c_neo_killer_damage_infos.h @@ -3,8 +3,6 @@ #include "shareddefs.h" #include "neo_player_shared.h" -static constexpr const int WEP_NAME_MAXSTRLEN = 32; - struct CNEOKillerInfos { bool bHasDmgInfos = false; diff --git a/src/game/client/neo/ui/neo_hud_deathnotice.cpp b/src/game/client/neo/ui/neo_hud_deathnotice.cpp index 463bcb68c..a4dc4a103 100644 --- a/src/game/client/neo/ui/neo_hud_deathnotice.cpp +++ b/src/game/client/neo/ui/neo_hud_deathnotice.cpp @@ -24,6 +24,7 @@ #include "takedamageinfo.h" #include "c_neo_killer_damage_infos.h" #include "neo_scoreboard.h" +#include "neo_hud_deathnotice.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -31,6 +32,9 @@ static ConVar hud_deathnotice_time( "hud_deathnotice_time", "20", 0 ); extern ConVar cl_neo_hud_scoreboard_hide_others; +inline int gRoundKillerUserIDs[MAX_PLAYERS_ARRAY_SAFE]; +inline int gRoundKillerUserIDsSize; + // Player entries in a death notice struct DeathNoticePlayer { @@ -145,52 +149,6 @@ void CNEOHud_DeathNotice::UpdateStateForNeoHudElementDraw() } -enum NeoHudDeathNoticeIcon : wchar_t -{ - NEO_HUD_DEATHNOTICEICON_EXPLODE = '!', - NEO_HUD_DEATHNOTICEICON_GUN, - NEO_HUD_DEATHNOTICEICON_HEADSHOT, - NEO_HUD_DEATHNOTICEICON_KILL, - NEO_HUD_DEATHNOTICEICON_MELEE, - NEO_HUD_DEATHNOTICEICON_SHORTBUS, - - NEO_HUD_DEATHNOTICEICON_RANKUP = '(', - NEO_HUD_DEATHNOTICEICON_RANKDOWN, - NEO_HUD_DEATHNOTICEICON_RANKLESS_DOG, - NEO_HUD_DEATHNOTICEICON_PVT, - NEO_HUD_DEATHNOTICEICON_CPL, - NEO_HUD_DEATHNOTICEICON_SGT, - NEO_HUD_DEATHNOTICEICON_LT, - - NEO_HUD_DEATHNOTICEICON_AA12 = '0', - NEO_HUD_DEATHNOTICEICON_GHOST, - NEO_HUD_DEATHNOTICEICON_GRENADE, - NEO_HUD_DEATHNOTICEICON_JITTE, - NEO_HUD_DEATHNOTICEICON_JITTESCOPED, - NEO_HUD_DEATHNOTICEICON_KNIFE, - NEO_HUD_DEATHNOTICEICON_KYLA, - NEO_HUD_DEATHNOTICEICON_M41, - NEO_HUD_DEATHNOTICEICON_M41L, - NEO_HUD_DEATHNOTICEICON_M41S, - NEO_HUD_DEATHNOTICEICON_MILSO, - NEO_HUD_DEATHNOTICEICON_MPN, - NEO_HUD_DEATHNOTICEICON_MPN_UNSUPRESSED, - NEO_HUD_DEATHNOTICEICON_MX, - NEO_HUD_DEATHNOTICEICON_MX_SILENCED, - NEO_HUD_DEATHNOTICEICON_PBK56S, - NEO_HUD_DEATHNOTICEICON_PZ, - NEO_HUD_DEATHNOTICEICON_REMOTEDET, - NEO_HUD_DEATHNOTICEICON_SMAC, - NEO_HUD_DEATHNOTICEICON_SMOKEGRENADE, - NEO_HUD_DEATHNOTICEICON_SRM, - NEO_HUD_DEATHNOTICEICON_SRM_S, - NEO_HUD_DEATHNOTICEICON_SRS, - NEO_HUD_DEATHNOTICEICON_SUPA7, - NEO_HUD_DEATHNOTICEICON_ZR68C, - NEO_HUD_DEATHNOTICEICON_ZR68L, - NEO_HUD_DEATHNOTICEICON_ZR68S, -}; - //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -798,6 +756,11 @@ void CNEOHud_DeathNotice::AddPlayerDeath(IGameEvent* event) C_NEO_Player* pVictim = ToNEOPlayer(UTIL_PlayerByIndex(victim)); C_NEO_Player* pAssist = ToNEOPlayer(UTIL_PlayerByIndex(assist)); + if (pKiller && gRoundKillerUserIDsSize < MAX_PLAYERS_ARRAY_SAFE) + { + gRoundKillerUserIDs[gRoundKillerUserIDsSize++] = pKiller->GetUserID(); + } + // Special case: Spectator assisted their own bot-takeover kill // Simplify to "Bot Name + Player Name" if (pKiller && pAssist && killer == assist && killer > 0) diff --git a/src/game/client/neo/ui/neo_hud_deathnotice.h b/src/game/client/neo/ui/neo_hud_deathnotice.h new file mode 100644 index 000000000..4f4cccbab --- /dev/null +++ b/src/game/client/neo/ui/neo_hud_deathnotice.h @@ -0,0 +1,51 @@ +#pragma once + +extern int gRoundKillerUserIDs[MAX_PLAYERS_ARRAY_SAFE]; +extern int gRoundKillerUserIDsSize; + +enum NeoHudDeathNoticeIcon : wchar_t +{ + NEO_HUD_DEATHNOTICEICON_EXPLODE = '!', + NEO_HUD_DEATHNOTICEICON_GUN, + NEO_HUD_DEATHNOTICEICON_HEADSHOT, + NEO_HUD_DEATHNOTICEICON_KILL, + NEO_HUD_DEATHNOTICEICON_MELEE, + NEO_HUD_DEATHNOTICEICON_SHORTBUS, + + NEO_HUD_DEATHNOTICEICON_RANKUP = '(', + NEO_HUD_DEATHNOTICEICON_RANKDOWN, + NEO_HUD_DEATHNOTICEICON_RANKLESS_DOG, + NEO_HUD_DEATHNOTICEICON_PVT, + NEO_HUD_DEATHNOTICEICON_CPL, + NEO_HUD_DEATHNOTICEICON_SGT, + NEO_HUD_DEATHNOTICEICON_LT, + + NEO_HUD_DEATHNOTICEICON_AA13 = '0', + NEO_HUD_DEATHNOTICEICON_GHOST, + NEO_HUD_DEATHNOTICEICON_GRENADE, + NEO_HUD_DEATHNOTICEICON_JITTE, + NEO_HUD_DEATHNOTICEICON_JITTESCOPED, + NEO_HUD_DEATHNOTICEICON_KNIFE, + NEO_HUD_DEATHNOTICEICON_KYLA, + NEO_HUD_DEATHNOTICEICON_M41, + NEO_HUD_DEATHNOTICEICON_M41L, + NEO_HUD_DEATHNOTICEICON_M41S, + NEO_HUD_DEATHNOTICEICON_MILSO, + NEO_HUD_DEATHNOTICEICON_MPN, + NEO_HUD_DEATHNOTICEICON_MPN_UNSUPRESSED, + NEO_HUD_DEATHNOTICEICON_MX, + NEO_HUD_DEATHNOTICEICON_MX_SILENCED, + NEO_HUD_DEATHNOTICEICON_PBK56S, + NEO_HUD_DEATHNOTICEICON_PZ, + NEO_HUD_DEATHNOTICEICON_REMOTEDET, + NEO_HUD_DEATHNOTICEICON_SMAC, + NEO_HUD_DEATHNOTICEICON_SMOKEGRENADE, + NEO_HUD_DEATHNOTICEICON_SRM, + NEO_HUD_DEATHNOTICEICON_SRM_S, + NEO_HUD_DEATHNOTICEICON_SRS, + NEO_HUD_DEATHNOTICEICON_SUPA7, + NEO_HUD_DEATHNOTICEICON_ZR68C, + NEO_HUD_DEATHNOTICEICON_ZR68L, + NEO_HUD_DEATHNOTICEICON_ZR68S, +}; + diff --git a/src/game/client/neo/ui/neo_hud_killer_info.cpp b/src/game/client/neo/ui/neo_hud_killer_info.cpp index cf4191eaa..81d153385 100644 --- a/src/game/client/neo/ui/neo_hud_killer_info.cpp +++ b/src/game/client/neo/ui/neo_hud_killer_info.cpp @@ -175,6 +175,7 @@ void CNEOHud_KillerInfo::DrawNeoHudElement() if (pAvatarImg) { + pAvatarImg->m_bDeadAvatar = false; pAvatarImg->SetPos(rect.x0 + iMargin, rect.y0 + iMargin); pAvatarImg->SetSize(iAvatarWT, iAvatarWT); pAvatarImg->Paint(); diff --git a/src/game/client/neo/ui/neo_hud_round_state.cpp b/src/game/client/neo/ui/neo_hud_round_state.cpp index 4aae1bed4..9cea01443 100644 --- a/src/game/client/neo/ui/neo_hud_round_state.cpp +++ b/src/game/client/neo/ui/neo_hud_round_state.cpp @@ -20,6 +20,7 @@ #include "c_playerresource.h" #include "vgui_avatarimage.h" #include "neo_scoreboard.h" +#include "neo_hud_spectator_overlay.h" #include "hltvcamera.h" @@ -558,6 +559,11 @@ void CNEOHud_RoundState::DrawNeoHudElement() if (!g_PR) return; + if (localPlayerSpecOrNoTeam && cl_neo_hud_spectator_overlay_enabled.GetBool()) + { + return; + } + if (NEORules()->IsTeamplay() && (!cl_neo_squad_hud_original.GetBool() || localPlayerSpecOrNoTeam)) { // Sort player list even if not drawing new hud so spectators can use commands for (int i = 0; i < MAX_PLAYERS; i++) @@ -1361,153 +1367,3 @@ int CNEOHud_RoundState::GetSelectedPlayerInHud() return -1; } -CON_COMMAND_F( spec_player_by_hud_position, "Spectate player by position in the top hud", FCVAR_CLIENTCMD_CAN_EXECUTE ) -{ - if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) - { - ConMsg( "%s: HLTV Camera is PVS locked\n", __FUNCTION__ ); - return; - } - - if ( args.ArgC() != 2 ) - { - ConMsg( "Usage: spec_player_by_hud_position { player position in top hud, 0 indexed }\n" ); - return; - } - - int positionInHud = atoi( args[1] ); - if (positionInHud < 0 || positionInHud > MAX_PLAYERS - 1) - { - ConMsg( "Usage: spec_player_by_hud_position { player position in top hud, 0 indexed }\n" ); - return; - } - - if (!g_pNeoHudRoundState) - return; - - C_NEO_Player *pNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); - if ( !pNeoPlayer || !pNeoPlayer->IsObserver() ) - return; - - const int entityIndex = g_pNeoHudRoundState->GetEntityIndexAtPositionInHud(positionInHud, true); - if (entityIndex) - { - engine->IsHLTV() ? HLTVCamera()->SetPrimaryTarget(entityIndex) : engine->ClientCmd(VarArgs("spec_player_entity_number %d", entityIndex)); - } -} - -CON_COMMAND_F( spec_next_entity_in_hud, "Spectate next valid player to the right of the current spectate target", FCVAR_CLIENTCMD_CAN_EXECUTE ) -{ - if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) - { - ConMsg( "%s: HLTV Camera is PVS locked\n", __FUNCTION__ ); - return; - } - - if (!g_pNeoHudRoundState) - return; - - C_NEO_Player *pNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); - if ( !pNeoPlayer || !pNeoPlayer->IsObserver() ) - return; - - int spectateTargetMinusIndexedPositionInHud = 0; - C_BaseEntity *pSpectateTarget = pNeoPlayer->GetObserverTarget(); - if (pSpectateTarget) - { - spectateTargetMinusIndexedPositionInHud = g_pNeoHudRoundState->GetMinusIndexedPositionOfPlayerInHud(pSpectateTarget->entindex()); - } - - const int playerIndex = g_pNeoHudRoundState->GetEntityIndexAtPositionInHud(g_pNeoHudRoundState->GetNextAlivePlayerInHud(spectateTargetMinusIndexedPositionInHud, false)); - if (playerIndex) - { - engine->IsHLTV() ? HLTVCamera()->SetPrimaryTarget(playerIndex) : engine->ClientCmd(VarArgs("spec_player_entity_number %d", playerIndex)); - } -} - -CON_COMMAND_F( spec_previous_entity_in_hud, "Spectate next valid player to the left of the current spectate target", FCVAR_CLIENTCMD_CAN_EXECUTE ) -{ - if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) - { - ConMsg( "%s: HLTV Camera is PVS locked\n", __FUNCTION__ ); - return; - } - - if (!g_pNeoHudRoundState) - return; - - C_NEO_Player *pNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); - if ( !pNeoPlayer || !pNeoPlayer->IsObserver() ) - return; - - int spectateTargetMinusIndexedPositionInHud = 0; - C_BaseEntity *pSpectateTarget = pNeoPlayer->GetObserverTarget(); - if (pSpectateTarget) - { - spectateTargetMinusIndexedPositionInHud = g_pNeoHudRoundState->GetMinusIndexedPositionOfPlayerInHud(pSpectateTarget->entindex()); - } - - const int playerIndex = g_pNeoHudRoundState->GetEntityIndexAtPositionInHud(g_pNeoHudRoundState->GetNextAlivePlayerInHud(spectateTargetMinusIndexedPositionInHud, true)); - if (playerIndex) - { - engine->IsHLTV() ? HLTVCamera()->SetPrimaryTarget(playerIndex) : engine->ClientCmd(VarArgs("spec_player_entity_number %d", playerIndex)); - } -} - -CON_COMMAND_F( select_next_alive_player_in_hud, "Select the next alive player in the top hud", FCVAR_CLIENTCMD_CAN_EXECUTE ) -{ - if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) - { - ConMsg( "%s: Selection is used to switch observer target in spectate_player_selected_in_hud, but HLTV Camera is PVS locked\n", __FUNCTION__ ); - return; - } - - if (!g_pNeoHudRoundState) - return; - - C_NEO_Player *pNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); - if ( !pNeoPlayer || !pNeoPlayer->IsObserver() ) - return; - - g_pNeoHudRoundState->SelectNextAlivePlayerInHud(); -} - -CON_COMMAND_F( select_previous_alive_player_in_hud, "Select the previous alive player in the top hud", FCVAR_CLIENTCMD_CAN_EXECUTE ) -{ - if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) - { - ConMsg( "%s: Selection is used to switch observer target in spectate_player_selected_in_hud, but HLTV Camera is PVS locked\n", __FUNCTION__ ); - return; - } - - if (!g_pNeoHudRoundState) - return; - - C_NEO_Player *pNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); - if ( !pNeoPlayer || !pNeoPlayer->IsObserver() ) - return; - - g_pNeoHudRoundState->SelectPreviousAlivePlayerInHud(); -} - -CON_COMMAND_F( spectate_player_selected_in_hud, "Spectate entity selected in the top hud", FCVAR_CLIENTCMD_CAN_EXECUTE ) -{ - if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) - { - ConMsg( "%s: HLTV Camera is PVS locked\n", __FUNCTION__ ); - return; - } - - if (!g_pNeoHudRoundState) - return; - - C_NEO_Player *pNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); - if ( !pNeoPlayer || !pNeoPlayer->IsObserver() ) - return; - - const int entityIndex = g_pNeoHudRoundState->GetSelectedPlayerInHud(); - if (entityIndex) - { - engine->IsHLTV() ? HLTVCamera()->SetPrimaryTarget(entityIndex) : engine->ClientCmd(VarArgs("spec_player_entity_number %d", entityIndex)); - } -} diff --git a/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp b/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp new file mode 100644 index 000000000..0aabbe72c --- /dev/null +++ b/src/game/client/neo/ui/neo_hud_spectator_overlay.cpp @@ -0,0 +1,855 @@ +#include "cbase.h" +#include "icliententitylist.h" +#include "cliententitylist.h" +#include "neo_hud_spectator_overlay.h" + +#include "c_neo_player.h" +#include "cdll_client_int.h" +#include "neo_player_shared.h" +#include "neo_scoreboard.h" +#include "neo_weapon_loadout.h" +#include "neo_hud_deathnotice.h" +#include "neo_misc.h" +#include + +#include "iclientmode.h" +#include +#include +#include +#include +#include +#include + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar cl_neo_hud_spectator_overlay_enabled("cl_neo_hud_spectator_overlay_enabled", "0", + FCVAR_ARCHIVE | FCVAR_DONTRECORD, + "Whether the spectator overlay HUD is enabled", true, 0.0f, true, 1.0f); + +DECLARE_NAMED_HUDELEMENT(CNEOHud_SpectatorOverlay, neo_spectator_overlay); + +NEO_HUD_ELEMENT_DECLARE_FREQ_CVAR(SpectatorOverlay, 0.2f); + +static const struct Theme +{ + Color hpLossBg = Color(150, 20, 20, 255); + Color cardBg = Color(20, 20, 20, 180); + Color deadBg = Color(40, 20, 20, 180); + Color textPrimary = Color(240, 240, 240, 255); + Color textSecondary = Color(180, 180, 180, 230); + Color wepInactive = Color(120, 120, 120, 255); + Color deadAvatarTint = Color(90, 40, 40, 160); +} OVERLAY_THEME = {}; + +static constexpr const float FL_CHECK_HP_REDUCTION_DELTA_MAX = 1.5f; + +CNEOHud_SpectatorOverlay *g_pNeoHudSpecOverlay; + +CNEOHud_SpectatorOverlay::CNEOHud_SpectatorOverlay(const char* pElementName, vgui::Panel* parent) + : CHudElement(pElementName), EditablePanel(parent, pElementName) +{ + g_pNeoHudSpecOverlay = this; + SetAutoDelete(true); + m_iHideHudElementNumber = NEO_HUD_ELEMENT_SPECTATOR_OVERLAY; + + if (parent) + { + SetParent(parent); + } + else + { + SetParent(g_pClientMode->GetViewport()); + } + + int iScrWide, iScrTall; + vgui::surface()->GetScreenSize(iScrWide, iScrTall); + SetBounds(0, 0, iScrWide, iScrTall); + + SetVisible(true); +} + +CNEOHud_SpectatorOverlay::~CNEOHud_SpectatorOverlay() +{ + g_pNeoHudSpecOverlay = nullptr; +} + +static bool ShouldDrawHUD() +{ + return (g_PR && NEORules() + && NEORules()->IsTeamplay() + && GetLocalPlayerTeam() == TEAM_SPECTATOR + && cl_neo_hud_spectator_overlay_enabled.GetBool()); +} + +void CNEOHud_SpectatorOverlay::Paint() +{ + if (!ShouldDrawHUD()) + { + return; + } + PaintNeoElement(); + BaseClass::Paint(); +} + +void CNEOHud_SpectatorOverlay::ClearAll() +{ + m_iCardsSize = 0; + V_memset(m_cards, 0, sizeof(m_cards)); + V_memset(m_iTeamPlayersCount, 0, sizeof(m_iTeamPlayersCount)); + m_iEntIndexSelect = 0; +} + +void CNEOHud_SpectatorOverlay::Init() +{ + ListenForGameEvent("round_start"); + ClearAll(); +} + +void CNEOHud_SpectatorOverlay::LevelShutdown() +{ + ClearAll(); +} + +void CNEOHud_SpectatorOverlay::FireGameEvent(IGameEvent *event) +{ + const char *type = event->GetName(); + if (0 == V_strcmp(type, "round_start")) + { + // Clear animation/fading related variables + for (int i = 0; i < m_iCardsSize; ++i) + { + SpectatorPlayerCard *pCard = &m_cards[i]; + pCard->flLastAttackTime = 0.0f; + pCard->flLastAliveTime = 0.0f; + pCard->flLastHPChangeTime = 0.0f; + pCard->iLastHP = 0; + } + } +} + +void CNEOHud_SpectatorOverlay::ApplySchemeSettings(vgui::IScheme* pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + LoadControlSettings("scripts/HudLayout.res"); + + int iScrWide, iScrTall; + vgui::surface()->GetScreenSize(iScrWide, iScrTall); + SetBounds(0, 0, iScrWide, iScrTall); + + SetFgColor(COLOR_TRANSPARENT); + SetBgColor(COLOR_TRANSPARENT); +} + +void CNEOHud_SpectatorOverlay::UpdateStateForNeoHudElementDraw() +{ + bool bScoreboardNeedUpdate = false; + + const int iOldCardsSize = m_iCardsSize; + SpectatorPlayerCard oldCards[MAX_PLAYERS_ARRAY_SAFE] = {}; + static_assert(sizeof(oldCards) == sizeof(m_cards)); + V_memcpy(oldCards, m_cards, sizeof(oldCards)); + + m_iCardsSize = 0; + V_memset(m_iTeamPlayersCount, 0, sizeof(m_iTeamPlayersCount)); + V_memset(m_cards, 0, sizeof(m_cards)); + + const auto *pLocalPlayer = C_NEO_Player::GetLocalNEOPlayer(); + if (!pLocalPlayer || !ShouldDrawHUD()) + { + return; + } + + for (const auto iPutTeam : {TEAM_JINRAI, TEAM_NSF}) + { + for (int i = 1; i <= gpGlobals->maxClients; ++i) + { + if (i == pLocalPlayer->entindex() || !g_PR->IsValid(i) || g_PR->IsHLTV(i)) + { + continue; + } + + const int iTeam = g_PR->GetTeam(i); + if (iTeam != iPutTeam || (m_iCardsSize >= MAX_PLAYERS_ARRAY_SAFE)) + { + continue; + } + + SpectatorPlayerCard *pCard = &m_cards[m_iCardsSize++]; + ++m_iTeamPlayersCount[iTeam]; + + // Reuse the avatar image already generated by the scoreboard + pCard->pAvatar = nullptr; + if (g_pNeoScoreBoard && g_pNeoScoreBoard->m_pImageList && CNEOScoreBoard::ShowAvatars()) + { + const CSteamID steamId = GetSteamIDForPlayerIndex(i); + if (steamId.IsValid()) + { + const CUtlMap &mapAvatars = g_pNeoScoreBoard->m_mapAvatarsToImageList; + const int iMapIdx = mapAvatars.Find(steamId); + bool bAvatarNeedUpdate = true; + if (iMapIdx != mapAvatars.InvalidIndex()) + { + const MapAvatarValue &avatar = mapAvatars[iMapIdx]; + if (avatar.i184Idx >= 0) + { + pCard->pAvatar = static_cast(g_pNeoScoreBoard->m_pImageList->GetImage(avatar.i184Idx)); + bAvatarNeedUpdate = false; + } if ((!pCard->pAvatar || !pCard->pAvatar->IsValid()) && avatar.i64Idx >= 0) + { + pCard->pAvatar = static_cast(g_pNeoScoreBoard->m_pImageList->GetImage(avatar.i64Idx)); + } + if ((!pCard->pAvatar || !pCard->pAvatar->IsValid()) && avatar.i32Idx >= 0) + { + pCard->pAvatar = static_cast(g_pNeoScoreBoard->m_pImageList->GetImage(avatar.i32Idx)); + } + } + bScoreboardNeedUpdate = bScoreboardNeedUpdate || bAvatarNeedUpdate; + } + } + + Q_UTF8ToUnicode(g_PR->GetPlayerName(i), pCard->wszPlayerName, sizeof(pCard->wszPlayerName)); + + C_NEO_Player *pNeoPlayer = ToNEOPlayer(UTIL_PlayerByIndex(i)); + + // XP and class from player resource + pCard->iTeam = iTeam; + pCard->iXP = g_PR->GetXP(i); + pCard->iDeath = g_PR->GetDeaths(i); + pCard->iClass = g_PR->GetClass(i); + + pCard->iRoundKills = 0; + if (pNeoPlayer) + { + pCard->iUserID = pNeoPlayer->GetUserID(); + pCard->iEntIndex = pNeoPlayer->entindex(); + + for (int j = 0; j < gRoundKillerUserIDsSize; ++j) + { + pCard->iRoundKills += (gRoundKillerUserIDs[j] == pCard->iUserID); + } + } + + // Health - NEO NOTE (nullsystem): GetHealth can be non-0 even when dead + pCard->bAlive = g_PR->IsAlive(i); + pCard->iHP = pCard->bAlive ? g_PR->GetHealth(i) : 0; + + // Deal with timings to can do animations/fading + SpectatorPlayerCard *pOldCard = nullptr; + if (pCard->iUserID > 0) + { + if (oldCards[m_iCardsSize - 1].iUserID == pCard->iUserID) + { + pOldCard = &oldCards[m_iCardsSize - 1]; + } + else + { + for (int j = 0; j < iOldCardsSize; ++j) + { + if (oldCards[j].iUserID == pCard->iUserID) + { + pOldCard = &oldCards[j]; + break; + } + } + } + } + if (pOldCard) + { + // Alive -> dead + pCard->flLastAliveTime = pOldCard->flLastAliveTime; + if (pOldCard->bAlive && !pCard->bAlive) + { + pCard->flLastAliveTime = gpGlobals->curtime; + } + + // HP reduction + pCard->flLastHPChangeTime = pOldCard->flLastHPChangeTime; + pCard->iLastHP = pOldCard->iLastHP; + if ((pOldCard->iHP > pCard->iHP) + && ((gpGlobals->curtime - pCard->flLastHPChangeTime) > FL_CHECK_HP_REDUCTION_DELTA_MAX)) + { + pCard->flLastHPChangeTime = gpGlobals->curtime; + pCard->iLastHP = pOldCard->iHP; + } + } + + // Weapon + pCard->flLastAttackTime = 0.0f; + pCard->iTotalWeaponIcons = 0; + pCard->iWeaponIdxPrimary = -1; + pCard->iWeaponIdxActive = -1; + if (pNeoPlayer) + { + const auto *pWepActive = static_cast(pNeoPlayer->GetActiveWeapon()); + if (pWepActive) + { + pCard->flLastAttackTime = pWepActive->GetLastAttackTime(); + } + + wchar_t wszWeaponIcon[2] = {}; + for (int j = 0; j < MAX_WEAPONS; ++j) + { + // NEO NOTE (nullsystem): Disallow knife, but keep ghost in + // array but ghost only for the outside indicator will be + // skipped over in the weapon icons list + if (const auto *pNeoWep = static_cast(pNeoPlayer->GetWeapon(j)); + pNeoWep && pNeoWep->WeaponIndex() != NEO_WIDX_KNIFE) + { + if (pNeoWep->GetSlot() == 0) + { + pCard->iWeaponIdxPrimary = pCard->iTotalWeaponIcons; + } + if (pWepActive == pNeoWep) + { + pCard->iWeaponIdxActive = pCard->iTotalWeaponIcons; + } + + // Icons are really only 1 character long + Q_UTF8ToUnicode(pNeoWep->GetDeathIcon(), + wszWeaponIcon, sizeof(wszWeaponIcon)); + pCard->wcaWeaponIcons[pCard->iTotalWeaponIcons++] = wszWeaponIcon[0]; + } + } + } + } + } + + // NEO NOTE (nullsystem): This is so the spectator don't need to tab press + // on the scoreboard to update the avatar. Generally only happens in the + // beginning of match/demo + // + // NEO TODO (nullsystem): Move avatar handling off to something separate from + // the scoreboard but shared between scoreboard, overlay, and killer info + if (bScoreboardNeedUpdate) + { + g_pNeoScoreBoard->Update(); + } +} + +void CNEOHud_SpectatorOverlay::DrawPlayerCard(const SpectatorPlayerCard &card, + const bool bIsLeftSide, + const int x, const int y, + const int wide, const int tall, + const Color accentColor, const float flWideAs43) const +{ + // Card background + static constexpr const float FL_LAST_ALIVE_DELTA_MAX = 1.5f; + const float flLastAliveDelta = gpGlobals->curtime - card.flLastAliveTime; + if (card.bAlive || flLastAliveDelta > FL_LAST_ALIVE_DELTA_MAX || flLastAliveDelta < 0.0f) + { + vgui::surface()->DrawSetColor(card.bAlive ? OVERLAY_THEME.cardBg : OVERLAY_THEME.deadBg); + } + else + { + // Just died fade from brighter red to normal dead red background + Color deadFadeBg = OVERLAY_THEME.deadBg; + deadFadeBg[0] += 80.0f * ((FL_LAST_ALIVE_DELTA_MAX - flLastAliveDelta) * (1.0f / FL_LAST_ALIVE_DELTA_MAX)); + vgui::surface()->DrawSetColor(deadFadeBg); + } + vgui::surface()->DrawFilledRect(x, y, x + wide, y + tall); + + const int iAvatarSize = tall; + const int iAvatarX = bIsLeftSide ? x : x + wide - iAvatarSize; + const int iAvatarY = y; + + // Draw the actual Steam avatar (borrowed from the scoreboard's avatar list) + if (card.pAvatar && card.pAvatar->IsValid()) + { + card.pAvatar->m_bDeadAvatar = !card.bAlive; + card.pAvatar->SetPos(iAvatarX, iAvatarY); + card.pAvatar->SetSize(iAvatarSize, iAvatarSize); + card.pAvatar->Paint(); + } + else if (card.wszPlayerName[0]) + { + vgui::surface()->DrawSetColor(OVERLAY_THEME.cardBg); + vgui::surface()->DrawFilledRect(iAvatarX, iAvatarY, iAvatarX + iAvatarSize, iAvatarY + iAvatarSize); + + // Fallback when no avatar: first character of the player name + // Usually just bots though + wchar_t wszInitial[2] = { card.wszPlayerName[0], L'\0' }; + vgui::surface()->DrawSetTextColor(OVERLAY_THEME.textSecondary); + vgui::surface()->DrawSetTextFont(m_hInfoFont); + int iInitialWide = 0; + [[maybe_unused]] int iInitialTall = 0; + vgui::surface()->GetTextSize(m_hInfoFont, wszInitial, iInitialWide, iInitialTall); + vgui::surface()->DrawSetTextPos(iAvatarX + (iAvatarSize - iInitialWide) / 2, iAvatarY + (iAvatarSize - vgui::surface()->GetFontTall(m_hInfoFont)) / 2); + vgui::surface()->DrawPrintText(wszInitial, 1); + } + + const int iTextPad = flWideAs43 / 360.0f; + + // HP text or dead icon over avatar + if (card.bAlive) + { + wchar_t wszHP[16] = {}; + V_swprintf_safe(wszHP, L"%d", card.iHP); + + int iWideHP = 0, iTallHP = 0; + vgui::surface()->GetTextSize(m_hRKHPFont, wszHP, iWideHP, iTallHP); + + const int iHPTextX = bIsLeftSide + ? iAvatarX + iTextPad + : iAvatarX + iAvatarSize - iWideHP - iTextPad; + const int iHPTextY = iAvatarY + iAvatarSize - iTallHP; + + // Back + vgui::surface()->DrawSetTextFont(m_hRKHPBackFont); + vgui::surface()->DrawSetTextColor(COLOR_BLACK); + vgui::surface()->DrawSetTextPos(iHPTextX, iHPTextY); + vgui::surface()->DrawPrintText(wszHP, V_wcslen(wszHP)); + + // Front + vgui::surface()->DrawSetTextFont(m_hRKHPFont); + vgui::surface()->DrawSetTextColor(OVERLAY_THEME.textPrimary); + vgui::surface()->DrawSetTextPos(iHPTextX, iHPTextY); + vgui::surface()->DrawPrintText(wszHP, V_wcslen(wszHP)); + } + else + { + // Dead icon + vgui::surface()->DrawSetColor(COLOR_WHITE); + vgui::surface()->DrawSetTexture(m_iDeadTexture); + vgui::surface()->DrawTexturedRect( + iAvatarX, + iAvatarY, + iAvatarX + iAvatarSize, + iAvatarY + iAvatarSize); + } + + // Round kills, over avatar, upper section + if (card.iRoundKills > 0) + { + // Kills in current round + wchar_t wszNum[8] = {}; + V_swprintf_safe(wszNum, L"x%d", card.iRoundKills); + + int iWideRK = 0, iTallRK = 0; + vgui::surface()->GetTextSize(m_hRKHPFont, wszNum, iWideRK, iTallRK); + + const int iRKTextX = bIsLeftSide + ? iAvatarX + iTextPad + : iAvatarX + iAvatarSize - iWideRK - iTextPad; + const int iRKTextY = iAvatarY; + + // Back + vgui::surface()->DrawSetTextFont(m_hRKHPBackFont); + vgui::surface()->DrawSetTextColor(COLOR_BLACK); + vgui::surface()->DrawSetTextPos(iRKTextX, iRKTextY); + vgui::surface()->DrawPrintText(wszNum, V_wcslen(wszNum)); + + // Front + vgui::surface()->DrawSetTextFont(m_hRKHPFont); + vgui::surface()->DrawSetTextColor(OVERLAY_THEME.textPrimary); + vgui::surface()->DrawSetTextPos(iRKTextX, iRKTextY); + vgui::surface()->DrawPrintText(wszNum, V_wcslen(wszNum)); + } + + // HP bar(s) + { + const int hpX = bIsLeftSide ? x + iAvatarSize : x; + const int hpY = y; + const int hpW = wide - iAvatarSize; + const int hpH = static_cast(iAvatarSize / 2.0f); + const float flClassHP = static_cast(IN_BETWEEN_AR(0, card.iClass, NEO_CLASS__ENUM_COUNT) + ? MAX_HEALTH_FOR_CLASS[card.iClass] : 100); + + const float flHpPerc = clamp(card.iHP / flClassHP, 0.0f, 1.0f); + const int iFillHPWide = static_cast(hpW * flHpPerc); + + // Red HP reduction/loss bar + const float flLastHPChangeDelta = gpGlobals->curtime - card.flLastHPChangeTime; + if (card.iLastHP > card.iHP + && IN_BETWEEN_EQ(0.0f, flLastHPChangeDelta, FL_CHECK_HP_REDUCTION_DELTA_MAX)) + { + static constexpr const float FL_CHECK_HP_REDUCTION_DELTA_ANIM_START = + FL_CHECK_HP_REDUCTION_DELTA_MAX * 0.40f; + static constexpr const float FL_CHECK_HP_REDUCTION_ANIM_MAX = + FL_CHECK_HP_REDUCTION_DELTA_MAX - FL_CHECK_HP_REDUCTION_DELTA_ANIM_START; + + const float flLastHpPerc = clamp(card.iLastHP / flClassHP, 0.0f, 1.0f); + int iFillReductHP = static_cast(hpW * flLastHpPerc) - iFillHPWide; + if (iFillReductHP > 0) + { + if (flLastHPChangeDelta > FL_CHECK_HP_REDUCTION_DELTA_ANIM_START) + { + iFillReductHP = static_cast(iFillReductHP) + * static_cast( + (FL_CHECK_HP_REDUCTION_ANIM_MAX - (flLastHPChangeDelta - FL_CHECK_HP_REDUCTION_DELTA_ANIM_START)) + * (1.0f / FL_CHECK_HP_REDUCTION_ANIM_MAX)); + } + const int iHPXStart = bIsLeftSide + ? hpX + iFillHPWide + : hpX + hpW - iFillHPWide - iFillReductHP; + + vgui::surface()->DrawSetColor(OVERLAY_THEME.hpLossBg); + vgui::surface()->DrawFilledRect( + iHPXStart, + hpY, + iHPXStart + iFillReductHP, + hpY + hpH); + } + } + + // HP bar + if (card.iHP > 0) + { + Color colorHpBar = accentColor; + colorHpBar[0] *= 0.8f; + colorHpBar[1] *= 0.8f; + colorHpBar[2] *= 0.8f; + + static constexpr const int FADE_RANGE = 196; + static constexpr const int FADE_START = 255 - FADE_RANGE; + const int iMaxFade = Clamp(static_cast(flHpPerc * static_cast(FADE_RANGE)), 0, FADE_RANGE); + const int iHPXStart = bIsLeftSide ? hpX : hpX + (hpW - iFillHPWide); + vgui::surface()->DrawSetColor(colorHpBar); + vgui::surface()->DrawFilledRectFade( + iHPXStart, + hpY, + iHPXStart + iFillHPWide, + hpY + hpH, + bIsLeftSide ? FADE_START : FADE_START + iMaxFade, + bIsLeftSide ? FADE_START + iMaxFade : FADE_START, + true); + } + } + + // Player name | Class + { + vgui::surface()->DrawSetTextFont(m_hNameFont); + + // Right-side right-aligned player name + int iPlayerNameWide = 0, iPlayerNameTall = 0; + vgui::surface()->GetTextSize(m_hNameFont, card.wszPlayerName, iPlayerNameWide, iPlayerNameTall); + + const int iNameX = bIsLeftSide + ? x + iAvatarSize + iTextPad + : x + (wide - iAvatarSize) - iTextPad - iPlayerNameWide; + const int iNameY = y + (((iAvatarSize / 2.0f) - iPlayerNameTall) / 2.0f); + + vgui::surface()->DrawSetTextColor(card.bAlive + ? OVERLAY_THEME.textPrimary + : Color(140, 140, 150, 200)); + vgui::surface()->DrawSetTextPos(iNameX, iNameY); + vgui::surface()->DrawPrintText(card.wszPlayerName, V_wcslen(card.wszPlayerName)); + + // Class name + vgui::surface()->DrawSetTextFont(m_hClassFont); + const wchar_t *pwszClassName = GetNeoClassNameW(card.iClass); + int iClassNameWide = 0, iClassNameTall = 0; + vgui::surface()->GetTextSize(m_hClassFont, pwszClassName, iClassNameWide, iClassNameTall); + const int iClNamePosX = bIsLeftSide + ? x + iAvatarSize + (wide - iAvatarSize) - iTextPad - iClassNameWide + : x + iTextPad; + const int iClNamePosY = y + (((iAvatarSize / 2.0f) - iClassNameTall) / 2.0f); + + vgui::surface()->DrawSetTextColor(card.bAlive + ? OVERLAY_THEME.textPrimary + : Color(140, 140, 150, 200)); + vgui::surface()->DrawSetTextPos(iClNamePosX, iClNamePosY); + vgui::surface()->DrawPrintText(pwszClassName, V_wcslen(pwszClassName)); + } + + // Extra info row: XP | Death | Round kills | Weapon logo + { + const int iInfoTall = iAvatarSize / 2.0f; + const int iInfoY = y + iInfoTall; + const int iTotalW = wide - iAvatarSize - (iTextPad * 2); + + // Sizing split between XP and death + const int iGap = flWideAs43 / 360.0f; + const int iColW = iTotalW / 5; + + const int iTextFontTall = vgui::surface()->GetFontTall(m_hInfoFont); + const int iTextFontOffset = (iInfoTall - iTextFontTall) / 2.0f; + + int iRankIconWide = 0, iRankIconTall = 0; + { + const wchar_t wszRankIcon[] = { NEO_HUD_DEATHNOTICEICON_LT, L'\0' }; + vgui::surface()->GetTextSize(g_hFontKillfeedIcons, wszRankIcon, iRankIconWide, iRankIconTall); + } + + int iDeathIconWide = 0, iDeathIconTall = 0; + { + const wchar_t wszDeathsIcon[] = { NEO_HUD_DEATHNOTICEICON_KILL, L'\0' }; + vgui::surface()->GetTextSize(g_hFontKillfeedIcons, wszDeathsIcon, iDeathIconWide, iDeathIconTall); + } + + wchar_t wszNum[8] = {}; + const int iInfoX = bIsLeftSide ? x + iAvatarSize + iTextPad : x + (wide - iAvatarSize) - iTextPad; + + // XP (left of death) + const int iXPosXP = bIsLeftSide ? iInfoX + iGap : iInfoX - (iColW * 2) + iGap; + // XP - Rank Icon + { + const wchar_t wcRankIcon = NEO_HUD_DEATHNOTICEICON_RANKLESS_DOG + GetRank(card.iXP); + vgui::surface()->DrawSetTextFont(g_hFontKillfeedIcons); + vgui::surface()->DrawSetTextPos(iXPosXP + iTextPad, iInfoY + ((iInfoTall - iRankIconTall) / 2.0f)); + vgui::surface()->DrawPrintText(&wcRankIcon, 1); + } + // XP - Number + { + V_swprintf_safe(wszNum, L"%d", card.iXP); + vgui::surface()->DrawSetTextFont(m_hInfoFont); + vgui::surface()->DrawSetTextPos(iXPosXP + iTextPad + iRankIconWide + iTextPad, iInfoY + iTextFontOffset); + vgui::surface()->DrawPrintText(wszNum, V_wcslen(wszNum)); + } + + // Death (right of XP) + const int iXPosDeath = bIsLeftSide ? iInfoX + iColW + iGap : iInfoX - iColW + iGap; + // Death - Icon + { + static const wchar_t WC_KILLICON = NEO_HUD_DEATHNOTICEICON_KILL; + vgui::surface()->DrawSetTextFont(g_hFontKillfeedIcons); + vgui::surface()->DrawSetTextPos(iXPosDeath, iInfoY + ((iInfoTall - iDeathIconTall) / 2.0f)); + vgui::surface()->DrawPrintText(&WC_KILLICON, 1); + } + // Death - Number + { + V_swprintf_safe(wszNum, L"%d", card.iDeath); + vgui::surface()->DrawSetTextFont(m_hInfoFont); + vgui::surface()->DrawSetTextPos(iXPosDeath + iDeathIconWide + iTextPad, iInfoY + iTextFontOffset); + vgui::surface()->DrawPrintText(wszNum, V_wcslen(wszNum)); + } + + if (card.bAlive) + { + int iWepPosOffsetX = iTextPad; + + // Primary (larger icon, most right in card for Jinrai, most left in card for NSF) + // If ghost is primary, this is differed to the ghoster player indicator + if (card.iWeaponIdxPrimary >= 0 + && NEO_HUD_DEATHNOTICEICON_GHOST != card.wcaWeaponIcons[card.iWeaponIdxPrimary]) + { + wchar_t wszWeaponIcon[2] = { card.wcaWeaponIcons[card.iWeaponIdxPrimary], L'\0' }; + + int iWepWide = 0, iWepTall = 0; + vgui::surface()->GetTextSize(g_hFontKillfeedIcons, wszWeaponIcon, iWepWide, iWepTall); + + const int iXPosWepIcon = bIsLeftSide ? x + wide - iTextPad - iWepWide : x + iTextPad; + iWepPosOffsetX += iWepWide + iTextPad; + vgui::surface()->DrawSetTextColor((card.iWeaponIdxPrimary == card.iWeaponIdxActive) + ? OVERLAY_THEME.textPrimary : OVERLAY_THEME.wepInactive); + vgui::surface()->DrawSetTextFont(g_hFontKillfeedIcons); + vgui::surface()->DrawSetTextPos(iXPosWepIcon, iInfoY + ((iInfoTall - iWepTall) / 2.0f)); + vgui::surface()->DrawPrintText(wszWeaponIcon, 1); + } + + // Secondary + Throwables (smaller icons) + for (int i = 0; i < card.iTotalWeaponIcons; ++i) + { + if (i == card.iWeaponIdxPrimary) + { + continue; + } + + wchar_t wszWeaponIcon[2] = { card.wcaWeaponIcons[i], L'\0' }; + + int iWepWide = 0, iWepTall = 0; + vgui::surface()->GetTextSize(m_hSmallWeaponsFont, wszWeaponIcon, iWepWide, iWepTall); + + const int iXPosWepIcon = bIsLeftSide ? x + wide - iWepPosOffsetX - iWepWide : x + iWepPosOffsetX; + iWepPosOffsetX += iWepWide + iTextPad; + vgui::surface()->DrawSetTextColor((i == card.iWeaponIdxActive) + ? OVERLAY_THEME.textPrimary : OVERLAY_THEME.wepInactive); + vgui::surface()->DrawSetTextFont(m_hSmallWeaponsFont); + vgui::surface()->DrawSetTextPos(iXPosWepIcon, iInfoY + ((iInfoTall - iWepTall) * 0.66f)); + vgui::surface()->DrawPrintText(wszWeaponIcon, 1); + } + } + } + + // Ghoster player indicator - Jinrai right side, NSF left side + if (card.iEntIndex == NEORules()->GetGhosterPlayer()) + { + const wchar_t WSZ_DNI_GHOST[] = { NEO_HUD_DEATHNOTICEICON_GHOST, L'\0' }; + + int iGhostWide = 0, iGhostTall = 0; + vgui::surface()->GetTextSize(m_hGhostFont, WSZ_DNI_GHOST, iGhostWide, iGhostTall); + + vgui::surface()->DrawSetTextColor( + (card.iWeaponIdxActive >= 0 + && NEO_HUD_DEATHNOTICEICON_GHOST == card.wcaWeaponIcons[card.iWeaponIdxActive]) + ? OVERLAY_THEME.textPrimary + : OVERLAY_THEME.wepInactive); + vgui::surface()->DrawSetTextFont(m_hGhostFont); + if (bIsLeftSide) + { + vgui::surface()->DrawSetTextPos(x + wide + iTextPad, y + ((tall - iGhostTall) / 2.0f)); + } + else + { + vgui::surface()->DrawSetTextPos(x - iTextPad - iGhostWide, y + ((tall - iGhostTall) / 2.0f)); + } + vgui::surface()->DrawPrintText(WSZ_DNI_GHOST, 1); + } + + const int iMargin = flWideAs43 / 480.0f; + + // Spectator current observing border + const auto *pLocalPlayer = C_NEO_Player::GetLocalNEOPlayer(); + if (card.iEntIndex == GetSpectatorTarget() + && pLocalPlayer + && pLocalPlayer->IsObserver() + && (pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE + || pLocalPlayer->GetObserverMode() == OBS_MODE_CHASE)) + { + vgui::surface()->DrawSetColor(COLOR_WHITE); + vgui::surface()->DrawFilledRect(x, y - iMargin, x + wide, y); // top + vgui::surface()->DrawFilledRect(x, y + tall, x + wide, y + tall + iMargin); // bottom + vgui::surface()->DrawFilledRect(x - iMargin, y - iMargin, x, y + tall + iMargin); // left + vgui::surface()->DrawFilledRect(x + wide, y - iMargin, x + wide + iMargin, y + tall + iMargin); // right + } + + // m_iEntIndexSelect indicator + if (card.iEntIndex == m_iEntIndexSelect) + { + vgui::surface()->DrawSetColor(COLOR_NEO_ORANGE); + if (bIsLeftSide) + { + // Left border, Jinrai + vgui::surface()->DrawFilledRect(x - iMargin, y - iMargin, x, y + tall + iMargin); + } + else + { + // Right border, NSF + vgui::surface()->DrawFilledRect(x + wide, y - iMargin, x + wide + iMargin, y + tall + iMargin); + } + } + + // Weapon firing indicator bar - Jinrai right side, NSF left side + static constexpr const float FL_LAST_ATTACK_DELTA_MAX = 0.5f; + const float flLastAttackDelta = gpGlobals->curtime - card.flLastAttackTime; + if (IN_BETWEEN_EQ(0.0f, flLastAttackDelta, FL_LAST_ATTACK_DELTA_MAX)) + { + Color fireIndColor = COLOR_WHITE; + fireIndColor[3] *= (FL_LAST_ATTACK_DELTA_MAX - flLastAttackDelta) * (1.0f / FL_LAST_ATTACK_DELTA_MAX); + vgui::surface()->DrawSetColor(fireIndColor); + if (bIsLeftSide) + { + vgui::surface()->DrawFilledRect(x + wide + iTextPad, y, x + wide + (2 * iTextPad), y + tall); + } + else + { + vgui::surface()->DrawFilledRect(x - (2 * iTextPad), y, x - iTextPad, y + tall); + } + } +} + +void CNEOHud_SpectatorOverlay::DrawNeoHudElement() +{ + if (!ShouldDrawHUD() || m_iCardsSize <= 0) + { + return; + } + + // In 1920x1080, the player card: + // Converted to 4:3 is 1440x1080, calculate from 4:3 width regardless of + // 16:9/16:10: + // * Total width (including avatar) of 351px, 1440/4.1=~351 + // * Avatar width of 64px, 1440/22.5=64 + // * Gap from left or right edge of 12px, 1440/120=12 + // * Gap between cards Y-axis of 4px, 1440/360=4 + int iScrWide, iScrTall; + vgui::surface()->GetScreenSize(iScrWide, iScrTall); + + const float flWide = static_cast(iScrWide); + float flWideAs43 = static_cast(iScrTall) * (4.0f / 3.0f); + if (flWideAs43 > flWide) flWideAs43 = flWide; + + const int iTotalCardWide = flWideAs43 / 4.1f; + const int iAvatarWH = flWideAs43 / 22.5f; + const int iGapWide = flWideAs43 / 120.0f; + const int iGapBetween = flWideAs43 / 360.0f; + + // NEO TODO (nullsystem): Draw team's name and logo on top + for (const auto iTeam : {TEAM_JINRAI, TEAM_NSF}) + { + const bool bIsLeftSide = (iTeam == TEAM_JINRAI); + const Color accentColor = g_PR->GetTeamColor(iTeam); + + // Center the column vertically on screen + const int iTeamCount = m_iTeamPlayersCount[iTeam]; + const int iTotalHeight = (iTeamCount * iAvatarWH) + ((iTeamCount - 1) * iGapBetween); + const int iColXStart = bIsLeftSide + ? iGapWide + : static_cast(iScrWide - iTotalCardWide - iGapWide); + const int iColYStart = ((iScrTall - iTotalHeight) / 2.0f); + + int iPlayerRow = 0; + for (int i = 0; i < m_iCardsSize; ++i) + { + if (m_cards[i].iTeam == iTeam) + { + const int iRowY = iColYStart + (iPlayerRow * iAvatarWH) + + ((iPlayerRow > 0) ? (iPlayerRow * iGapBetween) : 0); + DrawPlayerCard(m_cards[i], bIsLeftSide, + iColXStart, iRowY, iTotalCardWide, iAvatarWH, + accentColor, flWideAs43); + ++iPlayerRow; + } + } + } +} + +int CNEOHud_SpectatorOverlay::SpecTargetNextEntIdx(const ESpecType eSpecType, EDirection eDirection) const +{ + int iSpecTargetIdx = -1; + const C_NEO_Player *pNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); + if (pNeoPlayer && pNeoPlayer->IsObserver()) + { + const C_BaseEntity *pTarget = pNeoPlayer->GetObserverTarget(); + const bool bHasSelEntIndex = (eSpecType == SPECTYPE_SELECT && m_iEntIndexSelect > 0); + if (pTarget || bHasSelEntIndex) + { + const int iCmpEntIndex = bHasSelEntIndex ? m_iEntIndexSelect : pTarget->entindex(); + for (int i = 0; i < m_iCardsSize; ++i) + { + if (iCmpEntIndex == m_cards[i].iEntIndex) + { + iSpecTargetIdx = i; + break; + } + } + // If m_iEntIndexSelect selected is now invalid + if (pTarget && bHasSelEntIndex && iSpecTargetIdx == -1) + { + const int iCmpEntIndex2 = pTarget->entindex(); + for (int i = 0; i < m_iCardsSize; ++i) + { + if (iCmpEntIndex2 == m_cards[i].iEntIndex) + { + iSpecTargetIdx = i; + break; + } + } + } + } + else if ((eSpecType == SPECTYPE_SELECT && m_iEntIndexSelect == 0) || (eSpecType == SPECTYPE_DIRECT)) + { + iSpecTargetIdx = 0; + eDirection = DIRECTION_NIL; + } + } + if (iSpecTargetIdx >= 0) + { + if (eDirection != DIRECTION_NIL) + { + for (int i = 0; i < m_iCardsSize; ++i) + { + iSpecTargetIdx = LoopAroundInArray(iSpecTargetIdx + + ((eDirection == DIRECTION_PREV) ? -1 : +1), m_iCardsSize); + if (m_cards[iSpecTargetIdx].bAlive) + { + break; + } + } + } + return m_cards[iSpecTargetIdx].iEntIndex; + } + return 0; +} + diff --git a/src/game/client/neo/ui/neo_hud_spectator_overlay.h b/src/game/client/neo/ui/neo_hud_spectator_overlay.h new file mode 100644 index 000000000..1d60cfcff --- /dev/null +++ b/src/game/client/neo/ui/neo_hud_spectator_overlay.h @@ -0,0 +1,97 @@ +#pragma once + +#include "neo_hud_childelement.h" +#include "hudelement.h" +#include + +class CAvatarImage; + +extern ConVar cl_neo_hud_spectator_overlay_enabled; + +struct SpectatorPlayerCard +{ + int iUserID = 0; + int iEntIndex = 0; + int iTeam = 0; + wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH] = {}; + // NEO NOTE (nullsystem): wcaWeaponIcons is NOT used as a string + // but individual characters! + wchar_t wcaWeaponIcons[MAX_WEAPONS] = {}; + int iTotalWeaponIcons = 0; + int iWeaponIdxActive = 0; + int iWeaponIdxPrimary = 0; + int iXP = 0; + int iDeath = 0; + int iClass = 0; + int iHP = 0; + int iRoundKills = 0; + bool bAlive = false; + CAvatarImage *pAvatar = nullptr; + // Used for animations/fading + float flLastAttackTime = 0.0f; + float flLastAliveTime = 0.0f; + float flLastHPChangeTime = 0.0f; + int iLastHP = 0; +}; + +// Client-side spectator-only HUD overlay showing both teams players infos +// Mainly for competitve streams +class CNEOHud_SpectatorOverlay : public CNEOHud_ChildElement, public CHudElement, public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE(CNEOHud_SpectatorOverlay, EditablePanel); + +public: + CNEOHud_SpectatorOverlay(const char *pElementName, vgui::Panel *parent = nullptr); + CNEOHud_SpectatorOverlay(const CNEOHud_SpectatorOverlay& other) = delete; + virtual ~CNEOHud_SpectatorOverlay(); + + void ClearAll(); + void Init() final; + void LevelShutdown() final; + void FireGameEvent(IGameEvent *event) final; + void ApplySchemeSettings(vgui::IScheme *pScheme) final; + void Paint() final; + + SpectatorPlayerCard m_cards[MAX_PLAYERS_ARRAY_SAFE] = {}; + int m_iCardsSize = 0; + + enum ESpecType + { + SPECTYPE_DIRECT = 0, + SPECTYPE_SELECT, + }; + enum EDirection + { + DIRECTION_NIL = 0, + DIRECTION_NEXT, + DIRECTION_PREV, + }; + int SpecTargetNextEntIdx(const ESpecType eSpecType, EDirection eDirection) const; + int m_iEntIndexSelect = 0; + +protected: + void UpdateStateForNeoHudElementDraw() final; + void DrawNeoHudElement() final; + ConVar *GetUpdateFrequencyConVar() const final; + +private: + void DrawPlayerCard(const SpectatorPlayerCard &card, + const bool bIsLeftSide, + const int x, const int y, + const int wide, const int tall, + const Color accentColor, const float flWideAs43) const; + + CPanelAnimationVar(vgui::HFont, m_hNameFont, "NameFont", "NHudSpectatorOverlayName"); + CPanelAnimationVar(vgui::HFont, m_hInfoFont, "InfoFont", "NHudSpectatorOverlayInfo"); + CPanelAnimationVar(vgui::HFont, m_hClassFont, "ClassFont", "NHudSpectatorOverlayClass"); + CPanelAnimationVar(vgui::HFont, m_hRKHPFont, "RKHPFont", "NHudSpectatorOverlayRoundKillHPFront"); + CPanelAnimationVar(vgui::HFont, m_hRKHPBackFont, "RKHPBackFont", "NHudSpectatorOverlayRoundKillHPBack"); + CPanelAnimationVar(vgui::HFont, m_hGhostFont, "GhostFont", "NHudSpectatorOverlayGhost"); + CPanelAnimationVar(vgui::HFont, m_hSmallWeaponsFont, "SmallWeaponsFont", "NHudSpectatorOverlaySmallWeapons"); + CPanelAnimationVarAliasType(int, m_iDeadTexture, "DeadTexture", "vgui/hud/kill_kill", "textureid"); + + int m_iTeamPlayersCount[TEAM__TOTAL] = {}; +}; + +extern CNEOHud_SpectatorOverlay *g_pNeoHudSpecOverlay; + diff --git a/src/game/client/neo/ui/neo_scoreboard.cpp b/src/game/client/neo/ui/neo_scoreboard.cpp index 55e09126d..ce1baf2d5 100644 --- a/src/game/client/neo/ui/neo_scoreboard.cpp +++ b/src/game/client/neo/ui/neo_scoreboard.cpp @@ -984,6 +984,7 @@ void CNEOScoreBoard::OnMainLoop(const NeoUI::Mode eMode) if (pAvatarImg) { + pAvatarImg->m_bDeadAvatar = pPlayerInfo->bDead && bIsPlaying; pAvatarImg->SetPos(m_uiCtx.rWidgetArea.x0, m_uiCtx.rWidgetArea.y0); pAvatarImg->SetSize(m_uiCtx.irWidgetTall, m_uiCtx.irWidgetTall); pAvatarImg->Paint(); @@ -1012,10 +1013,9 @@ void CNEOScoreBoard::OnMainLoop(const NeoUI::Mode eMode) } if (iCurTeam >= FIRST_GAME_TEAM) { - // Darken the avatar if dead or not ready + // Darken the avatar if not ready if (pAvatarImg - && (pPlayerInfo->bDead - || (bShowReadyUp && false == pPlayerInfo->bReady))) + && (bShowReadyUp && false == pPlayerInfo->bReady)) { vgui::surface()->DrawSetColor(0, 0, 0, 200); vgui::surface()->DrawFilledRect( @@ -1130,6 +1130,7 @@ void CNEOScoreBoard::OnMainLoop(const NeoUI::Mode eMode) } if (pAvatarImg) { + pAvatarImg->m_bDeadAvatar = false; pAvatarImg->SetPos(m_uiCtx.dPanel.x + iAvatarOffset, m_uiCtx.dPanel.y + iAvatarOffset); pAvatarImg->SetSize(iAvatarWT, iAvatarWT); diff --git a/src/game/client/neo/ui/neo_spec_player_by_hud.cpp b/src/game/client/neo/ui/neo_spec_player_by_hud.cpp new file mode 100644 index 000000000..5983ab79a --- /dev/null +++ b/src/game/client/neo/ui/neo_spec_player_by_hud.cpp @@ -0,0 +1,201 @@ +#include "cbase.h" + +#include "neo_hud_spectator_overlay.h" +#include "neo_hud_round_state.h" +#include "hud_element_helper.h" +#include "hltvcamera.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static void SetPlayerEntityTarget(const int iEntIndex) +{ + if (iEntIndex > 0) + { + if (engine->IsHLTV()) + { + HLTVCamera()->SetPrimaryTarget(iEntIndex); + } + else + { + engine->ClientCmd(VarArgs("spec_player_entity_number %d", iEntIndex)); + } + } +} + +struct HUDCommonRet +{ + CNEOHud_SpectatorOverlay *pOverlay = nullptr; + CNEOHud_RoundState *pRoundState = nullptr; + C_NEO_Player *pNeoPlayer = nullptr; +}; + +static HUDCommonRet CheckCommonSpecOverlayErrors(const char *pszFuncName) +{ + HUDCommonRet ret = {}; + + const bool bUseOverlay = cl_neo_hud_spectator_overlay_enabled.GetBool(); + + auto *pOverlay = g_pNeoHudSpecOverlay; + auto *pRoundState = g_pNeoHudRoundState; + if ((bUseOverlay && !pOverlay) || (!bUseOverlay && !pRoundState)) + { + return ret; + } + + if (engine->IsHLTV() && HLTVCamera()->IsPVSLocked()) + { + ConMsg("%s: HLTV Camera is PVS locked\n", pszFuncName); + return ret; + } + + C_NEO_Player *pNeoPlayer = C_NEO_Player::GetLocalNEOPlayer(); + if (!pNeoPlayer || !pNeoPlayer->IsObserver()) + { + return ret; + } + + if (bUseOverlay) + { + ret.pOverlay = pOverlay; + } + else + { + ret.pRoundState = pRoundState; + } + ret.pNeoPlayer = pNeoPlayer; + return ret; +} + +CON_COMMAND_F(spec_player_by_hud_position, "Spectate player by position in the hud", FCVAR_CLIENTCMD_CAN_EXECUTE) +{ + static constexpr const char HELP_MSG_OL[] = + "Usage: spec_player_by_hud_position { player position in overlay hud, from first of Jinrai to last of NSF, 0 indexed }\n"; + static constexpr const char HELP_MSG_RS[] = + "Usage: spec_player_by_hud_position { player position in top hud, 0 indexed }\n"; + + auto [pOverlay, pRoundState, pNeoPlayer] = CheckCommonSpecOverlayErrors(__FUNCTION__); + + if (args.ArgC() != 2) + { + ConMsg(pOverlay ? HELP_MSG_OL : HELP_MSG_RS); + return; + } + + if (pOverlay) + { + const int iSpecTargetIdx = atoi(args[1]); + if (iSpecTargetIdx < 0 || iSpecTargetIdx >= pOverlay->m_iCardsSize) + { + ConMsg(HELP_MSG_OL); + return; + } + SetPlayerEntityTarget(pOverlay->m_cards[iSpecTargetIdx].iEntIndex); + } + else if (pRoundState) + { + const int positionInHud = atoi(args[1]); + if (positionInHud < 0 || positionInHud > MAX_PLAYERS - 1) + { + ConMsg(HELP_MSG_RS); + return; + } + SetPlayerEntityTarget(g_pNeoHudRoundState->GetEntityIndexAtPositionInHud(positionInHud, true)); + } +} + +CON_COMMAND_F(spec_next_entity_in_hud, "Spectate the next valid player in the hud", FCVAR_CLIENTCMD_CAN_EXECUTE) +{ + auto [pOverlay, pRoundState, pNeoPlayer] = CheckCommonSpecOverlayErrors(__FUNCTION__); + + if (pOverlay) + { + pOverlay->m_iEntIndexSelect = 0; + SetPlayerEntityTarget(pOverlay->SpecTargetNextEntIdx( + CNEOHud_SpectatorOverlay::SPECTYPE_DIRECT, + CNEOHud_SpectatorOverlay::DIRECTION_NEXT)); + } + else if (pRoundState) + { + C_BaseEntity *pSpectateTarget = pNeoPlayer->GetObserverTarget(); + const int iSpecTargetMinusIdxPos = (pSpectateTarget) + ? g_pNeoHudRoundState->GetMinusIndexedPositionOfPlayerInHud(pSpectateTarget->entindex()) + : 0; + SetPlayerEntityTarget( + g_pNeoHudRoundState->GetEntityIndexAtPositionInHud( + g_pNeoHudRoundState->GetNextAlivePlayerInHud( + iSpecTargetMinusIdxPos, false))); + } +} + +CON_COMMAND_F(spec_previous_entity_in_hud, "Spectate the previous valid player in the hud", FCVAR_CLIENTCMD_CAN_EXECUTE) +{ + auto [pOverlay, pRoundState, pNeoPlayer] = CheckCommonSpecOverlayErrors(__FUNCTION__); + + if (pOverlay) + { + pOverlay->m_iEntIndexSelect = 0; + SetPlayerEntityTarget(pOverlay->SpecTargetNextEntIdx( + CNEOHud_SpectatorOverlay::SPECTYPE_DIRECT, + CNEOHud_SpectatorOverlay::DIRECTION_PREV)); + } + else if (pRoundState) + { + C_BaseEntity *pSpectateTarget = pNeoPlayer->GetObserverTarget(); + const int iSpecTargetMinusIdxPos = (pSpectateTarget) + ? pRoundState->GetMinusIndexedPositionOfPlayerInHud(pSpectateTarget->entindex()) + : 0; + SetPlayerEntityTarget( + pRoundState->GetEntityIndexAtPositionInHud( + pRoundState->GetNextAlivePlayerInHud( + iSpecTargetMinusIdxPos, true))); + } +} + +CON_COMMAND_F(select_next_alive_player_in_hud, "Select the next alive player in the hud", FCVAR_CLIENTCMD_CAN_EXECUTE) +{ + auto [pOverlay, pRoundState, pNeoPlayer] = CheckCommonSpecOverlayErrors(__FUNCTION__); + + if (pOverlay) + { + pOverlay->m_iEntIndexSelect = pOverlay->SpecTargetNextEntIdx( + CNEOHud_SpectatorOverlay::SPECTYPE_SELECT, + CNEOHud_SpectatorOverlay::DIRECTION_NEXT); + } + else if (pRoundState) + { + pRoundState->SelectNextAlivePlayerInHud(); + } +} + +CON_COMMAND_F(select_previous_alive_player_in_hud, "Select the previous alive player in the hud", FCVAR_CLIENTCMD_CAN_EXECUTE) +{ + auto [pOverlay, pRoundState, pNeoPlayer] = CheckCommonSpecOverlayErrors(__FUNCTION__); + + if (pOverlay) + { + pOverlay->m_iEntIndexSelect = pOverlay->SpecTargetNextEntIdx( + CNEOHud_SpectatorOverlay::SPECTYPE_SELECT, + CNEOHud_SpectatorOverlay::DIRECTION_PREV); + } + else if (pRoundState) + { + pRoundState->SelectPreviousAlivePlayerInHud(); + } +} + +CON_COMMAND_F(spectate_player_selected_in_hud, "Spectate entity selected in the top hud", FCVAR_CLIENTCMD_CAN_EXECUTE) +{ + auto [pOverlay, pRoundState, pNeoPlayer] = CheckCommonSpecOverlayErrors(__FUNCTION__); + + if (pOverlay) + { + SetPlayerEntityTarget(pOverlay->m_iEntIndexSelect); + pOverlay->m_iEntIndexSelect = 0; + } + else if (pRoundState) + { + SetPlayerEntityTarget(g_pNeoHudRoundState->GetSelectedPlayerInHud()); + } +} + diff --git a/src/game/client/vgui_avatarimage.cpp b/src/game/client/vgui_avatarimage.cpp index 79085bb8e..edae32e1c 100644 --- a/src/game/client/vgui_avatarimage.cpp +++ b/src/game/client/vgui_avatarimage.cpp @@ -21,7 +21,11 @@ DECLARE_BUILD_FACTORY( CAvatarImagePanel ); #pragma warning(disable : 4355) // warning C4355: 'this': used in base member initializer list #endif +#ifdef NEO +CUtlMap< AvatarImagePair_t, AvatarImagePairValue_t> CAvatarImage::s_AvatarImageCache; +#else CUtlMap< AvatarImagePair_t, int> CAvatarImage::s_AvatarImageCache; // cache of steam id's to textureids to use for images +#endif // NEO bool CAvatarImage::m_sbInitializedAvatarCache = false; //----------------------------------------------------------------------------- @@ -55,6 +59,9 @@ CAvatarImage::CAvatarImage( void ) // [menglish] Default icon for avatar icons if there is no avatar icon for the player m_iTextureID = -1; +#ifdef NEO + m_iTextureDeadID = -1; +#endif // NEO // set up friend icon m_pFriendIcon = gHUD.GetIcon( "ico_friend_indicator_avatar" ); @@ -240,10 +247,43 @@ void CAvatarImage::InitFromRGBA( int iAvatar, const byte *rgba, int width, int h byte *rgbDest = (byte*)stackalloc( destBufferSize ); if ( steamapicontext->SteamUtils()->GetImageRGBA( iAvatar, rgbDest, destBufferSize ) ) { + // Create normal avatar from RGBA without edits m_iTextureID = vgui::surface()->CreateNewTextureID( true ); g_pMatSystemSurface->DrawSetTextureRGBAEx2( m_iTextureID, rgbDest, width, height, IMAGE_FORMAT_RGBA8888, true ); + + // Create dead avatar from RGBA with redness edits + const float contrast = -64.0f; + const float factor = (259.0f * (contrast + 255.0f)) / (255.0f * (259.0f - contrast)); + for (uint32 offset = 0; offset < (width * height * 4); offset += 4) + { + int r = (rgbDest + offset)[0]; + int g = (rgbDest + offset)[1]; + int b = (rgbDest + offset)[2]; + + // Bump brightness + r = Clamp((int)(((r / 255.0f) * 1.25f) * 255.0f), 0, 255); + g = Clamp((int)(((g / 255.0f) * 1.25f) * 255.0f), 0, 255); + b = Clamp((int)(((b / 255.0f) * 1.25f) * 255.0f), 0, 255); + + // Bump down contrast + r = Clamp((int)(factor * (r - 128) + 128), 0, 255); + g = Clamp((int)(factor * (g - 128) + 128), 0, 255); + b = Clamp((int)(factor * (b - 128) + 128), 0, 255); + + // Convert to grayscale - Luminosity, then only for red + const float flNGray = ((0.3f * (r / 255.0f)) + (0.59f * (g / 255.0f)) + (0.11f * (b / 255.0f))); + const int iGray = Clamp((int)(255.0f * flNGray), 0, 255); + (rgbDest + offset)[0] = iGray; + (rgbDest + offset)[1] = 0; + (rgbDest + offset)[2] = 0; + } + m_iTextureDeadID = vgui::surface()->CreateNewTextureID( true ); + g_pMatSystemSurface->DrawSetTextureRGBAEx2( m_iTextureDeadID, rgbDest, width, height, IMAGE_FORMAT_RGBA8888, true ); + + // Add textures to global cache iTexIndex = s_AvatarImageCache.Insert( AvatarImagePair_t( m_SteamID, iAvatar ) ); - s_AvatarImageCache[ iTexIndex ] = m_iTextureID; + s_AvatarImageCache[ iTexIndex ].normal = m_iTextureID; + s_AvatarImageCache[ iTexIndex ].dead = m_iTextureDeadID; } stackfree( rgbDest ); #else @@ -253,8 +293,16 @@ void CAvatarImage::InitFromRGBA( int iAvatar, const byte *rgba, int width, int h s_AvatarImageCache[ iTexIndex ] = m_iTextureID; #endif // NEO } +#ifdef NEO + else + { + m_iTextureID = s_AvatarImageCache[ iTexIndex ].normal; + m_iTextureDeadID = s_AvatarImageCache[ iTexIndex ].dead; + } +#else else m_iTextureID = s_AvatarImageCache[ iTexIndex ]; +#endif // NEO m_bValid = true; } @@ -287,7 +335,11 @@ void CAvatarImage::Paint( void ) if ( m_bValid ) { +#ifdef NEO + vgui::surface()->DrawSetTexture( m_bDeadAvatar ? m_iTextureDeadID : m_iTextureID ); +#else vgui::surface()->DrawSetTexture( m_iTextureID ); +#endif // NEO vgui::surface()->DrawSetColor( m_Color ); vgui::surface()->DrawTexturedRect(posX, posY, posX + m_avatarWide, posY + m_avatarTall); } diff --git a/src/game/client/vgui_avatarimage.h b/src/game/client/vgui_avatarimage.h index 2c264b905..89f1bffbe 100644 --- a/src/game/client/vgui_avatarimage.h +++ b/src/game/client/vgui_avatarimage.h @@ -34,6 +34,14 @@ // be retrieved. //============================================================================= +#ifdef NEO +struct AvatarImagePairValue_t +{ + int normal; + int dead; +}; +#endif // NEO + struct AvatarImagePair_t { AvatarImagePair_t() { m_iAvatar = 0; } @@ -120,7 +128,7 @@ class CAvatarImage : public vgui::IImage int GetAvatarWide() { return m_avatarWide; } int GetAvatarTall() { return m_avatarTall; } #ifdef NEO - int getTextureID() { return m_iTextureID; } + bool m_bDeadAvatar = false; #endif //============================================================================= @@ -153,6 +161,9 @@ class CAvatarImage : public vgui::IImage Color m_Color; int m_iTextureID; +#ifdef NEO + int m_iTextureDeadID; +#endif // NEO int m_nX, m_nY; int m_wide, m_tall; int m_avatarWide, m_avatarTall; @@ -180,7 +191,11 @@ class CAvatarImage : public vgui::IImage // HPE_END //============================================================================= +#ifdef NEO + static CUtlMap< AvatarImagePair_t, AvatarImagePairValue_t > s_AvatarImageCache; +#else static CUtlMap< AvatarImagePair_t, int > s_AvatarImageCache; +#endif static bool m_sbInitializedAvatarCache; CCallback m_sPersonaStateChangedCallback; diff --git a/src/game/shared/neo/neo_gamerules.cpp b/src/game/shared/neo/neo_gamerules.cpp index b001b4212..8acd11261 100644 --- a/src/game/shared/neo/neo_gamerules.cpp +++ b/src/game/shared/neo/neo_gamerules.cpp @@ -17,6 +17,7 @@ #include "filesystem.h" #include "hltvcamera.h" #include "hud_crosshair.h" +#include "ui/neo_hud_deathnotice.h" #else #include "neo_player.h" #include "team.h" @@ -1922,6 +1923,9 @@ void CNEORules::FireGameEvent(IGameEvent* event) { StartAutoRecording(); } + + V_memset(gRoundKillerUserIDs, 0, sizeof(gRoundKillerUserIDs)); + gRoundKillerUserIDsSize = 0; #endif #ifdef GAME_DLL m_flNeoRoundStartTime = gpGlobals->curtime; @@ -4347,24 +4351,7 @@ void CNEORules::DeathNotice(CBasePlayer* pVictim, const CTakeDamageInfo& info) event->SetInt("priority", 7); event->SetBool("headshot", pVictim->LastHitGroup() == HITGROUP_HEAD); event->SetBool("suicide", pKiller == pVictim || !pKiller->IsPlayer()); - - if (isGrenade) - { - event->SetString("deathIcon", "2"); // NEO TODO (Adam) get from enum - } - else if (isRemoteDetpack) - { - event->SetString("deathIcon", "A"); // NEO TODO (Adam) get from enum - } - else if (neoWep) - { - event->SetString("deathIcon", neoWep->GetNEOWpnData().szDeathIcon); - } - else - { - event->SetString("deathIcon", ""); - } - + event->SetString("deathIcon", neoWep ? neoWep->GetDeathIcon() : ""); event->SetBool("explosive", isGrenade || isRemoteDetpack); event->SetBool("ghoster", m_iGhosterPlayer == pVictim->entindex()); diff --git a/src/game/shared/neo/neo_gamerules.h b/src/game/shared/neo/neo_gamerules.h index 68a74fb8b..e59b8071b 100644 --- a/src/game/shared/neo/neo_gamerules.h +++ b/src/game/shared/neo/neo_gamerules.h @@ -170,6 +170,7 @@ enum NeoHudElements : NEO_HUD_BITS_UNDERLYING_TYPE { NEO_HUD_ELEMENT_SCOREBOARD = (static_cast(1) << 14), NEO_HUD_ELEMENT_PLAYER_PING = (static_cast(1) << 15), NEO_HUD_ELEMENT_WORLDPOS_MARKER_ENT = (static_cast(1) << 16), + NEO_HUD_ELEMENT_SPECTATOR_OVERLAY = (static_cast(1) << 17), }; enum NeoSpectateEvent { diff --git a/src/game/shared/neo/neo_player_shared.h b/src/game/shared/neo/neo_player_shared.h index a0368e340..9e621e937 100644 --- a/src/game/shared/neo/neo_player_shared.h +++ b/src/game/shared/neo/neo_player_shared.h @@ -419,4 +419,6 @@ static constexpr const SZWSZTexts SZWSZ_NEO_TEAM_STRS[TEAM__TOTAL] = { #define NEO_GAME_NAME "NEOTOKYO;REBUILD" +static constexpr const int WEP_NAME_MAXSTRLEN = 32; + #endif // NEO_PLAYER_SHARED_H diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp index 7727bbe1c..40447f42d 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.cpp @@ -1398,3 +1398,17 @@ void CNEOBaseCombatWeapon::Use(CBaseEntity* pActivator, CBaseEntity* pCaller, US // Calling BaseClass::Use will pick the weapon up without waiting for the touch cooldown, don't see anything important there that we need to do that we aren't doing here } #endif + +const char *CNEOBaseCombatWeapon::GetDeathIcon() const +{ + if (WeaponIndex() == NEO_WIDX_FRAG_GRENADE) + { + return "2"; + } + else if (WeaponIndex() == NEO_WIDX_DETPACK) + { + return "A"; + } + return GetNEOWpnData().szDeathIcon; +} + diff --git a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h index be1732aac..f209a94e2 100644 --- a/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h +++ b/src/game/shared/neo/weapons/weapon_neobasecombatweapon.h @@ -259,6 +259,8 @@ class CNEOBaseCombatWeapon : public CBaseHL2MPCombatWeapon float m_flTemperature; int m_spawnflags; #endif // CLIENT_DLL + + const char *GetDeathIcon() const; protected: WeaponHandlingInfo_t m_weaponHandling;