diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-03 23:51:02 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-03 23:51:02 +0100 |
| commit | 95b5be2299b332917ae63c45252cb9744a3460cd (patch) | |
| tree | 1da6d89d8ddf9f724368738a4969f8dd308cbcce /src/world.cpp | |
| parent | AI: treat scripted NPCs as neutral; Lamarr neutral (diff) | |
| download | hl2h-95b5be2299b332917ae63c45252cb9744a3460cd.tar.gz hl2h-95b5be2299b332917ae63c45252cb9744a3460cd.zip | |
World: add skybox changer
New Visuals -> Skybox card with a runtime sv_skyname override using the same
mechanism as the stock skybox_swapper entity. The choice list is the HL2
skybox set extracted from the game VPKs.
The map's own sky is captured from loaded materials (SkyBox group, parsing
skybox/<name>... face names) before the first override, so turning the
feature off or picking "Map default" restores it; a level change restores it
implicitly. Sky changes invalidate the world tint so new sky materials are
tinted as well.
GUI: add a CK_CHOICE control (value box with < / > zones).
Diffstat (limited to 'src/world.cpp')
| -rw-r--r-- | src/world.cpp | 152 |
1 files changed, 152 insertions, 0 deletions
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 | } | ||
