Split-screen co-op

This commit is contained in:
MrBruh committed 2026-10-04 22:27:13 +13:00
1 parent d578f88bd0
commit 193cbf59c7
16 files changed
+377 -31

No files matched your search

+1 -1
View File
@@ -51,7 +51,7 @@ until it is redrawn.
| `ce/shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__6.png` | 512x256 | `shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__6.png` |
| `ce/shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__8.png` | 512x256 | `shell/main_menu/settings_select/player_setup/player_profile_edit/profile_options__8.png` |
13 of 154 frames are placeholders.
13 of 155 frames are placeholders.
The PC version's `ui\gamespy` and `ui\ticker` fonts are drawn with `ui\small_ui`, which
the Xbox's map has.
+1 -1
View File
@@ -10,7 +10,7 @@ do (save a setting, join a game) does not happen; the screens open and close as
| Function | Uses | |
| --- | --- | --- |
| `common button bar update` | 24 | PC function |
| `common button bar update` | 25 | PC function |
| `direct ip connect init` | 1 | PC function |
| `direct ip connect update` | 1 | PC function |
| `direct ip edit field` | 4 | PC function |
+3
View File
@@ -309,6 +309,9 @@
<bitmap name="main_menu/solo_level_select/header_load_level">
<frame png="ce/shell/main_menu/solo_level_select/header_load_level__0.png" width="512" height="64"/>
</bitmap>
<bitmap name="main_menu/multiplayer_type_select/coop/header_player_2">
<frame png="ce/port/main_menu/multiplayer_type_select/coop/header_player_2.png" width="512" height="64"/>
</bitmap>
<bitmap name="main_menu/multiplayer_type_select/join_game/header_direct_link">
<frame png="ce/port/main_menu/multiplayer_type_select/join_game/header_direct_link.png" width="512" height="64"/>
</bitmap>
@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- The port's multiplayer screens, in the PC version's style (tools/port_settings.py) -->
<menus>
<widget name="main_menu/multiplayer_type_select/multiplayer_type_coop_item" type="text" left="51" width="232" height="32" bitmap="bitmaps/list_item_bkd" string_list="main_menu/multiplayer_type_select/multiplayer_options" string_index="7" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="5">
<on event="a" run="port coop begin" open="main_menu/multiplayer_type_select/coop/player_2_profile_screen" branch="true"/>
<on event="start" run="port coop begin" open="main_menu/multiplayer_type_select/coop/player_2_profile_screen" branch="true"/>
<on event="left_mouse" run="mouse emit accept event"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/player_2_profile_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/gradient">
<child widget="main_menu/new_select/sel_list_desc_bkd"/>
<child widget="main_menu/multiplayer_type_select/coop/player_2_profile_list"/>
<child widget="main_menu/multiplayer_type_select/coop/header_player_2"/>
<child widget="main_menu/multiplayer_type_select/coop/player_2_help" x="20" y="416"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/header_player_2" controller="1" left="35" top="11" width="363" height="59" bitmap="main_menu/multiplayer_type_select/coop/header_player_2">
</widget>
<widget name="main_menu/multiplayer_type_select/coop/player_2_profile_list" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/profile_manager/player_profile_extended_desc">
<data input="3wide player profile list update"/>
<on event="created" run="port coop player 2 list initialize"/>
<on event="deleted" run="player profile list dispose"/>
<on event="custom_activation" run="port coop player 2" open="main_menu/solo_level_select/solo_level_select_screen" branch="true"/>
<child widget="main_menu/multiplayer_type_select/coop/list_item_0" x="20" y="73"/>
<child widget="main_menu/multiplayer_type_select/coop/list_item_1" x="20" y="103"/>
<child widget="main_menu/multiplayer_type_select/coop/list_item_2" x="20" y="133"/>
<child widget="main_menu/multiplayer_type_select/coop/list_item_3" x="20" y="163"/>
<child widget="main_menu/multiplayer_type_select/coop/list_item_4" x="20" y="193"/>
<child widget="main_menu/multiplayer_type_select/coop/list_item_5" x="20" y="223"/>
<child widget="main_menu/multiplayer_type_select/coop/list_item_6" x="20" y="253"/>
<child widget="main_menu/multiplayer_type_select/coop/list_item_7" x="20" y="283"/>
<child widget="main_menu/multiplayer_type_select/coop/list_item_8" x="20" y="313"/>
<child widget="main_menu/multiplayer_type_select/coop/list_item_9" x="20" y="343"/>
<child widget="main_menu/multiplayer_type_select/coop/list_item_10" x="20" y="373"/>
<child widget="main_menu/multiplayer_type_select/coop/player_2_button_bar" y="414"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/list_item_0" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd_top" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
<on event="a" run="single prev cl item activated"/>
<on event="start" run="single prev cl item activated"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/new_select/list_item_text" x="25"/>
<child widget="main_menu/new_select/list_item_arrows"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/list_item_1" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
<on event="a" run="single prev cl item activated"/>
<on event="start" run="single prev cl item activated"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/new_select/list_item_text" x="25"/>
<child widget="main_menu/new_select/list_item_arrows"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/list_item_2" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
<on event="a" run="single prev cl item activated"/>
<on event="start" run="single prev cl item activated"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/new_select/list_item_text" x="25"/>
<child widget="main_menu/new_select/list_item_arrows"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/list_item_3" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
<on event="a" run="single prev cl item activated"/>
<on event="start" run="single prev cl item activated"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/new_select/list_item_text" x="25"/>
<child widget="main_menu/new_select/list_item_arrows"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/list_item_4" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
<on event="a" run="single prev cl item activated"/>
<on event="start" run="single prev cl item activated"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/new_select/list_item_text" x="25"/>
<child widget="main_menu/new_select/list_item_arrows"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/list_item_5" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
<on event="a" run="single prev cl item activated"/>
<on event="start" run="single prev cl item activated"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/new_select/list_item_text" x="25"/>
<child widget="main_menu/new_select/list_item_arrows"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/list_item_6" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
<on event="a" run="single prev cl item activated"/>
<on event="start" run="single prev cl item activated"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/new_select/list_item_text" x="25"/>
<child widget="main_menu/new_select/list_item_arrows"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/list_item_7" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
<on event="a" run="single prev cl item activated"/>
<on event="start" run="single prev cl item activated"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/new_select/list_item_text" x="25"/>
<child widget="main_menu/new_select/list_item_arrows"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/list_item_8" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
<on event="a" run="single prev cl item activated"/>
<on event="start" run="single prev cl item activated"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/new_select/list_item_text" x="25"/>
<child widget="main_menu/new_select/list_item_arrows"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/list_item_9" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
<on event="a" run="single prev cl item activated"/>
<on event="start" run="single prev cl item activated"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/new_select/list_item_text" x="25"/>
<child widget="main_menu/new_select/list_item_arrows"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/list_item_10" width="390" height="28" bitmap="bitmaps/sel_list_item_bkd" font="ui\large_ui" color="#FF2896FF" align="center" text_y="3">
<on event="a" run="single prev cl item activated"/>
<on event="start" run="single prev cl item activated"/>
<on event="left_mouse" run="mouse emit accept event"/>
<child widget="main_menu/new_select/list_item_text" x="25"/>
<child widget="main_menu/new_select/list_item_arrows"/>
</widget>
<widget name="main_menu/multiplayer_type_select/coop/player_2_help" type="text" width="350" height="24" text="Player 2: choose with your controller." font="ui\small_ui" color="#FF2896FF" text_y="5" text_flags="no_focus_test">
</widget>
<widget name="main_menu/multiplayer_type_select/coop/player_2_button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
<data input="common button bar update"/>
<child widget="main_menu/profile_manager/profile_manager_button_ok" x="380" y="1"/>
<child widget="common_button_back" x="510" y="1"/>
</widget>
</menus>
@@ -59,13 +59,14 @@
<child widget="main_menu/multiplayer_type_select/multiplayer_type_create_item" x="-32" y="210"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_create_internet_item" y="243"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_create_lan_item" y="276"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_gametypes_item" y="342"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_coop_item" y="309"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_gametypes_item" y="375"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_button_bar" y="414"/>
</widget>
<widget name="main_menu/multiplayer_type_select/multiplayer_type_select_screen" width="640" height="480" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/gradient">
<on event="created" run="mp type menu initialize"/>
<child widget="main_menu/multiplayer_type_select/header_multiplayer"/>
<child widget="main_menu/multiplayer_type_select/multiplayer_type_select_list"/>
<child widget="main_menu/blueline" x="64" y="325"/>
<child widget="main_menu/blueline" x="64" y="358"/>
</widget>
</menus>
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

