EDIT:
Now updated for 2.9.2, including:
- Known good settings for PUG matches, with a guarantee that PUG matches using defaults won't last longer than ten minutes!
- Configurable round settings (time length of round, of stalemate countdown, and many more!)
- The previous pub settings for the plugin are supported out-of-box by changing a single config setting (set pugSettings from true, to false)
- Framerate fixes for higher framerate versions
- Config settings at the top of the file to make behavior modification of the plugin easy
- Massive code cleanup
This plugin makes it so that if a stalemate in Dkoth has been going on for X minutes, where neither team has captured the enemies' point, the game will start decreasing both teams timers, until either time runs out or a team captures a point. This idea is meant to prevent endless turtling by forcing the other team to create a unified offense.
EDIT:
It may not be obvious, but this plugin can also be used to make Dkoth match times more predictable by always using stalemate timing (i. e, setting the time till a stalemate to zero). By doing so, you are guaranteed to have the match end after not more than twice the starting time. This makes DKoth have a much more predictable resolution.
The plugin updates connecting client times to match the servers, without need to use a server-sent plugin.
Installation
Add the below file to your Plugins folder. If you just want default PUG settings, you're done, when you next host DKoth your server will have anti-stall enabled. If you would rather use Pub settings, open the file and edit the file at top to match your desired settings.
Conflicts: None I'm aware of
Version: 2.9.2 and above, though checks should be made for compatibility for new versions.
//////////////////////////////////////////////////////////////////////////
// Machidro's Dkoth Anti-stall plugin
// Plugin Config
// META SETTINGS:
// ParamName: PluginEnabled
// Turns the FreezeTime Plugin on on off
// Options: true or false
// Default: true
PluginEnabled = true;
// ParamName: PugSettings
// If true, overwrites the rest of the config below with known good pug settings
// If false, uses the below config (with default-reasonable pub game settings)
// These settings will guarantee matches that last at most ~10 minutes (with average around 7.5 by):
// 1. Starting both side timers at 5 minutes
// 2. Immediately and automatically decrementing both side's timers once the round begins
// 3. Decrementing your timer, but not the enemies, if your point is capped.
// Options: true or false
// Default: true
PugSettings = true;
////////////////////////////////////////////////////////////////////////////////////
// MANUAL CONFIG OPTIONS:
// (PugSettings above overwrites these, make sure you have it set to false if you want to change things.)
global.dkStallSettings = ds_map_create();
// ParamName: StartingTimerSeconds
// Sets the number of seconds on the DKoth timer for each team when the match starts
// Default: 3 minutes (180 seconds)
ds_map_add(global.dkStallSettings, "StartingTimerSeconds", 60 * 3);
// ParamName: SecondsTillStalemate
// Sets the number of seconds after which to start decrementing neutral time for both dkoth points.
// If set to zero, dkoth timers will always be decrementing
// Default: Two minutes (120 seconds)
ds_map_add(global.dkStallSettings, "SecondsTillStalemate", 60 * 2);
// ParamName: ShowLobbyNotifications
// Sets if the players will receive a stalemate notification once a stalemate is detected
// If secondsTillStalemate is set to zero, we will not show notifications, ever, to avoid spamming them.
// Default: True
ds_map_add(global.dkStallSettings, "ShowLobbyNotifications", true);
// ParamName: HostTimerIgnore
// Dkoth games technically have a host timer that ticks down which can auto-end the game after your match timer runs out.
// This breaks DKoth if you set your timer low, and this plugin is about avoiding needing such a mercy kill.
// However, as a courtesy/concession, the option is left in at the user's discretion.
// This is extremely low-tested if false because you should leave this true.
// Default: true
ds_map_add(global.dkStallSettings, "HostTimerIgnore", true);
/////////////////////////////////////////////////////////////////////////////
// Machidro's Dkoth Anti-stall
// Server mod to prevent stalemates by reducing both timers after X minutes of a match have passed
if (PluginEnabled) {
if (PugSettings) {
// Max of 10 minute matches, average will be around 7.5 minutes
ds_map_replace(global.dkStallSettings, "StartingTimerSeconds", 60 * 5 );
ds_map_replace(global.dkStallSettings, "SecondsTillStalemate", 0 );
ds_map_replace(global.dkStallSettings, "ShowLobbyNotifications", false );
ds_map_replace(global.dkStallSettings, "HostTimerIgnore", true );
}
object_event_add(DKothHUD,ev_create,0,"
if (global.isHost) {
// Unpack the settings map for the Dkoth HUD for readability purposes.
StartingTimerSeconds = ds_map_find_value(global.dkStallSettings, 'StartingTimerSeconds');
SecondsTillStalemate = ds_map_find_value(global.dkStallSettings, 'SecondsTillStalemate');
ShowLobbyNotifications = ds_map_find_value(global.dkStallSettings, 'ShowLobbyNotifications');
HostTimerIgnore = ds_map_find_value(global.dkStallSettings, 'HostTimerIgnore');
maxTimeTillStalemate = SecondsTillStalemate * global.game_fps;
stalemateCountdown = maxTimeTillStalemate;
stalemateNotificationSent = false;
// Parameters defined in the original DKothHUD's constructor:
redTimer = StartingTimerSeconds * global.game_fps;
blueTimer = StartingTimerSeconds * global.game_fps;
}
");
object_event_add(DKothHUD,ev_step,0,"
if (global.isHost) {
if (HostTimerIgnore) {
// Prevents the host timer from decrementing
hostTimer = global.timeLimitMins * 60 * global.game_fps;
}
// Determines if either point is captured.
pointsAreNeutral = !KothRedControlPoint.locked
&& !KothBlueControlPoint.locked
&& KothRedControlPoint.team == TEAM_RED
&& redTimer > 0
&& KothBlueControlPoint.team == TEAM_BLUE
&& blueTimer > 0;
if (!pointsAreNeutral) {
// Reset the stalemate countdown, allow for a new notification
stalemateCountdown = maxTimeTillStalemate;
stalemateNotificationSent = false;
}
else
{
// Possible stalemate is occurring.
if (stalemateCountdown > 0) {
stalemateCountdown -= 1;
}
else {
// Known stalemate is occurring.
if (!stalemateNotificationSent && maxTimeTillStalemate > 0 && ShowLobbyNotifications) {
// Notify the lobby.
stalemateNotificationSent = true;
ServerMessageString('Stalemate detected! Both timers will decrease until one team gets a capture!', global.sendBuffer);
with NoticeO instance_destroy();
notice = instance_create(0, 0, NoticeO);
notice.notice = NOTICE_CUSTOM;
notice.message = 'Stalemate detected! Both timers will decrease until one team gets a capture!';
}
// Reduce both round timers.
redTimer -= 1;
blueTimer -= 1;
GameServer.syncTimer = 1;
}
}
// Check for stalemate end conditions:
// Absolute stalemate - both teams are out of time, not capping, and control their own point.
trueStalemate = redTimer <= 0
&& blueTimer <= 0
&& redTimer == blueTimer
&& KothRedControlPoint.team == TEAM_RED
&& KothBlueControlPoint.team == TEAM_BLUE
&& KothRedControlPoint.capping <= 0
&& KothBlueControlPoint.capping <= 0;
// Red is winning in a stalemate
redWinningStalemate = redTimer <= 0
&& blueTimer > 0
&& KothRedControlPoint.team == TEAM_RED
&& KothBlueControlPoint.team == TEAM_BLUE
&& stalemateCountdown <= 0
&& KothRedControlPoint.capping <= 0;
// Blue is winning in a stalemate
blueWinningStalemate = blueTimer <= 0
&& redTimer > 0
&& KothRedControlPoint.team == TEAM_RED
&& KothBlueControlPoint.team == TEAM_BLUE
&& stalemateCountdown <= 0
&& KothBlueControlPoint.capping <= 0;
if (trueStalemate) {
global.winners = TEAM_SPECTATOR;
}
else if (redWinningStalemate) {
global.winners = TEAM_RED;
}
else if (blueWinningStalemate) {
global.winners = TEAM_BLUE;
}
}
");
}