From 9185764b747b52558e388d4e0c8fee8d0759370e Mon Sep 17 00:00:00 2001 From: Daniel Harvey Date: Sun, 22 Feb 2026 20:38:19 -0700 Subject: [PATCH] feat: mode command Adds a with the mode being required, and every parameter after that being an optional unlocked mod option, allowing users to customize that mode from its defaults. This calls a vote on the set. --- etc/commands.conf | 4 +++ src/spads.pl | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/etc/commands.conf b/etc/commands.conf index 0a531f6..c8c95f4 100644 --- a/etc/commands.conf +++ b/etc/commands.conf @@ -103,6 +103,10 @@ battle,pv:player:stopped|100:10 [callVote] ::|0: +[mode] +battle,pv:player:stopped|100:10 +::|100: + [cancelQuit] ::|130: diff --git a/src/spads.pl b/src/spads.pl index c7fe788..b60f026 100644 --- a/src/spads.pl +++ b/src/spads.pl @@ -218,6 +218,7 @@ loadboxes => \&hLoadBoxes, lock => \&hLock, maplink => \&hMapLink, + mode => \&hMode, nextmap => \&hNextMap, nextpreset => \&hNextPreset, notify => \&hNotify, @@ -8182,6 +8183,76 @@ sub hBSet { } +sub hMode { + my ($source,$user,$p_params,$checkOnly)=@_; + + if($#{$p_params} < 0) { + invalidSyntax($user,"mode"); + return 0; + } + + my $modeKey=shift(@{$p_params}); + + # e.g. "Beyond All Reason test-27271-0ec467a" (from lobby battle data, or $targetMod global from config) + my $modName = $lobbyState >= LOBBY_STATE_BATTLE_OPENED ? $lobby->{battles}{$lobby->{battle}{battleId}}{mod} : $targetMod; + # hash ref of all mod option defs: { "sharemode" => {type=>"list", default=>"...", ...}, "unit_market_metal_cost" => {type=>"number", ...}, ... } + my $p_modOptions=getModOptions($modName); + + my @settings; + foreach my $param (@{$p_params}) { + if($param =~ /^([^=]+)=(.*)$/) { + my ($bSetting,$val)=(lc($1),$2); + if(! exists $p_modOptions->{$bSetting}) { + answer("\"$bSetting\" is not a valid mod option for current mod"); + return 0; + } + my $allowExternalValues=$conf{allowModOptionsValues}; + my @allowedValues=getBSettingAllowedValues($bSetting,$p_modOptions,$allowExternalValues); + my $optionType = $p_modOptions->{$bSetting}{type}; + if(! @allowedValues && $allowExternalValues) { + answer("\"$bSetting\" is a mod option of type \"$optionType\", it must be defined in current battle preset to be modifiable"); + return 0; + } + my $allowed=0; + foreach my $allowedValue (@allowedValues) { + if(isRange($allowedValue)) { + $allowed=1 if(matchRange($allowedValue,$val)); + }elsif($optionType eq 'string' && substr($allowedValue,0,1) eq '~') { + my $regexp=substr($allowedValue,1); + if(eval { qr/^$regexp$/ } && ! $@) { + $allowed=1 if($val =~ /^$regexp$/); + } + }elsif($val eq $allowedValue) { + $allowed=1; + } + last if($allowed); + } + if(! $allowed) { + answer("Value \"$val\" for mod option \"$bSetting\" is not allowed with current mod or battle preset"); + return 0; + } + push(@settings,{key => $bSetting, val => $val}); + }else{ + answer("Invalid parameter format \"$param\" (expected key=value)"); + return 0; + } + } + + return 1 if($checkOnly); + + my @changeDescs; + foreach my $setting (@settings) { + $spads->{bSettings}{$setting->{key}}=$setting->{val}; + sendBattleSetting($setting->{key}) if($lobbyState >= LOBBY_STATE_BATTLE_OPENED); + push(@changeDescs,"$setting->{key}=$setting->{val}"); + } + $timestamps{autoRestore}=time; + my $changesStr=join(', ',@changeDescs); + sayBattleAndGame("Mode \"$modeKey\" applied by $user ($changesStr)"); + answer("Mode \"$modeKey\" applied ($changesStr)") if($source eq "pv"); + return 1; +} + sub hCancelQuit { my ($source,$user,$p_params,$checkOnly)=@_; if(! defined $quitAfterGame{action}) {