The Problem with Votekick

The original, free Ace of Spades game powered by the Voxlap engine. Known as “Classic,” 0.75, 0.76, and all 0.x versions. Created by Ben Aksoy.
68 posts Page 5 of 5 First unread post

Should server owners reduce the amount of votes required to successfully pass a votekick?

38%
Yes
18
15%
No, it is fine as is
7
48%
No, it is a problem with the community, not the script
23

Total votes: 48

rakiru
Coder
Coder
Posts: 1349
Joined: Sun Nov 11, 2012 12:26 pm


Bigcheecho wrote:
rakiru wrote:
Bigcheecho wrote:
Oh, OK. I was taught that >4 meant to round up. Sorry then, Reki.
The usual way is >= .5 is up, although you can also take the ceiling (> 0) or floor (< 1) of the number too.
And I assume that's how it's programmed on most scripting languages, which is fine with me.
Thank god this didn't turn into a flame war about the rules of rounding, or then Dan would have to lock this thread, and I wouldn't be able to make more mathematical graphs for other suggested mathematical solutions for votekicking. Blue_Happy1
Haha
In programming, it tends to be either floor or nearest int, depending on the situation/laziness of the programmer. Ceiling tends to be rarely used.
TheSifodias
Modder
Modder
Posts: 108
Joined: Fri Nov 23, 2012 9:29 pm


rakiru wrote:
Ok, I haven't tested this as I currently lack the ability to, but this should work, and if it doesn't, then hopefully only a couple of small bugs need fixed:
Code: Select all
# This is based on the original votekick.py script as I don't have the
# luxury of a large server to test on, so I'll keep the same license.
# Oh, and since pyspades/pysnip is under the GPL, this is required to be
# under the same license, because it's cancerous like that. Anyway,
# original license below:

    # Copyright (c) James Hofmann 2012.

    # 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/>.

# Modifications copyright (c) Sean Gordon 2013.
# Also, 80-char line length limits are stupid - are we living in the 80s?

from twisted.internet.reactor import seconds
from scheduler import Scheduler
from commands import name, add, get_player, InvalidPlayer

######################
# OPTIONS & MESSAGES #
######################
# Note: You should change these options in the config if possible. These are defaults!
O_REQUIRE_REASON = True
# If a player starts a bad votekick and the threshold (as defined below) of negative votes is met, kick that player
O_KICK_INSTIGATOR = True
# Broadcast votes in chat?
O_SHOW_VOTES = True
O_VOTE_DURATION = 2 * 60 # In seconds
O_VOTE_COOLDOWN = 3 * 60 # In seconds
O_BAN_TIME = 15 # In minutes, set to 0 to kick without banning
O_CHAT_UPDATE_TIME = 30 # In seconds
# Formula for calculating required votes: ROUND(MULTIPLIER * TOTAL_PLAYERS + CONSTANT) where ROUND() rounds to nearest whole number
O_FORMULA_MULTIPLIER = 1/4
O_FORMULA_CONSTANT = 3
# The count can be limited at TOTAL_PLAYERS - 1 to prevent impossible votekick scenarios (e.g. 3 players online, 4 votes required)
O_CAP_REQUIRED_VOTES = True
# Disallow voting if there are less than a certain number of players
O_MINIMUM_PLAYERS = 3

# The only messages you're likely to want to change, if any, are the first two
S_DEFAULT_REASON = "NO REASON GIVEN, BECAUSE I'M LAZY"
S_BAD_VOTEKICK_REASON = "INSTIGATED A BAD VOTEKICK"
S_NO_VOTEKICK = "No votekick in progress"
S_IN_PROGRESS = "Votekick already in progress"
S_SELF_VOTEKICK = "You can't votekick yourself"
S_IMMUNE = "This player can't be votekicked"
S_COOLING_DOWN = "You can't start another votekick yet"
S_REASON_REQUIRED = "You must provide a reason for the votekick"
S_NOT_ENOUGH_PLAYERS = "There aren't enough players to vote"
S_DID_NOT_START = "You didn't start this votekick"
S_VOTE_YES = "{player} voted YES"
S_VOTE_NO = "{player} voted NO"
S_ENDED = "Votekick for {target} has ended. {result}"
S_RESULT_TIMED_OUT = 'Votekick timed out'
S_RESULT_CANCELLED = 'Votekick cancelled'
S_RESULT_BANNED = "Banned by admin"
S_RESULT_KICKED = "Kicked by admin"
S_RESULT_INSTIGATOR_KICKED = "Instigator kicked by admin"
S_RESULT_TARGET_LEFT = "Target {target} left during votekick"
S_RESULT_INSTIGATOR_LEFT = "Instigator {instigator} left during votekick"
S_RESULT_INSTIGATOR_BANNED = "Instigator {instigator} kicked"
S_RESULT_TARGET_BANNED = 'Player {target} kicked'
S_ANNOUNCE = '{instigator} started a VOTEKICK against {target} - Vote by using /Y or /N'
S_ANNOUNCE_SELF = "You started a votekick against {target} - Say /CANCEL to stop it"
S_ANNOUNCE_IRC = "* {instigator} started a votekick against player {target} - Reason: {reason}"
S_UPDATE = '{instigator} is votekicking {target} - Vote by using /Y or /N..'
S_REASON = 'Reason: {reason}'


