tr1/output: make water color player-customizable

This commit is contained in:
Marcin Kurczewski 2025-04-08 14:34:49 +02:00
parent 2fb3824b3b
commit 09ba43594d
No known key found for this signature in database
GPG key ID: CC65E6FD28CAE42A
23 changed files with 245 additions and 59 deletions

View file

@ -8,9 +8,6 @@
"savegame_fmt_legacy": "saveati.%d",
"savegame_fmt_bson": "save_tr1_%02d.dat",
"demo_delay": 16,
"water_color": [0.45, 1.0, 1.0],
"fog_start": -1,
"fog_end": -1,
"injections": [
"data/injections/backpack.bin",
"data/injections/braid.bin",

View file

@ -9,7 +9,6 @@
"savegame_fmt_legacy": "save_demo_pc.%d",
"savegame_fmt_bson": "save_demo_pc_%02d.dat",
"demo_delay": 16,
"water_color": [0.45, 1.0, 1.0],
"injections": [
"data/injections/backpack.bin",
"data/injections/braid.bin",

View file

@ -5,7 +5,6 @@
"savegame_fmt_legacy": "save_tmp.%d",
"savegame_fmt_bson": "save_tmp_%02d.dat",
"demo_delay": 0,
"water_color": [0.45, 1.0, 1.0],
"injections": [
"data/injections/backpack.bin",
"data/injections/braid.bin",

View file

@ -8,7 +8,6 @@
"savegame_fmt_legacy": "saveuba.%d",
"savegame_fmt_bson": "save_trub_%02d.dat",
"demo_delay": 16,
"water_color": [0.45, 1.0, 1.0],
"injections": [
"data/injections/backpack.bin",
"data/injections/braid.bin",

View file

@ -371,6 +371,9 @@
"DETAIL_UI_BAR_SCALE": "UI bar scale",
"DETAIL_UI_TEXT_SCALE": "UI text scale",
"DETAIL_VSYNC": "VSync",
"DETAIL_WATER_COLOR_B": "Water color (B)",
"DETAIL_WATER_COLOR_G": "Water color (G)",
"DETAIL_WATER_COLOR_R": "Water color (R)",
"HEADING_GAME_OVER": "GAME OVER",
"HEADING_INVENTORY": "INVENTORY",
"HEADING_ITEMS": "ITEMS",

View file

@ -1,5 +1,6 @@
## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr1-4.9...develop) - ××××-××-××
- added an ability to customize the fog distances (#634)
- added an ability to customize the water color (#1532)
- changed the `draw_distance_min` and `draw_distance_max` to `fog_start` and `fog_end`
- fixed the bilinear filter to not readjust the UVs (#2258)
- fixed anisotropy filter causing black lines on certain GPUs (#902)

View file

@ -1,5 +1,6 @@
#include "config/file.h"
#include "colors.h"
#include "debug.h"
#include "filesystem.h"
#include "game/console/history.h"
@ -244,6 +245,27 @@ void ConfigFile_LoadOptions(JSON_OBJECT *root_obj, const CONFIG_OPTION *options)
*(int *)opt->target = ConfigFile_ReadEnum(
root_obj, M_ResolveOptionName(opt->name),
*(int *)opt->default_value, opt->param);
break;
case COT_RGB888: {
JSON_VALUE *const value =
JSON_ObjectGetValue(root_obj, M_ResolveOptionName(opt->name));
bool success = false;
if (value != nullptr && value->type == JSON_TYPE_NUMBER) {
const uint32_t rgb_value =
JSON_ValueGetInt(value, JSON_INVALID_NUMBER);
ASSERT(rgb_value != JSON_INVALID_NUMBER);
RGB_888 *const target = (RGB_888 *)opt->target;
target->r = (rgb_value >> 0) & 0xFF;
target->g = (rgb_value >> 8) & 0xFF;
target->b = (rgb_value >> 16) & 0xFF;
success = true;
}
if (!success) {
*(RGB_888 *)opt->target = *(RGB_888 *)opt->default_value;
}
break;
}
}
opt++;
}
@ -282,6 +304,14 @@ void ConfigFile_DumpOptions(JSON_OBJECT *root_obj, const CONFIG_OPTION *options)
root_obj, M_ResolveOptionName(opt->name), *(int *)opt->target,
(const char *)opt->param);
break;
case COT_RGB888: {
const RGB_888 *const color = (RGB_888 *)opt->target;
JSON_ObjectAppendInt(
root_obj, M_ResolveOptionName(opt->name),
color->r | (color->g << 8) | (color->b << 16));
break;
}
}
opt++;
}

View file

@ -1,3 +1,4 @@
#include "colors.h"
#include "config/option.h"
#include "config/types.h"
#include "config/vars.h"
@ -39,6 +40,13 @@
.default_value = &(int32_t) { default_value_ }, \
.param = ENUM_MAP_NAME(enum_map) },
#define CFG_RGB888(parent, target_, default_r, default_g, default_b) \
{ .name = QUOTE(target_), \
.type = COT_RGB888, \
.target = &parent.target_, \
.default_value = &(RGB_888) { default_r, default_g, default_b }, \
.param = nullptr },
static const CONFIG_OPTION m_ConfigOptionMap[] = {
#include "map.def"
{}, // sentinel

View file

@ -8,6 +8,7 @@ CFG_BOOL(g_Config, gameplay.enable_deaths_counter, true)
CFG_BOOL(g_Config, gameplay.enable_enhanced_look, true)
CFG_INT32(g_Config, visuals.fog_start, 22)
CFG_INT32(g_Config, visuals.fog_end, 30)
CFG_RGB888(g_Config, visuals.water_color, 115, 255, 255)
CFG_BOOL(g_Config, visuals.enable_gun_lighting, true)
CFG_BOOL(g_Config, visuals.enable_shotgun_flash, true)
CFG_BOOL(g_Config, gameplay.fix_shotgun_targeting, true)

View file

@ -1,5 +1,6 @@
#include "game/console/cmd/config.h"
#include "colors.h"
#include "config.h"
#include "debug.h"
#include "enum_map.h"
@ -116,6 +117,13 @@ bool Console_Cmd_Config_GetCurrentValue(
target, target_size, "%s",
EnumMap_ToString(option->param, *(int32_t *)option->target));
break;
case COT_RGB888: {
const RGB_888 *color = option->target;
snprintf(
target, target_size, "%02hhx%02hhx%02hhx", color->r, color->g,
color->b);
break;
}
}
return true;
}
@ -175,6 +183,18 @@ bool Console_Cmd_Config_SetCurrentValue(
}
break;
}
case COT_RGB888: {
uint8_t r, g, b;
if (sscanf(new_value, "%02hhx%02hhx%02hhx", &r, &g, &b) == 3) {
RGB_888 *const color = (RGB_888 *)option->target;
color->r = r;
color->g = g;
color->b = b;
return true;
}
break;
}
}
return false;

