diff --git a/strings.py b/strings.py index c289fba8..4ab3b701 100644 --- a/strings.py +++ b/strings.py @@ -8,7 +8,9 @@ class SafeDict(dict): return key -def safely_format_string(string: str, words: typing.Dict[str, str], ignore_escaping=False) -> str: +def safely_format_string(string: str, words: typing.Dict[str, str] = None, ignore_escaping=False) -> str: + if words is None: + words = {} if ignore_escaping: escaped = words else: @@ -47,6 +49,12 @@ class BRIDGE: INACTIVE_BRIDGE = "⚠ Il collegamento tra Telegram e Discord non è attivo al momento." +# Random spellslinging +class CAST: + class ERRORS: + NOT_YET_AVAILABLE = "⚠ Il nuovo cast non è ancora disponibile! Per un'anteprima sulle nuove funzioni, usa /spell." + + # Ciao Ruozi! class CIAORUOZI: THE_LEGEND_HIMSELF = "👋 Ciao me!" @@ -200,10 +208,22 @@ class SHIP: RESULT = "💕 {one} + {two} = {result}" class ERRORS: - INVALID_SYNTAX = "⚠ Non hai specificato correttamente i due nomi!\nSintassi corretta: /ship (nome) (nome)" + INVALID_SYNTAX = "⚠ Non hai specificato correttamente i due nomi!\nSintassi: /ship (nome) (nome)" INVALID_NAMES = "⚠ I nomi specificati non sono validi.\nRiprova con dei nomi diversi!" +# Get information about a spell +class SPELL: + HEADER = "🔍 La magia {name} ha le seguenti proprietà (v{version}):\n" + ACCURACY = "Precisione - {accuracy}%\n" + DAMAGE = "Danni - {number}d{type}{constant}\n" + TYPE = "Tipo - {type}\n" + REPEAT = "Multiattacco - ×{repeat}\n" + + class ERRORS: + INVALID_SYNTAX = "⚠ Non hai specificato la magia di cui vuoi conoscere i dettagli!\nSintassi: /spell (nome)" + + # Secondo me, è colpa delle stringhe. SMECDS = "🤔 Secondo me, è colpa {ds}." diff --git a/telegrambot.py b/telegrambot.py index 3b6429f9..9c85e180 100644 --- a/telegrambot.py +++ b/telegrambot.py @@ -92,7 +92,7 @@ def command(func: "function"): # noinspection PyBroadException try: reply_msg(bot, main_group_id, strings.TELEGRAM.ERRORS.CRITICAL_ERROR, - exc_info=sys.exc_info()) + exc_info=repr(sys.exc_info())) except Exception: logger.error(f"Double critical error: {sys.exc_info()}") sentry.user_context({ @@ -173,21 +173,8 @@ def cmd_cv(bot: telegram.Bot, update: telegram.Update): @command -@database_access -def cmd_cast(bot: telegram.Bot, update: telegram.Update, session: db.Session): - try: - spell: str = update.message.text.split(" ", 1)[1] - except IndexError: - bot.send_message(update.message.chat.id, "⚠️ Non hai specificato nessun incantesimo!\n" - "Sintassi corretta: `/cast `", parse_mode="Markdown") - return - # Find a target for the spell - target = random.sample(session.query(db.Telegram).all(), 1)[0] - # END - bot.send_message(update.message.chat.id, cast(spell_name=spell, - target_name=target.username if target.username is not None - else target.first_name, platform="telegram"), - parse_mode="HTML") +def cmd_cast(bot: telegram.Bot, update: telegram.Update): + reply(bot, update, strings.CAST.ERRORS.NOT_YET_AVAILABLE) @command @@ -732,7 +719,6 @@ def cmd_dndmarkov(bot: telegram.Bot, update: telegram.Update): reply(bot, update, sentence) - def exec_roll(roll) -> str: result = int(roll.evaluate()) string = "" @@ -772,6 +758,17 @@ def cmd_start(bot: telegram.Bot, update: telegram.Update): reply(bot, update, strings.TELEGRAM.BOT_STARTED) +@command +def cmd_spell(bot: telegram.Bot, update: telegram.Update): + try: + input: str = update.message.text.split(" ", 1)[1] + except IndexError: + reply(bot, update, strings.SPELL.ERRORS.INVALID_SYNTAX) + return + spell = cast.Spell(input) + reply(bot, update, spell.stringify()) + + def process(arg_discord_connection): if arg_discord_connection is not None: global discord_connection @@ -806,6 +803,7 @@ def process(arg_discord_connection): u.dispatcher.add_handler(CommandHandler("search", cmd_search)) u.dispatcher.add_handler(CommandHandler("regex", cmd_regex)) u.dispatcher.add_handler(CommandHandler("start", cmd_start)) + u.dispatcher.add_handler(CommandHandler("spell", cmd_spell)) u.dispatcher.add_handler(CallbackQueryHandler(on_callback_query)) logger.info("Handlers registered.") u.start_polling() diff --git a/utils/__init__.py b/utils/__init__.py index 29f222d1..8b78ba4d 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,6 +1,6 @@ from .dirty import Dirty, DirtyDelta from .mmstatus import MatchmakingStatus -from .cast import cast +from .cast import Spell, Hit, Cast from .stagismo import smecds -__all__ = ["Dirty", "DirtyDelta", "MatchmakingStatus", "cast", "smecds"] +__all__ = ["Dirty", "DirtyDelta", "MatchmakingStatus", "Spell", "Hit", "Cast", "smecds"] diff --git a/utils/cast.py b/utils/cast.py index a8731bd1..c99c564d 100644 --- a/utils/cast.py +++ b/utils/cast.py @@ -1,79 +1,94 @@ import random import math -import db +import typing +import strings +s = strings.safely_format_string -def cast(spell_name: str, target_name: str, platform: str) -> str: - spell = spell_name.capitalize() - # Seed the rng with the spell name - # so that spells always deal the same damage - random.seed(spell) - dmg_dice = random.randrange(1, 11) - dmg_max = random.sample([4, 6, 8, 10, 12, 20, 100], 1)[0] - dmg_mod = random.randrange(math.floor(-dmg_max / 5), math.ceil(dmg_max / 5) + 1) - dmg_type = random.sample(["da fuoco", "da freddo", "elettrici", "sonici", "necrotici", "magici", - "da acido", "divini", "nucleari", "psichici", "fisici", "puri", "da taglio", - "da perforazione", "da impatto", "da caduta", "gelato", "onnipotenti", "oscuri", - "di luce", "da velocità", "da cactus", "meta", "dannosi", "da radiazione", - "tuamammici", "da maledizione", "pesanti", "leggeri", "immaginari", "da laser", - "da neutrini", "galattici", "cerebrali", "ritardati", "ritardanti"], 1)[0] - # Reseed the rng with a random value - # so that the dice roll always deals a different damage - random.seed() - total = dmg_mod - # Check for a critical hit - crit = 1 - while True: - crit_die = random.randrange(1, 21) - if crit_die == 20: - crit *= 2 +class Spell: + version = "3.0" + + dice_type_distribution = ([4] * 1) +\ + ([6] * 6) +\ + ([8] * 24) +\ + ([10] * 38) +\ + ([12] * 24) +\ + ([20] * 6) +\ + ([100] * 1) + + all_damage_types = ["da fuoco", "da freddo", "elettrici", "sonici", "necrotici", "magici", + "da acido", "divini", "nucleari", "psichici", "fisici", "puri", "da taglio", + "da perforazione", "da impatto", "da caduta", "gelato", "onnipotenti", "oscuri", + "di luce", "da velocità", "da cactus", "meta", "dannosi", "da radiazione", + "tuamammici", "da maledizione", "pesanti", "leggeri", "immaginari", "da laser", + "da neutrini", "galattici", "cerebrali", "ritardati", "ritardanti", "morali", "materiali", + "energetici", "esplosivi"] + + repeat_distribution = ([1] * 8) +\ + ([2] * 1) +\ + ([3] * 1) + + damage_types_distribution = ([1] * 6) + \ + ([2] * 3) + \ + ([3] * 1) + + def __init__(self, name: str): + seed = name.capitalize() + random.seed(seed) + # Spell data + self.name = seed + self.dice_number = random.randrange(1, 21) + self.dice_type = random.sample(self.dice_type_distribution, 1)[0] + self.constant = random.randrange(math.floor(-self.dice_type / 4), math.ceil(self.dice_type / 4) + 1) + self.miss_chance = random.randrange(80, 101) + self.repeat = random.sample(self.repeat_distribution, 1)[0] + self.damage_types_qty = random.sample(self.damage_types_distribution, 1)[0] + self.damage_types = random.sample(self.all_damage_types, self.damage_types_qty) + + def stringify(self) -> str: + string = s(strings.SPELL.HEADER, words={"name": self.name, "version": self.version}) + string += s(strings.SPELL.ACCURACY, words={"accuracy": str(self.miss_chance)}) + if self.constant > 0: + constant = "+" + str(self.constant) + elif self.constant == 0: + constant = "" else: - break - for dice in range(0, dmg_dice): - total += random.randrange(1, dmg_max + 1) - if crit > 1: - if platform == "telegram": - crit_msg = f"CRITICO ×{crit}{'!' * crit}\n" - elif platform == "discord": - crit_msg = f"**CRITICO ×{crit}{'!' * crit}**\n" - total *= crit - else: - crit_msg = "" - if platform == "telegram": - if dmg_dice == 10 and dmg_max == 100 and dmg_mod == 20: - return f"❇️‼️ Ho lanciato {spell} su " \ - f"{target_name}.\n" \ - f"Una grande luce illumina il cielo, seguita poco dopo da un fungo di fumo nel luogo" \ - f" in cui si trovava {target_name}.\n" \ - f"Il fungo si espande a velocità smodata, finchè il fumo non ricopre la Terra intera e le tenebre" \ - f" cadono su di essa.\n" \ - f"Dopo qualche minuto, la temperatura ambiente raggiunge gli 0 °C, e continua a diminuire.\n" \ - f"L'Apocalisse Nucleare è giunta, e tutto per polverizzare {target_name}" \ - f" con {spell}.\n" \ - f"{target_name} subisce 10d100+20=9999 danni apocalittici!" - return f"❇️ Ho lanciato {spell} su " \ - f"{target_name}.\n" \ - f"{crit_msg}" \ - f"{target_name} subisce {dmg_dice}d{dmg_max}" \ - f"{'+' if dmg_mod > 0 else ''}{str(dmg_mod) if dmg_mod != 0 else ''}" \ - f"{'×' + str(crit) if crit > 1 else ''}" \ - f"={total if total > 0 else 0} danni {dmg_type}!" - elif platform == "discord": - if dmg_dice == 10 and dmg_max == 100 and dmg_mod == 20: - return f"❇️‼️ Ho lanciato **{spell}** su " \ - f"_{target_name}_.\n" \ - f"Una grande luce illumina il cielo, seguita poco dopo da un fungo di fumo nel luogo" \ - f" in cui si trovava _{target_name}_.\n" \ - f"Il fungo si espande a velocità smodata, finchè il fumo non ricopre la Terra intera e le tenebre" \ - f" cadono su di essa.\n" \ - f"Dopo qualche minuto, la temperatura ambiente raggiunge gli 0 °C, e continua a diminuire.\n" \ - f"L'Apocalisse Nucleare è giunta, e tutto per polverizzare _{target_name}_" \ - f" con **{spell}**.\n" \ - f"_{target_name}_ subisce 10d100+20=**9999** danni apocalittici!" - return f"❇️ Ho lanciato **{spell}** su " \ - f"_{target_name}_.\n" \ - f"{crit_msg}" \ - f"_{target_name}_ subisce {dmg_dice}d{dmg_max}" \ - f"{'+' if dmg_mod > 0 else ''}{str(dmg_mod) if dmg_mod != 0 else ''}" \ - f"{'×' + str(crit) if crit > 1 else ''}" \ - f"=**{total if total > 0 else 0}** danni {dmg_type}!" + constant = str(self.constant) + string += s(strings.SPELL.DAMAGE, + words={"number": str(self.dice_number), + "type": str(self.dice_type), + "constant": constant}) + for dmg_type in self.damage_types: + string += s(strings.SPELL.TYPE, words={"type": dmg_type}) + if self.repeat > 1: + string += s(strings.SPELL.REPEAT, words={"repeat": str(self.repeat)}) + return string + + +class Hit: + def __init__(self, spell: Spell): + random.seed() + self.hit_roll = random.randrange(0, 101) + self.damage = 0 + self.crit_multiplier = 1 + self.damage_type = random.sample(spell.damage_types, 1)[0] + if self.hit_roll > spell.miss_chance: + return + for _ in range(spell.dice_number): + self.damage += random.randrange(1, spell.dice_type + 1) + self.damage += spell.constant + if self.damage < 0: + self.damage = 0 + while random.randrange(1, 21) == 20: + self.crit_multiplier *= 2 + + def damage(self): + return self.damage * self.crit_multiplier + + +class Cast: + def __init__(self, spell: Spell): + self.hits = [] + for _ in spell.repeat: + self.hits.append(Hit(spell)) +