def start(self): self.command_alias_service.add_alias("lvl", "level") self.command_alias_service.add_alias("pvp", "level") self.command_alias_service.add_alias("team", "level") self.command_alias_service.add_alias("missions", "mission") self.command_alias_service.add_alias("mish", "mission") self.command_alias_service.add_alias("ailevel", "axp") self.discord_controller.register_discord_command_handler( self.level_discord_cmd, "level", [Int("level")]) self.discord_controller.register_discord_command_handler( self.mission_discord_cmd, "mission", [Int("mission_level")])
class PlayfieldController: def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") @command(command="playfield", params=[], access_level="all", description="Show a list of playfields", aliases=["playfields"]) def playfield_list_command(self, request): data = self.db.query("SELECT * FROM playfields ORDER BY long_name") blob = "" for row in data: blob += "[<highlight>%d<end>] %s (%s)\n" % (row.id, row.long_name, row.short_name) return ChatBlob("Playfields", blob) @command(command="waypoint", params=[Regex("waypoint_data", "\s+.*?Pos: ([0-9.]+), ([0-9.]+), ([0-9.]+), Area: ([a-zA-Z ]+).*", num_groups=4)], access_level="all", description="Create a waypoint link from F9 output") def waypoint1_command(self, request, regex): x_coords, y_coords, _, playfield_arg = regex return self.create_waypoint_blob(x_coords, y_coords, playfield_arg) @command(command="waypoint", params=[Regex("waypoint_data", "\s+.*?([0-9.]+) ([0-9.]+) y ([0-9.]+) ([0-9]+).*", num_groups=4)], access_level="all", description="Create a waypoint link from Shift + F9 output") def waypoint2_command(self, request, regex): x_coords, y_coords, _, playfield_arg = regex return self.create_waypoint_blob(x_coords, y_coords, playfield_arg) @command(command="waypoint", params=[Int("x_coords"), Int("y_coords"), Any("playfield")], access_level="all", description="Manually create a waypoint link") def waypoint3_command(self, request, x_coords, y_coords, playfield_arg): return self.create_waypoint_blob(x_coords, y_coords, playfield_arg) def create_waypoint_blob(self, x_coords, y_coords, playfield_arg): playfield = self.get_playfield_by_name(playfield_arg) or self.get_playfield_by_id(playfield_arg) if not playfield: return "Could not find playfield <highlight>%s<end>." % playfield_arg else: title = "waypoint: %sx%s %s" % (x_coords, y_coords, playfield.long_name) blob = "Click here to use waypoint: " blob += self.text.make_chatcmd(title, "/waypoint %s %s %d" % (x_coords, y_coords, playfield.id)) return ChatBlob(title, blob) def get_playfield_by_name(self, name): return self.db.query_single("SELECT * FROM playfields WHERE long_name LIKE ? OR short_name LIKE ? LIMIT 1", [name, name]) def get_playfield_by_id(self, playfield_id): return self.db.query_single("SELECT * FROM playfields WHERE id = ?", [playfield_id])
class PerksController: def __init__(self): self.grades = ["shiny", "bright", "faded"] def inject(self, registry): self.db = registry.get_instance("db") self.util = registry.get_instance("util") def pre_start(self): self.db.load_sql_file(self.module_dir + "/" + "perks.sql") @command(command="perks", params=[Int("level"), Any("profession")], access_level="all", description= "Show what perks are available for specified level and profession" ) def perks_cmd(self, request, level, profession): if level < 1 or level > 220: return "Level must be between <highlight>1</highlight> and <highlight>220</highlight>." prof = self.util.get_profession(profession) if not prof: return "Could not find profession <highlight>%s</highlight>" % profession sql = """ SELECT p.name AS perk_name, MAX(pl.number) AS max_perk_level, SUM(plb.amount) AS buff_amount, plb.skill FROM perk p JOIN perk_prof pp ON p.id = pp.perk_id JOIN perk_level pl ON p.id = pl.perk_id JOIN perk_level_buffs plb ON pl.id = plb.perk_level_id WHERE pp.profession = ? AND pl.min_level <= ? GROUP BY p.name, plb.skill ORDER BY p.name""" data = self.db.query(sql, [prof, level]) blob = "" current_perk = "" for row in data: if row.perk_name != current_perk: blob += "\n<header2>%s %s</header2>\n" % (row.perk_name, row.max_perk_level) current_perk = row.perk_name blob += "%s <highlight>%d</highlight>\n" % (row.skill, row.buff_amount) return ChatBlob("Buff Perks for %d %s" % (level, prof), blob)
class OverequippedController: def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.discord_controller = registry.get_instance("discord_controller") def start(self): self.discord_controller.register_discord_command_handler( self.oe_discord_cmd, "oe", [Int("skill_level")]) @command(command="oe", params=[Int("skill_level")], access_level="all", description="Show the current time in every timezone") def oe_command(self, request, skill_level): oe = self.get_oe_vals(skill_level) blob = "With a skill requirement of <highlight>%s<end>, you will be\n" % skill_level blob += "Out of OE: <highlight>%d<end> or higher\n" % oe.oe100low blob += "75%%: <highlight>%d<end> to <highlight>%d<end>\n" % ( oe.oe75low, oe.oe100low - 1) blob += "50%%: <highlight>%d<end> to <highlight>%d<end>\n" % ( oe.oe50low, oe.oe75low - 1) blob += "25%%: <highlight>%d<end> to <highlight>%d<end>\n" % ( oe.oe25low, oe.oe50low - 1) blob += "0%%: <highlight>%d<end> or lower\n\n" % (oe.oe25low - 1) blob += "With a personal skill of <highlight>%s<end>, you can use up to\n" % skill_level blob += "Out of OE: <highlight>%d<end> or lower\n" % oe.oe100 blob += "75%%: <highlight>%d<end> to <highlight>%d<end>\n" % ( oe.oe100 + 1, oe.oe75) blob += "50%%: <highlight>%d<end> to <highlight>%d<end>\n" % ( oe.oe75 + 1, oe.oe50) blob += "25%%: <highlight>%d<end> to <highlight>%d<end>\n" % ( oe.oe50 + 1, oe.oe25) blob += "0%%: <highlight>%d<end> or higher\n" % (oe.oe25 - 1) return ChatBlob("%d - %d - %d" % (oe.oe100low, skill_level, oe.oe100), blob) def get_oe_vals(self, skill_level): return DictObject({ "oe100": int(math.floor(skill_level / 0.8)), "oe100low": int(math.floor(skill_level * 0.8)), "oe75": int(math.floor(skill_level / 0.6)), "oe75low": int(math.floor(skill_level * 0.6)), "oe50": int(math.floor(skill_level / 0.4)), "oe50low": int(math.floor(skill_level * 0.4)), "oe25": int(math.floor(skill_level / 0.2)), "oe25low": int(math.floor(skill_level * 0.2)) }) def oe_discord_cmd(self, reply, args): skill_level, = args oe = self.get_oe_vals(skill_level) reply("%d - %d - %d" % (oe.oe100low, skill_level, oe.oe100), "OE")
class ResearchController: def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.util = registry.get_instance("util") @command(command="research", params=[Int("research_level")], access_level="all", description="Show information about a specific research level") def research_command(self, request, research_level): if research_level > 10 or research_level < 1: return "Research level must be between 1 and 10." row = self.db.query_single("SELECT * FROM research WHERE level = ?", [research_level]) capsk = int(row.sk * 0.1) blob = "You must be level <highlight>%d<end> to research <highlight>Research Level %d<end>.\n" % (row.levelcap, research_level) blob += "You need <highlight>%s SK<end> to reach <highlight>Research Level %d<end> per research line.\n\n" % (self.util.format_number(row.sk), research_level) blob += "This equals <highlight>%s XP<end>.\n\n" % (self.util.format_number(row.sk * 1000)) blob += "Your research will cap at <highlight>%s XP<end> or <highlight>%s SK<end>." % (self.util.format_number(capsk * 1000), self.util.format_number(capsk)) return ChatBlob("Research Level %d" % research_level, blob) @command(command="research", params=[Int("research_level"), Int("research_level")], access_level="all", description="Show the amount of SK needed from one research level to another") def research_span_command(self, request, research_level1, research_level2): if research_level1 > 10 or research_level1 < 1 or research_level2 > 10 or research_level2 < 1: return "Research level must be between 1 and 10." elif research_level1 == research_level2: return "You must specify different research levels." if research_level1 > research_level2: # swap researches so the lower is research_level1 and higher is research_level2 research_level1, research_level2 = research_level2, research_level1 row = self.db.query_single("SELECT SUM(sk) AS total_sk, MAX(levelcap) AS levelcap FROM research WHERE level > ? AND level <= ?", [research_level1, research_level2]) blob = "You must be <highlight>Level %d<end> to reach Research Level <highlight>%d.<end>\n" % (row.levelcap, research_level2) blob += "It takes <highlight>%s SK<end> to go from Research Level <highlight>%d<end> to Research Level <highlight>%d<end> per research line.\n\n" \ % (self.util.format_number(row.total_sk), research_level1, research_level2) blob += "This equals <highlight>%s XP<end>." % self.util.format_number(row.total_sk * 1000) return ChatBlob("Research Levels %d - %d" % (research_level1, research_level2), blob)
class NotesController: def inject(self, registry): self.db = registry.get_instance("db") self.text = registry.get_instance("text") self.alts_service = registry.get_instance("alts_service") def start(self): self.db.exec("CREATE TABLE IF NOT EXISTS notes (" "id INT PRIMARY KEY AUTO_INCREMENT, " "char_id INT NOT NULL, " "note TEXT NOT NULL," "created_at INT NOT NULL)") @command(command="notes", params=[], access_level="all", description="Show your notes") def notes_list_cmd(self, request): alts = self.alts_service.get_alts(request.sender.char_id) cnt = 0 blob = "" for alt in alts: data = self.db.query("SELECT * FROM notes WHERE char_id = ? ORDER BY created_at DESC", [alt.char_id]) alt_cnt = len(data) cnt += alt_cnt if alt_cnt: blob += "\n<header2>%s<end>\n" % alt.name for row in data: blob += "%s %s\n\n" % (row.note, self.text.make_chatcmd("Remove", "/tell <myname> notes remove %d" % row.id)) return ChatBlob("Notes for %s (%d)" % (alts[0].name, cnt), blob) @command(command="notes", params=[Const("add"), Any("note")], access_level="all", description="Add a note") def notes_add_cmd(self, request, _, note): self.db.exec("INSERT INTO notes (char_id, note, created_at) VALUES (?, ?, ?)", [request.sender.char_id, note, int(time.time())]) return "Note added successfully." @command(command="notes", params=[Options(["rem", "remove"]), Int("note_id")], access_level="all", description="Remove a note") def notes_remove_cmd(self, request, _, note_id): note = self.db.query_single("SELECT n.*, p.name FROM notes n LEFT JOIN player p ON n.char_id = p.char_id WHERE n.id = ?", [note_id]) if not note: return "Could not find note with ID <highlight>%d<end>." % note_id if self.alts_service.get_main(request.sender.char_id).char_id != self.alts_service.get_main(note.char_id).char_id: return "You must be a confirmed alt of <highlight>%s<end> to remove this note." % note.name self.db.exec("DELETE FROM notes WHERE id = ?", [note_id]) return "Note with ID <highlight>%d<end> deleted successfully." % note_id
class CharacterHistoryController: def __init__(self): pass def inject(self, registry): self.bot = registry.get_instance("bot") self.util = registry.get_instance("util") self.character_history_service = registry.get_instance( "character_history_service") self.command_alias_service = registry.get_instance( "command_alias_service") def start(self): self.command_alias_service.add_alias("h", "history") @command( command="history", params=[Character("character"), Int("server_num", is_optional=True)], access_level="all", description="Get history of character") def handle_history_cmd1(self, request, char, server_num): server_num = server_num or self.bot.dimension data = self.character_history_service.get_character_history( char.name, server_num) if not data: return "Could not find history for <highlight>%s<end> on server <highlight>%d<end>." % ( char.name, server_num) return ChatBlob("History of %s (RK%d)" % (char.name, server_num), self.format_character_history(char.name, data)) def format_character_history(self, name, history): blob = "Date Level AI Faction Breed Guild (rank)\n" blob += "________________________________________________ \n" for row in history: if row.guild_name: org = "%s (%s)" % (row.guild_name, row.guild_rank_name) else: org = "" last_changed = self.util.format_date(int(float(row.last_changed))) blob += "%s | %s | <green>%s<end> | %s | %s | %s\n" % \ (last_changed, row.level, row.defender_rank or 0, row.faction, row.breed, org) blob += "\nHistory provided by Auno.org, Chrisax, and Athen Paladins" return blob
class OfabWeaponsController: def inject(self, registry): self.db = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.items_controller = registry.get_instance("items_controller") self.command_alias_service = registry.get_instance("command_alias_service") def start(self): self.command_alias_service.add_alias("ofabweapon", "ofabweapons") @command(command="ofabweapons", params=[], access_level="all", description="Show ofab weapons") def ofabweapons_list_command(self, request): data = self.db.query("SELECT type, name FROM ofab_weapons ORDER BY name ASC") blob = "" for row in data: blob += "<pagebreak>%s - Type %d\n" % (self.text.make_tellcmd(row.name, "ofabweapons %s" % row.name), row.type) return ChatBlob("Ofab Weapons", blob) @command(command="ofabweapons", params=[Int("ql", is_optional=True), Any("weapon"), Int("ql", is_optional=True)], access_level="all", description="Show info about an ofab weapon", extended_description="QL is optional and can come before or after the weapon") def ofabweapons_show_command(self, request, ql1, weapon_name, ql2): weapon_name = weapon_name.capitalize() ql = ql1 or ql2 or 300 weapon = self.db.query_single("SELECT type, vp FROM ofab_weapons w, ofab_weapons_cost c WHERE w.name LIKE ? AND c.ql = ?", [weapon_name, ql]) if not weapon: return "Could not find Ofab Weapon <highlight>%s</highlight> for QL <highlight>%d</highlight>." % (weapon_name, ql) type_ql = round(ql * 0.8) type_link = self.text.make_tellcmd("Kyr'Ozch Bio-Material - Type %d" % weapon.type, "bioinfo %d %d" % (weapon.type, type_ql)) blob = "Upgrade with %s (minimum QL %d)\n\n" % (type_link, type_ql) data = self.db.query("SELECT ql FROM ofab_weapons_cost ORDER BY ql ASC") for row in data: blob += self.text.make_tellcmd(row.ql, "ofabweapons %s %d" % (weapon_name, row.ql)) + " " blob += "\n\n" for i in range(1, 7): item = self.items_controller.find_by_name("Ofab %s Mk %d" % (weapon_name, i)) blob += "<pagebreak>" + self.text.format_item(item) if i == 1: blob += " (<highlight>%d</highlight> VP)" % weapon.vp blob += "\n" return ChatBlob("Ofab %s (QL %d)" % (weapon_name, ql), blob)
def test_multiple(self): param1 = Multiple(Int("num")) self.assertEqual([1], self.param_test_helper(param1, "1")) self.assertEqual([1, 2, 3], self.param_test_helper(param1, "1 2 3")) param2 = Multiple(Const("something")) self.assertEqual(["something"], self.param_test_helper(param2, "something")) self.assertEqual(["something", "something", "something"], self.param_test_helper(param2, "something something something")) param3 = Multiple(Time("time")) self.assertEqual([60], self.param_test_helper(param3, "1m")) self.assertEqual([304], self.param_test_helper(param3, "5M4S")) self.assertEqual([60, 304, 14521], self.param_test_helper(param3, "1m 5M4S 4h2m1s")) param4 = Multiple(Item("item")) self.assertEqual([{'low_id': 246817, 'high_id': 246817, 'ql': 200, 'name': 'Novictum Seed'}], self.param_test_helper(param4, "<a href=\"itemref://246817/246817/200\">Novictum Seed</a>")) self.assertEqual([{'low_id': 246817, 'high_id': 246817, 'ql': 200, 'name': 'Novictum Seed'}, {'low_id': 100, 'high_id': 101, 'ql': 300, 'name': 'It\'s cool'}], self.param_test_helper(param4, "<a href=\"itemref://246817/246817/200\">Novictum Seed</a> " "<a href=\"itemref://100/101/300\">It's cool</a>")) self.assertEqual([{'low_id': 246817, 'high_id': 246817, 'ql': 200, 'name': 'Novictum Seed'}, {'low_id': 100, 'high_id': 101, 'ql': 300, 'name': 'It\'s cool'}, {'low_id': 12345, 'high_id': 54321, 'ql': 123, 'name': 'It Works!'}], self.param_test_helper(param4, "<a href=\"itemref://246817/246817/200\">Novictum Seed</a> " "<a href=\"itemref://100/101/300\">It's cool</a> " "<a href=\"itemref://12345/54321/123\">It Works!</a>")) # no spaces self.assertEqual([{'low_id': 246817, 'high_id': 246817, 'ql': 200, 'name': 'Novictum Seed'}, {'low_id': 100, 'high_id': 101, 'ql': 300, 'name': 'It\'s cool'}, {'low_id': 12345, 'high_id': 54321, 'ql': 123, 'name': 'It Works!'}], self.param_test_helper(param4, "<a href=\"itemref://246817/246817/200\">Novictum Seed</a>" "<a href=\"itemref://100/101/300\">It's cool</a>" "<a href=\"itemref://12345/54321/123\">It Works!</a>")) param5 = Multiple(Any("time")) self.assertEqual(["test"], self.param_test_helper(param5, "test")) self.assertEqual(["test1", "test2"], self.param_test_helper(param5, "test1 test2")) self.assertEqual(["test1", "test2", "test3"], self.param_test_helper(param5, "test1 test2 test3"))
class LinksController: def inject(self, registry): self.db = registry.get_instance("db") self.text = registry.get_instance("text") def start(self): self.db.exec("CREATE TABLE IF NOT EXISTS links (" "id INT PRIMARY KEY AUTO_INCREMENT," "char_id INT NOT NULL," "website VARCHAR(255) NOT NULL," "comments VARCHAR(255) NOT NULL," "created_at INT NOT NULL);") @command(command="links", params=[], access_level="all", description="Show links") def links_list_cmd(self, request): data = self.db.query("SELECT l.*, p.name FROM links l LEFT JOIN player p ON l.char_id = p.char_id ORDER BY name ASC") blob = "" for row in data: blob += "%s <highlight>%s<end> [%s] %s" % (self.text.make_chatcmd("[Link]", "/start %s" % row.website), row.comments, row.name, self.text.make_chatcmd("Remove", "/tell <myname> links remove %d" % row.id)) return ChatBlob("Links (%d)" % len(data), blob) @command(command="links", params=[Const("add"), Any("website"), Any("comment")], access_level="moderator", description="Add a link") def links_add_cmd(self, request, _, website, comment): if not website.startswith("https://") and not website.startswith("http://"): return "Website must start with 'http://' or 'https://'." self.db.exec("INSERT INTO links (char_id, website, comments, created_at) VALUES (?, ?, ?, ?)", [request.sender.char_id, website, comment, int(time.time())]) return "Link added successfully." @command(command="links", params=[Options(["rem", "remove"]), Int("link_id")], access_level="moderator", description="Remove a link") def links_remove_cmd(self, request, _, link_id): link = self.db.query_single("SELECT * FROM links WHERE id = ?", [link_id]) if not link: return "Could not find link with ID <highlight>%d<end>." % link_id self.db.exec("DELETE FROM links WHERE id = ?", [link_id]) return "Link has been deleted"
class CharacterHistoryController: def __init__(self): pass def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.pork_manager = registry.get_instance("pork_manager") @command(command="history", params=[Any("character"), Int("server_num", is_optional=True)], access_level="all", description="Get history of character") def handle_history_cmd1(self, channel, sender, reply, args): name = args[0].capitalize() server_num = args[1] if args[1] else 5 reply(self.get_character_history(name, server_num)) def get_character_history(self, name, server_num): return "<header>" + name + " (" + str(server_num) + ")<end>"
class OverequippedController: def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") @command(command="oe", params=[Int("skill_level")], access_level="all", description="Show the current time in every timezone") def oe_command(self, request, skill_level): oe100 = int(math.floor(skill_level / 0.8)) oe100low = int(math.floor(skill_level * 0.8)) oe75 = int(math.floor(skill_level / 0.6)) oe75low = int(math.floor(skill_level * 0.6)) oe50 = int(math.floor(skill_level / 0.4)) oe50low = int(math.floor(skill_level * 0.4)) oe25 = int(math.floor(skill_level / 0.2)) oe25low = int(math.floor(skill_level * 0.2)) blob = "With a skill requirement of <highlight>%s<end>, you will be\n" % skill_level blob += "Out of OE: <highlight>%d<end> or higher\n" % oe100low blob += "75%%: <highlight>%d<end> to <highlight>%d<end>\n" % ( oe75low, oe100low - 1) blob += "50%%: <highlight>%d<end> to <highlight>%d<end>\n" % ( oe50low, oe75low - 1) blob += "25%%: <highlight>%d<end> to <highlight>%d<end>\n" % ( oe25low, oe50low - 1) blob += "0%%: <highlight>%d<end> or lower\n\n" % (oe25low - 1) blob += "With a personal skill of <highlight>%s<end>, you can use up to\n" % skill_level blob += "Out of OE: <highlight>%d<end> or lower\n" % oe100 blob += "75%%: <highlight>%d<end> to <highlight>%d<end>\n" % (oe100 + 1, oe75) blob += "50%%: <highlight>%d<end> to <highlight>%d<end>\n" % (oe75 + 1, oe50) blob += "25%%: <highlight>%d<end> to <highlight>%d<end>\n" % (oe50 + 1, oe25) blob += "0%%: <highlight>%d<end> or higher\n" % (oe25 - 1) return ChatBlob("%d - %d - %d" % (oe100low, skill_level, oe100), blob)
class RandomController: def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.util = registry.get_instance("util") self.character_service = registry.get_instance("character_service") self.command_alias_service = registry.get_instance( "command_alias_service") def start(self): self.command_alias_service.add_alias("verify", "roll verify") @command( command="random", params=[Any("items")], access_level="all", description="Randomly order a list of elements", extended_description="Enter a space-delimited list of items to randomize" ) def random_command(self, request, items): items = items.split(" ") random.shuffle(items) return " ".join(items) @command(command="roll", params=[Int("start_value", is_optional=True), Int("end_value")], access_level="all", description="Roll a number between 1 and a number") def roll_command(self, request, start_value, end_value): start_value = start_value or 1 if start_value > end_value: start_value, end = end_value, start_value result = random.randint(start_value, end_value) self.db.exec( "INSERT INTO roll (char_id, min_value, max_value, result, created_at) VALUES (?, ?, ?, ?, ?)", [ request.sender.char_id, start_value, end_value, result, int(time.time()) ]) return "Rolling between %d and %d: <highlight>%d<end>. /tell <myname> roll verify %d" % ( start_value, end_value, result, self.db.last_insert_id()) @command(command="roll", params=[Const("verify"), Int("roll_id")], access_level="all", description="Verify a roll that happened") def roll_verify_command(self, request, _, roll_id): row = self.db.query_single("SELECT * FROM roll WHERE id = ?", [roll_id]) if not row: return "Could not find roll with id <highlight>%d<end>." % roll_id else: time_string = self.util.time_to_readable( int(time.time()) - row.created_at) name = self.character_service.resolve_char_to_name(row.char_id) return "Rolling between %d and %d: <highlight>%d<end>. %s ago for %s." % ( row.min_value, row.max_value, row.result, time_string, name)
class DynaController: def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") @command(command="dyna", params=[], access_level="all", description="Show a list of dyna mob types") def dyna_mob_types_command(self, request): data = self.db.query( "SELECT mob, MIN(minQl) AS minQl, MAX(maxQl) AS maxQl FROM dynadb GROUP BY mob ORDER BY mob ASC" ) blob = "" for row in data: blob += "%s (%d - %d)\n" % (self.text.make_chatcmd( row.mob, "/tell <myname> dyna %s" % row.mob), row.minQl, row.maxQl) return ChatBlob("Dyna Mobs (%d)" % len(data), blob) @command(command="dyna", params=[Int("level")], access_level="all", description="Show a list of dyna camps +/- 25 of QL") def dyna_level_command(self, request, level): min_level = level - 25 max_level = level + 25 data = self.db.query( "SELECT * FROM dynadb d JOIN playfields p ON d.playfield_id = p.id WHERE d.minQl >= ? AND d.maxQl <= ? ORDER BY minQl", [min_level, max_level]) blob = "Results of dyna camps between QL <highlight>%d<end> and <highlight>%d<end>\n\n" % ( min_level, max_level) blob += self.format_results(data) url = "http://creativestudent.com/ao/files-helpfiles.html" blob += "Dyna camp information taken from CSP help files: " + self.text.make_chatcmd( url, "/start " + url) return ChatBlob("Dyna Camps (%d)" % len(data), blob) @command(command="dyna", params=[Any("search")], access_level="all", description="Search for dyna camps based on playfield or mob type" ) def dyna_search_command(self, request, search): search_param = "%" + search + "%" data = self.db.query( "SELECT * FROM dynadb d JOIN playfields p ON d.playfield_id = p.id " "WHERE p.long_name LIKE ? OR p.short_name LIKE ? OR d.mob LIKE ? ORDER BY d.minQl", [search_param, search_param, search_param]) blob = "Results of dyna camps search for <highlight>%s<end>\n\n" % search blob += self.format_results(data) url = "http://creativestudent.com/ao/files-helpfiles.html" blob += "Dyna camp information taken from CSP help files: " + self.text.make_chatcmd( url, "/start " + url) return ChatBlob("Dyna Camps (%d)" % len(data), blob) def format_results(self, data): blob = "" for row in data: coordinates = self.text.make_chatcmd( "%s %dx%d" % (row.long_name, row.cX, row.cY), "/waypoint %d %d %d" % (row.cX, row.cY, row.playfield_id)) blob += "<pagebreak>" + coordinates + "\n" blob += "%s - Level <highlight>%d-%d<end>\n\n" % ( row.mob, row.minQl, row.maxQl) return blob
class SpecialsController: def __init__(self): pass def inject(self, registry): self.db = registry.get_instance("db") self.text = registry.get_instance("text") self.util = registry.get_instance("util") self.items_controller = registry.get_instance("items_controller") self.command_alias_service = registry.get_instance( "command_alias_service") def pre_start(self): self.db.load_sql_file(self.module_dir + "/" + "weapon_attributes.sql") def start(self): self.command_alias_service.add_alias("aimshot", "aimedshot") self.command_alias_service.add_alias("as", "aimedshot") self.command_alias_service.add_alias("inits", "weapon") self.command_alias_service.add_alias("init", "weapon") self.command_alias_service.add_alias("specials", "weapon") self.command_alias_service.add_alias("fling", "flingshot") @command(command="aggdef", params=[ Decimal("weapon_attack"), Decimal("weapon_recharge"), Int("init_skill") ], access_level="all", description="Show agg/def information") def aggdef_cmd(self, request, weapon_attack, weapon_recharge, init_skill): init_result = self.get_init_result(weapon_attack, weapon_recharge, init_skill) inits_full_agg = self.get_inits_needed(100, weapon_attack, weapon_recharge) inits_neutral = self.get_inits_needed(87.5, weapon_attack, weapon_recharge) inits_full_def = self.get_inits_needed(0, weapon_attack, weapon_recharge) blob = "Attack: <highlight>%.2f secs</highlight>\n" % weapon_attack blob += "Recharge: <highlight>%.2f secs</highlight>\n" % weapon_recharge blob += "Init Skill: <highlight>%d</highlight>\n\n" % init_skill blob += "You must set your AGG/DEF bar at <highlight>%d%%</highlight> to wield your weapon at 1/1.\n\n" % int( init_result) blob += "Init needed for max speed at Full Agg (100%%): <highlight>%d</highlight>\n" % inits_full_agg blob += "Init needed for max speed at Neutral (87.5%%): <highlight>%d</highlight>\n" % inits_neutral blob += "Init needed for max speed at Full Def (0%%): <highlight>%d</highlight>\n\n" % inits_full_def blob += self.get_inits_display(weapon_attack, weapon_recharge) + "\n\n" blob += "Note that at the neutral position (87.5%), your attack and recharge time will match that of the weapon you are using.\n\n\n" blob += "Based on the !aggdef command from Budabot, which was based upon a RINGBOT module made by NoGoal(RK2) and modified for Budabot by Healnjoo(RK2)" return ChatBlob("Agg/Def Results", blob) @command(command="aimedshot", params=[ Decimal("weapon_attack"), Decimal("weapon_recharge"), Int("aimed_shot_skill") ], access_level="all", description="Show aimed shot information") def aimedshot_cmd(self, request, weapon_attack, weapon_recharge, aimed_shot_skill): as_info = self.get_aimed_shot_info(weapon_attack, weapon_recharge, aimed_shot_skill) blob = "Attack: <highlight>%.2f secs</highlight>\n" % weapon_attack blob += "Recharge: <highlight>%.2f secs</highlight>\n" % weapon_recharge blob += "Aimed Shot Skill: <highlight>%d</highlight>\n\n" % aimed_shot_skill blob += "Aimed Shot Multiplier: <highlight>1 - %dx</highlight>\n" % as_info.multiplier blob += "Aimed Shot Recharge: <highlight>%d secs</highlight>\n\n" % as_info.recharge blob += "You need <highlight>%d</highlight> Aimed Shot Skill to cap your recharge at <highlight>%d</highlight> seconds." % ( as_info.skill_cap, as_info.hard_cap) return ChatBlob("Aimed Shot Results", blob) @command(command="brawl", params=[Int("brawl_skill")], access_level="all", description="Show brawl information") def brawl_cmd(self, request, brawl_skill): brawl_info = self.get_brawl_info(brawl_skill) blob = "Brawl Skill: <highlight>%d</highlight>\n\n" % brawl_skill blob += "Brawl Recharge: <highlight>15 secs</highlight> (constant)\n" blob += "Brawl Damage: <highlight> %d - %d (%d)</highlight>\n" % ( brawl_info.min_dmg, brawl_info.max_dmg, brawl_info.crit_dmg) blob += "Stun Change: <highlight>%d%%</highlight>\n" % brawl_info.stun_chance blob += "Stun Duration: <highlight>%d secs</highlight>\n\n" % brawl_info.stun_duration blob += "Stun chance is 10% for brawl skill less than 1000 and 20% for brawl skill 1000 or greater.\n" blob += "Stun duration is 3 seconds for brawl skill less than 2001 and 4 seconds for brawl skill 2001 or greater.\n\n\n" blob += "Based on the !brawl command from Budabot by Imoutochan (RK1)" return ChatBlob("Brawl Results", blob) @command(command="burst", params=[ Decimal("weapon_attack"), Decimal("weapon_recharge"), Int("burst_recharge"), Int("burst_skill") ], access_level="all", description="Show burst information") def burst_cmd(self, request, weapon_attack, weapon_recharge, burst_recharge, burst_skill): burst_info = self.get_burst_info(weapon_attack, weapon_recharge, burst_recharge, burst_skill) blob = "Attack: <highlight>%.2f secs</highlight>\n" % weapon_attack blob += "Recharge: <highlight>%.2f secs</highlight>\n" % weapon_recharge blob += "Burst Recharge: <highlight>%d</highlight>\n" % burst_recharge blob += "Burst Skill: <highlight>%d</highlight>\n\n" % burst_skill blob += "Burst Recharge: <highlight>%d secs</highlight>\n\n" % burst_info.recharge blob += "You need <highlight>%d</highlight> Burst Skill to cap your recharge at <highlight>%d secs</highlight>." % ( burst_info.skill_cap, burst_info.hard_cap) return ChatBlob("Burst Results", blob) @command(command="dimach", params=[Int("dimach_skill")], access_level="all", description="Show dimach information") def dimach_cmd(self, request, dimach_skill): dimach_info = self.get_dimach_info(dimach_skill) blob = "Dimach Skill: <highlight>%d</highlight>\n\n" % dimach_skill blob += "<header2>Martial Artist</header2>\n" blob += "Damage: <highlight>%d</highlight>\n" % dimach_info.ma_dmg blob += "Recharge: <highlight>%s</highlight>\n\n" % self.util.time_to_readable( dimach_info.ma_recharge) blob += "<header2>Keeper</header2>\n" blob += "Self Heal: <highlight>%d</highlight>\n" % dimach_info.keeper_heal blob += "Recharge: <highlight>5 mins</highlight> (constant)\n\n" blob += "<header2>Shade</header2>\n" blob += "Damage: <highlight>%d</highlight>\n" % dimach_info.shade_dmg blob += "Self Heal: <highlight>%d%%</highlight> * <highlight>%d</highlight> = <highlight>%d</highlight>\n" % \ (dimach_info.shade_heal_percentage, dimach_info.shade_dmg, round(dimach_info.shade_heal_percentage * dimach_info.shade_dmg / 100)) blob += "Recharge: <highlight>%s</highlight>\n\n" % self.util.time_to_readable( dimach_info.shade_recharge) blob += "<header2>All other professions</header2>\n" blob += "Damage: <highlight>%d</highlight>\n" % dimach_info.general_dmg blob += "Recharge: <highlight>30 mins</highlight> (constant)\n\n\n" blob += "Based on the !dimach command from Budabot by Imoutochan (RK1)" return ChatBlob("Dimach Results", blob) @command(command="fastattack", params=[Decimal("weapon_attack"), Int("fast_attack_skill")], access_level="all", description="Show fast attack information") def fastattack_cmd(self, request, weapon_attack, fast_attack_skill): fast_attack_info = self.get_fast_attack_info(weapon_attack, fast_attack_skill) blob = "Attack: <highlight>%.2f secs</highlight>\n" % weapon_attack blob += "Fast Attack Skill: <highlight>%d</highlight>\n\n" % fast_attack_skill blob += "Fast Attack Recharge: <highlight>%.2f secs</highlight>\n\n" % fast_attack_info.recharge blob += "You need <highlight>%d</highlight> Fast Attack Skill to cap your recharge at <highlight>%.2f secs</highlight>." % ( fast_attack_info.skill_cap, fast_attack_info.hard_cap) return ChatBlob("Fast Attack Results", blob) @command(command="flingshot", params=[Decimal("weapon_attack"), Int("fling_shot_skill")], access_level="all", description="Show fling shot information") def flingshot_cmd(self, request, weapon_attack, fling_shot_skill): fling_shot_info = self.get_fling_shot_info(weapon_attack, fling_shot_skill) blob = "Attack: <highlight>%.2f secs</highlight>\n" % weapon_attack blob += "Fling Shot Skill: <highlight>%d</highlight>\n\n" % fling_shot_skill blob += "Fling Shot Recharge: <highlight>%.2f secs</highlight>\n\n" % fling_shot_info.recharge blob += "You need <highlight>%d</highlight> Fling Shot Skill to cap your recharge at <highlight>%.2f secs</highlight>." % ( fling_shot_info.skill_cap, fling_shot_info.hard_cap) return ChatBlob("Fling Shot Results", blob) @command(command="fullauto", params=[ Decimal("weapon_attack"), Decimal("weapon_recharge"), Int("full_auto_recharge"), Int("full_auto_skill") ], access_level="all", description="Show full auto information") def fullauto_cmd(self, request, weapon_attack, weapon_recharge, full_auto_recharge, full_auto_skill): full_auto_info = self.get_full_auto_info(weapon_attack, weapon_recharge, full_auto_recharge, full_auto_skill) blob = "Attack: <highlight>%.2f secs</highlight>\n" % weapon_attack blob += "Recharge: <highlight>%.2f secs</highlight>\n" % weapon_recharge blob += "Full Auto Recharge: <highlight>%d</highlight>\n" % full_auto_recharge blob += "Full Auto Skill: <highlight>%d</highlight>\n\n" % full_auto_skill blob += "Full Auto Recharge: <highlight>%d secs</highlight>\n" % full_auto_info.recharge blob += "Max Number of Bullets: <highlight>%d</highlight>\n\n" % full_auto_info.max_bullets blob += "You need <highlight>%d</highlight> Full Auto Skill to cap your recharge at <highlight>%d secs</highlight>.\n\n" % ( full_auto_info.skill_cap, full_auto_info.hard_cap) blob += "From <highlight>0 to 10K</highlight> damage, the bullet damage is unchanged.\n" blob += "From <highlight>10K to 11.5K</highlight> damage, each bullet damage is halved.\n" blob += "From <highlight>11K to 15K</highlight> damage, each bullet damage is halved again.\n" blob += "<highlight>15K</highlight> is the damage cap." return ChatBlob("Full Auto Results", blob) @command(command="mafist", params=[Int("ma_skill")], access_level="all", description="Show martial arts information") def mafist_cmd(self, request, ma_skill): ma_info = self.get_martial_arts_info(ma_skill) blob = "Martial Arts Skill: <highlight>%d</highlight>\n\n" % ma_skill blob += "<header2>Martial Artist</header2>\n" blob += "Speed: <highlight>%.2f / %.2f secs</highlight>\n" % ( ma_info.ma_speed, ma_info.ma_speed) blob += "Damage: <highlight>%d - %d (%d)</highlight>\n\n" % ( ma_info.ma_min_dmg, ma_info.ma_max_dmg, ma_info.ma_crit_dmg) blob += "<header2>Shade</header2>\n" blob += "Speed: <highlight>%.2f / %.2f secs</highlight>\n" % ( ma_info.shade_speed, ma_info.shade_speed) blob += "Damage: <highlight>%d - %d (%d)</highlight>\n\n" % ( ma_info.shade_min_dmg, ma_info.shade_max_dmg, ma_info.shade_crit_dmg) blob += "<header2>All other professions</header2>\n" blob += "Speed: <highlight>%.2f / %.2f secs</highlight>\n" % ( ma_info.gen_speed, ma_info.gen_speed) blob += "Damage: <highlight>%d - %d (%d)</highlight>\n\n" % ( ma_info.gen_min_dmg, ma_info.gen_max_dmg, ma_info.gen_crit_dmg) return ChatBlob("Martial Arts Results", blob) @command(command="nanoinit", params=[Decimal("nano_attack_time"), Int("nano_cast_init")], access_level="all", description="Show nano cast init information") def nanoinit_cmd(self, request, nano_attack_time, nano_cast_init): nano_cast_info = self.get_nano_cast_info(nano_cast_init, nano_attack_time) blob = "Attack: <highlight>%.2f secs</highlight>\n" % nano_attack_time blob += "Nano Cast Init: <highlight>%d</highlight>\n\n" % nano_cast_init blob += "Cast Time Reduction: <highlight>%.2f</highlight>\n" % nano_cast_info.cast_time_reduction blob += "Effective Cast Time: <highlight>%.2f</highlight>\n\n" % nano_cast_info.effective_cast_time if nano_cast_info.bar_setting > 100: blob += "You cannot instacast this nano at any AGG/DEF setting.\n\n" else: blob += "You must set your AGG/DEF bar to <highlight>%d%%</highlight> to instacast this nano.\n\n" % nano_cast_info.bar_setting blob += "NanoC. Init needed to instacast at Full Agg (100%%): <highlight>%d</highlight>\n" % nano_cast_info.instacast_full_agg blob += "NanoC. Init needed to instacast at Neutral (87.5%%): <highlight>%d</highlight>\n" % nano_cast_info.instacast_neutral blob += "NanoC. Init needed to instacast at Half (50%%): <highlight>%d</highlight>\n" % nano_cast_info.instacast_half blob += "NanoC. Init needed to instacast at Full Def (0%%): <highlight>%d</highlight>\n\n" % nano_cast_info.instacast_full_def blob += "Cast time at Full Agg (100%%): <highlight>%.2f</highlight>\n" % nano_cast_info.cast_time_full_agg blob += "Cast time at Neutral (87.5%%): <highlight>%.2f</highlight>\n" % nano_cast_info.cast_time_neutral blob += "Cast time at Half (50%%): <highlight>%.2f</highlight>\n" % nano_cast_info.cast_time_half blob += "Cast time at Full Def (0%%): <highlight>%.2f</highlight>" % nano_cast_info.cast_time_full_def return ChatBlob("Nano Cast Init Results", blob) @command(command="weapon", params=[Int("item_id"), Int("ql", is_optional=True)], access_level="all", description="Show weapon information") def weapon_cmd(self, request, item_id, ql): return self.get_weapon_info(item_id, ql) @command(command="weapon", params=[Item("weapon_link")], access_level="all", description="Show weapon information") def weapon_cmd(self, request, item): return self.get_weapon_info(item.high_id, item.ql) @command(command="weapon", params=[Int("item_id"), Int("ql", is_optional=True)], access_level="all", description="Show weapon information") def weapon_manual_cmd(self, request, item_id, ql): if not ql: item = self.items_controller.get_by_item_id(item_id) if item: ql = item.highql else: return "Could not find item with id <highlight>%d</highlight>." % item_id return self.get_weapon_info(item_id, ql) def get_weapon_info(self, item_id, ql): if ql: item = self.db.query_single( "SELECT * FROM aodb WHERE highid = ? AND lowql <= ? AND highql >= ? " "UNION " "SELECT * FROM aodb WHERE lowid = ? AND lowql <= ? AND highql >= ? " "LIMIT 1", [item_id, ql, ql, item_id, ql, ql]) else: item = self.db.query_single( "SELECT * FROM aodb WHERE highid = ? UNION SELECT * FROM aodb WHERE lowid = ? LIMIT 1", [item_id, item_id]) if not item: return "Could not find item with ID <highlight>%d</highlight>." % item_id ql = ql or item.highql low_attributes = self.db.query_single( "SELECT * FROM weapon_attributes WHERE id = ?", [item.lowid]) high_attributes = self.db.query_single( "SELECT * FROM weapon_attributes WHERE id = ?", [item.highid]) if not low_attributes or not high_attributes: return "Could not find weapon information or item is not a weapon for ID <highlight>%d</highlight>." % item_id weapon_attack = self.util.interpolate_value( ql, { item.lowql: low_attributes.attack_time, item.highql: high_attributes.attack_time }) / 100 weapon_recharge = self.util.interpolate_value( ql, { item.lowql: low_attributes.recharge_time, item.highql: high_attributes.recharge_time }) / 100 blob = "%s (QL %d)\n\n" % (self.text.make_item(item.lowid, item.highid, ql, item.name), ql) blob += "Attack: <highlight>%.2f</highlight>\n" % weapon_attack blob += "Recharge: <highlight>%.2f</highlight>\n\n" % weapon_recharge blob += self.get_inits_display(weapon_attack, weapon_recharge) + "\n" blob += "<header2>Specials</header2>\n" if high_attributes.aimed_shot: as_info = self.get_aimed_shot_info(weapon_attack, weapon_recharge, 1) blob += "Aimed Shot\n<highlight>%d</highlight> skill needed to cap Aimed Shot recharge at <highlight>%d secs</highlight>\n\n" % \ (as_info.skill_cap, as_info.hard_cap) if high_attributes.burst: burst_recharge = self.util.interpolate_value( ql, { item.lowql: low_attributes.burst, item.highql: high_attributes.burst }) burst_info = self.get_burst_info(weapon_attack, weapon_recharge, burst_recharge, 1) blob += "Burst Recharge: <highlight>%d</highlight>\n<highlight>%d</highlight> skill needed to cap Burst recharge at <highlight>%d secs</highlight>\n\n" % \ (burst_recharge, burst_info.skill_cap, burst_info.hard_cap) if high_attributes.fast_attack: fast_attack_info = self.get_fast_attack_info(weapon_attack, 1) blob += "Fast Attack\n<highlight>%d</highlight> skill needed to cap Fast Attack recharge at <highlight>%.2f secs</highlight>\n\n" % \ (fast_attack_info.skill_cap, fast_attack_info.hard_cap) if high_attributes.fling_shot: fling_shot_info = self.get_fling_shot_info(weapon_attack, 1) blob += "Fling Shot\n<highlight>%d</highlight> skill needed to cap Fling Shot recharge at <highlight>%.2f secs</highlight>\n\n" % \ (fling_shot_info.skill_cap, fling_shot_info.hard_cap) if high_attributes.full_auto: full_auto_recharge = self.util.interpolate_value( ql, { item.lowql: low_attributes.full_auto, item.highql: high_attributes.full_auto }) full_auto_info = self.get_full_auto_info(weapon_attack, weapon_recharge, full_auto_recharge, 1) blob += "Full Auto Recharge: <highlight>%d</highlight>\n<highlight>%d</highlight> skill needed to cap Full Auto recharge at <highlight>%d secs</highlight>\n\n" % \ (full_auto_recharge, full_auto_info.skill_cap, full_auto_info.hard_cap) return ChatBlob("Weapon Info for %s (QL %d)" % (item.name, ql), blob) def get_init_result(self, weapon_attack, weapon_recharge, init_skill): if init_skill < 1200: attack_calc = (((weapon_attack - (init_skill / 600)) - 1) / 0.02) + 87.5 recharge_calc = (((weapon_recharge - (init_skill / 300)) - 1) / 0.02) + 87.5 else: attack_calc = (((weapon_attack - (1200 / 600) - ( (init_skill - 1200) / 600 / 3)) - 1) / 0.02) + 87.5 recharge_calc = (((weapon_recharge - (1200 / 300) - ( (init_skill - 1200) / 300 / 3)) - 1) / 0.02) + 87.5 if attack_calc < recharge_calc: init_result = recharge_calc else: init_result = attack_calc init_result = min(init_result, 100) # max of 100 init_result = max(init_result, 0) # min of 0 return init_result def get_inits_needed(self, init_result, weapon_attack, weapon_recharge): inits_attack = (( (init_result - 87.5) * 0.02) + 1 - weapon_attack) * -600 inits_recharge = (( (init_result - 87.5) * 0.02) + 1 - weapon_recharge) * -300 if inits_attack > 1200: inits_attack = ((( (init_result - 87.5) * 0.02) + 1 - weapon_attack + 2) * -1800) + 1200 if inits_recharge > 1200: inits_recharge = ((( (init_result - 87.5) * 0.02) + 1 - weapon_attack + 4) * -900) + 1200 if inits_attack < inits_recharge: return inits_recharge else: return inits_attack def get_aimed_shot_info(self, weapon_attack, weapon_recharge, aimed_shot_skill): result = DictObject() result.multiplier = round(aimed_shot_skill / 95) result.hard_cap = math.floor(weapon_attack + 10) result.skill_cap = math.ceil((4000 * weapon_recharge - 1100) / 3) as_recharge = math.ceil((weapon_recharge * 40) - (aimed_shot_skill * 3 / 100) + weapon_attack - 1) if as_recharge < result.hard_cap: as_recharge = result.hard_cap result.recharge = as_recharge return result def get_brawl_info(self, brawl_skill): min_values = { 1: 1, 1000: 100, 1001: 101, 2000: 170, 2001: 171, 3000: 235 } max_values = { 1: 2, 1000: 500, 1001: 501, 2000: 850, 2001: 851, 3000: 1145 } crit_values = { 1: 3, 1000: 500, 1001: 501, 2000: 600, 2001: 601, 3000: 725 } brawl_info = DictObject() brawl_info.min_dmg = self.util.interpolate_value( brawl_skill, min_values) brawl_info.max_dmg = self.util.interpolate_value( brawl_skill, max_values) brawl_info.crit_dmg = self.util.interpolate_value( brawl_skill, crit_values) brawl_info.stun_chance = 10 if brawl_skill < 1000 else 20 brawl_info.stun_duration = 3 if brawl_skill < 2001 else 4 return brawl_info def get_burst_info(self, weapon_attack, weapon_recharge, burst_recharge, burst_skill): result = DictObject() result.hard_cap = round(weapon_attack + 8) result.skill_cap = math.floor( ((weapon_recharge * 20) + (burst_recharge / 100) - 8) * 25) recharge = math.floor((weapon_recharge * 20) + (burst_recharge / 100) - (burst_skill / 25) + weapon_attack) if recharge < result.hard_cap: recharge = result.hard_cap result.recharge = recharge return result def get_dimach_info(self, dimach_skill): # item ids: 42033, 42032, 213260, 213261, 213262, 213263 general_dmg = { 1: 1, 1000: 2000, 1001: 2001, 2000: 2500, 2001: 2501, 3000: 2850 } # item ids: 42033, 42032, 213264, 213265, 213266, 213267 ma_recharge = { 1: 1800, 1000: 1800, 1001: 1188, 2000: 600, 2001: 600, 3000: 300 } ma_dmg = { 1: 1, 1000: 2000, 1001: 2001, 2000: 2340, 2001: 2341, 3000: 2550 } # item ids: 213269, 213270, 213271, 213272, 213273, 213274 shade_recharge = { 1: 300, 1000: 300, 1001: 300, 2000: 300, 2001: 240, 3000: 200 } shade_dmg = { 1: 1, 1000: 920, 1001: 921, 2000: 1872, 2001: 1873, 3000: 2750 } shade_heal_percentage = { 1: 70, 1000: 70, 1001: 70, 2000: 75, 2001: 75, 3000: 80 } # item ids: 211399, 211400, 213275, 213276, 213277, 213278 keeper_heal = { 1: 1, 1000: 3000, 1001: 3001, 2000: 10500, 2001: 10501, 3000: 15000 } result = DictObject() result.general_dmg = self.util.interpolate_value( dimach_skill, general_dmg) result.ma_recharge = self.util.interpolate_value( dimach_skill, ma_recharge) result.ma_dmg = self.util.interpolate_value(dimach_skill, ma_dmg) result.shade_recharge = self.util.interpolate_value( dimach_skill, shade_recharge) result.shade_dmg = self.util.interpolate_value(dimach_skill, shade_dmg) result.shade_heal_percentage = self.util.interpolate_value( dimach_skill, shade_heal_percentage) result.keeper_heal = self.util.interpolate_value( dimach_skill, keeper_heal) return result def get_fast_attack_info(self, weapon_attack, fast_attack_skill): result = DictObject() result.hard_cap = weapon_attack + 5 result.skill_cap = ((weapon_attack * 16) - result.hard_cap) * 100 recharge = (weapon_attack * 16) - (fast_attack_skill / 100) if recharge < result.hard_cap: recharge = result.hard_cap result.recharge = recharge return result def get_fling_shot_info(self, weapon_attack, fling_shot_skill): result = DictObject() result.hard_cap = weapon_attack + 5 result.skill_cap = ((weapon_attack * 16) - result.hard_cap) * 100 recharge = (weapon_attack * 16) - (fling_shot_skill / 100) if recharge < result.hard_cap: recharge = result.hard_cap result.recharge = recharge return result def get_full_auto_info(self, weapon_attack, weapon_recharge, full_auto_recharge, full_auto_skill): result = DictObject() result.hard_cap = math.floor(weapon_attack + 10) result.skill_cap = ((weapon_recharge * 40) + (full_auto_recharge / 100) - 11) * 25 result.max_bullets = 5 + math.floor(full_auto_skill / 100) recharge = round((weapon_recharge * 40) + (full_auto_recharge / 100) - (full_auto_skill / 25) + round(weapon_attack - 1)) if recharge < result.hard_cap: recharge = result.hard_cap result.recharge = recharge return result def get_martial_arts_info(self, ma_skill): result = DictObject() # ma items: http://budabot.com/forum/viewtopic.php?f=7&t=1264&p=5739#p5739 # QL 1 100 500 1 500 1 500 # 211349, 211350, 211351, 211359, 211360, 211365, 211366 // Shade # 211352, 211353, 211354, 211357, 211358, 211363, 211364 // MA # 43712, 144745, 43713, 211355, 211356, 211361, 211362 // Gen/other ma_min_dmg = { 1: 4, 200: 45, 1000: 125, 1001: 130, 2000: 220, 2001: 225, 3000: 450 } ma_max_dmg = { 1: 8, 200: 75, 1000: 400, 1001: 405, 2000: 830, 2001: 831, 3000: 1300 } ma_crit_dmg = { 1: 3, 200: 50, 1000: 500, 1001: 501, 2000: 560, 2001: 561, 3000: 800 } ma_speed = { 1: 1.15, 200: 1.20, 1000: 1.25, 1001: 1.30, 2000: 1.35, 2001: 1.45, 3000: 1.50 } shade_min_dmg = { 1: 3, 200: 25, 1000: 55, 1001: 56, 2000: 130, 2001: 131, 3000: 280 } shade_max_dmg = { 1: 5, 200: 60, 1000: 258, 1001: 259, 2000: 682, 2001: 683, 3000: 890 } shade_crit_dmg = { 1: 3, 200: 50, 1000: 250, 1001: 251, 2000: 275, 2001: 276, 3000: 300 } shade_speed = { 1: 1.25, 200: 1.25, 1000: 1.45, 1001: 1.45, 2000: 1.65, 2001: 1.65, 3000: 1.85 } gen_min_dmg = { 1: 3, 200: 25, 1000: 65, 1001: 66, 2000: 140, 2001: 204, 3000: 300 } gen_max_dmg = { 1: 5, 200: 60, 1000: 280, 1001: 281, 2000: 715, 2001: 831, 3000: 990 } gen_crit_dmg = { 1: 3, 200: 50, 1000: 500, 1001: 501, 2000: 605, 2001: 605, 3000: 630 } gen_speed = { 1: 1.25, 200: 1.25, 1000: 1.45, 1001: 1.45, 2000: 1.65, 2001: 1.65, 3000: 1.85 } result.ma_min_dmg = self.util.interpolate_value(ma_skill, ma_min_dmg) result.ma_max_dmg = self.util.interpolate_value(ma_skill, ma_max_dmg) result.ma_crit_dmg = self.util.interpolate_value(ma_skill, ma_crit_dmg) result.ma_speed = self.util.interpolate_value(ma_skill, ma_speed, 2) result.shade_min_dmg = self.util.interpolate_value( ma_skill, shade_min_dmg) result.shade_max_dmg = self.util.interpolate_value( ma_skill, shade_max_dmg) result.shade_crit_dmg = self.util.interpolate_value( ma_skill, shade_crit_dmg) result.shade_speed = self.util.interpolate_value( ma_skill, shade_speed, 2) result.gen_min_dmg = self.util.interpolate_value(ma_skill, gen_min_dmg) result.gen_max_dmg = self.util.interpolate_value(ma_skill, gen_max_dmg) result.gen_crit_dmg = self.util.interpolate_value( ma_skill, gen_crit_dmg) result.gen_speed = self.util.interpolate_value(ma_skill, gen_speed, 2) return result def get_nano_cast_info(self, nano_cast_init, nano_attack_time): if nano_cast_init > 1200: nano_cast_reduction = (nano_cast_init - 1200) / 600 + 6 else: nano_cast_reduction = nano_cast_init / 200 result = DictObject() result.cast_time_reduction = nano_cast_reduction result.effective_cast_time = nano_attack_time - nano_cast_reduction result.instacast_full_agg = self.get_nano_init_for_instacast( nano_attack_time - 1) result.instacast_neutral = self.get_nano_init_for_instacast( nano_attack_time - 0.75) result.instacast_half = self.get_nano_init_for_instacast( nano_attack_time) result.instacast_full_def = self.get_nano_init_for_instacast( nano_attack_time + 1) result.cast_time_full_agg = result.effective_cast_time - 1 result.cast_time_neutral = result.effective_cast_time - 0.75 result.cast_time_half = result.effective_cast_time result.cast_time_full_def = result.effective_cast_time + 1 bar_setting = round(result.effective_cast_time / 0.02 + 50) if bar_setting < 0: bar_setting = 0 result.bar_setting = bar_setting return result def get_nano_init_for_instacast(self, nano_attack_time): if nano_attack_time < 6: return nano_attack_time * 200 else: return 1200 + (nano_attack_time - 6) * 600 def get_inits_display(self, weapon_attack, weapon_recharge): num_steps = 9 step_size = 100 / (num_steps - 1) blob = "" for i in reversed(range(0, num_steps)): inits_needed = self.get_inits_needed(i * step_size, weapon_attack, weapon_recharge) blob += "DEF >%s%s%s< AGG %d%% %d init \n" % ( "=" * i, "][", "=" * (num_steps - 1 - i), i * step_size, inits_needed) return blob
class CharacterHistoryController: def __init__(self): pass def inject(self, registry): self.bot = registry.get_instance("bot") self.text = registry.get_instance("text") self.util = registry.get_instance("util") self.character_history_service = registry.get_instance( "character_history_service") self.command_alias_service = registry.get_instance( "command_alias_service") def start(self): self.command_alias_service.add_alias("h", "history") @command( command="history", params=[Character("character"), Int("server_num", is_optional=True)], access_level="guest", description="Get history of character", extended_description= "Use server_num 6 for RK2019 and server_num 5 for live") def handle_history_cmd1(self, request, char, server_num): server_num = server_num or self.bot.dimension data = self.character_history_service.get_character_history( char.name, server_num) if not data: return "Could not find history for <highlight>%s</highlight> on server <highlight>%d</highlight>." % ( char.name, server_num) return ChatBlob( "History of %s (RK%d)" % (char.name, server_num), self.format_character_history(char.name, server_num, data)) def format_character_history(self, name, server_num, history): col_separator = " | " rows = [[ "Name", "Date", "Lvl", "AI", "Side", "Breed", "CharId", "Org (Rank)" ]] uniques = set() for row in history: if row.nickname and row.nickname != name: uniques.add(row.nickname) if row.char_id and row.char_id != name: uniques.add(row.char_id) if row.guild_name: org = "%s (%s)" % (row.guild_name, row.guild_rank_name) else: org = "" last_changed = self.util.format_date(int(float(row.last_changed))) current_row = [row.nickname, last_changed] if row.deleted == "1": # This value is output as string current_row.append("<red>DELETED</red>") else: current_row.extend([ row.level, "<green>%s</green>" % (row.defender_rank or "0"), row.faction, row.breed, row.char_id, org ]) rows.append(current_row) rows = self.text.pad_table(rows) blob = " ".join( map( lambda x: "[" + self.text.make_tellcmd( f"History {x}", f"history {x} {server_num}") + "]", uniques)) + "\n\n" blob += col_separator.join(rows[0]) + "\n" blob += "__________________________________________________________\n" for columns in rows[1:]: blob += col_separator.join(columns) + "\n" blob += "\nHistory provided by Auno.org, Chrisax, and Athen Paladins" return blob
class PointsController: def __init__(self): pass 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.character_service: CharacterService = registry.get_instance( "character_service") self.util: Util = registry.get_instance("util") self.setting_service: SettingService = registry.get_instance( "setting_service") self.alts_service: AltsService = registry.get_instance("alts_service") def start(self): if self.db.query_single( "SELECT COUNT(*) AS count FROM points_presets").count < 1: # Populate with pre-made presets if empty presets = [ "s13", "s28", "s35", "s42", "zodiac", "zod", "tnh", "beast", "12m", "tara", "pvp", "towers", "wipe", "clanwipe", "clan", "omniwipe", "omni", "bonus", "early" ] sql = "INSERT INTO points_presets (name) VALUES (?)" for preset in presets: self.db.exec(sql, [preset]) @setting(name="initial_points_value", value="0", description="How many points new accounts start with") def initial_points_value(self): return NumberSettingType() @command(command="account", params=[Const("create"), Character("char")], access_level="moderator", description="Create a new account for given character name", sub_command="modify") def bank_create_cmd(self, request, _, char: SenderObj): alts_info = self.alts_service.get_alts(char.char_id) for alt in alts_info: sql = "SELECT char_id, disabled FROM points WHERE char_id = ? LIMIT 1" count = self.db.query_single(sql, [alt.char_id]) if count: was_disabled = False if count.disabled == 1: if self.db.exec( "UPDATE points SET disabled = 0 WHERE char_id = ?", [alt.char_id]): was_disabled = True if alt.char_id == char.char_id: if was_disabled: if self.add_log_entry( alt.char_id, request.sender.char_id, "Account was re-enabled by %s" % self.character_service.resolve_char_to_name( request.sender.char_id)): return "<highlight>%s<end>'s account has been re-enabled." % char.name else: return "<highlight>%s<end> has an account, but failed to re-enable it." % char.name else: return "<highlight>%s<end> already has an account." % char.name else: if was_disabled: if self.add_log_entry( alt.char_id, request.sender.char_id, "Account was re-enabled by %s" % self.character_service.resolve_char_to_name( request.sender.char_id)): return "<highlight>%s<end>'s (%s) account has been re-enabled." % ( char.name, self.character_service.resolve_char_to_name( alt.char_id)) else: return "<highlight>%s<end> (%s) has an account, but failed to re-enable it." % ( char.name, self.character_service.resolve_char_to_name( alt.char_id)) else: return "<highlight>%s<end> (%s) already has an account." % ( char.name, self.character_service.resolve_char_to_name( alt.char_id)) main_info = alts_info.pop(0) changed_to_main = main_info.char_id == char.char_id initial_points = self.setting_service.get( "initial_points_value").get_value() sql = "INSERT INTO points (char_id, points, created) VALUES (?,?,?)" if self.db.exec(sql, [main_info.char_id, initial_points, int(time.time())]) < 1: return "Failed to create an account for <highlight>%s<end>." % char.name if not self.add_log_entry( main_info.char_id, request.sender.char_id, "Account opened by %s" % request.sender.name): sql = "DELETE FROM points WHERE char_id = ?" self.db.exec(sql, [main_info.char_id]) return "Failed to create an account for <highlight>%s<end>." % char.name name_reference = "%s (%s)" % ( char.name, self.character_service.resolve_char_to_name( main_info.char_id)) if changed_to_main else char.name return "A new account has been created for <highlight>%s<end>." % name_reference @command(command="account", params=[Const("close"), Character("char")], access_level="moderator", description="Close the account for given character name", sub_command="modify") def close_account_cmd(self, request, _, char: SenderObj): main_id = self.alts_service.get_main(char.char_id) sql = "UPDATE points SET disabled = 1 WHERE char_id = ?" if self.db.exec(sql, [main_id.char_id]) > 0: reason = "Account was closed by %s" % self.character_service.resolve_char_to_name( request.sender.char_id) if self.add_log_entry(main_id.char_id, request.sender.char_id, reason): name_reference = "%s (%s)" % ( char.name, self.character_service.resolve_char_to_name( main_id.char_id) ) if main_id.char_id != char.char_id else char.name return "<highlight>%s<end> has had their account disabled. Logs have been preserved." % name_reference return "<highlight>%s<end> does not have an open account." % char.name @command(command="account", params=[], access_level="all", description="Look up your account") def account_self_cmd(self, request): return self.get_account_display(request.sender) @command(command="account", params=[Const("logentry"), Int("log_id")], access_level="moderator", description="Look up specific log entry", sub_command="modify") def account_log_entry_cmd(self, _1, _2, log_id: int): log_entry = self.db.query_single( "SELECT * FROM points_log WHERE log_id = ?", [log_id]) if log_entry: char_name = self.character_service.resolve_char_to_name( log_entry.char_id) leader_name = self.character_service.resolve_char_to_name( log_entry.leader_id) blob = "Log entry ID: <highlight>%d<end>\n" % log_id blob += "Affecting account: <highlight>%s<end>\n" % char_name blob += "Action by: <highlight>%s<end>\n" % leader_name blob += "Type: <highlight>%s<end>\n" % ( "Management" if log_entry.audit == 0 else "Altering of points") blob += "Reason: <highlight>%s<end>\n" % log_entry.reason action_links = None if log_entry.audit == 0: if "closed" in log_entry.reason: action_links = self.text.make_chatcmd( "Open the account", "/tell <myname> account create %s" % char_name) elif "re-enabled" in log_entry.reason: action_links = self.text.make_chatcmd( "Close the account", "/tell <myname> account close %s" % char_name) else: if log_entry.audit < 0: reason = "Points from event (%d) has been retracted, %d points have been added." \ % (log_id, (-1*log_entry.audit)) action_links = self.text.make_chatcmd( "Retract", "/tell <myname> bank give %d %s %s" % ((-1 * log_entry.audit), char_name, reason)) else: reason = "Points from event (%d) has been retracted, %d points have been deducted." \ % (log_id, log_entry.audit) action_links = self.text.make_chatcmd( "Retract", "/tell <myname> bank take %d %s %s" % (log_entry.audit, char_name, reason)) blob += "Actions available: [%s]\n" % (action_links if action_links is not None else "No actions available") return ChatBlob("Log entry (%d)" % log_id, blob) return "No log entry with given ID <highlight>%d<end>." % log_id @command(command="account", params=[ Options(["give", "take"]), Int("amount"), Character("char"), Any("reason") ], access_level="moderator", description="Give or take points from character account", sub_command="modify") def account_give_take_cmd(self, request, action: str, amount: int, char: SenderObj, reason: str): main_id = self.alts_service.get_main(char.char_id) sql = "SELECT * FROM points WHERE char_id = ?" points = self.db.query_single(sql, [main_id.char_id]) if points: if points.disabled == 1: return "<highlight>%s<end>'s account is disabled, altering the account is not possible." % char.name if points.points == 0 and action == "take": return "<highlight>%s<end> has 0 points - can't have less than 0 points." % char.name if amount > points.points and action == "take": amount = points.points new_points = amount if action == "give" else 0 - amount if not self.alter_points(points.points, main_id.char_id, new_points, request.sender.char_id, reason): return "Failed to alter <highlight>%s<end>'s account." % char.name action = "taken from" if action == "take" else "added to" return "<highlight>%s<end> has had <highlight>%d<end> points %s their account." % ( char.name, amount, action) return "<highlight>%s<end> does not have an account." % char.name @command(command="account", params=[Character("char")], access_level="moderator", description="Look up account of another char", sub_command="modify") def account_other_cmd(self, request, char: SenderObj): return self.get_account_display(char) @command(command="presets", params=[Const("add"), Any("name"), Int("points")], access_level="admin", description="Add new points preset") def presets_add_cmd(self, _1, _2, name: str, points: int): count = self.db.query_single( "SELECT COUNT(*) AS count FROM points_presets WHERE name = ?", [name]).count if count > 0: return "A preset already exists with the name <highlight>%s<end>." % name sql = "INSERT INTO points_presets (name, points) VALUES (?,?)" if self.db.exec(sql, [name, points]) > 0: return "A preset with the name <highlight>%s<end> was added, worth <green>%d<end> points." % ( name, points) return "Failed to insert new preset in DB." @command(command="presets", params=[Const("rem"), Int("preset_id")], access_level="admin", description="Delete preset") def presets_rem_cmd(self, _1, _2, preset_id: int): if self.db.exec("DELETE FROM points_presets WHERE preset_id = ?", [preset_id]) > 0: return "Successfully removed preset with ID <highlight>%d<end>." % preset_id return "No preset with given ID <highlight>%d<end>." % preset_id @command(command="presets", params=[Const("alter"), Int("preset_id"), Int("new_points")], access_level="admin", description="Alter the points dished out by given preset") def presets_alter_cmd(self, _1, _2, preset_id: int, new_points: int): preset = self.db.query_single( "SELECT * FROM points_presets WHERE preset_id = ?", [preset_id]) if preset: if self.db.exec( "UPDATE points_presets SET points = ? WHERE preset_id = ?", [new_points, preset_id]) > 0: return "Successfully updated the preset, <highlight>%s<end>, to dish out " \ "<green>%d<end> points instead of <red>%d<end>." % (preset.name, new_points, preset.points) return "Failed to update preset with ID <highlight>%d<end>." % preset_id @command(command="presets", params=[], access_level="admin", description="See list of points presets") def presets_cmd(self, _): return ChatBlob("Points presets", self.build_preset_list()) def build_preset_list(self): presets = self.db.query( "SELECT * FROM points_presets ORDER BY name ASC, points DESC") if presets: blob = "" for preset in presets: add_points_link = self.text.make_chatcmd( "Add pts", "/tell <myname> raid addpts %s" % preset.name) delete_link = self.text.make_chatcmd( "Delete", "/tell <myname> presets rem %d" % preset.preset_id) blob += "<highlight>%s<end> worth <green>%d<end> points [id: %d]\n | [%s] [%s]\n\n" \ % (preset.name, preset.points, preset.preset_id, add_points_link, delete_link) return blob return "No presets available. To add new presets use <highlight><symbol>presets add preset_name preset_points<end>." def add_log_entry(self, char_id: int, leader_id: int, reason: str, amount=0): sql = "INSERT INTO points_log (char_id, audit, leader_id, reason, time) VALUES (?,?,?,?,?)" return self.db.exec( sql, [char_id, amount, leader_id, reason, int(time.time())]) > 0 def alter_points(self, current_points: int, char_id: int, amount: int, leader_id: int, reason: str): sql = "UPDATE points SET points = points + ? WHERE char_id = ?" if self.db.exec(sql, [amount, char_id]) < 1: return False if not self.add_log_entry(char_id, leader_id, reason, amount): sql = "UPDATE points p SET p.points = ? WHERE p.char_id = ?" self.db.exec(sql, [current_points, char_id]) return False return True def get_account(self, main_id): sql = "SELECT p.char_id, p.points, p.disabled FROM points p WHERE p.char_id = ?" return self.db.query_single(sql, [main_id]) def get_account_display(self, char: SenderObj): main = self.alts_service.get_main(char.char_id) if not main: return "Could not find character <highlight>%s<end>." % char.name points_log = self.db.query( "SELECT * FROM points_log WHERE char_id = ? ORDER BY time DESC LIMIT 50", [main.char_id]) points = self.db.query_single( "SELECT points, disabled FROM points WHERE char_id = ?", [main.char_id]) if not points: return "Could not find raid account for <highlight>%s<end>." % char.name alts_link = self.text.make_chatcmd( "Alts", "/tell <myname> alts %s" % main.name) blob = "" blob += "Holder of account: %s [%s]\n" % (main.name, alts_link) blob += "Points: %d\n" % points.points blob += "Status: %s\n\n" % ("<green>Open<end>" if points.disabled == 0 else "<red>Disabled<end>") blob += "<header2>Account log<end>\n" if points_log is None: blob += "No entries in log." else: for entry in points_log: name_reference = "<highlight>%s<end>" % char.name if entry.audit == 0: # If points is 0, then it's a general case log blob += "<grey>[%s]<end> <orange>\"%s\"<end>" % ( self.util.format_datetime(entry.time), entry.reason) elif entry.audit > 0: pts = "<green>%d<end>" % entry.audit blob += "<grey>[%s]<end> %s points were added to %s account " \ "by <highlight>%s<end> with reason <orange>%s<end>" \ % (self.util.format_datetime(entry.time), pts, name_reference, self.character_service.resolve_char_to_name(entry.leader_id), entry.reason) elif entry.audit < 0: pts = "<red>%d<end>" % (-1 * entry.audit) blob += "<grey>[%s]<end> %s points were taken from %s account " \ "by <highlight>%s<end> with reason <orange>%s<end>" \ % (self.util.format_datetime(entry.time), pts, name_reference, self.character_service.resolve_char_to_name(entry.leader_id), entry.reason) log_entry_link = self.text.make_chatcmd( "%d" % entry.log_id, "/tell <myname> account logentry %d" % entry.log_id) blob += " [%s]\n" % log_entry_link return ChatBlob("%s Account" % char.name, blob)
class rulesController: def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.bot: Tyrbot = registry.get_instance("bot") self.access_service: AccessService = registry.get_instance( "access_service") self.buddy_service = registry.get_instance("buddy_service") self.character_service: CharacterService = registry.get_instance( "character_service") self.setting_service: SettingService = registry.get_instance( "setting_service") self.alts_service: AltsService = registry.get_instance("alts_service") @command(command="rules", params=[], access_level="all", description="Displays the rules") def rules_command(self, request): rules = self.get_rules() self.bot.send_private_message(request.sender.char_id, ChatBlob("Rules", rules)) def get_rules(self): blob = "" rules = self.db.query( "SELECT * FROM rules ORDER BY priority, indent, identifier, rule ASC" ) admin_link = self.text.make_chatcmd("admin", "/tell <myname> admins") blob += "<header>:: Disclaimer :: <end>\n" blob += "The leaders of this bot are free to interpretate the rules in their own way.\n\n" blob += "If you feel like one of the leaders is abusing the status, feel free to message an %s.\n\n" % admin_link blob += "<header>:: Rules ::<end>\n" for rule in rules: entry = rule.rule priority = rule.priority indent = rule.indent identifier = rule.identifier if indent >= 0: blob += "<highlight>%s%s<end> <white>%s<end>\n" % ( rule.priority, rule.identifier, rule.rule) if rules: return blob else: return "No rules have been set yet." @command(command="addrule", params=[Any("rule")], access_level="moderator", description="Adds a rule") def rules_add_command(self, request, rule: str): sql = "INSERT INTO rules (rule, priority, indent) VALUES (?,?,?)" count = self.db.exec(sql, [rule, 0, 0]) layoutlink = self.text.make_chatcmd("ruleslayout", "/tell <myname> ruleslayout") if count > 0: return "Successfully added the new rules entry. Use %s to see an editorial overview of rules." % layoutlink else: return "Failed to add the new rules entry (DB insertion error)." @command(command="remrule", params=[Int("rule_id")], access_level="moderator", description="Removes a rule") def rules_rem_command(self, request, rule_id): sql = "DELETE FROM rules WHERE id = ?" count = self.db.exec(sql, [rule_id]) layoutlink = self.text.make_chatcmd("ruleslayout", "/tell <myname> ruleslayout") if count > 0: return "Successfully removed the rules entry. Use %s to see an editorial overview of rules." % layoutlink else: return "Failed to remove the new rules entry (DB insertion error)." @command(command="rulepinc", params=[Int("rule_id")], access_level="moderator", description="Changes the priority of the rule") def rules_pinc_command(self, request, rule_id): sql = "UPDATE rules SET priority = (priority + 1) WHERE id = ?" count = self.db.exec(sql, [rule_id]) layoutlink = self.text.make_chatcmd("ruleslayout", "/tell <myname> ruleslayout") if count > 0: return "Successfully changed the priority of rule entry %s. Use %s to see an editorial overview of rules." % ( rule_id, layoutlink) else: return "Failed to change priority of the rule entry (DB update error)." @command(command="rulepdec", params=[Int("rule_id")], access_level="moderator", description="Changes the priority of the rule") def rules_pdec_command(self, request, rule_id): sql = "UPDATE rules SET priority = (priority - 1) WHERE id = ?" count = self.db.exec(sql, [rule_id]) layoutlink = self.text.make_chatcmd("ruleslayout", "/tell <myname> ruleslayout") if count > 0: return "Successfully changed the priority of rule entry %s. Use %s to see an editorial overview of rules." % ( rule_id, layoutlink) else: return "Failed to change priority of the rule entry (DB update error)." @command(command="ruleindinc", params=[Int("rule_id")], access_level="moderator", description="Changes the indent of the rule") def rules_indic_command(self, request, rule_id): sql = "UPDATE rules SET indent = (indent + 1) WHERE id = ?" count = self.db.exec(sql, [rule_id]) layoutlink = self.text.make_chatcmd("ruleslayout", "/tell <myname> ruleslayout") if count > 0: return "Successfully changed the indent of rule entry %s. Use %s to see an editorial overview of rules." % ( rule_id, layoutlink) else: return "Failed to change indent of the rule entry (DB update error)." @command(command="ruleinddec", params=[Int("rule_id")], access_level="moderator", description="Changes the indent of the rule") def rules_inddec_command(self, request, rule_id): sql = "UPDATE rules SET indent = (indent - 1) WHERE id = ?" count = self.db.exec(sql, [rule_id]) layoutlink = self.text.make_chatcmd("ruleslayout", "/tell <myname> ruleslayout") if count > 0: return "Successfully changed the indent of rule entry %s. Use %s to see an editorial overview of rules." % ( rule_id, layoutlink) else: return "Failed to change indent of the rule entry (DB update error)." @command(command="rulealteridentifier", params=[Int("rule_id"), Any("identifier")], access_level="moderator", description="Changes the identifier of the rule") def rules_alteridentifier_command(self, request, rule_id, word: str): sql = "UPDATE rules SET identifier ? WHERE id = ?" count = self.db.exec(sql, [identifier, rule_id]) layoutlink = self.text.make_chatcmd("ruleslayout", "/tell <myname> ruleslayout") if count > 0: return "Successfully changed the identifier of rule entry %s. Use %s to see an editorial overview of rules." % ( rule_id, layoutlink) else: return "Failed to change identifier of the rule entry (DB update error)." @command(command="ruleslayout", params=[], access_level="moderator", description="Displays the ruleslayout") def rules_layout_command(self, request): ruleslayout = self.get_rules_layout() self.bot.send_private_message(request.sender.char_id, ChatBlob("Ruleslayout", ruleslayout)) def get_rules_layout(self): blob = "" sql = "SELECT * FROM rules ORDER BY priority, indent, identifier, rule ASC" rules = self.db.query(sql) admin_link = self.text.make_chatcmd("admin", "/tell <myname> admins") blob += "<header>:: Disclaimer :: <end>\n" blob += "The leaders of this bot are free to interpretate the rules in their own way.\n\n" blob += "If you feel like one of the leaders is abusing the status, feel free to message an %s.\n\n" % admin_link blob += "<header>:: Ruleslayout ::<end>\n" for rule in rules: entry = rule.rule priority = rule.priority indent = rule.indent identifier = rule.identifier if indent >= 0: incp = self.text.make_chatcmd( "p+", "/tell <myname> rulepinc %s") % rule.id decp = self.text.make_chatcmd( "p-", "/tell <myname> rulepdec %s") % rule.id inci = self.text.make_chatcmd( "i+", "/tell <myname> ruleindinc %s") % rule.id deci = self.text.make_chatcmd( "i-", "/tell <myname> ruleinddec %s") % rule.id blob += "%s%s <highlight>%s<end>" % (priority, identifier, entry) blob += " (p: %s || i: %s || id: %s)" % (priority, indent, rule.id) blob += " [%s] [%s] [%s] [%s]\n" % (incp, decp, inci, deci) return blob
class AlienBioController: def __init__(self): self.ofab_armor_types = ["64", "295", "468", "935"] self.ofab_weapon_types = ["18", "34", "687", "812"] self.alien_armor_types = ["mutated", "pristine"] self.alien_weapon_types = [ "1", "2", "3", "4", "5", "12", "13", "48", "76", "112", "240", "880", "992" ] def inject(self, registry): self.db = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.util = registry.get_instance("util") self.items_controller = registry.get_instance("items_controller") self.command_alias_service = registry.get_instance( "command_alias_service") def start(self): self.command_alias_service.add_alias("clump", "bio") @command(command="bio", params=[Item("bio_material")], access_level="all", description="Show info about Kyr'Ozch Bio-Material") def bio_command(self, request, item): high_id = item.high_id ql = item.ql if high_id == 247707 or high_id == 247708: bio_type = "1" elif high_id == 247709 or high_id == 247710: bio_type = "2" elif high_id == 247717 or high_id == 247718: bio_type = "3" elif high_id == 247711 or high_id == 247712: bio_type = "4" elif high_id == 247713 or high_id == 247714: bio_type = "5" elif high_id == 247715 or high_id == 247716: bio_type = "12" elif high_id == 247719 or high_id == 247720: bio_type = "13" elif high_id == 288699 or high_id == 288700: bio_type = "48" elif high_id == 247697 or high_id == 247698: bio_type = "76" elif high_id == 247699 or high_id == 247700: bio_type = "112" elif high_id == 247701 or high_id == 247702: bio_type = "240" elif high_id == 247703 or high_id == 247704: bio_type = "880" elif high_id == 247705 or high_id == 247706: bio_type = "992" elif high_id == 247102 or high_id == 247103: bio_type = "pristine" elif high_id == 247104 or high_id == 247105: bio_type = "mutated" elif high_id == 247764 or high_id == 254804: bio_type = "serum" else: bio_type = "unknown" bio_info = self.get_bio_info(bio_type, ql) if bio_info: return bio_info else: return "Bio-Material type unknown or not a bio-material." @command(command="bioinfo", params=[], access_level="all", description="Show list of Kyr'Ozch Bio-Material types") def bioinfo_list_command(self, request): blob = "<header2>OFAB Armor Types<end>\n" blob += self.get_type_blob(self.ofab_armor_types) blob += "\n<header2>OFAB Weapon Types<end>\n" blob += self.get_type_blob(self.ofab_weapon_types) blob += "\n<header2>AI Armor Types<end>\n" blob += self.get_type_blob(self.alien_armor_types) blob += "\n<header2>AI Weapon Types<end>\n" blob += self.get_type_blob(self.alien_weapon_types) blob += "\n<header2>Serum<end>\n" blob += self.get_type_blob(["serum"]) return ChatBlob("Bio-Material Types", blob) def get_type_blob(self, bio_types): blob = "" for bio_type in bio_types: blob += self.text.make_chatcmd( bio_type, "/tell <myname> bioinfo %s" % bio_type) + "\n" return blob @command(command="bioinfo", params=[Any("bio_type"), Int("ql", is_optional=True)], access_level="all", description="Show info about a bio-material type") def bioinfo_show_command(self, request, bio_type, ql): ql = ql or 300 bio_info = self.get_bio_info(bio_type, ql) if bio_info: return bio_info else: return "Unknown bio-material type <highlight>%s<end>." % bio_type def get_bio_info(self, bio_type, ql): if bio_type in self.ofab_armor_types: return self.ofab_armor_bio(bio_type, ql) elif bio_type in self.ofab_weapon_types: return self.ofab_weapon_bio(bio_type, ql) elif bio_type in self.alien_armor_types: return self.alien_armor_bio(bio_type, ql) elif bio_type in self.alien_weapon_types: return self.alien_weapon_bio(bio_type, ql) elif bio_type == "serum": return self.serum_bio(ql) else: return None def ofab_armor_bio(self, bio_type, ql): name = "Kyr'Ozch Bio-Material - Type %s" % bio_type data = self.db.query("SELECT * FROM ofab_armor_type WHERE type = ?", [bio_type]) blob = self.display_item(name, ql) + "\n\n" blob += "<highlight>Upgrades Ofab Armor for:<end>\n" for row in data: blob += self.text.make_chatcmd( row.profession, "/tell <myname> ofabarmor %s" % row.profession) + "\n" return ChatBlob("%s (QL %d)" % (name, ql), blob) def ofab_weapon_bio(self, bio_type, ql): name = "Kyr'Ozch Bio-Material - Type %s" % bio_type data = self.db.query("SELECT * FROM ofab_weapons WHERE type = ?", [bio_type]) blob = self.display_item(name, ql) + "\n\n" blob += "<highlight>Upgrades Ofab Weapons for:<end>\n" for row in data: blob += self.text.make_chatcmd( "Ofab %s Mk 1" % row.name, "/tell <myname> ofabweapons %s" % row.name) + "\n" return ChatBlob("%s (QL %d)" % (name, ql), blob) def alien_armor_bio(self, bio_type, ql): min_ql = math.floor(ql * 0.8) if ql <= 240: max_ql = math.floor(ql / 0.8) else: max_ql = 300 cl = math.floor(min_ql * 4.5) pharma = math.floor(ql * 6) nano_prog = math.floor(min_ql * 6) psyco = math.floor(ql * 6) max_psyco = math.floor(max_ql * 6) ts_bio = math.floor(ql * 4.5) if bio_type == "mutated": name = "Mutated Kyr'Ozch Bio-Material" chem = math.floor(ql * 7) chem_msg = "7 * QL" extra_info = "more tradeskill requirements then pristine" elif bio_type == "pristine": name = "Pristine Kyr'Ozch Bio-Material" chem = math.floor(ql * 4.5) chem_msg = "4.5 * QL" extra_info = "less tradeskill requirements then mutated" else: return None blob = self.display_item(name, ql) + "\n\n" blob += "It will take <highlight>%d EE & CL<end> (<highlight>4.5 * QL<end>) to analyze the Bio-Material.\n\n" % ts_bio blob += "Used to build Alien Armor\n\n" blob += "The following tradeskill amounts are required to make <highlight>QL %d<end>\n" % ql blob += "strong/arithmetic/enduring/spiritual/supple/observant armor:\n\n" blob += "Computer Literacy - <highlight>%d<end> (<highlight>4.5 * QL<end>)\n" % cl blob += "Chemistry - <highlight>%d<end> (<highlight>%s<end>) %s\n" % ( chem, chem_msg, extra_info) blob += "Nano Programming - <highlight>%d<end> (<highlight>6 * QL<end>)\n" % nano_prog blob += "Pharma Tech - <highlight>%d<end> (<highlight>6 * QL<end>)\n" % pharma blob += "Psychology - <highlight>%d<end> (<highlight>6 * QL<end>)\n\n" % psyco blob += "Note: Tradeskill requirements are based off the lowest QL items needed throughout the entire process." blob += "\n\nFor Supple, Arithmetic, or Enduring:\n\n" blob += "When completed, the armor piece can have as low as <highlight>QL %d<end> combined into it, depending on available tradeskill options.\n\n" % min_ql blob += "Does not change QL's, therefore takes <highlight>%d Psychology<end> for available combinations.<end>\n\n" % psyco blob += "For Spiritual, Strong, or Observant:\n\n" blob += "When completed, the armor piece can combine up to <highlight>QL %d<end>, depending on available tradeskill options.\n\n" % max_ql blob += "Changes QL depending on targets QL. " blob += "The max combination is: (<highlight>QL %d<end>) (<highlight>%d Psychology<end> required for this combination)" % ( max_ql, max_psyco) blob += "\n\n<yellow>Tradeskilling info added by Mdkdoc420 (RK2)<end>" return ChatBlob("%s (QL %d)" % (name, ql), blob) def alien_weapon_bio(self, bio_type, ql): name = "Kyr'Ozch Bio-Material - Type %s" % bio_type # Ensures that the maximum AI weapon that combines into doesn't go over QL 300 when the user presents a QL 271+ bio-material max_ai_type = math.floor(ql / 0.9) if max_ai_type > 300 or max_ai_type < 1: max_ai_type = 300 ts_bio = math.floor(ql * 4.5) blob = self.display_item(name, ql) + "\n\n" blob += "It will take <highlight>%d<end> EE & CL (<highlight>4.5 * QL<end>) to analyze the Bio-Material.\n\n" % ts_bio row = self.db.query_single( "SELECT specials FROM alien_weapon_specials WHERE type = ?", [bio_type]) blob += "<highlight>Adds %s to:<end>\n" % row.specials data = self.db.query("SELECT * FROM alien_weapons WHERE type = ?", [bio_type]) for row in data: blob += self.display_item(row.name, max_ai_type) + "\n" blob += self.get_weapon_info(max_ai_type) blob += "\n\n<yellow>Tradeskilling info added by Mdkdoc420<end>" return ChatBlob("%s (QL %d)" % (name, ql), blob) def serum_bio(self, ql): name = "Kyr'Ozch Viral Serum" item = self.display_item(name, ql) pharma_ts = math.floor(ql * 3.5) chem_me_ts = math.floor(ql * 4) ee_ts = math.floor(ql * 4.5) cl_ts = math.floor(ql * 5) ts_bio = math.floor(ql * 4.5) if pharma_ts < 400: pharma_ts = 400 if chem_me_ts < 400: chem_me_ts = 400 blob = item + "\n\n" blob += "It will take <highlight>%d EE & CL<end> (<highlight>4.5 * QL<end>) to analyze the Bio-Material.\n\n" % ts_bio blob += "Used to build city buildings\n\n" blob += "The following are the required skills throughout the process of making a building:\n\n" blob += "Quantum FT - <highlight>400<end> (<highlight>Static<end>)\n" blob += "Pharma Tech - <highlight>%d<end> (<highlight>3.5 * QL<end>) 400 is minimum requirement\n" % pharma_ts blob += "Chemistry - <highlight>%d<end> (<highlight>4 * QL<end>) 400 is minimum requirement\n" % chem_me_ts blob += "Mechanical Engineering - <highlight>%d<end> (<highlight>4 * QL<end>)\n" % chem_me_ts blob += "Electrical Engineering - <highlight>%d<end> (<highlight>4.5 * QL<end>)\n" % ee_ts blob += "Comp Liter - <highlight>%d<end> (<highlight>5 * QL<end>)" % cl_ts blob += "\n\n<yellow>Tradeskilling info added by Mdkdoc420 (RK2)<end>" return ChatBlob("%s (QL %d)" % (name, ql), blob) def get_weapon_info(self, ql): ts_wep = math.floor(ql * 6) text = "\n\n<highlight>QL %d<end> is the highest weapon this type will combine into." % ql if ql != 300: text += "\nNote: <highlight>The weapon can bump several QL's.<end>" text += "\n\nIt will take <highlight>%d<end> ME & WS (<highlight>6 * QL<end>) to combine with a <highlight>QL %d<end> Kyr'ozch Weapon." % ( ts_wep, ql) return text def display_item(self, name, ql): return self.text.format_item( self.items_controller.find_by_name(name, ql), ql)
class AlienBioController: def __init__(self): self.ofab_armor_types = ["64", "295", "468", "935"] self.ofab_weapon_types = ["18", "34", "687", "812"] self.alien_armor_types = ["mutated", "pristine"] self.alien_weapon_types = [ "1", "2", "3", "4", "5", "12", "13", "48", "76", "112", "240", "880", "992" ] def inject(self, registry): self.db = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.util = registry.get_instance("util") self.items_controller = registry.get_instance("items_controller") self.command_alias_service = registry.get_instance( "command_alias_service") self.ts: TranslationService = registry.get_instance( "translation_service") self.getresp = self.ts.get_response def pre_start(self): self.db.load_sql_file(self.module_dir + "/" + "alien_weapons.sql") def start(self): self.command_alias_service.add_alias("clump", "bio") @command(command="bio", params=[Item("bio_material")], access_level="all", description="Show info about Kyr'Ozch Bio-Material") def bio_command(self, request, item): high_id = item.high_id ql = item.ql if high_id == 247707 or high_id == 247708: bio_type = "1" elif high_id == 247709 or high_id == 247710: bio_type = "2" elif high_id == 247717 or high_id == 247718: bio_type = "3" elif high_id == 247711 or high_id == 247712: bio_type = "4" elif high_id == 247713 or high_id == 247714: bio_type = "5" elif high_id == 247715 or high_id == 247716: bio_type = "12" elif high_id == 247719 or high_id == 247720: bio_type = "13" elif high_id == 288699 or high_id == 288700: bio_type = "48" elif high_id == 247697 or high_id == 247698: bio_type = "76" elif high_id == 247699 or high_id == 247700: bio_type = "112" elif high_id == 247701 or high_id == 247702: bio_type = "240" elif high_id == 247703 or high_id == 247704: bio_type = "880" elif high_id == 247705 or high_id == 247706: bio_type = "992" elif high_id == 247102 or high_id == 247103: bio_type = "pristine" elif high_id == 247104 or high_id == 247105: bio_type = "mutated" elif high_id == 247764 or high_id == 254804: bio_type = "serum" else: bio_type = "unknown" bio_info = self.get_bio_info(bio_type, ql) if bio_info: return bio_info else: return "Bio-Material type unknown or not a bio-material." @command(command="bioinfo", params=[], access_level="all", description="Show list of Kyr'Ozch Bio-Material types") def bioinfo_list_command(self, request): return ChatBlob( self.getresp("module/alien", "bioinfo_list_title"), self.getresp( "module/alien", "bioinfo_list", { "ofab_armor": self.get_type_blob(self.ofab_armor_types), "ofab_weap": self.get_type_blob(self.ofab_weapon_types), "ai_armor": self.get_type_blob(self.alien_armor_types), "ai_weap": self.get_type_blob(self.alien_weapon_types), "serum": self.get_type_blob(["serum"]), })) def get_type_blob(self, bio_types): blob = "" for bio_type in bio_types: blob += self.text.make_tellcmd(bio_type, "bioinfo %s" % bio_type) + "\n" return blob @command(command="bioinfo", params=[Any("bio_type"), Int("ql", is_optional=True)], access_level="all", description="Show info about a bio-material type") def bioinfo_show_command(self, request, bio_type, ql): ql = ql or 300 bio_info = self.get_bio_info(bio_type, ql) if bio_info: return bio_info else: return self.getresp("module/alien", "bioinfo_unknown_type", {"type": bio_type}) def get_bio_info(self, bio_type, ql): if bio_type in self.ofab_armor_types: return self.ofab_armor_bio(bio_type, ql) elif bio_type in self.ofab_weapon_types: return self.ofab_weapon_bio(bio_type, ql) elif bio_type in self.alien_armor_types: return self.alien_armor_bio(bio_type, ql) elif bio_type in self.alien_weapon_types: return self.alien_weapon_bio(bio_type, ql) elif bio_type == "serum": return self.serum_bio(ql) else: return None def ofab_armor_bio(self, bio_type, ql): name = "Kyr'Ozch Bio-Material - Type %s" % bio_type data = self.db.query("SELECT * FROM ofab_armor_type WHERE type = ?", [bio_type]) item = self.items_controller.find_by_name(name, ql) upgrades = "" for row in data: upgrades += self.text.make_tellcmd( row.profession, "ofabarmor %s" % row.profession) + "\n" return ChatBlob( self.getresp("module/alien", "bioinfo_unknown_type", { "type": bio_type, "ql": ql }), self.getresp( "module/alien", "ofab_armor_bio", { "type": bio_type, **self.text.generate_item(item, ql), "upgrades": upgrades })) def ofab_weapon_bio(self, bio_type, ql): name = "Kyr'Ozch Bio-Material - Type %s" % bio_type data = self.db.query("SELECT * FROM ofab_weapons WHERE type = ?", [bio_type]) blob = self.display_item(name, ql) + "\n\n" blob += "<highlight>Upgrades Ofab Weapons for:</highlight>\n" for row in data: blob += self.text.make_tellcmd("Ofab %s Mk 1" % row.name, "ofabweapons %s" % row.name) + "\n" return ChatBlob("%s (QL %d)" % (name, ql), blob) def alien_armor_bio(self, bio_type, ql): min_ql = math.floor(ql * 0.8) if ql <= 240: max_ql = math.floor(ql / 0.8) else: max_ql = 300 cl = math.floor(min_ql * 4.5) pharma = math.floor(ql * 6) nano_prog = math.floor(min_ql * 6) psyco = math.floor(ql * 6) max_psyco = math.floor(max_ql * 6) ts_bio = math.floor(ql * 4.5) if bio_type == "mutated": name = "Mutated Kyr'Ozch Bio-Material" chem = math.floor(ql * 7) chem_msg = "7 * QL" extra_info = self.getresp("module/alien", "alien_armor_bio_extra_info_mutated") elif bio_type == "pristine": name = "Pristine Kyr'Ozch Bio-Material" chem = math.floor(ql * 4.5) chem_msg = "4.5 * QL" extra_info = self.getresp("module/alien", "alien_armor_bio_extra_info_pristine") else: return None return ChatBlob( "%s (QL %d)" % (name, ql), self.getresp( "module/alien", "alien_armor_bio", { "item": self.display_item(name, ql), "ee_cl_req": ts_bio, "ql": ql, "cl_req": cl, "chem_req": chem, "chem_info": chem_msg, "chem_extra_info": extra_info, "nano_prog_req": nano_prog, "pt_req": pharma, "psyco_req": psyco, "min_ql": min_ql, "max_ql": max_ql, "max_psyco": max_psyco })) def alien_weapon_bio(self, bio_type, ql): name = "Kyr'Ozch Bio-Material - Type %s" % bio_type # Ensures that the maximum AI weapon that combines into doesn't go over QL 300 when the user presents a QL 271+ bio-material max_ai_type = math.floor(ql / 0.9) if max_ai_type > 300 or max_ai_type < 1: max_ai_type = 300 specials = self.db.query_single( "SELECT specials FROM alien_weapon_specials WHERE type = ?", [bio_type]).specials data = self.db.query("SELECT * FROM alien_weapons WHERE type = ?", [bio_type]) display_blob = "" for row in data: display_blob += self.display_item(row.name, max_ai_type) + "\n" return ChatBlob( "%s (QL %d)" % (name, ql), self.getresp( "module/alien", "alien_weapon_bio", { "item_display": self.display_item(name, ql), "ee_cl_req": math.floor(ql * 4.5), "specials": specials, "display_blob": display_blob, "weapon_info": self.get_weapon_info(max_ai_type) })) def serum_bio(self, ql): name = "Kyr'Ozch Viral Serum" return ChatBlob( "%s (QL %d)" % (name, ql), self.getresp( "module/alien", "serum_bio", { "item_display": self.display_item(name, ql), "ee_cl_req": math.floor(ql * 4.5), "pt_req": (math.floor(ql * 3.5) if math.floor(ql * 3.5) > 400 else 400), "chem_me_req": (math.floor(ql * 4) if math.floor(ql * 4) > 400 else 400), "cl_req": math.floor(ql * 5) })) def get_weapon_info(self, ql): return self.getresp( "module/alien", "weapon_info", { "ql": ql, "bump": ("" if ql == 300 else self.getresp( "module/alien", "weapon_bump")), "me_ws_req": math.floor(ql * 6) }) def display_item(self, name, ql): return self.text.format_item( self.items_controller.find_by_name(name, ql), ql)
class NanoController: def inject(self, registry): self.db = registry.get_instance("db") self.util = registry.get_instance("util") self.text = registry.get_instance("text") self.command_alias_service = registry.get_instance( "command_alias_service") def start(self): self.command_alias_service.add_alias("nl", "nanolines") self.command_alias_service.add_alias("nanoline", "nanolines") @command(command="nano", params=[Any("search"), NamedParameters(["page"])], access_level="all", description="Search for a nano") def nano_cmd(self, request, search, named_params): page = int(named_params.page or "1") page_size = 30 offset = (page - 1) * page_size sql = "SELECT n1.lowid, n1.lowql, n1.name, n1.location, n1.profession, n3.id AS nanoline_id, n3.name AS nanoline_name " \ "FROM nanos n1 " \ "LEFT JOIN nanos_nanolines_ref n2 ON n1.lowid = n2.lowid " \ "LEFT JOIN nanolines n3 ON n2.nanolines_id = n3.id " \ "WHERE n1.name <EXTENDED_LIKE=0> ? " \ "ORDER BY n1.profession, n3.name, n1.lowql DESC, n1.name ASC" data = self.db.query(sql, [search], extended_like=True) count = len(data) paged_data = data[offset:offset + page_size] blob = "" if count > page_size: if page > 1 and len(paged_data) > 0: blob += " " + self.text.make_chatcmd( "<< Page %d" % (page - 1), self.get_chat_command(search, page - 1)) if offset + page_size < len(data): blob += " Page " + str(page) blob += " " + self.text.make_chatcmd( "Page %d >>" % (page + 1), self.get_chat_command(search, page + 1)) blob += "\n\n" current_nanoline = -1 for row in paged_data: if current_nanoline != row.nanoline_id: if row.nanoline_name: blob += "\n<header2>%s<end> - %s\n" % ( row.profession, self.text.make_chatcmd( row.nanoline_name, "/tell <myname> nanolines %d" % row.nanoline_id)) else: blob += "\n<header2>Unknown/General<end>\n" current_nanoline = row.nanoline_id blob += "%s [%d] %s\n" % (self.text.make_item( row.lowid, row.lowid, row.lowql, row.name), row.lowql, row.location) blob += self.get_footer() return ChatBlob( "Nano Search Results for '%s' (%d - %d of %d)" % (search, offset + 1, min(offset + page_size, count), count), blob) @command(command="nanoloc", params=[], access_level="all", description="Show all nano locations") def nanoloc_list_cmd(self, request): data = self.db.query( "SELECT location, COUNT(location) AS cnt FROM nanos GROUP BY location ORDER BY location ASC" ) blob = "" for row in data: blob += "%s (%d)\n" % (self.text.make_chatcmd( row.location, "/tell <myname> nanoloc %s" % row.location), row.cnt) blob += self.get_footer() return ChatBlob("Nano Locations", blob) @command(command="nanoloc", params=[Any("location")], access_level="all", description="Show nanos by location") def nanoloc_show_cmd(self, request, location): sql = "SELECT n1.lowid, n1.lowql, n1.name, n1.location, n3.profession " \ "FROM nanos n1 LEFT JOIN nanos_nanolines_ref n2 ON n1.lowid = n2.lowid LEFT JOIN nanolines n3 ON n2.nanolines_id = n3.id " \ "WHERE n1.location LIKE ? " \ "ORDER BY n1.profession ASC, n1.name ASC" data = self.db.query(sql, [location]) cnt = len(data) blob = "" for row in data: blob += "%s [%d] %s" % (self.text.make_item( row.lowid, row.lowid, row.lowql, row.name), row.lowql, row.location) if row.profession: blob += " - <highight>%s<end>" % row.profession blob += "\n" return ChatBlob("Nanos for Location '%s' (%d)" % (location, cnt), blob) @command(command="nanolines", params=[], access_level="all", description="Show nanos by nanoline") def nanolines_list_cmd(self, request): data = self.db.query( "SELECT DISTINCT profession FROM nanolines ORDER BY profession ASC" ) blob = "" for row in data: blob += self.text.make_chatcmd( row.profession, "/tell <myname> nanolines %s" % row.profession) + "\n" blob += self.get_footer() return ChatBlob("Nanolines", blob) @command(command="nanolines", params=[Int("nanoline_id")], access_level="all", description="Show nanos by nanoline id") def nanolines_id_cmd(self, request, nanoline_id): nanoline = self.db.query_single("SELECT * FROM nanolines WHERE id = ?", [nanoline_id]) if not nanoline: return "Could not find nanoline with ID <highlight>%d<end>." % nanoline_id data = self.db.query( "SELECT n1.lowid, n1.lowql, n1.name, n1.location " "FROM nanos n1 JOIN nanos_nanolines_ref n2 ON n1.lowid = n2.lowid " "WHERE n2.nanolines_id = ? " "ORDER BY n1.lowql DESC, n1.name ASC", [nanoline_id]) blob = "" for row in data: blob += "%s [%d] %s\n" % (self.text.make_item( row.lowid, row.lowid, row.lowql, row.name), row.lowql, row.location) blob += self.get_footer() return ChatBlob("%s %s Nanos" % (nanoline.profession, nanoline.name), blob) @command(command="nanolines", params=[Any("profession")], access_level="all", description="Show nanolines by profession") def nanolines_profession_cmd(self, request, prof_name): profession = self.util.get_profession(prof_name) if not profession: return "Could not find profession <highlight>%s<end>." % prof_name data = self.db.query( "SELECT * FROM nanolines WHERE profession = ? ORDER BY name ASC", [profession]) blob = "" for row in data: blob += self.text.make_chatcmd( row.name, "/tell <myname> nanolines %d" % row.id) + "\n" blob += self.get_footer() return ChatBlob("%s Nanolines" % profession, blob) def get_footer(self): return "\n\nNanos DB provided by Saavick & Lucier" def get_chat_command(self, search, page): return "/tell <myname> nano %s --page=%d" % (search, page)
class OfabArmorController: def inject(self, registry): self.db = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.util = registry.get_instance("util") @command(command="ofabarmor", params=[], access_level="all", description="Show ofab armor") def ofabarmor_list_command(self, request): data = self.db.query( "SELECT type, profession FROM ofab_armor_type ORDER BY profession ASC" ) blob = "" for row in data: blob += "<pagebreak>%s - Type %d\n" % (self.text.make_chatcmd( row.profession, "/tell <myname> ofabarmor %s" % row.profession), row.type) return ChatBlob("Ofab Armor", blob) @command(command="ofabarmor", params=[ Int("ql", is_optional=True), Any("profession"), Int("ql", is_optional=True) ], access_level="all", description="Show info about ofab armor", extended_description= "QL is optional and can come before or after the profession") def ofabarmor_show_command(self, request, ql1, prof_name, ql2): profession = self.util.get_profession(prof_name) ql = ql1 or ql2 or 300 if not profession: return "Could not find Ofab Armor for profession <highlight>%s<end>." % prof_name data = self.db.query( "SELECT * FROM ofab_armor o1 LEFT JOIN ofab_armor_cost o2 ON o1.slot = o2.slot WHERE o1.profession = ? AND o2.ql = ? ORDER BY upgrade ASC, name ASC", [profession, ql]) if not data: return "Could not find Ofab Armor for QL <highlight>%d<end>." % ql upgrade_type = self.db.query_single( "SELECT type FROM ofab_armor_type WHERE profession = ?", [profession]).type type_ql = round(ql * 0.8) type_link = self.text.make_chatcmd( "Kyr'Ozch Bio-Material - Type %d" % upgrade_type, "/tell <myname> bioinfo %d %d" % (upgrade_type, type_ql)) blob = "Upgrade with %s (minimum QL %d)\n\n" % (type_link, type_ql) cost_data = self.db.query( "SELECT DISTINCT ql FROM ofab_weapons_cost ORDER BY ql ASC") for row in cost_data: blob += self.text.make_chatcmd( row.ql, "/tell <myname> ofabarmor %s %d" % (profession, row.ql)) + " " blob += "\n\n" current_upgrade = "" total_vp = 0 for row in data: if current_upgrade != row.upgrade: current_upgrade = row.upgrade blob += "\n" blob += "<pagebreak>" + self.text.make_item( row.lowid, row.highid, ql, row.name) if row.upgrade == 0 or row.upgrade == 3: blob += " (<highlight>%d<end> VP)" % row.vp total_vp += row.vp blob += "\n" blob += "\nVP cost for full set: <highlight>%d<end>" % total_vp return ChatBlob("%s Ofab Armor (QL %d)" % (profession, ql), blob)
class TowerController: def __init__(self): self.logger = Logger(__name__) 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.util = registry.get_instance("util") self.command_alias_service = registry.get_instance( "command_alias_service") self.setting_service = registry.get_instance("setting_service") self.playfield_controller: PlayfieldController = registry.get_instance( "playfield_controller") self.level_controller = registry.get_instance("level_controller") def pre_start(self): self.db.load_sql_file(self.module_dir + "/" + "tower_site.sql") self.db.load_sql_file(self.module_dir + "/" + "tower_site_bounds.sql") def start(self): self.command_alias_service.add_alias("hot", "lc open") self.setting_service.register( self.module_name, "tower_api_address", "https://tower-api.jkbff.com/v1/api/towers", TextSettingType(["https://tower-api.jkbff.com/v1/api/towers"]), "The address of the Tower API") self.setting_service.register(self.module_name, "tower_api_custom_headers", "", DictionarySettingType(), "Custom headers for the Tower API") @command(command="lc", params=[], access_level="guest", description= "See a list of playfields containing land control tower sites") def lc_list_cmd(self, request): data = self.db.query( "SELECT id, long_name, short_name FROM playfields WHERE id IN (SELECT DISTINCT playfield_id FROM tower_site) ORDER BY short_name" ) blob = "" for row in data: blob += "[%d] %s <highlight>%s</highlight>\n" % ( row.id, self.text.make_tellcmd( row.long_name, "lc %s" % row.short_name), row.short_name) blob += "\n" + self.get_lc_blob_footer() return ChatBlob("Land Control Playfields (%d)" % len(data), blob) if FeatureFlags.USE_TOWER_API: @command(command="lc", params=[Const("org"), Any("org", is_optional=True)], access_level="guest", description="See a list of land control tower sites by org") def lc_org_cmd(self, request, _, org): params = list() params.append(("enabled", "true")) if not org: org = str(request.conn.org_id) if not org: return "Bot does not belong to an org so an org name or org id must be specified." if org.isdigit(): params.append(("org_id", org)) else: for org_name_piece in org.split(" "): params.append(("org_name", "%" + org_name_piece + "%")) data = self.lookup_tower_info(params).results t = int(time.time()) grouped_data = self.util.group_by(data, lambda x: (x.org_id, x.org_name)) blob = "" for k, v in grouped_data.items(): v = sorted(v, key=lambda x: x.ql) org_blob = "" ct_types = [] ql_total = 0 for ct in v: ct_types.append(self.get_ct_type(ct.ql)) ql_total += ct.ql org_blob += self.format_site_info(ct, t) + "\n" blob += f"<pagebreak><header2>{k[1]} ({k[0]})</header2>" blob += " Types: <highlight>" + ", ".join( ct_types ) + f"</highlight> Total CT QL: <highlight>{ql_total}</highlight>\n\n" blob += org_blob + "\n" return ChatBlob(f"Org Info for '{org}' ({len(data)})", blob) @command( command="lc", params=[ Options([ "all", "open", "closed", "penalty", "unplanted", "disabled" ]), Options(["omni", "clan", "neutral", "all"], is_optional=True), Int("pvp_level", is_optional=True), Time("time", is_optional=True) ], access_level="guest", description= "See a list of land control tower sites by QL, faction, and open status", extended_description= "The time param only applies when the first param is either 'open' or 'closed'" ) def lc_search_cmd(self, request, site_status, faction, pvp_level, time_offset): t = int(time.time()) relative_time = t + (time_offset or 0) min_ql = 1 max_ql = 300 if pvp_level: level_info = self.level_controller.get_level_info(pvp_level) if not level_info: return "PVP level must be between 1 and 220." min_ql = level_info.pvp_min max_ql = level_info.pvp_max params = list() if site_status.lower() == "disabled": params.append(("enabled", "false")) else: params.append(("enabled", "true")) if min_ql > 1: params.append(("min_ql", min_ql)) if max_ql < 300: params.append(("max_ql", max_ql)) if faction and faction != "all": params.append(("faction", faction)) if site_status.lower() == "open": params.append(("min_close_time", relative_time)) params.append(("max_close_time", relative_time + (3600 * 6))) elif site_status.lower() == "closed": params.append(("min_close_time", relative_time + (3600 * 6))) params.append(("max_close_time", relative_time + (3600 * 24))) elif site_status.lower() == "penalty": params.append(("penalty", "true")) elif site_status.lower() == "unplanted": params.append(("planted", "false")) data = self.lookup_tower_info(params).results blob = "" for row in data: blob += "<pagebreak>" + self.format_site_info(row, t) + "\n" if blob: blob += self.get_lc_blob_footer() title = "Tower Info: %s" % site_status.capitalize() if min_ql > 1 or max_ql < 300: title += " QL %d - %d" % (min_ql, max_ql) if faction: title += " [%s]" % faction.capitalize() if time_offset: title += " in " + self.util.time_to_readable(time_offset) title += " (%d)" % len(data) return ChatBlob(title, blob) @command(command="lc", params=[Any("playfield"), Int("site_number", is_optional=True)], access_level="guest", description= "See a list of land control tower sites in a particular playfield" ) def lc_playfield_cmd(self, request, playfield_name, site_number): playfield = self.playfield_controller.get_playfield_by_name_or_id( playfield_name) if not playfield: return f"Could not find playfield <highlight>{playfield_name}</highlight>." data = self.get_tower_site_info(playfield.id, site_number) blob = "" t = int(time.time()) for row in data: blob += "<pagebreak>" + self.format_site_info(row, t) + "\n" blob += self.get_lc_blob_footer() if site_number: title = "Tower Info: %s %d" % (playfield.long_name, site_number) else: title = "Tower Info: %s (%d)" % (playfield.long_name, len(data)) return ChatBlob(title, blob) def format_site_info(self, row, t): blob = "<highlight>%s %d</highlight> (QL %d-%d) %s\n" % ( row.playfield_short_name, row.site_number, row.min_ql, row.max_ql, row.site_name) if row.get("org_name"): current_day_time = t % 86400 value = datetime.fromtimestamp(row.close_time, tz=pytz.UTC) current_status_time = row.close_time - current_day_time if current_status_time < 0: current_status_time += 86400 status = "" if current_status_time <= 3600: status += "<red>5%%</red> (closes in %s)" % self.util.time_to_readable( current_status_time) elif current_status_time <= (3600 * 6): status += "<orange>25%%</orange> (closes in %s)" % self.util.time_to_readable( current_status_time) else: status += "<green>75%%</green> (opens in %s)" % self.util.time_to_readable( current_status_time - (3600 * 6)) if row.penalty_until > t: status += " <red>Penalty</red> (for %s)" % self.util.time_to_readable( row.penalty_until - t) blob += "%s (%d) [%s] <highlight>QL %d</highlight> %s %s\n" % ( row.org_name, row.org_id, self.text.get_formatted_faction(row.faction), row.ql, self.text.make_chatcmd( "%dx%d" % (row.x_coord, row.y_coord), "/waypoint %d %d %d" % (row.x_coord, row.y_coord, row.playfield_id)), self.util.time_to_readable(t - row.created_at)) blob += "Close Time: <highlight>%s</highlight> %s\n" % ( value.strftime("%H:%M:%S %Z"), status) else: blob += "%s\n" % self.text.make_chatcmd( "%dx%d" % (row.x_coord, row.y_coord), "/waypoint %d %d %d" % (row.x_coord, row.y_coord, row.playfield_id)) if not row.enabled: blob += "<red>Disabled</red>\n" return blob def get_tower_site_info(self, playfield_id, site_number): if FeatureFlags.USE_TOWER_API: params = list() params.append(("playfield_id", playfield_id)) if site_number: params.append(("site_number", site_number)) data = self.lookup_tower_info(params).results else: if site_number: data = self.db.query( "SELECT t.*, p.short_name AS playfield_short_name, p.long_name AS playfield_long_name " "FROM tower_site t JOIN playfields p ON t.playfield_id = p.id WHERE t.playfield_id = ? AND site_number = ?", [playfield_id, site_number]) else: data = self.db.query( "SELECT t.*, p.short_name AS playfield_short_name, p.long_name AS playfield_long_name " "FROM tower_site t JOIN playfields p ON t.playfield_id = p.id WHERE t.playfield_id = ?", [playfield_id]) return data def lookup_tower_info(self, params): url = self.setting_service.get("tower_api_address").get_value() headers = self.setting_service.get( "tower_api_custom_headers").get_value() or {} headers.update({"User-Agent": f"Tyrbot {self.bot.version}"}) r = requests.get(url, params, headers=headers, timeout=5) result = DictObject(r.json()) return result def get_lc_blob_footer(self): return "Thanks to Draex and Unk for providing the tower information. And a special thanks to Trey." def get_ct_type(self, ql): if ql < 34: return "I" elif ql < 82: return "II" elif ql < 129: return "III" elif ql < 177: return "IV" elif ql < 201: return "V" elif ql < 226: return "VI" else: return "VII"
class ImplantController: def __init__(self): self.grades = ["shiny", "bright", "faded"] self.normal_ability_req = {1: 6, 200: 404, 201: 426, 300: 1095} self.normal_treatment_req = {1: 11, 200: 951, 201: 1001, 300: 2051} self.jobe_ability_req = {1: 6, 200: 464, 201: 476, 300: 1231} self.jobe_treatment_req = {1: 11, 200: 1005, 201: 1001, 300: 2051} self.ability_shiny_bonus = {1: 5, 200: 55, 201: 55, 300: 73} self.ability_bright_bonus = {1: 3, 200: 33, 201: 33, 300: 44} self.ability_faded_bonus = {1: 2, 200: 22, 201: 22, 300: 29} self.skill_shiny_bonus = {1: 6, 200: 105, 201: 106, 300: 141} self.skill_bright_bonus = {1: 3, 200: 63, 201: 63, 300: 85} self.skill_faded_bonus = {1: 2, 200: 42, 201: 42, 300: 57} self.normal_build_shiny = {1: 4, 200: 800, 201: 994, 300: 1575} self.normal_build_bright = {1: 3, 200: 600, 201: 753, 300: 1125} self.normal_build_faded = {1: 2, 200: 400, 201: 552, 300: 825} self.jobe_build_shiny = {1: 6, 200: 1250, 201: 1356, 300: 2025} self.jobe_build_bright = {1: 4, 200: 950, 201: 1055, 300: 1575} self.jobe_build_faded = {1: 3, 200: 650, 201: 753, 300: 1125} self.clean_np = {1: 1, 200: 200} self.clean_be = {1: 4, 200: 950} def inject(self, registry): self.db = registry.get_instance("db") self.util = registry.get_instance("util") self.text = registry.get_instance("text") self.command_alias_service = registry.get_instance( "command_alias_service") def pre_start(self): self.db.load_sql_file(self.module_dir + "/sql/" + "Ability.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "Cluster.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "ClusterImplantMap.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "ClusterType.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "EffectTypeMatrix.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "EffectValue.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "ImplantMatrix.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "ImplantType.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "Profession.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "Symbiant.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "SymbiantAbilityMatrix.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "SymbiantClusterMatrix.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "SymbiantProfessionMatrix.sql") self.db.load_sql_file(self.module_dir + "/sql/" + "implant_requirements.sql") def start(self): self.command_alias_service.add_alias("implants", "implant") @command(command="implant", params=[Int("ql")], access_level="all", description="Shows information about an implant at given QL") def implant_cmd(self, request, ql): if ql > 300 or ql < 1: return "Implant QL must be between <highlight>1</highlight> and <highlight>300</highlight>." implant = self.get_implant_by_ql(ql) blob = self.format_implant(implant) return ChatBlob( f"Implant QL {implant.ql} ({implant.ability} Ability, {implant.treatment} Treatment)", blob) @command( command="implant", params=[Int("ability"), Int("treatment")], access_level="all", description="Shows highest QL implant for a given ability and treatment" ) def implant_requirement_cmd(self, request, ability, treatment): implant = self.get_implant_by_requirements(ability, treatment) if not implant: return "You do not have enough ability or treatment to wear an implant." blob = self.format_implant(implant) return ChatBlob( f"Implant QL {implant.ql} ({implant.ability} Ability, {implant.treatment} Treatment)", blob) def get_implant_by_requirements(self, ability, treatment): row = self.db.query_single( "SELECT ql FROM implant_requirement WHERE ability <= ? AND treatment <= ? ORDER BY ql DESC LIMIT 1", [ability, treatment]) if row: return self.get_implant_by_ql(row.ql) else: return None def format_implant(self, implant): blob = "<header2>Requirements to Wear</header2>\n" blob += "<highlight>%d</highlight> Treatment\n" % implant.treatment blob += "<highlight>%d</highlight> Ability\n" % implant.ability blob += "\n<header2>Ability Cluster Bonuses</header2>\n" blob += "<highlight>%d</highlight> Shiny (%d - %d)\n" % ( implant.ability_shiny, implant.ability_shiny_min, implant.ability_shiny_max) blob += "<highlight>%d</highlight> Bright (%d - %d)\n" % ( implant.ability_bright, implant.ability_bright_min, implant.ability_bright_max) blob += "<highlight>%d</highlight> Faded (%d - %d)\n" % ( implant.ability_faded, implant.ability_faded_min, implant.ability_faded_max) blob += "\n<header2>Skill Cluster Bonuses</header2>\n" blob += "<highlight>%d</highlight> Shiny (%d - %d)\n" % ( implant.skill_shiny, implant.skill_shiny_min, implant.skill_shiny_max) blob += "<highlight>%d</highlight> Bright (%d - %d)\n" % ( implant.skill_bright, implant.skill_bright_min, implant.skill_bright_max) blob += "<highlight>%d</highlight> Faded (%d - %d)\n" % ( implant.skill_faded, implant.skill_faded_min, implant.skill_faded_max) if implant.ql <= 200: blob += "\n<header2>Requirements to Clean</header2>\n" blob += "<highlight>%d</highlight> Break&Entry\n" % implant.clean_break_and_entry blob += "<highlight>%d</highlight> NanoProgramming\n" % implant.clean_nano_programming else: blob += "\nRefined implants cannot be cleaned\n" blob += "\n<header2>Max Requirements to Build</header2> (actual requirements may be lower)\n" blob += "<highlight>%d</highlight> NanoProgramming for Shiny\n" % implant.build_shiny blob += "<highlight>%d</highlight> NanoProgramming for Bright\n" % implant.build_bright blob += "<highlight>%d</highlight> NanoProgramming for Faded\n" % implant.build_faded blob += "\n<header2>Min Cluster QL</header2>\n" blob += "<highlight>%d</highlight> Shiny\n" % implant.minimum_cluster_shiny blob += "<highlight>%d</highlight> Bright\n" % implant.minimum_cluster_bright blob += "<highlight>%d</highlight> Faded\n" % implant.minimum_cluster_faded if implant.ql >= 99: blob += "\n<header2>Requirements to Wear Jobe Implants</header2>\n" blob += "<highlight>%d</highlight> Treatment\n" % implant.jobe_treatment blob += "<highlight>%d</highlight> Ability\n" % implant.jobe_ability blob += "\n<header2>Max Requirements to Build Jobe Implants</header2> (actual requirements may be lower)\n" blob += "<highlight>%d</highlight> NanoProgramming for Shiny\n" % implant.jobe_build_shiny blob += "<highlight>%d</highlight> NanoProgramming for Bright\n" % implant.jobe_build_bright blob += "<highlight>%d</highlight> NanoProgramming for Faded\n" % implant.jobe_build_faded blob += "\nJobe implants cannot be cleaned\n" blob += "\n\nBased on the !impql command written for %s by <highlight>Lucier</highlight>" % self.text.make_chatcmd( "Ttst", "/tell ttst help") return blob def get_implant_by_ql(self, ql): implant = DictObject({}) implant.ql = ql implant.treatment = int( self.util.interpolate_value(ql, self.normal_treatment_req)) implant.ability = int( self.util.interpolate_value(ql, self.normal_ability_req)) implant.jobe_treatment = self.util.interpolate_value( ql, self.jobe_treatment_req) implant.jobe_ability = self.util.interpolate_value( ql, self.jobe_ability_req) implant.ability_shiny = self.util.interpolate_value( ql, self.ability_shiny_bonus) implant.ability_shiny_min, implant.ability_shiny_max = self.get_range( ql, implant.ability_shiny, self.ability_shiny_bonus) implant.ability_bright = self.util.interpolate_value( ql, self.ability_bright_bonus) implant.ability_bright_min, implant.ability_bright_max = self.get_range( ql, implant.ability_bright, self.ability_bright_bonus) implant.ability_faded = self.util.interpolate_value( ql, self.ability_faded_bonus) implant.ability_faded_min, implant.ability_faded_max = self.get_range( ql, implant.ability_faded, self.ability_faded_bonus) implant.skill_shiny = self.util.interpolate_value( ql, self.skill_shiny_bonus) implant.skill_shiny_min, implant.skill_shiny_max = self.get_range( ql, implant.skill_shiny, self.skill_shiny_bonus) implant.skill_bright = self.util.interpolate_value( ql, self.skill_bright_bonus) implant.skill_bright_min, implant.skill_bright_max = self.get_range( ql, implant.skill_bright, self.skill_bright_bonus) implant.skill_faded = self.util.interpolate_value( ql, self.skill_faded_bonus) implant.skill_faded_min, implant.skill_faded_max = self.get_range( ql, implant.skill_faded, self.skill_faded_bonus) implant.clean_break_and_entry = self.util.interpolate_value( ql, self.clean_be) implant.clean_nano_programming = self.util.interpolate_value( ql, self.clean_np) implant.build_shiny = self.util.interpolate_value( ql, self.normal_build_shiny) implant.build_bright = self.util.interpolate_value( ql, self.normal_build_bright) implant.build_faded = self.util.interpolate_value( ql, self.normal_build_faded) implant.jobe_build_shiny = self.util.interpolate_value( ql, self.jobe_build_shiny) implant.jobe_build_bright = self.util.interpolate_value( ql, self.jobe_build_bright) implant.jobe_build_faded = self.util.interpolate_value( ql, self.jobe_build_faded) if ql >= 201: implant.minimum_cluster_shiny = max(201, math.floor(ql * 0.86)) implant.minimum_cluster_bright = max(201, math.floor(ql * 0.84)) implant.minimum_cluster_faded = max(201, math.floor(ql * 0.82)) else: implant.minimum_cluster_shiny = math.floor(ql * 0.86) implant.minimum_cluster_bright = math.floor(ql * 0.84) implant.minimum_cluster_faded = math.floor(ql * 0.82) return implant def get_range(self, ql, value, interpolation): min_ql = ql max_ql = ql while self.util.interpolate_value(min_ql - 1, interpolation) == value: min_ql -= 1 while self.util.interpolate_value(max_ql + 1, interpolation) == value: max_ql += 1 return [min_ql, max_ql]
class CloakController: MESSAGE_SOURCE = "cloak_reminder" CLOAK_EVENT = "cloak" CLOAK_STATUS_OFF = "off" CLOAK_STATUS_ON = "on" CLOAK_STATUS_MANUAL = "on*" def inject(self, registry): self.bot = registry.get_instance("bot") self.db = registry.get_instance("db") self.util = registry.get_instance("util") self.text = registry.get_instance("text") self.character_service = registry.get_instance("character_service") self.command_alias_service = registry.get_instance("command_alias_service") self.timer_controller = registry.get_instance("timer_controller", is_optional=True) self.event_service = registry.get_instance("event_service") self.access_service = registry.get_instance("access_service") self.message_hub_service = registry.get_instance("message_hub_service") def pre_start(self): self.bot.register_packet_handler(PublicChannelMessage.id, self.handle_public_message) self.event_service.register_event_type(self.CLOAK_EVENT) self.message_hub_service.register_message_source(self.MESSAGE_SOURCE) def start(self): self.db.exec("CREATE TABLE IF NOT EXISTS cloak_status (char_id INT NOT NULL, action VARCHAR(10) NOT NULL, created_at INT NOT NULL, org_id INT NOT NULL)") self.command_alias_service.add_alias("city", "cloak") @command(command="cloak", params=[Const("history"), Int("org_id")], access_level="org_member", description="Shows the cloak history") def cloak_history_command(self, request, _, org_id): data = self.db.query("SELECT c.*, p.name FROM cloak_status c LEFT JOIN player p ON c.char_id = p.char_id " "WHERE c.org_id = ? ORDER BY created_at DESC LIMIT 20", [org_id]) blob = "" for row in data: action = self.get_cloak_status_display(row.action) blob += "%s turned the device %s at %s.\n" % (row.name, action, self.util.format_datetime(row.created_at)) conn = self.bot.get_conn_by_org_id(org_id) org_name = conn.get_org_name() return ChatBlob(f"Cloak History for {org_name}", blob) @command(command="cloak", params=[NamedFlagParameters(["all"])], access_level="org_member", description="Shows the cloak status") def cloak_command(self, request, flag_params): t = int(time.time()) if flag_params.all: blob = "" for _id, conn in self.bot.get_conns(lambda x: x.is_main and x.org_id): row = self.db.query_single("SELECT c.char_id, c.action, c.created_at, p.name FROM cloak_status c LEFT JOIN player p ON c.char_id = p.char_id " "WHERE c.org_id = ? ORDER BY created_at DESC LIMIT 1", [conn.org_id]) org_name = conn.get_org_name() if row: action = self.get_cloak_status_display(row.action) time_str = self.util.time_to_readable(t - row.created_at) history_link = self.text.make_tellcmd("History", f"cloak history {conn.org_id}") blob += f"{org_name} - {action} [{time_str} ago] {history_link}\n" else: blob += f"{org_name} - Unknown\n" title = "Cloak Status" return ChatBlob(title, blob) else: conn = request.conn if not conn.org_id: return "This bot is not a member of an org." row = self.db.query_single("SELECT c.char_id, c.action, c.created_at, p.name FROM cloak_status c LEFT JOIN player p ON c.char_id = p.char_id " "WHERE c.org_id = ? ORDER BY created_at DESC LIMIT 1", [conn.org_id]) org_name = conn.get_org_name() if row: action = self.get_cloak_status_display(row.action) time_str = self.util.time_to_readable(t - row.created_at) return f"{org_name} - {action} [{time_str} ago]" else: return f"{org_name} - Unknown cloak status" @command(command="cloak", params=[Options(["raise", "on"]), Int("org_id", is_optional=True)], access_level="org_member", description="Manually raises the cloak status on the bot") def cloak_raise_command(self, request, _, org_id): org_id = org_id or request.conn.org_id if not org_id: return "This bot is not a member of an org." row = self.db.query_single("SELECT action FROM cloak_status WHERE org_id = ?", [org_id]) if row and (row.action == self.CLOAK_STATUS_ON or row.action == self.CLOAK_STATUS_MANUAL): return "The cloaking device is already <green>enabled</green>." self.db.exec("INSERT INTO cloak_status (char_id, action, created_at, org_id) VALUES (?, ?, ?, ?)", [request.sender.char_id, self.CLOAK_STATUS_MANUAL, int(time.time()), org_id]) return "The cloaking device has been set to enabled in the bot (you must still enable the cloak if it is disabled)." @event(event_type=CLOAK_EVENT, description="Record when the city cloak is turned off and on", is_system=True) def city_cloak_event(self, event_type, event_data): self.db.exec("INSERT INTO cloak_status (char_id, action, created_at, org_id) VALUES (?, ?, ?, ?)", [event_data.char_id, event_data.action, int(time.time()), event_data.conn.org_id]) @timerevent(budatime="15m", description="Reminds the players when cloak can be raised") def cloak_reminder_event(self, event_type, event_data): messages = [] for _id, conn in self.bot.get_conns(lambda x: x.is_main and x.org_id): row = self.db.query_single("SELECT c.*, p.name FROM cloak_status c LEFT JOIN player p ON c.char_id = p.char_id " "WHERE c.org_id = ? ORDER BY created_at DESC LIMIT 1", [conn.org_id]) if row: one_hour = 3600 t = int(time.time()) time_until_change = row.created_at + one_hour - t if row.action == self.CLOAK_STATUS_OFF and 0 >= time_until_change > (one_hour * 6 * -1): time_str = self.util.time_to_readable(t - row.created_at) org_name = conn.get_org_name() messages.append(f"The cloaking device for org <highlight>{org_name}</highlight> is <orange>disabled</orange> but can be enabled. " f"<highlight>{row.name}</highlight> disabled it {time_str} ago.") if messages: self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, None, "\n".join(messages)) @event(event_type=CLOAK_EVENT, description="Set a timer for when cloak can be raised and lowered") def city_cloak_timer_event(self, event_type, event_data): if event_data.action == self.CLOAK_STATUS_OFF: timer_name = f"Raise City Cloak ({event_data.conn.get_org_name()})" elif event_data.action == self.CLOAK_STATUS_ON: timer_name = f"Lower City Cloak ({event_data.conn.get_org_name()})" else: raise Exception(f"Unknown cloak action '{event_data.action}'") timer_name = self.timer_controller.get_timer_name(timer_name) self.timer_controller.add_timer(timer_name, event_data.char_id, PublicChannelService.ORG_CHANNEL_COMMAND, int(time.time()), 3600) def handle_public_message(self, conn: Conn, packet: PublicChannelMessage): if not conn.is_main: return ext_msg = packet.extended_message if ext_msg and ext_msg.category_id == 1001 and ext_msg.instance_id == 1: char_name = ext_msg.params[0] char_id = self.character_service.resolve_char_to_id(char_name) action = ext_msg.params[1] self.event_service.fire_event(self.CLOAK_EVENT, DictObject({"char_id": char_id, "name": char_name, "action": action, "conn": conn})) def get_cloak_status_display(self, status): if status == self.CLOAK_STATUS_ON: return "<green>on</green>" elif status == self.CLOAK_STATUS_MANUAL: return "<green>on*</green>" elif status == self.CLOAK_STATUS_OFF: return "<orange>off</orange>" else: return "<highlight>Unknown</highlight>"
class PointsController: def __init__(self): pass def inject(self, registry): self.bot = registry.get_instance("bot") self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.character_service: CharacterService = registry.get_instance("character_service") self.util: Util = registry.get_instance("util") self.setting_service: SettingService = registry.get_instance("setting_service") self.alts_service: AltsService = registry.get_instance("alts_service") def start(self): self.db.exec("CREATE TABLE IF NOT EXISTS points (char_id BIGINT PRIMARY KEY, points INT DEFAULT 0, created_at INT NOT NULL, " "disabled SMALLINT DEFAULT 0)") self.db.exec("CREATE TABLE IF NOT EXISTS points_log (log_id INT PRIMARY KEY, char_id BIGINT NOT NULL, audit INT NOT NULL, " "leader_id BIGINT NOT NULL, reason VARCHAR(255), created_at INT NOT NULL)") self.db.exec("CREATE TABLE IF NOT EXISTS points_presets (preset_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, " "points INT DEFAULT 1, UNIQUE(name))") if self.db.query_single("SELECT COUNT(*) AS count FROM points_presets").count < 1: # Populate with pre-made presets if empty presets = ["s13", "s28", "s35", "s42", "zodiac", "zod", "tnh", "beast", "12m", "tara", "pvp", "towers", "wipe", "clanwipe", "clan", "omniwipe", "omni", "bonus", "early"] sql = "INSERT INTO points_presets (name) VALUES (?)" for preset in presets: self.db.exec(sql, [preset]) @command(command="account", params=[Const("create"), Character("char")], access_level="moderator", description="Create a new account for given character name", sub_command="modify") def bank_create_cmd(self, request, _, char: SenderObj): alts_info = self.alts_service.get_alts(char.char_id) for alt in alts_info: sql = "SELECT char_id, disabled FROM points WHERE char_id = ? LIMIT 1" row = self.db.query_single(sql, [alt.char_id]) if row: was_disabled = False if row.disabled == 1: if self.db.exec("UPDATE points SET disabled = 0 WHERE char_id = ?", [alt.char_id]): was_disabled = True if alt.char_id == char.char_id: if was_disabled: self.add_log_entry(alt.char_id, request.sender.char_id, "Account was re-enabled by %s" % request.sender.name) return "<highlight>%s</highlight>'s account has been re-enabled." % char.name else: return "<highlight>%s</highlight> already has an account." % char.name else: if was_disabled: self.add_log_entry(alt.char_id, request.sender.char_id, "Account was re-enabled by %s" % request.sender.name) return "<highlight>%s</highlight>'s (%s) account has been re-enabled." % (char.name, alt.name) else: return "<highlight>%s</highlight> (%s) already has an account." % (char.name, alt.name) main_info = alts_info[0] changed_to_main = main_info.char_id == char.char_id self.create_account(main_info.char_id, request.sender) name_reference = "%s (%s)" % (char.name, main_info.name) if changed_to_main else char.name return "A new account has been created for <highlight>%s</highlight>." % name_reference @command(command="account", params=[Const("close"), Character("char")], access_level="moderator", description="Close the account for given character name", sub_command="modify") def close_account_cmd(self, request, _, char: SenderObj): main = self.alts_service.get_main(char.char_id) sql = "UPDATE points SET disabled = 1 WHERE char_id = ?" if self.db.exec(sql, [main.char_id]) > 0: reason = f"Account was closed by {request.sender.name}" self.add_log_entry(main.char_id, request.sender.char_id, reason) name_reference = "%s (%s)" % (char.name, main.name) if main.char_id != char.char_id else char.name return f"<highlight>{name_reference}</highlight> has had their account disabled. Logs have been preserved." else: return "<highlight>%s</highlight> does not have an open account." % char.name @command(command="account", params=[Const("history"), Int("log_id")], access_level="moderator", description="Look up specific account history record", sub_command="modify") def account_history_cmd(self, request, _, log_id: int): log_entry = self.db.query_single("SELECT log_id, char_id, audit, leader_id, reason, created_at FROM points_log WHERE log_id = ?", [log_id]) if not log_entry: return "No account history record with given ID <highlight>%d</highlight>." % log_id char_name = self.character_service.resolve_char_to_name(log_entry.char_id) leader_name = self.character_service.resolve_char_to_name(log_entry.leader_id) blob = f"ID: <highlight>{log_entry.log_id}</highlight>\n" blob += f"Account: <highlight>{char_name}</highlight>\n" blob += f"Action by: <highlight>{leader_name}</highlight>\n" blob += "Type: <highlight>%s</highlight>\n" % ("Management" if log_entry.audit == 0 else "Altering of points") blob += f"Reason: <highlight>{log_entry.reason}</highlight>\n" blob += f"Audit: <highlight>{log_entry.audit}</highlight>\n" action_links = None if log_entry.audit == 0: if "closed" in log_entry.reason: action_links = self.text.make_tellcmd("Open the account", "account create %s" % char_name) elif "re-enabled" in log_entry.reason: action_links = self.text.make_tellcmd("Close the account", "account close %s" % char_name) else: reason = f"Points from event ({log_id}) have been retracted" if log_entry.audit < 0: action_links = self.text.make_tellcmd("Retract", f"account add {char_name} {-log_entry.audit} {reason}") else: action_links = self.text.make_tellcmd("Retract", f"account rem {char_name} {log_entry.audit} {reason}") blob += "Actions available: [%s]\n" % (action_links if action_links is not None else "No actions available") return ChatBlob(f"Account History Record ({log_id})", blob) @command(command="account", params=[Const("add"), Character("char"), Int("amount"), Any("reason")], access_level="moderator", description="Add points to an account", sub_command="modify") def account_add_cmd(self, request, _, char: SenderObj, amount: int, reason: str): main = self.alts_service.get_main(char.char_id) row = self.get_account(main.char_id, request.conn) if not row: return f"<highlight>{char.name}</highlight> does not have an account." if row.disabled == 1: return f"Account for <highlight>{char.name}</highlight> is disabled and cannot be altered." self.alter_points(main.char_id, request.sender.char_id, reason, amount) return f"<highlight>{char.name}</highlight> has had <highlight>{amount}</highlight> points added to their account." @command(command="account", params=[Options(["rem", "remove"]), Character("char"), Int("amount"), Any("reason")], access_level="moderator", description="Remove points from an account", sub_command="modify") def account_remove_cmd(self, request, _, char: SenderObj, amount: int, reason: str): main = self.alts_service.get_main(char.char_id) row = self.get_account(main.char_id, request.conn) if not row: return f"<highlight>{char.name}</highlight> does not have an account." if row.disabled == 1: return f"Account for <highlight>{char.name}</highlight> is disabled and cannot be altered." if amount > row.points: return f"<highlight>{char.name}</highlight> only has <highlight>{row.points}</highlight> points." self.alter_points(main.char_id, request.sender.char_id, reason, -amount) return f"<highlight>{char.name}</highlight> has had <highlight>{amount}</highlight> points removed from their account." @command(command="account", params=[NamedParameters(["page"])], access_level="all", description="Look up your account") def account_self_cmd(self, request, named_params): return self.get_account_display(request.sender, named_params.page) @command(command="account", params=[Character("char"), NamedParameters(["page"])], access_level="moderator", description="Look up account of another char", sub_command="modify") def account_other_cmd(self, request, char: SenderObj, named_params): return self.get_account_display(char, named_params.page) @command(command="raid", params=[Const("presets"), Const("add"), Any("name"), Int("points")], access_level="admin", description="Add new points preset", sub_command="manage_points") def presets_add_cmd(self, request, _1, _2, name: str, points: int): count = self.db.query_single("SELECT COUNT(*) AS count FROM points_presets WHERE name = ?", [name]).count if count > 0: return "A preset already exists with the name <highlight>%s</highlight>." % name sql = "INSERT INTO points_presets (name, points) VALUES (?,?)" self.db.exec(sql, [name, points]) return "A preset with the name <highlight>%s</highlight> was added, worth <green>%d</green> points." % (name, points) @command(command="raid", params=[Const("presets"), Const("rem"), Int("preset_id")], access_level="admin", description="Delete preset", sub_command="manage_points") def presets_rem_cmd(self, request, _1, _2, preset_id: int): if self.db.exec("DELETE FROM points_presets WHERE preset_id = ?", [preset_id]) > 0: return "Successfully removed preset with ID <highlight>%d</highlight>." % preset_id else: return "No preset with given ID <highlight>%d</highlight>." % preset_id @command(command="raid", params=[Const("presets"), Const("alter"), Int("preset_id"), Int("new_points")], access_level="admin", description="Alter the points dished out by given preset", sub_command="manage_points") def presets_alter_cmd(self, request, _1, _2, preset_id: int, new_points: int): preset = self.db.query_single("SELECT * FROM points_presets WHERE preset_id = ?", [preset_id]) if not preset: return f"Preset with ID <highlight>{preset_id}</highlight> does not exist." self.db.exec("UPDATE points_presets SET points = ? WHERE preset_id = ?", [new_points, preset_id]) return "Successfully updated the preset, <highlight>%s</highlight>, to dish out " \ "<green>%d</green> points instead of <red>%d</red>." % (preset.name, new_points, preset.points) @command(command="raid", params=[Options(["presets", "addpts"])], access_level="member", description="See list of points presets") def presets_cmd(self, request, _): return ChatBlob("Raid Points Presets", self.build_preset_list()) def build_preset_list(self): presets = self.db.query("SELECT * FROM points_presets ORDER BY name ASC, points DESC") if presets: blob = "" for preset in presets: add_points_link = self.text.make_tellcmd("Add pts", "raid addpts %s" % preset.name) blob += "<highlight>%s</highlight> worth <green>%d</green> points %s [id: %d]\n\n" \ % (preset.name, preset.points, add_points_link, preset.preset_id) return blob return "No presets available. To add new presets use <highlight><symbol>presets add preset_name preset_points</highlight>." def add_log_entry(self, char_id: int, leader_id: int, reason: str, amount=0): sql = "INSERT INTO points_log (char_id, audit, leader_id, reason, created_at) VALUES (?,?,?,?,?)" return self.db.exec(sql, [char_id, amount, leader_id, reason, int(time.time())]) def alter_points(self, char_id: int, leader_id: int, reason: str, amount: int): sql = "UPDATE points SET points = points + ? WHERE char_id = ?" self.db.exec(sql, [amount, char_id]) self.add_log_entry(char_id, leader_id, reason, amount) def get_account(self, main_id, conn): sql = "SELECT p.char_id, p.points, p.disabled FROM points p WHERE p.char_id = ?" row = self.db.query_single(sql, [main_id]) if not row: self.create_account(main_id, SenderObj(conn.get_char_id(), conn.get_char_name(), None)) row = self.db.query_single(sql, [main_id]) return row def create_account(self, main_id, sender): sql = "INSERT INTO points (char_id, points, created_at) VALUES (?,?,?)" self.db.exec(sql, [main_id, 0, int(time.time())]) self.add_log_entry(main_id, sender.char_id, "Account opened by %s" % sender.name) def get_account_display(self, char: SenderObj, page): main = self.alts_service.get_main(char.char_id) if not main: return "Could not find character <highlight>%s</highlight>." % char.name page = int(page) if page else 1 page_size = 20 offset = (page - 1) * page_size points = self.db.query_single("SELECT points, disabled FROM points WHERE char_id = ?", [main.char_id]) if not points: return "Could not find raid account for <highlight>%s</highlight>." % char.name alts_link = self.text.make_tellcmd("Alts", "alts %s" % main.name) blob = "" blob += "Account: %s [%s]\n" % (main.name, alts_link) blob += "Points: %d\n" % points.points blob += "Status: %s\n\n" % ("<green>Open</green>" if points.disabled == 0 else "<red>Disabled</red>") points_log = self.db.query("SELECT * FROM points_log WHERE char_id = ? ORDER BY created_at DESC LIMIT ?, ?", [main.char_id, offset, page_size]) blob += "<header2>Account log</header2>\n" if points_log is None: blob += "No entries in log." else: for entry in points_log: if entry.audit > 0: pts = "<green>+%d</green>" % entry.audit blob += "<grey>[%s]</grey> %s points by <highlight>%s</highlight>; <orange>%s</orange>" \ % (self.util.format_datetime(entry.created_at), pts, self.character_service.resolve_char_to_name(entry.leader_id), entry.reason) elif entry.audit < 0: pts = "<red>-%d</red>" % (-1 * entry.audit) blob += "<grey>[%s]</grey> %s points by <highlight>%s</highlight>; <orange>%s</orange>" \ % (self.util.format_datetime(entry.created_at), pts, self.character_service.resolve_char_to_name(entry.leader_id), entry.reason) else: # If points is 0, then it's a general case log blob += "<grey>[%s]</grey> <orange>%s</orange>" % (self.util.format_datetime(entry.created_at), entry.reason) log_entry_link = self.text.make_tellcmd(entry.log_id, f"account history {entry.log_id}") blob += " [%s]\n" % log_entry_link return ChatBlob("%s Account" % char.name, blob)
class AuctionController: def __init__(self): self.auction: AuctionStrategy = None @setting(name="auction_length", value="90s", description="Regular auction length in seconds") def auction_length(self): return TimeSettingType() @setting(name="auction_announce_interval", value="15s", description="Auction announce interval") def auction_announce_interval(self): return TimeSettingType() @setting(name="auction_late_bid_extension", value="10s", description="How much to extend auction in case of late bid") def auction_late_bid_extension(self): return TimeSettingType() @setting(name="auction_late_bid_threshold", value="10s", description="Threshold for a bid to be considered late") def auction_late_bid_threshold(self): return TimeSettingType() @command(command="auction", params=[], description="Show auction status", access_level="member") def auction_cmd(self, request): if not self.is_auction_running(): return "No auction running." return self.auction.get_auction_list() @command(command="auction", params=[Const("start"), Any("items")], description="Start an auction, with one or more items", access_level="moderator", sub_command="modify") def auction_start_cmd(self, request, _, items): if self.is_auction_running(): return "Auction already running." items = re.findall( r"(([^<]+)?<a href=[\"\']itemref://(\d+)/(\d+)/(\d+)[\"\']>([^<]+)</a>([^<]+)?)", items) # TODO choose auction strategy impl self.auction = AuctionStrategy() for item in items: self.auction.add_item(item[0]) auction_length = self.auction_length().get_value() announce_interval = self.auction_announce_interval().get_value() late_bid_threshold = self.auction_late_bid_threshold().get_value() late_bid_extension = self.auction_late_bid_extension().get_value() return self.auction.start(request.sender, auction_length, announce_interval, late_bid_threshold, late_bid_extension) @command(command="auction", params=[Options(["cancel", "end"])], description="Cancel ongoing auction", access_level="moderator", sub_command="modify") def auction_cancel_cmd(self, request, _): if not self.is_auction_running(): return "No auction running." result = self.auction.cancel(request.sender) self.auction = None return result @command(command="auction", params=[ Const("bid"), Int("amount", is_optional=True), Const("all", is_optional=True), Int("item_index", is_optional=True) ], description="Bid on an item", access_level="member") def auction_bid_cmd(self, request, _, amount, all_amount, item_index): if not self.is_auction_running(): return "No auction running." return self.auction.add_bid(request.sender, all_amount or amount, item_index) def is_auction_running(self): return self.auction and self.auction.is_running
class LadderController: def __init__(self): self.grades = ["shiny", "bright", "faded"] def inject(self, registry): self.db = registry.get_instance("db") self.util = registry.get_instance("util") self.text = registry.get_instance("text") @command(command="ladder", params=[Const("treatment"), Int("starting_amount")], access_level="all", description="Show sequence of laddering implants for treatment", extended_description="The base amount should be the treatment or ability you have with all nano buffs, perks, and items-buffing equipment equipped, " "but minus any implants you have equipped.") def ladder_treatment_cmd(self, request, _, treatment): if treatment < 11: return "Base treatment must be at least <highlight>11<end>." prefix = "skill_" result = self.optimized_steps(prefix, treatment, self.get_implant_by_max_treatment) blob = self.format_steps("treatment", result.steps, treatment, (treatment + self.calculate_total(result.slots, prefix))) return ChatBlob("Laddering Treatment", blob) @command(command="ladder", params=[Const("ability"), Int("starting_amount")], access_level="all", description="Show sequence of laddering implants for ability", extended_description="The base amount should be the treatment or ability you have with all nano buffs, perks, and items-buffing equipment equipped, " "but minus any implants you have equipped.") def ladder_ability_cmd(self, request, _, ability): if ability < 6: return "Base ability must be at least <highlight>6<end>." prefix = "ability_" result = self.optimized_steps(prefix, ability, self.get_implant_by_max_ability) blob = self.format_steps("ability", result.steps, ability, (ability + self.calculate_total(result.slots, prefix))) return ChatBlob("Laddering Ability", blob) def format_steps(self, label, steps, starting, ending): blob = "Starting %s: <highlight>%s<end>\n\n" % (label, starting) blob += "-------------------\n\n" for (action, grade, implant) in steps: blob += "%s %s QL <highlight>%d<end>\n\n" % (action.capitalize(), grade, implant.ql) blob += "-------------------\n\n" blob += "Ending %s: <highlight>%s<end>\n\n" % (label, ending) blob += "\nInspired by a command written by Lucier of the same name" return blob def optimized_steps(self, prefix, base_value, get_max_implant): grade_combinations = [self.grades, ["shiny", "faded", "bright"], ["bright", "shiny", "faded"], ["bright", "faded", "shiny"], ["faded", "shiny", "bright"], ["faded", "bright", "shiny"]] best = None for grade_combo in grade_combinations: result = self.get_steps(prefix, base_value, get_max_implant, lambda current_grade: self.get_next_grade(current_grade, grade_combo)) if not best: best = result else: result_value = self.calculate_total(result.slots, prefix) best_value = self.calculate_total(best.slots, prefix) if result_value > best_value: # this is here as a sanity check, but it appears that the result is always the same no matter which order you insert the implants best = result elif best_value == result_value and len(result.steps) < len(best.steps): # this optimizes for the least amount of steps/implants best = result return best def get_steps(self, prefix, base_value, get_max_implant, get_next_grade): slots = DictObject({"shiny": None, "bright": None, "faded": None}) steps = [] num_skipped = 0 current_grade = None while num_skipped < 3: current_grade = get_next_grade(current_grade) # find next highest possible implant new_implant = get_max_implant(self.calculate_total(slots, prefix, current_grade) + base_value) if not slots.get(current_grade): steps.append(["add", current_grade, new_implant]) slots[current_grade] = new_implant elif new_implant.get(prefix + current_grade) > slots.get(current_grade).get(prefix + current_grade): steps.append(["remove", current_grade, slots.get(current_grade)]) steps.append(["add", current_grade, new_implant]) slots[current_grade] = new_implant else: num_skipped += 1 return DictObject({"slots": slots, "steps": steps}) def get_next_grade(self, current_grade, grades): if not current_grade: next_index = 0 else: next_index = grades.index(current_grade) + 1 if next_index >= len(grades): next_index = 0 return grades[next_index] def get_implant_by_max_treatment(self, treatment): return self.db.query_single("SELECT * from implant_requirement WHERE treatment <= ? ORDER BY ql DESC LIMIT 1", [treatment]) def get_implant_by_max_ability(self, ability): return self.db.query_single("SELECT * from implant_requirement WHERE ability <= ? ORDER BY ql DESC LIMIT 1", [ability]) def get_cluser_min_ql(self, ql, grade): if grade == "shiny": result = math.floor(ql * 0.86) elif grade == "bright": result = math.floor(ql * 0.84) elif grade == "faded": result = math.floor(ql * 0.82) else: raise Exception("Unknown grade: '%s'" % grade) if ql >= 201: return max(201, result) else: return result def calculate_total(self, slots, prefix, skip_grade=None): value = 0 for grade in self.grades: if grade != skip_grade: implant = slots.get(grade) if implant: value += implant.get(prefix + grade) return value
class TowerAttackController: def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot = registry.get_instance("bot") self.db = registry.get_instance("db") self.text = registry.get_instance("text") self.util = registry.get_instance("util") self.event_service = registry.get_instance("event_service") self.playfield_controller = registry.get_instance( "playfield_controller") self.command_alias_service = registry.get_instance( "command_alias_service") self.public_channel_service = registry.get_instance( "public_channel_service") def start(self): self.db.exec( "CREATE TABLE IF NOT EXISTS tower_attacker (id INT PRIMARY KEY AUTO_INCREMENT, att_org_name VARCHAR(50) NOT NULL, att_faction VARCHAR(10) NOT NULL, " "att_char_id INT, att_char_name VARCHAR(20) NOT NULL, att_level INT NOT NULL, att_ai_level INT NOT NULL, att_profession VARCHAR(15) NOT NULL, " "x_coord INT NOT NULL, y_coord INT NOT NULL, is_victory SMALLINT NOT NULL, " "tower_battle_id INT NOT NULL, created_at INT NOT NULL)") self.db.exec( "CREATE TABLE IF NOT EXISTS tower_battle (id INT PRIMARY KEY AUTO_INCREMENT, playfield_id INT NOT NULL, site_number INT NOT NULL, " "def_org_name VARCHAR(50) NOT NULL, def_faction VARCHAR(10) NOT NULL, is_finished INT NOT NULL, battle_type VARCHAR(20) NOT NULL, last_updated INT NOT NULL)" ) self.command_alias_service.add_alias("victory", "attacks") @command(command="attacks", params=[NamedParameters(["page"])], access_level="all", description="Show recent tower attacks and victories") def attacks_cmd(self, request, named_params): page = int(named_params.page or "1") page_size = 30 offset = (page - 1) * page_size sql = """ SELECT b.*, a.*, COALESCE(a.att_level, 0) AS att_level, COALESCE(a.att_ai_level, 0) AS att_ai_level, p.short_name, b.id AS battle_id FROM tower_battle b LEFT JOIN tower_attacker a ON a.tower_battle_id = b.id LEFT JOIN playfields p ON p.id = b.playfield_id ORDER BY b.last_updated DESC, a.created_at DESC LIMIT %d, %d """ % (offset, page_size) data = self.db.query(sql) t = int(time.time()) blob = self.check_for_all_towers_channel() if page > 1: blob += " " + self.text.make_chatcmd( "<< Page %d" % (page - 1), self.get_chat_command(page - 1)) if len(data) > 0: blob += " Page " + str(page) blob += " " + self.text.make_chatcmd( "Page %d >>" % (page + 1), self.get_chat_command(page + 1)) blob += "\n" current_battle_id = -1 for row in data: if current_battle_id != row.battle_id: blob += "\n<pagebreak>" current_battle_id = row.battle_id blob += self.format_battle_info(row, t) blob += self.text.make_chatcmd( "More Info", "/tell <myname> attacks battle %d" % row.battle_id) + "\n" blob += "<header2>Attackers:<end>\n" blob += "<tab>" + self.format_attacker(row) + "\n" return ChatBlob("Tower Attacks", blob) @command(command="attacks", params=[Const("battle"), Int("battle_id")], access_level="all", description="Show battle info for a specific battle") def attacks_battle_cmd(self, request, _, battle_id): battle = self.db.query_single( "SELECT b.*, p.short_name FROM tower_battle b LEFT JOIN playfields p ON p.id = b.playfield_id WHERE b.id = ?", [battle_id]) if not battle: return "Could not find battle with ID <highlight>%d<end>." % battle_id t = int(time.time()) attackers = self.db.query( "SELECT * FROM tower_attacker WHERE tower_battle_id = ? ORDER BY created_at DESC", [battle_id]) first_activity = attackers[-1].created_at if len( attackers) > 0 else battle.last_updated blob = self.check_for_all_towers_channel() blob += self.format_battle_info(battle, t) blob += "Duration: <highlight>%s<end>\n\n" % self.util.time_to_readable( battle.last_updated - first_activity) blob += "<header2>Attackers:<end>\n" for row in attackers: blob += "<tab>" + self.format_attacker(row) blob += " " + self.format_timestamp(row.created_at, t) blob += "\n" return ChatBlob("Battle Info %d" % battle_id, blob) @event(event_type=TowerController.TOWER_ATTACK_EVENT, description="Record tower attacks", is_hidden=True) def tower_attack_event(self, event_type, event_data): t = int(time.time()) site_number = self.find_closest_site_number( event_data.location.playfield.id, event_data.location.x_coord, event_data.location.y_coord) attacker = event_data.attacker or {} defender = event_data.defender battle = self.find_or_create_battle(event_data.location.playfield.id, site_number, defender.org_name, defender.faction, "attack", t) self.db.exec( "INSERT INTO tower_attacker (att_org_name, att_faction, att_char_id, att_char_name, att_level, att_ai_level, att_profession, " "x_coord, y_coord, is_victory, tower_battle_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ attacker.get("org_name", ""), attacker.get("faction", ""), attacker.get("char_id", 0), attacker.get("name", ""), attacker.get("level", 0), attacker.get("ai_level", 0), attacker.get("profession", ""), event_data.location.x_coord, event_data.location.y_coord, 0, battle.id, t ]) @event(event_type=TowerController.TOWER_VICTORY_EVENT, description="Record tower victories", is_hidden=True) def tower_victory_event(self, event_type, event_data): t = int(time.time()) if event_data.type == "attack": row = self.get_last_attack(event_data.winner.faction, event_data.winner.org_name, event_data.loser.faction, event_data.loser.org_name, event_data.location.playfield.id, t) if not row: site_number = 0 is_finished = 1 self.db.exec( "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)", [ event_data.location.playfield.id, site_number, event_data.loser.org_name, event_data.loser.faction, is_finished, event_data.type, t ]) battle_id = self.db.last_insert_id() attacker = event_data.winner or {} self.db.exec( "INSERT INTO tower_attacker (att_org_name, att_faction, att_char_id, att_char_name, att_level, att_ai_level, att_profession, " "x_coord, y_coord, is_victory, tower_battle_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ attacker.get("org_name", ""), attacker.get("faction", ""), attacker.get("char_id", 0), attacker.get("name", ""), attacker.get("level", 0), attacker.get("ai_level", 0), attacker.get("profession", ""), 0, 0, 0, battle_id, t ]) else: is_victory = 1 self.db.exec( "UPDATE tower_attacker SET is_victory = ? WHERE id = ?", [is_victory, row.attack_id]) is_finished = 1 self.db.exec( "UPDATE tower_battle SET is_finished = ?, last_updated = ? WHERE id = ?", [is_finished, t, row.battle_id]) elif event_data.type == "terminated": site_number = 0 is_finished = 1 self.db.exec( "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)", [ event_data.location.playfield.id, site_number, event_data.loser.org_name, event_data.loser.faction, is_finished, event_data.type, t ]) else: raise Exception("Unknown victory event type: '%s'" % event_data.type) def format_attacker(self, row): if row.att_char_name: level = ("%d/<green>%d<end>" % (row.att_level, row.att_ai_level) ) if row.att_ai_level > 0 else "%d" % row.att_level org = row.att_org_name + " " if row.att_org_name else "" victor = " - <notice>Winner!<end>" if row.is_victory else "" return "%s (%s %s) %s(%s)%s" % (row.att_char_name, level, row.att_profession, org, row.att_faction, victor) else: return "Unknown attacker" def find_closest_site_number(self, playfield_id, x_coord, y_coord): sql = """ SELECT site_number, ((x_distance * x_distance) + (y_distance * y_distance)) radius FROM (SELECT playfield_id, site_number, min_ql, max_ql, x_coord, y_coord, site_name, (x_coord - ?) as x_distance, (y_coord - ?) as y_distance FROM tower_site WHERE playfield_id = ?) t ORDER BY radius ASC LIMIT 1""" row = self.db.query_single(sql, [x_coord, y_coord, playfield_id]) if row: return row.site_number else: return 0 def find_or_create_battle(self, playfield_id, site_number, org_name, faction, battle_type, t): last_updated = t - (8 * 3600) is_finished = 0 sql = """ SELECT * FROM tower_battle WHERE playfield_id = ? AND site_number = ? AND is_finished = ? AND def_org_name = ? AND def_faction = ? AND last_updated >= ? """ battle = self.db.query_single(sql, [ playfield_id, site_number, is_finished, org_name, faction, last_updated ]) if battle: self.db.exec( "UPDATE tower_battle SET last_updated = ? WHERE id = ?", [t, battle.id]) return battle else: self.db.exec( "INSERT INTO tower_battle (playfield_id, site_number, def_org_name, def_faction, is_finished, battle_type, last_updated) VALUES (?, ?, ?, ?, ?, ?, ?)", [ playfield_id, site_number, org_name, faction, is_finished, battle_type, t ]) return self.db.query_single( "SELECT * FROM tower_battle WHERE id = ?", [self.db.last_insert_id()]) def get_last_attack(self, att_faction, att_org_name, def_faction, def_org_name, playfield_id, t): last_updated = t - (8 * 3600) is_finished = 0 sql = """ SELECT b.id AS battle_id, a.id AS attack_id FROM tower_battle b JOIN tower_attacker a ON a.tower_battle_id = b.id WHERE a.att_faction = ? AND a.att_org_name = ? AND b.def_faction = ? AND b.def_org_name = ? AND b.playfield_id = ? AND b.is_finished = ? AND b.last_updated >= ? ORDER BY last_updated DESC LIMIT 1""" return self.db.query_single(sql, [ att_faction, att_org_name, def_faction, def_org_name, playfield_id, is_finished, last_updated ]) def format_battle_info(self, row, t): blob = "" defeated = " - <notice>Defeated!<end>" if row.is_finished else "" blob += "Site: <highlight>%s %s<end>\n" % (row.short_name, row.site_number or "?") blob += "Defender: <highlight>%s<end> (%s)%s\n" % ( row.def_org_name, row.def_faction, defeated) blob += "Last Activity: %s\n" % self.format_timestamp( row.last_updated, t) return blob def format_timestamp(self, t, current_t): return "<highlight>%s<end> (%s ago)" % (self.util.format_datetime( t), self.util.time_to_readable(current_t - t)) def get_chat_command(self, page): return "/tell <myname> attacks --page=%d" % page def check_for_all_towers_channel(self): if not self.public_channel_service.get_channel_name( TowerController.ALL_TOWERS_ID): return "Notice: The bot must belong to an org and be promoted to a rank that is high enough to have the All Towers channel (e.g., Squad Commander) in order for the <symbol>attacks command to work correctly.\n\n" else: return ""
class ItemsController: PAGE_SIZE = 30 def inject(self, registry): self.db: DB = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.command_alias_service = registry.get_instance( "command_alias_service") def pre_start(self): self.db.load_sql_file(self.module_dir + "/" + "aodb.sql") def start(self): self.command_alias_service.add_alias("item", "items") self.command_alias_service.add_alias("i", "items") @command(command="items", params=[Int("item_id")], access_level="all", description="Search for an item by item id") def items_id_cmd(self, request, item_id): item = self.get_by_item_id(item_id) if item: return self.format_items_response(None, str(item_id), [item], 0, 1) else: return "Could not find item with ID <highlight>%d</highlight>." % item_id @command(command="items", params=[ Int("ql", is_optional=True), Any("search"), NamedParameters(["page"]) ], access_level="all", description="Search for an item") def items_search_cmd(self, request, ql, search, named_params): page = int(named_params.page or "1") search = html.unescape(search) offset = (page - 1) * self.PAGE_SIZE all_items = self.find_items(search, ql) return self.format_items_response(ql, search, all_items, offset, page) def format_items_response(self, ql, search, all_items, offset, page_number): items = self.sort_items(search, all_items)[offset:offset + self.PAGE_SIZE] cnt = len(items) if cnt == 0: if ql: return "No QL <highlight>%d</highlight> items found matching <highlight>%s</highlight>." % ( ql, search) else: return "No items found matching <highlight>%s</highlight>." % search elif cnt == 1: item = items[0] return self.format_single_item([item], ql) else: blob = "" # blob += "Version: <highlight>%s</highlight>\n" % "unknown" if ql: blob += "Search: <highlight>QL %d %s</highlight>\n" % (ql, search) else: blob += "Search: <highlight>%s</highlight>\n" % search blob += "\n" blob += self.text.get_paging_links( self.get_chat_command(ql, search), page_number, (offset + self.PAGE_SIZE) < len(all_items)) blob += "\n\n" blob += self.format_items(items, ql) blob += "\nItem DB rips created using the %s tool." % self.text.make_chatcmd( "Budabot Items Extractor", "/start https://github.com/Budabot/ItemsExtractor") return ChatBlob( "Item Search Results (%d - %d of %d)" % (offset + 1, min(offset + self.PAGE_SIZE, len(all_items)), len(all_items)), blob) def format_items(self, items, ql=None): blob = "" for item_group in ItemIter(items): blob += "<pagebreak>" blob += self.text.make_image(item_group[0].icon) + "\n" blob += self.format_single_item(item_group, ql) blob += "\n\n" return blob def format_single_item(self, item_group, ql): msg = "" msg += item_group[0].name for item in reversed(item_group): if ql: if item.lowql != item.highql: msg += " %s" % self.text.make_item(item.lowid, item.highid, ql, ql) msg += " [%s - %s]" % (self.text.make_item( item.lowid, item.highid, item.lowql, item.lowql), self.text.make_item( item.lowid, item.highid, item.highql, item.highql)) elif item.lowql == item.highql: msg += " [%s]" % self.text.make_item( item.lowid, item.highid, item.highql, item.highql) elif item.lowql == item.highql: msg += " [%s]" % self.text.make_item(item.lowid, item.highid, item.highql, item.highql) else: msg += " [%s - %s]" % (self.text.make_item( item.lowid, item.highid, item.lowql, item.lowql), self.text.make_item( item.lowid, item.highid, item.highql, item.highql)) return msg def find_items(self, name, ql=None): params = [name] sql = "SELECT * FROM aodb WHERE name LIKE ? " if ql: sql += " AND lowql <= ? AND highql >= ?" params.append(ql) params.append(ql) sql += " UNION SELECT * FROM aodb WHERE name <EXTENDED_LIKE=%d> ?" % len( params) params.append(name) if ql: sql += " AND lowql <= ? AND highql >= ?" params.append(ql) params.append(ql) sql += " ORDER BY name ASC, highql DESC" return self.db.query(sql, params, extended_like=True) def sort_items(self, search, items): search = search.lower() search_parts = search.split(" ") # if item name matches search exactly (case-insensitive) then priority = 0 # if item name contains every whole word from search (case-insensitive) then priority = 1 # +1 priority for each whole word from search that item name does not contain for row in items: row.priority = 0 row_name = row.name.lower() if row_name != search: row.priority += 1 row_parts = row_name.split(" ") for search_part in search_parts: if search_part not in row_parts: row.priority += 1 items.sort(key=lambda x: x.priority, reverse=False) return items def get_by_item_id(self, item_id, ql=None): if ql: return self.db.query_single( "SELECT * FROM aodb WHERE (highid = ? OR lowid = ?) AND (lowql <= ? AND highql >= ?) ORDER BY highid = ? DESC LIMIT 1", [item_id, item_id, ql, ql, item_id]) else: return self.db.query_single( "SELECT * FROM aodb WHERE highid = ? OR lowid = ? ORDER BY highid = ? DESC LIMIT 1", [item_id, item_id, item_id]) def find_by_name(self, name, ql=None): if ql: return self.db.query_single( "SELECT * FROM aodb WHERE name = ? AND lowql <= ? AND highql >= ? ORDER BY highid DESC LIMIT 1", [name, ql, ql]) else: return self.db.query_single( "SELECT * FROM aodb WHERE name = ? ORDER BY highql DESC, highid DESC LIMIT 1", [name]) def get_chat_command(self, ql, search): if ql: return "items %d %s" % (ql, search) else: return "items %s" % search