def __init__(self, nom, parent=None, modele=None): """Constructeur de la classe""" BaseObj.__init__(self) self.nom = nom self.synonymes = [] self.titre = "un détail aux alentours" self.description = Description(parent=self) self.positions = {} self.est_visible = True self.script = ScriptDetail(self) self.peut_asseoir = False self.peut_allonger = False self.facteur_asseoir = 1.1 self.facteur_allonger = 1.2 self.connecteur = "sur" self.nb_places_assises = 1 self.nb_places_allongees = 1 self.parent = parent self.flags = 0 self.supporte = None self._peut_supporter = 0.0 self.message_supporte = "Dessus se trouve" self.message_installation = "Vous posez %objet sur %detail." self.message_desinstallation = "Vous retirez %objet% de %detail." if modele is not None: self.synonymes = modele.synonymes self.titre = modele.titre self.description = modele.description # On passe le statut en CONSTRUIT self._construire()
def __init__(self, cle): """Constructeur du modèle.""" BaseObj.__init__(self) self.cle = cle self.nom = "un navire" self.vehicules = [] self.salles = {} self.poids_max = 200 self.tirant_eau = 3 self.fond_plat = False self.graph = {} self.m_valeur = 1000 self.duree_construction = 60 self.description = Description(parent=self) self.description_vente = Description(parent=self) self.canot = False self.masculin = True self.peut_conquerir = True self.niveau = 5 self.cale_max = 200 self.facteur_rames = 0.8 self.facteurs_orientations = { "vent debout": -0.3, "au près": 0.5, "bon plein": 0.8, "largue": 1.2, "grand largue": 0.9, "vent arrière": 0.7, } self.descriptions_independantes = False
def __init__(self, parent=None, expediteur=None, source=None): """Constructeur de la classe""" BaseObj.__init__(self) self.parent = parent self.id = -1 self.notifier = True if source is not None: # édition d'un brouillon self._etat = BROUILLON self.sujet = str(source.sujet) self.expediteur = expediteur self.liste_dest = [] for d in list(source.liste_dest): self.liste_dest.append(d) self.aliases = source.aliases self.copies_a = source.copies_a self.contenu = Description(parent=self, scriptable=False) self.contenu.ajouter_paragraphe(str(source.contenu)) self.id_source = int(source.id) else: self._etat = EN_COURS self.sujet = "aucun sujet" self.expediteur = expediteur self.liste_dest = [] self.aliases = [] self.copies_a = [] self.contenu = Description(parent=self) self.id_source = 0 self.destinataire = None self.date = None self.lu = False # On passe le statut en CONSTRUIT self._construire()
def __init__(self, prototype): """Constructeur du matelot.""" BaseObj.__init__(self) self.prototype = prototype self.nom_singulier = "un matelot" self.nom_pluriel = "matelots" self.poste_defaut = "matelot" self.description = Description(parent=self) self.aptitudes = {} self.m_valeur = 20
def __init__(self, cle=""): """Constructeur de l'objet""" BaseType.__init__(self, cle) self.nom_recette = "la recette de quelque chose" self.contenu = Description(parent=self, scriptable=False) # Éditeurs self.etendre_editeur("o", "nom de la recette", Uniligne, self, "nom_recette", 0, LOWER) self.etendre_editeur("c", "contenu de la recette", EdtDesc, self, "contenu")
def __init__(self, cle=""): """Constructeur d'un type""" if cle: valider_cle(cle) BaseObj.__init__(self) self.cle = cle self._attributs = {} self.nom = "un élément inconnu" self.description = Description(parent=self) # Editeur self._extensions_editeur = []
def afficher_apercu(apercu, objet, valeur): if valeur is None: return "" if isinstance(valeur, str): description = Desc(parent=objet, scriptable=False) for paragraphe in valeur.split("\n"): description.ajouter_paragraphe( paragraphe.replace("|nl|", " ")) valeur = description valeur = valeur.paragraphes_indentes return apercu.format(objet=objet, valeur=valeur)
def __init__(self, pere, objet=None, attribut=None): """Constructeur de l'éditeur""" attribut = attribut or "description" Editeur.__init__(self, pere, objet, attribut) self.opts.echp_sp_cars = False self.nom_attribut = attribut contenu = "" if objet: contenu = getattr(self.objet, self.nom_attribut) if contenu is None: setattr(self.objet, self.nom_attribut, "") else: contenu = str(contenu) self.description_complete = Desc(parent=objet, scriptable=False) if contenu: for paragraphe in contenu.split("\n"): self.description_complete.ajouter_paragraphe( paragraphe.replace("|nl|", " ")) self.ajouter_option("?", self.opt_aide) self.ajouter_option("j", self.opt_ajouter_paragraphe) self.ajouter_option("a", self.opt_inserer_paragraphe) self.ajouter_option("d", self.opt_supprimer) self.ajouter_option("r", self.opt_remplacer) self.ajouter_option("e", self.opt_editer_evt) self.ajouter_option("t", self.opt_tabulations) self.ajouter_option("de", self.opt_supprimer_evt) self.ajouter_option("re", self.opt_renommer_evt) self.ajouter_option("o", self.opt_editer_options)
def __init__(self, zone, mnemonic, x=0, y=0, z=0, valide=True): """Constructeur de la salle""" BaseObj.__init__(self) self._nom_zone = zone self._mnemonic = mnemonic self.coords = Coordonnees(x, y, z, valide, self) self.nom_terrain = "ville" self.titre = "" self.description = Description(parent=self) self.sorties = Sorties(parent=self) self.details = Details(parent=self) self._personnages = [] self.objets_sol = ObjetsSol(parent=self) self.script = ScriptSalle(self) self.interieur = False self.illuminee = False self.magasin = None self.flags = 0 # Repop self.pnj_repop = {} # Etendue self.etendue = None # Affections self.affections = {} # Décors self.decors = [] self._construire()
def __init__(self, cle): """Constructeur de la description flottante.""" BaseObj.__init__(self) self.cle = cle self.description = Description(parent=self, scriptable=True) self.details = Details(parent=self) self._construire()
def __init__(self, prototype): BaseObj.__init__(self) self.nom_singulier = "un bonhomme de neige" self.nom_pluriel = "bonhommes de neige" self.etat_singulier = "se tient ici" self.etat_pluriel = "se tiennent ici" self.description = Description(parent=self, scriptable=False) self._construire()
def __init__(self, x, y): """Constructeur du repère.""" BaseObj.__init__(self) self.x = x self.y = y self.nom = "un repère indéfini" self.description = Description(parent=self) self.amplificateur_portee = 1.5
class Recette(BaseType): """Type d'objet : recette de cuisine. Ce type d'objet permet de conserver une recette de cuisine sur un parchemin par exemple. En plus des attributs habituels, la recette de cuisine possède un nom (le ragoût de lapin) et un contenu. """ nom_type = "recette" def __init__(self, cle=""): """Constructeur de l'objet""" BaseType.__init__(self, cle) self.nom_recette = "la recette de quelque chose" self.contenu = Description(parent=self, scriptable=False) # Éditeurs self.etendre_editeur("o", "nom de la recette", Uniligne, self, "nom_recette", 0, LOWER) self.etendre_editeur("c", "contenu de la recette", EdtDesc, self, "contenu") def travailler_enveloppes(self, enveloppes): """Travail sur les enveloppes""" contenu = enveloppes["c"] contenu.aide_courte = \ "| |tit|" + "Contenu de la recette {}".format(self.cle).ljust( 76) + "|ff||\n" + "-" * 79 nom = enveloppes["o"] nom.apercu = "{valeur}" nom.prompt = "Nom de la recette" nom.aide_courte = \ "Entrez |ent|le nom de la recette|ff| ou |cmd|/|ff| pour " \ "revenir à la fenêtre parente.\n\n" \ "Choisissez le nom de la recette avec article (par exemple,\n" \ "|ent|le poulet farci aux chatâignes|ff|).\n\n"\ "Nom actuel : {objet.nom_recette}" def regarder(self, personnage): """Le personnage regarde l'objet.""" prototype = getattr(self, "prototype", self) variables = { "recette": prototype.nom_recette, "RECETTE": prototype.nom_recette.upper(), "Recette": prototype.nom_recette.capitalize(), "contenu": prototype.contenu.regarder(personnage, self), } BaseType.regarder(self, personnage, variables) def get_structure(self, structure): """Retourne la structure étenduee.""" BaseType.get_structure(self, structure) structure.nom_recette = self.nom_recette structure.contenu = self.contenu.regarder(None, self)
def __init__(self, cle): BaseObj.__init__(self) self.cle = cle self.nom_singulier = "une décoration" self.nom_pluriel = "décorations" self.etat_singulier = "se trouve ici" self.etat_pluriel = "se trouvent ici" self.description = Description(parent=self, scriptable=False) self._construire()
def __init__(self, quete): """Constructeur de l'étape.""" BaseObj.__init__(self) self.type = "etape" self.quete = quete self.niveau = () self.titre = "non renseigné" self.description = Description("", self) self.test = None self._construire()
def __init__(self, createur): """Constructeur d'un évènement""" BaseObj.__init__(self) self.id = type(self).id_actuel type(self).id_actuel += 1 self.date = datetime.date.today() self.responsables = [createur] self.titre = "Sans Titre" self.description = Description(parent=self) self.commentaires = []
def __init__(self, nom): """Constructeur d'une race.""" BaseObj.__init__(self) self.nom = nom self.description = Description(parent=self) self.stats = Stats(parent=self) self.genres = Genres(parent=self) self.squelette = None self.flags = 0 self._construire()
def __init__(self, structure, nom): """Constructeur d'un éditeur personnalisé.""" BaseObj.__init__(self) self.structure = structure self.nom = nom self.titre = nom self.description = Description(parent=self, scriptable=False) self.apercu = "$valeur" self.raccourci = None self._construire()
def __init__(self, sujet): """Constructeur de la News Letter.""" BaseObj.__init__(self) self.sujet = sujet self.contenu = Description(parent=self, scriptable=False) self.statut = "brouillon" self.editee = False self.date_creation = datetime.now() self.date_envoi = None self.nombre_envois = 0 self._construire()
def __init__(self, cle): """Constructeur du squelette""" BaseObj.__init__(self) self.cle = cle self.nom = "un squelette" self.description = Description(parent=self) self.__membres = [] self.__groupes = {} # Liste des personnages dont l'équipement dérive de ce squelette self.personnages = [] self._construire()
def __init__(self, nom, cycle): """Constructeur de la période.""" BaseObj.__init__(self) self.nom = nom self.cycle = cycle self.nom_singulier = "une plante" self.nom_pluriel = "plantes" self.description = Description(parent=self) self.fin = (0, 0) self.variation = 0 self.elements = [] self.poids_max = 0 self.visible = True
def __init__(self, cle=""): """Constructeur d'un type""" BaseObj.__init__(self) self.cle = cle self._attributs = {} self.no = 0 # nombre d'objets créés sur ce prototype self.nom_singulier = "un objet indéfini" self.etat_singulier = "est posé là" self.nom_pluriel = "objets indéfinis" self.etat_pluriel = "sont posés là" self.noms_sup = [] self.description = Description(parent=self) self.objets = [] self.unique = True # par défaut tout objet est unique self.flags = 0 self._prix = 1 # valeur en magasin self.sans_prix = False self.poids_unitaire = 1 # 1 Kg self.depecer_de = [] # Equipement self.peut_prendre = True # définit si on peut manipuler l'objet à main self.peut_tenir = False # définit si on peut tenir un objet par-dessus self.emplacement = "" self.epaisseur = 1 self.positions = () # Script self.script = ScriptObjet(self) self.etendre_script() # Editeur self._extensions_editeur = [] # Erreur de validation du type self.err_type = "Le type de '{}' est invalide." self._construire()
def __init__(self, cle, auteur, parent=None, niveau=(1, )): """Constructeur de la quête.""" BaseObj.__init__(self) self.type = "quete" self.cle = cle self.niveau = niveau self.auteur = auteur self.parent = parent self.date_creation = datetime.now() self.titre = "une quelconque quête" self.description = Description(parent=self) self.ordonnee = True self.__etapes = [] self._construire()
def __init__(self, nom, auteur, parent): """Constructeur du canal""" BaseObj.__init__(self) self.nom = nom self.auteur = auteur self.flags = AUCUN_FLAG self.clr = "|cyc|" self.resume = "canal de communication" self.description = Description(parent=parent) self.moderateurs = [] self.immerges = [] self.connectes = [] self.liste_noire = [] self.parent = parent self._construire()
def __init__(self, zone, mnemonic, x=0, y=0, z=0, valide=True): """Constructeur de la salle""" BaseObj.__init__(self) self._nom_zone = zone self._mnemonic = mnemonic self.coords = Coordonnees(x, y, z, valide, self) self.nom_terrain = "ville" self.titre = "" self.description = Description(parent=self) self.sorties = Sorties(parent=self) self.details = Details(parent=self) self._personnages = [] self.objets_sol = ObjetsSol(parent=self) self.script = ScriptSalle(self) self.interieur = False self.illuminee = False self.magasin = None self.flags = 0 self.mod_temperature = 0 # Repop self.pnj_repop = {} # Etendue self.etendue = None # Affections self.affections = {} # Décors self.decors = [] # Propriétaires de la salle (maison, cabine de bateau, etc) self._proprietaires = [] self._construire()
def __init__(self): """Constructeur du joueur""" Personnage.__init__(self) self.nom_groupe = type(self).importeur.joueur.groupe_par_defaut self.compte = None self.instance_connexion = None self.connecte = False self.garder_connecte = False self.afk = "" self.retenus = {} self.distinction_visible = "" self.distinction_audible = "" self.no_tick = 1 self.alias_francais = {} self.alias_anglais = {} self.tips = importeur.information.cfg_info.tips self.creation = datetime.now() self.derniere_connexion = None self.adresse_ip = "inconnue" self.cpt_mort = 0 self.description = Description(parent=self, scriptable=False) self.description_a_valider = Description(parent=self, scriptable=False) self.description_modifiee = False self.pk = False
def __init__(self, guilde, parent, nom_francais, nom_anglais): """Constructeur d'une commande dynamique.""" BaseObj.__init__(self) self.guilde = guilde self.parent = parent self.nom_francais = nom_francais self.nom_anglais = nom_anglais self.commande = None # commande statique liée self._utilisable = False self.doit_etre_membre = True self._groupe = "pnj" self._nom_categorie = "divers" self._aide_courte = "à renseigner..." self.aide_longue = Description(parent=self, scriptable=False, callback="maj") self._schema = "" self.etats = {} self.script = ScriptCommande(self) self._construire()
def __init__(self, cle, parent=None): """Constructeur du sort""" BaseObj.__init__(self) self.parent = parent self.cle = cle self.nom = "sortilège" self.description = Description(parent=self) self.offensif = False self.elements = [] self.stats = [] self.type = "destruction" self._type_cible = "aucune" self.cout = 10 self.duree = 3 self.difficulte = 0 self.distance = False self.points_tribut = 1 self.script = ScriptSort(self) # On passe le statut en CONSTRUIT self._statut = CONSTRUIT
class BaseElement(BaseObj, metaclass=MetaElt): """Classe abstraite représentant le type de base d'un élément. Si des données doivent être communes à tous les types d'éléments c'est dans cette classe qu'elles apparaissent. """ enregistrer = True nom_type = "" # à redéfinir def __init__(self, cle=""): """Constructeur d'un type""" if cle: valider_cle(cle) BaseObj.__init__(self) self.cle = cle self._attributs = {} self.nom = "un élément inconnu" self.description = Description(parent=self) # Editeur self._extensions_editeur = [] def __getnewargs__(self): return () def __str__(self): return self.cle def __getstate__(self): """Retourne le dictionnaire à enregistrer.""" attrs = self.__dict__.copy() if "_extensions_editeur" in attrs: del attrs["_extensions_editeur"] if "_attributs" in attrs: del attrs["_attributs"] return attrs def etendre_editeur(self, raccourci, ligne, editeur, objet, attribut, *sup): """Permet d'étendre l'éditeur d'éléments en fonction du type. Paramètres à entrer : - raccourci le raccourci permettant d'accéder à la ligne - ligne la ligne de l'éditeur (exemple 'Description') - editeur le contexte-éditeur (exemple Uniligne) - objet l'objet à éditer - attribut l'attribut à éditer Cette méthode est appelée lors de la création de l'éditeur d'éléments. """ self._extensions_editeur.append( (raccourci, ligne, editeur, objet, attribut, sup)) def travailler_enveloppes(self, enveloppes): """Travail sur les enveloppes. On récupère un dictionnaire représentant la présentation avec en clé les raccourcis et en valeur les enveloppes. Cela peut permettre de travailler sur les enveloppes ajoutées par 'etendre_editeur'. """ pass def get_description_ligne(self, personnage): """Retourne une description d'une ligne de l'élément.""" return self.nom.capitalize() + " est là" def construire(self, parent): """Construit l'élément basé sur le parent.""" pass def get_nom_pour(self, personnage): """Retourne le nom de l'élément.""" return self.nom def regarder(self, personnage): """personnage regarde self.""" msg = "Vous regardez {} :".format(self.nom) + "\n\n" msg += self.description.regarder(personnage, self) return msg
class BaseElement(BaseObj, metaclass=MetaElt): """Classe abstraite représentant le type de base d'un élément. Si des données doivent être communes à tous les types d'éléments c'est dans cette classe qu'elles apparaissent. """ enregistrer = True nom_type = "" # à redéfinir def __init__(self, cle=""): """Constructeur d'un type""" if cle: valider_cle(cle) BaseObj.__init__(self) self.cle = cle self._attributs = {} self.nom = "un élément inconnu" self.description = Description(parent=self) # Editeur self._extensions_editeur = [] def __getnewargs__(self): return () def __str__(self): return self.cle def __getstate__(self): """Retourne le dictionnaire à enregistrer.""" attrs = self.__dict__.copy() if "_extensions_editeur" in attrs: del attrs["_extensions_editeur"] if "_attributs" in attrs: del attrs["_attributs"] return attrs def editer(self, presentation): """Méthode à rédéfinir, appelée quand l'éditeur s'affiche. Cette méthode est à redéfinir quand on souhaite ajouter des options spécifiques à l'éditeur de l'élément en question. """ pass def get_description_ligne(self, personnage): """Retourne une description d'une ligne de l'élément.""" return self.nom.capitalize() + " est là" def construire(self, parent): """Construit l'élément basé sur le parent.""" pass def get_nom_pour(self, personnage): """Retourne le nom de l'élément.""" return self.nom def regarder(self, personnage): """personnage regarde self.""" msg = "Vous regardez {} :".format(self.nom) + "\n\n" msg += self.description.regarder(personnage, self) return msg
class Detail(BaseObj): """Cette classe représente un détail observable dans une salle. Elle permet d'ajouter à la description d'une salle des détails invisibles au premier abord, mais discernable avec la commande look. """ nom_scripting = "le détail" def __init__(self, nom, parent=None, modele=None): """Constructeur de la classe""" BaseObj.__init__(self) self.nom = nom self.synonymes = [] self.titre = "un détail aux alentours" self.description = Description(parent=self) self.positions = {} self.est_visible = True self.script = ScriptDetail(self) self.peut_asseoir = False self.peut_allonger = False self.facteur_asseoir = 1.1 self.facteur_allonger = 1.2 self.connecteur = "sur" self.nb_places_assises = 1 self.nb_places_allongees = 1 self.parent = parent self.flags = 0 self.supporte = None self._peut_supporter = 0.0 self.message_supporte = "Dessus se trouve" self.message_installation = "Vous posez %objet sur %detail." self.message_desinstallation = "Vous retirez %objet% de %detail." if modele is not None: self.synonymes = modele.synonymes self.titre = modele.titre self.description = modele.description # On passe le statut en CONSTRUIT self._construire() def __getnewargs__(self): return ("", "") def __str__(self): return self.titre.lower() @property def repos(self): return oui_ou_non(self.peut_asseoir or self.peut_allonger) @property def support(self): """Retourne oui_ou_non.""" return oui_ou_non(self._peut_supporter > 0) def _get_peut_supporter(self): return self._peut_supporter def _set_peut_supporter(self, poids): self._peut_supporter = poids if poids: if self.supporte is None: self.supporte = ConteneurObjet(self) else: if self.supporte: self.supporte.detruire() self.supporte = None peut_supporter = property(_get_peut_supporter, _set_peut_supporter) def get_nom_pour(self, personnage): """Retourne le nom pour le personnage précisé.""" return self.titre def a_flag(self, nom_flag): """Retourne True si le détail a le flag, False sinon.""" valeur = FLAGS[nom_flag] return self.flags & valeur != 0 def regarder(self, personnage): """Le personnage regarde le détail""" if not self.est_visible: personnage << "Il n'y a rien qui ressemble à cela par ici..." return personnage << "Vous examinez {} :".format(self.titre) self.script["regarde"]["avant"].executer(personnage=personnage, salle=personnage.salle) description = self.description.regarder(personnage, self) if not description: description = "Il n'y a rien de bien intéressant à voir." personnage << description self.script["regarde"]["apres"].executer(personnage=personnage, salle=personnage.salle)
class Salle(BaseObj): """Classe représentant une salle de l'univers. Une salle est un élément détaillant la géographie locale d'une petite portion de l'univers. Bien que cela dépende des MUDs, les salles décrivent généralement un espace d'environ 5 mètres sur 5. Ces salles comportent une description détaillant les alentours proches. Cette description est envoyée à chaque fois qu'un personnage se déplace dans l'univers, pour lui donner une idée de son nouvel environnement. Note sur le positionnement des salles : Les salles peuvent être caractérisées par des coordonnées. Ces coordonnées sont en soi facultatives. Il est possible de créer un univers sans aucune coordonnée. Il s'agit d'une facilité lors de la constitution de votre univers qui permet à certains modules, comme 'vehicule', de fonctionner. Si votre salle n'a pas de coordonnées, vous devrez créer chaque sortie "à la main". Les salles ne sont donc pas identifiées par leurs coordonnées, sauf dans certains cas, mais bien par leur zone et mnémonique. Ce couple caractérise de façon unique une salle dans l'univers. Exemple : une salle ayant pour zone 'picte' et pour mnémonique '1' sera accessible depuis la clé 'picte:1' ; aucune autre salle de l'univers ne pourra posséder cette clé 'picte:1'. """ nom_scripting = "la salle" _nom = "salle" _version = 4 enregistrer = True def __init__(self, zone, mnemonic, x=0, y=0, z=0, valide=True): """Constructeur de la salle""" BaseObj.__init__(self) self._nom_zone = zone self._mnemonic = mnemonic self.coords = Coordonnees(x, y, z, valide, self) self.nom_terrain = "ville" self.titre = "" self.description = Description(parent=self) self.sorties = Sorties(parent=self) self.details = Details(parent=self) self._personnages = [] self.objets_sol = ObjetsSol(parent=self) self.script = ScriptSalle(self) self.interieur = False self.illuminee = False self.magasin = None self.flags = 0 self.mod_temperature = 0 # Repop self.pnj_repop = {} # Etendue self.etendue = None # Affections self.affections = {} # Décors self.decors = [] # Propriétaires de la salle (maison, cabine de bateau, etc) self._proprietaires = [] self._construire() def __getnewargs__(self): return ("", "") def __repr__(self): """Affichage de la salle en mode debug""" return self._nom_zone + ":" + self._mnemonic def __str__(self): """Retourne l'identifiant 'zone:mnemonic'""" return self._nom_zone + ":" + self._mnemonic def _get_nom_zone(self): return self._nom_zone def _set_nom_zone(self, zone): prochain_ident = zone.lower() + ":" + self._mnemonic autre = importeur.salle.salles.get(prochain_ident) if autre is not None and autre is not self: raise ValueError("L'identifiant {} est déjà utilisé".format( repr(prochain_ident))) ident = self.ident self._nom_zone = zone.lower() type(self).importeur.salle.changer_ident(ident, self.ident) def _get_mnemonic(self): return self._mnemonic def _set_mnemonic(self, mnemonic): prochain_ident = self._nom_zone + ":" + mnemonic.lower() autre = importeur.salle.salles.get(prochain_ident) if autre is not None and autre is not self: raise ValueError("L'identifiant {} est déjà utilisé".format( repr(prochain_ident))) ident = self.ident self._mnemonic = mnemonic.lower() type(self).importeur.salle.changer_ident(ident, self.ident) nom_zone = property(_get_nom_zone, _set_nom_zone) mnemonic = property(_get_mnemonic, _set_mnemonic) @property def zone(self): """Retourne la zone correspondante.""" return type(self).importeur.salle.get_zone(self._nom_zone) @property def ident(self): """Retourne l'identifiant, c'est-à-dire une chaîne 'zone:mnemonic'""" return "{}:{}".format(self._nom_zone, self._mnemonic) @property def personnages(self): """Retourne une liste déférencée des personnages""" return list(self._personnages) @property def PNJ(self): """Retourne une liste déférencée des PNJ présents.""" return [p for p in self._personnages if hasattr(p, "prototype")] @property def joueurs(self): """Retourne une liste déférencée des joueurs présents.""" return [p for p in self._personnages if not hasattr(p, "prototype")] @property def exterieur(self): """Retourne True si la salle est extérieure, False sinon.""" return not self.interieur @property def terrain(self): """Retourne l'objet terrain.""" return importeur.salle.terrains[self.nom_terrain] @property def desc_survol(self): return self.terrain.desc_survol @property def str_coords(self): x, y, z = self.coords.tuple() if self.coords.valide: return "{}.{}.{}".format(x, y, z) return "Aucune" def get_etendue(self): return self.etendue @property def nom_unique(self): return self.ident @property def objets_uniques(self): """Retourne les objets uniques posés dans la salle.""" objets = [] for objet in self.objets_sol._objets: objets.append(objet) objets.extend(objet.prototype.objets_contenus(objet)) return objets @property def a_magasin(self): """Y a-t-il un magasin dans cette salle ?""" return self.magasin is not None @property def nb_sorties(self): """Retourne le mombre de sorties.""" sorties = [s for s in self.sorties if s and s.salle_dest] return len(sorties) @property def details_etendus(self): """Retourne la liste des détails étendus. Cette méthode retourne les détails de la salle et ceux des flottantes contenus dans la description. """ details = self.details._details.copy() description = self.description for paragraphe in description.paragraphes: p, flottantes = description.charger_descriptions_flottantes( paragraphe) for flottante in flottantes: for d in flottante.details: if d.nom not in details: details[d.nom] = d return tuple(details.values()) @property def proprietaires(self): """Retourne les propriétaires.""" return self._proprietaires @property def temperature(self): """Retourne la température de la salle.""" zone = self.zone if self.interieur: temperature = 18 else: temperature = zone.temperature feu = importeur.salle.feux.get(self.ident) if feu: if feu.puissance < 8: temperature += feu.puissance elif feu.puissance < 15: temperature = 20 elif feu.puissance < 40: temperature = 25 elif feu.puissance < 55: temperature = 30 else: temperature = 50 # Modification singulière de la salle temperature += self.mod_temperature # Bonus temporaires temperature += importeur.bonus.get(self, "temperature") return temperature def changer_terrain(self, nouveau_terrain): """Change le terrain de la salle.""" nouveau_terrain = supprimer_accents(nouveau_terrain).lower() for terrain in importeur.salle.terrains.keys(): sa_terrain = supprimer_accents(terrain).lower() if sa_terrain == nouveau_terrain: self.nom_terrain = terrain return raise ValueError("terrain {} inconnu".format(repr(nouveau_terrain))) def voit_ici(self, personnage): """Retourne True si le personnage peut voir ici, False sinon. Un personnage peut voir dans la salle si : Il est immortel La salle est illuminée Le personnage est un PNJ nyctalope Le personnage est un PNJ contrôlé par un immortel Il y a un feu dans la salle La race du personnage est nyctalope Le personnage a une affection qui lui permet de voir dans le noir La salle est en extérieure et : Il fait jour ou Le ciel est dégagé Une lumière est posée sur le sol Le personnage a une lumière """ if personnage.est_immortel(): return True if self.illuminee: return True if hasattr(personnage, "prototype") and \ (personnage.prototype.a_flag("nyctalope") or \ personnage.controle_par): return True if self.ident in importeur.salle.feux: return True # Vérification de la race if personnage.race and personnage.race.a_flag("nyctalope"): return True # Vérification de l'affection for affection in personnage.affections.values(): if affection.affection.a_flag("voit dans le noir"): return True if not self.interieur: if importeur.temps.temps.il_fait_jour: return True perturbation = importeur.meteo.salles.get(self) if perturbation is None or not perturbation.est_opaque(): return True # Vérification des lumières au sol for objet in self.objets_sol: if objet.est_de_type("lumière") and objet.allumee_depuis: return True # Vérification des lumières équipées for objet in personnage.equipement.equipes: if objet.est_de_type("lumière") and objet.allumee_depuis: return True return False def a_detail_flag(self, flag): """Retourne True si la salle a un détail du flag indiqué.""" for detail in self.details: if detail.a_flag(flag): return True description = self.description for paragraphe in description.paragraphes: p, flottantes = description.charger_descriptions_flottantes( paragraphe) for flottante in flottantes: for d in flottante.details: if d.a_flag(flag): return True return False def personnage_est_present(self, personnage): """Si le personnage est présent, retourne True, False sinon.""" return personnage in self._personnages def a_flag(self, nom_flag): """Retourne True si la salle a le flag, False sinon.""" valeur = FLAGS[nom_flag] return self.flags & valeur != 0 def ajouter_personnage(self, personnage): """Ajoute le personnage dans la salle""" if personnage not in self._personnages: self._personnages.append(personnage) def retirer_personnage(self, personnage): """Retire le personnage des personnages présents""" if personnage in self.personnages: self._personnages.remove(personnage) def salles_autour(self, rayon=5): """Retourne les chemins autour de self dans le rayon précisé. Si la salle a des coordonnées valide, ajoute également les salles semblant proches. Cependant, pour celles-ci, presque aucune vérification de chemin n'est faite, c'est-à-dire qu'elles peuvent être entièrement inaccessible. """ if self.accepte_discontinu(): empruntable = False else: empruntable = True return Chemins.salles_autour(self, rayon, empruntable=empruntable) def trouver_chemin_absolu(self, destination, rayon=5, explicite=False): """Retourne, si trouvé, le chemin menant à destination ou None. Le rayon passé en argument est celui de recherche. Plus il est élevé, plus le temps de calcul risque d'être important. """ chemins = Chemins.salles_autour(self, rayon, absolu=True) chemin = chemins.get(destination) if chemin is None and explicite: raise ValueError("le chemin absolu est introuvable") return chemin def trouver_chemin(self, destination, explicite=False): """Recherche et retourne le chemin entre deux salles. Plusieurs algorithmes de recherche sont utilisés en fonction des situations. Pour des raisons de performance, certains sont plus efficaces que d'autres. Avant tout, plusieurs chemins peuvent être retournés : * Un chemin continue (toutes les salles entre deux points) * Un chemin discontinu (ou brisé). La grande différence est qu'un personnage peut emprunter un chemin continu pour aller d'une salle à une autre, mais ne peut pas emprunter un chemin discontinu. Les chemins discontinus ne sont pas tolérés par défaut (ceci est réglable). La recherche du chemin (continu ou discontinu) fait ensuite appel à deux algorithmes : * Si les coordonnées des deux salles sont valides, cherche le chemin relatifs (Chemin.salles_entre) * Sinon, cherche le chemin absolu (toutes les salles autour de la première salle dans un rayon d'estime). Si explicite est mis à True, lève une exception ValueError décrivant pourquoi la recherche a échouée. """ # Si l'une des salles n'a pas de coordonnées valide if self.coords.invalide or destination.coords.invalide: return self.trouver_chemin_absolu(destination, 4, explicite=explicite) # Les deux salles ont des coordonnées valides # On vérifie que le rayon n'es tpas trop important v_origine = Vecteur(*self.coords.tuple()) v_destination = Vecteur(*destination.coords.tuple()) distance = (v_destination - v_origine).norme if distance > 25: if explicite: raise ValueError("la distance entre les deux salles " \ "est supérieure à 25") return None salles = Chemins.get_salles_entre(self, destination) # On détermine, si possible, le chemin entre chaque salle chemin = Chemin() a_salle = None continu = True for d_salle in salles: if a_salle is None: a_salle = d_salle continue d_chemin = a_salle.trouver_chemin_absolu(d_salle, 2) if d_chemin is None: continu = False vecteur = Vecteur(*d_salle.coords.tuple()) - \ Vecteur(*a_salle.coords.tuple()) sortie = a_salle.get_sortie(vecteur, d_salle) chemin.sorties.append(sortie) else: chemin.sorties.extend(d_chemin.sorties) a_salle = d_salle if chemin.origine is not self or chemin.destination is not destination: if explicite: raise ValueError("le chemin retourné ne commence ou " \ "ne finit pas au bon endroit") return None if not continu and (not self.accepte_discontinu() or not \ destination.accepte_discontinu()): if explicite: raise ValueError("un chemin non continu a été retourné") return None chemin.raccourcir() return chemin def accepte_discontinu(self): """Retourne True si cette salle supporte les chemins discontinu. Tsunami supporte ce type de chemin si la salle est une côte de l'étendue. """ return self.etendue is not None def trouver_chemins_droits(self, rayon, dir_sortie=None, chemins=None, chemin=None, hook=True): """Cherche les chemins droits d'une salle. La recherche de salle peut être étendue par un hook. Les chemins droits veulent dire que l'on vérifie les sorties initiales. Par exemple, si la salle A a la sortie est menant vers B, on demande à la salle B ses sorties... mais on ne sélectionnera que les sorties de même direcition que la salle A (c'est-à-dire est ici). """ chemins = chemins or Chemins() chemin = chemin or Chemin() # On parcourt les sorties de la salle for sortie in self.sorties: if dir_sortie is None or sortie.direction == dir_sortie: n_chemin = Chemin() n_chemin.sorties.extend(chemin.sorties) n_chemin.sorties.append(sortie) origine = n_chemin.origine destination = n_chemin.destination ancien_chemin = chemins.get(destination) if origine is not destination and (ancien_chemin is None or \ len(ancien_chemin) > len(n_chemin)): # On retire l'ancien chemin si besoin if ancien_chemin: chemins.chemins.remove(ancien_chemin) chemins.chemins.append(n_chemin) if rayon > 1: destination.trouver_chemins_droits( rayon - 1, sortie.direction, chemins, n_chemin, False) if hook: importeur.hook["salle:trouver_chemins_droits"].executer( self, chemins, rayon) return chemins def get_sortie(self, vecteur, destination): """Retourne une sortie en fonction du vecteur donné.""" sortie = Sortie(vecteur.nom_direction, vecteur.nom_direction, "le", destination, "", self) sortie.longueur = vecteur.norme return sortie def sortie_empruntable(self, sortie): """Retourne True si la sortie est empruntable, False sinon. Pour une salle standard, une sortie est empruntable si elle existe réellement. """ return sortie.direction in self.sorties def envoyer(self, message, *personnages, prompt=True, mort=False, ignore=True, lisser=False, **kw_personnages): """Envoie le message aux personnages présents dans la salle. Les personnages dans les paramètres supplémentaires (nommés ou non) sont utilisés pour formatter le message et font figure d'exceptions. Ils ne recevront pas le message. """ exceptions = personnages + tuple(kw_personnages.values()) if ignore \ else () for personnage in self.personnages: if personnage not in exceptions: if personnage.est_mort() and not mort: continue if hasattr(personnage, "instance_connexion") and \ personnage.instance_connexion and not prompt: personnage.instance_connexion.sans_prompt() personnage.envoyer(message, *personnages, lisser=lisser, **kw_personnages) def envoyer_lisser(self, chaine, *personnages, **kw_personnages): """Méthode redirigeant vers envoyer mais lissant la chaîne.""" self.envoyer(chaine, *personnages, lisser=True, **kw_personnages) def get_elements_observables(self, personnage): """Retourne une liste des éléments observables dans cette salle.""" liste = [] for methode in importeur.salle.details_dynamiques: liste.extend(methode(self, personnage)) return liste def regarder(self, personnage): """Le personnage regarde la salle""" if personnage.est_mort(): personnage << "|err|Vous êtes inconscient et ne voyez pas " \ "grand chose...|ff|" return if not self.voit_ici(personnage): personnage << "Il y fait trop sombre pour vos sens, " \ "l'obscurité vous environne." return res = "" if personnage.est_immortel(): res += "# |rgc|" + self.nom_zone + "|ff|:|vrc|" + self.mnemonic res += "|ff| ({})".format(self.coords) res += "\n\n" res += " |tit|" + (self.titre or "Une salle sans titre") + "|ff|\n" description = self.description.regarder(personnage, self) if not description: description = " Vous êtes au milieu de nulle part." res += description + "\n" res_decors = [] for nom in self.regrouper_decors(): res_decors.append(nom.capitalize() + ".") if res_decors: res += "\n".join(res_decors) + "\n" plus = self.decrire_plus(personnage) if plus: res += plus + "\n" res_affections = [] for affection in self.affections.values(): message = affection.affection.message(affection) if message: res_affections.append(message) if res_affections: res += "\n".join(res_affections) + "\n" liste_messages = [] flags = 0 type(self).importeur.hook["salle:regarder"].executer( self, liste_messages, flags) if liste_messages: res += "\n".join(liste_messages) + "\n" res += "\nSorties : " res += self.afficher_sorties(personnage) # Personnages personnages = OrderedDict() # Si le personnage est un joueur, il se retrouve avec un nombre de 1 # Si le personnage est un PNJ, on conserve son prototype avec # le nombre d'occurences de prototypes apparaissant etats = {} for personne in self.personnages: if personne is not personnage: if not hasattr(personne, "prototype"): if personnage.peut_voir(personne): personnages[personne] = 1 else: doit = importeur.hook["pnj:doit_afficher"].executer( personne) if any(not flag for flag in doit): continue nom = personne.get_nom_etat(personnage) if nom in etats: prototype = etats[nom] personnages[prototype] = \ personnages[prototype] + 1 else: etats[nom] = personne personnages[personne] = 1 if len(personnages): res += "\n" for personne, nombre in personnages.items(): res += "\n- {}".format( personne.get_nom_etat(personnage, nombre)) # Objets noms_objets = self.afficher_noms_objets() if len(noms_objets): res += "\n" for nom_objet in noms_objets: res += "\n+ {}".format(nom_objet) return res def afficher_sorties(self, personnage): """Affiche les sorties de la salle""" noms = [] for nom in NOMS_SORTIES.keys(): sortie = self.sorties[nom] if sortie: nom = sortie.nom nom_aff = self.sorties.get_nom_abrege(nom) if self.sorties.sortie_existe(nom): if sortie.porte and sortie.porte.fermee: res = "[|rgc|" + nom_aff + "|ff|]" else: res = "|vr|" + nom_aff + "|ff|" if sortie.cachee: if personnage.est_immortel(): res = "|rg|(I)|ff|" + res else: res = " ".ljust( len(self.sorties.get_nom_abrege(sortie.direction))) else: res = " ".ljust(len(nom_aff)) noms.append(res) return ", ".join(noms) + "." def afficher_noms_objets(self): """Retourne les noms et états des objets sur le sol de la salle""" objets = [] for o, nb in self.objets_sol.get_objets_par_nom(): objets.append(o.get_nom_etat(nb)) return objets def decrire_plus(self, personnage): """Ajoute un message au-dessous de la description. Si cette méthode retourne une chaîne non vide, alors cette chaîne sera ajoutée sous la description quand un personnage regardera la salle. """ pass def pop_pnj(self, pnj): """Méthode appelée quand un PNJ pop dans la salle.""" pro = pnj.prototype if pro in self.pnj_repop: self.pnj_repop[pro] = self.pnj_repop[pro] - 1 def det_pnj(self, pnj): """Méthode appelée quand un PNJ disparaît..""" pro = pnj.prototype if pro in self.pnj_repop: self.pnj_repop[pro] = self.pnj_repop[pro] + 1 def repop(self): """Méthode appelée à chaque repop.""" for pro, nb in self.pnj_repop.items(): if nb > 0: for i in range(nb): pnj = importeur.pnj.creer_PNJ(pro, self) pnj.script["repop"].executer(pnj=pnj) def affecte(self, cle, duree, force): """Affecte la salle avec une affection. Si l'affection est déjà présente, la force est modulée. """ affection = importeur.affection.get_affection("salle", cle) if cle in self.affections: concrete = self.affections[cle] affection.moduler(concrete, duree, force) else: concrete = Affection(affection, self, duree, force) self.affections[cle] = concrete concrete.affection.executer_script("cree", concrete) concrete.prevoir_tick() def peut_affecter(self, cle_affection): """La salle self peut-elle être affectée par l'affection ?""" if cle_affection == "neige": if self.interieur: return False elif self.nom_terrain in ["aquatique", "subaquatique"]: return False sortie = self.sorties["bas"] if sortie: return False return True def tick(self): """Méthode appelée à chaque tick de la salle.""" for affection in self.affections.values(): affection.affection.dec_duree(affection) for cle, affection in list(self.affections.items()): if not affection.e_existe or affection.duree <= 0: del self.affections[cle] self.script["tick"].executer(salle=self) def regrouper_decors(self): """Regroupe les décors par nom.""" decors = OrderedDict() nombres = OrderedDict() res = OrderedDict() for decor in self.decors: nom = decor.get_nom() decors[nom] = decor nb = nombres.get(nom, 0) nb += 1 nombres[nom] = nb for nom, nb in nombres.items(): decor = decors[nom] nom = decor.get_nom_etat(nb) res[nom] = decor return res def ajouter_decor(self, prototype): """Ajoute un décor dans la salle.""" if isinstance(prototype, PrototypeBonhommeNeige): decor = BonhommeNeige(prototype, self) else: decor = Decor(prototype, self) self.decors.append(decor) return decor def get_decors(self, cle): """Retourne tous les décors ayant la clé indiquée.""" return [d for d in self.decors if d.prototype and \ d.prototype.cle == cle] def supprimer_decor(self, decor): """Supprime les décors indiqués.""" self.decors.remove(decor) decor.detruire() def supprimer_decors(self, cle): """Supprime les décors de clé indiquée.""" for decor in self.get_decors(cle): self.supprimer_decor(decor) def supprimer_bonhommes_neige(self): """Supprime les bonhommes de neige présents.""" for decor in list(self.decors): if isinstance(decor, BonhommeNeige): self.supprimer_decor(decor) self.envoyer("{} se met à fondre rapidement.".format( decor.get_nom().capitalize())) def get_structure(self): """Retourne la structure de la salle.""" structure = StructureSimple() structure.zone = self._nom_zone structure.mnemonique = self._mnemonic structure.terrain = self.nom_terrain structure.titre = self.titre structure.interieur = Fraction(self.interieur) structure.illuminee = Fraction(self.illuminee) structure.proprietaires = list(self.proprietaires) # Coordonnées structure.coordonnees = Fraction(self.coords.valide) structure.x = Fraction(self.coords.x) structure.y = Fraction(self.coords.y) structure.z = Fraction(self.coords.z) structure.temperature = Fraction(self.temperature) structure.mod_temperature = Fraction(self.mod_temperature) structure.bonus_temperature = Fraction( importeur.bonus.get(self, "temperature", precision=1)) # Flags flags = [] for nom, valeur in FLAGS.items(): if self.flags & valeur != 0: flags.append(nom) structure.flags = flags return structure def appliquer_structure(self, structure): """Applique la structure passée en paramètre.""" for cle, valeur in structure.donnees.items(): if cle == "zone": self.nom_zone = str(valeur) elif cle == "mnemonique": self.mnemonic = str(valeur) elif cle == "terrain": self.changer_terrain(str(valeur)) elif cle == "titre": self.titre = str(valeur) elif cle == "interieur": self.interieur = bool(valeur) elif cle == "illuminee": self.illuminee = bool(valeur) elif cle == "proprietaires": proprietaires = [p for p in valeur if p] self.proprietaires[:] = proprietaires elif cle == "coordonnees": self.coords.valide = bool(valeur) elif cle == "x": self.coords.x = int(valeur) elif cle == "y": self.coords.y = int(valeur) elif cle == "z": self.coords.z = int(valeur) elif cle == "mod_temperature": self.mod_temperature = int(valeur) elif cle == "flags": self.flags = 0 for nom, val in FLAGS.items(): if nom in valeur: self.flags += val
class Repere(BaseObj): """Classe représentant un repère dans une étendue d'eau. Les repères sont des détails généralement visibles à distance, des phares, de très hautes montagnes. Ils sont caractérisés par une position en 2D (x;y), un amplificateur de portée (qui détermine à quelle distance ils sont visibles) et plusieurs informations concernant comment ils sont affichés. """ enregistrer = True def __init__(self, x, y): """Constructeur du repère.""" BaseObj.__init__(self) self.x = x self.y = y self.nom = "un repère indéfini" self.description = Description(parent=self) self.amplificateur_portee = 1.5 def __getnewargs__(self): return (0, 0) def __repr__(self): return "<Repère {}>".format(repr(self.nom)) def __str__(self): return self.nom @property def desc_survol(self): return self.nom def get_nom_pour(self, personnage): return self.nom def regarder(self, personnage): """Le personnage regarde le repère.""" salle = personnage.salle portee = get_portee(salle) visible = Visible.observer(personnage, portee, 5) points = [couple[1][1] for couple in visible.points.items()] if self not in points: personnage << "|err|Vous ne pouvez voir cela d'ici.|ff|" return salle.envoyer("{{}} regarde {}.".format(self.nom.lower()), personnage) msg = "Vous regardez " + self.nom + " :\n\n" msg += self.description.regarder(personnage, self) personnage << msg @staticmethod def trouver_reperes(visible, personnage, portee, precision, exceptions=None): """Cherche les repères visibles dans l'étendue.""" navire = personnage.salle.navire etendue = navire.etendue altitude = etendue.altitude nav_direction = navire.direction.direction pos_coords = personnage.salle.coords.tuple() x, y, z = pos_coords position = Vector(x, y, altitude) for t_coords, repere in importeur.navigation.reperes.items(): t_x, t_y = t_coords # On cherche l'angle entre la position du navire et du point v_point = Vector(t_x, t_y, altitude) v_dist = v_point - position if v_dist.mag > portee * repere.amplificateur_portee: continue direction = get_direction(v_dist) r_direction = (direction - navire.direction.direction) % 360 # On détermine l'angle minimum fonction de la précision angle = norme_angle(round(r_direction / precision) * precision) visible.entrer_point(angle, v_dist, repere, nav_direction, precision, exceptions=exceptions)
class BaseType(BaseObj, metaclass=MetaType): """Classe abstraite représentant le type de base d'un objet. Si des données doivent être communes à tous les types d'objet (un objet a un nom, une description, quelque soit son type) c'est dans cette classe qu'elles apparaissent. Notons les attributs d'objet : empilable_sur -- une liste de chaînes définissant les types sur lesquels on peut empiler le type d'objet empilable_sous -- une liste de chaînes identiques mais désignant les types d'objets qui peuvent être empilés par-dessus le type défini. On évitera d'utiliser cet attribut sauf si le type d'objet est défini dans un module secondaire """ nom_type = "" # à redéfinir nom_scripting = "l'objet" type_achat = "objet" _nom = "base_type_objet" _version = 3 # Doit-t-on nettoyer l'objet en cas d'inactivité nettoyer = True # Type d'objet sélectable dans le oedit selectable = True # Types enfants types = {} enregistrer = True # Équipement empilable_sur = [] empilable_sous = [] protection_froid = 0 def __init__(self, cle=""): """Constructeur d'un type""" BaseObj.__init__(self) self.cle = cle self._attributs = {} self.no = 0 # nombre d'objets créés sur ce prototype self.nom_singulier = "un objet indéfini" self.etat_singulier = "est posé là" self.nom_pluriel = "objets indéfinis" self.etat_pluriel = "sont posés là" self.noms_sup = [] self.description = Description(parent=self) self.objets = [] self.unique = True # par défaut tout objet est unique self.flags = 0 self._prix = 1 # valeur en magasin self.sans_prix = False self.poids_unitaire = 1 # 1 Kg self.depecer_de = [] # Equipement self.peut_prendre = True # définit si on peut manipuler l'objet à main self.peut_tenir = False # définit si on peut tenir un objet par-dessus self.emplacement = "" self.epaisseur = 1 self.positions = () # Script self.script = ScriptObjet(self) self.etendre_script() # Editeur self._extensions_editeur = [] # Erreur de validation du type self.err_type = "Le type de '{}' est invalide." self._construire() def __getnewargs__(self): return () def __repr__(self): return "<{} {}>".format(self.nom_type, self.cle) def __str__(self): return self.cle def __getstate__(self): """Retourne le dictionnaire à enregistrer.""" attrs = self.__dict__.copy() if "_extensions_editeur" in attrs: del attrs["_extensions_editeur"] if "_attributs" in attrs: del attrs["_attributs"] return attrs def _get_prix(self): """Retourne le prix""" return self._prix def _set_prix(self, prix): """Modifie le prix""" self._prix = int(prix) prix = property(_get_prix, _set_prix) @property def m_valeur(self): return self._prix @property def nom_achat(self): return self.nom_singulier @property def poids(self): """Retourne le poids unitaire.""" return self.poids_unitaire def etendre_script(self): """Méthode appelée pour étendre le scripting. Si une classe-fille la surcharge, elle peut ajouter des évènements au script de ce type d'objet, par exemple. """ pass def etendre_editeur(self, raccourci, ligne, editeur, objet, attribut, *sup): """Permet d'étendre l'éditeur d'objet en fonction du type. Paramètres à entrer : - raccourci le raccourci permettant d'accéder à la ligne - ligne la ligne de l'éditeur (exemple 'Description') - editeur le contexte-éditeur (exemple Uniligne) - objet l'objet à éditer - attribut l'attribut à éditer Cette méthode est appelée lors de la création de l'éditeur de prototype. """ self._extensions_editeur.append( (raccourci, ligne, editeur, objet, attribut, sup)) def reduire_editeur(self, raccourci): """Permet de supprimer un contexte-éditeur de la liste d'extensions.""" sup = () for editeur in self._extensions_editeur: if editeur[0] == raccourci: sup = editeur break if sup: self._extensions_editeur.remove(sup) def travailler_enveloppes(self, enveloppes): """Travail sur les enveloppes. On récupère un dictionnaire représentant la présentation avec en clé les raccourcis et en valeur les enveloppes. Cela peut permettre de travailler sur les enveloppes ajoutées par 'etendre_editeur'. """ pass def changer_type(self, nouveau_type): """Cette méthode force le changement de type du prototype. ATTENTION : cette méthode change le type en changeant '__class__' et performent certaines opérations automatiques. Il y a des conséquences à changer une instance de classe comme cela, et cela ne devrait pas se produire fréquemment ni automatiquement. """ classe = importeur.objet.get_type(nouveau_type) self.__class__ = classe # On force le prototype d'objet à réinitialiser ses attributs manquants del self._extensions_editeur self.__setstate__(self.__dict__.copy()) # Réinitialise les attributs for objet in self.objets: for nom, val in self._attributs.items(): if not hasattr(objet, nom): setattr(objet, nom, val.construire(self)) def get_nom(self, nombre=1, pluriels=True): """Retourne le nom complet en fonction du nombre. Par exemple : Si nombre == 1 : retourne le nom singulier Sinon : retourne le nombre et le nom pluriel """ if nombre <= 0: raise ValueError("la fonction get_nom a été appelée " \ "avec un nombre négatif ou nul.") elif nombre == 1: return self.nom_singulier else: if pluriels and self.noms_sup: noms_sup = list(self.noms_sup) noms_sup.reverse() for nom in noms_sup: if nombre >= nom[0]: variables = {} variables["nb"] = nombre for i in range(2, 1 + int(sqrt(nombre))): variables["nb{}".format(i)] = nombre // i return nom[1].format(nb=nombre, nb2=nombre // 2, nb4=nombre // 4, nb8=nombre // 8, nb16=nombre // 16) return str(nombre) + " " + self.nom_pluriel def get_nom_pour(self, personnage): """Retourne le nom pour le personnage précisé.""" return self.get_nom() def get_nom_etat(self, nombre): """Retourne le nom et l'état en fonction du nombre.""" nom = self.get_nom(nombre) if nombre == 1: return nom + " " + self.etat_singulier else: if self.noms_sup: noms_sup = list(self.noms_sup) noms_sup.reverse() for nom_sup in noms_sup: if nombre >= nom_sup[0]: return nom + " " + nom_sup[2] return nom + " " + self.etat_pluriel def extraire_contenus(self, quantite=None, contenu_dans=None): """Méthode redéfinie pour la manipulation d'objets non uniques.""" return [self] def extraire_contenus_qtt(self): """Méthode redéfinie pour la manipulation d'objets non uniques.""" return [(self, 1)] def est_de_type(self, nom_type): """Retourne True si le type d'objet est de celui entré ou dérivé. Par exemple, si on test si une épée est une arme, retournera True car le type 'arme' a pour classes-filles 'épée' (notamment). """ classe = importeur.objet.types[nom_type] prototype = hasattr(self, "prototype") and self.prototype or self return isinstance(prototype, classe) def calculer_poids(self): """Retourne le poids de l'objet.""" return self.poids_unitaire def objets_contenus(self, objet): """Retourne les objets contenus.""" return [] def detruire_objet(self, objet): """Détruit l'objet passé en paramètre. Par défaut cette méthode ne fait rien, mais si le type est fait pour contenir d'autres objets, il doit les détruire. """ pass # Actions sur les objets def acheter(self, quantite, magasin, transaction): """Achète les objets dans la quantité spécifiée.""" salle = magasin.parent objets = [] for i in range(quantite): objet = importeur.objet.creer_objet(self) salle.objets_sol.ajouter(objet) objets.append(objet) return objets def peut_vendre(self, vendeur): """Retourne True si peut vendre l'objet.""" return True def estimer_valeur(self, magasin, vendeur): """Estime la valeur d'un objet.""" valeur = self.m_valeur return valeur * 0.7 def peut_ramasser(self): """Essaye de ramasser l'objet.""" return self.peut_prendre def regarder(self, personnage, variables=None): """Le personnage regarde l'objet""" salle = personnage.salle variables = variables or {} personnage << "Vous regardez {} :".format(self.get_nom()) autre = "{{}} regarde {}.".format(self.get_nom()) salle.envoyer(autre, personnage) # Appel du script regarde.avant self.script["regarde"]["avant"].executer( objet=self, personnage=personnage) description = self.description.regarder(personnage, self, variables) if not description: description = "Il n'y a rien de bien intéressant à voir." personnage << description # Appel du script regarde.après self.script["regarde"]["apres"].executer( objet=self, personnage=personnage) return "" def veut_jeter(self, personnage, sur): """Méthode appelée pour tester si le personnage peut jeter l'objet. On doit préciser : personnage -- le personnage voulant jeter l'objet sur -- sur quoi veut-il jeter l'objet ? Le dernier paramètre peut être n'importe quel élément observable (un autre objet, un autre personnage...). La méthode doit retourner : Une chaîne vide si l'objet ne peut pas être lancé Un nom de méthode à appeler si l'objet peut être lancé """ return "" def jeter(self, personnage, sur): """Jette self sur sur. Les paramètres sont les mêmes que veut_jeter. On retourne : True si on a pu jeter l'objet False sinon """ return False def poser(self, objet, personnage, qtt=1): """L'objet est posé.""" objet.script["pose"].executer(objet=objet, personnage=personnage, quantite=Fraction(qtt)) def detruire(self): """Destruction du prototype d'objet.""" # Destruction des objets à dépecer for proto in self.depecer_de: if self in proto.a_depecer: del proto.a_depecer[self] BaseObj.detruire(self) def nettoyage_cyclique(self): """Nettoyage cyclique de l'objet si besoin.""" pass def get_structure(self, structure): """Retourne la structure étenduee. La structure doit être envoyée par l'objet. Il est peu probable d'avoir besoin d'appeler cette méthode direction. """ structure.type = self.nom_type flags = [] for nom, valeur in FLAGS.items(): if self.flags & valeur != 0: flags.append(nom) structure.flags = flags def appliquer_structure(self, structure): """Applique la structure passée en apramètre.""" for cle, valeur in structure.donnees.items(): if cle == "type": if valeur != self.nom_type: self.changer_type(valeur)
class MUDmail(BaseObj): """Cette classe contient un mudmail. """ def __init__(self, parent=None, expediteur=None, source=None): """Constructeur de la classe""" BaseObj.__init__(self) self.parent = parent self.id = -1 self.notifier = True if source is not None: # édition d'un brouillon self._etat = BROUILLON self.sujet = str(source.sujet) self.expediteur = expediteur self.liste_dest = [] for d in list(source.liste_dest): self.liste_dest.append(d) self.aliases = source.aliases self.copies_a = source.copies_a self.contenu = Description(parent=self, scriptable=False) self.contenu.ajouter_paragraphe(str(source.contenu)) self.id_source = int(source.id) else: self._etat = EN_COURS self.sujet = "aucun sujet" self.expediteur = expediteur self.liste_dest = [] self.aliases = [] self.copies_a = [] self.contenu = Description(parent=self) self.id_source = 0 self.destinataire = None self.date = None self.lu = False # On passe le statut en CONSTRUIT self._construire() def __getnewargs__(self): return () def __getstate__(self): """Enregistrement de l'objet. On ne peut pas enregistrer les salles telles qu'elles car MongoDB n'aime pas les dictionnaires contenant des tuples en clés. """ def retirer_aliases(liste): for i, e in enumerate(liste): if isinstance(e, type) and issubclass(e, Alias): liste[i] = e.nom_alias attrs = BaseObj.__getstate__(self) retirer_aliases(attrs["liste_dest"]) retirer_aliases(attrs["aliases"]) retirer_aliases(attrs["copies_a"]) return attrs def __setstate__(self, attrs): """Récupération de l'objet enregistré.""" def remettre_aliases(liste): for i, e in enumerate(liste): if isinstance(e, str): liste[i] = aliases[e] remettre_aliases(attrs["liste_dest"]) remettre_aliases(attrs["aliases"]) remettre_aliases(attrs["copies_a"]) BaseObj.__setstate__(self, attrs) @property def etat(self): """Renvoie l'état du mail""" return self._etat @property def aff_dest(self): """Renvoie le(s) destinataire(s) si existant(s)""" res = "" if self.destinataire: res += self.destinataire.nom else: res += ", ".join([dest.nom for dest in self.liste_dest]) if self.aliases: res += ", " if res else "" res += ", ".join(["|bc|@" + a.nom_alias + "|ff|" \ for a in self.aliases]) if not res: res += "aucun" return res @property def apercu_contenu(self): """Renvoie un aperçu du corps du message""" apercu = self.contenu.paragraphes_indentes if apercu == "\n Aucune description.": apercu = "\n Aucun contenu." return apercu @property def nom_expediteur(self): """Retourne, si trouvé, le nom de l'expéditeur.""" return self.expediteur and self.expediteur.nom or "inconnu" def afficher(self): """Affiche le mail""" ret = "Expéditeur : " + self.expediteur.nom + "\n" ret += "Destinataire(s) : " + self.aff_dest + "\n" ret += "Sujet : " + echapper_accolades(self.sujet) + "\n" ret += echapper_accolades(str(self.contenu)) ret += "\n" + get_date(self.date.timetuple()).capitalize() + "." return ret def envoyer(self): """Envoie le mail""" liste_dest = set(self.liste_dest) if self.aliases: for alias in self.aliases: [liste_dest.add(j) for j in alias.retourner_joueurs()] if self.expediteur in liste_dest: liste_dest.remove(self.expediteur) if not liste_dest: raise ValueError("la liste de destinataires est vide") self.date = datetime.datetime.now() for dest in liste_dest: mail = type(self).importeur.communication.mails.creer_mail( self.expediteur) mail.date = datetime.datetime.now() mail.sujet = self.sujet mail.destinataire = dest mail.contenu = self.contenu mail.copies_a = list(self.liste_dest) + list(self.aliases) if dest in mail.copies_a: mail.copies_a.remove(dest) mail._etat = RECU if dest in type(self).importeur.connex.joueurs_connectes: dest << "\n|jn|Vous avez reçu un nouveau message.|ff|" if self.notifier: self.envoyer_email(dest) self._etat = ENVOYE def archiver(self): """Archive le mail""" self._etat = ARCHIVE def restaurer(self): """Restaure le mail""" self._etat = RECU def envoyer_email(self, dest): """Envoie l'e-mail au destinataire.""" if not dest.compte.email: return destinateur = "equipe" destinataire = dest.compte.adresse_email expediteur = self.expediteur.nom sujet = "[VanciaMUD] : " + self.sujet nom_compte = dest.compte.nom contenu = self.afficher().replace("|tab|", " ") corps = contenu + bas_page.format(nom_compte=nom_compte, expediteur=expediteur) if not importeur.email.serveur_mail: return if not destinataire: return importeur.email.envoyer(destinateur, destinataire, sujet, corps)
def __init__(self, prototype, titre): BaseObj.__init__(self) self.prototype = prototype self.titre = titre self.description = Description(parent=self)
class FicheMatelot(BaseObj): """Fiche d'un matelot à créer. La fiche comprend des informations sur les aptitudes et postes spécifiques d'un matelot au niveau prototype. Une fiche pourrait, par exemple, dire que le prototype 'marin_ctn' a comme poste par défaut 'charpentier' et la compétence 'calfeutrage' à 'bon' (ce qui se traduit, pour le PNJ créé depuis cette fiche, en l'obtension du talent 'calfeutrage' à quelque chose comme 30%). """ enregistrer = True type_achat = "matelot" def __init__(self, prototype): """Constructeur du matelot.""" BaseObj.__init__(self) self.prototype = prototype self.nom_singulier = "un matelot" self.nom_pluriel = "matelots" self.poste_defaut = "matelot" self.description = Description(parent=self) self.aptitudes = {} self.m_valeur = 20 def __getnewargs__(self): return (None, ) def __repr__(self): return "<FicheMâtelot {}>".format(repr(self.cle)) def __str__(self): return self.cle @property def cle(self): return self.prototype and self.prototype.cle or "aucune" @property def nom_achat(self): return self.nom_singulier def get_nom(self, nombre=1): """Retourne le nom complet en fonction du nombre.""" if nombre == 0: raise ValueError("Nombre invalide") elif nombre == 1: return self.nom_singulier else: return str(nombre) + " " + self.nom_pluriel def ajouter_aptitude(self, nom_aptitude, nom_niveau): """Ajoute une aptitude.""" nom_aptitude = supprimer_accents(nom_aptitude).lower() nom_niveau = supprimer_accents(nom_niveau).lower() aptitude = CLES_APTITUDES.get(nom_aptitude) if aptitude is None: raise KeyError("Aptitude inconnue {}".format(repr(nom_aptitude))) niveau = VALEURS_NIVEAUX.get(nom_niveau) if niveau is None: raise KeyError("Niveau inconnu {}".format(repr(nom_niveau))) self.aptitudes[aptitude] = niveau def creer_PNJ(self, salle=None, nb=1): """Crée le PNJ sur la fiche.""" pnjs = [] if self.prototype is None: raise ValueError("Le prototype de cette fiche est inconnu") for i in range(nb): pnj = importeur.pnj.creer_PNJ(self.prototype, salle) pnjs.append(pnj) # Modifie les aptitudes for aptitude, niveau in self.aptitudes.items(): talents = TALENTS.get(aptitude, []) connaissance = CONNAISSANCES[niveau] for talent in talents: pnj.talents[talent] = connaissance # Annonce l'arrivée du PNJ if salle: salle.envoyer("{} arrive.", pnj) return pnjs def recruter(self, personnage, navire): """Recrute le matelot sur la fiche.""" salle = navire.salles[0, 0, 0] personnage.salle = salle salle.envoyer("{} arrive.", personnage) navire.equipage.ajouter_matelot(personnage) def acheter(self, quantite, magasin, transaction): """Achète les matelots dans la quantité spécifiée.""" salle = magasin.parent acheteur = transaction.initiateur nom = "trans_" + str(id(transaction)) importeur.diffact.ajouter_action(nom, 10, self.creer_PNJ, salle, quantite) def regarder(self, personnage): """Le personnage regarde le service (avant achat).""" desc = self.description.regarder(personnage, elt=self.prototype) desc += "\n\nPoste (par défaut) : " + self.poste_defaut if self.aptitudes: desc += "\nAptitudes :\n" for aptitude, niveau in self.aptitudes.items(): nom = NOMS_APTITUDES[aptitude] nom_niveau = NOMS_NIVEAUX[niveau] desc += "\n {:<15} : {:>10} ({} / 6)".format( nom.capitalize(), nom_niveau, niveau + 1) return desc
class Salle(BaseObj): """Classe représentant une salle de l'univers. Une salle est un élément détaillant la géographie locale d'une petite portion de l'univers. Bien que cela dépende des MUDs, les salles décrivent généralement un espace d'environ 5 mètres sur 5. Ces salles comportent une description détaillant les alentours proches. Cette description est envoyée à chaque fois qu'un personnage se déplace dans l'univers, pour lui donner une idée de son nouvel environnement. Note sur le positionnement des salles : Les salles peuvent être caractérisées par des coordonnées. Ces coordonnées sont en soi facultatives. Il est possible de créer un univers sans aucune coordonnée. Il s'agit d'une facilité lors de la constitution de votre univers qui permet à certains modules, comme 'vehicule', de fonctionner. Si votre salle n'a pas de coordonnées, vous devrez créer chaque sortie "à la main". Les salles ne sont donc pas identifiées par leurs coordonnées, sauf dans certains cas, mais bien par leur zone et mnémonique. Ce couple caractérise de façon unique une salle dans l'univers. Exemple : une salle ayant pour zone 'picte' et pour mnémonique '1' sera accessible depuis la clé 'picte:1' ; aucune autre salle de l'univers ne pourra posséder cette clé 'picte:1'. """ nom_scripting = "la salle" _nom = "salle" _version = 4 enregistrer = True def __init__(self, zone, mnemonic, x=0, y=0, z=0, valide=True): """Constructeur de la salle""" BaseObj.__init__(self) self._nom_zone = zone self._mnemonic = mnemonic self.coords = Coordonnees(x, y, z, valide, self) self.nom_terrain = "ville" self.titre = "" self.description = Description(parent=self) self.sorties = Sorties(parent=self) self.details = Details(parent=self) self._personnages = [] self.objets_sol = ObjetsSol(parent=self) self.script = ScriptSalle(self) self.interieur = False self.illuminee = False self.magasin = None self.flags = 0 # Repop self.pnj_repop = {} # Etendue self.etendue = None # Affections self.affections = {} # Décors self.decors = [] self._construire() def __getnewargs__(self): return ("", "") def __repr__(self): """Affichage de la salle en mode debug""" return self._nom_zone + ":" + self._mnemonic def __str__(self): """Retourne l'identifiant 'zone:mnemonic'""" return self._nom_zone + ":" + self._mnemonic def _get_nom_zone(self): return self._nom_zone def _set_nom_zone(self, zone): ident = self.ident self._nom_zone = zone.lower() type(self).importeur.salle.changer_ident(ident, self.ident) def _get_mnemonic(self): return self._mnemonic def _set_mnemonic(self, mnemonic): ident = self.ident self._mnemonic = mnemonic.lower() type(self).importeur.salle.changer_ident(ident, self.ident) nom_zone = property(_get_nom_zone, _set_nom_zone) mnemonic = property(_get_mnemonic, _set_mnemonic) @property def zone(self): """Retourne la zone correspondante.""" return type(self).importeur.salle.get_zone(self._nom_zone) @property def ident(self): """Retourne l'identifiant, c'est-à-dire une chaîne 'zone:mnemonic'""" return "{}:{}".format(self._nom_zone, self._mnemonic) @property def personnages(self): """Retourne une liste déférencée des personnages""" return list(self._personnages) @property def PNJ(self): """Retourne une liste déférencée des PNJ présents.""" return [p for p in self._personnages if hasattr(p, "prototype")] @property def joueurs(self): """Retourne une liste déférencée des joueurs présents.""" return [p for p in self._personnages if not hasattr(p, "prototype")] @property def exterieur(self): """Retourne True si la salle est extérieure, False sinon.""" return not self.interieur @property def terrain(self): """Retourne l'objet terrain.""" return type(self).importeur.salle.terrains[self.nom_terrain] @property def desc_survol(self): return self.terrain.desc_survol @property def str_coords(self): x, y, z = self.coords.tuple() if self.coords.valide: return "{}.{}.{}".format(x, y, z) return "Aucune" def get_etendue(self): return self.etendue @property def nom_unique(self): return self.ident @property def objets_uniques(self): """Retourne les objets uniques posés dans la salle.""" objets = [] for objet in self.objets_sol._objets: objets.append(objet) objets.extend(objet.prototype.objets_contenus(objet)) return objets @property def a_magasin(self): """Y a-t-il un magasin dans cette salle ?""" return self.magasin is not None @property def nb_sorties(self): """Retourne le mombre de sorties.""" sorties = [s for s in self.sorties if s and s.salle_dest] return len(sorties) @property def details_etendus(self): """Retourne la liste des détails étendus. Cette méthode retourne les détails de la salle et ceux des flottantes contenus dans la description. """ details = self.details._details.copy() description = self.description for paragraphe in description.paragraphes: p, flottantes = description.charger_descriptions_flottantes( paragraphe) for flottante in flottantes: for d in flottante.details: if d.nom not in details: details[d.nom] = d return tuple(details.values()) def voit_ici(self, personnage): """Retourne True si le personnage peut voir ici, False sinon. Un personnage peut voir dans la salle si : Il est immortel La salle est illuminée Le personnage est un PNJ nyctalope Le personnage est un PNJ contrôlé par un immortel Il y a un feu dans la salle La race du personnage est nyctalope Le personnage a une affection qui lui permet de voir dans le noir La salle est en extérieure et : Il fait jour ou Le ciel est dégagé Le personnage a une lumière """ if personnage.est_immortel(): return True if self.illuminee: return True if hasattr(personnage, "prototype") and \ (personnage.prototype.a_flag("nyctalope") or \ personnage.controle_par): return True if self.ident in importeur.salle.feux: return True # Vérification de la race if personnage.race and personnage.race.a_flag("nyctalope"): return True # Vérification de l'affection for affection in personnage.affections.values(): if affection.affection.a_flag("voit dans le noir"): return True if not self.interieur: if importeur.temps.temps.il_fait_jour: return True perturbation = importeur.meteo.get_perturbation(self) if perturbation is None or not perturbation.est_opaque(): return True for objet in personnage.equipement.equipes: if objet.est_de_type("lumière") and objet.allumee_depuis: return True return False def a_detail_flag(self, flag): """Retourne True si la salle a un détail du flag indiqué.""" for detail in self.details: if detail.a_flag(flag): return True description = self.description for paragraphe in description.paragraphes: p, flottantes = description.charger_descriptions_flottantes( paragraphe) for flottante in flottantes: for d in flottante.details: if d.a_flag(flag): return True return False def personnage_est_present(self, personnage): """Si le personnage est présent, retourne True, False sinon.""" return personnage in self._personnages def a_flag(self, nom_flag): """Retourne True si la salle a le flag, False sinon.""" valeur = FLAGS[nom_flag] return self.flags & valeur != 0 def ajouter_personnage(self, personnage): """Ajoute le personnage dans la salle""" if personnage not in self._personnages: self._personnages.append(personnage) def retirer_personnage(self, personnage): """Retire le personnage des personnages présents""" if personnage in self.personnages: self._personnages.remove(personnage) def salles_autour(self, rayon=5): """Retourne les chemins autour de self dans le rayon précisé. Si la salle a des coordonnées valide, ajoute également les salles semblant proches. Cependant, pour celles-ci, presque aucune vérification de chemin n'est faite, c'est-à-dire qu'elles peuvent être entièrement inaccessible. """ if self.accepte_discontinu(): empruntable = False else: empruntable = True return Chemins.salles_autour(self, rayon, empruntable=empruntable) def trouver_chemin_absolu(self, destination, rayon=5, explicite=False): """Retourne, si trouvé, le chemin menant à destination ou None. Le rayon passé en argument est celui de recherche. Plus il est élevé, plus le temps de calcul risque d'être important. """ chemins = Chemins.salles_autour(self, rayon, absolu=True) chemin = chemins.get(destination) if chemin is None and explicite: raise ValueError("le chemin absolu est introuvable") return chemin def trouver_chemin(self, destination, explicite=False): """Recherche et retourne le chemin entre deux salles. Plusieurs algorithmes de recherche sont utilisés en fonction des situations. Pour des raisons de performance, certains sont plus efficaces que d'autres. Avant tout, plusieurs chemins peuvent être retournés : * Un chemin continue (toutes les salles entre deux points) * Un chemin discontinu (ou brisé). La grande différence est qu'un personnage peut emprunter un chemin continu pour aller d'une salle à une autre, mais ne peut pas emprunter un chemin discontinu. Les chemins discontinus ne sont pas tolérés par défaut (ceci est réglable). La recherche du chemin (continu ou discontinu) fait ensuite appel à deux algorithmes : * Si les coordonnées des deux salles sont valides, cherche le chemin relatifs (Chemin.salles_entre) * Sinon, cherche le chemin absolu (toutes les salles autour de la première salle dans un rayon d'estime). Si explicite est mis à True, lève une exception ValueError décrivant pourquoi la recherche a échouée. """ # Si l'une des salles n'a pas de coordonnées valide if self.coords.invalide or destination.coords.invalide: return self.trouver_chemin_absolu(destination, 4, explicite=explicite) # Les deux salles ont des coordonnées valides # On vérifie que le rayon n'es tpas trop important v_origine = Vecteur(*self.coords.tuple()) v_destination = Vecteur(*destination.coords.tuple()) distance = (v_destination - v_origine).norme if distance > 25: if explicite: raise ValueError("la distance entre les deux salles " \ "est supérieure à 25") return None salles = Chemins.get_salles_entre(self, destination) # On détermine, si possible, le chemin entre chaque salle chemin = Chemin() a_salle = None continu = True for d_salle in salles: if a_salle is None: a_salle = d_salle continue d_chemin = a_salle.trouver_chemin_absolu(d_salle, 2) if d_chemin is None: continu = False vecteur = Vecteur(*d_salle.coords.tuple()) - \ Vecteur(*a_salle.coords.tuple()) sortie = a_salle.get_sortie(vecteur, d_salle) chemin.sorties.append(sortie) else: chemin.sorties.extend(d_chemin.sorties) a_salle = d_salle if chemin.origine is not self or chemin.destination is not destination: if explicite: raise ValueError("le chemin retourné ne commence ou " \ "ne finit pas au bon endroit") return None if not continu and (not self.accepte_discontinu() or not \ destination.accepte_discontinu()): if explicite: raise ValueError("un chemin non continu a été retourné") return None chemin.raccourcir() return chemin def accepte_discontinu(self): """Retourne True si cette salle supporte les chemins discontinu. Tsunami supporte ce type de chemin si la salle est une côte de l'étendue. """ return self.etendue is not None def trouver_chemins_droits(self, rayon, dir_sortie=None, chemins=None, chemin=None, hook=True): """Cherche les chemins droits d'une salle. La recherche de salle peut être étendue par un hook. Les chemins droits veulent dire que l'on vérifie les sorties initiales. Par exemple, si la salle A a la sortie est menant vers B, on demande à la salle B ses sorties... mais on ne sélectionnera que les sorties de même direcition que la salle A (c'est-à-dire est ici). """ chemins = chemins or Chemins() chemin = chemin or Chemin() # On parcourt les sorties de la salle for sortie in self.sorties: if dir_sortie is None or sortie.direction == dir_sortie: n_chemin = Chemin() n_chemin.sorties.extend(chemin.sorties) n_chemin.sorties.append(sortie) origine = n_chemin.origine destination = n_chemin.destination ancien_chemin = chemins.get(destination) if origine is not destination and (ancien_chemin is None or \ len(ancien_chemin) > len(n_chemin)): # On retire l'ancien chemin si besoin if ancien_chemin: chemins.chemins.remove(ancien_chemin) chemins.chemins.append(n_chemin) if rayon > 1: destination.trouver_chemins_droits(rayon - 1, sortie.direction, chemins, n_chemin, False) if hook: importeur.hook["salle:trouver_chemins_droits"].executer(self, chemins, rayon) return chemins def get_sortie(self, vecteur, destination): """Retourne une sortie en fonction du vecteur donné.""" sortie = Sortie(vecteur.nom_direction, vecteur.nom_direction, "le", destination, "", self) sortie.longueur = vecteur.norme return sortie def sortie_empruntable(self, sortie): """Retourne True si la sortie est empruntable, False sinon. Pour une salle standard, une sortie est empruntable si elle existe réellement. """ return sortie.direction in self.sorties def envoyer(self, message, *personnages, prompt=True, mort=False, ignore=True, lisser=False, **kw_personnages): """Envoie le message aux personnages présents dans la salle. Les personnages dans les paramètres supplémentaires (nommés ou non) sont utilisés pour formatter le message et font figure d'exceptions. Ils ne recevront pas le message. """ exceptions = personnages + tuple(kw_personnages.values()) if ignore \ else () for personnage in self.personnages: if personnage not in exceptions: if personnage.est_mort() and not mort: continue if hasattr(personnage, "instance_connexion") and \ personnage.instance_connexion and not prompt: personnage.instance_connexion.sans_prompt() personnage.envoyer(message, *personnages, lisser=lisser, **kw_personnages) def envoyer_lisser(self, chaine, *personnages, **kw_personnages): """Méthode redirigeant vers envoyer mais lissant la chaîne.""" self.envoyer(chaine, *personnages, lisser=True, **kw_personnages) def get_elements_observables(self, personnage): """Retourne une liste des éléments observables dans cette salle.""" liste = [] for methode in importeur.salle.details_dynamiques: liste.extend(methode(self, personnage)) return liste def regarder(self, personnage): """Le personnage regarde la salle""" if personnage.est_mort(): personnage << "|err|Vous êtes inconscient et ne voyez pas " \ "grand chose...|ff|" return if not self.voit_ici(personnage): personnage << "Il y fait trop sombre pour vos sens, " \ "l'obscurité vous environne." return res = "" if personnage.est_immortel(): res += "# |rgc|" + self.nom_zone + "|ff|:|vrc|" + self.mnemonic res += "|ff| ({})".format(self.coords) res += "\n\n" res += " |tit|" + (self.titre or "Une salle sans titre") + "|ff|\n" description = self.description.regarder(personnage, self) if not description: description = " Vous êtes au milieu de nulle part." res += description + "\n" res_decors = [] for nom in self.regrouper_decors(): res_decors.append(nom.capitalize() + ".") if res_decors: res += "\n".join(res_decors) + "\n" plus = self.decrire_plus(personnage) if plus: res += plus + "\n" res_affections = [] for affection in self.affections.values(): message = affection.affection.message(affection) if message: res_affections.append(message) if res_affections: res += "\n".join(res_affections) + "\n" liste_messages = [] flags = 0 type(self).importeur.hook["salle:regarder"].executer(self, liste_messages, flags) if liste_messages: res += "\n".join(liste_messages) + "\n" res += "\nSorties : " res += self.afficher_sorties(personnage) # Personnages personnages = OrderedDict() # Si le personnage est un joueur, il se retrouve avec un nombre de 1 # Si le personnage est un PNJ, on conserve son prototype avec # le nombre d'occurences de prototypes apparaissant etats = {} for personne in self.personnages: if personne is not personnage: if not hasattr(personne, "prototype"): if personnage.peut_voir(personne): personnages[personne] = 1 else: doit = importeur.hook["pnj:doit_afficher"].executer(personne) if any(not flag for flag in doit): continue nom = personne.get_nom_etat(personnage) if nom in etats: prototype = etats[nom] personnages[prototype] = \ personnages[prototype] + 1 else: etats[nom] = personne personnages[personne] = 1 if len(personnages): res += "\n" for personne, nombre in personnages.items(): res += "\n- {}".format(personne.get_nom_etat( personnage, nombre)) # Objets noms_objets = self.afficher_noms_objets() if len(noms_objets): res += "\n" for nom_objet in noms_objets: res += "\n+ {}".format(nom_objet) return res def afficher_sorties(self, personnage): """Affiche les sorties de la salle""" noms = [] for nom in NOMS_SORTIES.keys(): sortie = self.sorties[nom] if sortie: nom = sortie.nom nom_aff = self.sorties.get_nom_abrege(nom) if self.sorties.sortie_existe(nom): if sortie.porte and sortie.porte.fermee: res = "[|rgc|" + nom_aff + "|ff|]" else: res = "|vr|" + nom_aff + "|ff|" if sortie.cachee: if personnage.est_immortel(): res = "|rg|(I)|ff|" + res else: res = " ".ljust(len(self.sorties.get_nom_abrege( sortie.direction))) else: res = " ".ljust(len(nom_aff)) noms.append(res) return ", ".join(noms) + "." def afficher_noms_objets(self): """Retourne les noms et états des objets sur le sol de la salle""" objets = [] for o, nb in self.objets_sol.get_objets_par_nom(): objets.append(o.get_nom_etat(nb)) return objets def decrire_plus(self, personnage): """Ajoute un message au-dessous de la description. Si cette méthode retourne une chaîne non vide, alors cette chaîne sera ajoutée sous la description quand un personnage regardera la salle. """ pass def pop_pnj(self, pnj): """Méthode appelée quand un PNJ pop dans la salle.""" pro = pnj.prototype if pro in self.pnj_repop: self.pnj_repop[pro] = self.pnj_repop[pro] - 1 def det_pnj(self, pnj): """Méthode appelée quand un PNJ disparaît..""" pro = pnj.prototype if pro in self.pnj_repop: self.pnj_repop[pro] = self.pnj_repop[pro] + 1 def repop(self): """Méthode appelée à chaque repop.""" for pro, nb in self.pnj_repop.items(): if nb > 0: for i in range(nb): pnj = importeur.pnj.creer_PNJ(pro, self) pnj.script["repop"].executer(pnj=pnj) def affecte(self, cle, duree, force): """Affecte la salle avec une affection. Si l'affection est déjà présente, la force est modulée. """ affection = importeur.affection.get_affection("salle", cle) if cle in self.affections: concrete = self.affections[cle] affection.moduler(concrete, duree, force) else: concrete = Affection(affection, self, duree, force) self.affections[cle] = concrete concrete.affection.executer_script("cree", concrete) concrete.prevoir_tick() def peut_affecter(self, cle_affection): """La salle self peut-elle être affectée par l'affection ?""" if cle_affection == "neige": if self.interieur: return False elif self.nom_terrain in ["aquatique", "subaquatique"]: return False sortie = self.sorties["bas"] if sortie: return False return True def tick(self): """Méthode appelée à chaque tick de la salle.""" for affection in self.affections.values(): affection.affection.dec_duree(affection) for cle, affection in list(self.affections.items()): if not affection.e_existe or affection.duree <= 0: del self.affections[cle] self.script["tick"].executer(salle=self) def regrouper_decors(self): """Regroupe les décors par nom.""" decors = OrderedDict() nombres = OrderedDict() res = OrderedDict() for decor in self.decors: nom = decor.get_nom() decors[nom] = decor nb = nombres.get(nom, 0) nb += 1 nombres[nom] = nb for nom, nb in nombres.items(): decor = decors[nom] nom = decor.get_nom_etat(nb) res[nom] = decor return res def ajouter_decor(self, prototype): """Ajoute un décor dans la salle.""" if isinstance(prototype, PrototypeBonhommeNeige): decor = BonhommeNeige(prototype, self) else: decor = Decor(prototype, self) self.decors.append(decor) return decor def get_decors(self, cle): """Retourne tous les décors ayant la clé indiquée.""" return [d for d in self.decors if d.prototype and \ d.prototype.cle == cle] def supprimer_decor(self, decor): """Supprime les décors indiqués.""" self.decors.remove(decor) decor.detruire() def supprimer_decors(self, cle): """Supprime les décors de clé indiquée.""" for decor in self.get_decors(cle): self.supprimer_decor(decor)
class Description(Editeur): """Contexte-éditeur description. Ce contexte sert à éditer des descriptions. """ nom = "editeur:base:description" def __init__(self, pere, objet=None, attribut=None): """Constructeur de l'éditeur""" attribut = attribut or "description" Editeur.__init__(self, pere, objet, attribut) self.opts.echp_sp_cars = False self.nom_attribut = attribut contenu = "" if objet: contenu = getattr(self.objet, self.nom_attribut) if contenu is None: setattr(self.objet, self.nom_attribut, "") else: contenu = str(contenu) self.description_complete = Desc(parent=objet, scriptable=False) if contenu: for paragraphe in contenu.split("\n"): self.description_complete.ajouter_paragraphe( paragraphe.replace("|nl|", " ")) self.ajouter_option("?", self.opt_aide) self.ajouter_option("j", self.opt_ajouter_paragraphe) self.ajouter_option("a", self.opt_inserer_paragraphe) self.ajouter_option("d", self.opt_supprimer) self.ajouter_option("r", self.opt_remplacer) self.ajouter_option("e", self.opt_editer_evt) self.ajouter_option("t", self.opt_tabulations) self.ajouter_option("de", self.opt_supprimer_evt) self.ajouter_option("re", self.opt_renommer_evt) self.ajouter_option("o", self.opt_editer_options) @property def description(self): """Retourne la description, attribut de self.objet""" attribut = getattr(self.objet, self.nom_attribut) if isinstance(attribut, str): return self.description_complete return attribut def get_prompt(self): """Retourne le prompt.""" description = self.description personnage = self.pere.joueur options = importeur.interpreteur.options if not options.a_option(personnage, OPT_AUTONL) and not \ description.saut_de_ligne: return "-? " else: return "-> " @staticmethod def afficher_apercu(apercu, objet, valeur): if valeur is None: return "" if isinstance(valeur, str): description = Desc(parent=objet, scriptable=False) for paragraphe in valeur.split("\n"): description.ajouter_paragraphe( paragraphe.replace("|nl|", " ")) valeur = description valeur = valeur.paragraphes_indentes return apercu.format(objet=objet, valeur=valeur) def mettre_a_jour(self): """Met à jour l'attribut si nécessaire.""" attribut = getattr(self.objet, self.nom_attribut) if isinstance(attribut, str): contenu = self.description_complete.affichage_simple("|nl|") setattr(self.objet, self.nom_attribut, contenu) def accueil(self): """Retourne l'aide""" description = self.description # Message d'aide msg = self.aide_courte.format(objet = self.objet) + "\n" msg += "Entrez une |cmd|phrase|ff| à ajouter à la description " \ "ou |ent|/|ff| pour revenir à la\nfenêtre mère.\n" \ "Symboles :\n" \ " - |ent||tab||ff| : symbolise une tabulation\n" \ "Options :\n" \ " - |ent|/?|ff| pour obtenir la liste complète " \ "des options disponibles\n" \ " - |ent|/d <numéro>/*|ff| : supprime un paragraphe ou " \ "toute la description\n" \ " - |ent|/r <texte 1> / <texte 2>|ff| : remplace " \ "|cmd|texte 1|ff| par |cmd|texte 2|ff|\n" \ "Pour ajouter un paragraphe, entrez-le tout simplement.\n\n" \ "Description existante :\n" if len(description.paragraphes) > 0: no_ligne = 1 for paragraphe in description.paragraphes: paragraphe = description.wrap_paragraphe(paragraphe, aff_sp_cars=True) paragraphe = paragraphe.replace("\n", "\n ") msg += "\n{: 2} {}".format(no_ligne, paragraphe) no_ligne += 1 else: msg += "\n Aucune description." return msg def opt_aide(self, arguments): """Affiche la liste des options. Syntaxe : /? """ msg = \ "Liste des options disponibles :\n" \ " - |ent|/d *|ff| pour supprimer toute la description\n" \ " - |ent|/d <numéro du paragraphe>|ff| pour supprimer un " \ "paragraphe\n" \ " - |ent|/a <numéro du paragraphe> <texte>|ff| pour ajouter " \ "du texte\n à la fin du paragraphe spécifié\n" \ " - |ent|/j <numéro du paragraphe> <texte>|ff| pour insérer " \ "le\n paragraphe avant celui spécifié\n" \ " - |/r <texte 1> / <texte 2>|ff| pour remplacer " \ "|ent|texte 1|ff| par |ent|texte 2|ff|\n" \ " - |ent|/t|ff| pour ajouter ou retirer les tabulations" if self.description.scriptable: msg += \ "\nScripting de description :\n" \ " - |ent|/e (description dynamique)|ff| pour éditer un " \ "élément de\n description dynamique. Sans arguments, " \ "affiche les descriptions dynamiques\n existantes\n" \ " - |ent|/de <description dynamique>|ff| supprime la " \ "description dynamique\n" \ " - |ent|/re <ancien nom> <nouveau nom>|ff| renomme la " \ "description dynamique" self.pere << msg def opt_ajouter_paragraphe(self, arguments): """Ajoute un paragraphe. Syntaxe : /j <numéro> <texte> """ description = self.description paragraphes = description.paragraphes arguments = arguments.split(" ") if len(arguments) < 2: self.pere << "|err|Syntaxe invalide :|ent|/a <numéro du " \ "paragraphe> <texte à insérer>|ff|" return numero = arguments[0] texte = " ".join(arguments[1:]) # Conversion du numéro de paragraphe try: numero = int(numero) assert 1 <= numero <= len(paragraphes) except (ValueError, AssertionError): self.pere << "|err|Le numéro précisé est invalide.|ff|" return description.paragraphes.insert(numero - 1, texte) self.mettre_a_jour() self.actualiser() def opt_inserer_paragraphe(self, arguments): """Insère du texte à la fin d'un paragraphe. Syntaxe : /a <numéro> <texte> """ description = self.description paragraphes = description.paragraphes arguments = arguments.split(" ") if len(arguments) < 2: self.pere << "|err|Syntaxe invalide :|ent|/a <numéro du " \ "paragraphe> <texte à insérer>|ff|" return numero = arguments[0] texte = " ".join(arguments[1:]) # Conversion du numéro de paragraphe try: numero = int(numero) assert 1 <= numero <= len(paragraphes) except (ValueError, AssertionError): self.pere << "|err|Le numéro précisé est invalide.|ff|" return paragraphe = paragraphes[numero - 1] if not paragraphe.endswith(" "): paragraphe += " " paragraphe += texte description.paragraphes[numero - 1] = paragraphe self.mettre_a_jour() self.actualiser() def opt_supprimer(self, arguments): """Fonction appelé quand on souhaite supprimer un morceau de la description Les arguments peuvent être : * le signe '*' pour supprimer toute la description * un nombre pour supprimer le paragraphe n°<nombre> """ description = self.description if arguments == "*": # on supprime toute la description description.vider() self.mettre_a_jour() self.actualiser() else: # Ce doit être un nombre try: no = int(arguments) - 1 assert no >= 0 and no < len(description.paragraphes) except ValueError: self.pere << "|err|Numéro de ligne invalide.|ff|" except AssertionError: self.pere << "|err|Numéro de ligne inexistant.|ff|" else: description.supprimer_paragraphe(no) self.mettre_a_jour() self.actualiser() def opt_remplacer(self, arguments): """Fonction appelé pour remplacer du texte dans la description. La syntaxe de remplacement est : <texte 1> / <texte à remplacer> """ description = self.description # On commence par split au niveau du pipe try: recherche, remplacer_par = arguments.split(" / ") except ValueError: self.pere << "|err|Syntaxe invalide.|ff|" else: description.remplacer(recherche, remplacer_par) self.mettre_a_jour() self.actualiser() def opt_tabulations(self, arguments): """Ajoute ou retire les tabulations d'une description. Syntaxe : /t """ description = self.description # On parcourt tous les paragraphes for i, paragraphe in enumerate(description.paragraphes): if paragraphe.startswith("|tab|"): paragraphe = paragraphe[5:] else: paragraphe = "|tab|" + paragraphe description.paragraphes[i] = paragraphe self.mettre_a_jour() self.actualiser() def opt_editer_evt(self, arguments): """Edite ou affiche les éléments de la description.""" description = self.description if not description.scriptable: self.pere << "|err|Option inconnue.|ff|" return evenements = description.script["regarde"].evenements evt = supprimer_accents(arguments).strip() if not evt: msg = \ "Ci-dessous se trouve la liste des éléments observables " \ "dans cette description :\n" for nom in sorted(evenements.keys()): msg += "\n {}".format(nom) if not evenements: msg += "\n |att|Aucun|ff|" self.pere << msg else: if evt in evenements.keys(): evenement = evenements[evt] else: evenement = description.script["regarde"].creer_evenement(evt) description.script.init() evenement.creer_sinon() enveloppe = EnveloppeObjet(EdtInstructions, evenement.sinon) enveloppe.parent = self contexte = enveloppe.construire(self.pere.joueur) self.migrer_contexte(contexte) def opt_supprimer_evt(self, arguments): """Supprime un évènement de la description dynamique. Syntaxe : /de <nom> """ description = self.description if not description.scriptable: self.pere << "|err|Option inconnue.|ff|" return regarde = description.script["regarde"] evt = supprimer_accents(arguments).strip() if evt in regarde.evenements: regarde.supprimer_evenement(evt) self.actualiser() else: self.pere << "|err|Évènement inconnu : {}.|ff|".format( repr(evt)) def opt_renommer_evt(self, arguments): """Renomme un évènement de la description dynamique. Syntaxe : /de <nom> <nouveau nom> """ description = self.description if not description.scriptable: self.pere << "|err|Option inconnue.|ff|" return regarde = description.script["regarde"] try: ancien, nouveau = arguments.split(" ") except ValueError: self.pere << "|err|Entrez l'ancien nom, un espace et le " \ "nouveau nom de variable.|ff|" return ancien = supprimer_accents(ancien).strip() if ancien in regarde.evenements: regarde.renommer_evenement(ancien, nouveau) self.actualiser() else: self.pere << "|err|Évènement inconnu : {}.|ff|".format( repr(ancien)) def opt_editer_options(self, arguments): """Fonction appelé pour consulter et éditer ses options. La syntaxe est : /o pour consulter ses options /o nom pour modifier ses options """ arguments = arguments.strip() personnage = self.pere.joueur if arguments: options = importeur.interpreteur.options try: nombre = options.get_nombre_option(arguments) except ValueError: self.pere << "|err|Ce nom d'option est inconnu.|ff|" else: options.changer_option(personnage, nombre) self.pere << "Vos options ont bien été modifiées." else: # Affichage des options options = importeur.interpreteur.options.afficher_options( personnage) self.pere << "Vos options actuelles :\n\n " + "\n ".join( options) def interpreter(self, msg): """Interprétation du contexte""" description = self.description personnage = self.pere.joueur options = importeur.interpreteur.options if not options.a_option(personnage, OPT_AUTONL): if msg and not description.saut_de_ligne: if description.paragraphes: paragraphe = description.paragraphes[-1] msg = paragraphe + " " + msg description.supprimer_paragraphe(-1) elif not msg: description.saut_de_ligne = True self.mettre_a_jour() self.actualiser() return elif options.a_option(personnage, OPT_AUTOTAB) and msg: msg = "|tab|" + msg description.ajouter_paragraphe(msg) description.saut_de_ligne = False self.mettre_a_jour() self.actualiser()