469 lines
16 KiB
Plaintext
469 lines
16 KiB
Plaintext
#scope_file
|
|
|
|
Random :: #import "Random";
|
|
|
|
TRANSITION_SPEED :: 7.0;
|
|
MAX_BLOBS :: 32;
|
|
VOLUME_STEP_INITIAL :: 0.1;
|
|
VOLUME_STEP_HELD :: 0.3;
|
|
VOLUME_HOLD_DELAY :: 0.35;
|
|
CONFIG_PATH :: "settings.cfg";
|
|
|
|
Settings_Page :: enum {
|
|
MAIN;
|
|
SETTINGS;
|
|
AUDIO;
|
|
GRAPHICS;
|
|
}
|
|
|
|
Settings_State :: struct {
|
|
open : bool = false;
|
|
transition : float = 0.0;
|
|
page : Settings_Page = .MAIN;
|
|
cursor : s32 = 0;
|
|
title_font : *Font;
|
|
item_font : *Font;
|
|
last_screen_h : s32 = 0;
|
|
|
|
vol_hold_time : float = 0.0;
|
|
vol_hold_dir : s32 = 0;
|
|
|
|
mouse_hover : s32 = -1;
|
|
mouse_moved : bool = false;
|
|
last_mouse_x : float = -1;
|
|
last_mouse_y : float = -1;
|
|
|
|
blobs : [MAX_BLOBS]Blob;
|
|
blob_count : s32 = 0;
|
|
}
|
|
|
|
Blob :: struct {
|
|
cx, cy, radius : float;
|
|
r, g, b : float;
|
|
freq : float;
|
|
drift_x : float;
|
|
drift_y : float;
|
|
phase : float;
|
|
}
|
|
|
|
g_settings : Settings_State;
|
|
g_settings_config : Settings_Menu_Config;
|
|
|
|
MAIN_ITEMS :: string.["Resume", "Settings", "Exit"];
|
|
SETTINGS_ITEMS :: string.["Audio", "Graphics"];
|
|
AUDIO_LABELS :: string.["Master Volume", "Music Volume", "Sound Effects"];
|
|
GRAPHICS_ITEMS :: string.["Fullscreen"];
|
|
|
|
page_items :: () -> []string {
|
|
if g_settings.page == .MAIN return MAIN_ITEMS;
|
|
if g_settings.page == .SETTINGS return SETTINGS_ITEMS;
|
|
if g_settings.page == .AUDIO return AUDIO_LABELS;
|
|
if g_settings.page == .GRAPHICS return GRAPHICS_ITEMS;
|
|
return .[];
|
|
}
|
|
|
|
page_parent :: () -> Settings_Page {
|
|
if g_settings.page == .AUDIO return .SETTINGS;
|
|
if g_settings.page == .GRAPHICS return .SETTINGS;
|
|
if g_settings.page == .SETTINGS return .MAIN;
|
|
return .MAIN;
|
|
}
|
|
|
|
page_hint :: () -> string {
|
|
if g_settings.page == .MAIN return "Up/Down navigate Enter select";
|
|
if g_settings.page == .SETTINGS return "Up/Down navigate Enter select Esc back";
|
|
if g_settings.page == .AUDIO return "Up/Down navigate Left/Right adjust Esc back";
|
|
if g_settings.page == .GRAPHICS return "Enter toggle Esc back";
|
|
return "";
|
|
}
|
|
|
|
audio_get :: (i: s32) -> float {
|
|
if i == 0 return g_mixer.config.masterVolume;
|
|
if i == 1 return g_mixer.config.musicVolume;
|
|
if i == 2 return g_mixer.config.soundEffectVolume;
|
|
return 0.0;
|
|
}
|
|
|
|
audio_set :: (i: s32, v: float) {
|
|
if i == 0 then g_mixer.config.masterVolume = clamp(v, 0.0, 1.0);
|
|
if i == 1 then g_mixer.config.musicVolume = clamp(v, 0.0, 1.0);
|
|
if i == 2 then g_mixer.config.soundEffectVolume = clamp(v, 0.0, 1.0);
|
|
}
|
|
|
|
get_item_label :: (page: Settings_Page, index: s32) -> string {
|
|
items := page_items();
|
|
if index < 0 || index >= items.count then return "";
|
|
|
|
if page == .AUDIO {
|
|
pct := cast(s32)(audio_get(index) * 100.0 + 0.5);
|
|
return tprint("% %", items[index], make_volume_bar(pct));
|
|
}
|
|
if page == .GRAPHICS && index == 0 {
|
|
return tprint("%: %", items[index], ifx sapp_is_fullscreen() then "On" else "Off");
|
|
}
|
|
return items[index];
|
|
}
|
|
|
|
rand_range :: (lo: float, hi: float) -> float {
|
|
return lo + Random.random_get_zero_to_one() * (hi - lo);
|
|
}
|
|
|
|
generate_blobs :: () {
|
|
cfg := *g_settings_config.phosphenes;
|
|
count := clamp(cfg.blob_count, 0, MAX_BLOBS);
|
|
g_settings.blob_count = count;
|
|
|
|
for i: 0..count-1 {
|
|
blob := *g_settings.blobs[i];
|
|
blob.cx = rand_range(0.05, 0.95);
|
|
blob.cy = rand_range(0.15, 0.85);
|
|
blob.radius = rand_range(cfg.radius_min, cfg.radius_max);
|
|
blob.freq = rand_range(cfg.freq_min, cfg.freq_max);
|
|
blob.drift_x = rand_range(cfg.drift_min, cfg.drift_max);
|
|
blob.drift_y = rand_range(cfg.drift_min, cfg.drift_max);
|
|
blob.phase = rand_range(0.0, 6.28);
|
|
|
|
if cfg.palette.count > 0 {
|
|
idx := cast(s32)(Random.random_get_zero_to_one() * cast(float)(cfg.palette.count - 1) + 0.5);
|
|
idx = clamp(idx, 0, cast(s32)(cfg.palette.count - 1));
|
|
base := cfg.palette[idx];
|
|
blob.r = clamp(base.x + rand_range(-cfg.color_jitter, cfg.color_jitter), 0.0, 1.0);
|
|
blob.g = clamp(base.y + rand_range(-cfg.color_jitter, cfg.color_jitter), 0.0, 1.0);
|
|
blob.b = clamp(base.z + rand_range(-cfg.color_jitter, cfg.color_jitter), 0.0, 1.0);
|
|
} else {
|
|
blob.r = rand_range(0.1, 0.5);
|
|
blob.g = rand_range(0.1, 0.5);
|
|
blob.b = rand_range(0.1, 0.5);
|
|
}
|
|
}
|
|
}
|
|
|
|
update_fonts_for_screen :: () {
|
|
_, h := get_window_size();
|
|
if h == g_settings.last_screen_h then return;
|
|
g_settings.last_screen_h = h;
|
|
g_settings.title_font = get_font_at_size(max(cast(s32)(cast(float)h * 0.055), 20));
|
|
g_settings.item_font = get_font_at_size(max(cast(s32)(cast(float)h * 0.028), 14));
|
|
}
|
|
|
|
save_settings :: () {
|
|
file :: #import "File";
|
|
builder : String_Builder;
|
|
append(*builder, tprint("master_volume %\n", g_mixer.config.masterVolume));
|
|
append(*builder, tprint("music_volume %\n", g_mixer.config.musicVolume));
|
|
append(*builder, tprint("sfx_volume %\n", g_mixer.config.soundEffectVolume));
|
|
append(*builder, tprint("fullscreen %\n", cast(s32) sapp_is_fullscreen()));
|
|
file.write_entire_file(CONFIG_PATH, builder_to_string(*builder,, allocator = temp));
|
|
}
|
|
|
|
load_settings :: () {
|
|
file :: #import "File";
|
|
data, ok := file.read_entire_file(CONFIG_PATH,, allocator = temp);
|
|
if !ok then return;
|
|
|
|
text := cast(string) data;
|
|
while text.count > 0 {
|
|
line := consume_line(*text);
|
|
if line.count == 0 then continue;
|
|
key, val := split_first(line, #char " ");
|
|
if key == "master_volume" then g_mixer.config.masterVolume = parse_float(val);
|
|
if key == "music_volume" then g_mixer.config.musicVolume = parse_float(val);
|
|
if key == "sfx_volume" then g_mixer.config.soundEffectVolume = parse_float(val);
|
|
if key == "fullscreen" {
|
|
if (parse_int(val) != 0) != sapp_is_fullscreen() then sapp_toggle_fullscreen();
|
|
}
|
|
}
|
|
}
|
|
|
|
consume_line :: (text: *string) -> string {
|
|
s := text.*;
|
|
for i: 0..s.count-1 {
|
|
if s[i] == #char "\n" {
|
|
line := string.{count = i, data = s.data};
|
|
text.data += i + 1;
|
|
text.count -= i + 1;
|
|
return line;
|
|
}
|
|
}
|
|
result := s;
|
|
text.count = 0;
|
|
return result;
|
|
}
|
|
|
|
split_first :: (s: string, sep: u8) -> (string, string) {
|
|
for i: 0..s.count-1 {
|
|
if s[i] == sep {
|
|
return string.{count = i, data = s.data},
|
|
string.{count = s.count - i - 1, data = s.data + i + 1};
|
|
}
|
|
}
|
|
return s, "";
|
|
}
|
|
|
|
parse_float :: (s: string) -> float {
|
|
val, ok := string_to_float(s);
|
|
if ok return val;
|
|
return 0.0;
|
|
}
|
|
|
|
parse_int :: (s: string) -> s64 {
|
|
val, ok := string_to_int(s);
|
|
if ok return val;
|
|
return 0;
|
|
}
|
|
|
|
navigate_to :: (page: Settings_Page) {
|
|
g_settings.page = page;
|
|
g_settings.cursor = 0;
|
|
}
|
|
|
|
go_back :: () {
|
|
if g_settings.page == .AUDIO || g_settings.page == .GRAPHICS then save_settings();
|
|
if g_settings.page == .MAIN {
|
|
g_settings.open = false;
|
|
} else {
|
|
navigate_to(page_parent());
|
|
}
|
|
}
|
|
|
|
handle_enter :: () {
|
|
if g_settings.page == {
|
|
case .MAIN;
|
|
if g_settings.cursor == 0 then g_settings.open = false;
|
|
if g_settings.cursor == 1 then navigate_to(.SETTINGS);
|
|
if g_settings.cursor == 2 { save_settings(); sapp_request_quit(); }
|
|
case .SETTINGS;
|
|
if g_settings.cursor == 0 then navigate_to(.AUDIO);
|
|
if g_settings.cursor == 1 then navigate_to(.GRAPHICS);
|
|
case .GRAPHICS;
|
|
if g_settings.cursor == 0 then sapp_toggle_fullscreen();
|
|
}
|
|
}
|
|
|
|
#scope_export
|
|
|
|
Phosphene_Config :: struct {
|
|
blob_count : s32 = 12;
|
|
alpha : float = 0.06;
|
|
layers : s32 = 4;
|
|
radius_min : float = 0.08;
|
|
radius_max : float = 0.28;
|
|
freq_min : float = 0.1;
|
|
freq_max : float = 0.4;
|
|
drift_min : float = 0.02;
|
|
drift_max : float = 0.07;
|
|
color_jitter : float = 0.1;
|
|
palette : []Vector3;
|
|
}
|
|
|
|
Settings_Menu_Config :: struct {
|
|
game_title : string = "TRUENO";
|
|
credits : string = "Trueno engine by Tuomas Katajisto 2026";
|
|
bg_color : Vector4 = .{0.04, 0.04, 0.06, 1.0};
|
|
title_color : Vector4 = .{1.0, 1.0, 1.0, 1.0};
|
|
item_color : Vector4 = .{0.45, 0.45, 0.45, 1.0};
|
|
selected_color : Vector4 = .{1.0, 0.95, 0.65, 1.0};
|
|
hint_color : Vector4 = .{0.45, 0.45, 0.45, 1.0};
|
|
credits_color : Vector4 = .{0.3, 0.3, 0.3, 1.0};
|
|
phosphenes : Phosphene_Config;
|
|
}
|
|
|
|
set_settings_config :: (config: Settings_Menu_Config) {
|
|
g_settings_config = config;
|
|
}
|
|
|
|
settings_menu_blocks_game :: () -> bool {
|
|
return g_settings.open;
|
|
}
|
|
|
|
init_settings_menu :: () {
|
|
g_settings.title_font = get_font_at_size(60);
|
|
g_settings.item_font = get_font_at_size(30);
|
|
load_settings();
|
|
}
|
|
|
|
tick_settings_menu :: () {
|
|
if in_editor_view then return;
|
|
update_fonts_for_screen();
|
|
|
|
if is_action_start(Editor_Action.TOGGLE_SETTINGS) {
|
|
if !g_settings.open {
|
|
g_settings.open = true;
|
|
navigate_to(.MAIN);
|
|
generate_blobs();
|
|
} else {
|
|
go_back();
|
|
}
|
|
}
|
|
|
|
dt := cast(float) delta_time;
|
|
if g_settings.open {
|
|
g_settings.transition = min(g_settings.transition + TRANSITION_SPEED * dt, 1.0);
|
|
} else {
|
|
g_settings.transition = max(g_settings.transition - TRANSITION_SPEED * dt, 0.0);
|
|
}
|
|
|
|
if !g_settings.open then return;
|
|
if console_open_ignore_input then return;
|
|
|
|
count := cast(s32) page_items().count;
|
|
|
|
mx := input_mouse_x;
|
|
my := input_mouse_y;
|
|
g_settings.mouse_moved = (mx != g_settings.last_mouse_x || my != g_settings.last_mouse_y);
|
|
g_settings.last_mouse_x = mx;
|
|
g_settings.last_mouse_y = my;
|
|
|
|
if g_settings.mouse_moved && g_settings.mouse_hover >= 0 && g_settings.mouse_hover < count {
|
|
g_settings.cursor = g_settings.mouse_hover;
|
|
}
|
|
|
|
up := cast(bool)(input_button_states[Key_Code.ARROW_UP] & .START);
|
|
down := cast(bool)(input_button_states[Key_Code.ARROW_DOWN] & .START);
|
|
if up then g_settings.cursor = (g_settings.cursor - 1 + count) % count;
|
|
if down then g_settings.cursor = (g_settings.cursor + 1) % count;
|
|
|
|
enter := cast(bool)(input_button_states[Key_Code.ENTER] & .START);
|
|
click := cast(bool)(input_button_states[Key_Code.MOUSE_BUTTON_LEFT] & .START);
|
|
if click && g_settings.mouse_hover >= 0 && g_settings.mouse_hover < count {
|
|
g_settings.cursor = g_settings.mouse_hover;
|
|
enter = true;
|
|
}
|
|
if enter then handle_enter();
|
|
|
|
if g_settings.page == .AUDIO {
|
|
left := cast(bool)(input_button_states[Key_Code.ARROW_LEFT] & .START);
|
|
right := cast(bool)(input_button_states[Key_Code.ARROW_RIGHT] & .START);
|
|
left_held := cast(bool)(input_button_states[Key_Code.ARROW_LEFT] & .DOWN);
|
|
right_held := cast(bool)(input_button_states[Key_Code.ARROW_RIGHT] & .DOWN);
|
|
|
|
if left { audio_set(g_settings.cursor, audio_get(g_settings.cursor) - VOLUME_STEP_INITIAL); g_settings.vol_hold_time = 0; g_settings.vol_hold_dir = -1; }
|
|
if right { audio_set(g_settings.cursor, audio_get(g_settings.cursor) + VOLUME_STEP_INITIAL); g_settings.vol_hold_time = 0; g_settings.vol_hold_dir = 1; }
|
|
|
|
dir : s32 = 0;
|
|
if left_held then dir = -1;
|
|
if right_held then dir = 1;
|
|
|
|
if dir != 0 && dir == g_settings.vol_hold_dir {
|
|
g_settings.vol_hold_time += dt;
|
|
if g_settings.vol_hold_time > VOLUME_HOLD_DELAY {
|
|
audio_set(g_settings.cursor, audio_get(g_settings.cursor) + cast(float)dir * VOLUME_STEP_HELD * dt);
|
|
}
|
|
} else if dir == 0 {
|
|
g_settings.vol_hold_time = 0;
|
|
g_settings.vol_hold_dir = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
draw_settings_menu :: (theme: *GR.Overall_Theme) {
|
|
t := g_settings.transition;
|
|
if t < 0.001 then return;
|
|
|
|
fw := vw * 100.0;
|
|
fh := vh * 100.0;
|
|
bar_h := t * fh * 0.5;
|
|
|
|
cfg := *g_settings_config;
|
|
set_shader_for_color();
|
|
|
|
immediate_quad(.{0, 0}, .{fw, 0}, .{fw, bar_h}, .{0, bar_h}, cfg.bg_color);
|
|
immediate_flush();
|
|
bottom_y := fh - bar_h;
|
|
immediate_quad(.{0, bottom_y}, .{fw, bottom_y}, .{fw, fh}, .{0, fh}, cfg.bg_color);
|
|
immediate_flush();
|
|
|
|
if t > 0.5 {
|
|
draw_phosphenes(fw, fh, cast(float) get_time(), clamp((t - 0.5) / 0.5, 0.0, 1.0));
|
|
}
|
|
|
|
content_alpha := clamp((t - 0.75) / 0.25, 0.0, 1.0);
|
|
if content_alpha < 0.01 then return;
|
|
|
|
apply_alpha :: (base: Vector4, a: float) -> Vector4 {
|
|
return .{base.x, base.y, base.z, base.w * a};
|
|
}
|
|
|
|
title_col := apply_alpha(cfg.title_color, content_alpha);
|
|
item_col := apply_alpha(cfg.item_color, content_alpha);
|
|
sel_col := apply_alpha(cfg.selected_color, content_alpha);
|
|
hint_col := apply_alpha(cfg.hint_color, content_alpha);
|
|
credits_col := apply_alpha(cfg.credits_color, content_alpha);
|
|
|
|
prepare_text(g_settings.title_font, cfg.game_title);
|
|
draw_prepared_text(g_settings.title_font, xx ((fw - cast(float)gPreppedTextWidth) * 0.5), xx (fh * 0.22), title_col);
|
|
|
|
items := page_items();
|
|
item_h := cast(float)(g_settings.item_font.character_height) * 1.7;
|
|
total_h := cast(float)items.count * item_h;
|
|
start_y := fh * 0.5 - total_h * 0.5;
|
|
|
|
g_settings.mouse_hover = -1;
|
|
for i: 0..items.count-1 {
|
|
col := ifx cast(s32)i == g_settings.cursor then sel_col else item_col;
|
|
label := get_item_label(g_settings.page, cast(s32)i);
|
|
prepare_text(g_settings.item_font, label);
|
|
x := (fw - cast(float)gPreppedTextWidth) * 0.5;
|
|
row_y := start_y + cast(float)i * item_h;
|
|
draw_prepared_text(g_settings.item_font, xx x, xx row_y, col);
|
|
|
|
if input_mouse_y >= row_y && input_mouse_y < row_y + item_h {
|
|
g_settings.mouse_hover = cast(s32)i;
|
|
}
|
|
}
|
|
|
|
draw_centered_text(g_settings.item_font, page_hint(), fw, fh * 0.82, hint_col);
|
|
|
|
if g_settings.page == .MAIN && cfg.credits.count > 0 {
|
|
draw_centered_text(g_settings.item_font, cfg.credits, fw, fh * 0.92, credits_col);
|
|
}
|
|
}
|
|
|
|
#scope_file
|
|
|
|
draw_centered_text :: (font: *Font, text: string, fw: float, y: float, color: Vector4) {
|
|
prepare_text(font, text);
|
|
draw_prepared_text(font, xx ((fw - cast(float)gPreppedTextWidth) * 0.5), xx y, color);
|
|
}
|
|
|
|
draw_phosphenes :: (fw: float, fh: float, time: float, fade: float) {
|
|
if g_settings.blob_count < 1 then return;
|
|
set_shader_for_color();
|
|
|
|
max_alpha := g_settings_config.phosphenes.alpha;
|
|
num_layers := g_settings_config.phosphenes.layers;
|
|
if num_layers < 1 then num_layers = 4;
|
|
|
|
for i: 0..g_settings.blob_count-1 {
|
|
blob := g_settings.blobs[i];
|
|
pulse := 0.5 + 0.5 * sin(time * blob.freq * 6.28 + blob.phase);
|
|
alpha := fade * max_alpha * (0.6 + 0.4 * pulse);
|
|
cx := (blob.cx + sin(time * 0.3 + blob.phase) * blob.drift_x) * fw;
|
|
cy := (blob.cy + cos(time * 0.2 + blob.phase * 1.3) * blob.drift_y) * fh;
|
|
blob_r := blob.radius * fh;
|
|
|
|
for li: 0..num_layers-1 {
|
|
frac := cast(float)(num_layers - li) / cast(float)num_layers;
|
|
r := blob_r * frac;
|
|
layer_alpha := alpha * (1.0 - frac * 0.7);
|
|
col := Vector4.{blob.r, blob.g, blob.b, layer_alpha};
|
|
immediate_quad(.{cx - r, cy - r}, .{cx + r, cy - r}, .{cx + r, cy + r}, .{cx - r, cy + r}, col);
|
|
}
|
|
}
|
|
immediate_flush();
|
|
}
|
|
|
|
make_volume_bar :: (pct: s32) -> string {
|
|
STEPS :: 10;
|
|
filled := (pct + 5) / STEPS;
|
|
builder : String_Builder;
|
|
append(*builder, "[ ");
|
|
for i: 0..STEPS-1 {
|
|
if i < filled then append(*builder, "=");
|
|
else append(*builder, "-");
|
|
}
|
|
append(*builder, tprint(" %\%%", pct));
|
|
append(*builder, " ]");
|
|
return builder_to_string(*builder,, allocator = temp);
|
|
}
|