Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import minqlx
- import time
- import os
- import pysftp
- import re
- import codecs
- RECORDS_KEY = "minqlx:uberstats_records:{}"
- WEAPON_RECORDS = {
- "kill_machine": ["KILL MACHINE", "{:0.2f} frags/min"],
- "counterstrike": ["BEST COUNTERSTRIKE PLAYER", "{:0.2f} K/D ratio"],
- "most_damage": ["DESTRUCTICATOR", "{:,} dmg given"],
- "longest_spree": ["RAMBO", "{} kill streak"],
- "best_rail_accuracy": ["LASER EYES", "{:0.2f} percent rail accuracy"],
- "most_nade_kills": ["PINEAPPLE POWER", "{} grenade frags"],
- "most_pummels": ["PUMMEL LORD", "{} pummels"],
- "most_dmg_taken": ["BIGGEST PINCUSHION", "{:,} dmg taken"],
- "most_world_deaths": ["CLUMSIEST FOOL", "{:,} deaths by world"],
- "most_dmg_per_kill": ["GOOD SAMARITAN", "{:0.2f} damage per frag"]
- }
- FILE_PATTERN = re.compile('[\W_]+')
- class uberstats(minqlx.Plugin):
- def __init__(self):
- self.set_cvar_once("qlx_uberstats_sftp_hostname", "")
- self.set_cvar_once("qlx_uberstats_sftp_username", "")
- self.set_cvar_once("qlx_uberstats_sftp_password", "")
- self.set_cvar_once("qlx_uberstats_sftp_remote_path", "")
- self.sftp_hostname = self.get_cvar("qlx_uberstats_sftp_hostname")
- self.sftp_username = self.get_cvar("qlx_uberstats_sftp_username")
- self.sftp_password = self.get_cvar("qlx_uberstats_sftp_password")
- self.sftp_remote_path = self.get_cvar("qlx_uberstats_sftp_remote_path")
- self.add_command("score", self.cmd_score)
- self.add_command("highscores", self.cmd_highscores)
- self.add_command("clearhighscores", self.cmd_clear_highscores, 5)
- self.add_hook("stats", self.handle_stats)
- self.add_hook("map", self.handle_map)
- self.add_hook("game_end", self.handle_game_end)
- self.weapons = ["PLASMA", "ROCKET", "PROXMINE", "RAILGUN", "CHAINGUN", "NAILGUN", "GRENADE", "LIGHTNING", "SHOTGUN", "MACHINEGUN", "HMG", "BFG", "GAUNTLET"]
- self.weapon_sprees = ["PLASMORGASM", "ROCKET RENEGADE", "MINE MASTER", "RAIL RANGER", "CHAIN GANG", "GNARLY NAILER", "GRENADE GOON", "LIGHTNING LASHER", "SHOTGUN SAMURAI", "MACHINEGUN PECKER", "HMG HARASSER", "BFG BOSS", "GUANTLET GOD"]
- self.kill_streak = {}
- for weapon in self.weapons:
- self.kill_streak[weapon] = {}
- self.outputted_accuracy_players = []
- self.kamikaze_stats = {}
- self.best_kpm_names = []
- self.best_kpm = 0
- self.best_kd_names = []
- self.best_kd = 0
- self.most_damage_names = []
- self.most_damage = 0
- self.longest_spree_names = []
- self.longest_spree = 0
- self.best_rail_accuracy_names = []
- self.best_rail_accuracy = 0
- self.best_rail_hits = 0
- self.best_rail_shots = 0
- self.most_nade_kills_names = []
- self.most_nade_kills = 0
- self.most_pummels_names = []
- self.most_pummels = 0
- self.most_dmg_taken_names = []
- self.most_dmg_taken = 0
- self.world_death_types = ["UNKNOWN", "WATER", "SLIME", "LAVA", "CRUSH", "FALLING", "TRIGGER_HURT", "HURT"]
- self.world_death_stats = {}
- self.most_world_deaths_names = []
- self.most_world_deaths = 0
- self.most_dmg_per_kill_names = []
- self.most_dmg_per_kill = 0
- def cmd_score(self, player, msg, channel):
- if player.team != "spectator":
- sorted_players = sorted(self.players(), key = lambda p: p.stats.score, reverse=True)
- player_index = sorted_players.index(player) + 1
- player.tell("^2{} - ^3Score: ^7{} - ^3K/D: ^7{} - ^3DMG: ^7{} - ^3TIME: ^7{} - ^3PING: ^7{}".format(
- self.ordinal(player_index),
- player.stats.score,
- str(player.stats.kills) + "/" + str(player.stats.deaths),
- player.stats.damage_dealt,
- int((player.stats.time/(1000*60))%60),
- player.stats.ping
- )
- )
- def handle_stats(self, stats):
- if stats['DATA']['STEAM_ID'] != "0": #ignore games with bots in them
- if self.game is not None:
- if self.game.state == "in_progress":
- if stats['TYPE'] == "PLAYER_DEATH":
- victim_name = stats['DATA']['VICTIM']['NAME']
- #remove player from kill streak counters when they die
- for weapon in self.weapons:
- if self.kill_streak[weapon]:
- if victim_name in self.kill_streak[weapon]:
- self.kill_streak[weapon][victim_name] = 0
- #count player world deaths
- if stats['DATA']['MOD'] in self.world_death_types:
- victim_name = stats['DATA']['VICTIM']['NAME']
- if victim_name not in self.world_death_stats:
- self.world_death_stats[victim_name] = 1
- else:
- self.world_death_stats[victim_name] += 1
- elif stats['TYPE'] == "PLAYER_KILL" and stats['DATA']['MOD'] in self.weapons:
- killer_name = stats['DATA']['KILLER']['NAME']
- weapon = stats['DATA']['MOD']
- if killer_name != stats['DATA']['VICTIM']['NAME']:
- if killer_name not in self.kill_streak[weapon]:
- self.kill_streak[weapon][killer_name] = 1
- else:
- self.kill_streak[weapon][killer_name] += 1
- if self.kill_streak[weapon][killer_name] == 1:
- self.handle_kill_streak(killer_name, weapon)
- elif stats['TYPE'] == "PLAYER_KILL" and stats['DATA']['MOD'] == "KAMIKAZE":
- killer_name = stats['DATA']['KILLER']['NAME']
- if killer_name != stats['DATA']['VICTIM']['NAME']:
- if killer_name not in self.kamikaze_stats:
- self.kamikaze_stats[killer_name] = 1
- else:
- self.kamikaze_stats[killer_name] += 1
- if self.kamikaze_stats[killer_name] == 1:
- self.handle_kamikaze_stats(killer_name)
- if stats['TYPE'] == "PLAYER_STATS":
- #these stats come at end of game after MATCH_REPORT for each player
- if stats['DATA']['QUIT'] == 0 and stats['DATA']['WARMUP'] == 0:
- if stats['DATA']['PLAY_TIME'] >= 120:
- player_name = stats['DATA']['NAME']
- #player accuracies (sent to each player in tell)
- player = self.player(int(stats['DATA']['STEAM_ID']))
- #dont show if player is in spec, also handle multiple output bug as well
- if player.team != "spectator" and player.steam_id not in self.outputted_accuracy_players:
- accuracy_output = "^2YOUR ACCURACY:"
- for weapon in self.weapons:
- weapon_shots = stats['DATA']['WEAPONS'][weapon]["S"]
- weapon_hits = stats['DATA']['WEAPONS'][weapon]["H"]
- if weapon_shots > 0:
- if weapon_hits > 0:
- weapon_accuracy = 100 * (weapon_hits / weapon_shots)
- else:
- weapon_accuracy = 0.00
- accuracy_output += " - ^3{}: ^1{:0.2f}".format(weapon, weapon_accuracy)
- player.tell(accuracy_output)
- self.outputted_accuracy_players.append(player.steam_id)
- player_kpm = stats['DATA']['KILLS'] / (stats['DATA']['PLAY_TIME'] / 60)
- if stats['DATA']['DEATHS'] != 0: #we don't want to divide by 0!
- player_kd = stats['DATA']['KILLS'] / stats['DATA']['DEATHS']
- else:
- player_kd = stats['DATA']['KILLS']
- player_dmg = stats['DATA']['DAMAGE']['DEALT']
- player_longest_spree = stats['DATA']['MAX_STREAK']
- player_rail_hits = 0
- player_rail_shots = 0
- if stats['DATA']['WEAPONS']['RAILGUN']['S'] >= 15:
- player_rail_hits = stats['DATA']['WEAPONS']['RAILGUN']['H']
- player_rail_shots = stats['DATA']['WEAPONS']['RAILGUN']['S']
- player_rail_accuracy = 100 * (player_rail_hits / player_rail_shots)
- else:
- player_rail_accuracy = 0
- player_nade_kills = stats['DATA']['WEAPONS']['GRENADE']['K']
- player_pummels = stats['DATA']['WEAPONS']['GAUNTLET']['K']
- player_dmg_taken = stats['DATA']['DAMAGE']['TAKEN']
- player_dmg_per_kill = 0
- if stats['DATA']['KILLS'] > 0: #we don't want to divide by 0!
- player_dmg_per_kill = stats['DATA']['DAMAGE']['DEALT'] / stats['DATA']['KILLS']
- if not self.best_kpm_names:
- self.best_kpm_names = [player_name]
- self.best_kpm = player_kpm
- elif player_kpm > self.best_kpm:
- self.best_kpm_names = [player_name]
- self.best_kpm = player_kpm
- elif player_kpm == self.best_kpm:
- self.best_kpm_names.append(player_name)
- if not self.best_kd_names:
- self.best_kd_names = [player_name]
- self.best_kd = player_kd
- elif player_kd > self.best_kd:
- self.best_kd_names = [player_name]
- self.best_kd = player_kd
- elif player_kd == self.best_kd:
- self.best_kd_names.append(player_name)
- if not self.most_damage_names:
- self.most_damage_names = [player_name]
- self.most_damage = player_dmg
- elif player_dmg > self.most_damage:
- self.most_damage_names = [player_name]
- self.most_damage = player_dmg
- elif player_dmg == self.most_damage:
- self.most_damage_names.append(player_name)
- if not self.longest_spree:
- self.longest_spree_names = [player_name]
- self.longest_spree = player_longest_spree
- elif player_longest_spree > self.longest_spree:
- self.longest_spree_names = [player_name]
- self.longest_spree = player_longest_spree
- elif player_longest_spree == self.longest_spree:
- self.longest_spree_names.append(player_name)
- if not self.best_rail_accuracy_names:
- self.best_rail_accuracy_names = [player_name]
- self.best_rail_accuracy = player_rail_accuracy
- self.best_rail_hits = player_rail_hits
- self.best_rail_shots = player_rail_shots
- elif player_rail_accuracy > self.best_rail_accuracy:
- self.best_rail_accuracy_names = [player_name]
- self.best_rail_accuracy = player_rail_accuracy
- self.best_rail_hits = player_rail_hits
- self.best_rail_shots = player_rail_shots
- elif player_rail_accuracy == self.best_rail_accuracy:
- self.best_rail_accuracy_names.append(player_name)
- if not self.most_nade_kills_names:
- self.most_nade_kills_names = [player_name]
- self.most_nade_kills = player_nade_kills
- elif player_nade_kills > self.most_nade_kills:
- self.most_nade_kills_names = [player_name]
- self.most_nade_kills = player_nade_kills
- elif player_nade_kills == self.most_nade_kills:
- self.most_nade_kills_names.append(player_name)
- if not self.most_pummels_names:
- self.most_pummels_names = [player_name]
- self.most_pummels = player_pummels
- elif player_pummels > self.most_pummels:
- self.most_pummels_names = [player_name]
- self.most_pummels = player_pummels
- elif player_pummels == self.most_pummels:
- self.most_pummels_names.append(player_name)
- if not self.most_dmg_taken_names:
- self.most_dmg_taken_names = [player_name]
- self.most_dmg_taken = player_dmg_taken
- elif player_dmg_taken > self.most_dmg_taken:
- self.most_dmg_taken_names = [player_name]
- self.most_dmg_taken = player_dmg_taken
- elif player_dmg_taken == self.most_dmg_taken:
- self.most_dmg_taken_names.append(player_name)
- if not self.most_dmg_per_kill_names:
- self.most_dmg_per_kill_names = [player_name]
- self.most_dmg_per_kill = player_dmg_per_kill
- elif player_dmg_per_kill > self.most_dmg_per_kill:
- self.most_dmg_per_kill_names = [player_name]
- self.most_dmg_per_kill = player_dmg_per_kill
- elif player_dmg_per_kill == self.most_dmg_per_kill:
- self.most_dmg_per_kill_names.append(player_name)
- @minqlx.delay(2)
- @minqlx.thread
- def handle_game_end(self, data):
- if not data["ABORTED"] and self.best_kpm:
- self.msg("^5***UBERSTATS***")
- stats_output = "^1KILL MACHINE: "
- record_response = ""
- for i, player_name in enumerate(self.best_kpm_names):
- record_response = self.check_record("kill_machine", float(self.best_kpm), player_name)
- stats_output += "^7" + player_name
- if len(self.best_kpm_names) > 1 and len(self.best_kpm_names) - 1 != i:
- stats_output += ", "
- stats_output += "^2 - {:0.2f} frags/min".format(self.best_kpm)
- self.msg(record_response + stats_output)
- stats_output = "^1BEST COUNTERSTRIKE PLAYER: "
- record_response = ""
- for i, player_name in enumerate(self.best_kd_names):
- record_response = self.check_record("counterstrike", float(self.best_kd), player_name)
- stats_output += "^7" + player_name
- if len(self.best_kd_names) > 1 and len(self.best_kd_names) - 1 != i:
- stats_output += ", "
- stats_output += "^2 - {:0.2f} K/D ratio".format(self.best_kd)
- self.msg(record_response + stats_output)
- stats_output = "^1DESTRUCTICATOR: "
- record_response = ""
- for i, player_name in enumerate(self.most_damage_names):
- record_response = self.check_record("most_damage", float(self.most_damage), player_name)
- stats_output += "^7" + player_name
- if len(self.most_damage_names) > 1 and len(self.most_damage_names) - 1 != i:
- stats_output += ", "
- stats_output += "^2 - {:,} dmg given".format(self.most_damage)
- self.msg(record_response + stats_output)
- time.sleep(3)
- if self.longest_spree > 1:
- stats_output = "^1RAMBO: "
- record_response = ""
- for i, player_name in enumerate(self.longest_spree_names):
- record_response = self.check_record("longest_spree", float(self.longest_spree), player_name)
- stats_output += "^7" + player_name
- if len(self.longest_spree_names) > 1 and len(self.longest_spree_names) - 1 != i:
- stats_output += ", "
- stats_output += "^2 - {} kill streak".format(self.longest_spree)
- self.msg(record_response + stats_output)
- if self.best_rail_accuracy > 0:
- stats_output = "^1LASER EYES: "
- record_response = ""
- for i, player_name in enumerate(self.best_rail_accuracy_names):
- record_response = self.check_record("best_rail_accuracy", float(self.best_rail_accuracy), player_name)
- stats_output += "^7" + player_name
- if len(self.best_rail_accuracy_names) > 1 and len(self.best_rail_accuracy_names) - 1 != i:
- stats_output += ", "
- stats_output += "^2 - {:0.2f} percent rail accuracy ({} hits / {} shots)".format(self.best_rail_accuracy, self.best_rail_hits, self.best_rail_shots)
- self.msg(record_response + stats_output)
- if self.most_nade_kills > 0:
- stats_output = "^3PINEAPPLE POWER: "
- record_response = ""
- for i, player_name in enumerate(self.most_nade_kills_names):
- record_response = self.check_record("most_nade_kills", float(self.most_nade_kills), player_name)
- stats_output += "^7" + player_name
- if len(self.most_nade_kills_names) > 1 and len(self.most_nade_kills_names) - 1 != i:
- stats_output += ", "
- stats_output += "^2 - {} grenade frags".format(self.most_nade_kills)
- self.msg(record_response + stats_output)
- time.sleep(2)
- if self.most_pummels > 0:
- stats_output = "^1PUMMEL LORD: "
- record_response = ""
- for i, player_name in enumerate(self.most_pummels_names):
- record_response = self.check_record("most_pummels", float(self.most_pummels), player_name)
- stats_output += "^7" + player_name
- if len(self.most_pummels_names) > 1 and len(self.most_pummels_names) - 1 != i:
- stats_output += ", "
- stats_output += "^2 - {} pummels".format(self.most_pummels)
- self.msg(record_response + stats_output)
- stats_output = "^6BIGGEST PINCUSHION: "
- record_response = ""
- for i, player_name in enumerate(self.most_dmg_taken_names):
- record_response = self.check_record("most_dmg_taken", float(self.most_dmg_taken), player_name)
- stats_output += "^7" + player_name
- if len(self.most_dmg_taken_names) > 1 and len(self.most_dmg_taken_names) - 1 != i:
- stats_output += ", "
- stats_output += "^2 - {:,} ^6dmg taken".format(self.most_dmg_taken)
- self.msg(record_response + stats_output)
- stats_output = "^6CLUMSIEST FOOL: "
- record_response = ""
- for name, world_deaths in self.world_death_stats.items():
- if not self.most_world_deaths_names:
- self.most_world_deaths_names = [name]
- self.most_world_deaths = world_deaths
- elif world_deaths > self.most_world_deaths:
- self.most_world_deaths_names = [name]
- self.most_world_deaths = world_deaths
- elif world_deaths == self.most_world_deaths:
- self.most_world_deaths_names.append(name)
- if self.most_world_deaths > 0:
- for i, player_name in enumerate(self.most_world_deaths_names):
- record_response = self.check_record("most_world_deaths", float(self.most_world_deaths), player_name)
- stats_output += "^7" + player_name
- if len(self.most_world_deaths_names) > 1 and len(self.most_world_deaths_names) - 1 != i:
- stats_output += ", "
- stats_output += "^2 - {:,} deaths by world".format(self.most_world_deaths)
- self.msg(record_response + stats_output)
- time.sleep(2)
- if self.game.type not in ["Duel", "Instagib"]:
- stats_output = "^6GOOD SAMARITAN: "
- record_response = ""
- for i, player_name in enumerate(self.most_dmg_per_kill_names):
- record_response = self.check_record("most_dmg_per_kill", float(self.most_dmg_per_kill), player_name)
- stats_output += "^7" + player_name
- if len(self.most_dmg_per_kill_names) > 1 and len(self.most_dmg_per_kill_names) - 1 != i:
- stats_output += ", "
- stats_output += "^2 - {:0.2f} damage per frag".format(self.most_dmg_per_kill)
- self.msg(record_response + stats_output)
- if self.sftp_hostname:
- self.high_scores("endgame")
- @minqlx.delay(8)
- def handle_kill_streak(self, player_name, weapon):
- if int(self.kill_streak[weapon][player_name]) >= 4:
- self.play_sound("sound/uberstats/{}.ogg".format(weapon.lower()))
- self.center_print("{}^1 {}".format(player_name, self.weapon_sprees[self.weapons.index(weapon)]))
- self.msg("{} ^1{}: ^2({} {} frags in 8s)".format(player_name, self.weapon_sprees[self.weapons.index(weapon)], self.kill_streak[weapon][player_name], weapon))
- self.kill_streak[weapon][player_name] = 0
- @minqlx.delay(5)
- def handle_kamikaze_stats(self, player_name):
- kami_msg = "{}^7's ^3 KAMI: ^7{} ^1FRAGS".format(player_name, self.kamikaze_stats[player_name])
- self.center_print(kami_msg)
- self.msg(kami_msg)
- self.kamikaze_stats[player_name] = 0
- @minqlx.delay(10)
- def handle_map(self, mapname, factory):
- self.best_kpm_names = []
- self.best_kpm = 0
- self.best_kd_names = []
- self.best_kd = 0
- self.most_damage_names = []
- self.most_damage = 0
- self.longest_spree_names = []
- self.longest_spree = 0
- self.best_rail_accuracy_names = []
- self.best_rail_accuracy = 0
- self.best_rail_hits = 0
- self.best_rail_shots = 0
- self.most_nade_kills_names = []
- self.most_nade_kills = 0
- self.most_pummels_names = []
- self.most_pummels = 0
- self.most_dmg_taken_names = []
- self.most_dmg_taken = 0
- self.world_death_stats = {}
- self.most_world_deaths_names = []
- self.most_world_deaths = 0
- self.most_dmg_per_kill_names = []
- self.most_dmg_per_kill = 0
- self.kamikaze_stats = {}
- for weapon in self.weapons:
- self.kill_streak[weapon] = {}
- self.outputted_accuracy_players = []
- self.high_scores("triggered")
- def ordinal(self, value):
- try:
- value = int(value)
- except ValueError:
- return value
- if value % 100//10 != 1:
- if value % 10 == 1:
- ordval = u"%d%s" % (value, "st")
- elif value % 10 == 2:
- ordval = u"%d%s" % (value, "nd")
- elif value % 10 == 3:
- ordval = u"%d%s" % (value, "rd")
- else:
- ordval = u"%d%s" % (value, "th")
- else:
- ordval = u"%d%s" % (value, "th")
- return ordval
- def check_record(self, record_name, score, player_name):
- current_record = self.db.get(RECORDS_KEY.format(record_name) + ":high_score")
- if current_record is None:
- current_record = 0
- else:
- current_record = float(current_record)
- if score > current_record:
- self.db.set(RECORDS_KEY.format(record_name) + ":high_score", score)
- self.db.delete(RECORDS_KEY.format(record_name) + ":players")
- self.db.sadd(RECORDS_KEY.format(record_name) + ":players", player_name)
- self.play_sound("sound/vo_evil/new_high_score")
- return "^5NEW HIGH SCORE! - "
- elif score == current_record:
- self.db.sadd(RECORDS_KEY.format(record_name) + ":players", player_name)
- return "^5TIED HIGH SCORE! - "
- else:
- return ""
- def cmd_highscores(self, player, msg, channel):
- self.high_scores("triggered")
- @minqlx.thread
- def high_scores(self, method):
- if method == "triggered" and float(self.db.get(RECORDS_KEY.format("kill_machine") + ":high_score")) is not None:
- self.msg("^5***UBERSTATS HIGH SCORES***")
- elif method == "endgame":
- html = "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>\n" + \
- "<script>\n" + \
- "$(function(){\n"
- for key, val in WEAPON_RECORDS.items():
- high_score = float(self.db.get(RECORDS_KEY.format(key) + ":high_score"))
- if high_score is not None:
- players = ", ".join(self.db.smembers(RECORDS_KEY.format(key) + ":players"))
- if method == "triggered":
- self.msg("^1{} - ^7{} ^2- {}".format(val[0], players, val[1].format(high_score)).replace(".00", "").replace(".0", ""))
- elif method == "endgame":
- html += "$('.{}_record').text('{}');\n".format(key, val[1].format(high_score).replace(".00", "").replace(".0", ""))
- html += "$('.{}_players').text('{}');\n\n".format(key, players)
- if method == "endgame":
- html += "});\n</script>"
- #make nice filename from hostname
- uberfilename = re.sub(' +', '_', (re.sub("[^a-zA-Z.\d\s]", "", self.game.hostname) + "-uberstats.html").lower())
- f = codecs.open(uberfilename, "w+", "utf-8")
- f.write(html)
- f.close()
- cnopts = pysftp.CnOpts()
- cnopts.hostkeys = None
- srv = pysftp.Connection(host = self.sftp_hostname, username = self.sftp_username, password = self.sftp_password, cnopts=cnopts)
- srv.chdir(self.sftp_remote_path)
- srv.put(uberfilename)
- def cmd_clear_highscores(self, player, msg, channel):
- for key, val in WEAPON_RECORDS.items():
- self.db.delete(RECORDS_KEY.format(key) + ":players")
- self.db.delete(RECORDS_KEY.format(key) + ":high_score")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement