"""
The Ace
inspired by gamemode in Metroid Prime Hunters
by Desert Storm
"""

#You may modify this and post it elsewhere, however please leave the by Desert
#Storm part alone

#To get this working just set the game mode to "The Ace" don't include as
#a script
#I made this very dynamic so you can customize almost every attribute.
#Here is what you map.txt and/or config file can have included.
#Please note that anything that is in the map.txt file will override anything in the config file

#All options that are not map specific can be placed in your config file like so.
#"TheAce" : {
#   stuff goes in here
#}

#'ta':value not used(just use 0)
#if ALWS_ENBL is set to true BAM, The Ace is enabled. If it has this
#extension no matter what the value may be it is enabled

#'ta-heal':#>=0
#this is the amount of health that the ace gets when they kill someone.
#it can not be a negative value

#'ta-hit':#>=1
#this is the amount of damage that is dealt to The Ace when a clock interval is
#completed. The interval is explained below

#'ta-hit-interval':#>=1
#this is the amount of time in seconds that it takes for the ace to take passive damage

#'ta-damage-table':(#,#,#,#,#, #,#,#,#,#)>=1
#this is the damage table the values are Rifle, Smg, Shotgun, Grenade,
#and Spade in that order.
#the first five numbers are the multipliers applied to tha Aces' damage.
#the last five are the divisors for the damage that the ace takes.
#if a value is less than 1 it stays at the defualt

#'ta-score-table':(#,#,#,#)>=1
#this is the score table.the first number is a standard player on player kill.
#the second value when a player kills the Ace. thethird is when the ace kills
#a player. Finally the last value is the score that the ace gains after every
#hit interval

#'ta-max-score':#>=50
#this is the score that players must reach to win.

#'ta-build':True/False
#sets whether players are allowed to build/destroy blocks or not. Defaults to True

#'ta-spawns':[(x,y,z),(x1,y1,x2,y2),(x,y)]
#map specific
#If you want, you can define a set of spawn points which will be picked at random when a player spawns.
#There are three types of spawn points that you can define. Remember that y in AoS is not elevation.
#1. (x1, y1, x2, y2) - This will pick a random non-water location in the range of (x1,y1)-(x2,y2)
#2. (x, y, z) - This will just plop that player at the location
#3. (x, y) - If you just want the player to spawn at the highest point then use this. Also save space

#'ta-cp-spawns':[(x,y,z),(x,y,z)]
#map specific
#This is a list which must include two(2) pairs of xyz coordinates for the green and blue base respecively

#'ta-top-interval':#>=30 or #=0
#The time in seconds that players will be notified of the top scores.
#If zero(0) then the automated scoreboard message will be disabled

#'ta-top-num':#>0
#This is the number of players that are displayed by the automatic scoreboard or when a player calls 'ta-scores'

from operator import itemgetter
import random
from twisted.internet.task import LoopingCall
from pyspades.server import Flag, Base
from pyspades.constants import CTF_MODE, MELEE_KILL, GRENADE_KILL, SPADE_DESTROY, BLUE_BASE, GREEN_BASE
from pyspades.collision import vector_collision
from commands import get_player, alias, name, admin, add

HIDE_COORD = (0, 0, 0)
ALWS_ENBL = False
MSG_TA_ACE = 'the ace is {ta}'
MSG_TA_ACE_SELF = 'You have lost the ability to build or destroy blocks'
MSG_TA_NO_ACE = 'No one is the Ace'
MSG_TA_DEAD = 'The Ace is dead.'
MSG_TA_KILLED = '{player} is the new Ace.'
MSG_TA_TOP = '{rank}. {player} with a score of {score}'
MSG_TA_YOU = 'You have a score of {score} and are in position {rank}'
MSG_TA_SWITCH = 'You cant switch {switch} while you are the Ace'
MSG_TA_REFILL = 'You cant get health from base as the Ace. Kill someone to heal'
MSG_TA_WON = '{player} has won the Match'
MSG_TA_EXPLAIN = ['If there is no Ace kill someone to gain the status.',
                  'if there is an Ace kill them.',
                  'While you are the ace you take damage over time, but recieve a damage boost/resistance.',
                  'Reach {score} to win!']

@alias('tat')
@name('tatoggle')
@admin
def the_ace_toggle(connection):
    protocol = connection.protocol
    protocol.ta_enbl = not protocol.ta_enbl
    if protocol.ta_enbl:
        if protocol.ta_ace[0]:
            protocol.ta_hit_loop.start(protocol.ta_hit_interval, False)
        if protocol.ta_top_interval > 0:
            protocol.ta_top_loop.start(protocol.ta_top_interval, False)
        protocol.send_chat('The Ace match has been started')
    else:
        if protocol.ta_hit_loop.running:
            protocol.ta_hit_loop.stop()
        if protocol.ta_top_loop.running:
            protocol.ta_top_loop.stop();
        protocol.send_chat('The Ace match has been paused')
    connection.protocol.ta_reset_flags()
    return

@alias('tas')
@name('tascores')
def the_ace_scores(connection):
    protocol = connection.protocol
    scr = protocol.ta_scores[connection.name]
    scrs = protocol.ta_scores.items()
    scrs.sort(key=itemgetter(1), reverse=True)
    protocol.ta_top(connection)
    return MSG_TA_YOU.format(score = scr,
                             rank = scrs.index((connection.name,scr))+1)

@alias('tah')
@name('tahelp')
def the_ace_help(connection):
    msgs = []
    for i in MSG_TA_EXPLAIN:
        if i == MSG_TA_EXPLAIN[len(MSG_TA_EXPLAIN)-1]:
            msgs.append(i.format(score = connection.protocol.ta_score_max))
        else:
            msgs.append(i)
    connection.send_lines(msgs)
    return

@alias('ta')
@name('theace')
def the_ace(connection):
    protocol = connection.protocol
    msg = None
    if protocol.ta_ace[0]:
        msg = MSG_TA_ACE.format(ta = protocol.ta_ace[0])
    else:
        msg = MSG_TA_NO_ACE
    return msg

add(the_ace)
add(the_ace_help)
add(the_ace_scores)
add(the_ace_toggle)

def apply_script(protocol, connection, config):
    ta_config = config.get('TheAce', {})
    class tac(connection):

        def on_spawn_location(self, pos):
            protocol = self.protocol
            newPos = None
            if protocol.ta_spawns:
                spawn = random.choice(protocol.ta_spawns)
                if len(spawn) == 4:
                    newPos = protocol.get_random_location(True, spawn)
                elif len(spawn) == 2:
                    z = protocol.map.get_height(spawn[0], spawn[1])
                    newPos = (spawn[0], spawn[1], z);
                else:
                    newPos = spawn
            else:
                zne = None
                if protocol.ta_ace[0]:
                    player = get_player(protocol, '#' + str(protocol.ta_ace[1]), False)
                    loc = player.get_location()
                    zne = ((max(0, min(511,loc[0]-90))),
                           (max(0, min(511,loc[1]-90))),
                           (max(0, min(511,loc[0]+90))),
                           (max(0, min(511,loc[1]+90))))
                else:
                    zne = (0, 0, 511, 511)
                newPos = protocol.get_random_location(True, zne)
            return newPos

        def on_hit(self, damage, player, type, grenade):
            protocol = self.protocol
            if protocol.ta_enbl and player != self:
                weapon = self.weapon
                newDamage = damage
                if self.player_id == protocol.ta_ace[1]:
                    if type == GRENADE_KILL and grenade != None:
                        newDamage *= protocol.ta_damage_table[3]
                    elif type == MELEE_KILL:
                        newDamage *= protocol.ta_damage_table[4]
                    else:
                        newDamage *= protocol.ta_damage_table[weapon]
                elif player.player_id == protocol.ta_ace[1]:
                    if type == GRENADE_KILL and grenade != None:
                        newDamage /= protocol.ta_damage_table[8]
                    elif type == MELEE_KILL:
                        newDamage /= protocol.ta_damage_table[9]
                    else:
                        newDamage /= protocol.ta_damage_table[weapon+5]
                if connection.on_hit(self, newDamage, player, type, grenade) == False:
                    return False
                return newDamage
            return connection.on_hit(self, damage, player, type, grenade)

        def grenade_exploded(self, grenade):
            for player in self.team.get_players():
                if player == self:
                    continue

                damage = grenade.get_damage(player.world_object.position)
                if damage != 0:
                    returned = self.on_hit(damage, player, GRENADE_KILL, grenade)
                    if returned != False:
                        player.set_hp(player.hp - damage, self,
                            hit_indicator = grenade.position.get(), type = GRENADE_KILL,
                            grenade = grenade)
            return connection.grenade_exploded(self, grenade)

        def on_kill(self, killer, type, grenade):
            protocol = self.protocol
            if protocol.ta_enbl:
                if killer != self and killer != None:
                    if self.player_id == protocol.ta_ace[1]:
                        protocol.kill_ace(self, killer)
                        protocol.ta_scores[killer.name] += protocol.ta_score_table[1]
                    elif killer.player_id == protocol.ta_ace[1]:
                        protocol.ta_scores[killer.name] += protocol.ta_score_table[2]
                        hp = killer.hp + protocol.ta_heal
                        killer.set_hp(hp)
                    else:
                        if not protocol.ta_ace[0]:
                            protocol.kill_ace(self, killer)
                        protocol.ta_scores[killer.name] += protocol.ta_score_table[0]
                    killer.protocol.ta_check_score(killer)
                elif self.player_id == protocol.ta_ace[1]:
                    protocol.kill_ace(self)
            return connection.on_kill(self, killer, type, grenade)

        def on_login(self, name):
            self.protocol.ta_scores[name] = 0
            return connection.on_login(self, name)

        def on_weapon_set(self, value):
            if self.protocol.ta_enbl and self.player_id == self.protocol.ta_ace[1]:
                self.send_chat(MSG_TA_SWITCH.format(switch = 'weapons'))
                return False
            return connection.on_weapon_set(self, value)

        def on_team_join(self, team):
            if self.protocol.ta_enbl and self.player_id == self.protocol.ta_ace[1]:
                if team != self.team:
                    self.send_chat(MSG_TA_SWITCH.format(switch = 'teams'))
                    return False
            return connection.on_team_join(self, team)

        def on_block_build_attempt(self, x,y,z):
            if self.protocol.ta_enbl and (!self.protocol.ta_build or self.player_id == self.protocol.ta_ace[1]):
                if !self.protocol.ta_build:
                    self.send_chat('Building is disabled!')
                return False
            return connection.on_block_build_attempt(self, x,y,z)

        def on_block_destroy(self, x,y,z, mode):
            if self.protocol.ta_enbl and (!self.protocol.ta_build or self.player_id == self.protocol.ta_ace[1]):
                if !self.protocol.ta_build and mode == SPADE_DESTROY:
                    self.send_chat('Building is disabled!')
                return False
            return connection.on_block_destroy(self, x,y,z, mode)

        def on_refill(self):
            if self.protocol.ta_enbl and self.player_id == self.protocol.ta_ace[1]:
                self.send_chat(MSG_TA_REFILL)
                hp = self.hp
                self.refill()
                self.set_hp(hp)
                return False
            return connection.on_refill(self)

        def on_position_update(self):
            if vector_collision(self.world_object.position, self.team.other.base):
                self.check_refill()
            if self.protocol.ta_enbl:
                if self.player_id == self.protocol.ta_ace[1]:
                    team = self.team
                    loc = self.get_location()
                    loc = (loc[0], loc[1], 0)
                    team.flag.set(*loc)
                    team.flag.update()
            return connection.on_position_update(self)

        def on_flag_drop(self):
            self.protocol.ta_reset_flags()
            return connection.on_flag_drop(self)

        def on_flag_take(self):
            if self.protocol.ta_enbl and self.player_id != self.protocol.ta_ace[1]:
                return False
            return connection.on_flag_take(self)

        def on_flag_capture(self):
            if self.protocol.ta_enbl:
                if self.protocol.ta_scores[self.name] < self.protocol.ta_score_max:
                    return False
            return connection.on_flag_capture(self)

        def on_reset(self):
            if self.protocol.ta_enbl and self.player_id == self.protocol.ta_ace[1]:
                self.protocol.kill_ace(self)
            if self.name in self.protocol.ta_scores:
                del self.protocol.ta_scores[self.name]
            return connection.on_reset(self)

    class tap(protocol):
        game_mode = CTF_MODE
        ta_ace = [None, None]
        ta_enbl = False
        ta_hit = 5
        ta_hit_loop = None
        ta_hit_interval = 4
        ta_top_loop = None
        ta_top_interval = 90
        ta_top_num = 3
        ta_scores = {}
        ta_score_table = [1, 15, 5, 1]
        ta_damage_table = [1.4, 1, 1.3, 1.2, 2,  1.5, 1.5, 1.5, 2, 1.4]
        ta_score_max = 150
        ta_spawns = None
        ta_cp_spawns = None
        ta_build = True

        def ta_reset(self):
            for i in self.ta_scores:
                self.ta_scores[i] = 0
            if self.ta_hit_loop.running:
                self.ta_hit_loop.stop()
            self.ta_ace = [None, None]

        def ta_check_score(self, winner):
            if self.ta_scores[winner.name] >= self.ta_score_max:
                winner.team.score = self.max_score-1
                winner.team.other.flag.player = winner

                self.ta_ace = [winner.name, winner.player_id]
                winner.capture_flag()
                self.ta_reset()
                self.send_chat(MSG_TA_WON.format(player = winner.name))
            return

        def ta_reset_flags(self):
            if self.ta_enbl:
                flags = [self.green_team.flag, self.blue_team.flag]
                for i in flags:
                    i.set(*HIDE_COORD)
                    i.update()
            else:
                teams = [self.green_team, self.blue_team]
                for i in teams:
                    loc = i.get_random_location(True)
                    i.flag.set(*loc)
                    i.flag.update()
            return

        def ta_top(self, player = None):
            scores = self.ta_scores.items()
            scores.sort(key=itemgetter(1), reverse=True)
            for i in range(len(scores)):
                if i >= self.ta_top_num:
                    break
                if player:
                    player.send_chat(MSG_TA_TOP.format(player = scores[i][0],
                                                       rank = i+1, score = scores[i][1]))
                else:
                    self.send_chat(MSG_TA_TOP.format(player = scores[i][0],
                                                     rank = i+1, score = scores[i][1]))
            return

        def kill_ace(self, player, killer = None):
            if player.player_id == self.ta_ace[1]:
                player.drop_flag()
            if killer == None or killer == player:
                self.ta_ace = [None, None]
                self.send_chat(MSG_TA_DEAD)
                if self.ta_hit_loop.running:
                    self.ta_hit_loop.stop()
            else:
                self.ta_ace = [killer.name, killer.player_id]
                self.ta_scores[killer.name] += self.ta_score_table[1]
                self.send_chat(MSG_TA_KILLED.format(player = killer.name))
                killer.send_chat(MSG_TA_ACE_SELF)
                killer.take_flag()

                if self.ta_hit_loop.running:
                    self.ta_hit_loop.stop()
                self.ta_hit_loop.start(self.ta_hit_interval, False)

            return

        def hit_ace(self):
            if self.ta_ace[0]:
                player = get_player(self, "#" + str(self.ta_ace[1]), False)
                if player and player.name in self.ta_scores:
                    hp = player.hp
                    player.set_hp(hp - self.ta_hit, type = MELEE_KILL)
                    self.ta_scores[player.name] += self.ta_score_table[3]
            return

        def on_map_change(self, map):
            if not self.ta_hit_loop:
                self.ta_hit_loop = LoopingCall(self.hit_ace)
            if self.ta_hit_loop.running:
                self.ta_hit_loop.stop()

            if not self.ta_top_loop:
                self.ta_top_loop = LoopingCall(self.ta_top)
            if self.ta_top_loop.running:
                self.ta_top_loop.stop()

            ext = self.map_info.extensions
            self.ta_ace = [None, None]
            self.ta_scores.clear()
            self.ta_enbl = False
            self.ta_heal = 20
            self.ta_hit = 5
            self.ta_hit_interval = 4
            self.ta_top_interval = 90
            self.ta_top_num = 3
            self.ta_score_table = [1,15,5,1]
            self.ta_damage_table = [1.4,1,1.3,1.2,2, 1.5,1.5,1.5,2,1.4]
            self.ta_score_max = 150
            self.ta_spawns = None
            self.ta_cp_spawns = None
            self.ta_build = True

            if 'ta-hit' in ext:
                if ext['ta-hit'] >= 1:
                    self.ta_hit = ext['ta-hit']
            elif 'ta-hit' in ta_config:
                if ta_config['ta-hit'] >= 1:
                    self.ta_hit = ta_config['ta-hit']

            if 'ta-damage-table' in ext:
                for i in ext['ta-damage-table']:
                    if ext['ta-damage-table'][i] >= 1:
                        self.ta_damage_table[i] = ext['ta-damage-table'][i]
            elif 'ta-damage-table' in ta_config:
                for i in ta_config['ta-damage-table']:
                    if ta_config['ta-damage-table'][i] >= 1:
                        self.ta_damage_table[i] = ta_config['ta-damage-table'][i]

            if 'ta-heal' in ext:
                if ext['ta-heal'] >= 0:
                    self.ta_heal = ext['ta-heal']
            elif 'ta-heal' in ta_config:
                if ta_config['ta-heal'] >= 0:
                    self.ta_heal = ta_config['ta-heal']

            if 'ta-hit-interval' in ext:
                if ext['ta-hit-interval'] >= 1:
                    self.ta_hit_interval = ext['ta-hit-interval']
            elif 'ta-hit-interval' in ta_config:
                if ta_config['ta-hit-interval'] >= 1:
                    self.ta_hit_interval = ta_config['ta-hit-interval']

            if 'ta-score-table' in ext:
                for i in ext['ta-score-table']:
                    if ext['ta-score-table'][i] >= 1:
                        self.ta_score_table[i] = ext['ta-score-table'][i]
            elif 'ta-score-table' in ta_config:
                for i in ta_config['ta-score-table']:
                    if ta_config['ta-score-table'][i] >= 1:
                        self.ta_score_table[i] = ta_config['ta-score-table'][i]

            if 'ta-max-score' in ext:
                if ext['ta-max-score'] >= 50:
                    self.ta_score_max = ext['ta-max-score']
            elif 'ta-max-score' in ta_config:
                if ta_config['ta-max-score'] >= 50:
                    self.ta_score_max = ta_config['ta-max-score']

            if 'ta-build' in ext:
                self.ta_build = ext['ta-build']
            elif 'ta-build' in ta_config:
                self.ta_build = ta_config['ta-build']

            if 'ta-spawns' in ext:
                self.ta_spawns = ext['ta-spawns']

            if 'ta-cp-spawns' in ext:
                self.ta_cp_spawns = ext['ta-cp-spawns']

            if 'ta-top-interval' in ext:
                if ext['ta-top-interval'] >= 30 or ext['ta-top-interval'] == 0:
                    self.ta_top_interval = ext['ta-top-interval']
            elif 'ta-top-interval' in ta_config:
                if ta_config['ta-top-interval'] >= 30 or ta_config['ta-top-interval'] == 0:
                    self.ta_top_interval = ta_config['ta-top-interval']

            if 'ta-top-num' in ext:
                if ext['ta-top-num'] > 0:
                    self.ta_top_num = ext['ta-top-num'];
            elif 'ta-top-num' in ta_config:
                if ta_config['ta-top-num'] > 0:
                    self.ta_top_num = ta_config['ta-top-num'];

            if ALWS_ENBL or 'ta' in ext or 'ta' in ta_config:
                self.ta_enbl = True
                if self.ta_top_interval > 0:
                    self.ta_top_loop.start(self.ta_top_interval, False)
            self.friendly_fire = self.ta_enbl

            return protocol.on_map_change(self, map)

        def on_base_spawn(self, x, y, z, base, entity_id):
            if self.ta_enbl and self.ta_cp_spawns:
                if entity_id == GREEN_BASE:
                    return self.ta_cp_spawns[0]
                elif entity_id == BLUE_BASE:
                    return self.ta_cp_spawns[1]
            return protocol.on_base_spawn(self, x, y, z, base, entity_id)

        def on_flag_spawn(self, x, y, z, flag, entity_id):
            if self.ta_enbl:
                return HIDE_COORD
            return protocol.on_base_spawn(self, x, y, z, flag, entity_id)

    return tap, tac
