Code: Select all
This is the current match.py script used for the league. Sharing it incase anyone wants to use it for their own purposes and I have a few requests to be added to the script. Below are two list the first one is the primary requests and the second is secondary requests.
"""
Rewrite of match.py.
Intended to be used for tournaments and clan matches.
"""
from pyspades.common import coordinates, to_coordinates, prettify_timespan
from commands import add, admin, name
from twisted.internet import reactor
from twisted.internet.task import LoopingCall
S_MATCH_START = 'The match has started!'
S_MATCH_TIMELEFT = '{time} remaining.'
S_MATCH_CANCELLED = 'The match has been stopped by {who}!'
S_MATCH_OVER = 'The match is over! {team} wins {score1} to {score2}!'
S_MATCH_TIE = 'The match is over! It\'s a tie!'
S_MATCH_TIE_EXTEND = 'Tie! Extending match by {time}. {sd}'
S_MATCH_SUDDEN_DEATH = 'Sudden death!'
S_MATCH_KILL = '{killer} killed {victim}!'
S_MATCH_FLAG_TAKE = '{who} took the {team} flag in {coord}!'
S_MATCH_FLAG_DROP = '{who} dropped the {team} flag in {coord}!'
S_MATCH_FLAG_CAP = '{who} captured the {team} flag!'
# In minutes.
MATCH_OVERTIME_LENGTH = 15
MATCH_ALLOW_TIES = True # set MATCH_EXTEND_ON_TIE if you want a "one-time only" extension in the event of a tie
MATCH_DISABLE_CHAT = True
MATCH_FEED_IN_CHAT = False
MATCH_RESET_FLAG_IN_WATER = True
MATCH_EXTEND_ON_TIE = True # only extends once
MATCH_SUDDEN_DEATH_ON_TIE = True
@admin
@name('timer')
def begin_timer(connection, time=60):
if connection.protocol.match_in_progress:
return 'A match is already running!'
time = int(time) * 60
connection.protocol.begin_match(time)
add(begin_timer)
@admin
@name('stoptimer')
def stop_timer(connection):
if not connection.protocol.match_in_progress:
return 'There\'s no match to stop!'
connection.protocol.end_match(cancelled=True, cancelled_by=connection.printable_name)
add(stop_timer)
@admin
@name('toggleovertime')
def toggle_overtime(connection):
global MATCH_EXTEND_ON_TIE
MATCH_EXTEND_ON_TIE = not MATCH_EXTEND_ON_TIE
return 'Overtime is now %s!' % ['disabled', 'enabled'][int(MATCH_EXTEND_ON_TIE)]
add(toggle_overtime)
def apply_script(protocol, connection, config):
class MatchProtocol(protocol):
match_in_progress = False
match_timer_ends = 0
match_intel_caps = {}
match_sudden_death = False
match_timer_call = None
match_messages = []
match_message_loop = None
match_overtime = False
blue_kill_count = 0
green_kill_count = 0
blue_death_count = 0
green_death_count = 0
def msg(self, message, override=False):
if MATCH_FEED_IN_CHAT or override:
self.send_chat(message, irc=True)
else:
self.irc_say(message)
def begin_match(self, time):
self.match_in_progress = True
self.match_timer_ends = reactor.seconds() + time
self.match_message_loop = LoopingCall(self.show_match_messages)
self.match_message_loop.start(3)
self.msg(S_MATCH_START, override=True)
for p in self.players:
player = self.players[p]
player.spawn(player.team.get_random_location(force_land=True))
# This will start a loop of callLater()s.
self.show_match_timer()
def end_match(self, cancelled=False, cancelled_by=None):
if self.green_team.score > self.blue_team.score:
result = S_MATCH_OVER.format(team=self.green_team.name, score1=self.green_team.score, score2=self.blue_team.score)
elif self.blue_team.score > self.green_team.score:
result = S_MATCH_OVER.format(team=self.blue_team.name, score1=self.blue_team.score, score2=self.green_team.score)
elif not cancelled:
if (not MATCH_ALLOW_TIES) or (MATCH_EXTEND_ON_TIE and not self.match_overtime):
self.match_sudden_death = MATCH_SUDDEN_DEATH_ON_TIE
self.match_overtime = True
self.extend_match(MATCH_OVERTIME_LENGTH * 60)
self.show_match_timer()
return
else:
result = S_MATCH_TIE
greenkills = self.green_kill_count
greendeaths = float(max(1,self.green_death_count))
bluekills = self.blue_kill_count
bluedeaths = float(max(1,self.blue_death_count))
self.match_messages.append("Green team had %s kill(s) and %s death(s) (%.2f). Blue team had %s kill(s) and %s death(s) (%.2f)." % (self.green_kill_count, self.green_death_count, greenkills/greendeaths, self.blue_kill_count, self.blue_death_count, bluekills/bluedeaths))
for player in self.players.values():
player.drop_flag()
if player.kill_count > 0 or player.death_count > 0:
self.match_messages.append("%s has %s kill(s) and %s death(s) (%.2f) and (%s Caps, %s Grabs)." % (player.name, player.kill_count, player.death_count, player.kill_count/float(max(1,player.death_count)), player.cap_count, player.grab_count))
self.match_in_progress = False
self.match_sudden_death = False
self.match_timer_ends = 0
self.green_kill_count = 0
self.blue_kill_count = 0
self.green_death_count = 0
self.blue_death_count = 0
for player in self.players.values():
player.kill_count = 0
player.death_count = 0
player.cap_count = 0
player.grab_count = 0
if cancelled:
self.match_timer_call.cancel()
msg = S_MATCH_CANCELLED.format(who=cancelled_by)
self.msg(msg, override=True)
else:
self.msg(result, override=True)
def extend_match(self, time):
self.match_timer_ends += time
sd = S_MATCH_SUDDEN_DEATH if self.match_sudden_death else ''
self.msg(S_MATCH_TIE_EXTEND.format(time=prettify_timespan(time, get_seconds=True), sd=sd), override=True)
def show_match_timer(self):
from math import floor
time_left = self.match_timer_ends - reactor.seconds()
if time_left > 60:
next_call = 60
else:
next_call = max(1, floor(time_left / 2.0))
if time_left <= 0:
if self.match_in_progress:
self.end_match()
return
self.msg(S_MATCH_TIMELEFT.format(time=prettify_timespan(time_left, get_seconds=True)), override=True)
self.match_timer_call = reactor.callLater(next_call, self.show_match_timer)
def show_match_messages(self):
if len(self.match_messages) == 0:
if not self.match_in_progress:
self.match_message_loop.stop()
return
message = self.match_messages.pop(0)
self.msg(message)
class MatchConnection(connection):
connection.kill_count = 0
connection.death_count = 0
connection.cap_count = 0
connection.grab_count = 0
def on_block_build_attempt(self, x, y, z):
if not self.protocol.match_in_progress:
return False
return connection.on_block_build_attempt(self, x, y, z)
def on_block_destroy(self, x, y, z, value):
if not self.protocol.match_in_progress:
return False
return connection.on_block_destroy(self, x, y, z, value)
def on_chat(self, message, global_message):
if MATCH_DISABLE_CHAT and global_message and self.protocol.match_in_progress and not any(
t in self.user_types for t in ('admin', 'moderator', 'guard', 'trusted')):
self.send_chat("Global messages are disabled for the duration of this match.")
return False
return connection.on_chat(self, message, global_message)
def on_command(self, command, arguments):
if self.protocol.match_in_progress and command == 'time':
time_left = self.protocol.match_timer_ends - reactor.seconds()
msg = S_MATCH_TIMELEFT.format(time=prettify_timespan(time_left, get_seconds=True))
self.send_chat(msg)
return False
if command == 'kill':
self.kill()
return connection.on_command(self, command, arguments)
def on_flag_drop(self):
x, y, z = self.get_location()
if self.team.other.flag.z >= 63:
x_offset = 3
y_offset = 5
z = self.protocol.map.get_z(x, y)
if y > 256:
self.team.other.flag.set(x - x_offset, y - y_offset, z - 1)
if y < 256:
self.team.other.flag.set(x - x_offset, y + y_offset, z - 1)
self.team.other.flag.update()
self.protocol.match_messages.append(S_MATCH_FLAG_DROP.format(
who=self.printable_name, team=self.team.other.name, coord=to_coordinates(x,y)))
return connection.on_flag_drop(self)
def on_flag_take(self):
if not self.protocol.match_in_progress:
return False
x, y, z = self.get_location()
self.grab_count += 1
self.protocol.match_messages.append(S_MATCH_FLAG_TAKE.format(
who=self.printable_name, team=self.team.other.name, coord=to_coordinates(x,y)))
return connection.on_flag_take(self)
def on_flag_capture(self):
if not self.protocol.match_in_progress:
return False
self.cap_count += 1
result = connection.on_flag_capture(self)
message = S_MATCH_FLAG_CAP.format(
who=self.printable_name, team=self.team.other.name)
if self.protocol.match_sudden_death:
self.protocol.msg(message, override=True)
self.protocol.end_match()
else:
self.protocol.match_messages.append(S_MATCH_FLAG_CAP.format(
who=self.printable_name, team=self.team.other.name))
return result
def on_kill(self, killer, type, grenade):
if not self.protocol.match_in_progress:
return False
if self.team == self.protocol.green_team:
if killer is not None and self.team is not killer.team:
if self != killer:
killer.kill_count += 1
self.protocol.blue_kill_count += 1
self.death_count += 1
self.protocol.green_death_count += 1
if self.team == self.protocol.blue_team:
if killer is not None and self.team is not killer.team:
if self != killer:
killer.kill_count += 1
self.protocol.green_kill_count += 1
self.death_count += 1
self.protocol.blue_death_count += 1
self.protocol.match_messages.append(S_MATCH_KILL.format(
killer=killer.name if killer else self.name, victim=self.name))
return connection.on_kill(self, killer, type, grenade)
def on_line_build_attempt(self, points):
if not self.protocol.match_in_progress:
return False
return connection.on_line_build_attempt(self, points)
return MatchProtocol, MatchConnection
PRIMARY:
- /PAUSE - pauses the timer toggles killing and building, can no longer pick up the flag.
- There are two ways to handle pause. We could freeze players in there current positions, or we could let them run around and when we unpause they respawn on their side of the map.
This isnt a command but after x number of players have joined either team no one else can join. So for example if the max number of players is 5 it will be impossible for a sixth player to join. Well i guess it would need a command to toggle it off or increase its size.
The intel can only spawn on a 20 block radius away from the command post, it wouldnt spawn in water either.
- after the match was over and there is a tie it will print out the distance the flag moved from the center