diff --git a/.gitignore b/.gitignore index 76b1e1f1f..ee7f7c9b6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ /research /out +# Local build logs +/_build_exit.txt +/_build_release.log + # Visual Studio *.sln *.VC.* diff --git a/README.md b/README.md index 43eb8cf72..73aa9a6bb 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Most important: Multiplayer: * Increased (and configurable) tick rate -* TOML-based `ADS` dedicated server configuration with per-level rules, on-demand reloading, rule presets, and on-the-fly game type switching +* TOML-based `ADS` dedicated server configuration with per-level rules, on-demand reloading, and on-the-fly game type switching * "GunGame" game mode * Competitive match framework including "ready up" system and overtime * Improved gaussian distribution method for bullet spread diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index dc1b6ea99..ac80fde9c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -45,6 +45,7 @@ Version 1.4.0 (Lupin): Not yet released - Servers describe their votable levels, game types, and mutator options to clients - `Level` and `Match` can now select a game type and any number of mutators (with their options) for the voted level - Add a vote panel for calling any vote the server allows, opened during gameplay with the bindable `Call Vote Menu` control (`F4` by default) + - The vote panel pre-selects the chosen level's game type and the server's currently active mutator set, with buttons to reset the game type and restore the `Base` or `Current` mutator set - Vote HUD notification now shows live tally, time remaining, and whether you have already voted - Add FactionFiles-integrated multiplayer statistics tracking - Dedicated servers with a configured `fflink_gsk` report a gameplay event stream to FactionFiles @@ -114,6 +115,7 @@ Version 1.4.0 (Lupin): Not yet released - Add `spectate_cameras` console command to toggle showing camera meshes at static camera locations while free look spectating - Deprecated and removed legacy GunGame dedicated server config items now that `GG` is an actual gametype - Deprecated and removed legacy critical hits dedicated server config items now that it is a mutator +- Deprecated and removed the `rules_presets` dedicated server config item and its `rules_preset_aliases` table - Default inactivity tracking for players in dedicated servers to `true`, but kicking inactive players to `false` - Add `-debug` command line switch to enable additional debug logging, intended to help identify long-standing netcode issues that are difficult to nail down. - Add automatic team balance option which handles unbalanced teams mid-game and stops players from switching teams if it would unbalance them @@ -145,6 +147,8 @@ Version 1.4.0 (Lupin): Not yet released - Support af://demo/ID on the af protocol to play demos from FactionFiles - Set client netfps to 40, server default netfps to 40 (configurable) - Make color pickers in the level editor open at the current color instead of black +- A voted level now derives its rules from the server's base rules plus the voted mutator set, rather than inheriting the rules of whichever rotation slot happens to name that level +- `sv_gametype` against a level in the rotation now rebuilds the rules from the base rules for the new game type instead of retargeting the old game type's rules in place [@is-this-c](https://github.com/is-this-c) - Rewrite `VArray` to fix crashes due to MinGW @@ -226,6 +230,8 @@ Version 1.4.0 (Lupin): Not yet released - Fix particle emitters created from `emitters.tbl` templates inheriting uninitialized UID and `Active Distance` values that could make their particles silently fail to spawn in rare cases - Fix spacebar (when bound to `Jump`) moving freelook camera upward when typing in chat - Fix crash risk when leaving a match or changing levels by keeping animation skeletons loaded while animation instances are still playing them, instead of unloading as soon as no character references them +- Fix dedicated servers not loading `alpinefaction.vpp`, which prevented `af_level_quirks.tbl` from loading and left known run maps unrecognized +- Fix potential crash when an Alpine options `.tbl` file contains an unrecognized option name [@is-this-c](https://github.com/is-this-c) - Clear cached server config output after a shuffle of a server's rotation diff --git a/game_patch/graphics/gr.cpp b/game_patch/graphics/gr.cpp index 74b1ec650..38a27d65a 100644 --- a/game_patch/graphics/gr.cpp +++ b/game_patch/graphics/gr.cpp @@ -618,10 +618,9 @@ ConsoleCommand2 pow2_tex_cmd{ void evaluate_pow2tex(const rf::String& level_filename) { // if dbg_pow2tex is active, use manual override instead of level filename lookup if (!override_pow2tex) { - bool should_p2t_fix = false; - - if (is_p2t_fix_level(level_filename)) { - should_p2t_fix = true; + const bool should_p2t_fix = is_p2t_fix_level(level_filename); + // Renderer-only, so a dedicated server has nothing to report. + if (!rf::is_dedicated_server && should_p2t_fix) { rf::console::print("Applying power of 2 texture fix to known affected level {}", level_filename); } @@ -631,7 +630,7 @@ void evaluate_pow2tex(const rf::String& level_filename) { // Always sync D3D11 state with current p2t value at level load if (g_game_config.renderer == GameConfig::Renderer::d3d11) { gr::d3d11::set_pow2_tex_active(rf::gr::d3d::p2t != 0); - if (is_sky_fix_level(level_filename)) { + if (!rf::is_dedicated_server && is_sky_fix_level(level_filename)) { rf::console::print("Applying sky fix to known affected level {}", level_filename); } } diff --git a/game_patch/hud/multi_spectate.cpp b/game_patch/hud/multi_spectate.cpp index 8e8c6e7f9..6342167d8 100644 --- a/game_patch/hud/multi_spectate.cpp +++ b/game_patch/hud/multi_spectate.cpp @@ -1637,9 +1637,12 @@ void multi_spectate_process_bind_input() const bool player_mode = g_spectate_mode_enabled; // first/third person const bool static_mode = g_spectate_static_active; // static cameras const bool bindable = player_mode || static_mode; - // Still consume the numpad counters while typing (so they don't queue up), but don't act on - // them - the console/chat-say box uses the separate character buffer for typing. - const bool typing = rf::console::console_is_visible() || rf::multi_chat_is_say_visible(); + // Still consume the numpad counters while an overlay owns input (so they don't queue up), but + // don't act on them - the console/chat-say box reads the separate character buffer, and the + // vote panel's own rows sit under the numpad keys whether or not a text box has focus. + const bool typing = rf::console::console_is_visible() || + rf::multi_chat_is_say_visible() || + vote_panel_is_gameplay_overlay_active(); if (rf::key_get_and_reset_down_counter(rf::KEY_PADENTER) > 0 && !typing) { g_spectate_bind_dialog_open = bindable && !g_spectate_bind_dialog_open; diff --git a/game_patch/input/control_input_filter.cpp b/game_patch/input/control_input_filter.cpp index e03bc68e6..2b1eb89b0 100644 --- a/game_patch/input/control_input_filter.cpp +++ b/game_patch/input/control_input_filter.cpp @@ -44,9 +44,11 @@ ControlInputInjection resolve_injection(rf::ControlConfig* ccp, rf::ControlConfi FunHook control_config_check_pressed_hook{ 0x0043D4F0, [](rf::ControlConfig* ccp, rf::ControlConfigAction action, bool* just_pressed) { - // A veto owns the action completely: the engine is not consulted and no - // injection can lift it. + // A veto owns the action completely: no injection can lift it. The engine is + // still called, for every veto participant, so the press is consumed rather + // than piling up and firing all at once when the veto lifts. if (action_is_vetoed(ccp, action)) { + control_input_filter_check_pressed_unfiltered(ccp, action, nullptr); if (just_pressed) { *just_pressed = false; } diff --git a/game_patch/input/key.cpp b/game_patch/input/key.cpp index 7833e9c05..108b333c8 100644 --- a/game_patch/input/key.cpp +++ b/game_patch/input/key.cpp @@ -524,9 +524,11 @@ CodeInjection controls_process_chat_menu_patch{ const bool chat_menu_numeric_capture_active = get_chat_menu_is_active() && !rf::console::console_is_visible() - && !rf::multi_chat_is_say_visible(); + && !rf::multi_chat_is_say_visible() + && !vote_panel_is_capturing_text(); const bool waypoint_link_numeric_capture_active = - waypoints_utils_link_editor_text_input_active(); + waypoints_utils_link_editor_text_input_active() + && !vote_panel_is_capturing_text(); // Consume top-row number keys for active overlay input modes so they do // not trigger gameplay bindings. diff --git a/game_patch/misc/alpine_options.cpp b/game_patch/misc/alpine_options.cpp index 8daa01fac..44d08d2f5 100644 --- a/game_patch/misc/alpine_options.cpp +++ b/game_patch/misc/alpine_options.cpp @@ -848,12 +848,13 @@ void load_single_af_options_file(const std::string& file_name) } auto meta_it = option_metadata.find(option_name); + const bool meta_found = meta_it != option_metadata.end(); // Allow any af_client*.tbl file for options designated to af_client.tbl - bool is_af_client_variant = (meta_it->second.filename == "af_client.tbl" && - file_name.rfind("af_client", 0) == 0 && file_name.ends_with(".tbl")); + const bool is_af_client_variant = (meta_found && meta_it->second.filename == "af_client.tbl" && + file_name.rfind("af_client", 0) == 0 && file_name.ends_with(".tbl")); - if (meta_it != option_metadata.end() && + if (meta_found && (meta_it->second.filename == file_name || is_af_client_variant) && (!rf::is_dedicated_server || meta_it->second.apply_on_server)) { @@ -866,7 +867,7 @@ void load_single_af_options_file(const std::string& file_name) xlog::debug("Option ID {} marked as loaded", static_cast(metadata.id)); } } - else if (meta_it != option_metadata.end()) { + else if (meta_found) { if (meta_it->second.filename != file_name && !is_af_client_variant) { xlog::warn("Option {} in {} skipped (wrong alpine tbl file)", option_name, file_name); } diff --git a/game_patch/misc/alpine_settings.cpp b/game_patch/misc/alpine_settings.cpp index 2277939d7..238c78fcb 100644 --- a/game_patch/misc/alpine_settings.cpp +++ b/game_patch/misc/alpine_settings.cpp @@ -1625,7 +1625,7 @@ void alpine_player_settings_save(rf::Player* player) const std::vector& unreadable_saved_votes = saved_votes_unparsed(); if (!saved_votes.empty() || !unreadable_saved_votes.empty()) { file << "\n[SavedVotes]\n"; - file << "; Format is SavedVote{N}=1|{Name}|{Type}|{Level}|{GameType}|{TeamSize}|{ExtendMinutes}|{Mutators}\n"; + file << "; Format is SavedVote{N}=2|{Name}|{Type}|{Level}|{GameType}|{TeamSize}|{ExtendMinutes}|{Mutators}|{MutatorsExplicit}\n"; for (size_t i = 0; i < saved_votes.size(); ++i) { file << "SavedVote" << i << "=" << saved_vote_encode(saved_votes[i]) << "\n"; diff --git a/game_patch/misc/saved_votes.cpp b/game_patch/misc/saved_votes.cpp index cb9eaba72..efb98598d 100644 --- a/game_patch/misc/saved_votes.cpp +++ b/game_patch/misc/saved_votes.cpp @@ -15,6 +15,7 @@ #include "alpine_settings.h" #include "../multi/multi.h" #include "../multi/vote_client.h" +#include "../rf/level.h" namespace { @@ -33,12 +34,14 @@ constexpr size_t max_unparsed_lines = 50; // --------------------------------------------------------------------------- // One-line INI encoding // -// 1||||||| +// 2|||||||| // // is entries joined by ';', each `name` or `name:opt=Xval,opt=Xval`, // where the value's first character types it: b0/b1 bool, i int, f float, -// c