class VotekickException(Exception):
    pass

############
# COMMANDS #
############

@name('votekick')
def start_votekick(connection, *args):
    protocol = connection.protocol
    if connection not in protocol.players:
        raise KeyError()
    player = connection
    
    if not args:
        if protocol.votekick:
            # player requested votekick info
            protocol.votekick.send_chat_update(player)
            return
        else:
			raise ValueError()
    
    value = args[0]
    try:
        target = get_player(protocol, '#' + value)
    except InvalidPlayer:
        target = get_player(protocol, value)
    reason = " ".join(args[1:])
    
    try:
        votekick = Votekick.start(player, target, reason)
        protocol.votekick = votekick
    except VotekickException as err:
        return str(err)


@name('cancel')
def cancel_votekick(connection):
    protocol = connection.protocol
    votekick = protocol.votekick
    if not votekick:
        return S_DID_NOT_START
    if connection in protocol.players:
        player = connection
        if player is votekick.instigator or player.admin or player.rights.cancel:
			#TODO: Check if instigator is losing vote, so they don't cancel it just to stop themselves being kicked
			votekick.end(S_RESULT_CANCELLED)
        else:
            return S_CANT_CANCEL


@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
    else:
		votekick.vote(player, True)


@name('n')
def vote_no(connection):
    protocol = connection.protocol
    if connection not in protocol.players:
        raise KeyError()
    player = connection
    
    votekick = protocol.votekick
    if not votekick:
        return S_NO_VOTEKICK
    else:
		votekick.vote(player, False)


add(start_votekick)
add(cancel_votekick)
add(vote_yes)
add(vote_no)


