import re import secrets import time from collections import OrderedDict from core.chat_blob import ChatBlob from core.command_alias_service import CommandAliasService from core.command_param_types import Const, Int, Any from core.db import DB from core.decorators import instance, command, timerevent from core.setting_service import SettingService from core.text import Text from core.igncore import Tyrbot from modules.core.accounting.services.account_service import AccountService from modules.raidbot.raid.raidbot_controller import Raider from modules.standard.items.items_controller import ItemsController from modules.standard.loot.item_types import LootItem from modules.standard.raid.leader_controller import LeaderController @instance() class LootController: NOT_LEADER_MSG = "Error! You must be raid leader, or have higher access " \ "level than the raid leader to use this command." def __init__(self): self.loot_list = OrderedDict() self.last_modify = None def inject(self, registry): self.bot: Tyrbot = registry.get_instance("bot") self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.leader_controller: LeaderController = registry.get_instance("leader_controller", is_optional=True) self.setting_service: SettingService = registry.get_instance("setting_service") self.items_controller: ItemsController = registry.get_instance("items_controller") self.raid_controller = None self.raid_controller = registry.get_instance("raidbot_controller", is_optional=True) self.account_service: AccountService = registry.get_instance("account_service") self.alts_service: AccountService = registry.get_instance("account_service") self.alias: CommandAliasService = registry.get_instance("command_alias_service") def pre_start(self): self.db.load_sql_file(self.module_dir + "/raid_loot.sql", pre_optimized=True) self.db.create_view("raid_loot") self.alias.add_alias("add", "loot add") self.alias.add_alias("rem", "loot rem") self.alias.add_alias("remove", "loot remo") self.alias.add_alias("flatroll", "loot roll") self.alias.add_alias("result", "loot roll") self.alias.add_alias("results", "loot roll") self.alias.add_alias("list", "loot") self.alias.add_alias("win", "loot roll") @command(command="loot", params=[], description="Show the list of added items", access_level="member") def loot_cmd(self, _): if not self.loot_list: return "Loot list is empty." return self.get_loot_list() @command(command="loot", params=[Const("edit")], description="Show the list of added items", access_level="leader", sub_command="modify") def loot_edit_cmd(self, _, _1): if not self.loot_list: return "Loot list is empty." return self.get_loot_list(edit=True) @command(command="loot", params=[Const("clear")], description="Clear all loot", access_level="leader", sub_command="modify") def loot_clear_cmd(self, request, _): if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False: return LeaderController.NOT_LEADER_MSG if self.loot_list: self.loot_list.clear() self.last_modify = None self.bot.send_private_channel_message("Loot list cleared.") else: return "Loot list is already empty." @command(command="loot", params=[Const("remitem"), Int("item_index")], description="Remove an existing loot item", access_level="leader", sub_command="modify") def loot_rem_item_cmd(self, request, _, item_index: int): if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False: return LeaderController.NOT_LEADER_MSG if not self.loot_list: return "Loot list is empty." try: if self.loot_list[item_index]: self.last_modify = int(time.time()) self.bot.send_private_channel_message( "Removed %s from loot list." % self.loot_list.pop(item_index).get_item_str()) else: return "Item error." except KeyError: return "Wrong index given." @command(command="loot", params=[Const("increase"), Int("item_index")], description="Increase item count", access_level="leader", sub_command="modify") def loot_increase_item_cmd(self, request, _, item_index: int): if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False: return LeaderController.NOT_LEADER_MSG if not self.loot_list: return "Loot list is empty." try: loot_item = self.loot_list[item_index] if loot_item: loot_item.count += 1 self.last_modify = int(time.time()) self.bot.send_private_channel_message( "Increased item count for %s to %d." % (loot_item.get_item_str(), loot_item.count)) else: return "Item error." except KeyError: return "Wrong index given." @command(command="loot", params=[Const("decrease"), Int("item_index")], description="Decrease item count", access_level="leader", sub_command="modify") def loot_decrease_item_cmd(self, request, _, item_index: int): if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False: return LeaderController.NOT_LEADER_MSG if not self.loot_list: return "Loot list is empty." try: loot_item = self.loot_list[item_index] if loot_item: loot_item.count = loot_item.count - 1 if loot_item.count > 1 else 1 self.last_modify = int(time.time()) self.bot.send_private_channel_message( "Decreased item count for %s to %d." % (loot_item.get_item_str(), loot_item.count)) else: return "Item error." except KeyError: return "Wrong index given." @command(command="loot", params=[Const("add"), Int("item_index")], description="Add yourself to item", access_level="member") def loot_add_to_cmd(self, request, _, item_index: int): main = self.alts_service.get_account(request.sender.char_id) if self.raid_controller: raider: Raider = self.raid_controller.is_in_raid(main.char_id) if type(raider) == Raider: if not raider.is_active: self.bot.send_mass_message(request.sender.char_id, "You are not participating in the raid, and cannot add to the loot.") return elif raider is None: self.bot.send_mass_message(request.sender.char_id, "You are not participating in the raid, and cannot add to the loot.") return try: loot_item = self.loot_list[item_index] old_item = self.is_already_added(request.sender.name) if old_item: if old_item.get_item_str() == loot_item.get_item_str(): name = "You have" if request.channel == "msg" else request.sender.name self.bot.send_mass_message(request.sender.char_id, "%s already added to %s." % (name, loot_item.get_item_str())) old_item.bidders.remove(request.sender.name) loot_item.bidders.append(request.sender.name) self.last_modify = int(time.time()) if old_item is not None: text = "moved from %s to %s." % (old_item.get_item_str(), loot_item.get_item_str()) self.bot.send_mass_message(request.sender.char_id, "You have %s" % text) self.bot.send_private_channel_message(request.sender.name + " " + text) else: text = "added to %s." % loot_item.get_item_str() self.bot.send_mass_message(request.sender.char_id, "You have %s" % text) self.bot.send_private_channel_message(request.sender.name + " " + text) except KeyError: return "Wrong index given." @command(command="loot", params=[Const("rem")], description="Remove yourself from item", access_level="member") def loot_rem_from_cmd(self, request, _): try: loot_item = self.is_already_added(request.sender.name) if loot_item is not None: loot_item.bidders.remove(request.sender.name) self.last_modify = int(time.time()) text = "removed from %s." % loot_item.get_item_str() self.bot.send_private_message(request.sender.char_id, "You were %s" % text) self.bot.send_private_channel_message(request.sender.name + " was " + text) else: return "You are not added to any loot." except KeyError: return "Wrong index given." @command(command="loot", params=[Const("roll")], description="Roll all loot", access_level="leader", sub_command="modify") def loot_roll_cmd(self, request, _): if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False: return LeaderController.NOT_LEADER_MSG if self.loot_list: blob = "" remove = [] for i, loot_item in self.loot_list.items(): winners = [] if loot_item.bidders: if len(loot_item.bidders) <= loot_item.count: winners = loot_item.bidders.copy() loot_item.count -= len(loot_item.bidders) loot_item.bidders = [] else: for j in range(0, loot_item.count): winner = secrets.choice(loot_item.bidders) winners.append(winner) loot_item.bidders.remove(winner) loot_item.count = loot_item.count - 1 if loot_item.count > 0 else 0 for user in winners: self.account_service.add_log(self.account_service.character_service.resolve_char_to_id(user), "loot", f"{loot_item.get_item_str()}", request.sender.char_id) blob += "%d. %s\n" % (i, loot_item.get_item_str()) blob += " | Winners: %s\n\n" % ', '.join(winners) if loot_item.count == 0: remove.append(i) for i in remove: self.loot_list.pop(i) msg = ChatBlob("Roll results", blob) msg.page_prefix = "Time is up! " self.bot.send_private_channel_message(msg if len(blob) > 0 else "No one was added to any loot") if self.loot_list: count = 1 for key in sorted(list(self.loot_list.keys())): if self.loot_list[key].count <= 0: del self.loot_list[key] else: loot_item = self.loot_list[key] del self.loot_list[key] self.loot_list[count] = loot_item count += 1 else: return "No loot to roll." @command(command="loot", params=[Const("addraiditem"), Int("raid_item_id"), Int("item_count")], description="Add item from pre-defined raid to loot list", access_level="leader", sub_command="modify") def loot_add_raid_item(self, request, _, raid_item_id: int, item_count: int): if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False: return LeaderController.NOT_LEADER_MSG sql = "SELECT r.name, r.comment, r.ql, a.lowid AS low_id, a.highid AS high_id FROM aodb a " \ "LEFT JOIN raid_loot r ON (a.name = r.name AND a.highql >= r.ql) " \ "WHERE r.id = ? LIMIT 1" item = self.db.query_single(sql, [raid_item_id]) if item: self.add_item_to_loot(item, item.comment, item_count) self.bot.send_private_channel_message("Added %s to loot list." % item.name) else: return "Failed to add item with ID %s." % raid_item_id @command(command="loot", params=[Const("addraid"), Any("raid"), Any("category")], description="Add all loot from pre-defined raid", access_level="leader", sub_command="modify") def loot_add_raid_loot(self, request, _, raid: str, category: str): if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False: return LeaderController.NOT_LEADER_MSG items = self.db.query( "SELECT r.raid, r.category, r.id, r.ql, r.name, r.comment, r.multiloot, " "a.lowid AS low_id, " "a.highid AS high_id, a.icon " "FROM raid_loot r " "LEFT JOIN aodb a " "ON (r.name = a.name AND r.ql <= a.highql) " "WHERE r.raid = ? AND r.category = ? " "ORDER BY r.name", [raid, category] ) if items: for item in items: self.add_item_to_loot(item, item.comment, item.multiloot) self.bot.send_private_channel_message(f"{category} table was added to loot.") else: return f"{category} does not have any items registered in loot table." @command(command="loot", params=[Const("additem", is_optional=True), Int("item"), Int("item_count", is_optional=True)], description="Add an item to loot list by item id", access_level="leader", sub_command="modify") def loot_add_item_id_cmd(self, request, _, item_id, item_count: int): if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False: return LeaderController.NOT_LEADER_MSG if item_count is None: item_count = 1 item = self.items_controller.get_by_item_id(item_id) if not item: return f"Could not find item with ID {item_id}." self.add_item_to_loot(item, item_count=item_count) self.bot.send_private_channel_message("%s was added to loot list." % item.name) @command(command="loot", params=[Const("additem", is_optional=True), Any("item"), Int("item_count", is_optional=True)], description="Add an item to loot list", access_level="leader", sub_command="modify") def loot_add_item_cmd(self, request, _, item, item_count: int): if not self.leader_controller.can_use_command(request.sender.char_id) if self.leader_controller else False: return LeaderController.NOT_LEADER_MSG loot = "" if item_count is None: item_count = 1 item = item.replace("'", "'") items = re.findall(r"(([^<]+)?([^<]+)([^<]+)?)", item) if items and item_count == 1: for item in items: item = self.text.make_item(int(item[2]), int(item[3]), int(item[4]), item[5]) if loot != "": loot += ", " + item else: loot += item self.add_item_to_loot(item) else: loot += item self.add_item_to_loot(item, item_count=item_count) self.bot.send_private_channel_message(f"{loot} was added to loot list.") @timerevent(budatime="1h", description="Periodically check when loot list was last modified, and clear it " "if last modification was done 1+ hours ago") def loot_clear_event(self, _1, _2): if self.loot_list and self.last_modify: if int(time.time()) - self.last_modify > 3600 and self.loot_list: self.last_modify = None self.loot_list = OrderedDict() self.bot.send_org_message("Loot was last modified more than 1 hour ago, list has been cleared.") self.bot.send_private_channel_message( "Loot was last modified more than 1 hour ago, list has been cleared.") def is_already_added(self, name: str): for i, loot_item in self.loot_list.items(): if name in loot_item.bidders: return loot_item return None def add_item_to_loot(self, item, comment=None, item_count=1): existing_item = next((loot_item for x, loot_item in self.loot_list.items() if loot_item.item == item), None) if existing_item: existing_item.count += 1 else: # this prevents loot items from being re-numbered when items are removed end_index = list(self.loot_list.keys())[-1] + 1 if len(self.loot_list) > 0 else 1 self.loot_list[end_index] = LootItem(item, comment, item_count) self.last_modify = int(time.time()) def get_loot_list(self, edit=False): blob = "" remove = self.text.make_chatcmd("Remove from rolls", "/tell loot rem") blob += f"[{remove}]\n\n" for i, loot_item in self.loot_list.items(): bidders = loot_item.bidders img = "" if loot_item.get_item_str()[:2] == "": item = re.findall(r"(([^<]+)?([^<]+)([^<]+)?)", loot_item.get_item_str()) imgid = self.db.query_single("SELECT icon from aodb where lowid=? or highid=?", [item[0][2], item[0][3]]) if imgid is not None: img = self.text.make_image(imgid.get("icon")) + " - " blob += f"{i:d}. {img}{item[0][0]} " else: blob += f"{i:d}. {loot_item.get_item_str()} " blob += f"x{loot_item.count} [{self.text.make_chatcmd('Add', f'/tell loot add {i}')}]\n" blob += " └ " if edit: blob += f"[ {self.text.make_tellcmd('+1', f'loot increase {i}')} | " \ f"{self.text.make_tellcmd('-1', f'loot decrease {i}')} ]" \ f"[ {self.text.make_tellcmd('REM', f'loot remitem {i}')} ]\n\n" else: if len(bidders) > 0: blob += "%s\n\n" % ', '.join(bidders) else: blob += "None added\n\n" return ChatBlob("Loot (%d)" % len(self.loot_list), blob)