Pion.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. #from __future__ import unicode_literals
  2. # -*- coding: utf-8 -*-
  3. from PyQt4.QtCore import QPointF, Qt, QRectF, QString
  4. from PyQt4.QtGui import QGraphicsItem, QColor, QGraphicsPolygonItem, QPen, \
  5. QGraphicsDropShadowEffect, QGraphicsPixmapItem, QGraphicsSimpleTextItem, \
  6. QFont, QGraphicsObject
  7. from Forme import Forme
  8. from lib import dmF
  9. from lib.rsc import RImage
  10. class Pion(QGraphicsObject):
  11. """pion du plateau de combat"""
  12. def __init__(self, parent=None):
  13. super(Pion, self).__init__()
  14. #caracteristiques du pion
  15. self.numero = -1
  16. self.pion = PolygonePion()
  17. self.couleur = QColor(200, 200, 200)
  18. self.logo = RImage()
  19. self.img = ImgPion()
  20. self.etiquette = EtiquettePion()
  21. self.brillance = BrillancePion()
  22. #infos liees au plateau (forme et position)
  23. self.plateau = None
  24. self.numComplementaire = "" #numero complementaire si necessaire
  25. #(si plusieurs pions portent le meme nom)
  26. self.position = (-1, -1)
  27. self.zR = 0 #altitude relative du pion par rapport aux cases qu'il occupe (vol slt)
  28. self.h = 1
  29. self.hMax = False #[decors slt] la hauteur doit etre calculee pour atteindre le plafond (s'il existe)
  30. self.posPile = 0 #position du pion dans la pile des pions (pour l'ordre de re-creation des pions)
  31. self._place = False #le pion est place sur le plateau
  32. self.forme = None
  33. self.formeDef = {"H":[], "C":[]}
  34. self.etat = 0 #de 0 (indemne) a 4 (mort ou hors combat)
  35. self.statuts = []
  36. #objets et parametres graphiques
  37. self.etiquetteGraphique = None
  38. self.nbRotations = 0
  39. def __getstate__(self):
  40. nePasSvg = ["_place", "plateau", "brillance", "pion", "etiquetteGraphique"]
  41. state = {key:value for key, value in self.__dict__.items() if not key in nePasSvg}
  42. return (state)
  43. def __setstate__(self, state):
  44. self.__dict__ = state
  45. self.pion = PolygonePion()
  46. self.brillance = BrillancePion()
  47. self.etiquetteGraphique = None
  48. self._place = False
  49. super(Pion, self).__init__()
  50. def paint(self, painter, option, widget = None):
  51. """reimplemente de QGraphicsItem: on ne peint pas cet item, seulement ses items enfants"""
  52. pass
  53. def txtId(self):
  54. """renvoie le nom et le numero complementaire du pion"""
  55. txt = dmF.contractTxt(self._nom, 20)
  56. return "{} {}".format(txt, self.numComplementaire)
  57. def icone(self):
  58. """renvoie l'image a afficher dans les listes"""
  59. return self.logo
  60. def yReel(self):
  61. """renvoie le y reel (pour les contructions graphiques)"""
  62. if 1 == (self.position[0] % 2):
  63. y = self.position[1] + 0.5
  64. else:
  65. y = self.position[1]
  66. return y
  67. ###attributs du pion
  68. def position(self):
  69. """retourne la position actuelle du pion"""
  70. return self.position
  71. def projectionValide(self, proj):
  72. """prend en parametre la projection en cours, et renvoie vrai si celle-ci est valide
  73. attention: cette fonction peut etre appellee avant la creation du pion
  74. *** reimplemente dans les classes heritees ***"""
  75. lst = proj.listeCases()
  76. for case in lst:
  77. #1- la case est occupee par un autre combattant
  78. if (case.occupant() > 0): return False
  79. #2- z1 retourne None
  80. z1 = case.z1()
  81. if z1 == None: return False
  82. #3- la hauteur sous le plafond est insuffisante : (zP - z1) < h
  83. if case.zP() and not self.hMax:
  84. if case.zP() - z1 < self.h: return False
  85. return True
  86. def coordOccupees(self):
  87. """retourne la liste des cases occupees sur le plateau par le pion (x,y)"""
  88. retour = []
  89. if self.position != (-1, -1):
  90. for x, y in self.forme.listeCases(self.position, self.nbRotations):
  91. # for z in range(0, self.h):
  92. # retour.append((x, y, (self.plateau.cases[(x, y)].z0 + self.zR + z)))
  93. retour.append((x, y))
  94. return retour
  95. def espaceOccupe(self):
  96. """retourne l'espace occupe sur le plateau par le pion
  97. sous la forme de coordonnees (x, y, zA)"""
  98. retour = []
  99. if self.position != (-1, -1):
  100. for x, y in self.forme.listeCases(self.position, self.nbRotations):
  101. zA = self.plateau.cases[(x, y)].zA(self.numero)
  102. for z in range(0, (self.h if self.h > 0 else 1)):
  103. retour.append((x, y, (zA + z)))
  104. return retour
  105. def occuper(self):
  106. """signale aux cases qu'il occupe cet espace"""
  107. for x, y in self.coordOccupees():
  108. self.plateau.cases[(x,y)].occuper(self.numero, self.h)
  109. def liberer(self):
  110. """signale aux cases qu'il n'occupe plus cet espace"""
  111. for x, y in self.coordOccupees():
  112. self.plateau.cases[(x,y)].liberer(self.numero)
  113. def majZ(self, zR):
  114. """met a jour l'altitude relative zR du pion"""
  115. if zR != self.zR: self.zR = zR
  116. def zA(self):
  117. """retourne la coord z absolue du pion"""
  118. return (self.plateau.cases[self.position].zA(self.numero))
  119. ########### fonctions graphiques et geometriques ##############
  120. def ajouterAuPlateau(self, plateau):
  121. """cerre l'objet graphique representant le pion et le place sur le plateau"""
  122. self.plateau = plateau
  123. #definition de la forme (interpretation de formeDef)
  124. self.forme = Forme(self.plateau.formeCases)
  125. if len(self.formeDef[self.plateau.formeCases]) > 0:
  126. self.forme.definirForme(self.formeDef[self.plateau.formeCases])
  127. #parametres de l'objet graphique
  128. self.setFlag(QGraphicsItem.ItemHasNoContents)
  129. # self.setHandlesChildEvents(True)
  130. self.setFiltersChildEvents(True)
  131. self.setAcceptHoverEvents(True)
  132. ### ajout des items enfants (dans l'ordre d'empilement)
  133. #creation du polygone
  134. polygone = self.plateau.geo.polygoneAgglo(self.forme.listeCases((0,0)))
  135. self.pion.creer(self, polygone)
  136. #image:
  137. self.img.creer(self)
  138. #brillance (au survol)
  139. self.brillance.creer(self)
  140. #on ajoute l'objet au plateau
  141. self.plateau.addItem(self)
  142. self.setZValue(90)
  143. self.majPosition(self.position, self.nbRotations)
  144. self._place = True
  145. def majPosition(self, nouvellePosition, nbRotations = 0):
  146. """met a jour la position de l'objet graphique et de sa forme en fonction de sa position enregistree"""
  147. if self.plateau:
  148. #on met a jour l'occupation des cases
  149. if self._place: self.liberer()
  150. #on met a jour la position du pion
  151. self.position = nouvellePosition
  152. self.majNbRotation(nbRotations)
  153. #on replace
  154. if self.plateau.formeCases == "H":
  155. positionGraphique = QPointF(self.position[0] * 0.866 * 120, self.yReel() * 120)
  156. else:
  157. positionGraphique = QPointF(self.position[0] * 120, self.position[1] * 120)
  158. self.prepareGeometryChange()
  159. self.setPos(positionGraphique)
  160. self.pion.maj()
  161. #maj de l'image
  162. self.img.maj()
  163. #on met a jour l'occupation des cases
  164. self.occuper()
  165. #gestion de la pile du plateau
  166. self.posPile = self.plateau.incrementerPile()
  167. def majEtiquette(self):
  168. """met a jour la taille, le format et l'orientation de l'etiquette"""
  169. self.etiquetteGraphique = QGraphicsSimpleTextItem(QString().fromUtf8(self.txtId()))
  170. self.etiquetteGraphique.setPos(QPointF(self.etiquette.dx - 0.112*self.plateau.hCase, \
  171. self.etiquette.dy - 0.5*self.plateau.hCase))
  172. police = QFont("Verdana", self.etiquette.taille_police)
  173. police.setBold(self.etiquette.gras)
  174. self.etiquetteGraphique.setFont(police)
  175. self.etiquetteGraphique.setParentItem(self)
  176. self.etiquetteGraphique.setZValue(94)
  177. def majNbRotation(self, nbRotations):
  178. """ajoute/retranche le nombre au nombre total de rotations du pion"""
  179. self.nbRotations = nbRotations
  180. if self.plateau.formeCases == "H":
  181. rotationsTour = 6
  182. else:
  183. rotationsTour = 4
  184. if self.nbRotations >= 0:
  185. self.nbRotations = self.nbRotations % rotationsTour
  186. else:
  187. self.nbRotations = self.nbRotations % (-rotationsTour)
  188. def retirerDuPlateau(self):
  189. """'deconnecte' les items enfants avant de supprimer du pion du plateau"""
  190. self.liberer()
  191. self.setVisible(False)
  192. self.brillance.supprimer()
  193. self.plateau.removeItem(self.brillance)
  194. self.img.supprimer()
  195. self.plateau.removeItem(self.img)
  196. self.pion.supprimer()
  197. self.plateau.removeItem(self.pion)
  198. if self.etiquetteGraphique:
  199. self.etiquetteGraphique.prepareGeometryChange()
  200. self.etiquetteGraphique.setParentItem(None)
  201. self.plateau.removeItem(self.etiquetteGraphique)
  202. self.plateau.removeItem(self)
  203. self.plateau = None
  204. ###effets graphiques
  205. def afficheOmbreSelection(self, actif = False):
  206. """modifie l'ombre du pion en fonction de si celui-ci est selectionne ou non"""
  207. self.pion.ombre(actif)
  208. def surbrillance(self, active, opacite = 0.7, couleur = "white"):
  209. """active/desactive la surbrillance"""
  210. self.brillance.activer(active, opacite, self.couleurSurbrillance(couleur))
  211. def estCibleAttaque(self, estCible, possible = True):
  212. """le pion s'affiche comme etant cible d'une attaque"""
  213. if not possible:
  214. couleur = "red"
  215. else:
  216. couleur = "white"
  217. self.surbrillance(estCible, 0.8, couleur)
  218. def couleurSurbrillance(self, couleur = "white"):
  219. """renvoie une QColor visible pour la surbrillance, selon la couleur du pion"""
  220. retour = QColor(couleur)
  221. if self.pion.brush().color().lightness() > 220:
  222. retour = retour.darker(140)
  223. elif self.pion.brush().color().lightness() < 80:
  224. retour = retour.lighter(140)
  225. return retour
  226. ##################
  227. ############### evenements clavier et souris ##############
  228. def boundingRect(self):
  229. return QRectF()
  230. #######################
  231. class PolygonePion(QGraphicsPolygonItem):
  232. """polygone representant le pion"""
  233. def __init__(self):
  234. super(PolygonePion, self).__init__()
  235. def __setstate__(self, state):
  236. self.__dict__ = state
  237. super(PolygonePion, self).__init__()
  238. def numero(self):
  239. return self._pion.numero
  240. def creer(self, pion, polygone):
  241. self._pion = pion
  242. self.setPolygon(polygone)
  243. self.setAcceptHoverEvents(True)
  244. self.setFlag(QGraphicsItem.ItemIsFocusable) #l'item peut recevoir des commandes souris/clavier
  245. self.setPos(QPointF(0,0))
  246. origine = QPointF(2*0.2886*120, 0.5*120) if self._pion.plateau.formeCases == "H" else QPointF(0.5*120, 0.5*120)
  247. self.setTransformOriginPoint(origine)
  248. pinceau = QPen()
  249. pinceau.setWidth(10)
  250. couleur = self._pion.couleur if self._pion.couleur.isValid() else QColor(150, 150, 150)
  251. pinceau.setColor(couleur.darker(130))
  252. self.setPen(pinceau)
  253. self.setBrush(couleur)
  254. #ombre
  255. self.shadow = QGraphicsDropShadowEffect()
  256. self.shadow.setColor(QColor(50, 50, 50))
  257. self.shadow.setXOffset(1)
  258. self.shadow.setYOffset(2)
  259. self.shadow.setBlurRadius(3)
  260. self.shadow.setEnabled(True)
  261. self.setGraphicsEffect(self.shadow)
  262. self.setParentItem(self._pion)
  263. # self.setZValue(91)
  264. def maj(self):
  265. angleRotation = 60 if self._pion.plateau.formeCases == "H" else 90
  266. self.setRotation(self._pion.nbRotations * angleRotation)
  267. def ombre(self, actif):
  268. if actif:
  269. self.shadow.setXOffset(3)
  270. self.shadow.setYOffset(3)
  271. else:
  272. self.shadow.setXOffset(1)
  273. self.shadow.setYOffset(2)
  274. def supprimer(self):
  275. self.prepareGeometryChange()
  276. self.setParentItem(None)
  277. class BrillancePion(QGraphicsPolygonItem):
  278. """polygone representant le pion"""
  279. def __init__(self):
  280. super(BrillancePion, self).__init__()
  281. def __setstate__(self, state):
  282. self.__dict__ = state
  283. super(BrillancePion, self).__init__()
  284. def numero(self):
  285. return self._pion.numero
  286. def creer(self, pion):
  287. self._pion = pion
  288. self.setPolygon(self._pion.pion.polygon())
  289. self.setVisible(False)
  290. self.setFlag(QGraphicsItem.ItemIsFocusable)
  291. self.setAcceptHoverEvents(True)
  292. self.setParentItem(self._pion.pion)
  293. # self.setZValue(93)
  294. def activer(self, actif, opacite, couleur):
  295. if actif:
  296. self.setOpacity(opacite)
  297. self.setBrush(couleur)
  298. self.setPen(self._pion.pion.pen())
  299. self.setVisible(actif)
  300. def supprimer(self):
  301. self.prepareGeometryChange()
  302. self.setParentItem(None)
  303. class ImgPion(QGraphicsPixmapItem):
  304. def __init__(self):
  305. super(ImgPion, self).__init__()
  306. self._pion = None
  307. self.rimage = RImage() #ressource: image
  308. self.kx = 10 #coeff d'agrandissement horizontal
  309. self.ky = 10 #coeff d'agrandissement vertical
  310. self.dx = 0 #decalage horizontal
  311. self.dy = 0 #decalage vertical
  312. self.rotation = 0 #rotation(en degres)
  313. self.pivote = False #l'image pivote avec le pion?
  314. self.masqueAuto = False #creer un masque automatiquement
  315. def __setstate__(self, state):
  316. self.__dict__ = state
  317. super(ImgPion, self).__init__()
  318. def numero(self):
  319. num = self._pion.numero if self._pion else -1
  320. return num
  321. def creer(self, pion):
  322. self._pion = pion
  323. if not self.rimage.estValide(): return
  324. pix = self.rimage.pix()
  325. if pix.isNull(): return
  326. self.setAcceptedMouseButtons(Qt.NoButton)
  327. if pix.height() >= pix.width():
  328. pix = pix.scaledToHeight( 120 * 0.9, Qt.SmoothTransformation)
  329. else:
  330. pix = pix.scaledToWidth( 120 * 0.9, Qt.SmoothTransformation)
  331. pix = pix.scaled( (self.kx / 10) * pix.width(), \
  332. (self.ky / 10) * pix.height(), \
  333. Qt.IgnoreAspectRatio, Qt.SmoothTransformation )
  334. self.setPixmap(pix)
  335. if self.rimage.idR() == self._pion.logo.idR():
  336. #si l'image est le logo, elle ne doit pas pivoter
  337. self.setParentItem(self._pion)
  338. else:
  339. self.setParentItem(self._pion.pion)
  340. # self.setZValue(92)
  341. def maj(self):
  342. """met a jour la taille, la position et l'orientation de l'image"""
  343. pix = self.pixmap()
  344. if pix.isNull(): return
  345. deltaX = self.dx + 0.5 * ( (120 * 1.1544) - pix.width() )
  346. deltaY = self.dy + 0.5 * ( 120 - pix.height())
  347. self.setRotation(self.rotation)
  348. self.setPos(QPointF(deltaX, deltaY))
  349. def supprimer(self):
  350. self.prepareGeometryChange()
  351. self.setParentItem(None)
  352. class EtiquettePion():
  353. def __init__(self):
  354. self.txt = ""
  355. self.taille_police = 28 #taille de la police
  356. self.gras = False #en gras
  357. self.dx = 0 #decalage horizontal
  358. self.dy = 0 #decalage vertical