diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/gui.cpp | 66 | ||||
| -rw-r--r-- | src/hl2h.cpp | 1 | ||||
| -rw-r--r-- | src/hl2h.h | 11 | ||||
| -rw-r--r-- | src/menu.cpp | 21 | ||||
| -rw-r--r-- | src/world.cpp | 152 |
5 files changed, 244 insertions, 7 deletions
diff --git a/src/gui.cpp b/src/gui.cpp index aa47868..5fb8fa7 100644 --- a/src/gui.cpp +++ b/src/gui.cpp | |||
| @@ -183,7 +183,8 @@ enum CtrlKind { | |||
| 183 | CK_SECTION, | 183 | CK_SECTION, |
| 184 | CK_INFO, | 184 | CK_INFO, |
| 185 | CK_BUTTON, | 185 | CK_BUTTON, |
| 186 | CK_COLOR | 186 | CK_COLOR, |
| 187 | CK_CHOICE | ||
| 187 | }; | 188 | }; |
| 188 | 189 | ||
| 189 | enum CtrlAction { ACT_NONE = 0, ACT_RELOAD, ACT_RESET }; | 190 | enum CtrlAction { ACT_NONE = 0, ACT_RELOAD, ACT_RESET }; |
| @@ -195,6 +196,8 @@ struct Ctrl { | |||
| 195 | bool *bval; // CK_TOGGLE | 196 | bool *bval; // CK_TOGGLE |
| 196 | int *ival; // CK_SLIDER | 197 | int *ival; // CK_SLIDER |
| 197 | int *rgb; // CK_COLOR: three ints, 0..255 | 198 | int *rgb; // CK_COLOR: three ints, 0..255 |
| 199 | const char *const *opts; // CK_CHOICE: option labels | ||
| 200 | int nopts; | ||
| 198 | int min, max; | 201 | int min, max; |
| 199 | int action; // CK_BUTTON | 202 | int action; // CK_BUTTON |
| 200 | bool zero_unlimited; // slider: value 0 displays as "All" | 203 | bool zero_unlimited; // slider: value 0 displays as "All" |
| @@ -253,6 +256,8 @@ static void add_ctrl(int card, int kind, const char *label, const char *desc, | |||
| 253 | c.action = action; | 256 | c.action = action; |
| 254 | c.zero_unlimited = false; | 257 | c.zero_unlimited = false; |
| 255 | c.rgb = nullptr; | 258 | c.rgb = nullptr; |
| 259 | c.opts = nullptr; | ||
| 260 | c.nopts = 0; | ||
| 256 | c.hue = 0.0f; | 261 | c.hue = 0.0f; |
| 257 | c.sat = 0.0f; | 262 | c.sat = 0.0f; |
| 258 | c.val = 1.0f; | 263 | c.val = 1.0f; |
| @@ -282,11 +287,19 @@ static void add_color(int card, const char *label, const char *desc, | |||
| 282 | add_ctrl(card, CK_COLOR, label, desc, 0, 255, nullptr, nullptr, ACT_NONE); | 287 | add_ctrl(card, CK_COLOR, label, desc, 0, 255, nullptr, nullptr, ACT_NONE); |
| 283 | g_ctrls[i].rgb = rgb; | 288 | g_ctrls[i].rgb = rgb; |
| 284 | } | 289 | } |
| 290 | static void add_choice(int card, const char *label, const char *desc, | ||
| 291 | const char *const *opts, int nopts, int *value) { | ||
| 292 | int i = g_nctrl; | ||
| 293 | add_ctrl(card, CK_CHOICE, label, desc, 0, nopts > 0 ? nopts - 1 : 0, | ||
| 294 | nullptr, value, ACT_NONE); | ||
| 295 | g_ctrls[i].opts = opts; | ||
| 296 | g_ctrls[i].nopts = nopts; | ||
| 297 | } | ||
| 285 | 298 | ||
| 286 | static int ctrl_val(const Ctrl &c) { | 299 | static int ctrl_val(const Ctrl &c) { |
| 287 | if (c.kind == CK_TOGGLE && c.bval) | 300 | if (c.kind == CK_TOGGLE && c.bval) |
| 288 | return *c.bval ? 1 : 0; | 301 | return *c.bval ? 1 : 0; |
| 289 | if (c.kind == CK_SLIDER && c.ival) | 302 | if ((c.kind == CK_SLIDER || c.kind == CK_CHOICE) && c.ival) |
| 290 | return *c.ival; | 303 | return *c.ival; |
| 291 | return 0; | 304 | return 0; |
| 292 | } | 305 | } |
| @@ -294,7 +307,7 @@ static void ctrl_set(Ctrl &c, int v) { | |||
| 294 | if (c.kind == CK_TOGGLE && c.bval) { | 307 | if (c.kind == CK_TOGGLE && c.bval) { |
| 295 | *c.bval = v != 0; | 308 | *c.bval = v != 0; |
| 296 | config_save(); | 309 | config_save(); |
| 297 | } else if (c.kind == CK_SLIDER && c.ival) { | 310 | } else if ((c.kind == CK_SLIDER || c.kind == CK_CHOICE) && c.ival) { |
| 298 | if (v < c.min) v = c.min; | 311 | if (v < c.min) v = c.min; |
| 299 | if (v > c.max) v = c.max; | 312 | if (v > c.max) v = c.max; |
| 300 | if (*c.ival != v) { | 313 | if (*c.ival != v) { |
| @@ -420,6 +433,13 @@ static void build_cards() { | |||
| 420 | add_toggle(fog, "Remove fog", | 433 | add_toggle(fog, "Remove fog", |
| 421 | "Disable fog entirely instead of recolouring it.", &g_fog_disable); | 434 | "Disable fog entirely instead of recolouring it.", &g_fog_disable); |
| 422 | 435 | ||
| 436 | int sky = add_card(2, &g_skybox, "Skybox", | ||
| 437 | "Swap the skybox at runtime through sv_skyname. The " | ||
| 438 | "map's own sky comes back on level change.", | ||
| 439 | true); | ||
| 440 | add_choice(sky, "Sky", "Pick a skybox from the HL2 set.", skybox_options(), | ||
| 441 | skybox_option_count(), &g_skybox_choice); | ||
| 442 | |||
| 423 | int spread = add_card(3, &g_spread, "Spread control", | 443 | int spread = add_card(3, &g_spread, "Spread control", |
| 424 | "Choose a server shot seed that tightens the cone.", | 444 | "Choose a server shot seed that tightens the cone.", |
| 425 | true); | 445 | true); |
| @@ -835,6 +855,8 @@ static int header_h(const Card &c) { | |||
| 835 | static int label_max_w(const Ctrl &c) { | 855 | static int label_max_w(const Ctrl &c) { |
| 836 | if (c.kind == CK_SLIDER) | 856 | if (c.kind == CK_SLIDER) |
| 837 | return S(214.0f); | 857 | return S(214.0f); |
| 858 | if (c.kind == CK_CHOICE) | ||
| 859 | return card_content_w() - S(16.0f) - S(250.0f); | ||
| 838 | return card_content_w() - S(16.0f) - S(110.0f); | 860 | return card_content_w() - S(16.0f) - S(110.0f); |
| 839 | } | 861 | } |
| 840 | 862 | ||
| @@ -1409,6 +1431,25 @@ static void handle_mouse_click() { | |||
| 1409 | } | 1431 | } |
| 1410 | return; | 1432 | return; |
| 1411 | } | 1433 | } |
| 1434 | if (ct.kind == CK_CHOICE) { | ||
| 1435 | int base = label_row_h(ct); | ||
| 1436 | Rect box{cr.x + cr.w - S(238.0f), | ||
| 1437 | by + (base - S(24.0f)) / 2, S(222.0f), S(24.0f)}; | ||
| 1438 | if (box.has(mx, my)) { | ||
| 1439 | int v = ctrl_val(ct); | ||
| 1440 | if (mx < box.x + S(36.0f)) | ||
| 1441 | v--; | ||
| 1442 | else if (mx >= box.x + box.w - S(36.0f)) | ||
| 1443 | v++; | ||
| 1444 | if (v < 0) v = 0; | ||
| 1445 | if (ct.nopts > 0 && v >= ct.nopts) v = ct.nopts - 1; | ||
| 1446 | if (v != ctrl_val(ct)) { | ||
| 1447 | *ct.ival = v; | ||
| 1448 | config_save(); | ||
| 1449 | } | ||
| 1450 | } | ||
| 1451 | return; | ||
| 1452 | } | ||
| 1412 | if (ct.kind == CK_BUTTON) { | 1453 | if (ct.kind == CK_BUTTON) { |
| 1413 | int bw = button_w(ct.label); | 1454 | int bw = button_w(ct.label); |
| 1414 | Rect b{cr.x + cr.w - S(16.0f) - bw, | 1455 | Rect b{cr.x + cr.w - S(16.0f) - bw, |
| @@ -1799,6 +1840,25 @@ static void draw_card(const Rect &clip, int index, int y) { | |||
| 1799 | outline(sw, picker_open_for(ct) ? C_ACCENT : C_BORDER, &clip); | 1840 | outline(sw, picker_open_for(ct) ? C_ACCENT : C_BORDER, &clip); |
| 1800 | if (picker_open_for(ct)) | 1841 | if (picker_open_for(ct)) |
| 1801 | draw_picker(clip, cr, ct, by); | 1842 | draw_picker(clip, cr, ct, by); |
| 1843 | } else if (ct.kind == CK_CHOICE) { | ||
| 1844 | int v = ctrl_val(ct); | ||
| 1845 | const char *name = (ct.opts && v >= 0 && v < ct.nopts) | ||
| 1846 | ? ct.opts[v] | ||
| 1847 | : "?"; | ||
| 1848 | Rect box{cr.x + cr.w - S(238.0f), | ||
| 1849 | by + (base_h - S(24.0f)) / 2, S(222.0f), S(24.0f)}; | ||
| 1850 | fill(box, Col{45, 50, 60, 255}, &clip); | ||
| 1851 | outline(box, C_BORDER, &clip); | ||
| 1852 | int ty = box.y + (box.h - g_body_h) / 2; | ||
| 1853 | text_in(clip, box.x + S(8.0f), ty, "<", g_font_body, g_body_h, 0, | ||
| 1854 | C_DIM); | ||
| 1855 | text_in(clip, box.x + box.w - S(16.0f), ty, ">", g_font_body, g_body_h, | ||
| 1856 | 0, C_DIM); | ||
| 1857 | int w = text_w(name, g_body_h); | ||
| 1858 | int tx = box.x + (box.w - w) / 2; | ||
| 1859 | if (tx < box.x + S(24.0f)) | ||
| 1860 | tx = box.x + S(24.0f); | ||
| 1861 | text_in(clip, tx, ty, name, g_font_body, g_body_h, 0, C_TEXT); | ||
| 1802 | } | 1862 | } |
| 1803 | } | 1863 | } |
| 1804 | by += rh; | 1864 | by += rh; |
diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 3f1acb4..4ba1af3 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp | |||
| @@ -592,6 +592,7 @@ static void do_move(int seq, float frametime) { | |||
| 592 | fullbright_invalidate(); | 592 | fullbright_invalidate(); |
| 593 | world_tint_invalidate(); | 593 | world_tint_invalidate(); |
| 594 | fog_invalidate(); | 594 | fog_invalidate(); |
| 595 | skybox_invalidate(); | ||
| 595 | } | 596 | } |
| 596 | 597 | ||
| 597 | if (feature_active(FEAT_SPREAD)) { | 598 | if (feature_active(FEAT_SPREAD)) { |
| @@ -180,7 +180,8 @@ enum FeatureId { | |||
| 180 | FEAT_FULLBRIGHT = 10, | 180 | FEAT_FULLBRIGHT = 10, |
| 181 | FEAT_WORLD_TINT = 11, | 181 | FEAT_WORLD_TINT = 11, |
| 182 | FEAT_FOG = 12, | 182 | FEAT_FOG = 12, |
| 183 | FEAT_COUNT = 13 | 183 | FEAT_SKYBOX = 13, |
| 184 | FEAT_COUNT = 14 | ||
| 184 | }; | 185 | }; |
| 185 | enum EspOption { | 186 | enum EspOption { |
| 186 | ESP_OPT_SHOW_NEUTRAL = 0, | 187 | ESP_OPT_SHOW_NEUTRAL = 0, |
| @@ -213,11 +214,15 @@ struct FeatureState { | |||
| 213 | 214 | ||
| 214 | extern FeatureState g_bhop, g_abh, g_jumpbug, g_melee, g_spread, g_esp, | 215 | extern FeatureState g_bhop, g_abh, g_jumpbug, g_melee, g_spread, g_esp, |
| 215 | g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright, | 216 | g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright, |
| 216 | g_world_tint, g_fog; | 217 | g_world_tint, g_fog, g_skybox; |
| 217 | extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on; | 218 | extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on; |
| 218 | extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3]; | 219 | extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3]; |
| 219 | extern bool g_fog_skybox, g_fog_disable; | 220 | extern bool g_fog_skybox, g_fog_disable; |
| 220 | extern int g_fog_rgb[3], g_fog_start, g_fog_end; | 221 | extern int g_fog_rgb[3], g_fog_start, g_fog_end; |
| 222 | extern int g_skybox_choice; | ||
| 223 | int skybox_option_count(); | ||
| 224 | const char *skybox_option_name(int i); | ||
| 225 | const char *const *skybox_options(); | ||
| 221 | extern bool g_bhop_autostrafe; | 226 | extern bool g_bhop_autostrafe; |
| 222 | extern bool g_jumpbug_assist; | 227 | extern bool g_jumpbug_assist; |
| 223 | extern bool g_spread_no_spread; | 228 | extern bool g_spread_no_spread; |
| @@ -260,6 +265,8 @@ void world_tint_update(); | |||
| 260 | void world_tint_invalidate(); | 265 | void world_tint_invalidate(); |
| 261 | void fog_update(); | 266 | void fog_update(); |
| 262 | void fog_invalidate(); | 267 | void fog_invalidate(); |
| 268 | void skybox_update(); | ||
| 269 | void skybox_invalidate(); | ||
| 263 | bool resolve_server_netvars(); | 270 | bool resolve_server_netvars(); |
| 264 | int server_entity_health(int idx, int *maxhealth, int *lifestate, void **base_out); | 271 | int server_entity_health(int idx, int *maxhealth, int *lifestate, void **base_out); |
| 265 | bool server_set_velocity_z(int idx, float vz); | 272 | bool server_set_velocity_z(int idx, float vz); |
diff --git a/src/menu.cpp b/src/menu.cpp index 237502b..dfd973d 100644 --- a/src/menu.cpp +++ b/src/menu.cpp | |||
| @@ -59,6 +59,8 @@ bool g_fog_disable = false; | |||
| 59 | int g_fog_rgb[3] = {160, 170, 190}; | 59 | int g_fog_rgb[3] = {160, 170, 190}; |
| 60 | int g_fog_start = 200; | 60 | int g_fog_start = 200; |
| 61 | int g_fog_end = 3000; | 61 | int g_fog_end = 3000; |
| 62 | FeatureState g_skybox{false, 0}; // sv_skyname override | ||
| 63 | int g_skybox_choice = 0; // 0 = map default | ||
| 62 | bool g_item_health = true; | 64 | bool g_item_health = true; |
| 63 | bool g_item_ammo = true; | 65 | bool g_item_ammo = true; |
| 64 | bool g_item_armor = true; | 66 | bool g_item_armor = true; |
| @@ -212,6 +214,8 @@ void config_save() { | |||
| 212 | "start=%d\nend=%d\nskybox=%d\ndisable=%d\n", | 214 | "start=%d\nend=%d\nskybox=%d\ndisable=%d\n", |
| 213 | g_fog.active, g_fog.bind, g_fog_rgb[0], g_fog_rgb[1], g_fog_rgb[2], | 215 | g_fog.active, g_fog.bind, g_fog_rgb[0], g_fog_rgb[1], g_fog_rgb[2], |
| 214 | g_fog_start, g_fog_end, (int)g_fog_skybox, (int)g_fog_disable); | 216 | g_fog_start, g_fog_end, (int)g_fog_skybox, (int)g_fog_disable); |
| 217 | fprintf(f, "[skybox]\nactive=%d\nbind=%d\nchoice=%d\n", g_skybox.active, | ||
| 218 | g_skybox.bind, g_skybox_choice); | ||
| 215 | fprintf(f, "[items]\nactive=%d\nbind=%d\nhealth=%d\nammo=%d\n" | 219 | fprintf(f, "[items]\nactive=%d\nbind=%d\nhealth=%d\nammo=%d\n" |
| 216 | "armor=%d\nweapons=%d\ncrates=%d\ndistance=%d\nmax_dist=%d\n", | 220 | "armor=%d\nweapons=%d\ncrates=%d\ndistance=%d\nmax_dist=%d\n", |
| 217 | g_item_esp.active, g_item_esp.bind, (int)g_item_health, | 221 | g_item_esp.active, g_item_esp.bind, (int)g_item_health, |
| @@ -246,7 +250,7 @@ static bool config_load() { | |||
| 246 | FeatureState *cur = nullptr; | 250 | FeatureState *cur = nullptr; |
| 247 | bool in_esp = false, in_bhop = false, in_aim = false, in_silent = false, | 251 | bool in_esp = false, in_bhop = false, in_aim = false, in_silent = false, |
| 248 | in_jumpbug = false, in_spread = false, in_items = false, | 252 | in_jumpbug = false, in_spread = false, in_items = false, |
| 249 | in_tint = false, in_fog = false; | 253 | in_tint = false, in_fog = false, in_skybox = false; |
| 250 | while (fgets(line, sizeof(line), f)) { | 254 | while (fgets(line, sizeof(line), f)) { |
| 251 | line[strcspn(line, "\r\n")] = 0; | 255 | line[strcspn(line, "\r\n")] = 0; |
| 252 | if (line[0] == '[') { | 256 | if (line[0] == '[') { |
| @@ -270,6 +274,8 @@ static bool config_load() { | |||
| 270 | cur = &g_world_tint; | 274 | cur = &g_world_tint; |
| 271 | else if (strstr(line, "[fog]")) | 275 | else if (strstr(line, "[fog]")) |
| 272 | cur = &g_fog; | 276 | cur = &g_fog; |
| 277 | else if (strstr(line, "[skybox]")) | ||
| 278 | cur = &g_skybox; | ||
| 273 | else if (strstr(line, "[aimbot]")) | 279 | else if (strstr(line, "[aimbot]")) |
| 274 | cur = &g_aimbot; | 280 | cur = &g_aimbot; |
| 275 | else if (strstr(line, "[triggerbot]")) | 281 | else if (strstr(line, "[triggerbot]")) |
| @@ -287,6 +293,7 @@ static bool config_load() { | |||
| 287 | in_items = (cur == &g_item_esp); | 293 | in_items = (cur == &g_item_esp); |
| 288 | in_tint = (cur == &g_world_tint); | 294 | in_tint = (cur == &g_world_tint); |
| 289 | in_fog = (cur == &g_fog); | 295 | in_fog = (cur == &g_fog); |
| 296 | in_skybox = (cur == &g_skybox); | ||
| 290 | } else if (cur) { | 297 | } else if (cur) { |
| 291 | char *eq = strchr(line, '='); | 298 | char *eq = strchr(line, '='); |
| 292 | if (!eq) | 299 | if (!eq) |
| @@ -407,6 +414,12 @@ static bool config_load() { | |||
| 407 | g_fog_disable = atoi(val) != 0; | 414 | g_fog_disable = atoi(val) != 0; |
| 408 | } | 415 | } |
| 409 | } | 416 | } |
| 417 | if (in_skybox && strcmp(key, "choice") == 0) { | ||
| 418 | int v = atoi(val); | ||
| 419 | if (v < 0) v = 0; | ||
| 420 | if (v >= skybox_option_count()) v = 0; | ||
| 421 | g_skybox_choice = v; | ||
| 422 | } | ||
| 410 | if (in_esp) { | 423 | if (in_esp) { |
| 411 | if (strcmp(key, "neutral") == 0) | 424 | if (strcmp(key, "neutral") == 0) |
| 412 | g_esp_opt[ESP_OPT_SHOW_NEUTRAL] = atoi(val) != 0; | 425 | g_esp_opt[ESP_OPT_SHOW_NEUTRAL] = atoi(val) != 0; |
| @@ -475,6 +488,8 @@ void config_reset_defaults() { | |||
| 475 | g_fog_rgb[2] = 190; | 488 | g_fog_rgb[2] = 190; |
| 476 | g_fog_start = 200; | 489 | g_fog_start = 200; |
| 477 | g_fog_end = 3000; | 490 | g_fog_end = 3000; |
| 491 | g_skybox = {false, 0}; | ||
| 492 | g_skybox_choice = 0; | ||
| 478 | g_item_health = true; | 493 | g_item_health = true; |
| 479 | g_item_ammo = true; | 494 | g_item_ammo = true; |
| 480 | g_item_armor = true; | 495 | g_item_armor = true; |
| @@ -592,7 +607,7 @@ static FeatureState *feature_state(int feature) { | |||
| 592 | static FeatureState *feats[FEAT_COUNT] = { | 607 | static FeatureState *feats[FEAT_COUNT] = { |
| 593 | &g_bhop, &g_abh, &g_esp, &g_aimbot, &g_triggerbot, | 608 | &g_bhop, &g_abh, &g_esp, &g_aimbot, &g_triggerbot, |
| 594 | &g_silentaim, &g_jumpbug, &g_melee, &g_spread, &g_item_esp, | 609 | &g_silentaim, &g_jumpbug, &g_melee, &g_spread, &g_item_esp, |
| 595 | &g_fullbright, &g_world_tint, &g_fog}; | 610 | &g_fullbright, &g_world_tint, &g_fog, &g_skybox}; |
| 596 | if (feature < 0 || feature >= FEAT_COUNT) | 611 | if (feature < 0 || feature >= FEAT_COUNT) |
| 597 | return nullptr; | 612 | return nullptr; |
| 598 | return feats[feature]; | 613 | return feats[feature]; |
| @@ -800,6 +815,7 @@ void outer_draw() { | |||
| 800 | fullbright_update(); | 815 | fullbright_update(); |
| 801 | world_tint_update(); | 816 | world_tint_update(); |
| 802 | fog_update(); | 817 | fog_update(); |
| 818 | skybox_update(); | ||
| 803 | gui_handle_input(); | 819 | gui_handle_input(); |
| 804 | // HL2H_DRAW_TEST=1 draws a probe rect without needing the GUI open | 820 | // HL2H_DRAW_TEST=1 draws a probe rect without needing the GUI open |
| 805 | static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; | 821 | static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; |
| @@ -822,6 +838,7 @@ void outer_draw() { | |||
| 822 | font_reset = true; | 838 | font_reset = true; |
| 823 | world_tint_invalidate(); | 839 | world_tint_invalidate(); |
| 824 | fog_invalidate(); | 840 | fog_invalidate(); |
| 841 | skybox_invalidate(); | ||
| 825 | } | 842 | } |
| 826 | } | 843 | } |
| 827 | if (font_reset && g_font) { | 844 | if (font_reset && g_font) { |
diff --git a/src/world.cpp b/src/world.cpp index 2333829..73738db 100644 --- a/src/world.cpp +++ b/src/world.cpp | |||
| @@ -340,3 +340,155 @@ void fog_update() { | |||
| 340 | logf("fog: %s colour=(%d,%d,%d) start=%d end=%d skybox=%d", disable ? "off" | 340 | logf("fog: %s colour=(%d,%d,%d) start=%d end=%d skybox=%d", disable ? "off" |
| 341 | : "on", r, g, b, start, end, (int)skybox); | 341 | : "on", r, g, b, start, end, (int)skybox); |
| 342 | } | 342 | } |
| 343 | |||
| 344 | // --------------------------------------------------------------------------- | ||
| 345 | // Skybox changer (sv_skyname; same mechanism as the skybox_swapper entity) | ||
| 346 | // --------------------------------------------------------------------------- | ||
| 347 | |||
| 348 | namespace { | ||
| 349 | |||
| 350 | // HL2's skybox set. Index 0 means "do not force a sky"; the rest are the | ||
| 351 | // base names the engine expands to skybox/<name>{rt,bk,lf,ft,up,dn}. | ||
| 352 | const char *const kSkyboxLabels[] = { | ||
| 353 | "Map default", "sky_day01_01", "sky_day01_04", "sky_day01_05", | ||
| 354 | "sky_day01_06", "sky_day01_07", "sky_day01_08", "sky_day01_09", | ||
| 355 | "sky_day02_01", "sky_day02_02", "sky_day02_03", "sky_day02_04", | ||
| 356 | "sky_day02_05", "sky_day02_06", "sky_day02_07", "sky_day02_09", | ||
| 357 | "sky_day02_10", "sky_day03_01", "sky_day03_02", "sky_day03_03", | ||
| 358 | "sky_day03_04", "sky_day03_05", "sky_day03_06", "sky_day03_06b", | ||
| 359 | "sky_day03_06c", "sky_wasteland02", "sky_borealis01", "sky_fog_002", | ||
| 360 | "sky_fake_white"}; | ||
| 361 | |||
| 362 | std::string g_sky_original; // map's own sky, captured before overriding | ||
| 363 | bool g_sky_original_valid = false; | ||
| 364 | std::string g_sky_active; // value we forced, empty when not forcing | ||
| 365 | |||
| 366 | // "skybox/sky_day01_01_hdrrt" -> "sky_day01_01" | ||
| 367 | bool parse_sky_material(const char *mat, char *out, size_t outn) { | ||
| 368 | if (!mat) | ||
| 369 | return false; | ||
| 370 | const char *n = mat; | ||
| 371 | if (strncmp(n, "skybox/", 7) == 0) | ||
| 372 | n += 7; | ||
| 373 | size_t len = strlen(n); | ||
| 374 | if (len < 4) | ||
| 375 | return false; | ||
| 376 | static const char *faces[] = {"rt", "bk", "lf", "ft", "up", "dn", nullptr}; | ||
| 377 | const char *suf = n + len - 2; | ||
| 378 | bool face = false; | ||
| 379 | for (int i = 0; faces[i]; i++) { | ||
| 380 | if (strcmp(suf, faces[i]) == 0) { | ||
| 381 | face = true; | ||
| 382 | break; | ||
| 383 | } | ||
| 384 | } | ||
| 385 | if (!face) | ||
| 386 | return false; | ||
| 387 | size_t blen = len - 2; | ||
| 388 | if (blen > 4 && (strncmp(n + blen - 4, "_hdr", 4) == 0 || | ||
| 389 | strncmp(n + blen - 4, "_ldr", 4) == 0)) | ||
| 390 | blen -= 4; | ||
| 391 | if (blen == 0 || blen >= outn) | ||
| 392 | return false; | ||
| 393 | memcpy(out, n, blen); | ||
| 394 | out[blen] = 0; | ||
| 395 | return true; | ||
| 396 | } | ||
| 397 | |||
| 398 | void capture_map_sky() { | ||
| 399 | if (g_sky_original_valid) | ||
| 400 | return; | ||
| 401 | g_sky_original_valid = true; // one attempt per map, even on failure | ||
| 402 | void *ms = material_system(); | ||
| 403 | if (!ms) | ||
| 404 | return; | ||
| 405 | auto invalid = vfunc<MatCalls::InvalidFn>(ms, MATSYS_InvalidMaterial); | ||
| 406 | auto first = vfunc<MatCalls::FirstFn>(ms, MATSYS_FirstMaterial); | ||
| 407 | auto next = vfunc<MatCalls::NextFn>(ms, MATSYS_NextMaterial); | ||
| 408 | auto get = vfunc<MatCalls::GetFn>(ms, MATSYS_GetMaterial); | ||
| 409 | auto count = vfunc<MatCalls::CountFn>(ms, MATSYS_GetNumMaterials); | ||
| 410 | int total = count(ms); | ||
| 411 | if (total <= 0 || total > 200000) | ||
| 412 | return; | ||
| 413 | unsigned short inv = invalid(ms); | ||
| 414 | int seen = 0; | ||
| 415 | std::string last; | ||
| 416 | for (unsigned short h = first(ms); h != inv; h = next(ms, h)) { | ||
| 417 | if (++seen > total + 16) | ||
| 418 | break; | ||
| 419 | void *mat = get(ms, h); | ||
| 420 | if (!mat) | ||
| 421 | continue; | ||
| 422 | const char *name = vfunc<MatCalls::NameFn>(mat, MAT_GetName)(mat); | ||
| 423 | const char *group = | ||
| 424 | vfunc<MatCalls::GroupFn>(mat, MAT_GetTextureGroupName)(mat); | ||
| 425 | if (!plausible_name(name) || !plausible_name(group)) | ||
| 426 | continue; | ||
| 427 | if (strncmp(group, GROUP_SKYBOX, strlen(GROUP_SKYBOX)) != 0) | ||
| 428 | continue; | ||
| 429 | char base[128]; | ||
| 430 | if (parse_sky_material(name, base, sizeof(base))) | ||
| 431 | last = base; // newest loaded sky materials win after a level change | ||
| 432 | } | ||
| 433 | if (last.empty()) { | ||
| 434 | logd("skybox: could not capture the map sky"); | ||
| 435 | return; | ||
| 436 | } | ||
| 437 | g_sky_original = last; | ||
| 438 | logf("skybox: map sky is %s", g_sky_original.c_str()); | ||
| 439 | } | ||
| 440 | |||
| 441 | } // namespace | ||
| 442 | |||
| 443 | int skybox_option_count() { | ||
| 444 | return (int)(sizeof(kSkyboxLabels) / sizeof(kSkyboxLabels[0])); | ||
| 445 | } | ||
| 446 | |||
| 447 | const char *skybox_option_name(int i) { | ||
| 448 | int n = skybox_option_count(); | ||
| 449 | return (i >= 0 && i < n) ? kSkyboxLabels[i] : kSkyboxLabels[0]; | ||
| 450 | } | ||
| 451 | |||
| 452 | const char *const *skybox_options() { return kSkyboxLabels; } | ||
| 453 | |||
| 454 | void skybox_invalidate() { | ||
| 455 | // Level change: the engine sets sv_skyname from worldspawn again, so there is | ||
| 456 | // nothing to restore. Forget the captured base and let it recapture. | ||
| 457 | g_sky_original.clear(); | ||
| 458 | g_sky_original_valid = false; | ||
| 459 | g_sky_active.clear(); | ||
| 460 | } | ||
| 461 | |||
| 462 | void skybox_update() { | ||
| 463 | bool on = feature_active(FEAT_SKYBOX); | ||
| 464 | int choice = g_skybox_choice; | ||
| 465 | int n = skybox_option_count(); | ||
| 466 | if (choice < 0 || choice >= n) | ||
| 467 | choice = 0; | ||
| 468 | |||
| 469 | if (!on || choice == 0) { | ||
| 470 | if (g_sky_active.empty()) | ||
| 471 | return; | ||
| 472 | if (g_sky_original_valid && !g_sky_original.empty() && | ||
| 473 | g_sky_original != g_sky_active) { | ||
| 474 | char cmd[160]; | ||
| 475 | snprintf(cmd, sizeof(cmd), "sv_skyname %s", g_sky_original.c_str()); | ||
| 476 | console_cmd(cmd); | ||
| 477 | logf("skybox: restored %s", g_sky_original.c_str()); | ||
| 478 | world_tint_invalidate(); // the new sky materials need the sky tint | ||
| 479 | } | ||
| 480 | g_sky_active.clear(); | ||
| 481 | return; | ||
| 482 | } | ||
| 483 | |||
| 484 | capture_map_sky(); // before the first override of this map | ||
| 485 | const char *want = skybox_option_name(choice); | ||
| 486 | if (g_sky_active == want) | ||
| 487 | return; | ||
| 488 | char cmd[160]; | ||
| 489 | snprintf(cmd, sizeof(cmd), "sv_skyname %s", want); | ||
| 490 | console_cmd(cmd); | ||
| 491 | logf("skybox: %s", want); | ||
| 492 | g_sky_active = want; | ||
| 493 | world_tint_invalidate(); // tint materials for the freshly loaded sky | ||
| 494 | } | ||