+2
View File
@@ -374,6 +374,7 @@
<string text="Join a game by its invite link,\nor one a Discord invite\nreached."/>
<string text="Host a game on the Internet:\nplayers join by its invite\nlink or Discord."/>
<string text="Host a game on your LAN\nonly."/>
<string text="Play the campaign with a\nfriend in split screen:\nplayer 2 on a gamepad."/>
<string text="Set all the attributes for your \nmultiplayer gametypes and\nkeep them for future use."/>
</strings>
<strings name="main_menu/multiplayer_type_select/multiplayer_options">
@@ -384,6 +385,7 @@
<string text="DIRECT LINK"/>
<string text="EDIT GAMETYPES"/>
<string text="SERVER BROWSER"/>
<string text="CO-OP CAMPAIGN"/>
</strings>
<strings name="main_menu/multiplayer_type_select/server_settings/cap_server_settings_options">
<string text="Enter a name for your server."/>
+2
View File
@@ -8,6 +8,7 @@
"ce/main_menu.gametype_select.xml",
"ce/main_menu.multiplayer_type_select.connected.4way_profile_select.xml",
"ce/main_menu.multiplayer_type_select.connected.xml",
"ce/main_menu.multiplayer_type_select.coop.xml",
"ce/main_menu.multiplayer_type_select.direct_ip.xml",
"ce/main_menu.multiplayer_type_select.join_game.filters.xml",
"ce/main_menu.multiplayer_type_select.join_game.port.xml",
@@ -50,6 +51,7 @@
"ce/shell.xml",
"ce/strings.xml",
"ce/port/blank__0.png",
"ce/port/main_menu/multiplayer_type_select/coop/header_player_2.png",
"ce/port/main_menu/multiplayer_type_select/join_game/header_direct_link.png",
"ce/port/main_menu/multiplayer_type_select/join_game/header_server_browser.png",
"ce/port/main_menu/multiplayer_type_select/lobby/header_lobby.png",
@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="64" viewBox="0 0 512 64">
<defs>
<filter id="soft" filterUnits="userSpaceOnUse" x="-60" y="-60" width="632" height="184">
<feGaussianBlur stdDeviation="14.23 13.06"/>
</filter>
<mask id="cut" maskUnits="userSpaceOnUse" x="0" y="0" width="512" height="64">
<rect width="512" height="59" fill="#fff"/>
</mask>
</defs>
<g mask="url(#cut)">
<rect x="29.08" y="28.05" width="323.93" height="40.95" fill="#021931" fill-opacity="0.893" filter="url(#soft)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 563 B

