import time from typing import Union from core.aochat import server_packets from core.aochat.BaseModule import BaseModule from core.buddy_service import BuddyService from core.chat_blob import ChatBlob from core.command_param_types import Const, Int, Any, Options, Character from core.command_request import CommandRequest from core.db import DB from core.decorators import instance, command, timerevent, event from core.event_service import EventService from core.lookup.character_service import CharacterService from core.lookup.pork_service import PorkService from core.private_channel_service import PrivateChannelService from core.sender_obj import SenderObj from core.setting_service import SettingService from core.text import Text from core.igncore import IgnCore from core.util import Util from modules.core.accounting.services.account_service import AccountService from modules.raidbot.raid.preset_controller import PresetController from modules.standard.raid.leader_controller import LeaderController class Raider: def __init__(self, alts, active): self.main_id = alts[0].char_id self.alts = alts self.active_id = active self.accumulated_points = 0 self.is_active = True self.left_raid = None self.was_kicked = None self.was_kicked_reason = None def get_active_char(self): for alt in self.alts: if self.active_id == alt.char_id: return alt return None class Raid: def __init__(self, raid_name, started_by, raiders=None): self.raid_name = raid_name self.desc = raid_name self.started_at = int(time.time()) self.started_by = started_by self.raiders = raiders or [] self.is_open = True self.raid_orders = None self.level = 180 @instance() class RaidbotController(BaseModule): NO_RAID_RUNNING_RESPONSE = "No raid is running." def __init__(self): self.raid = None def inject(self, registry): self.bot: IgnCore = registry.get_instance("bot") self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.setting_service: SettingService = registry.get_instance("setting_service") self.character_service: CharacterService = registry.get_instance("character_service") self.preset_controller: PresetController = registry.get_instance("preset_controller") self.util: Util = registry.get_instance("util") self.event_service: EventService = registry.get_instance("event_service") self.leader: LeaderController = registry.get_instance("leader_controller") self.getresp = registry.get_instance("translation_service").get_response self.pork: PorkService = registry.get_instance("pork_service") self.private_channel_service: PrivateChannelService = registry.get_instance("private_channel_service") self.account_service: AccountService = registry.get_instance("account_service") self.buddy_service: BuddyService = registry.get_instance("buddy_service") def pre_start(self): self.event_service.register_event_type("RAID_END") @event("connect", "Adds all raiders to buddylist") def connect(self, _, _1): query = self.db.query("SELECT char_id, member from account where member != -1 and disabled = 0") if query: for player in query: self.buddy_service.add_buddy(player.char_id, "member") @command(command="raid", params=[], access_level="member", description="Show the current raid status") def raid_cmd(self, _): if not self.raid: return self.NO_RAID_RUNNING_RESPONSE t = int(time.time()) blob = "" blob += f"Name: {self.raid.desc}\n" blob += f"Minimum Level: {self.raid.level}\n" blob += f"Started By: {self.raid.started_by.name}\n" blob += f"Raidleader: " \ f"" \ f"{self.leader.leader.name if self.leader.leader else 'not set - use !leader set'}" \ f"\n" blob += f"Started At: {self.util.format_datetime(self.raid.started_at)} " \ f"({self.util.time_to_readable(t - self.raid.started_at)} ago)\n" if self.raid.is_open: blob += f"Status: Open [{self.text.make_chatcmd('Join', '/tell raid join')}] " \ f"[{self.text.make_chatcmd('Lock', '/tell raid lock')}]" else: blob += f"Status: Closed " \ f"[{self.text.make_chatcmd('Unlock', '/tell raid open')}]" blob += "\n\n" if self.raid.raid_orders: blob += "Orders\n" blob += self.raid.raid_orders + "\n\n" blob += "Raiders\n" for raider in self.raid.raiders: if raider.is_active: blob += self.format_row(raider.get_active_char()) return ChatBlob("Raid Status", blob) def format_row(self, user): return f" {self.util.get_prof_icon(user.profession)} " \ f"<{user.faction.lower()}>{user.name}" \ f" ({user.level}/{user.ai_level}) " \ f"[<{user.faction.lower()}>{user.org_name}|{user.org_rank_name}]\n" @event(event_type="private_channel_left", description="Autokick inactive characters") def left_channel(self, _1, event_data: server_packets.PrivateChannelClientLeft): self.raid_kick_cmd(None, None, self.pork.get_character_info(event_data.char_id), "Left private channel") @command(command="raid", params=[Const("start"), Any("raid_name")], description="Start new raid", access_level="leader", sub_command="manage") def raid_start_cmd(self, request: CommandRequest, _, raid_name: str): if self.raid: return "The %s raid is already running." % self.raid.raid_name self.raid = Raid(raid_name, request.sender) leader_alts = self.account_service.get_alts(request.sender.char_id) self.raid.raiders.append(Raider(leader_alts, request.sender.char_id)) join_link = self.get_raid_join_blob("Click here") msg = "\n" \ f"────────[ Raid starting ]────────\n" \ f"Initiator: {request.sender.name}\n" \ f"Raid Name: {raid_name}\n" \ f"{join_link} to join\n" \ f"────────[ Raid starting ]────────" self.leader.leader = None self.leader.set_raid_leader(request.sender, request.sender) # self.bot.send_org_message(msg) self.bot.send_private_channel_message(msg) self.account_service.add_log(leader_alts[0].char_id, "raid", f"[STARTED] Raid with desc: {raid_name}", request.sender.char_id) if request.channel == "msg": return "You have been set as the active Raidleader. " \ "Set another Raidleader by using !leader set <name>" else: return f"{request.sender.name} has been set as the active Raidleader automatically." @command(command="raid", params=[Const("desc"), Any("description")], description="Change the raid description", access_level="leader", sub_command="manage") def raid_desc_cmd(self, _1, _2, desc): if not self.raid: return self.NO_RAID_RUNNING_RESPONSE else: self.raid.desc = desc return f"Changed raid description to {desc} successfully." @command(command="raid", params=[Const("level"), Int("level")], description="Change the minimum raider level", access_level="leader", sub_command="manage") def raid_level_cmd(self, _1, _2, level): if not self.raid: return self.NO_RAID_RUNNING_RESPONSE else: self.raid.level = level return f"Changed minimum raider level to {level} successfully." @command(command="raid", params=[Const("add"), Character("character_name")], description="add an player to the raid", access_level="leader", sub_command="manage") def raid_add_cmd(self, request, _, char: SenderObj): if not self.raid: return self.NO_RAID_RUNNING_RESPONSE if not char.char_id: return self.getresp("global", "char_not_found", {"char": char.name}) if not self.private_channel_service.in_private_channel(char.char_id): return f"The player {char.name} is not in my private channel. Could not add him." main_id = self.account_service.get_main(char.char_id).char_id in_raid = self.is_in_raid(main_id) if in_raid is not None: if in_raid.active_id == char.char_id: if in_raid.is_active: return f"The Character {char.name} is already participating in the Raid." else: in_raid.is_active = True in_raid.was_kicked = None in_raid.was_kicked_reason = None in_raid.left_raid = None self.account_service.add_log(main_id, "raid", f"[ADD] Added to the raid by " f"{request.sender.name}", request.sender.char_id) self.send_raid_msg("READD", f"{char.name} by " f"{request.sender.name}") elif in_raid.is_active: former_active_name = self.character_service.resolve_char_to_name(in_raid.active_id) return f"{char.name} is already participating in the raid with the char " \ f"{former_active_name}" else: alts = self.account_service.get_alts(char.char_id) self.raid.raiders.append(Raider(alts, char.char_id)) self.account_service.add_log(main_id, "raid", f"[ADD] Added to the raid by " f"{request.sender.name}", request.sender.char_id) self.send_raid_msg("ADD", f"{char.name} by " f"{request.sender.name}") @command(command="raid", params=[Const("join")], description="Join the ongoing raid", access_level="member") def raid_join_cmd(self, request, _): if not self.raid: return self.NO_RAID_RUNNING_RESPONSE if not self.private_channel_service.in_private_channel(request.sender.char_id): return "You are not in my private channel. you cant join the raid." main_id = self.account_service.get_main(request.sender.char_id).char_id in_raid = self.is_in_raid(main_id) user = self.pork.get_character_info(request.sender.char_id) if user.level < self.raid.level: return f"You need to be at least level {self.raid.level} " \ f"to participate in this raid." if in_raid is not None: if in_raid.active_id == request.sender.char_id: if in_raid.is_active: return "You are already participating in the raid." else: if not self.raid.is_open: return "Raid is closed." in_raid.is_active = True in_raid.was_kicked = None in_raid.was_kicked_reason = None in_raid.left_raid = None self.account_service.add_log(main_id, "raid", f"[JOIN] Raid: " f"{self.raid.raid_name}", request.sender.char_id) self.send_raid_msg("REJOIN", f"{request.sender.name}") elif in_raid.is_active: former_active_name = self.character_service.resolve_char_to_name(in_raid.active_id) in_raid.active_id = request.sender.char_id self.account_service.add_log(main_id, "raid", f"[JOIN] Raid: " f"{self.raid.raid_name}", request.sender.char_id) self.send_raid_msg("ALT", f"{request.sender.name} " f"[before: {former_active_name}]") elif not in_raid.is_active: if not self.raid.is_open: return "Raid is closed." former_active_name = self.character_service.resolve_char_to_name(in_raid.active_id) in_raid.active_id = request.sender.char_id in_raid.was_kicked = None in_raid.was_kicked_reason = None in_raid.left_raid = None self.account_service.add_log(main_id, "raid", f"[JOIN] Raid: " f"{self.raid.raid_name}", request.sender.char_id) self.send_raid_msg("ALT", f"{request.sender.name} " f"[before: {former_active_name}]") elif self.raid.is_open: alts = self.account_service.get_alts(request.sender.char_id) self.raid.raiders.append(Raider(alts, request.sender.char_id)) self.account_service.add_log(main_id, "raid", f"[JOIN] Raid: {self.raid.raid_name}", request.sender.char_id) self.send_raid_msg("JOIN", f"{request.sender.name}") # self.bot.send_private_channel_message("%s joined the raid." % request.sender.name) else: return "Raid is closed." @command(command="raid", params=[Const("leave")], description="Leave the ongoing raid", access_level="member") def raid_leave_cmd(self, request, _): main = self.account_service.get_main(request.sender.char_id).char_id in_raid = self.is_in_raid(main) if in_raid: if not in_raid.is_active: return "You are not active in the raid." in_raid.is_active = False in_raid.left_raid = int(time.time()) self.account_service.add_log(main, "raid", f"[LEAVE] Raid: {self.raid.raid_name}", request.sender.char_id) self.send_raid_msg("LEAVE", f"{request.sender.name}") # self.bot.send_private_channel_message("%s left the raid." % request.sender.name) else: return "You are not in the raid." @command(command="raid", params=[Const("addpts")], description="Show Presets for adding points", access_level="leader", sub_command="manage") def points_presets_cmd(self, request, _): return ChatBlob("PTS Presets", self.preset_controller.build_preset_list) @command(command="raid", params=[Const("addpts"), Any("name")], description="Add points to all active participants", access_level="leader", sub_command="manage") def points_add_cmd(self, request, _, name: str): if not self.raid: return self.NO_RAID_RUNNING_RESPONSE preset = self.db.query_single("SELECT * FROM points_presets WHERE name = ?", [name]) if not preset: return ChatBlob("No such preset - see list of presets", self.preset_controller.build_preset_list) count = 0 for raider in self.raid.raiders: current_points = self.account_service.get_account(raider.main_id) if raider.is_active: if current_points and current_points.disabled == 0: self.account_service.add_pts(raider.main_id, preset.points, f"{preset.name}", request.sender.char_id) raider.accumulated_points += preset.points count += 1 self.bot.send_private_channel_message(f"{preset.points} points added " f"to all active raiders ({count}).") @command(command="raid", params=[Const("check")], description="Get a list of raiders to do active check", access_level="leader", sub_command="manage") def raid_active_cmd(self, _1, _): if not self.raid: return self.NO_RAID_RUNNING_RESPONSE blob = "" raider_names = [] for raider in self.raid.raiders: if not raider.is_active: continue raider_name = self.character_service.resolve_char_to_name(raider.active_id) akick_link = self.text.make_chatcmd("Active kick", f"/tell raid kick {raider.main_id} inactive") warn_link = self.text.make_chatcmd("Warn", f"/tell cmd {raider_name} missed active check, " f"please give notice.") blob += "%s [%s] [%s]\n" % (raider_name, akick_link, warn_link) raider_names.append(raider_name) active_check_names = "/assist " active_check_names += "\\n /assist ".join(raider_names) blob += "[Active check]\n\n" % active_check_names raider_names.clear() return ChatBlob("Active check", blob) @command(command="raid", params=[Const("kick"), Character("char"), Any("reason")], description="Set raider as kicked with a reason", access_level="leader", sub_command="manage") def raid_kick_cmd(self, request, _2, char: SenderObj, reason: str): if self.raid is None: return self.NO_RAID_RUNNING_RESPONSE if not char.char_id: return self.getresp("global", "char_not_found", {"char": char.name}) main_id = self.account_service.get_main(char.char_id).char_id in_raid = self.is_in_raid(main_id) if in_raid is not None: if not in_raid.is_active: return "%s is not an active participant of the raid." % char.name in_raid.is_active = False in_raid.was_kicked = int(time.time()) in_raid.was_kicked_reason = reason name = self.character_service.resolve_char_to_name(in_raid.active_id) if request: self.account_service.add_log(main_id, "raid", f"[KICKED] by " f"{request.sender.name} " f"for {reason}", request.sender.char_id) self.account_service.add_log(self.account_service.get_main(request.sender.char_id).char_id, "raid", f"[KICKED] {char.name} " f"for {reason}", request.sender.char_id) else: self.account_service.add_log(main_id, "raid", f"[KICKED] by " f"{self.bot.get_char_name()} for {reason}", self.bot.get_char_id()) self.send_raid_msg("KICKED", f"{name} for: {reason}") else: return "%s is not participating." % char.name @command(command="raid", params=[Options(["unlock", "open", "lock", "close"])], description="Open/close raid for new participants", access_level="leader", sub_command="manage") def raid_open_close_cmd(self, request, action): if not self.raid: return self.NO_RAID_RUNNING_RESPONSE if action in ["unlock", "open"]: if self.raid.is_open: return "Raid is already open." self.raid.is_open = True self.send_raid_msg("unlocked", f"by {request.sender.name}") return elif action in ["lock", "close"]: if self.raid.is_open: self.raid.is_open = False self.send_raid_msg("locked", f"by {request.sender.name}") return return "Raid is already closed." @command(command="raid", params=[Options(["save", "end", "stop"])], description="Save and log running raid", access_level="leader", sub_command="manage") def raid_save_cmd(self, _1, _2): if not self.raid: return self.NO_RAID_RUNNING_RESPONSE sql = "INSERT INTO raid_log (raid_name, started_by, raid_start, raid_end) VALUES (?,?,?,?)" num_rows = self.db.exec(sql, [self.raid.raid_name, self.raid.started_by.char_id, self.raid.started_at, int(time.time())]) if num_rows > 0: raid_id = self.db.query_single("SELECT raid_id FROM raid_log ORDER BY raid_id DESC LIMIT 1").raid_id for raider in self.raid.raiders: sql = "INSERT INTO raid_log_participants (" \ "raid_id, raider_id, accumulated_points, " \ "left_raid, was_kicked, was_kicked_reason" \ ") " \ "VALUES (?,?,?,?,?,?)" self.db.exec(sql, [raid_id, raider.active_id, raider.accumulated_points, raider.left_raid, raider.was_kicked, raider.was_kicked_reason]) self.raid = None self.leader.set_raid_leader(self.leader.leader, None) return "Raid has ended, logs got saved. Raidleader cleared." else: return "Failed to end raid. Try again." @command(command="raid", params=[Const("logentry"), Int("raid_id"), Character("char", is_optional=True)], description="Show log entry for raid, with possibility of narrowing down the log for character in raid", access_level="member") def raid_log_entry_cmd(self, _1, _2, raid_id: int, char: SenderObj): log_entry_spec = None if char: sql = "SELECT * FROM raid_log r " \ "LEFT JOIN raid_log_participants p ON r.raid_id = p.raid_id " \ "WHERE r.raid_id = ? AND p.raider_id = ?" log_entry_spec = self.db.query_single(sql, [raid_id, char.char_id]) sql = "SELECT * FROM raid_log r " \ "LEFT JOIN raid_log_participants p ON r.raid_id = p.raid_id " \ "WHERE r.raid_id = ? ORDER BY p.accumulated_points DESC" log_entry = self.db.query(sql, [raid_id]) pts_sum = self.db.query_single("SELECT SUM(p.accumulated_points) AS sum FROM raid_log_participants p " "WHERE p.raid_id = ?", [raid_id]).sum if not log_entry: return "No such log entry." blob = f"Raid name: {log_entry[0].raid_name}\n" blob += f"Started by: {self.character_service.resolve_char_to_name(log_entry[0].started_by)}\n" blob += f"Start time: {self.util.format_datetime(log_entry[0].raid_start)}\n" blob += f"End time: {self.util.format_datetime(log_entry[0].raid_end)}\n" blob += f"Run time: " \ f"{self.util.time_to_readable(log_entry[0].raid_end - log_entry[0].raid_start)}\n" blob += f"Total points: {pts_sum}\n\n" if char and log_entry_spec: raider_name = self.character_service.resolve_char_to_name(log_entry_spec.raider_id) main_info = self.account_service.get_main(log_entry_spec.raider_id) alt_link = f"Alt of {main_info.name}" if main_info.char_id != log_entry_spec.raider_id else "Alts" alt_link = self.text.make_chatcmd(alt_link, f"/tell alts {main_info.name}") blob += f"Log entry for {raider_name}\n" blob += f"Raider: {raider_name} [{alt_link}]\n" blob += "Left raid: %s\n" % ("n/a" if log_entry_spec.left_raid is None else self.util.format_datetime(log_entry_spec.left_raid)) blob += "Was kicked: %s\n" % ("No" if log_entry_spec.was_kicked is None else f"Yes [{self.util.format_datetime(log_entry_spec.was_kicked)}]") blob += "Kick reason: %s\n\n" % ("n/a" if log_entry_spec.was_kicked_reason is None else log_entry_spec.was_kicked_reason) blob += "Participants\n" for raider in log_entry: raider_name = self.character_service.resolve_char_to_name(raider.raider_id) main_info = self.account_service.get_main(raider.raider_id) alt_link = "Alt of %s" % main_info.name if main_info.char_id != raider.raider_id else "Alts" alt_link = self.text.make_chatcmd(alt_link, "/tell alts %s" % main_info.name) log_link = self.text.make_chatcmd("Log", "/tell raid logentry %d %s" % (raid_id, raider_name)) account_link = self.text.make_chatcmd("Account", "/tell account %s" % raider_name) blob += f"{raider_name} - {raider.accumulated_points:d} points earned " \ f"[{log_link}] [{account_link}] [{alt_link}]\n" log_entry_reference = "the raid %s" % log_entry[0].raid_name \ if char is None \ else "%s in raid %s" \ % (self.character_service.resolve_char_to_name(char.char_id), log_entry[0].raid_name) return ChatBlob("Log entry for %s" % log_entry_reference, blob) @command(command="raid", params=[Const("history")], description="Show a list of recent raids", access_level="member") def raid_history_cmd(self, _1, _2): sql = "SELECT * FROM raid_log ORDER BY raid_end DESC LIMIT 30" raids = self.db.query(sql) blob = "" for raid in raids: participant_link = self.text.make_chatcmd("Log", "/tell raid logentry %d" % raid.raid_id) timestamp = self.util.format_datetime(raid.raid_start) leader_name = self.character_service.resolve_char_to_name(raid.started_by) blob += f"[{raid.raid_id:d}] [{timestamp}] {raid.raid_name} " \ f"started by {leader_name} [{participant_link}]\n" return ChatBlob("Raid history", blob) def is_in_raid(self, main_id: int) -> Union[bool, Raider]: if self.raid is None: return True for raider in self.raid.raiders: if raider.main_id == main_id: return raider def send_raid_msg(self, msg_type, msg): self.bot.send_private_channel_message(f"[Raid: {msg_type}] {msg}") def get_raid_join_blob(self, link_txt: str): blob = "1. Join the raid\n" \ "To join the current raid %s, send the following tell to \n" \ "/tell raid join\n" \ "\n" \ "2. Enable LFT\n" \ "When you have joined the raid, go lft with \"\" as description\n" \ "/lft \n" \ "\n" \ "3. Announce\n" \ "You could announce to the raid leader, that you have enabled LFT\n" \ "Announce that you have enabled lft\n" \ "\n" \ "4. Rally with yer mateys\n" \ "Finally, move towards the starting location of the raid.\n" \ "Ask for help if you're in doubt of where to go." % self.raid.raid_name return self.text.paginate_single(ChatBlob(link_txt, blob)) @timerevent(budatime="5m", description="announce raid") def announce_raid(self, event_type, event_data): if self.raid: if self.raid.is_open: self.bot.send_private_channel_message(f"Raid Running " f":: {self.raid.desc} :: %s" % ( f"" f"{self.get_raid_join_blob('Click to Join')}" f"")) else: self.bot.send_private_channel_message("Raid Running :: %s " ":: Closed" % self.raid.desc) @timerevent(budatime="1m", description="No leader reminder") def leader_auto_remove(self, _1, _2): if self.raid: leader = self.leader.leader if not leader: self.bot.send_private_channel_message(f":: No Leader set :: " f"use !leader set") return account = self.account_service.get_account(leader.char_id) if not self.is_in_raid(account.main): self.bot.send_private_channel_message(f":: Raidleader {leader.name} " f"is not in raid ::")