Files
my-botty/src/game_stats.py
T

240 lines
9.3 KiB
Python

import math
import numpy as np
import time
import threading
import inspect
from beautifultable import BeautifulTable
from logger import Logger
from config import Config
from messages import Messenger
from utils.misc import hms
from utils.levels import get_level
from version import __version__
from ui import player_bar
class GameStats:
def __init__(self):
self._messenger = Messenger()
self._start_time = time.time()
self._timer = None
self._timepaused = None
self._paused = False
self._game_counter = 0
self._chicken_counter = 0
self._death_counter = 0
self._merc_death_counter = 0
self._runs_failed = 0
self._run_counter = 1
self._consecutive_runs_failed = 0
self._failed_game_time = 0
self._location = None
self._location_stats = {}
self._location_stats["totals"] = { "items": 0, "deaths": 0, "chickens": 0, "merc_deaths": 0, "failed_runs": 0 }
self._stats_filename = f'stats_{time.strftime("%Y%m%d_%H%M%S")}.log'
self._nopickup_active = False
self._starting_exp = 0
self._current_exp = 1
self._current_lvl = 0
def update_location(self, loc: str):
if self._location != loc:
self._location = str(loc)
self.populate_location_stat()
def populate_location_stat(self):
if self._location not in self._location_stats:
self._location_stats[self._location] = { "items": [], "deaths": 0, "chickens": 0, "merc_deaths": 0, "failed_runs": 0 }
def log_item_keep(self, item_name: str, send_message: bool, img: np.ndarray, ocr_text: str = '', expression: str = '', item_props: dict = {}):
filtered_substrings = [" POTION", " OF IDENTIFY", " OF TOWN PORTAL", " AMETHYST", " RUBY", " TOPAZ", " EMERALD", " SAPPHIRE", " DIAMOND"]
filtered_matches = ["DIAMOND", "AMETHYST", "RUBY", "TOPAZ", "EMERALD", "SAPPHIRE", "ARROWS", "BOLTS",
"CHIPPED SKULL", "FLAWED SKULL", "SKULL", "FLAWLESS SKULL", "PERFECT SKULL"]
skip_log = any(substring in item_name for substring in filtered_substrings) or any(match == item_name.strip() for match in filtered_matches)
if self._location is not None and not skip_log:
Logger.debug(f"Stashed and logged: {item_name}")
self._location_stats[self._location]["items"].append(item_name)
self._location_stats["totals"]["items"] += 1
if send_message and self._messenger.enabled and not skip_log:
if expression[0] != "@":
self._messenger.send_item(item_name, img, self._location, ocr_text, expression, item_props)
def log_death(self, img: str):
self._death_counter += 1
if self._location is not None:
self._location_stats[self._location]["deaths"] += 1
self._location_stats["totals"]["deaths"] += 1
if self._messenger.enabled:
self._messenger.send_death(self._location, img)
def log_chicken(self, img: str):
self._chicken_counter += 1
if self._location is not None:
self._location_stats[self._location]["chickens"] += 1
self._location_stats["totals"]["chickens"] += 1
if Config().general["discord_log_chicken"] and self._messenger.enabled:
self._messenger.send_chicken(self._location, img)
def log_merc_death(self):
self._merc_death_counter += 1
if self._location is not None:
self._location_stats[self._location]["merc_deaths"] += 1
self._location_stats["totals"]["merc_deaths"] += 1
def log_start_game(self):
if self._game_counter > 0:
self._save_stats_to_file()
if Config().general["discord_status_count"] and self._game_counter % Config().general["discord_status_count"] == 0:
# every discord_status_count game send a message update about current status
self._send_status_update()
self._game_counter += 1
self._timer = time.time()
Logger.info(f"Starting game #{self._game_counter}")
def log_end_game(self, failed: bool = False):
elapsed_time = 0
if self._timer is not None:
elapsed_time = time.time() - self._timer
self._timer = None
if failed:
self._runs_failed += 1
self._consecutive_runs_failed += 1
if self._location is not None:
self._location_stats[self._location]["failed_runs"] += 1
self._location_stats["totals"]["failed_runs"] += 1
self._failed_game_time += elapsed_time
Logger.warning(f"End failed game: Elapsed time: {elapsed_time:.2f}s Fails: {self._consecutive_runs_failed}")
else:
self._consecutive_runs_failed = 0
Logger.info(f"End game. Elapsed time: {elapsed_time:.2f}s")
def log_exp(self):
exp = player_bar.get_experience()
if exp[1] > 0:
curr_lvl = get_level(exp[1])['lvl']
if curr_lvl > 0:
self._current_lvl = curr_lvl-1
if self._starting_exp == 0:
self._starting_exp = exp[0]
if exp[0] > 0:
self._current_exp = exp[0]
def pause_timer(self):
if self._timer is None or self._paused:
return
self._timepaused = time.time()
self._paused = True
def resume_timer(self):
if self._timer is None or not self._paused:
return
pausetime = time.time() - self._timepaused
self._timer = self._timer + pausetime
self._paused = False
def get_current_game_length(self):
if self._timer is None:
return 0
if self._paused:
return self._timepaused - self._timer
else:
return time.time() - self._timer
def get_consecutive_runs_failed(self):
return self._consecutive_runs_failed
def _create_msg(self):
elapsed_time = time.time() - self._start_time
elapsed_time_str = hms(elapsed_time)
avg_length_str = "n/a"
good_games_count = self._game_counter - self._runs_failed
good_games_time = elapsed_time - self._failed_game_time
if good_games_count == 0:
good_games_count = 1
avg_length = good_games_time / float(good_games_count)
avg_length_str = hms(avg_length)
curr_lvl = get_level(self._current_lvl)
msg = f'\nSession length: {elapsed_time_str}'
msg += f'\nGames: {self._game_counter}'
msg += f'\nAvg Game Length: {avg_length_str}'
msg += f'\nCurrent Level: {curr_lvl["lvl"]}'
if curr_lvl["lvl"] < 99:
try:
exp_gained = self._current_exp - curr_lvl['exp']
per_to_lvl = exp_gained / curr_lvl["xp_to_next"]
gained_exp = self._current_exp - self._starting_exp
exp_per_second = gained_exp / good_games_time
exp_per_hour = round(exp_per_second * 3600, 1)
exp_per_game = round(gained_exp / float(good_games_count), 1)
exp_needed = curr_lvl['xp_to_next'] - exp_gained
time_to_lvl = exp_needed / exp_per_second
games_to_lvl = exp_needed / exp_per_game
msg += f'\nPercent to Level: {math.ceil(per_to_lvl*100)}%'
msg += f'\nXP Gained: {gained_exp:,}'
msg += f'\nXP Per Hour: {exp_per_hour:,}'
msg += f'\nXP Per Game: {exp_per_game:,}'
msg += f'\nTime Needed To Level: {hms(time_to_lvl)}'
msg += f'\nGames Needed To Level: {math.ceil(games_to_lvl):,}'
except:
Logger.warning("Failed to log exp")
table = BeautifulTable()
table.set_style(BeautifulTable.STYLE_BOX_ROUNDED)
for location in self._location_stats:
if location == "totals":
continue
stats = self._location_stats[location]
table.rows.append([location, len(stats["items"]), stats["chickens"], stats["deaths"], stats["merc_deaths"], stats["failed_runs"]])
table.rows.append([
"T",
self._location_stats["totals"]["items"],
self._location_stats["totals"]["chickens"],
self._location_stats["totals"]["deaths"],
self._location_stats["totals"]["merc_deaths"],
self._location_stats["totals"]["failed_runs"]
])
table.columns.header = ["Run", "I", "C", "D", "MD", "F"]
msg += f"\n{str(table)}\n"
return msg
def _send_status_update(self):
msg = f"Status Report\n{self._create_msg()}\nVersion: {__version__}"
if self._messenger.enabled:
self._messenger.send_message(msg)
def _save_stats_to_file(self):
msg = self._create_msg()
msg += "\nItems:"
for location in self._location_stats:
if location == "totals":
continue
stats = self._location_stats[location]
msg += f"\n {location}:"
for item_name in stats["items"]:
msg += f"\n {item_name}"
with open(file=f"log/stats/{self._stats_filename}", mode="w+", encoding="utf-8") as f:
f.write(msg)
if __name__ == "__main__":
game_stats = GameStats()
game_stats.log_item_keep("rune_12", True)
game_stats._save_stats_to_file()