| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- #from __future__ import unicode_literals
- # -*- coding: utf-8 -*-
- from __future__ import division
- import os
- from PyQt4.QtCore import *
- from PyQt4.QtGui import *
- from outilsSvg import *
- from Terrain import Terrain
- import Modes
- class Case(QGraphicsPolygonItem):
- """objet graphique representant une case du plateau"""
- def __init__(self, plateau, parent=None):
- super(Case, self).__init__(parent)
- #plateau
- self.plateau = plateau
-
- #attributs
- self.x = 0
- self.y = 0
- self.altitude = 0
- self.terrain = Terrain() #terrain par defaut
- ## self.couleur = None #couleur du fond par defaut
- self.bordure = QColor(85, 85, 85, 85) #couleur de la bordure par defaut
- self.centreGraphique = None
- self.occupeePar = {} #objet: altitudes occupees (sous forme de tuple, ex: (0,1,2) pour une creature occupant les cases d'altitude 0, 1 et 2)
- self.cachesActifs = [] #liste des caches places par le MJ (cache le terrain, les decors, les pions aux joueurs...)
- self.cachesInactifs = []
- #effet sur la case
- self.effetActif = ""
- #polygones d'affichage
- self.polygoneEffet = None
- self.polygoneAffichageSpecial = None
- self.etiquetteAltitude = None
- self.polygoneCache = None
-
- def __getstate__(self):
- """selectionne les attributs qui seront sauvegardes"""
- state = {key:value for key, value in self.__dict__.items() if key in ["x", "y", "altitude","terrain","bordure","couleur", \
- "ombre", "effetActif", "cachesActifs", "cachesInactifs"]}
- return (state)
- def __setstate__(self, state):
- """recupere les attributs sauvegardes"""
- self.__dict__ = state
-
- ######## fonctions de base : geometrie, aspect graphique ######
- def creer(self, x, y, couleur = QColor(0, 255, 0, 80)):
- """creation du polygone et enregistrement des donnees geometriques"""
- self.x = x
- self.y = y
- self.terrain.couleur = couleur
- #creation de l'objet graphique sur le plateau
- self.creerGraphique()
-
- def creerGraphique(self):
- """cree les objets graphiques representant la case"""
- #reinitialisation des variables
- self.couleurEffet = {"brule":QColor("orange"), "glace":QColor("white"), "poison":QColor("green"), "eau":QColor("blue")}
- self.imgEffet = {"brule":"effFeu.jpg", "glace":"effGlace.jpg", "poison":"effPoison.png", "eau":"effEau.png"}
- #enregistrement des cases voisines:
- self.voisins = self.lstVoisins(self.x, self.y)
- self.occupeePar = {}
-
- #enregistrement du centre
- if self.plateau.formeCases == "H": #refPlateau
- k = 0
- if 1 == (self.x % 2): k = 0.5
- self.centreGraphique = QPointF(((self.x*0.866)+0.5773)*self.plateau.hCase, (self.y+k+0.5)*self.plateau.hCase)
- else:
- self.centreGraphique = QPointF((self.x+0.5)*self.plateau.hCase, (self.y+0.5)*self.plateau.hCase)
-
- #cree le polygone de la case
- self.setPolygon(self.polygone(self.x, self.y))
- self.plateau.addItem(self) #refPlateau
-
- #interactions graphiques:
- self.setFlag(QGraphicsItem.ItemIsFocusable)
- self.setAcceptHoverEvents(True)
- self.setAcceptDrops(True)
- self.setZValue(0)
- self.setFiltersChildEvents(True)
- # #pour afficher les coordonnees des cases:
- self.etiquette = None
- # self.afficherEtiquette("{}-{}".format(self.x,self.y))
- self.logoDep = LogoDep(self)
- self.logoDep.creer()
-
- #apparence initiale de la case
- self.majTerrain(self.terrain)
- #polygone utilise pour afficher la cible du curseur
- self.polygoneCible = QGraphicsPolygonItem(self.polygone(self.x, self.y), parent=self)
- self.polygoneCible.setVisible(False)
- self.polygoneCible.setAcceptHoverEvents(False)
-
- #cache
- self.polygoneCache = PolygoneCache(self)
- self.polygoneCache.creer()
- self.majCache()
- def recreer(self, plateau):
- ## self.plateau = plateau #refPlateau
- self.plateau = plateau
- super(Case, self).__init__()
- #polygones d'affichage
- self.polygoneEffet = None
- self.polygoneAffichageSpecial = None
- self.polygoneCache = None
- self.etiquetteAltitude = None
-
- self.creerGraphique()
- self.majTerrain(self.terrain)
- self.majAffichageSpecial("")
- self.majEffet(self.effetActif)
- def polygone(self, x, y):
- """renvoie l'objet graphique hexagone de la case"""
- polygone = QPolygonF()
- if self.plateau.formeCases == "H":
- #si x est impair sur un plateau a cases hexagonales, le y est augmente de 0.5
- if 1 == (x % 2):
- y += 0.5
- polygone << QPointF(((x*0.866)+0.2886)*self.plateau.hCase, y*self.plateau.hCase) \
- << QPointF(((x*0.866)+0.866)*self.plateau.hCase, y*self.plateau.hCase) \
- << QPointF(((x*0.866)+1.1547)*self.plateau.hCase, (y+0.5)*self.plateau.hCase) \
- << QPointF(((x*0.866)+0.866)*self.plateau.hCase, (y+1)*self.plateau.hCase) \
- << QPointF(((x*0.866)+0.2886)*self.plateau.hCase, (y+1)*self.plateau.hCase) \
- << QPointF( (x*0.866)*self.plateau.hCase, (y+0.5)*self.plateau.hCase)
- else:
- polygone << QPointF(x*self.plateau.hCase, y*self.plateau.hCase) \
- << QPointF((x+1)*self.plateau.hCase, y*self.plateau.hCase) \
- << QPointF((x+1)*self.plateau.hCase, (y+1)*self.plateau.hCase) \
- << QPointF(x*self.plateau.hCase, (y+1)*self.plateau.hCase)
- return polygone
- def lstVoisins(self, x, y):
- """renvoie la liste des cases voisines
- seulement cases existantes sur le plateau / seulement cases adjacentes (cas des cases carrees)"""
- voisins = []
- if self.plateau.formeCases == "H":
- if 1 == (x % 2):
- lst = [(x, y-1), (x+1, y), (x+1, y+1), (x, y+1), (x-1, y+1), (x-1, y)]
- else:
- lst = [(x, y-1), (x+1, y-1), (x+1, y), (x, y+1), (x-1, y), (x-1, y-1)]
- else:
- lst = [(x, y-1), (x+1, y-1), (x+1, y), (x+1, y+1), (x, y+1), (x-1, y+1), (x-1, y), (x-1, y-1)]
-
- for coord in lst:
- if (coord[0] >= 0 and coord[1] >= 0 and coord[0] < self.plateau.nbCasesX and coord[1] < self.plateau.nbCasesY):
- voisins.append(coord)
- return voisins
-
- def afficherEtiquette(self, txt):
- """affiche l'etiquette avec le texte demande"""
- if self.etiquette == None:
- self.etiquette = QGraphicsSimpleTextItem(QString().fromUtf8(str(txt)), parent=self)
- k = 0
- if 1 == (self.x % 2): k = 0.5
- if self.plateau.formeCases == "H":
- self.etiquette.setPos(QPointF(((self.x*0.866)+0.2886)*self.plateau.hCase, (self.y+k+0.5)*self.plateau.hCase))
- else:
- self.etiquette.setPos(QPointF(self.x*self.plateau.hCase, self.y*self.plateau.hCase))
- else:
- self.etiquette.setText(QString().fromUtf8(str(txt)))
-
-
-
- ########################
- ### deplacement
- def estFranchissable(self, z=0):
- """la case est-elle franchissable?"""
- retour = True
- if self.terrain.franchissable == False:
- retour = False
- else:
- if self.estOccupee(z) == True:
- retour = False
- return retour
- def coutDep(self):
- #a implementer, en fonction des
- #capacites de deplacement du pion actuellement selectionne
- return 1
- ### occupation
- def majOccupation(self, objet, nouveauZ = None):
- """met a jour l'occupation de la case par les pions, decors..."""
- if objet != None:
- if not objet in self.occupeePar and nouveauZ != None:
- #on ajoute l'objet a la liste des objets occupant la case ou on met a jour son altitude
- casesOccupees = []
- for i in range(0, objet.hauteur):
- casesOccupees.append(self.altitude + nouveauZ + i)
- self.occupeePar[objet] = casesOccupees
-
- elif objet in self.occupeePar and nouveauZ == None:
- #on supprime l'objet de la liste des objets occupant la case
- del self.occupeePar[objet]
- else:
- pass
-
- def estOccupee(self, z=0):
- """renvoie vrai si la case correspondant a la hauteur z est occupee"""
- retour = False
- for objet in self.occupeePar:
- if objet.hauteur > 0 and z in self.occupeePar[objet]:
- retour = True
- return retour
- def estOccupeePar(self, z=0):
- """si la case correspondant a la hauteur z est occupee, renvoie l'objet en question
- sinon renvoie None"""
- retour = None
- for objet in self.occupeePar:
- if objet.hauteur > 0 and z in self.occupeePar[objet]:
- retour = objet
- return retour
- def pionOccupant(self):
- """si un pion occupe cette case, le renvoie"""
- retour = None
- for objet in self.occupeePar:
- if objet.__class__.__name__ == "Combattant":
- retour = objet
- return retour
- #autres interaction de combat
- def estObstacleVision(self, hauteurObs):
- """renvoie vrai si la case et l'eventuel decor qui l'occupe bloquent le champ de
- vision d'un observateur situe a la hauteur precisee"""
- retour = False
- if self.altitude > hauteurObs:
- retour = True
- else:
- hauteurObstacle = 0
- for objet in self.occupeePar:
- if objet.hauteur > hauteurObstacle:
- hauteurObstacle = objet.hauteur
- if (self.altitude + hauteurObstacle) > hauteurObs:
- retour = True
- return retour
- ########## fonctions de maj ###########
- def majTerrain(self, terrain = Terrain()):
- """met a jour le terrain de la case"""
- self.terrain = terrain
- if self.terrain.imgTexture.estValide():
- self.setBrush(QBrush(QImage(self.terrain.imgTexture.chemin())))
- else:
- if self.terrain.couleur.isValid():
- self.setBrush(QBrush(self.terrain.couleur))
- pinceau = QPen()
- pinceau.setColor(self.couleurBordure())
- pinceau.setWidth(1)
- self.setPen(pinceau)
- def majAltitude(self, altitude):
- """met a jour l'altitude de la case"""
- self.altitude = altitude
- self.afficherEtiquette(altitude)
- def majAffichageDeplacement(self, cout, valide = True):
- """lorsque cette case est sur le chemin d'un pion,
- un disque blanc ou rouge apparait indiquant le
- cout du deplacement jusqua cette case
- un cout de 0 fait disparaitre ce symbole"""
- self.logoDep.afficher(cout, valide)
- def majEstCibleCurseur(self, actif, valide=True):
- """affiche la case comme etant la cible du curseur (lors d'une creation de pion, d'un deplacement...)"""
- if actif:
- pinceau = QPen()
- pinceau.setWidth(5)
- brush = QBrush()
- brush.setStyle(13)
- pinceau.setColor(self.couleurProj(valide))
- brush.setColor(self.couleurProj(valide))
- self.polygoneCible.setPen(pinceau)
- self.polygoneCible.setBrush(brush)
- self.setZValue(99)
- else:
- self.setZValue(0)
- self.polygoneCible.setVisible(actif)
-
- def majEffet(self, effet):
- """met a jour l'effet actif sur la case"""
- if self.polygoneEffet == None:
- #on cree le polygone utilise pour afficher les effets
- self.polygoneEffet = QGraphicsPolygonItem(self.polygone(self.x, self.y), parent=self)
- pinceau = QPen()
- pinceau.setColor(self.bordure)
- pinceau.setWidth(1)
- self.polygoneEffet.setPen(pinceau)
- self.polygoneEffet.setVisible(False)
- if len(effet) > 0 and effet != "aucun":
- #gradient de couleur
- gradient = QRadialGradient(self.centreGraphique, 0.5*self.plateau.hCase)
- couleur0 = QColor(0,0,0,0)
- couleur20 = self.couleurEffet[effet]
- couleur20.setAlpha(70)
- couleur10 = self.couleurEffet[effet]
- couleur10.setAlpha(255)
- gradient.setColorAt(0, couleur0)
- gradient.setColorAt(0.8, couleur0)
- gradient.setColorAt(0.95, couleur10)
- gradient.setColorAt(0.98, couleur20)
- gradient.setColorAt(1, couleur10)
- self.polygoneEffet.setBrush(gradient)
-
- self.polygoneEffet.setVisible(True)
- self.effetActif = effet
- else:
- self.polygoneEffet.setVisible(False)
- def majAffichageSpecial(self, affichage=""):
- """donne a la case l'aspect demande en rendant visible/invisible le polygone d'affichage special"""
- #polygone d'affichage special (altitude, tactique...)
- if self.polygoneAffichageSpecial == None:
- self.polygoneAffichageSpecial = QGraphicsPolygonItem(self.polygone(self.x, self.y), parent=self)
- pinceau = QPen()
- pinceau.setColor(self.bordure)
- pinceau.setWidth(1)
- self.polygoneAffichageSpecial.setPen(pinceau)
- if affichage != "altitude" and self.etiquetteAltitude:
- self.etiquetteAltitude.setVisible(False)
-
- if affichage == "tactique":
- if self.terrain.franchissable:
- self.polygoneAffichageSpecial.setBrush(QColor(255,255,255))
- else:
- self.polygoneAffichageSpecial.setBrush(QColor(50,50,50))
- self.polygoneAffichageSpecial.setVisible(True)
-
- elif affichage == "altitude":
- if self.etiquetteAltitude == None:
- self.etiquetteAltitude = QGraphicsSimpleTextItem("{}".format(self.altitude), parent=self)
- police = QFont("Georgia", 18)
- police.setItalic(True)
- self.etiquetteAltitude.setFont(police)
- self.etiquetteAltitude.setPos(QPointF(((self.x*0.866)+0.65)*self.plateau.hCase, (self.y+0.7)*self.plateau.hCase))
- if self.altitude >= 0:
- couleur = QColor("red").lighter(200-(5*self.altitude))
- else:
- couleur = QColor("purple").lighter(200+(5*self.altitude))
- self.polygoneAffichageSpecial.setBrush(couleur)
- self.polygoneAffichageSpecial.setZValue(5)
- self.polygoneAffichageSpecial.setVisible(True)
- self.etiquetteAltitude.setText(QString.fromUtf8("{}".format(self.altitude)))
- self.etiquetteAltitude.setVisible(True)
- self.etiquetteAltitude.setZValue(6)
- else:
- self.polygoneAffichageSpecial.setVisible(False)
- #caches
- def ajouterCache(self, idCache, actif = True):
- if actif:
- if not idCache in self.cachesActifs:
- self.cachesActifs.append(idCache)
- self.majCache()
- else:
- if not idCache in self.cachesInactifs:
- self.cachesInactifs.append(idCache)
-
- def supprimerCache(self, idCache):
- if idCache in self.cachesActifs:
- self.cachesActifs.remove(idCache)
- self.majCache()
- elif idCache in self.cachesInactifs:
- self.cachesInactifs.remove(idCache)
- def activerCache(self, idCache, actif = True):
- if actif:
- if idCache in self.cachesInactifs:
- self.cachesInactifs.remove(idCache)
- self.cachesActifs.append(idCache)
- self.majCache()
- else:
- if idCache in self.cachesActifs:
- self.cachesActifs.remove(idCache)
- self.cachesInactifs.append(idCache)
- self.majCache()
-
- def majCache(self):
- self.polygoneCache.setVisible((len(self.cachesActifs) > 0))
- if len(self.cachesActifs) > 0:
- self.polygoneCache.majTransparence()
- def estCachee(self):
- """une case cachee ne recoit plus aucun evenement souris ou clavier"""
- retour = (len(self.cachesActifs) > 0 and \
- not issubclass(self.plateau.modeActif.__class__, Modes.ModeBaseCp))
- return retour
- #fonctions secondaires
- def couleurDep(self):
- """renvoie une couleur secondaire utilisee pour les champs de deplacements"""
- luminositeActuelle = self.couleurEffective().lightness()
- if luminositeActuelle < 150:
- retour = QColor(210,255,255,100)
- else:
- retour = QColor(210,255,255,100)
- return retour
- def couleurProj(self, valide):
- """renvoie une couleur secondaire utilisee pour les projections de deplacement"""
- luminositeActuelle = self.couleurEffective().lightness()
- if valide:
- retour = QColor("white")
- else:
- retour = QColor("red")
- if luminositeActuelle > 220:
- retour = retour.darker(120)
-
- return retour
- def couleurBordure(self):
- """renvoie la couleur utilisee pour les bordures de la case"""
- luminositeActuelle = self.couleurEffective().lightness()
- if luminositeActuelle > 150:
- retour = QColor(85, 85, 85, 130)
- elif luminositeActuelle > 100 and luminositeActuelle <= 150:
- retour = QColor(10, 10, 10, 130)
- else:
- retour = QColor(255, 255, 255, 180)
- return retour
- def couleurEffective(self):
- """renvoie la couleur effective de la case (utile quand la texture est une image)"""
- texture = self.brush()
- if texture.textureImage().isNull():
- couleurFond = texture.color()
- else:
- couleurFond = self.couleurDominante(texture.textureImage())
- return couleurFond
- def couleurDominante(self, img):
- """retourne la couleur dominante d'une QImage"""
- r = 0
- v = 0
- b = 0
- for i in range(0, img.width()):
- for j in range(0, img.height()):
- pixel = img.pixel(i,j)
- r += qRed(pixel)
- v += qGreen(pixel)
- b += qBlue(pixel)
- nb_pix = img.width() * img.height()
- r_moy = int(r / nb_pix)
- v_moy = int(v / nb_pix)
- b_moy = int(b / nb_pix)
- return QColor(r_moy, v_moy, b_moy)
- ######### evenements souris et clavier #################
- def mousePressEvent(self, event):
- """evenement lors du clic souris"""
- super(Case, self).mousePressEvent(event)
- if event.button() == 1: #sur clic gauche
- accepte = self.plateau.caseCliquee(self.x, self.y) #refPlateau
- if accepte: event.accept()
- else:
- event.ignore()
- def hoverEnterEvent(self, event):
- """met a jour l'affichage de la case au survol de la souris, si un pion est selectionne"""
- super(Case, self).hoverEnterEvent(event)
- if not self.estCachee():
- self.plateau.caseSurvol(self.x, self.y)
- def hoverLeaveEvent(self, event):
- """met a jour l'affichage de la case au survol de la souris, si un pion est selectionne"""
- super(Case, self).hoverLeaveEvent(event)
-
- ########################
- class LogoDep(QGraphicsEllipseItem):
- """logo utilise pour afficher le chemin que va suivre le pion lors d'un deplacement"""
- def __init__(self, parent = None):
- super(LogoDep, self).__init__()
- self.parent = parent
- self.couleur = None
- self.txt = None
- def creer(self):
-
- hC = self.parent.plateau.hCase
- rect = QRectF()
- rayon = 0.4*hC
- rect.setTopLeft(QPointF(self.parent.centreGraphique.x() - rayon, self.parent.centreGraphique.y() - rayon))
- rect.setBottomRight(QPointF(self.parent.centreGraphique.x() + rayon, self.parent.centreGraphique.y() + rayon))
- self.setRect(rect)
-
- self.txt = QGraphicsTextItem(self)
- police = QFont("Georgia", 24)
- police.setBold(True)
- self.txt.setFont(police)
- self.txt.setParentItem(self)
- self.txt.setPos(QPointF(self.parent.centreGraphique.x() - rayon, self.parent.centreGraphique.y() - 0.6*rayon))
- self.txt.setTextWidth(2*rayon)
- self.txt.setPlainText(QString.fromUtf8(""))
- self.centrerTexte()
- self.setVisible(False)
- self.setAcceptHoverEvents(False)
-
- self.setParentItem(self.parent)
- def afficher(self, valeur, valide):
- """lorsque cette case est sur le chemin d'un pion,
- un disque blanc ou rouge apparait indiquant le
- mode de deplacement (icone: 'dep_simple', 'nage', 'escalade', 'vol')
- un cout de 0 fait disparaitre ce symbole"""
- if valeur > 0:
- #couleur
- if valide:
- couleur = QColor(255, 255, 220, 200)
- else:
- couleur = QColor(240, 60, 20, 200)
- self.setBrush(couleur)
- self.setPen(QPen(couleur))
- #txt
- self.txt.setPlainText(QString.fromUtf8(str(valeur)))
- self.centrerTexte()
- self.setVisible(True)
- else:
- self.setVisible(False)
- def centrerTexte(self):
- f = QTextBlockFormat()
- f.setAlignment(Qt.AlignCenter)
- cursor = self.txt.textCursor()
- cursor.select(QTextCursor.Document)
- cursor.mergeBlockFormat(f)
- cursor.clearSelection()
- self.txt.setTextCursor(cursor)
-
- class PolygoneCache(QGraphicsPolygonItem):
- def __init__(self, case, parent=None):
- """initialisation de la fenetre"""
- super(PolygoneCache, self).__init__(parent)
- self.case = case
- self.case.plateau.fenetre.connect(self.case.plateau.fenetre.ui.cbt_vue, SIGNAL("zoomChange(int)"), self.zoomChange)
-
- def creer(self):
- polygone = self.case.polygone(self.case.x, self.case.y)
- self.setPolygon(polygone)
- self.majTransparence()
- self.setTransformOriginPoint(self.case.centreGraphique)
- self.setZValue(100)
- self.setVisible(False)
- self.setScale(1.05)
- # self.setAcceptHoverEvents(True)
- # self.setAcceptDrops(True)
- self.case.plateau.addItem(self)
-
- def afficher(self, actif = True):
- self.setVisible(actif)
- def majTransparence(self):
- self.setBrush(QColor(0,0,0,self.transparence()))
- pinceau = QPen()
- pinceau.setBrush(QColor(0,0,0,self.transparence()))
- pinceau.setWidth(1)
- self.setPen(pinceau)
- def transparence(self):
- if issubclass(self.case.plateau.modeActif.__class__, Modes.ModeBaseCp):
- retour = 100
- else:
- retour = 255
- return retour
- def activerSurbrillance(self, actif):
- if actif:
- couleur = QColor(150,150,150,100)
- else:
- couleur = QColor(0,0,0,100)
- self.setBrush(couleur)
-
- def zoomChange(self, nbZoom):
- """le zoom sur la dmGraphicsView a change, on met a jour le scale de l'objet,
- on cherche a eviter un phenomene qui fait que lorsque le zoom est lointain,
- on devine les couleurs entre les polygones du cache, le rendant a moitie inutile"""
- s = (1 + 0.01*(10-nbZoom))
- self.setScale(s)
-
- def sceneEvent(self, event):
- """filtre les evenements souris et clavier
- si faux: l'evenement est bloque"""
- autorise = not self.case.estCachee()
- if not autorise:
- event.accept()
- else:
- event.ignore()
- return autorise
- # def hoverEnterEvent(self, event):
- # print "1"
- # if not self.case.estCachee():
- # print "2"
- # self.case.plateau.caseSurvol(self.case.x, self.case.y)
- # event.accept()
-
|