View file

@ -7,12 +7,6 @@ static DECLARE_SEQUENCE_EVENT_HANDLER_FUNC(M_HandleMeshSwapEvent);
static void M_LoadLevelItemDrops(
JSON_OBJECT *obj, const GAME_FLOW *gf, GF_LEVEL *level);
static GF_LEVEL_SETTINGS m_DefaultSettings = {
.water_color = { .r = 0.6, .g = 0.7, .b = 1.0 },
.fog_start = -1,
.fog_end = -1,
};
static M_SEQUENCE_EVENT_HANDLER m_SequenceEventHandlers[] = {
// clang-format off
// Events without arguments
@ -108,7 +102,8 @@ static void M_LoadSettings(
const double value =
JSON_ObjectGetDouble(obj, "fog_start", JSON_INVALID_NUMBER);
if (value != JSON_INVALID_NUMBER) {
settings->fog_start = value;
settings->fog_start.is_present = true;
settings->fog_start.value = value;
}
}
@ -116,19 +111,28 @@ static void M_LoadSettings(
const double value =
JSON_ObjectGetDouble(obj, "fog_end", JSON_INVALID_NUMBER);
if (value != JSON_INVALID_NUMBER) {
settings->fog_end = value;
settings->fog_end.is_present = true;
settings->fog_end.value = value;
}
}
{
JSON_ARRAY *const tmp_arr = JSON_ObjectGetArray(obj, "water_color");
if (tmp_arr != nullptr) {
settings->water_color.r =
JSON_ArrayGetDouble(tmp_arr, 0, settings->water_color.r);
settings->water_color.g =
JSON_ArrayGetDouble(tmp_arr, 1, settings->water_color.g);
settings->water_color.b =
JSON_ArrayGetDouble(tmp_arr, 2, settings->water_color.b);
const RGB_F color = {
JSON_ArrayGetDouble(tmp_arr, 0, JSON_INVALID_NUMBER),
JSON_ArrayGetDouble(tmp_arr, 1, JSON_INVALID_NUMBER),
JSON_ArrayGetDouble(tmp_arr, 2, JSON_INVALID_NUMBER),
};
if (color.r != JSON_INVALID_NUMBER && color.g != JSON_INVALID_NUMBER
&& color.b != JSON_INVALID_NUMBER) {
settings->water_color.is_present = true;
settings->water_color.value = (RGB_888) {
color.r * 255.0f,
color.g * 255.0f,
color.b * 255.0f,
};
}
}
}
}
@ -237,7 +241,6 @@ static void M_LoadRoot(JSON_OBJECT *const obj, GAME_FLOW *const gf)
}
gf->demo_delay = tmp_d;
gf->settings = m_DefaultSettings;
M_LoadSettings(obj, &gf->settings);
gf->enable_tr2_item_drops =

