Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add overtime when round ends in a tie #292

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ Configuration example:
+Health Is Super:
// Limit armor reward to 200 instead of 100
+Armor Is Super:
// Enable for round to go to overtime when the time limit is up but score is tied
$DF Overtime Enabled: false
// Overtime ends when tie is broken or after this duration (whichever comes first)
+Duration: 5


Building
Expand Down
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Version 1.9.0 (not released yet)
- Add level filename to "Level Initializing" console message
- Properly handle WM_PAINT in dedicated server, may improve performance (DF bug)
- Fix crash when `verify_level` command is run without a level being loaded
- Add `$DF Overtime Enabled` option in dedicated server config

Version 1.8.0 (released 2022-09-17)
-----------------------------------
Expand Down
118 changes: 118 additions & 0 deletions game_patch/multi/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "../main/main.h"
#include <common/utils/list-utils.h>
#include "../rf/player/player.h"
#include "../rf/gameseq.h"
#include "../rf/multi.h"
#include "../rf/parse.h"
#include "../rf/weapon.h"
Expand All @@ -44,6 +45,7 @@ const char* g_rcon_cmd_whitelist[] = {

ServerAdditionalConfig g_additional_server_config;
std::string g_prev_level;
bool g_is_overtime = false;

void parse_vote_config(const char* vote_name, VoteConfig& config, rf::Parser& parser)
{
Expand Down Expand Up @@ -192,6 +194,13 @@ void load_additional_server_config(rf::Parser& parser)
}
}

if (parser.parse_optional("$DF Overtime Enabled:")) {
g_additional_server_config.overtime.enabled = parser.parse_bool();
if (parser.parse_optional("+Duration:")) {
g_additional_server_config.overtime.additional_time = parser.parse_uint();
}
}

if (!parser.parse_optional("$Name:") && !parser.parse_optional("#End")) {
parser.error("end of server configuration");
}
Expand Down Expand Up @@ -659,6 +668,111 @@ CodeInjection multi_limbo_init_injection{
},
};

bool round_is_tied(rf::NetGameType game_type)
{
if (rf::multi_num_players() <= 1) {
return false;
}

switch (game_type) {
case rf::NG_TYPE_DM: {
int highest_score = rf::player_list->stats->score;
int players_with_highest_score = 1;

for (rf::Player* player = rf::player_list->next; player != nullptr &&
player != rf::player_list; player = player->next) {
if (player->stats->score > highest_score) {
highest_score = player->stats->score;
players_with_highest_score = 1;
}
else if (player->stats->score == highest_score) {
players_with_highest_score++;
}
}
return players_with_highest_score >= 2;
}
case rf::NG_TYPE_CTF: {
int red_score = rf::multi_ctf_get_red_team_score();
int blue_score = rf::multi_ctf_get_blue_team_score();

if (red_score == blue_score) {
return true;
}

bool red_flag_stolen = !rf::multi_ctf_is_red_flag_in_base();
bool blue_flag_stolen = !rf::multi_ctf_is_blue_flag_in_base();

// not currently tied, but if the team with the flag right now caps it, they will be
return (red_flag_stolen && blue_score == red_score - 1) || (blue_flag_stolen && red_score == blue_score - 1);
}
case rf::NG_TYPE_TEAMDM: {
return rf::multi_tdm_get_red_team_score() == rf::multi_tdm_get_blue_team_score();
}
default:
return false;
}
}

FunHook<void()> multi_check_for_round_end_hook{
0x0046E7C0,
[]() {
const bool time_up = (rf::multi_time_limit > 0.0f && rf::level.time >= rf::multi_time_limit);
bool round_over = time_up;
const auto game_type = rf::multi_get_game_type();

if (g_is_overtime) {
round_over = (time_up || !round_is_tied(game_type));
}
else {
switch (game_type) {
case rf::NG_TYPE_DM: {
for (rf::Player* player = rf::player_list; player; player = player->next) {
if (player->stats->score >= rf::multi_kill_limit) {
round_over = true;
break;
}
if (player == rf::player_list)
break;
}
break;
}
case rf::NG_TYPE_CTF: {
if (rf::multi_ctf_get_red_team_score() >= rf::multi_cap_limit ||
rf::multi_ctf_get_blue_team_score() >= rf::multi_cap_limit) {
round_over = true;
}
break;
}
case rf::NG_TYPE_TEAMDM: {
if (rf::multi_tdm_get_red_team_score() >= rf::multi_kill_limit ||
rf::multi_tdm_get_blue_team_score() >= rf::multi_kill_limit) {
round_over = true;
}
break;
}
default:
break;
}
}

if (round_over && rf::gameseq_get_state() != rf::GS_MULTI_LIMBO) {
if (time_up && g_additional_server_config.overtime.enabled && !g_is_overtime && round_is_tied(game_type)) {
g_is_overtime = true;
extend_round_time(g_additional_server_config.overtime.additional_time);

std::string msg = std::format("\xA6 OVERTIME! Game will end when the tie is broken");
msg += g_additional_server_config.overtime.additional_time > 0
? std::format(", or in {} minutes!", g_additional_server_config.overtime.additional_time)
: "!";
send_chat_line_packet(msg.c_str(), nullptr);
}
else {
rf::multi_change_level(nullptr);
}
}
}
};

void server_init()
{
// Override rcon command whitelist
Expand Down Expand Up @@ -724,6 +838,9 @@ void server_init()

// Reduce limbo duration if server is empty
multi_limbo_init_injection.install();

// Check if round is finished or if overtime should begin
multi_check_for_round_end_hook.install();
}

void server_do_frame()
Expand All @@ -734,6 +851,7 @@ void server_do_frame()

void server_on_limbo_state_enter()
{
g_is_overtime = false;
g_prev_level = rf::level.filename.c_str();
server_vote_on_limbo_state_enter();

Expand Down
7 changes: 7 additions & 0 deletions game_patch/multi/server_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ struct HitSoundsConfig
int rate_limit = 10;
};

struct OvertimeConfig
{
bool enabled = false;
int additional_time = 5;
};

struct ServerAdditionalConfig
{
VoteConfig vote_kick;
Expand All @@ -38,6 +44,7 @@ struct ServerAdditionalConfig
std::optional<float> spawn_life;
std::optional<float> spawn_armor;
HitSoundsConfig hit_sounds;
OvertimeConfig overtime;
std::map<std::string, std::string> item_replacements;
std::string default_player_weapon;
std::optional<int> default_player_weapon_ammo;
Expand Down
3 changes: 3 additions & 0 deletions game_patch/rf/multi.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ namespace rf
static auto& multi_io_send_buffered_reliable_packets = addr_as_ref<void(Player *pp)>(0x004796C0);
static auto& multi_find_player_by_addr = addr_as_ref<Player*(const NetAddr& addr)>(0x00484850);
static auto& multi_find_player_by_id = addr_as_ref<Player*(uint8_t id)>(0x00484890);
static auto& multi_time_limit = addr_as_ref<float>(0x0064EC4C);
static auto& multi_kill_limit = addr_as_ref<int>(0x0064EC50);
static auto& multi_cap_limit = addr_as_ref<int>(0x0064EC58);
static auto& multi_ctf_get_red_team_score = addr_as_ref<uint8_t()>(0x00475020);
static auto& multi_ctf_get_blue_team_score = addr_as_ref<uint8_t()>(0x00475030);
static auto& multi_ctf_get_red_flag_player = addr_as_ref<Player*()>(0x00474E60);
Expand Down