mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-17 13:00:25 +02:00
Add in-game settings menu
This commit is contained in:
1 parent
933aac6175
commit
31e54eba1b
8 files changed
+536
-15
No files matched your search
@@ -22,6 +22,19 @@ the full screen, the frame rate, the volumes, internet play, the
|
||||
multiplayer HUD), the profile's controller settings, and the keyboard and
|
||||
mouse's controls.
|
||||
|
||||
In a game, the pause menus are the maps' own. The widgets named `in_game/...`
|
||||
(`ce/in_game.xml`, also from `tools/port_settings.py`) are built in the game's
|
||||
maps instead of `ui.map`, with only what they lead to (the screens they open,
|
||||
and their bitmaps and strings): they are drawn with the pause menus' art,
|
||||
which `ui.map` has not. `in_game/settings_button` is SETTINGS, which the full
|
||||
screen pause menus (the campaign's, and a network game's with one player
|
||||
here) get after RESUME GAME (`source/interface/ui_widget.c`), on the
|
||||
desktop. Its screen opens Edit Profile's Controls Setup, Gamepads, Mouse
|
||||
Setup, Audio Setup and Video Setup over the pause menu's dim. They save to
|
||||
`config.toml` as they do from the main menu, but Gamepads, whose changes
|
||||
Edit Profile saves there, saves only its controller settings to the
|
||||
player's active profile, on OK (`port active profile edit begin` and `end`).
|
||||
|
||||
To change the menus without building the game, put files in a `menus` folder
|
||||
next to `config.toml`. A file with the same path as one here replaces it. On
|
||||
the desktop, any other `.xml` file there is added too. A PNG that a `<frame>`
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- The port's SETTINGS in the game's pause menus (tools/port_settings.py) -->
|
||||
<menus>
|
||||
<widget name="in_game/settings_button" text="SETTINGS" type="text" width="202" height="27" bitmap="ui\shell\bitmaps\menu_bkds" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3" platform="desktop">
|
||||
<on event="a" open="in_game/settings_screen"/>
|
||||
<on event="start" open="in_game/settings_screen"/>
|
||||
</widget>
|
||||
<widget name="in_game/settings_screen" width="640" height="480" flags="pass_unhandled_to_focused_child pause_game" bitmap="ui\shell\bitmaps\semi_transparent_grey" platform="desktop">
|
||||
<on event="b" back="true"/>
|
||||
<on event="back" back="true"/>
|
||||
<child widget="in_game/settings_box" x="210" y="164"/>
|
||||
<child widget="in_game/settings_list" x="218" y="170"/>
|
||||
<child widget="ui\shell\main_menu\button_key_sm" x="235" y="298"/>
|
||||
</widget>
|
||||
<widget name="in_game/settings_box" width="226" height="154" platform="desktop">
|
||||
<child widget="ui\shell\solo_game\pause_game\pausebox2_left" x="-4"/>
|
||||
<child widget="ui\shell\solo_game\pause_game\pausebox2_left_center" x="12"/>
|
||||
<child widget="ui\shell\solo_game\pause_game\pausebox2_right" x="206"/>
|
||||
</widget>
|
||||
<widget name="in_game/settings_list" type="column_list" width="202" height="119" flags="pass_unhandled_to_focused_child up_down_tabs_items" platform="desktop">
|
||||
<child widget="in_game/controls_setup_button" y="0"/>
|
||||
<child widget="in_game/gamepads_button" y="23"/>
|
||||
<child widget="in_game/mouse_setup_button" y="46"/>
|
||||
<child widget="in_game/audio_setup_button" y="69"/>
|
||||
<child widget="in_game/video_setup_button" y="92"/>
|
||||
</widget>
|
||||
<widget name="in_game/controls_setup_button" string_list="main_menu/settings_select/player_setup/player_profile_edit/profile_edit_options" string_index="1" type="text" width="202" height="27" bitmap="ui\shell\bitmaps\menu_bkds" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3" platform="desktop">
|
||||
<on event="a" open="in_game/controls_setup_screen"/>
|
||||
<on event="start" open="in_game/controls_setup_screen"/>
|
||||
</widget>
|
||||
<widget name="in_game/controls_setup_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="ui\shell\bitmaps\semi_transparent_grey" platform="desktop">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/controls_setup/controls_settings_screen"/>
|
||||
</widget>
|
||||
<widget name="in_game/gamepads_button" string_list="main_menu/settings_select/player_setup/player_profile_edit/profile_edit_options" string_index="2" type="text" width="202" height="27" bitmap="ui\shell\bitmaps\menu_bkds" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3" platform="desktop">
|
||||
<on event="a" run="port active profile edit begin" open="in_game/gamepads_screen"/>
|
||||
<on event="start" run="port active profile edit begin" open="in_game/gamepads_screen"/>
|
||||
</widget>
|
||||
<widget name="in_game/gamepads_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="ui\shell\bitmaps\semi_transparent_grey" platform="desktop">
|
||||
<on event="deleted" run="port active profile edit end"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/gamepad_setup/gamepad_setup_screen"/>
|
||||
</widget>
|
||||
<widget name="in_game/mouse_setup_button" string_list="main_menu/settings_select/player_setup/player_profile_edit/profile_edit_options" string_index="3" type="text" width="202" height="27" bitmap="ui\shell\bitmaps\menu_bkds" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3" platform="desktop">
|
||||
<on event="a" open="in_game/mouse_setup_screen"/>
|
||||
<on event="start" open="in_game/mouse_setup_screen"/>
|
||||
</widget>
|
||||
<widget name="in_game/mouse_setup_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="ui\shell\bitmaps\semi_transparent_grey" platform="desktop">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/mouse_settings/mouse_settings_screen"/>
|
||||
</widget>
|
||||
<widget name="in_game/audio_setup_button" string_list="main_menu/settings_select/player_setup/player_profile_edit/profile_edit_options" string_index="4" type="text" width="202" height="27" bitmap="ui\shell\bitmaps\menu_bkds" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3" platform="desktop">
|
||||
<on event="a" open="in_game/audio_setup_screen"/>
|
||||
<on event="start" open="in_game/audio_setup_screen"/>
|
||||
</widget>
|
||||
<widget name="in_game/audio_setup_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="ui\shell\bitmaps\semi_transparent_grey" platform="desktop">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/audio_settings/audio_settings_screen"/>
|
||||
</widget>
|
||||
<widget name="in_game/video_setup_button" string_list="main_menu/settings_select/player_setup/player_profile_edit/profile_edit_options" string_index="5" type="text" width="202" height="27" bitmap="ui\shell\bitmaps\menu_bkds" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3" platform="desktop">
|
||||
<on event="a" open="in_game/video_setup_screen"/>
|
||||
<on event="start" open="in_game/video_setup_screen"/>
|
||||
</widget>
|
||||
<widget name="in_game/video_setup_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="ui\shell\bitmaps\semi_transparent_grey" platform="desktop">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/video_settings/video_settings_screen"/>
|
||||
</widget>
|
||||
</menus>
|
||||
@@ -3,6 +3,7 @@
|
||||
"files": [
|
||||
"ce/bitmaps.xml",
|
||||
"ce/error.xml",
|
||||
"ce/in_game.xml",
|
||||
"ce/main_menu.campaign_select.xml",
|
||||
"ce/main_menu.difficulty_select.xml",
|
||||
"ce/main_menu.gametype_select.xml",
|
||||
|
||||
@@ -10,6 +10,9 @@ calls them from PC_MENU_FUNCTION_BASE on):
|
||||
- the PC version's "profile set edit begin" begins editing the first player
|
||||
profile, as its settings screens need, and fails with none (its handlers
|
||||
then open the screens that make one);
|
||||
- in a game, "port active profile edit begin" and "port active profile edit
|
||||
end" edit the player's active profile for GAMEPADS (in_game.xml) and keep
|
||||
the controller settings it sets;
|
||||
- "gamespy screen init" hides the server browser's error and filter panels,
|
||||
and the title of the mode "mp type set mode" did not choose (Internet or
|
||||
LAN);
|
||||
@@ -480,6 +483,64 @@ boolean pc_menu_profile_edit_begin(void)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* in a game, GAMEPADS (port/assets/menus/ce/in_game.xml) sets the player's
|
||||
active profile, which no Edit Profile screen is there to save: "port active
|
||||
profile edit begin" (A on its row) edits the profile, and "port active
|
||||
profile edit end" (its screen gone) puts the controller settings OK wrote
|
||||
into the profile as it is stored and as the game has it, nothing else (the
|
||||
game's copy can be older than the file: Edit Profile saves only the file),
|
||||
then saves the one and applies the other, as the game's own pause menu does
|
||||
with its inverted look (player0_look_invert_pitch). FALSE if the player has
|
||||
no profile. */
|
||||
static long active_profile_edited = NONE;
|
||||
|
||||
static boolean active_profile_edit_begin(short local_player)
|
||||
{
|
||||
long index = player_ui_get_active_player_profile_index(local_player);
|
||||
|
||||
active_profile_edited = NONE;
|
||||
if (index == NONE)
|
||||
return FALSE;
|
||||
player_ui_begin_editing_profile(index);
|
||||
if (!player_ui_get_edit_player_profile())
|
||||
return FALSE;
|
||||
active_profile_edited = index;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static boolean active_profile_edit_end(short local_player)
|
||||
{
|
||||
struct player_profile *edited = player_ui_get_edit_player_profile();
|
||||
long index = active_profile_edited;
|
||||
|
||||
active_profile_edited = NONE;
|
||||
if (index == NONE)
|
||||
return TRUE;
|
||||
if (edited && player_ui_edit_profile_is_dirty())
|
||||
{
|
||||
struct player_profile profile;
|
||||
|
||||
if (player_profile_get(index, &profile))
|
||||
{
|
||||
profile.controller_settings = edited->controller_settings;
|
||||
player_profile_save(index, &profile);
|
||||
if (player_ui_get_active_player_profile_index(local_player) == index)
|
||||
{
|
||||
player_ui_get_active_player_profile(local_player, &profile);
|
||||
profile.controller_settings = edited->controller_settings;
|
||||
player_ui_set_active_player_profile(local_player, index, &profile);
|
||||
}
|
||||
platform_log("menus: the profile's controller settings saved");
|
||||
}
|
||||
else
|
||||
{
|
||||
platform_log("menus: could not read the profile; its controller settings not saved");
|
||||
}
|
||||
}
|
||||
player_ui_end_editing_profile();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ---------- the campaign */
|
||||
|
||||
/* the rows of its lists (main_menu/new_select/list_item_0 to 10) */
|
||||
@@ -3386,6 +3447,14 @@ boolean pc_menu_event_function_invoke(
|
||||
{
|
||||
return pc_menu_profile_edit_begin();
|
||||
}
|
||||
else if (!strcmp(name, "port active profile edit begin"))
|
||||
{
|
||||
return active_profile_edit_begin(controller_of(widget));
|
||||
}
|
||||
else if (!strcmp(name, "port active profile edit end"))
|
||||
{
|
||||
return active_profile_edit_end(controller_of(widget));
|
||||
}
|
||||
else if (!strcmp(name, "mouse emit accept event"))
|
||||
{
|
||||
event_manager_post_button(controller_of(widget), BUTTON_A);
|
||||
|
||||
+142
-14
@@ -23,6 +23,11 @@ game keeps its own menus.
|
||||
The tag table cannot grow where it is (the tags' names follow it), so it
|
||||
is copied, with ours after it; every existing tag keeps its index. All of it
|
||||
is let go in scenario_tags_unload, before the next map's tags load.
|
||||
|
||||
The game's maps get the in-game widgets (in_game/..., which ui.map has not:
|
||||
they are drawn with the pause menus' art, which only the game's maps have),
|
||||
and only what they lead to: the settings screens they open, with their
|
||||
bitmaps and strings (ui_widget.c adds SETTINGS to the pause menus).
|
||||
*/
|
||||
|
||||
#include "cseries.h"
|
||||
@@ -58,6 +63,7 @@ boolean pc_menu_tag(long tag_index);
|
||||
/* ---------- constants */
|
||||
|
||||
#define PC_MENU_TAG_PREFIX "pc\\"
|
||||
#define IN_GAME_WIDGET_PREFIX "in_game/"
|
||||
#define UI_WIDGET_DEFINITION_TAG 'DeLa'
|
||||
#define UNICODE_STRING_LIST_TAG 'ustr'
|
||||
#define FONT_TAG 'font'
|
||||
@@ -334,6 +340,8 @@ static char const *const port_function_names[] =
|
||||
"mp profile init indicator opts", "mp profile set ctf rules", "mp profile set koth rules",
|
||||
"mp profile set slayer rules", "mp profile set oddball rules", "mp profile set racing rules",
|
||||
"mp profile set player options", "mp profile set item options", "mp profile set indicator opts",
|
||||
/* (in a game: GAMEPADS on the player's active profile) */
|
||||
"port active profile edit begin", "port active profile edit end",
|
||||
};
|
||||
|
||||
/* the PC version's game data functions that the Xbox's have not, from
|
||||
@@ -365,6 +373,8 @@ static struct
|
||||
struct pc_menu_setting *settings;
|
||||
long setting_count;
|
||||
boolean loaded;
|
||||
/* built in a game's map: the in-game widgets, not the main menu */
|
||||
boolean in_game;
|
||||
char root[300];
|
||||
} menu_tags;
|
||||
|
||||
@@ -382,6 +392,10 @@ static struct
|
||||
long *strings_tags;
|
||||
long *text_tags;
|
||||
long *spinner_tags;
|
||||
/* which widgets, bitmaps and string lists are built */
|
||||
boolean *wanted_widgets;
|
||||
boolean *wanted_bitmaps;
|
||||
boolean *wanted_strings;
|
||||
boolean failed;
|
||||
} build;
|
||||
|
||||
@@ -492,6 +506,70 @@ static long widget_named(char const *name)
|
||||
return find(name, build.menus->widget_count, widget_name);
|
||||
}
|
||||
|
||||
static boolean in_game_widget(char const *name)
|
||||
{
|
||||
return !strncmp(name, IN_GAME_WIDGET_PREFIX, strlen(IN_GAME_WIDGET_PREFIX));
|
||||
}
|
||||
|
||||
/* one of ours by its name (not the map's, with a backslash) wanted */
|
||||
static void want_named(char const *name, long count, char const *(*name_of)(long), boolean *wanted)
|
||||
{
|
||||
long index = name && !strchr(name, '\\') ? find(name, count, name_of) : NONE;
|
||||
|
||||
if (index != NONE)
|
||||
wanted[index] = TRUE;
|
||||
}
|
||||
|
||||
static void want_widget(long index);
|
||||
|
||||
static void want_widget_named(char const *name)
|
||||
{
|
||||
long index = name && !strchr(name, '\\') ? widget_named(name) : NONE;
|
||||
|
||||
if (index != NONE)
|
||||
want_widget(index);
|
||||
}
|
||||
|
||||
/* a widget wanted, and what it names: its bitmaps and strings, its children,
|
||||
its description, the widgets its handlers open and its conditional ones */
|
||||
static void want_widget(long index)
|
||||
{
|
||||
struct halo_menus const *menus = build.menus;
|
||||
struct halo_menu_widget const *widget = &menus->widgets[index];
|
||||
long child, handler, conditional;
|
||||
|
||||
if (build.wanted_widgets[index])
|
||||
return;
|
||||
build.wanted_widgets[index] = TRUE;
|
||||
want_named(widget->bitmap, menus->bitmap_count, bitmap_name, build.wanted_bitmaps);
|
||||
want_named(widget->header_bitmap, menus->bitmap_count, bitmap_name, build.wanted_bitmaps);
|
||||
want_named(widget->footer_bitmap, menus->bitmap_count, bitmap_name, build.wanted_bitmaps);
|
||||
want_named(widget->string_list, menus->string_list_count, strings_name, build.wanted_strings);
|
||||
want_widget_named(widget->description);
|
||||
for (child = widget->first_child; child != HALO_MENU_NONE; child = menus->children[child].next)
|
||||
{
|
||||
if (menus->children[child].nested != HALO_MENU_NONE)
|
||||
want_widget(menus->children[child].nested);
|
||||
else
|
||||
want_widget_named(menus->children[child].widget);
|
||||
}
|
||||
for (handler = widget->first_handler; handler != HALO_MENU_NONE; handler = menus->handlers[handler].next)
|
||||
{
|
||||
struct halo_menu_handler const *on = &menus->handlers[handler];
|
||||
|
||||
want_widget_named(on->open);
|
||||
want_widget_named(on->replace);
|
||||
want_widget_named(on->focus);
|
||||
want_widget_named(on->widget);
|
||||
want_widget_named(on->otherwise);
|
||||
}
|
||||
for (conditional = widget->first_conditional; conditional != HALO_MENU_NONE;
|
||||
conditional = menus->conditionals[conditional].next)
|
||||
{
|
||||
want_widget_named(menus->conditionals[conditional].widget);
|
||||
}
|
||||
}
|
||||
|
||||
static long map_tag(long group_tag, char const *name, char const *file, long line)
|
||||
{
|
||||
long index = tag_loaded(group_tag, name);
|
||||
@@ -1255,8 +1333,11 @@ void menu_tags_loaded(
|
||||
struct halo_menus const *menus;
|
||||
struct cache_file_tag_instance *instances;
|
||||
long widget_count, own_lists = 0, total, index;
|
||||
long widgets_built = 0, strings_built = 0, bitmaps_built = 0;
|
||||
/* (any map but ui.map is a game's) */
|
||||
boolean in_game = strcmp(map_name, "ui") != 0;
|
||||
|
||||
if (strcmp(map_name, "ui") || strcmp(config_string("display.menus"), "pc"))
|
||||
if (strcmp(config_string("display.menus"), "pc"))
|
||||
return;
|
||||
menus = halo_menus_load();
|
||||
if (!menus)
|
||||
@@ -1269,33 +1350,64 @@ void menu_tags_loaded(
|
||||
build.spinner_tags = malloc((widget_count + 1) * sizeof(long));
|
||||
build.bitmap_tags = malloc((menus->bitmap_count + 1) * sizeof(long));
|
||||
build.strings_tags = malloc((menus->string_list_count + 1) * sizeof(long));
|
||||
build.wanted_widgets = malloc((widget_count + 1) * sizeof(boolean));
|
||||
build.wanted_bitmaps = malloc((menus->bitmap_count + 1) * sizeof(boolean));
|
||||
build.wanted_strings = malloc((menus->string_list_count + 1) * sizeof(boolean));
|
||||
/* ui.map: all but the in-game widgets; a game's map: those, and what
|
||||
they lead to */
|
||||
for (index = 0; index < widget_count; index++)
|
||||
build.wanted_widgets[index] = !in_game && !in_game_widget(menus->widgets[index].name);
|
||||
for (index = 0; index < menus->bitmap_count; index++)
|
||||
build.wanted_bitmaps[index] = !in_game;
|
||||
for (index = 0; index < menus->string_list_count; index++)
|
||||
build.wanted_strings[index] = !in_game;
|
||||
for (index = 0; in_game && index < widget_count; index++)
|
||||
{
|
||||
if (in_game_widget(menus->widgets[index].name))
|
||||
want_widget(index);
|
||||
}
|
||||
for (index = 0; index < widget_count; index++)
|
||||
{
|
||||
own_lists += (menus->widgets[index].text != NULL) + (menus->widgets[index].strings != NULL);
|
||||
if (build.wanted_widgets[index])
|
||||
{
|
||||
widgets_built++;
|
||||
own_lists += (menus->widgets[index].text != NULL) + (menus->widgets[index].strings != NULL);
|
||||
}
|
||||
/* (names are unique) */
|
||||
if (widget_named(menus->widgets[index].name) != index)
|
||||
problem(menus->widgets[index].file, menus->widgets[index].line, "two widgets are named",
|
||||
menus->widgets[index].name);
|
||||
}
|
||||
total = widget_count + own_lists + menus->string_list_count + menus->bitmap_count;
|
||||
for (index = 0; index < menus->string_list_count; index++)
|
||||
strings_built += build.wanted_strings[index] != FALSE;
|
||||
for (index = 0; index < menus->bitmap_count; index++)
|
||||
bitmaps_built += build.wanted_bitmaps[index] != FALSE;
|
||||
/* (a game's map with no in-game widgets: nothing to add) */
|
||||
if (!widgets_built && !build.failed)
|
||||
goto done;
|
||||
total = widgets_built + own_lists + strings_built + bitmaps_built;
|
||||
instances = build.failed ? NULL : instances_grow(total, &build.first_index, &build.first_salt);
|
||||
if (!instances)
|
||||
goto failed;
|
||||
/* each new tag's index */
|
||||
for (index = 0; index < widget_count; index++)
|
||||
{
|
||||
build.widget_tags[index] = next_tag();
|
||||
build.text_tags[index] = menus->widgets[index].text ? next_tag() : NONE;
|
||||
build.spinner_tags[index] = menus->widgets[index].strings ? next_tag() : NONE;
|
||||
boolean wanted = build.wanted_widgets[index];
|
||||
|
||||
build.widget_tags[index] = wanted ? next_tag() : NONE;
|
||||
build.text_tags[index] = wanted && menus->widgets[index].text ? next_tag() : NONE;
|
||||
build.spinner_tags[index] = wanted && menus->widgets[index].strings ? next_tag() : NONE;
|
||||
}
|
||||
for (index = 0; index < menus->string_list_count; index++)
|
||||
build.strings_tags[index] = next_tag();
|
||||
build.strings_tags[index] = build.wanted_strings[index] ? next_tag() : NONE;
|
||||
for (index = 0; index < menus->bitmap_count; index++)
|
||||
build.bitmap_tags[index] = next_tag();
|
||||
build.bitmap_tags[index] = build.wanted_bitmaps[index] ? next_tag() : NONE;
|
||||
/* the tags: the bitmaps and strings first, which the widgets name; the
|
||||
widgets' names in the table before any is built, which they find there */
|
||||
for (index = 0; index < menus->bitmap_count && !build.failed; index++)
|
||||
{
|
||||
if (!build.wanted_bitmaps[index])
|
||||
continue;
|
||||
instance_set(instances, BITMAP_GROUP_TAG, build.bitmap_tags[index], menus->bitmaps[index].name, "",
|
||||
bitmap_build(&menus->bitmaps[index], build.bitmap_tags[index]));
|
||||
}
|
||||
@@ -1303,6 +1415,8 @@ void menu_tags_loaded(
|
||||
{
|
||||
struct halo_menu_strings const *list = &menus->string_lists[index];
|
||||
|
||||
if (!build.wanted_strings[index])
|
||||
continue;
|
||||
instance_set(instances, UNICODE_STRING_LIST_TAG, build.strings_tags[index], list->name, "",
|
||||
string_list_build(menus->strings + list->first, list->count));
|
||||
}
|
||||
@@ -1310,6 +1424,8 @@ void menu_tags_loaded(
|
||||
{
|
||||
struct halo_menu_widget const *widget = &menus->widgets[index];
|
||||
|
||||
if (!build.wanted_widgets[index])
|
||||
continue;
|
||||
if (widget->text)
|
||||
{
|
||||
instance_set(instances, UNICODE_STRING_LIST_TAG, build.text_tags[index], widget->name, " text",
|
||||
@@ -1329,10 +1445,17 @@ void menu_tags_loaded(
|
||||
goto failed;
|
||||
cache_files_set_tag_instances(instances, build.first_index + total);
|
||||
for (index = 0; index < widget_count && !build.failed; index++)
|
||||
instances[DATUM_INDEX_TO_ABSOLUTE_INDEX(build.widget_tags[index])].base_address = widget_build(index);
|
||||
{
|
||||
if (build.wanted_widgets[index])
|
||||
instances[DATUM_INDEX_TO_ABSOLUTE_INDEX(build.widget_tags[index])].base_address = widget_build(index);
|
||||
}
|
||||
if (build.failed)
|
||||
goto failed;
|
||||
if (widget_named(menus->root) != NONE)
|
||||
if (in_game)
|
||||
{
|
||||
/* (no main menu here) */
|
||||
}
|
||||
else if (widget_named(menus->root) != NONE)
|
||||
{
|
||||
snprintf(menu_tags.root, sizeof(menu_tags.root), "%s%s", PC_MENU_TAG_PREFIX, menus->root);
|
||||
for (index = 0; menu_tags.root[index]; index++)
|
||||
@@ -1346,12 +1469,14 @@ void menu_tags_loaded(
|
||||
platform_log("menus: there is no widget named %s, the main menu", menus->root);
|
||||
}
|
||||
menu_tags.loaded = TRUE;
|
||||
menu_tags.in_game = in_game;
|
||||
platform_log("menus: %ld widgets, %ld string lists and %ld bitmaps added to the map's %ld tags",
|
||||
widget_count, own_lists + menus->string_list_count, menus->bitmap_count, build.first_index);
|
||||
widgets_built, own_lists + strings_built, bitmaps_built, build.first_index);
|
||||
goto done;
|
||||
|
||||
failed:
|
||||
platform_log("menus: not added; using the game's own menus");
|
||||
platform_log(in_game ? "menus: not added; no SETTINGS in the pause menu" :
|
||||
"menus: not added; using the game's own menus");
|
||||
menu_tags_release();
|
||||
|
||||
done:
|
||||
@@ -1360,6 +1485,9 @@ done:
|
||||
free(build.spinner_tags);
|
||||
free(build.strings_tags);
|
||||
free(build.bitmap_tags);
|
||||
free(build.wanted_widgets);
|
||||
free(build.wanted_bitmaps);
|
||||
free(build.wanted_strings);
|
||||
memset(&build, 0, sizeof(build));
|
||||
}
|
||||
|
||||
@@ -1377,7 +1505,7 @@ char const *pc_menus_root_name(
|
||||
settings' screens) */
|
||||
char const *open = config_string("debug.menu_open");
|
||||
|
||||
if (menu_tags.loaded && *open && build.menus == NULL)
|
||||
if (menu_tags.loaded && !menu_tags.in_game && *open && build.menus == NULL)
|
||||
{
|
||||
static char name[300];
|
||||
long index;
|
||||
@@ -1442,7 +1570,7 @@ char const *pc_menus_screen(
|
||||
};
|
||||
short index;
|
||||
|
||||
if (!menu_tags.loaded)
|
||||
if (!menu_tags.loaded || menu_tags.in_game)
|
||||
return name;
|
||||
for (index = 0; index < NUMBEROF(screens); index++)
|
||||
{
|
||||
|
||||
@@ -3694,6 +3694,16 @@ static void widget_instance_initialize(
|
||||
widget->render_regardless_of_controller_index =
|
||||
TEST_FLAG(definition->flags, _widget_render_regardless_of_controller_index_bit);
|
||||
widget->pause_game_time = TEST_FLAG(definition->flags, _widget_pause_game_time_bit);
|
||||
/* port: the menus' screens (port/assets/menus) pause the game, as its
|
||||
pause menu does, but not a network game, which its pause menu does not
|
||||
pause (their SETTINGS: ui_pause_menu_loaded) */
|
||||
if (widget->pause_game_time &&
|
||||
pc_menu_tag(tag_index) &&
|
||||
!we_are_at_the_main_menu &&
|
||||
network_game_is_active())
|
||||
{
|
||||
widget->pause_game_time = FALSE;
|
||||
}
|
||||
widget->creation_time = widget_globals.current_system_milliseconds;
|
||||
widget->milliseconds_to_auto_close = MAX(definition->milliseconds_to_auto_close, 0);
|
||||
widget->auto_close_fade_time = MAX(definition->auto_close_fade_time, 0);
|
||||
@@ -3831,6 +3841,144 @@ void ui_widget_port_go_back(
|
||||
widget_instance_go_back_to_previous(widget);
|
||||
}
|
||||
|
||||
/* ---------- SETTINGS in the pause menus (port)
|
||||
|
||||
The full screen pause menus (the campaign's, and a network game's with one
|
||||
player here) get SETTINGS after RESUME GAME: the menus' own row for it
|
||||
(port/assets/menus/ce/in_game.xml), which menu_tags.c builds in the game's
|
||||
maps when the menus are there. It opens its screen as any widget opens
|
||||
another, so B comes back here, to SETTINGS. The rows are found by their tags'
|
||||
names, not their places, and spaced to fit the box (ui_pause_menu_rows_fit):
|
||||
a row added after SETTINGS is spaced with the others. */
|
||||
|
||||
#define UI_PAUSE_MENU_SETTINGS_TAG "pc\\in_game\\settings_button"
|
||||
|
||||
enum
|
||||
{
|
||||
/* the body of the pause menus' box between the lines of its art
|
||||
(ui\shell\bitmaps\pausebox2_*), and how far inside it the rows stay */
|
||||
UI_PAUSE_BOX_BODY_TOP = 2,
|
||||
UI_PAUSE_BOX_BODY_BOTTOM = 130,
|
||||
UI_PAUSE_BOX_ROWS_MARGIN = 4
|
||||
};
|
||||
|
||||
/* whether the widget is drawn from a tag whose name ends with the leaf */
|
||||
static boolean ui_pause_menu_tag_is(
|
||||
struct widget_instance *widget,
|
||||
char const *leaf)
|
||||
{
|
||||
char const *name = tag_get_name(widget->definition_tag_index);
|
||||
char const *last = name ? strrchr(name, '\\') : NULL;
|
||||
|
||||
return last && !csstrcmp(last + 1, leaf);
|
||||
}
|
||||
|
||||
/* the rows spaced as the map spaces them, around the middle where it has
|
||||
them, if they fit in the box's body; else the widest spacing that fits */
|
||||
static void ui_pause_menu_rows_fit(
|
||||
struct widget_instance *list,
|
||||
struct widget_instance *box)
|
||||
{
|
||||
struct ui_widget_definition *definition = ui_widget_definition_get(list->definition_tag_index);
|
||||
struct ui_widget_child_reference *references =
|
||||
(struct ui_widget_child_reference *)definition->child_widgets.address;
|
||||
struct ui_widget_definition *row_definition;
|
||||
struct widget_instance *row;
|
||||
short row_count = 0;
|
||||
short row_height, pitch, middle_twice, body_top, body_bottom, span, top;
|
||||
|
||||
for (row = list->child; row; row = row->next)
|
||||
row_count++;
|
||||
if (definition->child_widgets.count < 2 || row_count < 2)
|
||||
return;
|
||||
row_definition = ui_widget_definition_get(list->child->definition_tag_index);
|
||||
row_height = row_definition->bounds.y1 - row_definition->bounds.y0;
|
||||
pitch = references[1].vertical_offset - references[0].vertical_offset;
|
||||
/* (on the screen, as the list's place and the box's are) */
|
||||
middle_twice = 2 * list->vertical_offset + references[0].vertical_offset +
|
||||
references[definition->child_widgets.count - 1].vertical_offset + row_height;
|
||||
body_top = box->vertical_offset + UI_PAUSE_BOX_BODY_TOP + UI_PAUSE_BOX_ROWS_MARGIN;
|
||||
body_bottom = box->vertical_offset + UI_PAUSE_BOX_BODY_BOTTOM - UI_PAUSE_BOX_ROWS_MARGIN;
|
||||
if ((row_count - 1) * pitch + row_height > body_bottom - body_top)
|
||||
pitch = (body_bottom - body_top - row_height) / (row_count - 1);
|
||||
span = (row_count - 1) * pitch + row_height;
|
||||
top = PIN((middle_twice - span) / 2, body_top, body_bottom - span);
|
||||
row_count = 0;
|
||||
for (row = list->child; row; row = row->next)
|
||||
row->vertical_offset = top - list->vertical_offset + row_count++ * pitch;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void ui_pause_menu_loaded(
|
||||
struct widget_instance *root)
|
||||
{
|
||||
static char const *const pause_menus[] =
|
||||
{
|
||||
"ui\\shell\\solo_game\\pause_game\\pause_game",
|
||||
"ui\\shell\\multiplayer_game\\pause_game\\1p_pause_game",
|
||||
};
|
||||
char const *name = tag_get_name(root->definition_tag_index);
|
||||
long settings_tag_index;
|
||||
struct widget_instance *box = NULL;
|
||||
struct widget_instance *list = NULL;
|
||||
struct widget_instance *resume = NULL;
|
||||
struct widget_instance *child;
|
||||
struct widget_instance *settings;
|
||||
short index;
|
||||
|
||||
for (index = 0; name && index < (short)NUMBEROF(pause_menus); index++)
|
||||
{
|
||||
if (!csstrcmp(name, pause_menus[index]))
|
||||
break;
|
||||
}
|
||||
/* (and with one player here: the campaign's falls back on its full
|
||||
screen pause menu for more, ui_check_for_pause_game) */
|
||||
if (!name || index == (short)NUMBEROF(pause_menus) || local_player_count() > 1)
|
||||
return;
|
||||
settings_tag_index = tag_loaded(UI_WIDGET_DEFINITION_TAG, UI_PAUSE_MENU_SETTINGS_TAG);
|
||||
if (settings_tag_index == NONE)
|
||||
return;
|
||||
for (child = root->child; child; child = child->next)
|
||||
{
|
||||
if (ui_pause_menu_tag_is(child, "pause_dialog_bkd"))
|
||||
{
|
||||
box = child;
|
||||
}
|
||||
else if (child->type == _ui_widget_type_column_list && !resume)
|
||||
{
|
||||
for (resume = child->child; resume; resume = resume->next)
|
||||
{
|
||||
if (ui_pause_menu_tag_is(resume, "resume_game_button"))
|
||||
break;
|
||||
}
|
||||
if (resume)
|
||||
list = child;
|
||||
}
|
||||
}
|
||||
if (!box || !list)
|
||||
return;
|
||||
settings = ui_widget_load_by_name_or_tag(
|
||||
NULL,
|
||||
settings_tag_index,
|
||||
list,
|
||||
list->local_player_index,
|
||||
NONE,
|
||||
NONE,
|
||||
NONE);
|
||||
if (!settings)
|
||||
return;
|
||||
settings->horizontal_offset = resume->horizontal_offset;
|
||||
settings->previous = resume;
|
||||
settings->next = resume->next;
|
||||
if (resume->next)
|
||||
resume->next->previous = settings;
|
||||
resume->next = settings;
|
||||
ui_pause_menu_rows_fit(list, box);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
struct widget_instance *ui_widget_load_by_name_or_tag(
|
||||
char const *name,
|
||||
long tag_index,
|
||||
@@ -3925,6 +4073,10 @@ struct widget_instance *ui_widget_load_by_name_or_tag(
|
||||
tag_index,
|
||||
local_player_index,
|
||||
widget_stack);
|
||||
/* port: SETTINGS in the pause menus, unless a created handler
|
||||
closed the widget */
|
||||
if (!parent && widget_globals.active_widgets[widget_stack] == widget)
|
||||
ui_pause_menu_loaded(widget);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -7180,6 +7332,11 @@ static boolean ui_check_for_pause_game(
|
||||
else
|
||||
{
|
||||
ui_widget_delete(widget_globals.active_widgets[controller_index]);
|
||||
/* port: and the screens B would have gone back to (the
|
||||
pause menu, from its SETTINGS), as the campaign's START
|
||||
closes them all (ui_widgets_close_all) */
|
||||
if (widget_globals.widget_stack[controller_index])
|
||||
dispose_widget_stack(&widget_globals.widget_stack[controller_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -567,7 +567,8 @@ def main() -> None:
|
||||
lines += widget_xml(tag, widgets[tag], tags, functions, inputs, fonts, {})
|
||||
lines.append("</menus>")
|
||||
(CE / f"{folder.replace('/', '.')}.xml").write_text("\n".join(lines) + "\n")
|
||||
for name, lines in [*port_settings.settings_files().items(), *port_settings.multiplayer_files().items()]:
|
||||
for name, lines in [*port_settings.settings_files().items(), *port_settings.multiplayer_files().items(),
|
||||
*port_settings.in_game_files().items()]:
|
||||
(CE / name).write_text("\n".join(lines))
|
||||
# their strings
|
||||
lines = ['<?xml version="1.0" encoding="UTF-8"?>',
|
||||
|
||||
@@ -745,3 +745,92 @@ def multiplayer_files() -> dict:
|
||||
|
||||
|
||||
REPLACED_FOLDERS = [f"{MT}/server_settings"]
|
||||
|
||||
|
||||
# ---------- in a game: SETTINGS in the pause menus
|
||||
|
||||
# The game's pause menus are the maps' own, not these files', and ui.map has
|
||||
# none of their art, so these widgets (in_game/...) are built in the game's
|
||||
# maps instead, with the screens they open (port/linux/game/menu_tags.c);
|
||||
# ui_widget.c adds SETTINGS to the pause menus after RESUME GAME. Its screen
|
||||
# is the multiplayer pause menu's box and key, from the maps' own tags (every
|
||||
# campaign and multiplayer map has them), with rows as the pause menus have
|
||||
# them, named and ordered as Edit Profile has them. Each opens Edit Profile's
|
||||
# screen over the pause menu's dim (behind them, the main menu has its dark
|
||||
# backdrop, a game the game), and the screen saves config.toml's settings as
|
||||
# it does there; GAMEPADS, on OK, only the controller settings of the
|
||||
# player's active profile ("port active profile edit begin" and "end",
|
||||
# port/linux/game/menu_functions.c).
|
||||
# The desktop builds' only, for now.
|
||||
IG = "in_game"
|
||||
PAUSE_DIM = "ui\\shell\\bitmaps\\semi_transparent_grey"
|
||||
PAUSE_ROW = [("type", "text"), ("width", 202), ("height", 27), ("bitmap", "ui\\shell\\bitmaps\\menu_bkds"),
|
||||
("font", "ui\\large_ui"), ("color", "#FF2896FF"), ("align", "center"), ("text_y", 3)]
|
||||
DESKTOP = [("platform", "desktop")]
|
||||
# Edit Profile's setups: their rows' names (its profile_edit_options), and
|
||||
# the screens they open
|
||||
IN_GAME_SETUPS = [("controls_setup", 1, f"{PE}/controls_setup/controls_settings_screen"),
|
||||
("gamepads", 2, f"{PE}/gamepad_setup/gamepad_setup_screen"),
|
||||
("mouse_setup", 3, f"{PE}/mouse_settings/mouse_settings_screen"),
|
||||
("audio_setup", 4, f"{PE}/audio_settings/audio_settings_screen"),
|
||||
("video_setup", 5, f"{PE}/video_settings/video_settings_screen")]
|
||||
# the box where the multiplayer pause menu has it; the body of its art
|
||||
# (pausebox2_*) between its rows 2 and 130, the key's band below; the rows
|
||||
# that far inside it (ui_widget.c's UI_PAUSE_BOX_ROWS_MARGIN), spaced as the
|
||||
# multiplayer pause menu's at most
|
||||
BOX_Y, BOX_BODY_TOP, BOX_BODY_BOTTOM, ROWS_MARGIN, ROW_HEIGHT, ROW_SPACING = 164, 2, 130, 4, 27, 35
|
||||
|
||||
|
||||
def _pause_row(name: str, label: list, handlers: list) -> list:
|
||||
return _widget(name, label + PAUSE_ROW + DESKTOP, handlers)
|
||||
|
||||
|
||||
def _opens(screen: str, run: str = None) -> list:
|
||||
action = (f'run="{run}" ' if run else "") + f'open="{screen}"'
|
||||
return [f'<on event="a" {action}/>', f'<on event="start" {action}/>']
|
||||
|
||||
|
||||
def _rows_place(count: int) -> tuple:
|
||||
"""the rows' top and spacing: in the middle of the box's body"""
|
||||
body = BOX_BODY_BOTTOM - BOX_BODY_TOP
|
||||
spacing = min(ROW_SPACING, (body - 2 * ROWS_MARGIN - ROW_HEIGHT) // (count - 1))
|
||||
span = (count - 1) * spacing + ROW_HEIGHT
|
||||
return BOX_Y + BOX_BODY_TOP + (body - span) // 2, spacing
|
||||
|
||||
|
||||
def in_game_files() -> dict:
|
||||
"""SETTINGS in the pause menus, its screen, and the setups' screens"""
|
||||
top, spacing = _rows_place(len(IN_GAME_SETUPS))
|
||||
lines = _pause_row(f"{IG}/settings_button", [("text", "SETTINGS")], _opens(f"{IG}/settings_screen"))
|
||||
lines += _widget(f"{IG}/settings_screen",
|
||||
[("width", 640), ("height", 480), ("flags", "pass_unhandled_to_focused_child pause_game"),
|
||||
("bitmap", PAUSE_DIM)] + DESKTOP,
|
||||
['<on event="b" back="true"/>', '<on event="back" back="true"/>',
|
||||
f'<child widget="{IG}/settings_box" x="210" y="{BOX_Y}"/>',
|
||||
f'<child widget="{IG}/settings_list" x="218" y="{top}"/>',
|
||||
'<child widget="ui\\shell\\main_menu\\button_key_sm" x="235" y="298"/>'])
|
||||
lines += _widget(f"{IG}/settings_box", [("width", 226), ("height", 154)] + DESKTOP,
|
||||
['<child widget="ui\\shell\\solo_game\\pause_game\\pausebox2_left" x="-4"/>',
|
||||
'<child widget="ui\\shell\\solo_game\\pause_game\\pausebox2_left_center" x="12"/>',
|
||||
'<child widget="ui\\shell\\solo_game\\pause_game\\pausebox2_right" x="206"/>'])
|
||||
lines += _widget(f"{IG}/settings_list",
|
||||
[("type", "column_list"), ("width", 202),
|
||||
("height", (len(IN_GAME_SETUPS) - 1) * spacing + ROW_HEIGHT),
|
||||
("flags", "pass_unhandled_to_focused_child up_down_tabs_items")] + DESKTOP,
|
||||
[f'<child widget="{IG}/{key}_button" y="{index * spacing}"/>'
|
||||
for index, (key, _, _) in enumerate(IN_GAME_SETUPS)])
|
||||
for key, string_index, screen in IN_GAME_SETUPS:
|
||||
# (GAMEPADS sets the profile's controller settings: the player's
|
||||
# active profile, being edited while its screen is up)
|
||||
gamepads = key == "gamepads"
|
||||
lines += _pause_row(f"{IG}/{key}_button",
|
||||
[("string_list", f"{PE}/profile_edit_options"), ("string_index", string_index)],
|
||||
_opens(f"{IG}/{key}_screen", "port active profile edit begin" if gamepads else None))
|
||||
lines += _widget(f"{IG}/{key}_screen",
|
||||
[("width", 640), ("height", 480), ("flags", "pass_unhandled_to_focused_child"),
|
||||
("bitmap", PAUSE_DIM)] + DESKTOP,
|
||||
(['<on event="deleted" run="port active profile edit end"/>'] if gamepads else []) +
|
||||
[f'<child widget="{screen}"/>'])
|
||||
return {"in_game.xml": ['<?xml version="1.0" encoding="UTF-8"?>',
|
||||
"<!-- The port's SETTINGS in the game's pause menus (tools/port_settings.py) -->",
|
||||
"<menus>", *lines, "</menus>", ""]}
|
||||
Reference in new issue
Block a user