+12 -3
View File
@@ -95,8 +95,10 @@ The keyboard and the mouse are a control scheme of their own for the player
of controller 1: each action has up to two keys or mouse buttons, which
Settings > Controls Setup (or `[controls]` in `config.toml`) changes. The
game adds the input of the first gamepad to controller 1. The other
gamepads operate controllers 2 to 4. The profile's button layout (Settings >
Gamepads) is the gamepads' only.
gamepads operate controllers 2 to 4. In co-op with only one gamepad, that
gamepad is controller 2 (player 2) and the keyboard and mouse stay
controller 1. The profile's button layout (Settings > Gamepads) is the
gamepads' only.
| Action | Keys and buttons (default) |
| --- | --- |
@@ -157,6 +159,13 @@ change. The screens open, close and move between each other as the PC
version's. `display.menus = "xbox"` gives the Xbox's menus, which start every
kind of game.
Multiplayer > CO-OP CAMPAIGN is the Xbox's cooperative play, which the PC
version does not have: two players on this computer play the campaign in
split screen. Player 1 is the player who chose it, on the current profile.
Player 2 then chooses a profile with their own controller (a gamepad), and
New Game's levels are those either profile has reached. A co-op game does
not continue a saved game of one player.
In a multiplayer game, the pause menu (escape) has SETTINGS, which opens
the profile's settings while the game goes on, and for the host END GAME.
END GAME ends the game as its time limit does: the players stay, and after
@@ -671,7 +680,7 @@ Other changes:
| `networking/`, `game/`, `interface/`, `bungie_net/network/` and the pools of objects, effects and sounds | The system link limits and the memory for them. |
| `game/`, `objects/`, `units/`, `networking/` | The distributed netcode. Refer to `NETCODE.md`. |
| `cache/cache_files.c` | When a map's tags load and unload, the port finds the bitmaps that the high-res HUD replaces (`game/hud_hires_tags.c`), and adds the tags of the menus to the menus' map (`game/menu_tags.c`). |
| `interface/ui_widget.c`, `interface/ui_widget_event_handler_functions.c`, `interface/ui_widget_game_data_input_functions.c` | The main menu is the PC version's from `port/assets/menus` (`display.menus`); the menus' widgets can call the port's functions (`game/menu_functions.c`) and send the PC version's custom activation event; the widgets' memory is 256 KB, not 16 KB. |
| `interface/ui_widget.c`, `interface/ui_widget_event_handler_functions.c`, `interface/ui_widget_game_data_input_functions.c` | The main menu is the PC version's from `port/assets/menus` (`display.menus`); the menus' widgets can call the port's functions (`game/menu_functions.c`) and send the PC version's custom activation event; the widgets' memory is 256 KB, not 16 KB; the main menu and Multiplayer clear co-op's controllers, so that a gamepad going back to controller 1 is not a controller unplugged. |
| `input/input_abstraction.c`, `game/player_control.c`, `game/players.c`, `game/player_queues_new.c`, `units/units.h` | The keyboard and mouse's actions (`src/xinput_sdl.c`, `include/halo_keyboard.h`) join controller 1's game controls; their reload key reloads on its own, and their action key only acts (a control flag of the port's, sent with the player's action, stops the reload the controller's X falls back to). |
| `sound/sound_manager.c`, `interface/hud.c`, `game/game_engine.c` | The music's and the other sounds' volumes; the HUD's and the scoreboard's settings are read again when Settings changes them. |
| `interface/hud.c` | In multiplayer, players' names are drawn above their heads (`display.player_names`, `display.player_name_scale`). |
+121 -14
View File
@@ -51,6 +51,11 @@ the one last used, else the first) and the game's saved game in it:
The Xbox's functions of those names take the Xbox's widgets (a spinner of
levels, the difficulty list itself), so ours run instead (menu_tags.c).
Co-op (the Xbox's Cooperative Play, Multiplayer's CO-OP CAMPAIGN): "port coop
begin" makes two players, player 1 on its profile; "port coop player 2"
gives player 2 the profile chosen and the controller that chose it; then the
campaign's New Game and difficulty start the game for both.
The rest are the PC version's, which its menus (port/assets/menus/ce)
name and the Xbox's has not: they do nothing yet, and succeed, so that what
their handlers open opens.
@@ -660,7 +665,8 @@ static void rows_update(struct widget_instance *list, short count, void (*row_te
}
/* player 1's profile, read again (the active one, else the one last used,
else the first), on the controller; FALSE if there is none */
else the first), on the controller (in co-op, on the one that chose co-op:
coop_begin); FALSE if there is none */
static boolean campaign_profile(short controller, struct player_profile *profile)
{
long profile_index = player_ui_get_active_player_profile_index(0);
@@ -679,39 +685,52 @@ static boolean campaign_profile(short controller, struct player_profile *profile
}
}
player_ui_set_active_player_profile(0, profile_index, profile);
player_ui_set_single_player_local_player_controller(0, controller);
if (player_spawn_count < 2)
player_ui_set_single_player_local_player_controller(0, controller);
return TRUE;
}
/* the levels the profile has reached (as the Xbox's list has them: those
it has played, the one after the last it finished, the first) and finished,
and its saved game's */
and its saved game's; in co-op, those either player's has reached and
finished, and no saved game (a game of one player's does not go on with
two: game_state.c's game_state_header_valid) */
static void campaign_levels_read(struct player_profile const *profile)
{
char const *map_name;
short highest_level, highest_difficulty, difficulty, level;
struct player_profile player2;
short highest_level, highest_difficulty, difficulty, level, player;
player_profile_get_highest_completed_solo_level((struct player_profile *)profile, &highest_level,
&highest_difficulty);
for (level = 0; level < NUMBER_OF_SINGLE_PLAYER_LEVELS; level++)
memset(&campaign.levels[level], 0, sizeof(campaign.levels[level]));
if (player_spawn_count >= 2)
player_ui_get_active_player_profile(1, &player2);
for (player = 0; player < (player_spawn_count >= 2 ? 2 : 1); player++)
{
byte flags = profile->single_player_map_flags[level];
short marker;
struct player_profile const *reader = player == 0 ? profile : &player2;
campaign.levels[level].available = flags || level == highest_level + 1 || level == 0;
for (marker = 0; marker < 3; marker++)
campaign.levels[level].finished[marker] = (flags >> (marker + 1)) & 1;
player_profile_get_highest_completed_solo_level((struct player_profile *)reader, &highest_level,
&highest_difficulty);
for (level = 0; level < NUMBER_OF_SINGLE_PLAYER_LEVELS; level++)
{
byte flags = reader->single_player_map_flags[level];
short marker;
campaign.levels[level].available |= flags || level == highest_level + 1 || level == 0;
for (marker = 0; marker < 3; marker++)
campaign.levels[level].finished[marker] |= (flags >> (marker + 1)) & 1;
}
}
if (!ui_widget_port_saved_game(&map_name, &campaign.saved_level, &difficulty))
if (player_spawn_count >= 2 || !ui_widget_port_saved_game(&map_name, &campaign.saved_level, &difficulty))
campaign.saved_level = NONE;
(void)map_name;
}
/* plays the map, at the difficulty (a saved game in it goes on: main.c's
main_new_map, if its difficulty is this one) */
static void campaign_start(char const *map_name, short difficulty, short controller)
{
player_ui_set_single_player_local_player_controller(0, controller);
if (player_spawn_count < 2)
player_ui_set_single_player_local_player_controller(0, controller);
main_set_map_name(map_name);
main_set_difficulty(difficulty);
game_connection_set(0);
@@ -1397,6 +1416,82 @@ static boolean profile_delete(void)
return TRUE;
}
/* ---------- Co-op: the campaign for two players on this machine, in split
screen (the Xbox's Cooperative Play, which the PC version has not):
Multiplayer's CO-OP CAMPAIGN ("port coop begin"), player 2's profile, chosen
with player 2's controller ("port coop player 2"), then New Game's levels
(those either has reached) and difficulty. The main menu and Multiplayer
go back to one player (main_menu_initialize, multiplayer_type_menu_initialize) */
/* (xinput_sdl.c) whether two players play on this machine: then one gamepad
is player 2's, not the keyboard's player 1's */
unsigned char pc_menu_coop_players(void)
{
return player_spawn_count >= 2;
}
/* "port coop begin": two players, player 1 on its profile (campaign_profile)
and the controller that chose co-op */
static boolean coop_begin(short controller)
{
struct player_profile profile;
player_spawn_count = 1;
player_ui_reset_single_player_local_player_controllers();
if (!campaign_profile(controller, &profile))
return campaign_fail();
player_spawn_count = 2;
return TRUE;
}
/* "port coop player 2 list initialize": on player 2's profile, else the first
that is not player 1's */
static boolean coop_player2_list_initialize(struct widget_instance *list)
{
long player1 = player_ui_get_active_player_profile_index(0);
long player2 = player_ui_get_active_player_profile_index(1);
short index;
profile_list_read(TRUE);
profile_list.chosen = 0;
for (index = profile_list.count - 1; index >= 0; index--)
{
if (profile_list.indices[index] != player1)
profile_list.chosen = index;
}
for (index = 0; index < profile_list.count && player2 != NONE; index++)
{
if (profile_list.indices[index] == player2)
profile_list.chosen = index;
}
profile_list.first = (short)PIN(profile_list.chosen - PROFILE_ROWS / 2, 0,
MAX(0, profile_list.count + 1 - PROFILE_ROWS));
focus_row(list, (short)(profile_list.chosen - profile_list.first));
return TRUE;
}
/* "port coop player 2": the profile chosen made player 2's, on the controller
that chose it, which must not be player 1's */
static boolean coop_player2_choose(short controller)
{
struct player_profile profile;
if (player_spawn_count < 2 || profile_list.chosen >= profile_list.count ||
!player_profile_get(profile_list.indices[profile_list.chosen], &profile))
{
return campaign_fail();
}
if (controller == player_ui_get_single_player_local_player_controller(0))
{
display_error_text_deferred(L"Player 2 chooses their\r\nprofile with their own\r\ncontroller.",
NONE);
return campaign_fail();
}
player_ui_set_single_player_local_player_controller(1, controller);
player_ui_set_active_player_profile(1, profile_list.indices[profile_list.chosen], &profile);
return TRUE;
}
/* ---------- Controls Setup: the keyboard and mouse's controls
(port/linux/include/halo_keyboard.h), shown a group at a time, two
bindings each */
@@ -4022,6 +4117,18 @@ boolean pc_menu_event_function_invoke(
{
return profile_choose(controller);
}
else if (!strcmp(name, "port coop begin"))
{
return coop_begin(controller);
}
else if (!strcmp(name, "port coop player 2 list initialize"))
{
return coop_player2_list_initialize(widget);
}
else if (!strcmp(name, "port coop player 2"))
{
return coop_player2_choose(controller);
}
else if (!strcmp(name, "request del player profile"))
{
return profile_delete_request();
+1
View File
@@ -341,6 +341,7 @@ static char const *const port_function_names[] =
"port lobby preview join",
"port setup edit",
"port pause end game",
"port coop begin", "port coop player 2 list initialize", "port coop player 2",
/* (the gametype editor's: the Xbox's walk their rows by place, which the
PC version's screens changed) */
"mp profile begin editing", "mp profile save changes", "request del playlist profile", "final del playlist profile",
+24 -8
View File
@@ -54,6 +54,9 @@ drive the controller.
/* main/console.c */
extern unsigned char console_is_active(void);
/* port/linux/game/menu_functions.c: two players on this machine (the
campaign's co-op) */
extern unsigned char pc_menu_coop_players(void);
/* ---------- device tables */
@@ -634,6 +637,16 @@ static int sdl_gamepads(SDL_Gamepad *gamepads[PORT_COUNT])
return found;
}
/* the gamepad of a port: the first shares port 0 with the keyboard, but for
two players with one gamepad (co-op), port 1 has it (the keyboard's player
is 1, the gamepad's 2) */
static SDL_Gamepad *port_gamepad(SDL_Gamepad *gamepads[PORT_COUNT], int count, int port)
{
if (count == 1 && pc_menu_coop_players())
return port == 1 ? gamepads[0] : NULL;
return port < count ? gamepads[port] : NULL;
}
static SHORT stick(Sint16 value, BOOL flip)
{
int result = flip ? -(int)value - 1 : value;
@@ -717,9 +730,12 @@ static DWORD connected_gamepads(void)
DWORD mask = XDEVICE_PORT0_MASK;
int port;
/* the first pad shares port 0 with the keyboard */
for (port = 1; port < count; port++)
mask |= 1UL << port;
/* the first pad shares port 0 with the keyboard (port_gamepad) */
for (port = 1; port < PORT_COUNT; port++)
{
if (port_gamepad(gamepads, count, port))
mask |= 1UL << port;
}
return mask;
}
@@ -813,7 +829,7 @@ DWORD WINAPI XInputGetState(HANDLE device, PXINPUT_STATE state)
else
keyboard_controls(&input, &state->Gamepad);
}
if (count > 0)
if (port_gamepad(gamepads, count, 0))
sdl_gamepad_state(gamepads[0], &state->Gamepad);
test_input_gamepad(&state->Gamepad);
if (abs(state->Gamepad.sThumbRX) > STICK_AIMING_DEFLECTION ||
@@ -824,9 +840,9 @@ DWORD WINAPI XInputGetState(HANDLE device, PXINPUT_STATE state)
pthread_mutex_unlock(&mouse_lock);
}
}
else if (port < count)
else if (port_gamepad(gamepads, count, port))
{
sdl_gamepad_state(gamepads[port], &state->Gamepad);
sdl_gamepad_state(port_gamepad(gamepads, count, port), &state->Gamepad);
}
if (memcmp(&state->Gamepad, &controllers[port].previous, sizeof(state->Gamepad)))
@@ -850,11 +866,11 @@ DWORD WINAPI XInputSetState(HANDLE device, PXINPUT_FEEDBACK feedback)
if (port < 0)
return ERROR_DEVICE_NOT_CONNECTED;
count = sdl_gamepads(gamepads);
if (port < count)
if (port_gamepad(gamepads, count, port))
{
/* the game refreshes the motors every frame; rumble a little longer
than that so they do not stutter */
SDL_RumbleGamepad(gamepads[port], feedback->Rumble.wLeftMotorSpeed,
SDL_RumbleGamepad(port_gamepad(gamepads, count, port), feedback->Rumble.wLeftMotorSpeed,
feedback->Rumble.wRightMotorSpeed, 100);
}
return ERROR_SUCCESS;
@@ -2029,6 +2029,10 @@ static boolean multiplayer_type_menu_initialize(
boolean *widget_deleted)
{
player_spawn_count = 1;
/* port: and no co-op player's controller, which one player with one
gamepad gives back to the keyboard's (xinput_sdl.c's port_gamepad): its
going is not a controller unplugged (input_abstraction.c) */
player_ui_reset_single_player_local_player_controllers();
return TRUE;
}
@@ -2315,6 +2319,8 @@ static boolean main_menu_initialize(
dispose_global_network_game_server();
network_game_accept_remote_connections(FALSE);
player_spawn_count = 1;
/* port: as multiplayer_type_menu_initialize */
player_ui_reset_single_player_local_player_controllers();
player_ui_end_editing_profile();
if (!ui_main_menu_music_active())
ui_start_main_menu_music();
+6 -1
View File
@@ -70,9 +70,14 @@ LEFT_OUT = {
"ui\\shell\\main_menu\\multiplayer_type_select\\checking_updates_screen_join",
}
# children moved: (parent, child) to (x, y); the main menu's Quit up into
# Credits' place
# Credits' place; Multiplayer's Edit Gametypes and its line down a row, for
# Co-op (port_settings.WIDGET_PATCHES)
CHILD_OFFSETS = {
("ui\\shell\\main_menu\\main_menu_select_list", "ui\\shell\\main_menu\\main_menu_item_quit_game"): (192, 391),
("ui\\shell\\main_menu\\multiplayer_type_select\\multiplayer_type_select_list",
"ui\\shell\\main_menu\\multiplayer_type_select\\multiplayer_type_gametypes_item"): (0, 375),
("ui\\shell\\main_menu\\multiplayer_type_select\\multiplayer_type_select_screen",
"ui\\shell\\main_menu\\blueline"): (64, 358),
# the browser's rows up under the column titles, into the place of the
# scroll up button (hidden: the list does not scroll)
**{("ui\\shell\\main_menu\\multiplayer_type_select\\join_game\\join_game_items_list",
+63 -1
View File
@@ -350,13 +350,14 @@ def LOADOUT_HELPS(slot: str) -> list:
STRING_OVERRIDES.update({
f"{MT}/multiplayer_options": ["JOIN GAME", "CREATE GAME", "INTERNET", "LAN", "DIRECT LINK", "EDIT GAMETYPES",
"SERVER BROWSER"],
"SERVER BROWSER", "CO-OP CAMPAIGN"],
f"{MT}/multiplayer_option_descriptions": [
"Browse the games on the\\nInternet.",
"Join a multiplayer game on\\nyour LAN.",
"Join a game by its invite link,\\nor one a Discord invite\\nreached.",
"Host a game on the Internet:\\nplayers join by its invite\\nlink or Discord.",
"Host a game on your LAN\\nonly.",
"Play the campaign with a\\nfriend in split screen:\\nplayer 2 on a gamepad.",
"Set all the attributes for your \\nmultiplayer gametypes and\\nkeep them for future use.",
],
"main_menu/gametype_select/var_gametype_banks": ["STANDARD", "CUSTOM"],
@@ -418,6 +419,10 @@ WIDGET_PATCHES = {
f'<on event="start" run="mp type set mode" open="{MT}/join_game/join_game_screen"/>',
'<on event="left_mouse" run="mouse emit accept event"/>',
]},
# (Co-op, after Create Game's: _coop)
f"{MT}/multiplayer_type_select_list": {"insert_before": {
f"{MT}/multiplayer_type_gametypes_item": [f'<child widget="{MT}/multiplayer_type_coop_item" y="309"/>'],
}},
f"{MT}/join_game/header_join_game": {"children": [
f'<child widget="{MT}/join_game/header_server_browser"/>',
f'<child widget="{MT}/join_game/header_direct_link"/>',
@@ -455,6 +460,7 @@ TITLES = {
f"{MT}/join_game/header_server_browser": "SERVER BROWSER",
f"{MT}/join_game/header_direct_link": "DIRECT LINK",
f"{MT}/lobby/header_lobby": "GAME LOBBY",
f"{MT}/coop/header_player_2": "PLAYER 2 PROFILE",
}
@@ -760,6 +766,61 @@ def _lobby() -> list:
return lines
def _coop() -> list:
"""Co-op, the campaign for two players on this machine in split screen
(the Xbox's Cooperative Play): Multiplayer's CO-OP CAMPAIGN, then player
2's profile, chosen with player 2's controller (its rows take any
controller's presses, as the shared rows do only controller 1's), then
New Game's levels and difficulty (menu_functions.c's coop_begin)"""
base = f"{MT}/coop"
lines = _widget(f"{MT}/multiplayer_type_coop_item",
[("type", "text"), ("left", 51), ("width", 232), ("height", 32), ("bitmap", "bitmaps/list_item_bkd"),
("string_list", f"{MT}/multiplayer_options"), ("string_index", 7), ("font", "ui\\large_ui"),
("color", "#FF2896FF"), ("text_x", 13), ("text_y", 5)],
[f'<on event="a" run="port coop begin" open="{base}/player_2_profile_screen" branch="true"/>',
f'<on event="start" run="port coop begin" open="{base}/player_2_profile_screen" branch="true"/>',
'<on event="left_mouse" run="mouse emit accept event"/>'])
lines += _widget(f"{base}/player_2_profile_screen", [("width", 640), ("height", 480),
("flags", "pass_unhandled_to_focused_child"),
("bitmap", "bitmaps/gradient")],
['<child widget="main_menu/new_select/sel_list_desc_bkd"/>',
f'<child widget="{base}/player_2_profile_list"/>',
f'<child widget="{base}/header_player_2"/>',
f'<child widget="{base}/player_2_help" x="20" y="416"/>'])
lines += _header(f"{base}/header_player_2", f"{base}/header_player_2")
rows = [f'<child widget="{base}/list_item_{index}" x="20" y="{73 + 30 * index}"/>' for index in range(11)]
lines += _widget(f"{base}/player_2_profile_list",
[("type", "column_list"), ("width", 640), ("height", 480),
("flags", "pass_unhandled_to_focused_child up_down_tabs_children"),
("description", "main_menu/profile_manager/player_profile_extended_desc")],
['<data input="3wide player profile list update"/>',
'<on event="created" run="port coop player 2 list initialize"/>',
'<on event="deleted" run="player profile list dispose"/>',
'<on event="custom_activation" run="port coop player 2" '
'open="main_menu/solo_level_select/solo_level_select_screen" branch="true"/>',
*rows, f'<child widget="{base}/player_2_button_bar" y="414"/>'])
for index in range(11):
lines += _widget(f"{base}/list_item_{index}",
[("width", 390), ("height", 28),
("bitmap", "bitmaps/sel_list_item_bkd_top" if index == 0 else "bitmaps/sel_list_item_bkd"),
("font", "ui\\large_ui"), ("color", "#FF2896FF"), ("align", "center"), ("text_y", 3)],
['<on event="a" run="single prev cl item activated"/>',
'<on event="start" run="single prev cl item activated"/>',
'<on event="left_mouse" run="mouse emit accept event"/>',
'<child widget="main_menu/new_select/list_item_text" x="25"/>',
'<child widget="main_menu/new_select/list_item_arrows"/>'])
lines += _widget(f"{base}/player_2_help", [("type", "text"), ("width", 350), ("height", 24),
("text", "Player 2: choose with your controller."),
("font", "ui\\small_ui"), ("color", "#FF2896FF"), ("text_y", 5),
("text_flags", "no_focus_test")], [])
lines += _widget(f"{base}/player_2_button_bar", [("type", "column_list"), ("width", 640), ("height", 28),
("flags", "pass_unhandled_to_focused_child left_right_tabs_items")],
['<data input="common button bar update"/>',
'<child widget="main_menu/profile_manager/profile_manager_button_ok" x="380" y="1"/>',
'<child widget="common_button_back" x="510" y="1"/>'])
return lines
def _item_options_extras() -> list:
"""Item Options' rows of the port's: the map's weapons (YES or NO), and
the loadout, CATEGORY (the weapon set) or CUSTOM (each player's primary
@@ -801,6 +862,7 @@ def multiplayer_files() -> dict:
f"{MT}/join_game".replace("/", ".") + ".port.xml": head + _join_game_extras() + ["</menus>", ""],
f"{MT}/server_settings".replace("/", ".") + ".xml": head + _server_settings() + ["</menus>", ""],
f"{MT}/lobby".replace("/", ".") + ".xml": head + _lobby() + ["</menus>", ""],
f"{MT}/coop".replace("/", ".") + ".xml": head + _coop() + ["</menus>", ""],
"main_menu/settings_select/multiplayer_setup/item_options_edit".replace("/", ".") + ".port.xml": head + _item_options_extras() + ["</menus>", ""],
}