#from __future__ import unicode_literals # -*- coding: utf-8 -*- from PyQt4.QtCore import QPointF, Qt, QRectF, QString from PyQt4.QtGui import QGraphicsItem, QColor, QGraphicsPolygonItem, QPen, \ QGraphicsDropShadowEffect, QGraphicsPixmapItem, QGraphicsSimpleTextItem, \ QFont, QGraphicsObject from Forme import Forme from lib import dmF from lib.rsc import RImage class Pion(QGraphicsObject): """pion du plateau de combat""" def __init__(self, parent=None): super(Pion, self).__init__() #caracteristiques du pion self.numero = -1 self.pion = PolygonePion() self.couleur = QColor(200, 200, 200) self.logo = RImage() self.img = ImgPion() self.etiquette = EtiquettePion() self.brillance = BrillancePion() #infos liees au plateau (forme et position) self.plateau = None self.numComplementaire = "" #numero complementaire si necessaire #(si plusieurs pions portent le meme nom) self.position = (-1, -1) self.zR = 0 #altitude relative du pion par rapport aux cases qu'il occupe (vol slt) self.h = 1 self.hMax = False #[decors slt] la hauteur doit etre calculee pour atteindre le plafond (s'il existe) self.posPile = 0 #position du pion dans la pile des pions (pour l'ordre de re-creation des pions) self._place = False #le pion est place sur le plateau self.forme = None self.formeDef = {"H":[], "C":[]} self.etat = 0 #de 0 (indemne) a 4 (mort ou hors combat) self.statuts = [] #objets et parametres graphiques self.etiquetteGraphique = None self.nbRotations = 0 def __getstate__(self): nePasSvg = ["_place", "plateau", "brillance", "pion", "etiquetteGraphique"] state = {key:value for key, value in self.__dict__.items() if not key in nePasSvg} return (state) def __setstate__(self, state): self.__dict__ = state self.pion = PolygonePion() self.brillance = BrillancePion() self.etiquetteGraphique = None self._place = False super(Pion, self).__init__() def paint(self, painter, option, widget = None): """reimplemente de QGraphicsItem: on ne peint pas cet item, seulement ses items enfants""" pass def txtId(self): """renvoie le nom et le numero complementaire du pion""" txt = dmF.contractTxt(self._nom, 20) return "{} {}".format(txt, self.numComplementaire) def icone(self): """renvoie l'image a afficher dans les listes""" return self.logo def yReel(self): """renvoie le y reel (pour les contructions graphiques)""" if 1 == (self.position[0] % 2): y = self.position[1] + 0.5 else: y = self.position[1] return y ###attributs du pion def position(self): """retourne la position actuelle du pion""" return self.position def projectionValide(self, proj): """prend en parametre la projection en cours, et renvoie vrai si celle-ci est valide attention: cette fonction peut etre appellee avant la creation du pion *** reimplemente dans les classes heritees ***""" lst = proj.listeCases() for case in lst: #1- la case est occupee par un autre combattant if (case.occupant() > 0): return False #2- z1 retourne None z1 = case.z1() if z1 == None: return False #3- la hauteur sous le plafond est insuffisante : (zP - z1) < h if case.zP() and not self.hMax: if case.zP() - z1 < self.h: return False return True def coordOccupees(self): """retourne la liste des cases occupees sur le plateau par le pion (x,y)""" retour = [] if self.position != (-1, -1): for x, y in self.forme.listeCases(self.position, self.nbRotations): # for z in range(0, self.h): # retour.append((x, y, (self.plateau.cases[(x, y)].z0 + self.zR + z))) retour.append((x, y)) return retour def espaceOccupe(self): """retourne l'espace occupe sur le plateau par le pion sous la forme de coordonnees (x, y, zA)""" retour = [] if self.position != (-1, -1): for x, y in self.forme.listeCases(self.position, self.nbRotations): zA = self.plateau.cases[(x, y)].zA(self.numero) for z in range(0, (self.h if self.h > 0 else 1)): retour.append((x, y, (zA + z))) return retour def occuper(self): """signale aux cases qu'il occupe cet espace""" for x, y in self.coordOccupees(): self.plateau.cases[(x,y)].occuper(self.numero, self.h) def liberer(self): """signale aux cases qu'il n'occupe plus cet espace""" for x, y in self.coordOccupees(): self.plateau.cases[(x,y)].liberer(self.numero) def majZ(self, zR): """met a jour l'altitude relative zR du pion""" if zR != self.zR: self.zR = zR def zA(self): """retourne la coord z absolue du pion""" return (self.plateau.cases[self.position].zA(self.numero)) ########### fonctions graphiques et geometriques ############## def ajouterAuPlateau(self, plateau): """cerre l'objet graphique representant le pion et le place sur le plateau""" self.plateau = plateau #definition de la forme (interpretation de formeDef) self.forme = Forme(self.plateau.formeCases) if len(self.formeDef[self.plateau.formeCases]) > 0: self.forme.definirForme(self.formeDef[self.plateau.formeCases]) #parametres de l'objet graphique self.setFlag(QGraphicsItem.ItemHasNoContents) # self.setHandlesChildEvents(True) self.setFiltersChildEvents(True) self.setAcceptHoverEvents(True) ### ajout des items enfants (dans l'ordre d'empilement) #creation du polygone polygone = self.plateau.geo.polygoneAgglo(self.forme.listeCases((0,0))) self.pion.creer(self, polygone) #image: self.img.creer(self) #brillance (au survol) self.brillance.creer(self) #on ajoute l'objet au plateau self.plateau.addItem(self) self.setZValue(90) self.majPosition(self.position, self.nbRotations) self._place = True def majPosition(self, nouvellePosition, nbRotations = 0): """met a jour la position de l'objet graphique et de sa forme en fonction de sa position enregistree""" if self.plateau: #on met a jour l'occupation des cases if self._place: self.liberer() #on met a jour la position du pion self.position = nouvellePosition self.majNbRotation(nbRotations) #on replace if self.plateau.formeCases == "H": positionGraphique = QPointF(self.position[0] * 0.866 * 120, self.yReel() * 120) else: positionGraphique = QPointF(self.position[0] * 120, self.position[1] * 120) self.prepareGeometryChange() self.setPos(positionGraphique) self.pion.maj() #maj de l'image self.img.maj() #on met a jour l'occupation des cases self.occuper() #gestion de la pile du plateau self.posPile = self.plateau.incrementerPile() def majEtiquette(self): """met a jour la taille, le format et l'orientation de l'etiquette""" self.etiquetteGraphique = QGraphicsSimpleTextItem(QString().fromUtf8(self.txtId())) self.etiquetteGraphique.setPos(QPointF(self.etiquette.dx - 0.112*self.plateau.hCase, \ self.etiquette.dy - 0.5*self.plateau.hCase)) police = QFont("Verdana", self.etiquette.taille_police) police.setBold(self.etiquette.gras) self.etiquetteGraphique.setFont(police) self.etiquetteGraphique.setParentItem(self) self.etiquetteGraphique.setZValue(94) def majNbRotation(self, nbRotations): """ajoute/retranche le nombre au nombre total de rotations du pion""" self.nbRotations = nbRotations if self.plateau.formeCases == "H": rotationsTour = 6 else: rotationsTour = 4 if self.nbRotations >= 0: self.nbRotations = self.nbRotations % rotationsTour else: self.nbRotations = self.nbRotations % (-rotationsTour) def retirerDuPlateau(self): """'deconnecte' les items enfants avant de supprimer du pion du plateau""" self.liberer() self.setVisible(False) self.brillance.supprimer() self.plateau.removeItem(self.brillance) self.img.supprimer() self.plateau.removeItem(self.img) self.pion.supprimer() self.plateau.removeItem(self.pion) if self.etiquetteGraphique: self.etiquetteGraphique.prepareGeometryChange() self.etiquetteGraphique.setParentItem(None) self.plateau.removeItem(self.etiquetteGraphique) self.plateau.removeItem(self) self.plateau = None ###effets graphiques def afficheOmbreSelection(self, actif = False): """modifie l'ombre du pion en fonction de si celui-ci est selectionne ou non""" self.pion.ombre(actif) def surbrillance(self, active, opacite = 0.7, couleur = "white"): """active/desactive la surbrillance""" self.brillance.activer(active, opacite, self.couleurSurbrillance(couleur)) def estCibleAttaque(self, estCible, possible = True): """le pion s'affiche comme etant cible d'une attaque""" if not possible: couleur = "red" else: couleur = "white" self.surbrillance(estCible, 0.8, couleur) def couleurSurbrillance(self, couleur = "white"): """renvoie une QColor visible pour la surbrillance, selon la couleur du pion""" retour = QColor(couleur) if self.pion.brush().color().lightness() > 220: retour = retour.darker(140) elif self.pion.brush().color().lightness() < 80: retour = retour.lighter(140) return retour ################## ############### evenements clavier et souris ############## def boundingRect(self): return QRectF() ####################### class PolygonePion(QGraphicsPolygonItem): """polygone representant le pion""" def __init__(self): super(PolygonePion, self).__init__() def __setstate__(self, state): self.__dict__ = state super(PolygonePion, self).__init__() def numero(self): return self._pion.numero def creer(self, pion, polygone): self._pion = pion self.setPolygon(polygone) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemIsFocusable) #l'item peut recevoir des commandes souris/clavier self.setPos(QPointF(0,0)) origine = QPointF(2*0.2886*120, 0.5*120) if self._pion.plateau.formeCases == "H" else QPointF(0.5*120, 0.5*120) self.setTransformOriginPoint(origine) pinceau = QPen() pinceau.setWidth(10) couleur = self._pion.couleur if self._pion.couleur.isValid() else QColor(150, 150, 150) pinceau.setColor(couleur.darker(130)) self.setPen(pinceau) self.setBrush(couleur) #ombre self.shadow = QGraphicsDropShadowEffect() self.shadow.setColor(QColor(50, 50, 50)) self.shadow.setXOffset(1) self.shadow.setYOffset(2) self.shadow.setBlurRadius(3) self.shadow.setEnabled(True) self.setGraphicsEffect(self.shadow) self.setParentItem(self._pion) # self.setZValue(91) def maj(self): angleRotation = 60 if self._pion.plateau.formeCases == "H" else 90 self.setRotation(self._pion.nbRotations * angleRotation) def ombre(self, actif): if actif: self.shadow.setXOffset(3) self.shadow.setYOffset(3) else: self.shadow.setXOffset(1) self.shadow.setYOffset(2) def supprimer(self): self.prepareGeometryChange() self.setParentItem(None) class BrillancePion(QGraphicsPolygonItem): """polygone representant le pion""" def __init__(self): super(BrillancePion, self).__init__() def __setstate__(self, state): self.__dict__ = state super(BrillancePion, self).__init__() def numero(self): return self._pion.numero def creer(self, pion): self._pion = pion self.setPolygon(self._pion.pion.polygon()) self.setVisible(False) self.setFlag(QGraphicsItem.ItemIsFocusable) self.setAcceptHoverEvents(True) self.setParentItem(self._pion.pion) # self.setZValue(93) def activer(self, actif, opacite, couleur): if actif: self.setOpacity(opacite) self.setBrush(couleur) self.setPen(self._pion.pion.pen()) self.setVisible(actif) def supprimer(self): self.prepareGeometryChange() self.setParentItem(None) class ImgPion(QGraphicsPixmapItem): def __init__(self): super(ImgPion, self).__init__() self._pion = None self.rimage = RImage() #ressource: image self.kx = 10 #coeff d'agrandissement horizontal self.ky = 10 #coeff d'agrandissement vertical self.dx = 0 #decalage horizontal self.dy = 0 #decalage vertical self.rotation = 0 #rotation(en degres) self.pivote = False #l'image pivote avec le pion? self.masqueAuto = False #creer un masque automatiquement def __setstate__(self, state): self.__dict__ = state super(ImgPion, self).__init__() def numero(self): num = self._pion.numero if self._pion else -1 return num def creer(self, pion): self._pion = pion if not self.rimage.estValide(): return pix = self.rimage.pix() if pix.isNull(): return self.setAcceptedMouseButtons(Qt.NoButton) if pix.height() >= pix.width(): pix = pix.scaledToHeight( 120 * 0.9, Qt.SmoothTransformation) else: pix = pix.scaledToWidth( 120 * 0.9, Qt.SmoothTransformation) pix = pix.scaled( (self.kx / 10) * pix.width(), \ (self.ky / 10) * pix.height(), \ Qt.IgnoreAspectRatio, Qt.SmoothTransformation ) self.setPixmap(pix) if self.rimage.idR() == self._pion.logo.idR(): #si l'image est le logo, elle ne doit pas pivoter self.setParentItem(self._pion) else: self.setParentItem(self._pion.pion) # self.setZValue(92) def maj(self): """met a jour la taille, la position et l'orientation de l'image""" pix = self.pixmap() if pix.isNull(): return deltaX = self.dx + 0.5 * ( (120 * 1.1544) - pix.width() ) deltaY = self.dy + 0.5 * ( 120 - pix.height()) self.setRotation(self.rotation) self.setPos(QPointF(deltaX, deltaY)) def supprimer(self): self.prepareGeometryChange() self.setParentItem(None) class EtiquettePion(): def __init__(self): self.txt = "" self.taille_police = 28 #taille de la police self.gras = False #en gras self.dx = 0 #decalage horizontal self.dy = 0 #decalage vertical