remove a character, get character status, and the main level up check thread Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
182 lines
8.7 KiB
Python
182 lines
8.7 KiB
Python
"""IRC support for managing and executing the IdleRPG game.
|
|
|
|
SPDX-FileCopyrightText: © 2024 Brian S. Stephan <bss@incorporeal.org>
|
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""
|
|
import logging
|
|
import threading
|
|
import time
|
|
|
|
import irc.client
|
|
from django.db.utils import IntegrityError
|
|
from ircbot.lib import Plugin
|
|
|
|
from idlerpg.models import Character, Game
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class IdleRPG(Plugin):
|
|
"""Run a game of IdleRPG."""
|
|
|
|
SLEEP_BETWEEN_LEVEL_CHECKS = 1
|
|
|
|
LOGIN_COMMAND_PATTERN = r'^LOGIN\s+(?P<name>\S+)\s+(?P<password>\S+)$'
|
|
LOGOUT_COMMAND_PATTERN = r'^LOGOUT$'
|
|
REGISTER_COMMAND_PATTERN = r'^REGISTER\s+(?P<name>\S+)\s+(?P<password>\S+)\s+(?P<char_class>.*)$'
|
|
REMOVEME_COMMAND_PATTERN = r'^REMOVEME$'
|
|
STATUS_COMMAND_PATTERN = r'^(STATUS|WHOAMI)$'
|
|
|
|
def __init__(self, bot, connection, event):
|
|
"""Initialize miscellaneous state stuff."""
|
|
self.seen_hostmasks = set()
|
|
super().__init__(bot, connection, event)
|
|
|
|
def handle_join(self, connection, event):
|
|
"""Track a character as online if their player joins the game channel."""
|
|
self.seen_hostmasks.add(event.source)
|
|
logger.info("Added %s to the set of seen hostmasks.", event.source)
|
|
|
|
def handle_kick_penalty(self, connection, event):
|
|
"""Penalize characters for their user getting kicked from the channel."""
|
|
self.seen_hostmasks.discard(event.source)
|
|
logger.info("Removed %s from the set of seen hostmasks.", event.source)
|
|
return self._handle_generic_penalty_and_logout(event.source, event.target, 250,
|
|
"getting kicked from the game channel",
|
|
'time_penalized_kicked')
|
|
|
|
def handle_nick_penalty(self, connection, event):
|
|
"""Penalize characters for changing their nick while in the channel."""
|
|
# TODO: figure out how to update the character and seen hostmasks
|
|
return self._handle_generic_penalty_and_logout(event.source, event.target, 30,
|
|
"changing their nick",
|
|
'time_penalized_nick_change')
|
|
|
|
def handle_part_penalty(self, connection, event):
|
|
"""Penalize characters for their user parting."""
|
|
self.seen_hostmasks.discard(event.source)
|
|
logger.info("Removed %s from the set of seen hostmasks.", event.source)
|
|
return self._handle_generic_penalty_and_logout(event.source, event.target, 200,
|
|
"parting the game channel",
|
|
'time_penalized_part')
|
|
|
|
def handle_message_penalty(self, connection, event):
|
|
"""Pay attention to messages in the game channel, and penalize talkers."""
|
|
hostmask = event.source
|
|
|
|
logger.debug("looking for %s to try to penalize them", hostmask)
|
|
try:
|
|
character = Character.objects.get(enabled=True, hostmask=hostmask)
|
|
logger.debug("found character %s", character)
|
|
message = event.arguments[0]
|
|
penalty = character.penalize(len(message), "sending a privmsg to the game channel")
|
|
character.time_penalized_privmsg += penalty
|
|
character.save()
|
|
except Character.DoesNotExist:
|
|
logger.debug("no character found for %s", hostmask)
|
|
return
|
|
|
|
def handle_quit_penalty(self, connection, event):
|
|
"""Penalize characters for their user quitting IRC."""
|
|
self.seen_hostmasks.discard(event.source)
|
|
logger.info("Removed %s from the set of seen hostmasks.", event.source)
|
|
return self._handle_generic_penalty_and_logout(event.source, event.target, 20,
|
|
"quitting IRC",
|
|
'time_penalized_quit')
|
|
|
|
def handle_login(self, connection, event, match):
|
|
"""Log in a character when requested, assuming they're online."""
|
|
hostmask = event.source
|
|
|
|
nick = irc.client.NickMask(hostmask).nick
|
|
try:
|
|
character = Character.objects.get(enabled=True, name=match.group('name'))
|
|
character.check_password(match.group('password'))
|
|
except (Character.DoesNotExist, ValueError):
|
|
return self.bot.reply(event, "The requested character does not exist, or has been disabled, "
|
|
"or your password does not match.")
|
|
|
|
if hostmask not in self.seen_hostmasks:
|
|
return self.bot.reply(event, f"Please join {character.game.channel.name} before logging in.")
|
|
|
|
if character.status != Character.CHARACTER_STATUS_OFFLINE:
|
|
return self.bot.reply(event, f"Cannot log in {character.name}, either they already are, "
|
|
"or they are in a state that cannot be modified.")
|
|
|
|
character.log_in(match.group('password'), hostmask)
|
|
character.save()
|
|
self.bot.reply(None, f"{character}, is now online from nickname {nick}. "
|
|
f"Next level at {character.next_level_str()}.",
|
|
explicit_target=character.game.channel.name)
|
|
return self.bot.reply(event, f"{character} has been successfully logged in.")
|
|
|
|
def handle_remove(self, connection, event, match):
|
|
"""Handle the disabling of a character."""
|
|
hostmask = event.source
|
|
try:
|
|
character = Character.objects.get(enabled=True, hostmask=hostmask)
|
|
character.enabled = False
|
|
character.save()
|
|
return self.bot.reply(event, f"Character {character.name} has been disabled.")
|
|
except Character.DoesNotExist:
|
|
return self.bot.reply(event, "No character associated to your hostmask found (try logging in first).")
|
|
|
|
def handle_status(self, connection, event, match):
|
|
"""Handle the request for character/player status."""
|
|
hostmask = event.source
|
|
try:
|
|
character = Character.objects.get(enabled=True, hostmask=hostmask)
|
|
self.bot.reply(event, f"{character}, is {hostmask}.")
|
|
self.bot.reply(event, f"{character.name} is {Character.CHARACTER_STATUSES[character.status]}.")
|
|
if character.status == Character.CHARACTER_STATUS_LOGGED_IN:
|
|
self.bot.reply(event, f"{character.name} will level up on {character.next_level_str()}.")
|
|
except Character.DoesNotExist:
|
|
self.bot.reply(event, "No character associated to your hostmask found (try logging in first).")
|
|
|
|
def _handle_generic_penalty_and_logout(self, hostmask: str, channel: str, penalty: int, reason: str,
|
|
penalty_log_attr: str):
|
|
"""Penalize a character and log them out, for a provided reason.
|
|
|
|
Args:
|
|
hostmask: the hostmask for the character in question
|
|
channel: the game channel the event occurred in
|
|
penalty: the penalty to apply
|
|
reason: the reason for the penalty
|
|
"""
|
|
logger.debug("looking for %s to try to penalize them", hostmask)
|
|
try:
|
|
character = Character.objects.get(enabled=True, hostmask=hostmask)
|
|
logger.debug("character found for %s", hostmask)
|
|
seconds = character.penalize(penalty, reason)
|
|
log = getattr(character, penalty_log_attr)
|
|
setattr(character, penalty_log_attr, log + seconds)
|
|
try:
|
|
character.log_out()
|
|
except ValueError:
|
|
logger.debug("tried to log out %s but they already were", character)
|
|
character.save()
|
|
except Character.DoesNotExist:
|
|
logger.debug("no character found for %s", hostmask)
|
|
|
|
def level_thread(self):
|
|
"""Check for characters who have leveled up, and log as such in the channel."""
|
|
while self.check_for_level_ups:
|
|
time.sleep(self.SLEEP_BETWEEN_LEVEL_CHECKS)
|
|
self._level_up_games()
|
|
|
|
def _level_up_games(self):
|
|
"""Find games under management and level up characters within them, updating their channel."""
|
|
logger.debug("checking for level ups in games")
|
|
for game in Game.objects.filter(active=True):
|
|
logger.debug("checking for level ups in %s", game)
|
|
for character in Character.objects.levelable(game):
|
|
logger.debug("going to try to level up %s", character)
|
|
character.level_up()
|
|
self.bot.reply(None, f"{character.name}, the {character.character_class}, has attained level "
|
|
f"{character.level}! Next level at {character.next_level_str()}.",
|
|
explicit_target=game.channel.name)
|
|
character.save()
|
|
|
|
|
|
plugin = IdleRPG
|