2019-11-11 09:34:05 +00:00
|
|
|
|
import aiohttp
|
|
|
|
|
import sortedcontainers
|
2020-02-18 00:15:36 +00:00
|
|
|
|
import logging
|
2019-11-11 09:34:05 +00:00
|
|
|
|
from royalnet.commands import *
|
2020-02-18 00:15:36 +00:00
|
|
|
|
from royalnet.utils import ordinalformat, andformat, sentry_exc
|
2019-11-11 09:34:05 +00:00
|
|
|
|
from ..utils import parse_5etools_entry
|
|
|
|
|
|
|
|
|
|
|
2020-02-18 00:15:36 +00:00
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
2019-11-11 09:34:05 +00:00
|
|
|
|
class DndspellCommand(Command):
|
|
|
|
|
name: str = "dndspell"
|
|
|
|
|
|
|
|
|
|
aliases = ["spell"]
|
|
|
|
|
|
|
|
|
|
description: str = "Ottieni informazioni su una magia di D&D5e."
|
|
|
|
|
|
|
|
|
|
syntax = "{nomemagia}"
|
|
|
|
|
|
|
|
|
|
_dnddata: sortedcontainers.SortedKeyList = None
|
|
|
|
|
|
|
|
|
|
def __init__(self, interface: CommandInterface):
|
|
|
|
|
super().__init__(interface)
|
|
|
|
|
interface.loop.create_task(self._fetch_dnddata())
|
|
|
|
|
|
|
|
|
|
async def _fetch_dnddata(self):
|
|
|
|
|
self._dnddata = self._dnddata = sortedcontainers.SortedKeyList([], key=lambda i: i["name"].lower())
|
|
|
|
|
async with aiohttp.ClientSession() as session:
|
|
|
|
|
for url in [
|
|
|
|
|
"https://5e.tools/data/spells/spells-ai.json",
|
|
|
|
|
"https://5e.tools/data/spells/spells-ggr.json",
|
|
|
|
|
"https://5e.tools/data/spells/spells-phb.json",
|
|
|
|
|
"https://5e.tools/data/spells/spells-scag.json",
|
2020-02-18 00:15:36 +00:00
|
|
|
|
"https://5e.tools/data/spells/spells-xge.json",
|
|
|
|
|
"https://5e.tools/data/spells/spells-ua-frw.json",
|
2019-11-11 09:34:05 +00:00
|
|
|
|
"https://5e.tools/data/spells/spells-stream.json",
|
2020-02-18 00:15:36 +00:00
|
|
|
|
"https://5e.tools/data/spells/spells-llk.json",
|
|
|
|
|
"https://5e.tools/data/spells/spells-ua-saw.json",
|
2019-11-11 09:34:05 +00:00
|
|
|
|
"https://5e.tools/data/spells/spells-ua-mm.json",
|
|
|
|
|
"https://5e.tools/data/spells/spells-ua-ss.json",
|
|
|
|
|
"https://5e.tools/data/spells/spells-ua-tobm.json",
|
2020-02-18 00:15:36 +00:00
|
|
|
|
"https://5e.tools/data/spells/spells-ua-ar.json",
|
2019-11-11 09:34:05 +00:00
|
|
|
|
]:
|
|
|
|
|
async with session.get(url) as response:
|
|
|
|
|
j = await response.json()
|
|
|
|
|
for spell in j["spell"]:
|
|
|
|
|
self._dnddata.add(spell)
|
2020-02-18 00:15:36 +00:00
|
|
|
|
self._test_all()
|
2019-11-11 09:34:05 +00:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _parse_spell(spell: dict) -> str:
|
2020-02-18 13:48:21 +00:00
|
|
|
|
string = [f'✨ [b]{spell["name"]}[/b]\n']
|
2020-02-18 00:15:36 +00:00
|
|
|
|
|
|
|
|
|
# Source (manual, page)
|
2019-11-11 09:34:05 +00:00
|
|
|
|
if "source" in spell:
|
2020-02-18 00:15:36 +00:00
|
|
|
|
if "page" in spell:
|
|
|
|
|
string.append(f'[i]{spell["source"]}, page {spell["page"]}[/i]\n')
|
|
|
|
|
else:
|
|
|
|
|
string.append(f'[i]{spell["source"]}[/i]\n')
|
|
|
|
|
string.append("\n")
|
|
|
|
|
|
|
|
|
|
# Level
|
2020-02-18 13:48:21 +00:00
|
|
|
|
string.append({
|
|
|
|
|
0: "0️⃣ [b]Cantrip[/b]\n",
|
|
|
|
|
1: "1️⃣ [b]1st[/b] level\n",
|
|
|
|
|
2: "2️⃣ [b]2nd[/b] level\n",
|
|
|
|
|
3: "3️⃣ [b]3rd[/b] level\n",
|
|
|
|
|
4: "4️⃣ [b]4th[/b] level\n",
|
|
|
|
|
5: "5️⃣ [b]5th[/b] level\n",
|
|
|
|
|
6: "6️⃣ [b]6th[/b] level\n",
|
|
|
|
|
7: "7️⃣ [b]7th[/b] level\n",
|
|
|
|
|
8: "8️⃣ [b]8th[/b] level\n",
|
|
|
|
|
9: "9️⃣ [b]9th[/b] level\n",
|
|
|
|
|
}[spell["level"]])
|
2020-02-18 00:15:36 +00:00
|
|
|
|
|
|
|
|
|
# School
|
|
|
|
|
string.append({
|
|
|
|
|
"A": "Abjuration",
|
|
|
|
|
"C": "Conjuration",
|
|
|
|
|
"D": "Divination",
|
|
|
|
|
"E": "Enchantment",
|
|
|
|
|
"V": "Evocation",
|
|
|
|
|
"I": "Illusion",
|
|
|
|
|
"N": "Necromancy",
|
|
|
|
|
"P": "Psionic",
|
|
|
|
|
"T": "Transmutation",
|
|
|
|
|
}[spell["school"]])
|
2020-02-18 13:48:21 +00:00
|
|
|
|
string.append("\n\n")
|
2020-02-18 00:15:36 +00:00
|
|
|
|
|
|
|
|
|
# Cast time
|
|
|
|
|
for time in spell.get("time", []):
|
|
|
|
|
string.append(f'Cast time: ⌛️ [b]{time["number"]} {time["unit"]}[/b]\n')
|
|
|
|
|
|
|
|
|
|
# Cast range
|
|
|
|
|
range = spell.get("range")
|
|
|
|
|
if range:
|
|
|
|
|
if range["type"] == "point":
|
|
|
|
|
distance = range["distance"]
|
|
|
|
|
if distance["type"] == "touch":
|
|
|
|
|
string.append("Range: 👉 [b]Touch[/b]\n")
|
|
|
|
|
elif distance["type"] == "self":
|
|
|
|
|
string.append("Range: 👤 [b]Self[/b]\n")
|
|
|
|
|
elif distance["type"] == "sight":
|
|
|
|
|
string.append("Range: 👁 [b]Sight[/b]\n")
|
|
|
|
|
elif distance["type"] == "unlimited":
|
|
|
|
|
string.append("Range: ♾ [b]Unlimited[/b]\n")
|
2019-11-11 09:34:05 +00:00
|
|
|
|
else:
|
2020-02-18 00:15:36 +00:00
|
|
|
|
string.append(f'Range: 🏹 [b]{spell["range"]["distance"]["amount"]}'
|
2020-02-18 13:48:21 +00:00
|
|
|
|
f' {spell["range"]["distance"]["type"]}[/b] (point)\n')
|
|
|
|
|
elif range["type"] == "radius":
|
|
|
|
|
string.append(f'Range: ⭕️ [b]{spell["range"]["distance"]["amount"]}'
|
|
|
|
|
f' {spell["range"]["distance"]["type"]}[/b] (radius)\n')
|
|
|
|
|
elif range["type"] == "sphere":
|
|
|
|
|
string.append(f'Range: 🌕 [b]{spell["range"]["distance"]["amount"]}'
|
|
|
|
|
f' {spell["range"]["distance"]["type"]}[/b] (sphere)\n')
|
|
|
|
|
elif range["type"] == "cone":
|
|
|
|
|
string.append(f'Range: 🍦 [b]{spell["range"]["distance"]["amount"]}'
|
|
|
|
|
f' {spell["range"]["distance"]["type"]}[/b] (cone)\n')
|
|
|
|
|
elif range["type"] == "line":
|
|
|
|
|
string.append(f'Range: ➖ [b]{spell["range"]["distance"]["amount"]}'
|
|
|
|
|
f' {spell["range"]["distance"]["type"]}[/b] (line)\n')
|
|
|
|
|
elif range["type"] == "hemisphere":
|
|
|
|
|
string.append(f'Range: 🌗 [b]{spell["range"]["distance"]["amount"]}'
|
|
|
|
|
f' {spell["range"]["distance"]["type"]}[/b] (hemisphere)\n')
|
|
|
|
|
elif range["type"] == "cube":
|
|
|
|
|
string.append(f'Range: ⬜️ [b]{spell["range"]["distance"]["amount"]}'
|
|
|
|
|
f' {spell["range"]["distance"]["type"]}[/b] (cube)\n')
|
2020-02-18 00:15:36 +00:00
|
|
|
|
elif range["type"] == "special":
|
|
|
|
|
string.append("Range: ⭐️ Special")
|
2020-02-18 13:48:21 +00:00
|
|
|
|
else:
|
|
|
|
|
string.append('Range: ⚠️[b]UNKNOWN[/b]')
|
2020-02-18 00:15:36 +00:00
|
|
|
|
|
|
|
|
|
# Components
|
|
|
|
|
components = spell.get("components")
|
|
|
|
|
if components:
|
|
|
|
|
string.append(f'Components: ')
|
|
|
|
|
if components.get("v", False):
|
|
|
|
|
string.append("👄 [b]Verbal[/b] | ")
|
|
|
|
|
if components.get("s", False):
|
|
|
|
|
string.append("🤙 [b]Somatic[/b] | ")
|
|
|
|
|
if components.get("r", False):
|
|
|
|
|
string.append("©️ [b]Royalty[/b] | ")
|
|
|
|
|
if components.get("m", False):
|
|
|
|
|
if isinstance(components["m"], dict):
|
|
|
|
|
string.append(f'💎 [b]Material[/b] ([i]{spell["components"]["m"]["text"]}[/i]) | ')
|
|
|
|
|
elif isinstance(components["m"], str):
|
|
|
|
|
string.append(f'💎 [b]Material[/b] ([i]{spell["components"]["m"]}[/i]) | ')
|
|
|
|
|
string[-1] = string[-1].replace(" | ", "\n")
|
|
|
|
|
string.append("\n")
|
|
|
|
|
|
|
|
|
|
# Durations
|
|
|
|
|
for duration in spell.get("duration", []):
|
|
|
|
|
if duration["type"] == "timed":
|
|
|
|
|
string.append(f'Duration: 🕒 [b]{duration["duration"]["amount"]} {duration["duration"]["type"]}[/b]')
|
|
|
|
|
elif duration["type"] == "instant":
|
|
|
|
|
string.append('Duration: ☁️ [b]Instantaneous[/b]')
|
|
|
|
|
elif duration["type"] == "special":
|
|
|
|
|
string.append('Duration: ⭐️ [b]Special[/b]')
|
|
|
|
|
elif duration["type"] == "permanent":
|
|
|
|
|
string.append(f"Duration: ♾ [b]Permanent[/b] (ends on {andformat(duration['ends'], final=' or ')})")
|
|
|
|
|
else:
|
|
|
|
|
string.append(f'Duration: ⚠️[b]UNKNOWN[/b]')
|
|
|
|
|
if duration.get("concentration", False):
|
|
|
|
|
string.append(" (requires 🧠 Concentration)")
|
|
|
|
|
string.append("\n")
|
|
|
|
|
|
|
|
|
|
# Extra data
|
|
|
|
|
meta = spell.get("meta")
|
|
|
|
|
if meta:
|
|
|
|
|
if meta.get("ritual", False):
|
|
|
|
|
string.append("🔮 Can be casted as ritual\n")
|
|
|
|
|
string.append("\n")
|
|
|
|
|
|
|
|
|
|
# Text entries
|
2019-11-11 09:34:05 +00:00
|
|
|
|
for entry in spell.get("entries", []):
|
2020-02-18 00:15:36 +00:00
|
|
|
|
string.append(parse_5etools_entry(entry))
|
|
|
|
|
string.append("\n\n")
|
|
|
|
|
|
|
|
|
|
# At an higher level... text entries
|
2019-11-11 09:34:05 +00:00
|
|
|
|
for entry in spell.get("entriesHigherLevel", []):
|
2020-02-18 00:15:36 +00:00
|
|
|
|
string.append(parse_5etools_entry(entry))
|
|
|
|
|
string.append("\n\n")
|
|
|
|
|
|
|
|
|
|
return "".join(string)
|
2019-11-11 09:34:05 +00:00
|
|
|
|
|
|
|
|
|
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
|
|
|
|
if self._dnddata is None:
|
|
|
|
|
await data.reply("⚠️ Il database degli oggetti di D&D non è ancora stato scaricato.")
|
|
|
|
|
return
|
|
|
|
|
search = args.joined().lower()
|
|
|
|
|
result = self._dnddata[self._dnddata.bisect_key_left(search)]
|
|
|
|
|
await data.reply(self._parse_spell(result))
|
2020-02-18 00:15:36 +00:00
|
|
|
|
|
|
|
|
|
def _test_all(self):
|
|
|
|
|
for spell in self._dnddata:
|
|
|
|
|
try:
|
|
|
|
|
log.debug(f"Testing: {spell['name']}")
|
|
|
|
|
self._parse_spell(spell)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error(f"Failed: {spell['name']}")
|
|
|
|
|
sentry_exc(e)
|
|
|
|
|
log.info(f"All spell tests complete!")
|