import time from core.chat_blob import ChatBlob from core.command_alias_service import CommandAliasService from core.command_param_types import Any from core.decorators import instance, command, event, setting from core.dict_object import DictObject from core.igncore import IgnCore from core.job_scheduler import JobScheduler from core.logger import Logger from core.message_hub_service import MessageHubService from core.setting_service import SettingService from core.setting_types import BooleanSettingType, TextSettingType from core.text import Text from core.util import Util from modules.standard.datanet.ws_controller import WebsocketRelayController @instance() class WorldBossController: # Timers are provided through a local websocket relay, which gets fed by an external API. # example timer data: # [{"name":"Tarasque","time": }, # {"name":"Vizaresh","time": }] timer_data = [] alerts = [480 * 60, 360 * 60, 240 * 60, 120 * 60, 60 * 60, 60 * 15, 60 * 5, 60 * 3, 60 * 2, 60, 30, 15, 5, 0] jobs = [] def inject(self, registry): self.logger = Logger(__name__) self.bot: IgnCore = registry.get_instance("bot") self.text: Text = registry.get_instance("text") self.util: Util = registry.get_instance("util") self.command_alias_service: CommandAliasService = registry.get_instance("command_alias_service") self.job_scheduler: JobScheduler = registry.get_instance("job_scheduler") self.setting_service: SettingService = registry.get_instance("setting_service") self.relay_hub: MessageHubService = registry.get_instance("message_hub_service") def pre_start(self): self.relay_hub.register_message_source("timers") self.setting_service.register(self.module_name, 'timer_spam', False, BooleanSettingType(), "should timers be spammed") self.command_alias_service.add_alias("tara", "wb tara") self.command_alias_service.add_alias("gaunt", "wb gaunt") self.command_alias_service.add_alias("loren", "wb loren") self.command_alias_service.add_alias("reaper", "wb reaper") @event(WebsocketRelayController.WS_RELAY, "save most current local_timers") def get_timer(self, _, data): if data.type != "timer": return local_timers = {} spam = True if self.setting_service.get_value("timer_spam") == "1" else False for x in self.timer_data: local_timers[x['name']] = x['time'] for row in data.payload: spawn = self.get_spawn(row) if not spawn: continue if row['name'] not in local_timers: self.timer_data.append(row) if spam: for x in self.jobs: if x['name'] == row['name']: self.job_scheduler.cancel_job(x['id']) self.jobs.append( {'name': row['name'], 'id': self.job_scheduler.delayed_job(self.timer_alert, 2, spawn)}) continue elif (local_timers[row['name']] + 1) < row['time']: for timer in self.timer_data: if timer['name'] == row['name']: timer['time'] = row['time'] if spam: for job in self.jobs: if job['name'] == row['name']: self.job_scheduler.cancel_job(job['id']) alert_duration = self.get_next_alert(spawn.at - time.time()) job_id = self.job_scheduler.scheduled_job(self.timer_alert, 2, spawn) job['id'] = job_id def get_spawn(self, timer): timer = DictObject(timer) if timer.name == "Loren Warr": data = self.calc_spawn_mortal(timer.time, 9 * 60 * 60, 15 * 60) elif timer.name == "Tarasque": data = self.calc_spawn_mortal(timer.time, 9 * 60 * 60, 30 * 60) elif timer.name == "The Hollow Reaper": data = self.calc_spawn_mortal(timer.time, 9 * 60 * 60, 15 * 60) elif timer.name == "Vizaresh": data = self.calc_spawn_mortal(timer.time, 17 * 60 * 60, 6 * 60) else: return None if not data: return if data.spawn < time.time(): if data.mortal > time.time(): return DictObject( {'name': timer.name, 'type': 'mortal', 'time': data.mortal - time.time(), 'at': data.mortal, 'data': data}) return DictObject( {'name': timer.name, 'type': 'spawn', 'time': data.spawn - time.time(), 'at': data.spawn, 'data': data}) def calc_spawn_mortal(self, last, respawn, immortal): pop = last + respawn attackable = pop + immortal now = time.time() # Mortal if immortal > attackable - now > 0: return DictObject({'spawn': pop, 'mortal': attackable}) elif immortal > attackable + respawn - now > 0: return DictObject({'spawn': pop + respawn, 'mortal': attackable + respawn}) # Spawn elif respawn > (pop - now) > 0: return DictObject({'spawn': pop, 'mortal': pop}) elif respawn > pop + respawn - now > 0: return DictObject({'spawn': attackable + respawn, 'mortal': attackable + respawn + immortal}) elif last - now > 0: return DictObject({'spawn': last - immortal, 'mortal': last}) def send_warn(self, msg): self.relay_hub.send_message("timers", None, f"[WB] {msg}", f"[WB] {msg}") def get_next_alert(self, duration): for alert in self.alerts: if duration - 1 > alert: return duration - alert - 1 return duration @command(command="wb", params=[Any("worldboss", is_optional=True)], description="Displays the next worldboss spawns", access_level="member") def show_worldboss(self, request, boss: str): if boss: boss = boss.lower() if boss in ["tara", "tarasque"]: boss = "Tarasque" elif boss in ["viza", "vizaresh", "gaunt", "gauntlet"]: boss = "Vizaresh" elif boss in ["loren", "loren warr", "loren war", "lw"]: boss = "Loren Warr" elif boss in ["thr", "the hollow", "the hollow reaper", "reaper"]: boss = "The Hollow Reaper" for x in self.timer_data: if x['name'] == boss: return self.show_user(x) else: blob = "\n".join([self.show_user(x) for x in self.timer_data if self.show_user(x) != "No timers cached; please try again later."]) return ChatBlob("Next Worldboss spawns", blob) def show_user(self, timer): timer = self.get_spawn(timer) if not timer: return "No timers cached; please try again later." if timer.type == "mortal": return f"{timer.name} :: mortal in {self.util.format_time(timer.time)}" elif timer.type == "spawn": return f"{timer.name} :: spawn in {self.util.format_time(timer.time)}" def timer_alert(self, t, timer): if self.setting_service.get_value("timer_spam") == "0": return for row in self.timer_data: if row["name"] == timer.name: timer = self.get_spawn(row) if not timer.at: return alert_duration = self.get_next_alert(timer.at - time.time()) if timer.at - time.time() < 1: if timer.type == "mortal": self.send_warn(f"{timer.name} :: is now mortal") self.jobs = [x for x in self.jobs if x['name'] != timer.name] elif timer.type == "spawn": self.send_warn(f"{timer.name} :: has just spawned") self.jobs = [x for x in self.jobs if x['name'] != timer.name] else: if timer.type == "mortal": self.send_warn(f"{timer.name} :: mortal in {self.util.format_time(timer.time)}") elif timer.type == "spawn": if alert_duration > 60 * 2: self.send_warn( f"{timer.name} :: spawn in {self.util.format_time(timer.time)}") for row in self.timer_data: if row["name"] == timer.name: timer = self.get_spawn(row) job_id = self.job_scheduler.scheduled_job(self.timer_alert, time.time() + alert_duration, timer) for job in self.jobs: if job['name'] == timer.name: job['id'] = job_id return self.jobs.append({'name': timer.name, 'id': job_id}) # @setting('alert_times', '[480 * 60, 360 * 60, 240 * 60, 120 * 60, 60 * 60, 60 * 15, 60 * 5, 60 * 3, 60 * 2, 60, 30, 15, 5, 0]', 'Worldboss timer spam messages (ETA and actual)') # def alert_times(self): # return TextSettingType(['[480 * 60, 360 * 60, 240 * 60, 120 * 60, 60 * 60, 60 * 15, 60 * 5, 60 * 3, 60 * 2, 60, 30, 15, 5, 0]'])