View file

@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
typedef struct {
float r;
float g;
float b;
} RGB_F;
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
} RGB_888;
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
} RGBA_8888;

View file

@ -1,11 +1,12 @@
#pragma once
typedef enum {
COT_BOOL = 0,
COT_INT32 = 1,
COT_FLOAT = 2,
COT_DOUBLE = 3,
COT_ENUM = 4,
COT_BOOL,
COT_INT32,
COT_FLOAT,
COT_DOUBLE,
COT_ENUM,
COT_RGB888,
} CONFIG_OPTION_TYPE;
typedef struct {

View file

@ -1,5 +1,6 @@
#pragma once
#include "../game/output/types.h"
#include "../game/sound/enum.h"
#include "../gfx/common.h"
#include "../screenshot.h"
@ -100,6 +101,7 @@ typedef struct {
bool fix_texture_issues;
bool enable_ps1_crystals;
RGB_888 water_color;
int32_t fog_start;
int32_t fog_end;
} visuals;

View file

@ -79,9 +79,14 @@ typedef struct {
typedef struct {
#if TR_VERSION == 1
RGB_F water_color;
float fog_start;
float fog_end;
struct {
bool is_present;
RGB_888 value;
} water_color;
struct {
bool is_present;
float value;
} fog_start, fog_end;
#elif TR_VERSION == 2
char *sfx_path;
#endif

View file

@ -1,5 +1,7 @@
#pragma once
#include "../../colors.h"
#include <stdint.h>
typedef enum {
@ -59,25 +61,6 @@ typedef struct ANIMATED_TEXTURE_RANGE {
struct ANIMATED_TEXTURE_RANGE *next_range;
} ANIMATED_TEXTURE_RANGE;
typedef struct {
float r;
float g;
float b;
} RGB_F;
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
} RGB_888;
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
} RGBA_8888;
typedef struct {
uint8_t index[256];
} LIGHT_MAP;

View file

@ -11,6 +11,9 @@ GS_DEFINE(DETAIL_SELECT_DETAIL, "Select Detail")
GS_DEFINE(DETAIL_FPS, "FPS")
GS_DEFINE(DETAIL_FOG_START, "Fog start")
GS_DEFINE(DETAIL_FOG_END, "Fog end")
GS_DEFINE(DETAIL_WATER_COLOR_R, "Water color (R)")
GS_DEFINE(DETAIL_WATER_COLOR_G, "Water color (G)")
GS_DEFINE(DETAIL_WATER_COLOR_B, "Water color (B)")
GS_DEFINE(DETAIL_TRAPEZOID_FILTER, "Trapezoid filter")
GS_DEFINE(DETAIL_REFLECTIONS, "Reflections")
GS_DEFINE(DETAIL_BILINEAR, "Bilinear")

View file

@ -327,25 +327,37 @@ void Level_Load(const GF_LEVEL *const level)
Inject_Cleanup();
Output_SetWaterColor(&level->settings.water_color);
Output_SetSkyboxEnabled(
g_Config.visuals.enable_skybox && Object_Get(O_SKYBOX)->loaded);
Benchmark_End(&benchmark, nullptr);
}
RGB_888 Level_GetWaterColor(void)
{
const GF_LEVEL *const level = GF_GetCurrentLevel();
if (level != nullptr && level->settings.water_color.is_present) {
return level->settings.water_color.value;
}
return g_Config.visuals.water_color;
}
float Level_GetFogStart(void)
{
const GF_LEVEL *const level = GF_GetCurrentLevel();
const float result = level != nullptr ? level->settings.fog_start : 0.0f;
return result > 0 ? result : g_Config.visuals.fog_start;
if (level != nullptr && level->settings.fog_start.is_present) {
return level->settings.fog_start.value;
}
return g_Config.visuals.fog_start;
}
float Level_GetFogEnd(void)
{
const GF_LEVEL *const level = GF_GetCurrentLevel();
const float result = level != nullptr ? level->settings.fog_end : 0.0f;
return result > 0 ? result : g_Config.visuals.fog_end;
if (level != nullptr && level->settings.fog_end.is_present) {
return level->settings.fog_end.value;
}
return g_Config.visuals.fog_end;
}
void Level_Unload(void)

View file

@ -2,7 +2,11 @@
#include "game/game_flow/types.h"
#include <libtrx/colors.h>
bool Level_Initialise(const GF_LEVEL *level, GF_SEQUENCE_CONTEXT seq_ctx);
void Level_Load(const GF_LEVEL *level);
RGB_888 Level_GetWaterColor(void);
float Level_GetFogStart(void);
float Level_GetFogEnd(void);

View file

@ -24,6 +24,7 @@
#define LEFT_ARROW_OFFSET (-20)
#define RIGHT_ARROW_OFFSET_MIN 35
#define RIGHT_ARROW_OFFSET_MAX 85
#define COLOR_SHIFT 10
typedef enum {
TEXT_TITLE,
@ -40,6 +41,9 @@ typedef enum {
OPTION_FPS,
OPTION_FOG_START,
OPTION_FOG_END,
OPTION_WATER_COLOR_R,
OPTION_WATER_COLOR_G,
OPTION_WATER_COLOR_B,
OPTION_TEXTURE_FILTER,
OPTION_FBO_FILTER,
OPTION_VSYNC,
@ -77,6 +81,12 @@ static const GRAPHICS_OPTION_ROW m_GfxOptionRows[] = {
{ OPTION_FPS, GS_ID(DETAIL_FPS), GS_ID(DETAIL_INTEGER_FMT) },
{ OPTION_FOG_START, GS_ID(DETAIL_FOG_START), GS_ID(DETAIL_INTEGER_FMT) },
{ OPTION_FOG_END, GS_ID(DETAIL_FOG_END), GS_ID(DETAIL_INTEGER_FMT) },
{ OPTION_WATER_COLOR_R, GS_ID(DETAIL_WATER_COLOR_R),
GS_ID(DETAIL_INTEGER_FMT) },
{ OPTION_WATER_COLOR_G, GS_ID(DETAIL_WATER_COLOR_G),
GS_ID(DETAIL_INTEGER_FMT) },
{ OPTION_WATER_COLOR_B, GS_ID(DETAIL_WATER_COLOR_B),
GS_ID(DETAIL_INTEGER_FMT) },
{ OPTION_TEXTURE_FILTER, GS_ID(DETAIL_TEXTURE_FILTER), GS_ID(MISC_OFF) },
{ OPTION_FBO_FILTER, GS_ID(DETAIL_FBO_FILTER), GS_ID(MISC_OFF) },
{ OPTION_VSYNC, GS_ID(DETAIL_VSYNC), GS_ID(MISC_ON) },
@ -277,6 +287,18 @@ static void M_UpdateArrows(
m_HideArrowLeft = g_Config.visuals.fog_end <= 1;
m_HideArrowRight = g_Config.visuals.fog_end >= 100;
break;
case OPTION_WATER_COLOR_R:
m_HideArrowLeft = g_Config.visuals.water_color.r <= 0;
m_HideArrowRight = g_Config.visuals.water_color.r >= 255;
break;
case OPTION_WATER_COLOR_G:
m_HideArrowLeft = g_Config.visuals.water_color.g <= 0;
m_HideArrowRight = g_Config.visuals.water_color.g >= 255;
break;
case OPTION_WATER_COLOR_B:
m_HideArrowLeft = g_Config.visuals.water_color.b <= 0;
m_HideArrowRight = g_Config.visuals.water_color.b >= 255;
break;
case OPTION_TEXTURE_FILTER:
m_HideArrowLeft = g_Config.rendering.texture_filter == GFX_TF_FIRST;
m_HideArrowRight = g_Config.rendering.texture_filter == GFX_TF_LAST;
@ -407,6 +429,21 @@ static void M_ChangeTextOption(
Text_ChangeText(value_text, buf);
break;
case OPTION_WATER_COLOR_R:
sprintf(buf, GS(DETAIL_INTEGER_FMT), g_Config.visuals.water_color.r);
Text_ChangeText(value_text, buf);
break;
case OPTION_WATER_COLOR_G:
sprintf(buf, GS(DETAIL_INTEGER_FMT), g_Config.visuals.water_color.g);
Text_ChangeText(value_text, buf);
break;
case OPTION_WATER_COLOR_B:
sprintf(buf, GS(DETAIL_INTEGER_FMT), g_Config.visuals.water_color.b);
Text_ChangeText(value_text, buf);
break;
case OPTION_TEXTURE_FILTER: {
bool is_enabled = g_Config.rendering.texture_filter == GFX_TF_BILINEAR;
Text_ChangeText(
@ -507,6 +544,7 @@ void Option_Graphics_Control(INVENTORY_ITEM *inv_item, const bool is_busy)
int32_t reset = -1;
const int32_t color_shift = g_Input.slow ? 1 : COLOR_SHIFT;
if (g_InputDB.menu_right) {
switch (m_GraphicsMenu.cur_option->option_name) {
case OPTION_FPS:
@ -524,6 +562,33 @@ void Option_Graphics_Control(INVENTORY_ITEM *inv_item, const bool is_busy)
reset = OPTION_FOG_END;
break;
case OPTION_WATER_COLOR_R:
if (g_Config.visuals.water_color.r < 255 - color_shift) {
g_Config.visuals.water_color.r += color_shift;
} else {
g_Config.visuals.water_color.r = 255;
}
reset = OPTION_WATER_COLOR_R;
break;
case OPTION_WATER_COLOR_G:
if (g_Config.visuals.water_color.g < 255 - color_shift) {
g_Config.visuals.water_color.g += color_shift;
} else {
g_Config.visuals.water_color.g = 255;
}
reset = OPTION_WATER_COLOR_G;
break;
case OPTION_WATER_COLOR_B:
if (g_Config.visuals.water_color.b < 255 - color_shift) {
g_Config.visuals.water_color.b += color_shift;
} else {
g_Config.visuals.water_color.b = 255;
}
reset = OPTION_WATER_COLOR_B;
break;
case OPTION_TEXTURE_FILTER:
if (g_Config.rendering.texture_filter != GFX_TF_LAST) {
g_Config.rendering.texture_filter++;
@ -626,6 +691,34 @@ void Option_Graphics_Control(INVENTORY_ITEM *inv_item, const bool is_busy)
reset = OPTION_FOG_END;
break;
case OPTION_WATER_COLOR_R:
if (g_Config.visuals.water_color.r >= color_shift) {
g_Config.visuals.water_color.r -= color_shift;
} else {
g_Config.visuals.water_color.r = 0;
}
reset = OPTION_WATER_COLOR_R;
break;
case OPTION_WATER_COLOR_G:
if (g_Config.visuals.water_color.g >= color_shift) {
g_Config.visuals.water_color.g -= color_shift;
} else {
g_Config.visuals.water_color.g = 0;
}
reset = OPTION_WATER_COLOR_G;
break;
case OPTION_WATER_COLOR_B:
if (g_Config.visuals.water_color.b >= color_shift) {
g_Config.visuals.water_color.b -= color_shift;
} else {
g_Config.visuals.water_color.b = 0;
}
CLAMP(g_Config.visuals.water_color.b, 0, 255);
reset = OPTION_WATER_COLOR_B;
break;
case OPTION_TEXTURE_FILTER:
if (g_Config.rendering.texture_filter != GFX_TF_FIRST) {
g_Config.rendering.texture_filter--;

View file

@ -386,9 +386,10 @@ void Output_SetWindowSize(int32_t width, int32_t height)
void Output_ApplyLevelSettings(void)
{
const RGB_888 color = Level_GetWaterColor();
Output_SetWaterColor(Level_GetWaterColor());
Output_SetDrawDistFade(Level_GetFogStart() * WALL_L);
Output_SetDrawDistMax(Level_GetFogEnd() * WALL_L);
LOG_INFO("%f %f", Level_GetFogStart(), Level_GetFogEnd());
}
void Output_ApplyRenderSettings(void)

View file

@ -25,7 +25,7 @@ int32_t Output_GetDrawDistFade(void);
int32_t Output_GetDrawDistMax(void);
void Output_SetDrawDistFade(int32_t dist);
void Output_SetDrawDistMax(int32_t dist);
void Output_SetWaterColor(const RGB_F *color);
void Output_SetWaterColor(const RGB_888 color);
void Output_BeginScene(void);
void Output_EndScene(void);

View file

@ -90,11 +90,11 @@ bool Output_GetWibbleEffect(void)
return m_IsWibbleEffect;
}
void Output_SetWaterColor(const RGB_F *const color)
void Output_SetWaterColor(const RGB_888 color)
{
m_WaterColor.r = color->r;
m_WaterColor.g = color->g;
m_WaterColor.b = color->b;
m_WaterColor.r = color.r / 255.0f;
m_WaterColor.g = color.g / 255.0f;
m_WaterColor.b = color.b / 255.0f;
}
RGB_F Output_GetTint(void)