Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # maintained by triplefox
- # Copyright (c) James Hofmann 2012.
- # This file is part of pyspades.
- # pyspades is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- # pyspades is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- # You should have received a copy of the GNU General Public License
- # along with pyspades. If not, see <http://www.gnu.org/licenses/>.
- from twisted.internet.reactor import seconds
- from scheduler import Scheduler
- from commands import name, add, get_player, join_arguments, InvalidPlayer, admin, alias
- REQUIRE_REASON = True
- S_NO_VOTEKICK = 'No votekick in progress'
- S_DEFAULT_REASON = 'NO REASON GIVEN'
- S_IN_PROGRESS = 'Votekick already in progress'
- S_SELF_VOTEKICK = "You can't votekick yourself"
- S_NOT_ENOUGH_PLAYERS = "There aren't enough players to vote"
- S_VOTEKICK_IMMUNE = "You can't votekick this player"
- S_NOT_YET = "You can't start another votekick yet!"
- S_NEED_REASON = 'You must provide a reason for the votekick'
- S_CANT_CANCEL = "You didn't start the votekick!"
- S_YES = '{player} voted YES'
- S_ENDED = 'Votekick for {victim} has ended. {result}'
- S_RESULT_TIMED_OUT = 'Votekick timed out'
- S_RESULT_CANCELLED = 'Cancelled'
- S_RESULT_BANNED = 'Banned by admin'
- S_RESULT_KICKED = 'Kicked by admin'
- S_RESULT_INSTIGATOR_KICKED = 'Instigator kicked by admin'
- S_RESULT_LEFT = '{victim} left during votekick'
- S_RESULT_INSTIGATOR_LEFT = 'Instigator {instigator} left'
- S_RESULT_PASSED = 'Player kicked'
- S_ANNOUNCE_IRC = '* {instigator} started a votekick against player {victim}. ' \
- 'Reason: {reason}'
- S_ANNOUNCE = '{instigator} started a VOTEKICK against {victim}. Say /Y to agree'
- S_ANNOUNCE_SELF = 'You started a votekick against {victim}. Say /CANCEL to ' \
- 'stop it'
- S_UPDATE = '{instigator} is votekicking {victim}. /Y to vote ({needed} left)'
- S_REASON = 'Reason: {reason}'
- class VotekickFailure(Exception):
- pass
- @name('votekick')
- def start_votekick(connection, *args):
- protocol = connection.protocol
- if connection not in protocol.players:
- raise KeyError()
- player = connection
- if protocol.votekick_enabled == False:
- return "Votekicking disabled"
- if player.votekick_enabled == False:
- return "You are not allowed to initiate a votekick."
- if not args:
- if protocol.votekick:
- # player requested votekick info
- protocol.votekick.send_chat_update(player)
- return
- raise ValueError()
- value = args[0]
- try:
- # vanilla aos behavior
- victim = get_player(protocol, '#' + value)
- except InvalidPlayer:
- victim = get_player(protocol, value)
- reason = join_arguments(args[1:])
- try:
- # attempt to start votekick
- votekick = Votekick.start(player, victim, reason)
- protocol.votekick = votekick
- except VotekickFailure as err:
- return str(err)
- @name('cancel')
- def cancel_votekick(connection):
- protocol = connection.protocol
- votekick = protocol.votekick
- if not votekick:
- return S_NO_VOTEKICK
- if connection in protocol.players:
- player = connection
- if (player is not votekick.instigator and not player.admin and
- not player.rights.cancel):
- return S_CANT_CANCEL
- votekick.end(S_RESULT_CANCELLED)
- @name('y')
- def vote_yes(connection):
- protocol = connection.protocol
- if connection not in protocol.players:
- raise KeyError()
- player = connection
- votekick = protocol.votekick
- if not votekick:
- return S_NO_VOTEKICK
- votekick.vote(player)
- @alias('tvk')
- @admin
- def togglevotekick(connection, *args):
- protocol = connection.protocol
- if len(args) == 0:
- protocol.votekick_enabled = not protocol.votekick_enabled
- return "Votekicking globally %s." % ['disabled', 'enabled'][protocol.votekick_enabled]
- try:
- player = get_player(protocol, '#' + args[0])
- except InvalidPlayer:
- player = get_player(protocol, args[0])
- player.votekick_enabled = not player.votekick_enabled
- return "Votekicking is %s for %s." % (['disabled', 'enabled'][player.votekick_enabled], player.name)
- add(start_votekick)
- add(cancel_votekick)
- add(vote_yes)
- add(togglevotekick)
- class Votekick(object):
- duration = 120.0 # 2 minutes
- interval = 2 * 60.0 # 3 minutes
- ban_duration = 15.0
- public_votes = True
- schedule = None
- def _get_votes_remaining(self):
- return self.protocol.get_required_votes() - len(self.votes) + 1
- votes_remaining = property(_get_votes_remaining)
- @classmethod
- def start(cls, instigator, victim, reason = None):
- protocol = instigator.protocol
- last_votekick = instigator.last_votekick
- reason = reason.strip() if reason else None
- if protocol.votekick:
- raise VotekickFailure(S_IN_PROGRESS)
- elif instigator is victim:
- raise VotekickFailure(S_SELF_VOTEKICK)
- elif protocol.get_required_votes() <= 0:
- raise VotekickFailure(S_NOT_ENOUGH_PLAYERS)
- elif victim.admin or victim.rights.cancel:
- raise VotekickFailure(S_VOTEKICK_IMMUNE)
- elif not instigator.admin and (last_votekick is not None and
- seconds() - last_votekick < cls.interval):
- raise VotekickFailure(S_NOT_YET)
- elif REQUIRE_REASON and not reason:
- raise VotekickFailure(S_NEED_REASON)
- result = protocol.on_votekick_start(instigator, victim, reason)
- if result is not None:
- raise VotekickFailure(result)
- reason = reason or S_DEFAULT_REASON
- return cls(instigator, victim, reason)
- def __init__(self, instigator, victim, reason):
- self.protocol = protocol = instigator.protocol
- self.instigator = instigator
- self.victim = victim
- self.reason = reason
- self.votes = {instigator : True}
- self.ended = False
- protocol.irc_say(S_ANNOUNCE_IRC.format(instigator = instigator.name,
- victim = victim.name, reason = self.reason))
- protocol.send_chat(S_ANNOUNCE.format(instigator = instigator.name,
- victim = victim.name), sender = instigator)
- protocol.send_chat(S_REASON.format(reason = self.reason),
- sender = instigator)
- instigator.send_chat(S_ANNOUNCE_SELF.format(victim = victim.name))
- schedule = Scheduler(protocol)
- schedule.call_later(self.duration, self.end, S_RESULT_TIMED_OUT)
- schedule.loop_call(30.0, self.send_chat_update)
- self.schedule = schedule
- def vote(self, player):
- if self.victim is player:
- return
- elif player in self.votes:
- return
- if self.public_votes:
- self.protocol.send_chat(S_YES.format(player = player.name))
- self.votes[player] = True
- if self.votes_remaining <= 0:
- # vote passed, ban or kick accordingly
- victim = self.victim
- self.end(S_RESULT_PASSED)
- print '%s votekicked' % victim.name
- if self.ban_duration > 0.0:
- victim.ban(self.reason, self.ban_duration)
- else:
- victim.kick(silent = True)
- def release(self):
- self.instigator = None
- self.victim = None
- self.votes = None
- if self.schedule:
- self.schedule.reset()
- self.schedule = None
- self.protocol.votekick = None
- def end(self, result):
- self.ended = True
- message = S_ENDED.format(victim = self.victim.name, result = result)
- self.protocol.send_chat(message, irc = True)
- if not self.instigator.admin:
- self.instigator.last_votekick = seconds()
- self.protocol.on_votekick_end()
- self.release()
- def send_chat_update(self, target = None):
- # send only to target player if provided, otherwise broadcast to server
- target = target or self.protocol
- target.send_chat(S_UPDATE.format(instigator = self.instigator.name,
- victim = self.victim.name, needed = self.votes_remaining))
- target.send_chat(S_REASON.format(reason = self.reason))
- def apply_script(protocol, connection, config):
- Votekick.ban_duration = config.get('votekick_ban_duration', 15.0)
- Votekick.public_votes = config.get('votekick_public_votes', True)
- required_percentage = config.get('votekick_percentage', 25.0)
- class VotekickProtocol(protocol):
- votekick = None
- votekick_enabled = True
- def get_required_votes(self):
- # votekicks are invalid if this returns <= 0
- player_count = sum(not player.disconnected for player in
- self.players.itervalues()) - 1
- return int(player_count / 100.0 * required_percentage)
- def on_map_leave(self):
- if self.votekick:
- self.votekick.release()
- protocol.on_map_leave(self)
- def on_ban(self, banee, reason, duration):
- votekick = self.votekick
- if votekick and votekick.victim is self:
- votekick.end(S_RESULT_BANNED)
- protocol.on_ban(self, connection, reason, duration)
- def on_votekick_start(self, instigator, victim, reason):
- pass
- def on_votekick_end(self):
- pass
- class VotekickConnection(connection):
- last_votekick = None
- votekick_enabled = True
- def on_disconnect(self):
- votekick = self.protocol.votekick
- if votekick:
- if votekick.victim is self:
- # victim leaves, gets votekick ban
- reason = votekick.reason
- votekick.end(S_RESULT_LEFT.format(victim = self.name))
- self.ban(reason, Votekick.ban_duration)
- elif votekick.instigator is self:
- # instigator leaves, votekick is called off
- s = S_RESULT_INSTIGATOR_LEFT.format(instigator = self.name)
- votekick.end(s)
- else:
- # make sure we still have enough players
- votekick.votes.pop(self, None)
- if votekick.votes_remaining <= 0:
- votekick.end(S_NOT_ENOUGH_PLAYERS)
- connection.on_disconnect(self)
- def kick(self, reason = None, silent = False):
- votekick = self.protocol.votekick
- if votekick:
- if votekick.victim is self:
- votekick.end(S_RESULT_KICKED)
- elif votekick.instigator is self:
- votekick.end(S_RESULT_INSTIGATOR_KICKED)
- connection.kick(self, reason, silent)
- return VotekickProtocol, VotekickConnection
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement