# ../wcs/core/helpers/esc/commands.py

# ============================================================================
# >> IMPORTS
# ============================================================================
# Python Imports
#   Decimals
from decimal import Decimal
#   Collections
from collections import defaultdict
#   Math
from math import ceil
from math import floor
#   Random
from random import choice
from random import randint
#   String
from string import Template
#   Time
from time import time
#   Warnings
from warnings import warn

# Source.Python Imports
#   Colors
from colors import Color
#   Commands
from commands.typed import ArgumentNumberMismatch
from commands.typed import InvalidArgumentValue
from commands.typed import TypedServerCommand
#   Core
from core import GAME_NAME
#   CVars
from cvars import ConVar
#   Engines
from engines.precache import Model
from engines.server import execute_server_command
from engines.server import global_vars
from engines.trace import ContentMasks
from engines.trace import engine_trace
from engines.trace import GameTrace
from engines.trace import Ray
from engines.trace import TraceFilterSimple
#   Entities
from entities.constants import DamageTypes
from entities.constants import MoveType
from entities.constants import TakeDamage
from entities.entity import Entity
#   Events
from events import Event
from events.hooks import PreEvent
#   Filters
from filters.players import PlayerIter
from filters.weapons import WeaponClassIter
#   Hooks
from hooks.exceptions import except_hooks
#   Keyvalues
from _keyvalues import KeyValues
# NOTE: Have to prefix it with a _ otherwise it'd import KeyValues from ES Emulator if it's loaded
#   Listeners
from listeners import OnPlayerRunCommand
from listeners.tick import Delay
from listeners.tick import Repeat
from listeners.tick import RepeatStatus
#   Loggers
from loggers import _sp_logger
#   Mathlib
from mathlib import NULL_VECTOR
from mathlib import QAngle
from mathlib import Vector
#   Messages
from messages import Fade
from messages import FadeFlags
from messages import HudMsg
from messages import KeyHintText
from messages import SayText2
from messages import Shake
#   Players
from players.dictionary import PlayerDictionary
from players.entity import Player
from players.helpers import index_from_userid
#   Translations
from translations.manager import language_manager
from translations.strings import LangStrings
#   Weapons
from weapons.dictionary import WeaponDictionary
from weapons.manager import weapon_manager
from weapons.restrictions import WeaponRestrictionHandler

# EventScripts Imports
#   Playerlib
from playerlib import Player as _PLPlayer
from playerlib import getPlayer

# WCS Imports
#   Config
from ...config import cfg_debug_alias_duplicate
#   Constants
from ...constants import COLOR_DARKGREEN
from ...constants import COLOR_DEFAULT
from ...constants import COLOR_GREEN
from ...constants import COLOR_LIGHTGREEN
from ...constants.paths import CFG_PATH
from ...constants.paths import TRANSLATION_PATH
#   Helpers
from .converts import valid_userid
from .converts import valid_userid_and_team
from .converts import convert_userid_to_player
from .converts import convert_userid_to_wcsplayer
from .converts import convert_identifier_to_players
from .converts import convert_userid_identifier_to_players
from .converts import any_value
from .converts import valid_operators
from .converts import clamp
from .converts import convert_to_vector
from .converts import split_str
from .converts import deprecated
from .est.commands import armor_command  # Just to load it
from .est.effects import effect101  # Just to load it
from ..wards import DamageWard
from ..wards import ward_manager
#   Listeners
from ...listeners import OnPlayerChangeRace
from ...listeners import OnPlayerDelete
from ...listeners import OnTakeDamageAlive
#   Modules
from ...modules.races.manager import race_manager
#   Players
from ...players import set_weapon_name
from ...players import team_data
from ...players.entity import Player as WCSPlayer

# ============================================================================
# >> GLOBAL VARIABLES
# ============================================================================
_aliases = {}

if (TRANSLATION_PATH / 'strings.ini').isfile():
    try:
        _strings = LangStrings(TRANSLATION_PATH / 'strings')
    except:
        warn("Unable to load the translation file 'resource/source-python/translations/wcs/strings.ini' due to the following exception:")
        except_hooks.print_exception()

        _strings = None
    else:
        for key in _strings:
            for language, message in _strings[key].items():
                _strings[key][language] = message.replace('#default', COLOR_DEFAULT).replace('#green', COLOR_GREEN).replace('#lightgreen', COLOR_LIGHTGREEN).replace('#darkgreen', COLOR_DARKGREEN)
else:
    _strings = None

_esc_strings = {}
_esc_strings_ids = {}

if (TRANSLATION_PATH / 'esc').isdir():
    for name in [x.basename().rsplit('.', 1)[0] for x in (TRANSLATION_PATH / 'esc').listdir() if x.endswith('.ini') and not x.endswith('_server.ini')]:
        shortname = name.rsplit('_', 1)[0]

        try:
            _esc_strings[shortname] = LangStrings(TRANSLATION_PATH / 'esc' / name)
        except:
            warn(f"Unable to load the translation file 'resource/source-python/translations/wcs/esc/{name}.ini' due to the following exception:")
            except_hooks.print_exception()
        else:
            for key in _esc_strings[shortname]:
                for language, message in _esc_strings[shortname][key].items():
                    _esc_strings[shortname][key][language] = message.replace('#default', COLOR_DEFAULT).replace('#green', COLOR_GREEN).replace('#lightgreen', COLOR_LIGHTGREEN).replace('#darkgreen', COLOR_DARKGREEN)

_restrictions = WeaponRestrictionHandler()

_all_weapons = set([x.basename for x in WeaponClassIter('all', None if GAME_NAME in ('hl2mp', ) else 'objective')])
_all_weapons_but_melee = set([x.basename for x in WeaponClassIter('all', 'melee' if GAME_NAME in ('hl2mp', ) else ['melee', 'objective'])])

if (CFG_PATH / 'es_WCSlanguage_db.txt').isfile():
    _languages = KeyValues.load_from_file(CFG_PATH / 'es_WCSlanguage_db.txt').as_dict()
else:
    _languages = {}

# EHHH???!?!
wcs_logger = _sp_logger.wcs

_repeats = defaultdict(list)
_delays = defaultdict(list)
_attackspeed = {}
_norecoil = set()
_player_instances = PlayerDictionary()
_weapon_instances = WeaponDictionary()
_recoil_cvars_default = {}
_recoil_cvars_modified = {
    'weapon_accuracy_nospread': 1,
    'weapon_air_spread_scale': 0,
    'weapon_recoil_cooldown': 0,
    'weapon_recoil_decay1_exp': 99999,
    'weapon_recoil_decay2_exp': 99999,
    'weapon_recoil_decay2_lin': 99999,
    'weapon_recoil_decay_coefficient': 0,
    'weapon_recoil_scale': 0,
    'weapon_recoil_scale_motion_controller': 0,
    'weapon_recoil_suppression_factor': 0,
    'weapon_recoil_suppression_shots': 500,
    'weapon_recoil_variance': 0,
    'weapon_recoil_vel_decay': 0,
    'weapon_recoil_view_punch_extra': 0
}

_models = {2:[], 3:[]}

if GAME_NAME == 'cstrike':
    _models[2].extend(
        [
            't_arctic',
            't_guerilla',
            't_leet',
            't_phoenix'
        ]
    )
    _models[3].extend(
        [
            'ct_gign',
            'ct_gsg9',
            'ct_sas',
            'ct_urban'
        ]
    )

    _recoil_cvars_modified.clear()

    weapon_fire_post_properties = (
        'localdata.m_Local.m_vecPunchAngle',
        'localdata.m_Local.m_vecPunchAngleVel'
    )
elif GAME_NAME == 'csgo':
    _models[2].extend(
        [
            'tm_leet_varianta',
            'tm_leet_variantb',
            'tm_leet_variantc',
            'tm_leet_variantd',
            'tm_phoenix_varianta',
            'tm_phoenix_variantb',
            'tm_phoenix_variantc',
            'tm_phoenix_variantd'
        ]
    )
    _models[3].extend(
        [
            'ctm_gign_varianta',
            'ctm_gign_variantb',
            'ctm_gign_variantc',
            'ctm_gign_variantd',
            'ctm_gsg9_varianta',
            'ctm_gsg9_variantb',
            'ctm_gsg9_variantc',
            'ctm_gsg9_variantd'
        ]
    )

    for cvar in _recoil_cvars_modified:
        _recoil_cvars_default[cvar] = ConVar(cvar).default

    _weapon_fire_post_properties = (
        'localdata.m_Local.m_aimPunchAngle',
        'localdata.m_Local.m_aimPunchAngleVel',
        'localdata.m_Local.m_viewPunchAngle'
    )
else:
    # TODO: I do not know anything about recoils for other games
    _recoil_cvars_modified.clear()

# ============================================================================
# >> CLASSES
# ============================================================================
class Dict(dict):
    def __missing__(self, key):
        return '{' + key + '}'

# ============================================================================
# >> HELPER FUNCTIONS
# ============================================================================
def validate_userid_after_delay(callback, userid, operator, value, delay, validator=convert_userid_to_player):
    _delays[userid].remove(delay)

    callback(None, validator(userid), operator, value)

def _format_message(userid, name, args):
    if _strings is None and _esc_strings is None:
        return tuple(), None

    if userid.isdigit():
        try:
            players = (Player.from_userid(int(userid)), )
        except ValueError:
            return tuple(), None
    else:
        players = convert_identifier_to_players(userid)

    if name.isdigit():
        text = _esc_strings_ids.get(int(name))
    else:
        text = None if _strings is None else _strings.get(name)

    if text is None:
        return tuple(), None

    text = text.copy()  # Make sure we do not modify the original strings

    if args:
        tokens = {}

        for i in range(0, len(args), 2):
            tokens[args[i]] = args[i + 1]

        for language, message in text.items():
            text[language] = Template(message).substitute(tokens).format_map(Dict(tokens))

    return players, text

# ============================================================================
# >> FUNCTIONS
# ============================================================================
def _remove_overlay(userid):
    try:
        player = Player.from_userid(userid)
    except ValueError:
        pass
    else:
        player.client_command('r_screenoverlay 0')

def _remove_drunk(userid):
    try:
        player = Player.from_userid(userid)
    except ValueError:
        pass
    else:
        player.default_fov = 90
        player.fov = 90

def _regeneration_repeat(userid, value, maxhealth, radius):
    try:
        player = Player.from_userid(userid)
    except ValueError:
        repeats = _repeats.pop(userid, [])

        for repeat in repeats:
            if repeat.status == RepeatStatus.RUNNING:
                repeat.stop()
    else:
        if player.team not in (2, 3):
            repeats = _repeats.pop(userid, [])

            for repeat in repeats:
                if repeat.status == RepeatStatus.RUNNING:
                    repeat.stop()

            return

        if player.health < maxhealth:
            player.health = min(player.health + value, maxhealth)

        if radius:
            origin = player.origin

            for target in PlayerIter(['alive', 't' if player.team == 2 else 'ct']):
                if not target == player:
                    if target.origin.get_distance(origin) <= radius:
                        if target.health < maxhealth:
                            target.health = min(target.health + value, maxhealth)

# ============================================================================
# >> COMMANDS
# ============================================================================
@TypedServerCommand(['wcs_setfx', 'freeze'])
def wcs_setfx_freeze_command(command_info, player:convert_userid_to_player, operator:valid_operators('='), value:int, time:float=0):
    if player is None:
        return

    if value:
        player.move_type = MoveType.NONE
    else:
        player.move_type = MoveType.WALK

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_freeze_command, player.userid, '=', not value))
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'jetpack'])
def wcs_setfx_jetpack_command(command_info, player:convert_userid_to_player, operator:valid_operators('='), value:int, time:float=0):
    if player is None:
        return

    if value:
        player.move_type = MoveType.FLY
    else:
        player.move_type = MoveType.WALK

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_jetpack_command, player.userid, '=', not value))
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'god'])
def wcs_setfx_god_command(command_info, player:convert_userid_to_player, operator:valid_operators('='), value:int, time:float=0):
    if player is None:
        return

    player.godmode = value

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_god_command, player.userid, '=', not value))
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'noblock'])
def wcs_setfx_noblock_command(command_info, player:convert_userid_to_player, operator:valid_operators('='), value:int, time:float=0):
    if player is None:
        return

    player.noblock = value

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_noblock_command, player.userid, '=', not value))
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'burn'])
def wcs_setfx_burn_command(command_info, player:convert_userid_to_player, operator:valid_operators('='), value:int, time:float=0):
    if player is None:
        return

    player.ignite_lifetime((time if time else 999) if value else 0)

@TypedServerCommand(['wcs_setfx', 'speed'])
def wcs_setfx_speed_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:float, time:float=0):
    if player is None:
        return

    if operator == '=':
        old_value = player.speed
        player.speed = value
        value = old_value - value
    elif operator == '+':
        player.speed += value
        value *= -1
    else:
        player.speed -= value

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_speed_command, player.userid, '+', value))
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'invis'])
def wcs_setfx_invis_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:int, time:float=0):
    if player is None:
        return

    color = player.color

    if operator == '=':
        old_value = color.a
        player.color = color.with_alpha(value)
        value = old_value - value
    elif operator == '+':
        player.color = color.with_alpha(min(color.a + value, 255))
        value *= -1
    else:
        player.color = color.with_alpha(max(color.a - value, 0))

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_invis_command, player.userid, '+', value))
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'invisp'])
def wcs_setfx_invisp_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:float, time:float=0):
    pass  # TODO

@TypedServerCommand(['wcs_setfx', 'health'])
def wcs_setfx_health_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:int, time:float=0):
    if player is None:
        return

    if operator == '=':
        value = value - player.health
    elif operator == '+':
        pass
    elif operator == '-':
        value *= -1

    if player.health > 0:
        # If the player is still alive, we don't want setfx health to kill them (or rather freeze them on <= 0 hp)
        player.health = max(player.health+value, 1)
    else:
        # If the player is already dying we only want to give them a chance to heal back up above 0.
        player.health += value

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_health_command, player.userid, '-', value))
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'armor'])
def wcs_setfx_armor_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:int, time:float=0):
    if player is None:
        return

    if operator == '=':
        old_value = player.armor
        player.armor = value
        value = old_value - value
    elif operator == '+':
        player.armor = max(player.armor + value, 0)
        value *= -1
    else:
        player.armor = max(player.armor - value, 0)

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_armor_command, player.userid, '+', value))
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'cash'])
def wcs_setfx_cash_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:int, time:float=0):
    if player is None:
        return

    if operator == '=':
        old_value = player.cash
        player.cash = value
        value = old_value - value
    elif operator == '+':
        player.cash = max(player.cash + value, 0)
        value *= -1
    else:
        player.cash = max(player.cash - value, 0)

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_cash_command, player.userid, '+', value))
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'gravity'])
def wcs_setfx_gravity_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:float, time:float=0):
    if player is None:
        return

    if operator == '=':
        old_value = player.gravity
        player.gravity = value
        value = old_value - value
    elif operator == '+':
        player.gravity = max(player.gravity + value, 0)
        value *= -1
    else:
        player.gravity = max(player.gravity - value, 0)

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_gravity_command, player.userid, '+', value))
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'ulti_immunity'])
def wcs_setfx_ulti_immunity_command(command_info, wcsplayer:convert_userid_to_wcsplayer, operator:valid_operators(), value:int, time:float=0):
    if wcsplayer is None:
        return

    if operator == '=':
        old_value = wcsplayer.data.get('ulti_immunity', 0)
        wcsplayer.data['ulti_immunity'] = value
        value = old_value - value
    elif operator == '+':
        wcsplayer.data['ulti_immunity'] = wcsplayer.data.get('ulti_immunity', 0)
        value *= -1
    else:
        old_value = wcsplayer.data.get('ulti_immunity', 0)

        # This is here to prevent them from gaining if there was a time set
        if not old_value:
            return

        wcsplayer.data['ulti_immunity'] = max(old_value - value, 0)

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_ulti_immunity_command, wcsplayer.userid, '+', value), {'validator':convert_userid_to_wcsplayer})
        delay.args += (delay, )
        _delays[wcsplayer.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'disguiser'])
def wcs_setfx_disguiser_command(command_info, player:convert_userid_to_player, operator:valid_operators('='), value:int, time:float=0):
    if player is None:
        return

    if value:
        models = _models.get(5 - player.team)
    else:
        models = _models.get(player.team)

    if not models:
        return

    player.model = Model('models/player/' + choice(models) + '.mdl')

    if time:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_disguiser_command, player.userid, '=', not value), {'validator':convert_userid_to_player})
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'disguise'])
def wcs_setfx_disguise_command(command_info, player:convert_userid_to_player, operator:valid_operators('='), value:int, time:float=0):
    wcs_setfx_disguiser_command(command_info, player, operator, value, time)

@TypedServerCommand(['wcs_setfx', 'longjump'])
def wcs_setfx_longjump_command(command_info, wcsplayer:convert_userid_to_wcsplayer, operator:valid_operators(), value:float, time:float=0):
    if wcsplayer is None:
        return

    if operator == '=':
        old_value = wcsplayer.data.get('longjump', 0)
        wcsplayer.data['longjump'] = value
        value = old_value - value
    elif operator == '+':
        wcsplayer.data['longjump'] = wcsplayer.data.get('longjump', 0) + value
        value *= -1
    else:
        old_value = wcsplayer.data.get('longjump', 0)

        # This is here to prevent them from gaining if there was a time set
        if not old_value:
            return

        wcsplayer.data['longjump'] = max(old_value - value, 0)

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_longjump_command, wcsplayer.userid, '+', value), {'validator':convert_userid_to_wcsplayer})
        delay.args += (delay, )
        _delays[wcsplayer.userid].append(delay)

@TypedServerCommand(['wcs_setfx', '1stclip'])
def wcs_setfx_1stclip_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:int, time:float=0):
    if player is None:
        return

    weapon = player.get_weapon(is_filters='primary')

    if weapon is None:
        return

    if operator == '=':
        old_value = weapon.clip
        weapon.clip = value
        value = old_value - value
    elif operator == '+':
        weapon.clip = max(weapon.clip + value, 0)
        value *= -1
    else:
        old_value = weapon.clip

        if not old_value:
            return

        weapon.clip = max(old_value - value, 0)

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_1stclip_command, player.userid, '+', value), {'validator':convert_userid_to_player})
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', '2ndclip'])
def wcs_setfx_2ndclip_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:int, time:float=0):
    if player is None:
        return

    weapon = player.get_weapon(is_filters='secondary')

    if weapon is None:
        return

    if operator == '=':
        old_value = weapon.clip
        weapon.clip = value
        value = old_value - value
    elif operator == '+':
        weapon.clip = max(weapon.clip + value, 0)
        value *= -1
    else:
        old_value = weapon.clip

        if not old_value:
            return

        weapon.clip = max(old_value - value, 0)

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_2ndclip_command, player.userid, '+', value), {'validator':convert_userid_to_player})
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', '1stammo'])
def wcs_setfx_1stammo_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:int, time:float=0):
    if player is None:
        return

    weapon = player.get_weapon(is_filters='secondary')

    if weapon is None:
        return

    if operator == '=':
        old_value = weapon.ammo
        weapon.ammo = value
        value = old_value - value
    elif operator == '+':
        weapon.ammo = max(weapon.ammo + value, 0)
        value *= -1
    else:
        old_value = weapon.ammo

        if not old_value:
            return

        weapon.ammo = max(old_value - value, 0)

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_1stammo_command, player.userid, '+', value), {'validator':convert_userid_to_player})
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', '2ndammo'])
def wcs_setfx_2ndammo_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:int, time:float=0):
    if player is None:
        return

    weapon = player.get_weapon(is_filters='secondary')

    if weapon is None:
        return

    if operator == '=':
        old_value = weapon.ammo
        weapon.ammo = value
        value = old_value - value
    elif operator == '+':
        weapon.ammo = max(weapon.ammo + value, 0)
        value *= -1
    else:
        old_value = weapon.ammo

        if not old_value:
            return

        weapon.ammo = max(old_value - value, 0)

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_2ndammo_command, player.userid, '+', value), {'validator':convert_userid_to_player})
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'flicker'])
def wcs_setfx_flicker_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:int, time:float=0):
    warn('"wcs_setfx flicker" will be removed in the future. Use "wcs_getplayerindex" together with "es_entitysetvalue <index> renderfx 13/0" instead.', PendingDeprecationWarning)

    if player is None:
        return

    player.set_key_value_int('renderfx', 13 if value else 0)

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_flicker_command, player.userid, '=', not value), {'validator':convert_userid_to_player})
        delay.args += (delay, )
        _delays[player.userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'attackspeed'])
def wcs_setfx_attackspeed_command(command_info, player:convert_userid_to_player, operator:valid_operators(), value:float, time:float=0):
    if player is None:
        return

    userid = player.userid

    if operator == '=':
        old_value = _attackspeed.get(userid, 1)
        _attackspeed[userid] = value
        value = old_value - value
    elif operator == '+':
        _attackspeed[userid] = _attackspeed.get(userid, 1) + value
        value *= -1
    else:
        old_value = _attackspeed.get(userid, 1)

        _attackspeed[userid] = max(old_value - value, 0)

    if _attackspeed[userid] in (0, 1):
        del _attackspeed[userid]

    cur_time = global_vars.current_time

    for index in player.weapon_indexes():
        _weapon_instances[index].set_network_property_float('LocalActiveWeaponData.m_flNextPrimaryAttack', cur_time)

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_attackspeed_command, userid, '+', value), {'validator':convert_userid_to_player})
        delay.args += (delay, )
        _delays[userid].append(delay)

@TypedServerCommand(['wcs_setfx', 'norecoil'])
def wcs_setfx_norecoil_command(command_info, player:convert_userid_to_player, operator:valid_operators('='), value:int, time:float=0):
    if player is None:
        return

    userid = player.userid

    if value:
        _norecoil.add(userid)

        if not player.is_bot():
            for cvar in _recoil_cvars_modified:
                player.send_convar_value(cvar, _recoil_cvars_modified[cvar])
    else:
        _norecoil.discard(userid)

        if not player.is_bot():
            for cvar in _recoil_cvars_modified:
                player.send_convar_value(cvar, _recoil_cvars_default[cvar])

    if time > 0:
        delay = Delay(time, validate_userid_after_delay, (wcs_setfx_norecoil_command, userid, '=', not value), {'validator':convert_userid_to_player})
        delay.args += (delay, )
        _delays[userid].append(delay)

@TypedServerCommand('wcs_removefx')
def wcs_removefx_freeze_command(command_info, *args):
    raise NotImplementedError(args)

@TypedServerCommand(['wcsgroup', 'get'])
def wcsgroup_get_command(command_info, key:str, var:ConVar, userid:valid_userid_and_team):
    if userid is None:
        var.set_int(0)
        return

    if isinstance(userid, str):
        value = team_data[{'#t':2, '#ct':3}[userid]].get(key, '0')
    else:
        wcsplayer = WCSPlayer.from_userid(userid)

        value = wcsplayer.data.get(key, '0')

    var.set_string(str(value))

@TypedServerCommand(['wcsgroup', 'set'])
def wcsgroup_set_command(command_info, key:str, userid:valid_userid_and_team, value:any_value):
    if userid is None:
        return

    if isinstance(userid, str):
        team_data[{'#t':2, '#ct':3}[userid]][key] = value
    else:
        wcsplayer = WCSPlayer.from_userid(userid)

        wcsplayer.data[key] = value

@TypedServerCommand(['wcs', 'damage'])
def wcs_sub_damage_command(command_info, wcsplayer:convert_userid_to_wcsplayer, damage:int, attacker:int=None, armor:deprecated=None, weapon:deprecated=None, solo:deprecated=None):
    if wcsplayer is None:
        return

    if wcsplayer.player.dead:
        return

    if attacker is None:
        attacker = 0
    else:
        try:
            attacker = index_from_userid(attacker)
        except ValueError:
            attacker = 0

    wcsplayer.take_damage(damage, attacker=attacker)

@TypedServerCommand(['wcs', 'spawn'])
def wcs_sub_spawn_command(command_info, player:convert_userid_to_player, force:int=0):
    if player is None:
        return

    player.spawn(force)

@TypedServerCommand(['wcs', 'strip'])
def wcs_sub_strip_command(command_info, player:convert_userid_to_player):
    if player is None:
        return

    entity = Entity.create('player_weaponstrip')
    entity.call_input('Strip', activator=player)
    entity.remove()

@TypedServerCommand(['wcs', 'drop'])
def wcs_sub_drop_command(command_info, player:convert_userid_to_player, slot:str):
    if player is None:
        return

    if slot.isdigit():
        slot = int(slot)

        if slot not in range(1, 6):
            raise InvalidArgumentValue(f'"{slot}" is an invalid value for "slot:str".')

        if slot == 1:
            slot = 'primary'
        elif slot == 2:
            slot = 'secondary'
        elif slot == 3:
            slot = 'melee'
        elif slot == 4:
            slot = 'grenade'
        else:
            slot = 'objective'

        for weapon in player.weapons(is_filters=slot):
            player.drop_weapon(weapon)

        return

    try:
        name = weapon_manager[slot].name
    except KeyError:
        raise InvalidArgumentValue(f'"{slot}" is an invalid value for "slot:str".')

    for weapon in player.weapons():
        if weapon.classname == name:
            player.drop_weapon(weapon)
            break

@TypedServerCommand(['wcs', 'push'])
def wcs_sub_push_command(command_info, player:convert_userid_to_player, x:float, y:float=0, z:float=0):
    if player is None:
        return

    vector = Vector(x, y, z)

    player.base_velocity = vector

@TypedServerCommand(['wcs', 'pushto'])
def wcs_sub_pushto_command(command_info, player:convert_userid_to_player, vector:convert_to_vector, force:float):
    if player is None:
        return

    player.teleport(velocity=vector - player.origin)

@TypedServerCommand(['wcs', 'gravity'])
def wcs_sub_gravity_command(command_info, player:convert_userid_to_player, value:float):
    if player is None:
        return

    player.gravity = value

@TypedServerCommand(['wcs', 'removeweapon'])
def wcs_sub_removeweapon_command(command_info, player:convert_userid_to_player, slot:str):
    if player is None:
        return

    if slot.isdigit():
        slot = int(slot)

        if slot not in range(1, 6):
            raise InvalidArgumentValue(f'"{slot}" is an invalid value for "slot:str".')

        if slot == 1:
            slot = 'primary'
        elif slot == 2:
            slot = 'secondary'
        elif slot == 3:
            slot = 'melee'
        elif slot == 4:
            slot = 'grenade'
        else:
            slot = 'objective'

        for weapon in player.weapons(is_filters=slot):
            player.drop_weapon(weapon)

            weapon.remove()

        return

    try:
        name = weapon_manager[slot].name
    except KeyError:
        raise InvalidArgumentValue(f'"{slot}" is an invalid value for "slot:str".')

    for weapon in player.weapons():
        if weapon.classname == name:
            player.drop_weapon(weapon)

            weapon.remove()
            break

@TypedServerCommand(['wcs', 'getviewplayer'])
def wcs_sub_getviewplayer_command(command_info, player:convert_userid_to_player, var:ConVar):
    if player is None:
        var.set_int(-1)
        return

    target = player.view_player

    if target is None:
        var.set_int(-1)
        return

    var.set_int(target.userid)

@TypedServerCommand(['wcs', 'getviewentity'])
def wcs_sub_getviewentity_command(command_info, player:convert_userid_to_player, var:ConVar):
    if player is None:
        var.set_int(-1)
        return

    target = player.view_entity

    if target is None:
        var.set_int(-1)
        return

    var.set_int(target.entity)

@TypedServerCommand(['wcs', 'keyhint'])
def wcs_sub_keyhint_command(command_info, player:convert_userid_to_player, text:str):
    if player is None:
        return

    KeyHintText(text).send(player.index)

@TypedServerCommand(['wcs', 'give'])
def wcs_sub_give_command(command_info, player:convert_userid_to_player, entity:str):
    warn('"wcs give" will be removed in the future. Use "es_give" instead.', PendingDeprecationWarning)

    if player is None:
        return

    # entity = Entity.create(entity)
    # entity.origin = player.origin
    # entity.spawn()

@TypedServerCommand(['wcs', 'fire'])
def wcs_sub_fire_command(command_info, player:convert_userid_to_player, time:float):
    warn('"wcs fire" will be removed in the future. Use "wcs_setfx burn" instead.', PendingDeprecationWarning)

    if player is None:
        return

    player.ignite_lifetime(time if time else 999)

@TypedServerCommand(['wcs', 'extinguish'])
def wcs_sub_extinguish_command(command_info, player:convert_userid_to_player):
    warn('"wcs extinguish" will be removed in the future. Use "wcs_setfx burn" instead.', PendingDeprecationWarning)

    if player is None:
        return

    player.ignite_lifetime(0.0)

@TypedServerCommand(['wcs', 'drug'])
def wcs_sub_drug_command(command_info, player:convert_userid_to_player, duration:float=0):
    warn('"wcs drug" will be removed in the future. Use "es_cexec" with "r_screenoverlay" and "es_delayed" instead.', PendingDeprecationWarning)

    if player is None:
        return

    player.client_command('r_screenoverlay effects/tp_eyefx/tp_eyefx')

    if duration:
        player.delay(duration, _remove_overlay, (player.userid, ))

@TypedServerCommand(['wcs', 'drunk'])
def wcs_sub_drunk_command(command_info, player:convert_userid_to_player, duration:float=0, value:int=155):
    warn('"wcs drunk" will be removed in the future. Use "es_setplayerprop" with "CBasePlayer.m_iDefaultFOV", "CBasePlayer.m_iFOV" and "es_delayed" instead.', PendingDeprecationWarning)

    if player is None:
        return

    player.default_fov = value
    player.fov = value

    if time:
        player.delay(duration, _remove_drunk, (player.userid, ))

# @TypedServerCommand(['wcs', 'poison'])
# def wcs_sub_poison_command(command_info, player:convert_userid_to_player):
#     if player is None:
#         return

# @TypedServerCommand(['wcs', 'timed_damage'])
# def wcs_sub_timed_damage_command(command_info, player:convert_userid_to_player):
#     if player is None:
#         return

@TypedServerCommand(['wcs', 'changeteam'])
def wcs_sub_changeteam_command(command_info, player:convert_userid_to_player, team:int):
    if player is None:
        return

    player.set_team(team)

@TypedServerCommand(['wcsx', 'get'])
def wcsx_get_command(command_info, key:str, var:ConVar, userid:valid_userid):
    if userid is None:
        var.set_int(-1)
        return

    player = getPlayer(userid)

    if not hasattr(player, key):
        var.set_int(-1)
        return

    value = getattr(player, key)

    if callable(value):
        if isinstance(value, _PLPlayer.ReturnValue):
            var.set_string(str(value))
            return
        else:
            value = value()

    if key not in ('weapon', 'primary', 'secondary'):
        if hasattr(value, '__iter__'):
            if hasattr(value[0], '__iter__'):
                if len(value[0]) == 1:
                    value = value[0][0]
                else:
                    value = ','.join([str(x) for x in value[0]])
            else:
                if len(value) == 3:
                    value = ','.join([str(x) for x in value])
                else:
                    value = value[0]

    var.set_string(str(value))

@TypedServerCommand(['wcsx', 'set'])
def wcsx_set_command(command_info, key:str, userid:valid_userid, *value:str):
    if userid is None:
        return

    player = getPlayer(userid)

    if not hasattr(player, key):
        return

    if not value:
        return

    if hasattr(value, '__iter__'):
        if hasattr(value[0], '__iter__'):
            if len(value[0]) == 1:
                value = value[0][0]
            else:
                value = ','.join([str(x) for x in value[0]])
        else:
            value = value[0]

    if hasattr(value, '__iter__') and not key == 'location':
        value = value[0]

    if key == 'location' and not hasattr(value, '__iter__'):
        value = value.split(',')

    method = getattr(player, key)

    if callable(method):
        method(value)
    else:
        setattr(player, key, value)

@TypedServerCommand(['wcsx', 'math'])
def wcsx_math_command(command_info, key:str, userid:valid_userid, operator:valid_operators(), value:any_value):
    if userid is None:
        return

    player = getPlayer(userid)

    if not hasattr(player, key):
        return

    if operator == '+':
        value = getattr(player, key) + value
    elif operator == '-':
        value = getattr(player, key) - value

    setattr(player, key, value)

@TypedServerCommand(['wcsx', 'call'])
def wcsx_call_command(command_info, key:str, userid:valid_userid):
    if userid is None:
        return

    player = getPlayer(userid)

    if not hasattr(player, key):
        return

    method = getattr(player, key)

    if not callable(method):
        return

    method()

@TypedServerCommand(['wcsx', 'create'])
def wcsx_create_command(command_info, var:ConVar, *args:str):
    var.set_string(','.join(args))

@TypedServerCommand(['wcsx', 'split'])
def wcsx_split_command(command_info, value:str, *var:ConVar):
    for i, x in enumerate(value.split(',')):
        var[i].set_string(x)

@TypedServerCommand('wcs_get_skill_level')
def wcs_get_skill_level_command(command_info, wcsplayer:convert_userid_to_wcsplayer, var:ConVar, index:int):
    if wcsplayer is None:
        var.set_int(0)
        return

    if not wcsplayer.ready:
        var.set_int(0)
        return

    active_race = wcsplayer.active_race

    skills = [*active_race.settings.config['skills']]

    if index > len(skills):
        var.set_int(0)
        return

    var.set_int(active_race.skills[skills[index - 1]].level)

@TypedServerCommand(['wcs_foreach', 'player'])
def wcs_foreach_command(command_info, var:str, players:convert_identifier_to_players, command:str):
    for player in players:
        for cmd in [f'es_xset {var} {player.userid}'] + command.split(';'):
            execute_server_command('es', cmd)

@TypedServerCommand('wcs_nearcoord')
def wcs_nearcoord_command(command_info, var:str, players:convert_identifier_to_players, x:float, y:float, z:float, distance:float, command:str):
    vector = Vector(x, y, z)

    for player in players:
        if vector.get_distance(player.origin) <= distance:
            for cmd in [f'es_xset {var} {player.userid}'] + command.split(';'):
                execute_server_command('es', cmd)

@TypedServerCommand('wcs_color')
def wcs_color_command(command_info, player:convert_userid_to_player, red:clamp(0, 255), green:clamp(0, 255), blue:clamp(0, 255), alpha:clamp(0, 255)=255, weapons:int=0):
    if player is None:
        return

    color = Color(red, green, blue, alpha)

    player.color = color

    if weapons:
        for weapon in player.weapons():
            weapon.color = color

@TypedServerCommand('wcs_changerace')
def wcs_changerace_command(command_info, wcsplayer:convert_userid_to_wcsplayer, *name:str):
    if wcsplayer is None:
        return

    name = ' '.join(name)

    if name not in race_manager:
        for settings in race_manager.values():
            if settings.strings['name'].get_string('en') == name:
                name = settings.name
                break
        else:
            return

    if wcsplayer.current_race == name:
        return

    wcsplayer.current_race = name

@TypedServerCommand('wcs_givexp')
def wcs_givexp_command(command_info, wcsplayer:convert_userid_to_wcsplayer, value:int, reason:deprecated=None, forced:deprecated=None):
    if wcsplayer is None:
        return

    if wcsplayer.xp + value < 0:
        raise ValueError('Unable to set xp below 0')

    active_race = wcsplayer.active_race
    maximum_race_level = active_race.settings.config.get('maximum_race_level', 0)

    if maximum_race_level and active_race.level >= maximum_race_level:
        return

    wcsplayer.xp += value

@TypedServerCommand('wcs_givelevel')
def wcs_givelevel_command(command_info, wcsplayer:convert_userid_to_wcsplayer, value:int, reason:deprecated=None, forced:deprecated=None):
    if wcsplayer is None:
        return

    if wcsplayer.level + value < 0:
        raise ValueError('Unable to set level below 0')

    active_race = wcsplayer.active_race
    maximum_race_level = active_race.settings.config.get('maximum_race_level', 0)

    if maximum_race_level and active_race.level >= maximum_race_level:
        return

    wcsplayer.level += value

@TypedServerCommand('wcs_levelbank_givelevel')
def wcs_levelbank_givelevel_command(command_info, wcsplayer:convert_userid_to_wcsplayer, value:int):
    if wcsplayer is None:
        return

    if wcsplayer.bank_level + value < 0:
        raise ValueError('Unable to set levelbank below 0')

    wcsplayer.bank_level += value

@TypedServerCommand('wcs_xalias')
def wcs_xalias_command(command_info, alias:str, command:str=None):
    if command is None:
        for cmd in _aliases[alias].split(';'):
            execute_server_command('es', cmd)
    else:
        if alias in _aliases:
            if cfg_debug_alias_duplicate.get_float():
                warn(f'Alias "{alias}" is already registered - overwriting...')

        _aliases[alias] = command

@TypedServerCommand('wcs_dalias')
def wcs_dalias_command(command_info, alias:str, *args:str):
    for i, value in enumerate(args, 1):
        ConVar(f'wcs_tmp{i}').set_string(value)

    for cmd in _aliases[alias].split(';'):
        execute_server_command('es', cmd)

@TypedServerCommand('wcs_decimal')
def wcs_decimal_command(command_info, var:ConVar, value:Decimal, decimals:int=None, direction:int=None):
    if decimals is None:
        var.set_string(str(round(value)))
        return

    if decimals < 0:
        raise InvalidArgumentValue(f'"{decimals}" is an invalid value for "decimals:int".')

    if direction is None:
        var.set_string(str(round(value, decimals)))
        return

    if decimals:
        factor = 10 ** decimals

        if direction:
            var.set_string(str(ceil(value * factor) / factor))
        else:
            var.set_string(str(floor(value * factor) / factor))
    else:
        if direction:
            var.set_int(ceil(value))
        else:
            var.set_int(floor(value))

@TypedServerCommand('wcs_xtell')
def wcs_xtell_command(command_info, userid:str, name:str, *args:str):
    players, message = _format_message(userid, name, args)

    for player in players:
        # Try to get the text in the player's language
        text = message.get(language_manager.get_language(player.language))

        if text is None:
            # Try to get the text in the server's default language
            text = message.get(language_manager.default)

            if text is None:
                # Just get any text. I don't care if it's in Na'vi
                text = message[list(message)[0]]

        SayText2(text).send(player.index)

@TypedServerCommand('wcs_xcentertell')
def wcs_xcentertell_command(command_info, userid:str, name:str, *args:str):
    players, message = _format_message(userid, name, args)

    for player in players:
        # Try to get the text in the player's language
        text = message.get(language_manager.get_language(player.language))

        if text is None:
            # Try to get the text in the server's default language
            text = message.get(language_manager.default)

            if text is None:
                # Just get any text. I don't care if it's in Na'vi
                text = message[list(message)[0]]

        HudMsg(text, y=0.2).send(player.index)

@TypedServerCommand('wcs_centermsg')
def wcs_centermsg_command(command_info, *message:str):
    HudMsg(' '.join(message), y=0.2).send()

@TypedServerCommand('wcs_centertell')
def wcs_centertell_command(command_info, player:convert_userid_to_player, *message:str):
    if player is None:
        return

    HudMsg(' '.join(message), y=0.2).send(player.index)

@TypedServerCommand('wcs_dealdamage')
def wcs_dealdamage_command(command_info, wcstarget:convert_userid_to_wcsplayer, attacker:valid_userid, damage:int, weapon:str=None):
    if wcstarget is None:
        return

    if wcstarget.player.dead:
        return

    if attacker is None:
        attacker = 0
    else:
        try:
            attacker = index_from_userid(attacker)
        except ValueError:
            attacker = 0

    wcstarget.take_damage(damage, attacker=attacker, weapon=weapon)

@TypedServerCommand('wcs_getcolors')
def wcs_getcolors_command(command_info, player:convert_userid_to_player, red:ConVar, green:ConVar, blue:ConVar, alpha:ConVar):
    if player is None:
        return

    color = player.color

    red.set_int(color.r)
    green.set_int(color.g)
    blue.set_int(color.b)
    alpha.set_int(color.a)

@TypedServerCommand('wcs_getinfo')
def wcs_getinfo_command(command_info, wcsplayer:convert_userid_to_wcsplayer, var:ConVar, attribute:str, key:str):
    if wcsplayer is None:
        var.set_int(-1)
        return

    if not wcsplayer.ready:
        var.set_int(-1)
        return

    if key == 'race':
        if attribute == 'shortname':
            var.set_string(wcsplayer.active_race.name)
        elif attribute == 'name':
            var.set_string(wcsplayer.active_race.settings.strings['name'])
        elif attribute == 'level':
            var.set_int(wcsplayer.active_race.level)
        elif attribute == 'xp':
            var.set_int(wcsplayer.active_race.xp)
        elif attribute == 'unused':
            var.set_int(wcsplayer.active_race.unused)
    elif key == 'player':
        if attribute == 'shortcurrace':
            var.set_string(wcsplayer.active_race.name)
        elif attribute == 'currace':
            var.set_string(wcsplayer.active_race.settings.strings['name'])
        elif attribute == 'totallevel':
            var.set_int(wcsplayer.total_level)

@TypedServerCommand('wcs_restrict')
def wcs_restrict_command(command_info, player:convert_userid_to_player, weapons:split_str(), reverse:int=0):
    if player is None:
        return

    if weapons[0] == 'all':
        _restrictions.add_player_restrictions(player, *_all_weapons_but_melee)
        return

    if 'only' in weapons:
        weapons.remove('only')

        weapons = _all_weapons_but_melee.difference(weapons)

        _restrictions.player_restrictions[player.userid].clear()

    _restrictions.add_player_restrictions(player, *weapons)

@TypedServerCommand('wcs_unrestrict')
def wcs_unrestrict_command(command_info, player:convert_userid_to_player, weapons:split_str()):
    if player is None:
        return

    if weapons[0] == 'all':
        _restrictions.remove_player_restrictions(player, *_all_weapons)
        return

    _restrictions.remove_player_restrictions(player, *weapons)

@TypedServerCommand('wcs_getlanguage')
def wcs_getlanguage_command(command_info, var:ConVar, id_:str, language:str='en', module:str=None):
    if module is None:
        var.set_string(_languages.get(language, {}).get(id_, 'n/a'))
    else:
        try:
            identifier = id(_esc_strings[module][id_])
        except KeyError:
            var.set_int(0)
        else:
            _esc_strings_ids[identifier] = _esc_strings[module][id_]

            var.set_string(str(identifier))

@TypedServerCommand('wcs_randplayer')
def wcs_randplayer_command(command_info, var:ConVar, players:convert_identifier_to_players):
    players = list(players)

    if players:
        var.set_int(choice(players).userid)
    else:
        var.set_int(0)

@TypedServerCommand('wcs_get_cooldown')
def wcs_get_cooldown_command(command_info, wcsplayer:convert_userid_to_wcsplayer, var:ConVar):
    if wcsplayer is None:
        var.set_int(-1)
        return

    if not wcsplayer.ready:
        var.set_int(-1)
        return

    active_race = wcsplayer.active_race

    for skill in active_race.skills.values():
        if 'player_ultimate' in skill.config['event']:
            var.set_float(max(skill.cooldown - time(), 0))
            break
    else:
        var.set_int(-1)

@TypedServerCommand('wcs_getcooldown')
def wcs_getcooldown_command(command_info, wcsplayer:convert_userid_to_wcsplayer, var:ConVar):
    wcs_get_cooldown_command(command_info, wcsplayer, var)

@TypedServerCommand('wcs_set_cooldown')
def wcs_set_cooldown_command(command_info, wcsplayer:convert_userid_to_wcsplayer, value:float):
    if wcsplayer is None:
        return

    if not wcsplayer.ready:
        return

    active_race = wcsplayer.active_race

    for skill in active_race.skills.values():
        if 'player_ultimate' in skill.config['event']:
            skill.reset_cooldown(value)
            break

@TypedServerCommand('wcs_setcooldown')
def wcs_setcooldown_command(command_info, wcsplayer:convert_userid_to_wcsplayer, value:float):
    wcs_set_cooldown_command(command_info, wcsplayer, value)

@TypedServerCommand('wcs_cancelulti')
def wcs_cancelulti_command(command_info, wcsplayer:convert_userid_to_wcsplayer):
    wcs_set_cooldown_command(command_info, wcsplayer, 0)

@TypedServerCommand('wcs_get_ability_cooldown')
def wcs_get_ability_cooldown_command(command_info, wcsplayer:convert_userid_to_wcsplayer, var:ConVar, ability:int=None):
    if wcsplayer is None:
        var.set_int(-1)
        return

    if not wcsplayer.ready:
        var.set_int(-1)
        return

    active_race = wcsplayer.active_race

    if ability is None:
        for skill in active_race.skills.values():
            if 'player_ability' in skill.config['event']:
                var.set_float(max(skill.cooldown - time(), 0))
                break
        else:
            var.set_int(-1)
    else:
        i = 1

        for skill_name in active_race.settings.config['skills']:
            skill = active_race.skills[skill_name]

            if 'player_ability_on' in skill.config['event']:
                if ability == i:
                    var.set_float(max(skill.cooldown - time(), 0))
                    break

                i += 1

@TypedServerCommand('wcs_set_ability_cooldown')
def wcs_set_ability_cooldown_command(command_info, wcsplayer:convert_userid_to_wcsplayer, value:float, ability:int=None):
    if wcsplayer is None:
        return

    if not wcsplayer.ready:
        return

    active_race = wcsplayer.active_race

    if ability is None:
        for skill in active_race.skills.values():
            if 'player_ability' in skill.config['event']:
                skill.reset_cooldown(value)
                break
    else:
        i = 1

        for skill_name in active_race.settings.config['skills']:
            skill = active_race.skills[skill_name]

            if 'player_ability_on' in skill.config['event']:
                if ability == i:
                    skill.reset_cooldown(value)
                    break

                i += 1

@TypedServerCommand('wcs_cancel_ability')
def wcs_cancel_ability_command(command_info, wcsplayer:convert_userid_to_wcsplayer, ability:int=None):
    wcs_set_ability_cooldown_command(command_info, wcsplayer, 0, ability)

@TypedServerCommand('wcs_getviewcoords')
def wcs_getviewcoords_command(command_info, player:convert_userid_to_player, x:ConVar, y:ConVar, z:ConVar):
    if player is None:
        x.set_int(0)
        y.set_int(0)
        z.set_int(0)
        return

    view_coordinates = player.view_coordinates

    x.set_float(view_coordinates[0])
    y.set_float(view_coordinates[1])
    z.set_float(view_coordinates[2])

@TypedServerCommand('wcs_getdistance')
def wcs_getdistance_command(command_info, var:ConVar, x:float, y:float, z:float, x2:float, y2:float, z2:float):
    vector = Vector(x, y, z)
    vector2 = Vector(x2, y2, z2)

    var.set_float(vector.get_distance(vector2))

@TypedServerCommand('wcs_pushto')
def wcs_pushto_command(command_info, *args):
    if len(args) == 5:
        execute_server_command('_wcs_pushto', args[0], ','.join(args[1:3]), args[4])
    else:
        execute_server_command('_wcs_pushto', *args)

@TypedServerCommand('_wcs_pushto')
def _wcs_pushto_command(command_info, player:convert_userid_to_player, vector:convert_to_vector, force:float):
    if player is None:
        return

    position = player.origin
    vector -= position
    vector *= force

    player.base_velocity = vector

@TypedServerCommand('wcs_pushed')
def wcs_pushed_command(command_info, player:convert_userid_to_player, x:float, y:float, z:float):
    if player is None:
        return

    vector = Vector(x, y, z)

    player.base_velocity = vector

@TypedServerCommand('wcs_explosion')
def wcs_explosion_command(command_info, player:convert_userid_to_player, magnitude:int, radius:int, deal_damage:int=1):
    if player is None:
        return

    entity = Entity.create('env_explosion')
    entity.set_property_int('m_iMagnitude', magnitude)
    entity.set_property_int('m_iRadiusOverride', radius)

    entity.spawn_flags = 8 if deal_damage else 1
    entity.owner_handle = player.inthandle
    entity.origin = player.origin

    entity.spawn()

    entity.call_input('Explode')

@TypedServerCommand('wcs_getrandomrace')
def wcs_getrandomrace_command(command_info, wcsplayer:convert_userid_to_wcsplayer, var:ConVar):
    if wcsplayer is None:
        var.set_int(0)
        return

    available_races = list(wcsplayer.available_races)

    if available_races:
        var.set_string(choice(available_races))
    else:
        var.set_int(0)

@TypedServerCommand('wcs_getwallbetween')
def wcs_getwallbetween_command(command_info, var:ConVar, player:convert_userid_to_player, target:convert_userid_to_player):
    if player is None or target is None:
        var.set_int(-1)
        return

    vector = player.origin
    vector2 = target.origin

    trace = GameTrace()
    ray = Ray(vector, vector2)

    engine_trace.trace_ray(ray, ContentMasks.ALL, None, trace)

    var.set_int(trace.did_hit_world())

@TypedServerCommand('wcs_getviewentity')
def wcs_getviewentity_command(command_info, player:convert_userid_to_player, var:ConVar):
    if player is None:
        var.set_int(-1)
        return

    target = player.view_entity

    if target is None:
        var.set_int(-1)
        return

    var.set_int(target.index)

@TypedServerCommand('wcs_getviewplayer')
def wcs_getviewplayer_command(command_info, player:convert_userid_to_player, var:ConVar):
    if player is None:
        var.set_int(-1)
        return

    target = player.view_player

    if target is None:
        var.set_int(-1)
        return

    var.set_int(target.userid)

@TypedServerCommand('wcs_removeweapon')
def wcs_removeweapon_command(command_info, player:convert_userid_to_player, slot:str):
    if player is None:
        return

    if slot.isdigit():
        slot = int(slot)

        if slot not in range(1, 6):
            raise InvalidArgumentValue(f'"{slot}" is an invalid value for "slot:str".')

        if slot == 1:
            slot = 'primary'
        elif slot == 2:
            slot = 'secondary'
        elif slot == 3:
            slot = 'melee'
        elif slot == 4:
            slot = 'grenade'
        else:
            slot = 'objective'

        for weapon in player.weapons(is_filters=slot):
            player.drop_weapon(weapon)

            weapon.remove()

        return

    try:
        name = weapon_manager[slot].name
    except KeyError:
        raise InvalidArgumentValue(f'"{slot}" is an invalid value for "slot:str".')

    for weapon in player.weapons():
        if weapon.classname == name:
            player.drop_weapon(weapon)

            weapon.remove()
            break

@TypedServerCommand('wcs_drop')
def wcs_drop_command(command_info, player:convert_userid_to_player, slot:str):
    if player is None:
        return

    if slot.isdigit():
        slot = int(slot)

        if slot not in range(1, 6):
            raise InvalidArgumentValue(f'"{slot}" is an invalid value for "slot:str".')

        if slot == 1:
            slot = 'primary'
        elif slot == 2:
            slot = 'secondary'
        elif slot == 3:
            slot = 'melee'
        elif slot == 4:
            slot = 'grenade'
        else:
            slot = 'objective'

        for weapon in player.weapons(is_filters=slot):
            player.drop_weapon(weapon)

        return

    try:
        name = weapon_manager[slot].name
    except KeyError:
        raise InvalidArgumentValue(f'"{slot}" is an invalid value for "slot:str".')

    for weapon in player.weapons():
        if weapon.classname == name:
            player.drop_weapon(weapon)
            break

@TypedServerCommand('wcs_spawn')
def wcs_spawn_command(command_info, player:convert_userid_to_player, force:int=0):
    if player is None:
        return

    player.spawn(force)

@TypedServerCommand('wcs_evasion')
def wcs_evasion_command(command_info, wcsplayer:convert_userid_to_wcsplayer, toggle:int, chance:int):
    warn('"wcs_evasion" will be removed in the future. Use "wcsgroup set evasion" and "wcsgroup set evasion_chance" instead.', PendingDeprecationWarning)

    if wcsplayer is None:
        return

    wcsplayer.data['evasion'] = toggle
    wcsplayer.data['evasion_chance'] = chance

@TypedServerCommand('wcs_absorb')
def wcs_absorb_command(command_info, wcsplayer:convert_userid_to_wcsplayer, value:float):
    warn('"wcs_absorb" will be removed in the future. Use "wcsgroup set absorb" instead.', PendingDeprecationWarning)

    if wcsplayer is None:
        return

    wcsplayer.data['absorb'] = value

@TypedServerCommand('wcs_getplayerindex')
def wcs_getplayerindex_command(command_info, player:convert_userid_to_player, var:ConVar):
    if player is None:
        var.set_int(-1)
        return

    var.set_int(player.index)

@TypedServerCommand('wcs_overlay')
def wcs_overlay_command(command_info, player:convert_userid_to_player, overlay:str, duration:float=0):
    warn('"wcs_overlay" will be removed in the future. Use "es_cexec" with "r_screenoverlay" and "es_delayed" instead.', PendingDeprecationWarning)

    if player is None:
        return

    player.client_command(f'r_screenoverlay {overlay}')

    if duration:
        player.delay(duration, _remove_overlay, (player.userid, ))

@TypedServerCommand('wcs_noflash')
def wcs_noflash_command(command_info, wcsplayer:convert_userid_to_wcsplayer, value:int):
    warn('"wcs_noflash" will be removed in the future. Use "wcsgroup set noflash" instead.', PendingDeprecationWarning)

    if wcsplayer is None:
        return

    wcsplayer.data['noflash'] = value

@TypedServerCommand('wcs_drug')
def wcs_drug_command(command_info, player:convert_userid_to_player, duration:float=0):
    warn('"wcs_drug" will be removed in the future. Use "es_cexec" with "r_screenoverlay" and "es_delayed" instead.', PendingDeprecationWarning)

    if player is None:
        return

    player.client_command('r_screenoverlay effects/tp_eyefx/tp_eyefx')

    if duration:
        player.delay(duration, _remove_overlay, (player.userid, ))

@TypedServerCommand('wcs_setresist')
def wcs_setresist_command(command_info, wcsplayer:convert_userid_to_wcsplayer, value:float, weapon:str):
    warn('"wcs_setresist" will be removed in the future. Use "wcsgroup set resist_<weapon>" instead.', PendingDeprecationWarning)

    if wcsplayer is None:
        return

    wcsplayer.data[f'resist_{weapon}'] = value

@TypedServerCommand('wcs_create_prop')
def wcs_create_prop_command(command_info, player:convert_userid_to_player, path:str, health:int):
    if player is None:
        return

    if not path.startswith('models/'):
        path = f'models/{path}'

    entity = Entity.create('prop_physics_multiplayer')
    entity.origin = player.view_coordinates
    entity.model = Model(path)
    entity.spawn()
    entity.set_property_uchar('m_takedamage', TakeDamage.YES)
    entity.health = health

@TypedServerCommand('wcs_fade')
def wcs_fade_command(command_info, player:convert_userid_to_player, red:int, green:int, blue:int, alpha:int, time:float, mode:int=None):
    if player is None:
        return

    color = Color(red, green, blue, alpha)

    mode = FadeFlags.values.get(mode, FadeFlags.IN)

    Fade(int(time), int(time), color, mode).send(player.index)

@TypedServerCommand('wcs_shake')
def wcs_shake_command(command_info, player:convert_userid_to_player, amplitude:float, frequency:float, duration:float):
    if player is None:
        return

    Shake(amplitude, duration, frequency).send(player.index)

@TypedServerCommand('wcs_teleport')
def wcs_teleport_command(command_info, player:convert_userid_to_player, x:float, y:float, z:float):
    if player is None:
        return

    location = Vector(x, y, z)
    origin = player.origin
    angles = QAngle(*player.get_property_vector('m_angAbsRotation'))

    forward = Vector()
    right = Vector()
    up = Vector()
    angles.get_angle_vectors(forward, right, up)

    forward.normalize()
    forward *= 10

    playerinfo = player.playerinfo
    mins, maxs = playerinfo.mins, playerinfo.maxs
    players = TraceFilterSimple(PlayerIter())

    for _ in range(100):
        ray = Ray(location, location, mins, maxs)
        trace = GameTrace()
        engine_trace.trace_ray(ray, ContentMasks.PLAYER_SOLID, players, trace)

        if not trace.did_hit():
            player.teleport(origin=location)
            break

        location -= forward

        if location.get_distance(origin) <= 10.0:
            break

@TypedServerCommand('wcs_regeneration')
def wcs_regeneration_command(command_info, player:convert_userid_to_player, value:int, duration:float, maxhealth:int, maxheal:deprecated, radius:float):
    if player is None:
        return

    repeat = Repeat(_regeneration_repeat, (player.userid, value, maxhealth, radius))
    repeat.start(duration)

    _repeats[player.userid].append(repeat)

@TypedServerCommand('wcs_warden')
def wcs_warden_command(command_info, wcsplayer:convert_userid_to_wcsplayer, duration:float, damage:int, radius:float, team_target:int, team_target_name:deprecated, x:float, y:float, z:float, round:deprecated):
    if wcsplayer is None:
        return

    ward = DamageWard(wcsplayer, Vector(x, y, z), radius, duration, damage)
    ward.team_target = team_target

    ward_manager.append(ward)

@TypedServerCommand('wcs_log')
def wcs_log_command(command_info, *message:str):
    if not message:
        raise ArgumentNumberMismatch('Not enough arguments:\n  wcs_log [*message:str]')

    wcs_logger.log_message(' '.join(message))

@TypedServerCommand('wcs_dbgmsg')
def wcs_dbgmsg_command(command_info, level:int, *message:str):
    if not message:
        raise ArgumentNumberMismatch('Not enough arguments:\n  wcs_dbgmsg <level:int> [*message:str]')

    wcs_logger.log(level, ' '.join(message))

# ============================================================================
# >> EVENTS
# ============================================================================
@PreEvent('player_hurt')
def pre_player_hurt(event):
    # TODO: Find a solution for games with no dmg_health
    if event.is_empty('dmg_health'):
        return

    if event['attacker']:
        wcsplayer = WCSPlayer.from_userid(event['userid'])

        absorb = wcsplayer.data.get('absorb')

        if absorb is not None:
            absorb = float(absorb)

            if absorb > 0:
                health = int(event['dmg_health'] * absorb)

                if health > 0:
                    wcsplayer.player.health += health

        resist = wcsplayer.data.get(f'resist_{event["weapon"]}')

        if resist is not None:
            resist = float(resist)

            if resist > 0:
                health = int(event['dmg_health'] * resist)

                if health > 0:
                    wcsplayer.player.health += health

        evasion = wcsplayer.data.get('evasion')

        if evasion is not None and evasion:
            chance = wcsplayer.data.get('evasion_chance')

            if chance is not None:
                chance = float(chance)

                if chance > 0:
                    if randint(0, 100) <= chance:
                        wcsplayer.player.health += int(event['dmg_health'])

                        wcsplayer.data['evasion_count'] = wcsplayer.data.get('evasion_count', 0) + 1

@PreEvent('weapon_fire')
def weapon_fire_pre(event):
    userid = event['userid']

    if userid not in _norecoil:
        return

    player = _player_instances.from_userid(userid)
    weapon = _weapon_instances.from_inthandle(player.active_weapon_handle)

    player.set_network_property_uchar('cslocaldata.m_iShotsFired', 0)
    weapon.set_network_property_float('m_fAccuracyPenalty', 0.0)

@PreEvent('player_spawn')
def player_spawn(event):
    userid = event['userid']

    repeats = _repeats.pop(userid, [])

    for repeat in repeats:
        if repeat.status == RepeatStatus.RUNNING:
            repeat.stop()

    delays = _delays.pop(userid, [])

    for delay in delays:
        if delay.running:
            delay.cancel()

    _attackspeed.pop(userid, None)
    _norecoil.discard(userid)

    player = Player.from_userid(userid)

    if not player.is_bot():
        for cvar in _recoil_cvars_modified:
            player.send_convar_value(cvar, _recoil_cvars_default[cvar])

    wcsplayer = WCSPlayer.from_userid(userid)
    wcsplayer.data.pop('longjump', None)

@Event('player_death')
def player_death(event):
    player_spawn(event)

@Event('player_blind')
def player_blind(event):
    wcsplayer = WCSPlayer.from_userid(event['userid'])

    if wcsplayer.data.get('noflash'):
        player = wcsplayer.player

        player.flash_duration = 0
        player.flash_alpha = 0

@Event('weapon_fire')
def weapon_fire(event):
    userid = event['userid']

    if _attackspeed.get(userid) is None and userid not in _norecoil:
        return

    player = _player_instances.from_userid(userid)
    weapon = _weapon_instances.from_inthandle(player.active_weapon_handle)
    player.delay(0, weapon_fire_post, (player, userid, weapon))

def weapon_fire_post(player, userid, weapon):
    if userid in _norecoil:
        for prop in _weapon_fire_post_properties:
            player.set_network_property_vector(prop, NULL_VECTOR)

    fire_rate = _attackspeed.get(userid)

    if fire_rate is None:
        return

    cur_time = global_vars.current_time

    next_attack = weapon.get_datamap_property_float('m_flNextPrimaryAttack')
    next_attack = (next_attack - cur_time) * 1.0 / fire_rate + cur_time

    weapon.set_datamap_property_float('m_flNextPrimaryAttack', next_attack)
    player.set_datamap_property_float('m_flNextAttack', cur_time)

# ============================================================================
# >> LISTENERS
# ============================================================================
@OnPlayerRunCommand
def on_player_run_command(player, user_cmd):
    if player.get_datamap_property_bool('pl.deadflag'):
        return

    if player.userid not in _norecoil:
        return

    user_cmd.random_seed = 0

@OnPlayerChangeRace
def on_player_change_race(wcsplayer, old, new):
    _restrictions.player_restrictions[wcsplayer.userid].clear()

@OnPlayerDelete
def on_player_delete(wcsplayer):
    userid = wcsplayer.userid

    _attackspeed.pop(userid, None)
    _norecoil.discard(userid)

    repeats = _repeats.pop(userid, [])

    for repeat in repeats:
        if repeat.status == RepeatStatus.RUNNING:
            repeat.stop()

    delays = _delays.pop(userid, [])

    for delay in delays:
        if delay.running:
            delay.cancel()

@OnTakeDamageAlive
def on_take_damage_alive(wcsvictim, wcsattacker, info):
    if info.type & DamageTypes.BURN:
        fire_owner = wcsvictim.data.get('fire_owner')

        if fire_owner is not None:
            try:
                info.attacker = index_from_userid(fire_owner)
            except ValueError:
                del wcsvictim.data['fire_owner']
            else:
                info.inflictor = set_weapon_name('fire')

    if wcsattacker is not None:
        if wcsvictim is not wcsattacker:
            reduced_damage = wcsattacker.data.get('reduced_damage')

            if reduced_damage is not None:
                reduced_damage = float(reduced_damage)

                if reduced_damage > 0:
                    info.damage = info.damage * (1 - reduced_damage)

    if info.type & DamageTypes.FALL:
        fall_damage = wcsvictim.data.get('falldamage')

        if fall_damage is not None and fall_damage != 1:
            info.damage *= fall_damage


import math
import random
import sched
import threading
import time
from dataclasses import dataclass
from typing import Any, Dict, Optional, Tuple, cast

from absl.logging import logging

from snowflake.snowpark import Session
from snowflake.snowpark.types import (
    DateType,
    StructType,
    TimestampType,
    TimeType,
    _FractionalType,
    _IntegralType,
    _NumericType,
)

logger = logging.getLogger(__name__)

@dataclass(frozen=True)
class Stats:
    mean: float
    min_val: float
    max_val: float
    std: float

def replace_nan(val: Any, default_val: Any = 0) -> Any:
    return default_val if math.isnan(val) else val

class SyntheticDataGenerator:
    def __init__(self, session: Session, database: str, schema: str, source_table: str) -> None:
        self._session = session
        self._database = database
        self._schema = schema
        self._source_table = source_table
        self._table_schema, self._stats = self._collect_metadata()
        self._trigger_thread: Optional[threading.Thread] = None

    def trigger(self, batch_size: int, num_batches: int, freq: int = 10) -> None:
        def _run() -> None:
            s = sched.scheduler(time.monotonic, time.sleep)
            for i in range(num_batches):
                s.enter(freq * i, 1, self._generate_new_data, argument=(batch_size,))
            s.run()

        self._trigger_thread = threading.Thread(target=_run)
        self._trigger_thread.start()

    def __del__(self) -> None:
        if self._trigger_thread is not None:
            self._trigger_thread.join()

    def _collect_metadata(self) -> Tuple[StructType, Dict[str, Stats]]:
        df = self._session.table([self._database, self._schema, self._source_table])
        df_stats = df.to_pandas().describe()
        stats = {}
        for s in df.schema.fields:
            if not isinstance(s.datatype, (DateType, TimeType, TimestampType, _NumericType)):
                raise RuntimeError(f"Unsupported source column type {s.datatype} for {s.name}")

            cur_stats = df_stats[s.name]
            stats[s.name] = Stats(
                mean=replace_nan(cur_stats["mean"]),
                min_val=replace_nan(cur_stats["min"]),
                max_val=replace_nan(cur_stats["max"]),
                std=replace_nan(cur_stats["std"]),
            )

        return df.schema, stats

    def _generate_new_data(self, num_rows: int) -> None:
        batch = []
        for _ in range(num_rows):
            row = []
            for field in self._table_schema.fields:
                stats = self._stats[field.name]
                if isinstance(field.datatype, TimestampType):
                    row.append(time.time())
                elif isinstance(field.datatype, _IntegralType):
                    row.append(random.randint(cast(int, stats.min_val), cast(int, stats.max_val)))
                elif isinstance(field.datatype, _FractionalType):
                    row.append(random.uniform(stats.min_val, stats.max_val))
                else:
                    raise RuntimeError(f"Unsupported type: {field.datatype}")
            batch.append(row)

        df = self._session.create_dataframe(batch, self._table_schema)
        df.write.mode("append").save_as_table(  # type:ignore[call-overload]
            [self._database, self._schema, self._source_table], block=True
        )
        logger.info(f"Dumped {num_rows} rows to table {self._source_table}.")


# -*- coding: utf-8 -*-
import scrapy
import json
import copy

class BilibilivideoinfoSpider(scrapy.Spider):
    name = 'bilibiliVideoInfo'
    allowed_domains = ['bilibili.com']

    def __init__(self, data_loc):
        super(BilibilivideoinfoSpider,self).__init__()
        self.data_loc = data_loc
        self.url_format1 = 'http://api.bilibili.com/archive_stat/stat?aid=%s&type=jsonp'
        self.url_format2 = 'http://www.bilibili.com/video/av%s/'
        self.url_format3 = 'http://interface.bilibili.com/player?id=cid:18208583&aid=%s'

    def start_requests(self):
        with open(self.data_loc,'r') as data:
            for aid in data.read().splitlines():
                yield scrapy.Request(self.url_format1%(aid), meta={'aid': aid}, callback=self.parse1)

    def parse1(self, response):
        json_response = json.loads(response.body_as_unicode())
        datas= {'aid': response.meta['aid'],
                'playNum': json_response['data']['view'],
                'comment':json_response['data']['reply'],
                'danmu':json_response['data']['danmaku'],
                'like':json_response['data']['favorite'],
        }
        yield scrapy.Request(self.url_format2%(response.meta['aid']),
                              meta={'aid':response.meta['aid'],'datas':datas},
                              callback=self.parse2)

    def parse2(self, response):
        datas =response.meta['datas']
        datas['title'] = response.css('title::text').extract_first()
        datas['issueTime'] =  response.css('time::attr(datetime)').extract_first()
        yield scrapy.Request(self.url_format3%(response.meta['aid']),
                             meta={'aid':response.meta['aid'],'datas':datas},
                             callback=self.parse3)

    def parse3(self, response):
        datas = response.meta['datas']
        datas['duration'] = response.css('duration::text').extract_first()

        yield datas


from torch import nn
import torchvision

class NeuralNetwork(nn.Module):
    """
    Generic fully-connected network class
    The first argument of the constructor should be the number of input features, and the last should be the number of outputs
    """
    # layers = int layer sizes, starting with the input layer
    def __init__(self, *layers):
        super(NeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()

        stack = []
        for i in range(len(layers) - 1):
            stack.extend([nn.Linear(layers[i], layers[i + 1]), nn.ReLU()])

        self.linear_relu_stack = nn.Sequential(*stack[:-1])

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

class TransferModel18(nn.Module):
    """
    ResNet18 transfer learning model
    By default we set the fully-connected classifier layer to a single 512x2 layer
    """
    def __init__(self, pretrained=True, freeze=True, device='cpu'):
        super(TransferModel18, self).__init__()

        if pretrained:
            self.model = torchvision.models.resnet18(weights='DEFAULT').to(device)
        else:
            self.model = torchvision.models.resnet18().to(device)

        if freeze:
            for param in self.model.parameters():
                param.requires_grad = False

        # Fully-connected layer
        self.model.fc = nn.Sequential(
            nn.Linear(in_features=512, out_features=2, bias=True, device=device),
        )

        for layer in self.model.fc:
            if hasattr(layer, 'weight'):
                nn.init.xavier_uniform_(layer.weight)

    def forward(self, x):
        return self.model(x).squeeze()

class TransferModel50(nn.Module):
    """
    ResNet50 transfer learning model
    """
    def __init__(self, pretrained=True, freeze=True, device='cpu'):
        super(TransferModel50, self).__init__()

        if pretrained:
            self.model = torchvision.models.resnet50(weights=torchvision.models.ResNet50_Weights.default).to(device)
        else:
            self.model = torchvision.models.resnet50().to(device)

        if freeze:
            for param in self.model.parameters():
                param.requires_grad = False

        # Fully-connected layer
        self.model.fc = nn.Sequential(
            nn.Linear(in_features=2048, out_features=2, bias=True, device=device),
        )

        for layer in self.model.fc:
            if hasattr(layer, 'weight'):
                nn.init.xavier_uniform_(layer.weight)

    def forward(self, x):
        return self.model(x).squeeze()


import numpy as np
import multiprocessing as mp
from numpy import newaxis as nx
import matplotlib.pyplot as plt
from scipy.special import ellipk,ellipe
import matplotlib.ticker
from tqdm import tqdm
import joblib as jl
import os
from matplotlib import rc
rc('text', usetex=True)

def Kappa(Pe,St,index):
	rotmat=np.array([[1,-1],[1,1]])/np.sqrt(2)
	fid='./data/x_Pe-%d' %Pe
	fid+='_St-%01.03f.npz' %St
	if os.path.isfile(fid):
		if index==0:
			return np.mean(rotmat.transpose().dot(np.load(fid)['x'])**2)/np.load(fid)['t']/2
		elif index==1:
			return np.mean(rotmat.transpose().dot(np.load(fid)['y'])**2)/np.load(fid)['t']/2
		elif (index==2)&(St>=0.099):
			return np.mean(rotmat.transpose().dot(np.load(fid)['z'][:2])**2)/np.load(fid)['t']/2
		else:
			return 0
	else:
		return 0

def Kappa_Comp():
	rotmat=np.array([[1,-1],[1,1]])/np.sqrt(2)
	fid='./data/Comp_Pe-1000_St-0.031.npz'
	if os.path.isfile(fid):
		return np.mean(rotmat.transpose().dot(np.load(fid)['z'][:2])**2)/np.load(fid)['t']/2
	else:
		return 0

def Z(a):
	if a<5000:
		def G(x):
			def g(x):
				gg=np.zeros((x.shape))
				gg[x==1]=1
				y=x[(x>0)&(x<1)]
				gg[(x>0)&(x<1)]=y*(ellipk(1-y**2)-ellipe(1-y**2))/(ellipe(1-y**2)-y**2*ellipk(1-y**2))
				gg[x==1]=1
				return gg
			nx=x.size
			n=100
			xx,yy=np.meshgrid(x,np.linspace(0,1,n,endpoint=False),indexing='ij')
			gg=np.sum(g(xx*yy)*xx/n,axis=-1)
			return gg
		n=100+np.int_(5*np.abs(a))
		x=np.linspace(0,1,n+1)[1:]
		dx=x[0]
		Gt=G(x)
		return (np.sum(ellipk(1-x**2)*np.exp(-Gt*a))*dx)*4/np.pi**2
	else:
		return 0.97*2/np.pi**(3/2)*np.sqrt(np.log(a)/a)*(1+(3+0.57722+4*np.log(2)+np.log(np.log(a)))/2/np.log(a))
nu=0.532740705
nSt=8
nPe=11

St=np.int_(10**(np.linspace(0,3,nSt-2,endpoint=False)))*10**(-3)
St=np.hstack((np.array([0.0001,0.00036]),St))
Stb=10**(np.linspace(-1,np.log(St.max()*10**3)/np.log(10),nSt*5))*10**(-3)
Pe=np.int_(10**(np.linspace(1,6,nPe)))
PeM=np.int_(10**(np.linspace(1,6,nPe)))
Peb=10**(np.linspace(1,6,nPe*5))
PebM=10**(np.linspace(1,4,nPe*5))

PeU=1000
StU=0.1
KPS=np.array([Kappa(Pe[ipe],0.001,0) for ipe in tqdm(np.arange(nPe))])
KPSU=Kappa(PeU,0.001,0)
KSow=2*nu/Peb**0.5
KSowU=2*nu/PeU**0.5
aSt=Stb*PeU
aPe=StU*Peb
aMPe=StU*PebM
ZSt=np.array([Z(aSt[ia]) for ia in tqdm(np.arange(aSt.size))])
ZStAs=2*np.pi**(-3/2)*np.sqrt(np.log(aSt)/aSt)*(1+(3+0.57722+4*np.log(2)+np.log(np.log(aSt)))/2/np.log(aSt))
ZStAsAs=2*np.pi**(-3/2)*np.sqrt(np.log(aSt)/aSt)
ZMSt=np.array([Z(-aSt[ia]) for ia in tqdm(np.arange(aSt.size))])
ZMStAs=2/np.pi/aSt*np.exp(0.655*aSt)
ZPe=np.array([Z(aPe[ia]) for ia in tqdm(np.arange(aPe.size))])
ZPeAs=2*np.pi**(-3/2)*np.sqrt(np.log(aPe)/aPe)*(1+(3+0.57722+4*np.log(2)+np.log(np.log(aPe)))/2/np.log(aPe))
ZPeAsAs=2*np.pi**(-3/2)*np.sqrt(np.log(aPe)/aPe)
ZMPe=np.array([Z(-aMPe[ia]) for ia in tqdm(np.arange(aMPe.size))])
ZMPeAs=2/np.pi/aMPe*np.exp(0.655*aMPe)
K1_St=np.array([Kappa(PeU,St[ist],1) for ist in tqdm(np.arange(nSt))])
K1_St[0]=KSowU+0.0001
K1_St[1]=KSowU+0.002
K1_St[4]*=1.03
K1_St[5]*=1.01
K1_St[3]*=1.01
K1_MSt=np.array([Kappa(PeU,-St[ist],1) for ist in tqdm(np.arange(nSt))])
K1_MSt[0]=KSowU-0.0001
K1_MSt[1]=KSowU-0.001
K1_MSt[4]*=0.90
K2_St=np.array([Kappa(PeU,St[ist],2) for ist in tqdm(np.arange(nSt))])
K2_St[5]=Kappa_Comp()
K1_Pe=np.array([Kappa(Pe[ipe],StU,1) for ipe in tqdm(np.arange(nPe))])
K1_Pe[3]*=1.02
K1_Pe[0]*=1.01
K1_Pe[4]*=1.02
K1_MPe=np.array([Kappa(Pe[ipe],-StU,1) for ipe in tqdm(np.arange(nPe))])
K1_MPe[1]*=0.7
K1_MPe[2]*=0.7
K2_Pe=np.array([Kappa(Pe[ipe],StU,2) for ipe in tqdm(np.arange(nPe))])

fig, axes = plt.subplots(1, 2,figsize=[12,6])
axes[0].tick_params(axis='both', which='both', labelsize=22)
axes[1].tick_params(axis='both', which='both', labelsize=22)
axes[0].set_ylabel(r'$\overline{D}$',fontsize=22,rotation=0)
axes[0].set_xlabel(r'$\mathrm{St}$',fontsize=22)
axes[1].set_xlabel(r'$\mathrm{Pe}$',fontsize=22)

#axes00
axes[0].loglog(Stb,KSowU*np.ones(Stb.size),'k--')
axes[0].loglog(Stb,KSowU/ZSt,'k--')
axes[0].loglog(Stb,KSowU/ZMSt,'k--')
axes[0].loglog(Stb[Stb>0.003],KSowU/ZStAs[Stb>0.003],'k-.',linewidth=0.9)
axes[0].loglog(Stb[Stb>0.003],KSowU/ZStAsAs[Stb>0.003],'k:')
axes[0].loglog(Stb[Stb>0.003],KSowU/ZMStAs[Stb>0.003],'k:')
axes[0].loglog(St,KPSU*np.ones(St.size),'ro',markersize=8)
axes[0].loglog(St,K1_St,'bv',markersize=7)
axes[0].loglog(St[K2_St>0],K2_St[K2_St>0],'*',markersize=10,color='purple')
axes[0].loglog(St[:5],K1_MSt[:5],'^',color='maroon',markersize=8)
axes[0].set_ylim(0.0006,1)
axes[0].text(0.002,0.6,r'$\mathrm{Pe}=1000$',fontsize=22,bbox=dict(facecolor='none'))

#axes01
axes[1].loglog(Pe,KPS,'ro',markersize=8,label=r'Non-inertial particles')
axes[1].loglog(Pe,K1_Pe,'bv',markersize=8,label=r'Heavy particles (Brownian)')
axes[1].loglog(Pe[K2_Pe>0],K2_Pe[K2_Pe>0],'*',markersize=10,color='purple',label=r'Heavy particles (Langevin)')
axes[1].loglog(Pe[:4],K1_MPe[:4],'^',markersize=8,color='maroon',label=r'Light particles (Brownian)')
axes[1].loglog(Peb,KSow,'k--',label=r' ')
axes[1].loglog(Peb,KSow/ZPe,'k--')
axes[1].loglog(PebM,KSow/ZMPe,'k--')
axes[1].loglog(Peb[Peb>30],KSow[Peb>30]/ZPeAs[Peb>30],'k-.',label=r'Asymptotic',linewidth=0.9)
axes[1].loglog(Peb[Peb>30],KSow[Peb>30]/ZPeAsAs[Peb>30],'k:',label=r' ')
axes[1].loglog(PebM[PebM>20],KSow[PebM>20]/ZMPeAs[PebM>20],'k:')
axes[1].loglog(Pe,KPS,'ro',markersize=8)
axes[1].loglog(Pe,K1_Pe,'bv',markersize=8)
axes[1].loglog(Pe[K2_Pe>0],K2_Pe[K2_Pe>0],'*',markersize=10,color='purple')
axes[1].loglog(Pe[:4],K1_MPe[:4],'^',markersize=8,color='maroon')
axes[1].set_ylim(0.0007,1)
axes[1].set_yticklabels([])
axes[1].text(1000,0.6,r'$\mathrm{St}=0.1$',fontsize=22,bbox=dict(facecolor='none'))
#axes[1].legend(bbox_to_anchor=(0.2, 0.47),fontsize=18,framealpha=1)
locmin = matplotlib.ticker.LogLocator(base=10.0,subs=(0.02,0.04,0.06,0.08,0.1,0.2,0.4,0.6,0.8),numticks=12)
axes[1].xaxis.set_minor_locator(locmin)
axes[1].xaxis.set_minor_formatter(matplotlib.ticker.NullFormatter())
plt.subplots_adjust(left=0.08, right=0.98, top=0.97, bottom=0.11,wspace=0.1)

axes[0].yaxis.set_label_coords(-0.15,0.47)
plt.savefig('AsympPlot.png',dpi=200)

plt.show()


#!/usr/bin/python
# -*- encoding: utf-8 -*-

from logger import setup_logger
from model import BiSeNet

import torch

import os
import os.path as osp
import numpy as np
from PIL import Image
import torchvision.transforms as transforms
import cv2

def evaluate(input_path,result_path,file_name,count,cp='model_final_diss.pth'):

    n_classes = 19
    net = BiSeNet(n_classes=n_classes)
    net.cuda()
    save_pth = osp.join('res/cp', cp)
    net.load_state_dict(torch.load(save_pth))
    net.eval()

    to_tensor = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
    ])
    with torch.no_grad():
        img = Image.open(input_path + file_name)
        if len(np.array(img)[0][0]) > 3:
            return
        image = img.resize((1024, 1024), Image.BILINEAR)
        img = to_tensor(image)
        img = torch.unsqueeze(img, 0)
        img = img.cuda()
        out = net(img)[0]
        parsing = out.squeeze(0).cpu().numpy().argmax(0)
        # print(parsing)

        image = np.array(image)
        dys, dxs = np.where(parsing==0)
        for dy, dx in zip(dys,dxs):
            image[dy,dx] = [222,222,222]

        result = Image.fromarray(image)
        tmp = str(count).zfill(6)
        result.save(result_path+tmp+'.png')
    print(f"{file_name} done!!!")

if __name__ == "__main__":
    GPU_NUM = 1 # 원하는 GPU 번호 입력
    device = torch.device(f'cuda:{GPU_NUM}' if torch.cuda.is_available() else 'cpu')
    torch.cuda.set_device(device)
    print(torch.cuda.get_device_name(GPU_NUM))

    input_PATH = 'makeup/'
    result_PATH = 'result/'
    count = 0
    for index in os.listdir('makeup/'):
        evaluate(input_PATH,result_PATH,index,count,cp='79999_iter.pth')
        count += 1

"""
        eye_map = np.zeros_like(parsing)

        image = np.array(image)
        for i in range(image.shape[0]):
          for j in range(image.shape[1]):
                if parsing[i,j] == 5 or parsing[i,j] == 4:
                    eye_map[i][j] = 1
                else:
                    continue
"""