class Votekick(object):
    schedule = None
    
    def _get_votes_remaining(self):
        return self.protocol.get_required_votes() - len([a for a,b in d.iteritems() if b]) + 1
    def _get_votes_remaining_no(self):
        return self.protocol.get_required_votes() - len([a for a,b in d.iteritems() if not b) + 1
    votes_remaining = property(_get_votes_remaining)
    votes_remaining_no = property(_get_votes_remaining_no)
    
    @classmethod
    def start(cls, instigator, target, reason = None):
        protocol = instigator.protocol
        last_votekick = instigator.last_votekick
        if reason then:
			reason = reason.strip()
			
        if protocol.votekick:
            raise VotekickException(S_IN_PROGRESS)
        elif instigator is target:
            raise VotekickException(S_SELF_VOTEKICK)
        elif not protocol.is_enough_players():
            raise VotekickException(S_NOT_ENOUGH_PLAYERS)
        elif target.admin or target.rights.cancel:
            raise VotekickException(S_IMMUNE)
        elif not instigator.admin and (last_votekick is not None and seconds() - last_votekick < cls.interval):
            raise VotekickException(S_COOLING_DOWN)
        elif self.require_reason and not reason:
            raise VotekickException(S_REASON_REQUIRED)
        
        result = protocol.on_votekick_start(instigator, target, reason)
        if result is not None:
            raise VotekickException(result)
        
        reason = reason or S_DEFAULT_REASON
        return cls(instigator, target, reason)
    
    def __init__(self, instigator, target, reason):
        self.protocol = protocol = instigator.protocol
        self.instigator = instigator
        self.target = target
        self.reason = reason
        self.votes = {instigator : True}
        self.ended = False
        
        protocol.irc_say(S_ANNOUNCE_IRC.format(instigator = instigator.name,
            target = target.name, reason = self.reason))
        protocol.send_chat(S_ANNOUNCE.format(instigator = instigator.name,
            target = target.name), sender = instigator)
        protocol.send_chat(S_REASON.format(reason = self.reason),
            sender = instigator)
        instigator.send_chat(S_ANNOUNCE_SELF.format(target = target.name))
        
        schedule = Scheduler(protocol)
        schedule.call_later(self.duration, self.end, S_RESULT_TIMED_OUT)
        schedule.loop_call(self.chat_update_time, self.send_chat_update)
        self.schedule = schedule
    
    def vote(self, player, vote):
        if self.target is player:
            return
        if self.public_votes:
            self.protocol.send_chat((S_VOTE_YES if vote else S_VOTE_NO).format(player = player.name))
        self.votes[player] = vote
        if self.votes_remaining <= 0:
            # vote passed, ban or kick accordingly
            target = self.target
            self.end(S_RESULT_TARGET_BANNED)
            print '%s votekicked' % target.name
            if self.ban_duration > 0.0:
                target.ban(self.reason, self.ban_duration)
            else:
                target.kick(silent = True)
        elif self.votes_remaining_no <= 0:
            # vote failed badly, ban or kick accordingly
            target = self.instigator
            self.end(S_RESULT_INSTIGATOR_BANNED
            print '%s votekicked' % target.name
            if self.ban_duration > 0.0:
                target.ban(S_BAD_VOTEKICK_REASON, self.ban_duration)
            else:
                target.kick(silent = True)
    
    def release(self):
        self.instigator = None
        self.target = 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(target = self.target.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, target = self.target.name, needed = self.votes_remaining))
        target.send_chat(S_REASON.format(reason = self.reason))


def apply_script(protocol, connection, config):
	# Using the same config keys as the other version where possible
    Votekick.require_reason   = config.get('votekick_require_reason',     O_REQUIRE_REASON)
    Votekick.kick_instigator  = config.get('votekick_kick_instigator',    O_KICK_INSTIGATOR)
    Votekick.show_votes       = config.get('votekick_public_votes',       O_SHOW_VOTES)
    Votekick.vote_duration    = config.get('votekick_vote_duration',      O_VOTE_DURATION)
    Votekick.cooldown         = config.get('votekick_cooldown',           O_VOTE_COOLDOWN)
    Votekick.ban_duration     = config.get('votekick_ban_duration',       O_BAN_TIME)
    Votekick.chat_update_time = config.get('votekick_chat_update_time',   O_CHAT_UPDATE_TIME)
    formula_multiplier        = config.get('votekick_formula_multiplier', O_FORMULA_MULTIPLIER)
    formula_constant          = config.get('votekick_formula_constant',   O_FORMULA_CONSTANT)
    cap_required_votes        = config.get('votekick_cap_required_votes', O_CAP_REQUIRED_VOTES)
    minimum_players           = config.get('votekick_minimum_players',    O_MINIMUM_PLAYERS)
    
    class VotekickProtocol(protocol):
        votekick = None
        
        def get_player_count(self):
            return sum(not player.disconnected for player in self.players.itervalues())
        
        def is_enough_players(self):
            return self.get_player_count() >= self.minimum_players
        
        def get_required_votes(self):
			player_count = self.get_player_count
            threshold = self.formula_multiplier * player_count + self.formula_constant
            if self.cap_required_votes and threshold > player_count - 1:
				threshold = player_count - 1
        
        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.target is self:
                votekick.end(S_RESULT_BANNED)
            protocol.on_ban(self, connection, reason, duration)
        
        def on_votekick_start(self, instigator, target, reason):
            pass
        
        def on_votekick_end(self):
            pass
    
    class VotekickConnection(connection):
        last_votekick = None
        
        def on_disconnect(self):
            votekick = self.protocol.votekick
            if votekick:
                if votekick.target is self:
                    # target leaves, gets votekick ban
                    reason = votekick.reason
                    votekick.end(S_RESULT_TARGET_LEFT.format(target = 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.target 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
Edit: It's also possible that I've missed something crucial and not realised.
Anyone willing to test this? I think it's a good idea.
Reki
Deuced Up
Posts: 106
Joined: Fri Nov 09, 2012 11:07 pm


I was thinking of using floor instead of round, but 4.75 really should be 5 and not 4 :P
Looking at the graph, it's still somewhat linear, though with the constant shift there's a nice leveling effect. Maybe we should do a power function instead?
izzy
Head Admin / Co-founder
Head Admin / Co-founder
Posts: 474
Joined: Tue Oct 09, 2012 8:16 pm


rakiru wrote:
Ok, I haven't tested this as I currently lack the ability to, but this should work, and if it doesn't, then hopefully only a couple of small bugs need fixed:

...

Edit: It's also possible that I've missed something crucial and not realised.
I had to fix some syntax and indent errors to get it to run. Fixed version below:
Code: Select all
# This is based on the original votekick.py script as I don't have the
# luxury of a large server to test on, so I'll keep the same license.
# Oh, and since pyspades/pysnip is under the GPL, this is required to be
# under the same license, because it's cancerous like that. Anyway,
# original license below:

    # Copyright (c) James Hofmann 2012.

    # 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/>.

# Modifications copyright (c) Sean Gordon 2013.
# Also, 80-char line length limits are stupid - are we living in the 80s?

from twisted.internet.reactor import seconds
from scheduler import Scheduler
from commands import name, add, get_player, InvalidPlayer

######################
# OPTIONS & MESSAGES #
######################
# Note: You should change these options in the config if possible. These are defaults!
O_REQUIRE_REASON = True
# If a player starts a bad votekick and the threshold (as defined below) of negative votes is met, kick that player
O_KICK_INSTIGATOR = True
# Broadcast votes in chat?
O_SHOW_VOTES = True
O_VOTE_DURATION = 2 * 60 # In seconds
O_VOTE_COOLDOWN = 3 * 60 # In seconds
O_BAN_TIME = 15 # In minutes, set to 0 to kick without banning
O_CHAT_UPDATE_TIME = 30 # In seconds
# Formula for calculating required votes: ROUND(MULTIPLIER * TOTAL_PLAYERS + CONSTANT) where ROUND() rounds to nearest whole number
O_FORMULA_MULTIPLIER = 1/4
O_FORMULA_CONSTANT = 3
# The count can be limited at TOTAL_PLAYERS - 1 to prevent impossible votekick scenarios (e.g. 3 players online, 4 votes required)
O_CAP_REQUIRED_VOTES = True
# Disallow voting if there are less than a certain number of players
O_MINIMUM_PLAYERS = 3

# The only messages you're likely to want to change, if any, are the first two
S_DEFAULT_REASON = "NO REASON GIVEN, BECAUSE I'M LAZY"
S_BAD_VOTEKICK_REASON = "INSTIGATED A BAD VOTEKICK"
S_NO_VOTEKICK = "No votekick in progress"
S_IN_PROGRESS = "Votekick already in progress"
S_SELF_VOTEKICK = "You can't votekick yourself"
S_IMMUNE = "This player can't be votekicked"
S_COOLING_DOWN = "You can't start another votekick yet"
S_REASON_REQUIRED = "You must provide a reason for the votekick"
S_NOT_ENOUGH_PLAYERS = "There aren't enough players to vote"
S_DID_NOT_START = "You didn't start this votekick"
S_VOTE_YES = "{player} voted YES"
S_VOTE_NO = "{player} voted NO"
S_ENDED = "Votekick for {target} has ended. {result}"
S_RESULT_TIMED_OUT = 'Votekick timed out'
S_RESULT_CANCELLED = 'Votekick cancelled'
S_RESULT_BANNED = "Banned by admin"
S_RESULT_KICKED = "Kicked by admin"
S_RESULT_INSTIGATOR_KICKED = "Instigator kicked by admin"
S_RESULT_TARGET_LEFT = "Target {target} left during votekick"
S_RESULT_INSTIGATOR_LEFT = "Instigator {instigator} left during votekick"
S_RESULT_INSTIGATOR_BANNED = "Instigator {instigator} kicked"
S_RESULT_TARGET_BANNED = 'Player {target} kicked'
S_ANNOUNCE = '{instigator} started a VOTEKICK against {target} - Vote by using /Y or /N'
S_ANNOUNCE_SELF = "You started a votekick against {target} - Say /CANCEL to stop it"
S_ANNOUNCE_IRC = "* {instigator} started a votekick against player {target} - Reason: {reason}"
S_UPDATE = '{instigator} is votekicking {target} - Vote by using /Y or /N..'
S_REASON = 'Reason: {reason}'


class VotekickException(Exception):
    pass

############
# COMMANDS #
############

@name('votekick')
def start_votekick(connection, *args):
    protocol = connection.protocol
    if connection not in protocol.players:
        raise KeyError()
    player = connection
    
    if not args:
        if protocol.votekick:
            # player requested votekick info
            protocol.votekick.send_chat_update(player)
            return
        else:
			raise ValueError()
    
    value = args[0]
    try:
        target = get_player(protocol, '#' + value)
    except InvalidPlayer:
        target = get_player(protocol, value)
    reason = " ".join(args[1:])
    
    try:
        votekick = Votekick.start(player, target, reason)
        protocol.votekick = votekick
    except VotekickException as err:
        return str(err)


@name('cancel')
def cancel_votekick(connection):
    protocol = connection.protocol
    votekick = protocol.votekick
    if not votekick:
        return S_DID_NOT_START
    if connection in protocol.players:
        player = connection
        if player is votekick.instigator or player.admin or player.rights.cancel:
			#TODO: Check if instigator is losing vote, so they don't cancel it just to stop themselves being kicked
			votekick.end(S_RESULT_CANCELLED)
        else:
            return S_CANT_CANCEL


@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
    else:
		votekick.vote(player, True)


@name('n')
def vote_no(connection):
    protocol = connection.protocol
    if connection not in protocol.players:
        raise KeyError()
    player = connection
    
    votekick = protocol.votekick
    if not votekick:
        return S_NO_VOTEKICK
    else:
		votekick.vote(player, False)


add(start_votekick)
add(cancel_votekick)
add(vote_yes)
add(vote_no)


class Votekick(object):
    schedule = None
    
    def _get_votes_remaining(self):
        return self.protocol.get_required_votes() - len([a for a,b in d.iteritems() if b]) + 1
    def _get_votes_remaining_no(self):
        return self.protocol.get_required_votes() - len([a for a,b in d.iteritems() if not b]) + 1
    votes_remaining = property(_get_votes_remaining)
    votes_remaining_no = property(_get_votes_remaining_no)
    
    @classmethod
    def start(cls, instigator, target, reason = None):
        protocol = instigator.protocol
        last_votekick = instigator.last_votekick
        if reason:
			reason = reason.strip()
			
        if protocol.votekick:
            raise VotekickException(S_IN_PROGRESS)
        elif instigator is target:
            raise VotekickException(S_SELF_VOTEKICK)
        elif not protocol.is_enough_players():
            raise VotekickException(S_NOT_ENOUGH_PLAYERS)
        elif target.admin or target.rights.cancel:
            raise VotekickException(S_IMMUNE)
        elif not instigator.admin and (last_votekick is not None and seconds() - last_votekick < cls.interval):
            raise VotekickException(S_COOLING_DOWN)
        elif self.require_reason and not reason:
            raise VotekickException(S_REASON_REQUIRED)
        
        result = protocol.on_votekick_start(instigator, target, reason)
        if result is not None:
            raise VotekickException(result)
        
        reason = reason or S_DEFAULT_REASON
        return cls(instigator, target, reason)
    
    def __init__(self, instigator, target, reason):
        self.protocol = protocol = instigator.protocol
        self.instigator = instigator
        self.target = target
        self.reason = reason
        self.votes = {instigator : True}
        self.ended = False
        
        protocol.irc_say(S_ANNOUNCE_IRC.format(instigator = instigator.name,
            target = target.name, reason = self.reason))
        protocol.send_chat(S_ANNOUNCE.format(instigator = instigator.name,
            target = target.name), sender = instigator)
        protocol.send_chat(S_REASON.format(reason = self.reason),
            sender = instigator)
        instigator.send_chat(S_ANNOUNCE_SELF.format(target = target.name))
        
        schedule = Scheduler(protocol)
        schedule.call_later(self.duration, self.end, S_RESULT_TIMED_OUT)
        schedule.loop_call(self.chat_update_time, self.send_chat_update)
        self.schedule = schedule
    
    def vote(self, player, vote):
        if self.target is player:
            return
        if self.public_votes:
            self.protocol.send_chat((S_VOTE_YES if vote else S_VOTE_NO).format(player = player.name))
        self.votes[player] = vote
        if self.votes_remaining <= 0:
            # vote passed, ban or kick accordingly
            target = self.target
            self.end(S_RESULT_TARGET_BANNED)
            print '%s votekicked' % target.name
            if self.ban_duration > 0.0:
                target.ban(self.reason, self.ban_duration)
            else:
                target.kick(silent = True)
        elif self.votes_remaining_no <= 0:
            # vote failed badly, ban or kick accordingly
            target = self.instigator
            self.end(S_RESULT_INSTIGATOR_BANNED)
            print '%s votekicked' % target.name
            if self.ban_duration > 0.0:
                target.ban(S_BAD_VOTEKICK_REASON, self.ban_duration)
            else:
                target.kick(silent = True)
    
    def release(self):
        self.instigator = None
        self.target = 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(target = self.target.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, target = self.target.name, needed = self.votes_remaining))
        target.send_chat(S_REASON.format(reason = self.reason))


def apply_script(protocol, connection, config):
	# Using the same config keys as the other version where possible
    Votekick.require_reason   = config.get('votekick_require_reason',     O_REQUIRE_REASON)
    Votekick.kick_instigator  = config.get('votekick_kick_instigator',    O_KICK_INSTIGATOR)
    Votekick.show_votes       = config.get('votekick_public_votes',       O_SHOW_VOTES)
    Votekick.vote_duration    = config.get('votekick_vote_duration',      O_VOTE_DURATION)
    Votekick.cooldown         = config.get('votekick_cooldown',           O_VOTE_COOLDOWN)
    Votekick.ban_duration     = config.get('votekick_ban_duration',       O_BAN_TIME)
    Votekick.chat_update_time = config.get('votekick_chat_update_time',   O_CHAT_UPDATE_TIME)
    formula_multiplier        = config.get('votekick_formula_multiplier', O_FORMULA_MULTIPLIER)
    formula_constant          = config.get('votekick_formula_constant',   O_FORMULA_CONSTANT)
    cap_required_votes        = config.get('votekick_cap_required_votes', O_CAP_REQUIRED_VOTES)
    minimum_players           = config.get('votekick_minimum_players',    O_MINIMUM_PLAYERS)
    
    class VotekickProtocol(protocol):
        votekick = None
        
        def get_player_count(self):
            return sum(not player.disconnected for player in self.players.itervalues())
        
        def is_enough_players(self):
            return self.get_player_count() >= self.minimum_players
        
        def get_required_votes(self):
            player_count = self.get_player_count
            threshold = self.formula_multiplier * player_count + self.formula_constant
            if self.cap_required_votes and threshold > player_count - 1:
                threshold = player_count - 1
        
        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.target is self:
                votekick.end(S_RESULT_BANNED)
            protocol.on_ban(self, connection, reason, duration)
        
        def on_votekick_start(self, instigator, target, reason):
            pass
        
        def on_votekick_end(self):
            pass
    
    class VotekickConnection(connection):
        last_votekick = None
        
        def on_disconnect(self):
            votekick = self.protocol.votekick
            if votekick:
                if votekick.target is self:
                    # target leaves, gets votekick ban
                    reason = votekick.reason
                    votekick.end(S_RESULT_TARGET_LEFT.format(target = 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.target 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
Testing this out with your defaults on aloha.pk ctf 24/7 classicgen right now. To everyone who plays on this server, please report any problems with votekick here.
Sasquatch
Mapper
Mapper
Posts: 141
Joined: Wed Nov 14, 2012 5:18 pm


Well, alright Izzy I agree with you now. I had my server run for a few hours and I saw votekick getting abused >.> So I turned it back up.
rakiru
Coder
Coder
Posts: 1349
Joined: Sun Nov 11, 2012 12:26 pm


izzy wrote:
Testing this out with your defaults on aloha.pk ctf 24/7 classicgen right now. To everyone who plays on this server, please report any problems with votekick here.
*crosses fingers*
TheSifodias
Modder
Modder
Posts: 108
Joined: Fri Nov 23, 2012 9:29 pm


Aloha Classicgen really isn't popular enough to give a good count. You may want to try hallway.
Bigcheecho
Build and Shoot's 1st Birthday
Build and Shoot's 1st Birthday
Posts: 582
Joined: Sat Dec 22, 2012 3:43 pm


Reki wrote:
I was thinking of using floor instead of round, but 4.75 really should be 5 and not 4 :P
Looking at the graph, it's still somewhat linear, though with the constant shift there's a nice leveling effect. Maybe we should do a power function instead?
I made this on an online graphing calculator so you can muck around with the c and a values until you get the desired effect.
Image
Modified original quote for emphasis on last sentence.
68 posts Page 5 of 5 First unread post
Return to “Ace of Spades 0.x Discussion”

Who is online

Users browsing this forum: No registered users and 22 guests