def add_action(action): """Enregistre une action et programme son ouverture le cas échéant. Args: action (.bdd.Action): l'action à enregistrer """ if not action.active: action.active = True action.add() # Ajout tâche ouverture if action.base.trigger_debut == ActionTrigger.temporel: # Temporel : on programme Tache(timestamp=tools.next_occurence(action.base.heure_debut), commande=f"!open {action.id}", action=action).add() elif action.base.trigger_debut == ActionTrigger.perma: # Perma : ON LANCE DIRECT Tache(timestamp=datetime.datetime.now(), commande=f"!open {action.id}", action=action).add()
async def open_action(action): """Ouvre l'action. Args: action (.bdd.Action): l'action à ouvir Opérations réalisées : - Vérification des conditions (cooldown, charges...) et reprogrammation si nécessaire ; - Gestion des tâches planifiées (planifie remind/close si applicable) ; - Information du joueur. """ joueur = action.joueur chan = joueur.private_chan # Vérification base if not action.base: await tools.log(f"{action} : pas de base, exit") return # Vérification active if not action.active: await tools.log(f"{action} : inactive, exit (pas de reprogrammation)") return # Vérification cooldown if action.cooldown > 0: # Action en cooldown action.cooldown = action.cooldown - 1 config.session.commit() await tools.log(f"{action} : en cooldown, exit " "(reprogrammation si temporel).") if action.base.trigger_debut == ActionTrigger.temporel: # Programmation action du lendemain ts = tools.next_occurence(action.base.heure_debut) Tache(timestamp=ts, commande=f"!open {action.id}", action=action).add() return # Vérification role_actif if not joueur.role_actif: # role_actif == False : on reprogramme la tâche au lendemain tanpis await tools.log(f"{action} : role_actif == False, exit " "(reprogrammation si temporel).") if action.base.trigger_debut == ActionTrigger.temporel: ts = tools.next_occurence(action.base.heure_debut) Tache(timestamp=ts, commande=f"!open {action.id}", action=action).add() return # Vérification charges if action.charges == 0: # Plus de charges, mais action maintenue en base car refill / ... await tools.log(f"{action} : plus de charges, exit " "(reprogrammation si temporel).") return # Action "automatiques" (passives : notaire...) : # lance la procédure de clôture / résolution if action.base.trigger_fin == ActionTrigger.auto: if action.base.trigger_debut == ActionTrigger.temporel: await tools.log( f"Action {action.base.slug} pour {joueur.nom} pas vraiment " f"automatique, {config.Role.mj.mention} VENEZ M'AIDER " "JE PANIQUE 😱 (comme je suis vraiment sympa je vous " f"file son chan, {chan.mention})") else: await tools.log( f"{action} : automatique, appel processus de clôture") await close_action(action) return # Tous tests préliminaires n'ont pas return ==> Vraie action à lancer # Calcul heure de fin (si applicable) heure_fin = None if action.base.trigger_fin == ActionTrigger.temporel: heure_fin = action.base.heure_fin ts = tools.next_occurence(heure_fin) elif action.base.trigger_fin == ActionTrigger.delta: # Si delta, on calcule la vraie heure de fin (pas modifié en base) delta = action.base.heure_fin ts = (datetime.datetime.now() + datetime.timedelta( hours=delta.hour, minutes=delta.minute, seconds=delta.second)) heure_fin = ts.time() # Programmation remind / close if action.base.trigger_fin in [ ActionTrigger.temporel, ActionTrigger.delta ]: Tache(timestamp=ts - datetime.timedelta(minutes=30), commande=f"!remind {action.id}", action=action).add() Tache(timestamp=ts, commande=f"!close {action.id}", action=action).add() elif action.base.trigger_fin == ActionTrigger.perma: # Action permanente : fermer pour le WE # ou rappel / réinitialisation chaque jour ts_matin = tools.next_occurence(datetime.time(hour=7)) ts_pause = tools.debut_pause() if ts_matin < ts_pause: # Réopen le lendamain Tache(timestamp=ts_matin, commande=f"!open {action.id}", action=action).add() else: # Sauf si pause d'ici là Tache(timestamp=ts_pause, commande=f"!close {action.id}", action=action).add() # Information du joueur if action.is_open: # déjà ouverte message = await chan.send( f"{tools.montre()} Rappel : tu peux utiliser quand tu le " f"souhaites ton action {tools.code(action.base.slug)} ! " f" {config.Emoji.action} \n" + (f"Tu as jusqu'à {heure_fin} pour le faire. \n" if heure_fin else "") + tools.ital(f"Tape {tools.code('!action (ce que tu veux faire)')}" " ou utilise la réaction pour agir.")) else: # on ouvre ! util = Utilisation(action=action) util.add() util.open() message = await chan.send( f"{tools.montre()} Tu peux maintenant utiliser ton action " f"{tools.code(action.base.slug)} ! {config.Emoji.action} \n" + (f"Tu as jusqu'à {heure_fin} pour le faire. \n" if heure_fin else "") + tools.ital(f"Tape {tools.code('!action (ce que tu veux faire)')}" " ou utilise la réaction pour agir.")) await message.add_reaction(config.Emoji.action) config.session.commit()
async def close_action(action): """Ferme l'action. Args: action (.bdd.Action): l'action à enregistrer Opérations réalisées : - Archivage si nécessaire ; - Gestion des tâches planifiées (planifie prochaine ouverture si applicable) ; - Information du joueur (si charge-- seulement). """ joueur = action.joueur chan = joueur.private_chan # Vérification base if not action.base: await tools.log(f"{action} : pas de base, exit") return # Vérification active if not action.active: await tools.log(f"{action} : inactive, exit") return # Vérification ouverte if not action.is_open: await tools.log(f"{action} : pas ouverte, exit") return deleted = False if not action.is_waiting: # décision prise # Résolution de l'action # (pour l'instant juste charge -= 1 et suppression le cas échéant) if action.charges: action.charges = action.charges - 1 pcs = (" pour cette semaine" if "weekends" in action.base.refill else "") await chan.send(f"Il te reste {action.charges} charge(s){pcs}.") if action.charges == 0 and not action.base.refill: delete_action(action) deleted = True if not deleted: # Si l'action a été faite et a un cooldown, on le met if (not action.is_waiting) and (action.base.base_cooldown > 0): action.cooldown = action.base.base_cooldown action.utilisation_ouverte.close() # Programmation prochaine ouverture if action.base.trigger_debut == ActionTrigger.temporel: ts = tools.next_occurence(action.base.heure_debut) Tache(timestamp=ts, commande=f"!open {action.id}", action=action).add() elif action.base.trigger_debut == ActionTrigger.perma: # Action permanente : ouvrir après le WE ts = tools.fin_pause() Tache(timestamp=ts, commande=f"!open {action.id}", action=action).add() config.session.commit()
async def cparti(self, ctx): """Lance le jeu (COMMANDE MJ) - Programme les votes condamnés quotidiens (avec chaînage) 10h-18h - Programme un vote maire 10h-18h - Programme les actions au lancement du jeu (choix de mentor...) et permanentes (forgeron)... à 19h - Crée les "actions de vote", sans quoi !open plante À utiliser le jour du lancement après 10h (lance les premières actions le soir et les votes le lendemain) """ message = await ctx.send( "C'est parti ?\n" "Les rôles ont bien été attribués et synchronisés ?" " (si non, le faire AVANT de valider)\n\n" "On est bien après 10h le jour du lancement ?\n\n" "Tu es conscient que tous les joueurs reçevront à 18h55 un message" " en mode « happy Hunger Games » ? (codé en dur parce que flemme)") if not await tools.yes_no(message): await ctx.send("Mission aborted.") return message = await ctx.send( "Les actions des joueurs ont été attribuées à la synchronisation " "des rôles, mais les !open n'ont aucun impact tant que tout le " "monde est en `role_actif == False` sur le Tableau de bord.\n" "Il faut donc **passer tout le monde à `True` maintenant**" "(puis `!sync silent`) avant de continuer.") if not await tools.yes_no(message): await ctx.send("Mission aborted.") return taches = [] r = "C'est parti !\n" n10 = tools.next_occurence(datetime.time(hour=10)) n19 = tools.next_occurence(datetime.time(hour=19)) # Programmation votes condamnés chainés 10h-18h r += "\nProgrammation des votes :\n" taches.append(Tache(timestamp=n10, commande="!open cond 18h 10h")) r += " - À 10h : !open cond 18h 10h\n" # Programmation votes loups chainés 19h-23h taches.append(Tache(timestamp=n19, commande="!open loups 23h 19h")) r += " - À 19h : !open loups 23h 19h\n" # Programmation premier vote maire 10h-17h taches.append(Tache(timestamp=n10, commande="!open maire 17h")) r += " - À 10h : !open maire 17h\n" # Programmation actions au lancement et actions permanentes r += "\nProgrammation des actions start / perma :\n" start_perma = Action.query.filter( Action.base.has( BaseAction.trigger_debut.in_( [ActionTrigger.start, ActionTrigger.perma]))).all() for action in start_perma: r += (f" - À 19h : !open {action.id} " f"(trigger_debut == {action.base.trigger_debut})\n") taches.append( Tache(timestamp=n19, commande=f"!open {action.id}", action=action)) # Programmation refill weekends # r += "\nProgrammation des refills weekends :\n" # ts = tools.fin_pause() - datetime.timedelta(minutes=5) # taches.append(Tache(timestamp=ts, # commande=f"!refill weekends all")) # r += " - Dimanche à 18h55 : !refill weekends all\n" # Programmation envoi d'un message aux connards r += ("\nEt, à 18h50 : !send all [message de hype oue oue " "c'est génial]\n") taches.append( Tache( timestamp=(n19 - datetime.timedelta(minutes=10)), commande=("!send all Ah {member.mention}... J'espère que tu " "es prêt(e), parce que la partie commence DANS 10 " " MINUTES !!! https://tenor.com/view/thehungergames-" "hungergames-thggifs-effie-gif-5114734"))) await tools.log(r, code=True) # Drop (éventuel) et (re-)création actions de vote Action.query.filter_by(base=None).delete() actions = [] for joueur in Joueur.query.all(): for vote in Vote: actions.append(Action(joueur=joueur, vote=vote)) Tache.add(*taches) # On enregistre et programme le tout ! Action.add(*actions) await ctx.send( f"C'est tout bon ! (détails dans {config.Channel.logs.mention})")
async def close(self, ctx, qui, heure=None, heure_chain=None): """Ferme un vote / des actions de rôle (COMMANDE BOT / MJ) Args: qui: =========== =========== ``cond`` pour le vote du condamné ``maire`` pour le vote du maire ``loups`` pour le vote des loups ``action`` pour les actions se terminant à ``heure`` ``{id}`` pour une action spécifique =========== =========== heure: - si ``qui == "cond"``, ``"maire"`` ou ``"loup"``, programme en plus une prochaine ouverture à ``heure`` (et un rappel 30 minutes avant) ; - si ``qui == "action"``, il est obligatoire : heure des actions à lancer (cf plus haut). Pour les actions, la prochaine est de toute façon programmée le cas échéant (cooldown à 0 et reste des charges). Dans tous les cas, format ``HHh`` ou ``HHhMM``. heure_chain: permet de chaîner des votes : ferme le vote immédiatement et programme une prochaine ouverture à ``heure``, en appellant ``!close`` de sorte à programmer une nouvelle fermeture le lendemain à ``heure_chain``, et ainsi de suite. Format ``HHh`` ou ``HHhMM``. Une sécurité empêche de fermer un vote ou une action qui n'est pas en cours. Cette commande a pour vocation première d'être exécutée automatiquement par des tâches planifiées. Elle peut être utilisée à la main, mais attention à ne pas faire n'importe quoi (penser à envoyer / planifier la fermeture des votes, par exemple). Examples: - ``!close maire`` : ferme le vote condamné maintenant - ``!close cond 10h`` : ferme le vote condamné maintenant et programme une prochaine ouverture à 10h00 - ``!close cond 10h 18h`` : ferme le vote condamné maintenant, programme une prochaine ouverture à 10h00, qui sera fermé à 18h, puis une nouvelle ouverture à 10h, etc - ``!close action 22h`` : ferme toutes les actions se terminant à 22h00 - ``!close 122`` : ferme l'action d'ID 122 """ try: qui = Vote[qui.lower()] # cond / maire / loups except KeyError: pass joueurs = await recup_joueurs("close", qui, heure) str_joueurs = "\n - ".join([joueur.nom for joueur in joueurs]) await tools.send_code_blocs( ctx, f"Utilisateur(s) répondant aux critères ({len(joueurs)}) : \n" + str_joueurs) # Fermeture utilisations et envoi messages for joueur in joueurs: chan = joueur.private_chan if isinstance(qui, Vote): util = joueur.action_vote(qui).utilisation_ouverte nom_cible = util.cible.nom if util.cible else "*non défini*" util.close() # update direct pour empêcher de voter if qui == Vote.cond: await chan.send( f"{tools.montre()} Fin du vote pour le condamné du jour !" f"\nVote définitif : {nom_cible}\n" f"Les résultats arrivent dans l'heure !\n") elif qui == Vote.maire: await chan.send( f"{tools.montre()} Fin du vote pour le maire ! \n" f"Vote définitif : {nom_cible}") elif qui == Vote.loups: await chan.send( f"{tools.montre()} Fin du vote pour la victime du soir !" f"\nVote définitif : {nom_cible}") else: # Action for action in joueurs[joueur]: await chan.send( f"{tools.montre()} Fin de la possiblité d'utiliser " f"ton action {tools.code(action.base.slug)} ! \n" f"Action définitive : {action.decision}") await gestion_actions.close_action(action) config.session.commit() # Actions déclenchées par fermeture if isinstance(qui, Vote): for action in Action.query.filter( Action.base.has( BaseAction.trigger_debut == ActionTrigger.close(qui))): await gestion_actions.open_action(action) for action in Action.query.filter( Action.base.has( BaseAction.trigger_fin == ActionTrigger.close(qui))): await gestion_actions.close_action(action) # Programme prochaine ouverture if isinstance(qui, Vote) and heure: ts = tools.next_occurence(tools.heure_to_time(heure)) if heure_chain: Tache( timestamp=ts, commande=f"!open {qui.name} {heure_chain} {heure}").add() # Programmera prochaine fermeture else: Tache(timestamp=ts, commande=f"!open {qui.name}").add()
async def open(self, ctx, qui, heure=None, heure_chain=None): """Lance un vote / des actions de rôle (COMMANDE BOT / MJ) Args: qui: =========== =========== ``cond`` pour le vote du condamné ``maire`` pour le vote du maire ``loups`` pour le vote des loups ``action`` pour les actions commençant à ``heure`` ``{id}`` pour une action spécifique =========== =========== heure: - si ``qui == "cond"``, ``"maire"`` ou ``"loup"``, programme en plus la fermeture à ``heure`` (et un rappel 30 minutes avant) ; - si ``qui == "action"``, il est obligatoire : heure des actions à lancer (cf plus haut). Pour les actions, la fermeture est de toute façon programmée le cas échéant (``trigger_fin`` ``temporel`` ou ``delta``). Dans tous les cas, format ``HHh`` ou ``HHhMM``. heure_chain: permet de chaîner des votes : lance le vote immédiatement et programme sa fermeture à ``heure``, en appellant ``!close`` de sorte à programmer une nouvelle ouverture le lendemain à ``heure_chain``, et ainsi de suite. Format ``HHh`` ou ``HHhMM``. Une sécurité empêche de lancer un vote ou une action déjà en cours. Cette commande a pour vocation première d'être exécutée automatiquement par des tâches planifiées. Elle peut être utilisée à la main, mais attention à ne pas faire n'importe quoi (penser à envoyer / planifier la fermeture des votes, par exemple). Examples: - ``!open maire`` : lance un vote condamné maintenant - ``!open cond 19h`` : lance un vote condamné maintenant et programme sa fermeture à 19h00 (ex. Juge Bègue) - ``!open cond 18h 10h`` : lance un vote condamné maintenant, programme sa fermeture à 18h00, et une prochaine ouverture à 10h qui se fermera à 18h, et ainsi de suite - ``!open action 19h`` : lance toutes les actions commençant à 19h00 - ``!open 122`` : lance l'action d'ID 122 """ try: qui = Vote[qui.lower()] # cond / maire / loups except KeyError: pass joueurs = await recup_joueurs("open", qui, heure) # Liste de joueurs (votes) ou dictionnaire joueur : action str_joueurs = "\n - ".join([joueur.nom for joueur in joueurs]) await tools.send_code_blocs( ctx, f"Utilisateur(s) répondant aux critères ({len(joueurs)}) : \n" + str_joueurs) # Création utilisations & envoi messages for joueur in joueurs: chan = joueur.private_chan if isinstance(qui, Vote): action = joueur.action_vote(qui) util = Utilisation(action=action) util.add() util.open() if qui == Vote.cond: message = await chan.send( f"{tools.montre()} Le vote pour le condamné du " f"jour est ouvert ! {config.Emoji.bucher} \n" + (f"Tu as jusqu'à {heure} pour voter. \n" if heure else "" ) + tools.ital(f"Tape {tools.code('!vote (nom du joueur)')}" " ou utilise la réaction pour voter.")) await message.add_reaction(config.Emoji.bucher) elif qui == Vote.maire: message = await chan.send( f"{tools.montre()} Le vote pour l'élection du " f"maire est ouvert ! {config.Emoji.maire} \n" + (f"Tu as jusqu'à {heure} pour voter. \n" if heure else "" ) + tools.ital( f"Tape {tools.code('!votemaire (nom du joueur)')} " "ou utilise la réaction pour voter.")) await message.add_reaction(config.Emoji.maire) elif qui == Vote.loups: message = await chan.send( f"{tools.montre()} Le vote pour la victime de " f"cette nuit est ouvert ! {config.Emoji.lune} \n" + (f"Tu as jusqu'à {heure} pour voter. \n" if heure else "" ) + tools.ital( f"Tape {tools.code('!voteloups (nom du joueur)')} " "ou utilise la réaction pour voter.")) await message.add_reaction(config.Emoji.lune) else: # Action for action in joueurs[joueur]: await gestion_actions.open_action(action) config.session.commit() # Actions déclenchées par ouverture if isinstance(qui, Vote): for action in Action.query.filter( Action.base.has( BaseAction.trigger_debut == ActionTrigger.open(qui))): await gestion_actions.open_action(action) for action in Action.query.filter( Action.base.has( BaseAction.trigger_fin == ActionTrigger.open(qui))): await gestion_actions.close_action(action) # Réinitialise haros/candids items = [] if qui == Vote.cond: items = CandidHaro.query.filter_by(type=CandidHaroType.haro).all() elif qui == Vote.maire: items = CandidHaro.query.filter_by( type=CandidHaroType.candidature).all() if items: CandidHaro.delete(*items) await tools.log(f"!open {qui.name} : haros/candids wiped") await config.Channel.haros.send( f"{config.Emoji.void}\n" * 30 + "Nouveau vote, nouveaux haros !\n" + tools.ital( "Les posts ci-dessus sont invalides pour le vote actuel. " f"Utilisez {tools.code('!haro')} pour en relancer.")) # Programme fermeture if isinstance(qui, Vote) and heure: ts = tools.next_occurence(tools.heure_to_time(heure)) Tache(timestamp=ts - datetime.timedelta(minutes=30), commande=f"!remind {qui.name}").add() if heure_chain: Tache( timestamp=ts, commande=f"!close {qui.name} {heure_chain} {heure}").add() # Programmera prochaine ouverture else: Tache(timestamp=ts, commande=f"!close {qui.name}").add()