Stubbed out Drawable and MenuDrawable, added commenting for epydoc.
[asgard.git] / fighter.py
blob0b3e0c891d5f016ce349530b0dd3ae427458e74e
1 ########################################################
2 #Copyright (c) 2006 Russ Adams, Sean Eubanks, Asgard Contributors
3 #This file is part of Asgard.
5 #Asgard is free software; you can redistribute it and/or modify
6 #it under the terms of the GNU General Public License as published by
7 #the Free Software Foundation; either version 2 of the License, or
8 #(at your option) any later version.
10 #Asgard is distributed in the hope that it will be useful,
11 #but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 #GNU General Public License for more details.
15 #You should have received a copy of the GNU General Public License
16 #along with Asgard; if not, write to the Free Software
17 #Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 ########################################################
19 from random import *
20 from equiption import *
21 from event import *
22 from status import *
23 from stat import *
24 from statistic import *
25 import libxml2
26 import struct
27 from utilities import *
28 from struct import pack
29 class Fighter:
30 """ This class represents a playable or non-playable fighter. """
32 name = ""
33 """ Name of the fighter. Let's keep it down to 15 characters. """
35 jobType = ""
36 """ Name of the Fighter's job type. """
38 playable = False
39 """ Whether the user gets to decide a fighter's course of action or not. """
41 timeOfMove = 0
42 """ Time that the Fighter gets to move next. """
44 stat = []
45 """ List of stats for a fighter, i.e. HP, MP, EXP ... """
47 equiption = None
48 """ All armor and weapons on the fighter's person. """
50 status = []
51 """ Statuses that are afflicting this Fighter. Statuses are not saved
52 in the data files (at the moment). Generally, rpgs don't save statuses
53 after battle. This should be talked about in the future."""
55 elemMod = []
56 """ A tuple-list. Each tuple contains the name of the element and how
57 it modifies the base change. If the element is not in this list,
58 modifier is assumed to be 0 (no modification). """
60 statusMod = []
61 """ A tuple-list. Each tuple contains the name of the status and what
62 it's chance of being afflicted is. If the status is not in this list,
63 chance is assumed to be 100% (=1.0, always inflicts). """
66 def __init__(self,fTree=None,binary=None):
67 """ Construct a Fighter object from a job type XML tree and either a fighter type XML tree or packed binary data.
68 * If "binary" specified:
69 Uses unpack() to instantiate the Fighter object based
70 off a string of binary data. Note that it needs a job class xml tree to
71 populate job class related stat members.
73 * If "binary" is "":
74 Uses XPath queries to instantiate the Fighter object based off a given XML
75 document tree. Note that it needs a job class xml tree to populate job class
76 related stat members.
77 """
79 # If there was a fighter tree specified
80 if fTree != None:
81 # Fighter members
82 self.__name = fTree.xpathEval("//name")[0].content
83 self.__playable = (fTree.xpathEval("//playable")[0].content == "True")
84 self.__jobType = fTree.xpathEval("//job")[0].content
86 # Find correct job XML file
87 j = fTree.xpathEval("//job")[0].content + '.xml'
89 # Create Job XML Tree
90 jTree = libxml2.parseFile('./data/job/'+j)
92 # Equiption
93 head = fTree.xpathEval("//equiption/head")[0].content
94 body = fTree.xpathEval("//equiption/body")[0].content
95 left = fTree.xpathEval("//equiption/left")[0].content
96 right = fTree.xpathEval("//equiption/right")[0].content
97 self.__equiption = Equiption(head,body,left,right)
99 #Statistics
100 self.__stats = []
101 statTree = fTree.xpathEval("//stat")
102 for statElem in statTree:
104 current = 0
105 afterBattle = False
107 # The following might should be moved to the Statistics Constructor in the future
108 sname = statElem.prop("name")
110 afterBattle = (statElem.prop("afterbattle") == "True")
112 if statElem.prop("current") != None:
113 current = int(statElem.prop("current"))
115 if statElem.prop("max") != None:
116 max = int(statElem.prop("max"))
118 if afterBattle == False and statElem.prop("current") == None:
119 current = max
121 (plus,chance,growthRate) = jobStatQuery(jTree, sname)
123 stat = Statistic(sname,current,max,afterBattle,plus,chance,growthRate)
124 self.__stats.append(stat)
126 elementTree = fTree.xpathEval("//modifiers/element")
127 statusTree = fTree.xpathEval("//modifiers/status")
128 self.__elemMod = []
129 for elementNode in elementTree:
130 self.__elemMod.append((elementNode.prop("name"),elementNode.prop("mod")))
132 self.__statusMod = []
133 for statusNode in statusTree:
134 self.__statusMod.append((statusNode.prop("name"),statusNode.prop("chance")))
136 jTree.freeDoc()
137 fTree.freeDoc()
138 elif binary != None:
140 # for the sake of more legible code
141 # break it up
143 # unpack data
145 (name,job,head,body,right,left,level,exp,exptnl_current,exptnl_max,hp_current,hp_max,mp_current,mp_max,strg,dex,agl,spd,wis,wil) = struct.unpack("!20s20s20s20s20s20sB7I6B",binary)
147 #These might be useful later.
148 #print "Name:" + name
149 #print "Job" + job
150 #print "Head" + head
151 #print "Body" + body
152 #print "Right" + right
153 #print "Left" + left
154 #print "Level" + str(level)
155 #print "Exp" + str(exp)
156 #print "Exptnl_current" + str(exptnl_current)
157 #print "Exptnl_max" + str(exptnl_max)
158 #print "Hp_Current" + str(hp_current)
159 #print "Hp_Max" + str(hp_max)
160 #print "Mp_Current" + str(mp_current)
161 #print "Mp_Max" + str(mp_max)
162 #print "Str" + str(strg)
163 #print "Dex" + str(dex)
164 #print "Agl" + str(agl)
165 #print "Spd" + str(spd)
166 #print "Wis" + str(wis)
167 #print "Wil" + str(wil)
169 # Meta Data
170 self.__name = name.strip('\0')
171 self.__jobType = job.strip('\0')
172 self.__playable = True
174 # Create Job XML Tree
175 jTree = libxml2.parseFile('./data/job/'+self.__jobType+'.xml')
177 # Equiption
178 self.__equiption = Equiption(head.strip('\0'), body.strip('\0'), left.strip('\0'), right.strip('\0'))
180 #Stats
181 self.__stats = []
182 (plus,chance,growthRate) = jobStatQuery(jTree, "level")
183 self.__stats.append(Statistic("level",level,level,False,plus,chance,growthRate))
185 (plus,chance,growthRate) = jobStatQuery(jTree, "exp")
186 self.__stats.append(Statistic("exp",level,level,False,plus,chance,growthRate))
188 (plus,chance,growthRate) = jobStatQuery(jTree, "exptnl")
189 self.__stats.append(Statistic("exptnl",exptnl_current,exptnl_max,True,plus,chance,growthRate))
191 (plus,chance,growthRate) = jobStatQuery(jTree, "hp")
192 self.__stats.append(Statistic("hp",hp_current,hp_max,True,plus,chance,growthRate))
194 (plus,chance,growthRate) = jobStatQuery(jTree, "mp")
195 self.__stats.append(Statistic("mp",mp_current,mp_max,True,plus,chance,growthRate))
197 (plus,chance,growthRate) = jobStatQuery(jTree, "str")
198 self.__stats.append(Statistic("str",strg,strg,False,plus,chance,growthRate))
200 (plus,chance,growthRate) = jobStatQuery(jTree, "dex")
201 self.__stats.append(Statistic("dex",dex,dex,False,plus,chance,growthRate))
203 (plus,chance,growthRate) = jobStatQuery(jTree, "agl")
204 self.__stats.append(Statistic("agl",agl,agl,False,plus,chance,growthRate))
206 (plus,chance,growthRate) = jobStatQuery(jTree, "spd")
207 self.__stats.append(Statistic("spd",spd,spd,False,plus,chance,growthRate))
209 (plus,chance,growthRate) = jobStatQuery(jTree, "wis")
210 self.__stats.append(Statistic("wis",wis,wis,False,plus,chance,growthRate))
212 (plus,chance,growthRate) = jobStatQuery(jTree, "wil")
213 self.__stats.append(Statistic("wil",wis,wis,False,plus,chance,growthRate))
215 jTree.freeDoc()
217 self.__elemMod = []
218 self.__statusMod = []
220 # tom calculation
221 spd = self.getStat("spd").getCurrent()
222 tom = round((1/float(spd)*100))
223 self.__stats.append(Statistic("tom",tom,tom,False,0,0,0))
225 # set canmove to true
226 self.__stats.append(Statistic("canMove",1,1,False,0,0,0))
228 # set fighter ai
229 if self.__jobType == 'monster':
230 self.__stats.append(Statistic("ai",1,1,False,0,0,0))
231 else:
232 self.__stats.append(Statistic("ai",0,0,False,0,0,0))
234 # initialize statuses list
235 self.__status = []
237 def toBinaryString(self):
238 """Convert Fighter data to binary string, using struct's pack method."""
239 binStr = pack("!B20s20s20s20s20s20sB7I6B",0,self.__name,self.__jobType,self.__equiption.getHead(),self.__equiption.getArmor(),self.__equiption.getRight(),self.__equiption.getLeft(),self.getStat("level").getMax(),self.getStat("exp").getCurrent(),self.getStat("exptnl").getCurrent(),self.getStat("exptnl").getMax(),self.getStat("hp").getCurrent(),self.getStat("hp").getMax(),self.getStat("mp").getCurrent(),self.getStat("mp").getMax(),self.getStat("str").getMax(),self.getStat("dex").getMax(),self.getStat("agl").getMax(),self.getStat("spd").getMax(),self.getStat("wis").getMax(),self.getStat("wil").getMax())
240 return binStr
242 def getName(self):
243 """Get fighter's name."""
244 return self.__name
246 def setName(self, n):
247 """Change fighter's name."""
248 self.__name = n
250 def getJobType(self):
251 """Get Fighter's job type"""
252 return self.__jobType
254 def setJobType(self,jobType):
255 """ Set Fighter's job type"""
256 self.__jobType = jobType
258 def getEquiption(self):
259 """Get equiption for fighter."""
260 return self.__equiption
262 def setEquiption(self, eq):
263 """Set equiption for fighter."""
264 self.__equiption = eq
266 def getPlayable(self):
267 """Is fighter playable?"""
268 return self.__playable
270 def setPlayable(self, plyble):
271 """Set fighter's playability."""
272 self.__playable = plyble
274 def getAttack(self):
275 """Computes value of attack, taking into account all modifiers."""
276 return self.getStat("str").getCurrent() + self.__equiption.computeAtkMod()
278 def getDefense(self):
279 """Computes value of defense, taking into account all modifiers."""
280 return self.__equiption.computeDefMod()
282 def getEvade(self):
283 """Computes value of evade, taking into account all modifiers."""
284 return self.getStat("agl").getCurrent() + self.__equiption.computeEvaMod()
287 def makeEvent(self, eparty, fparty, currentTime, cont):
288 """
289 This method does a lot of stuff. If the Fighter is not ready to move or is being prevented from making a move (i.e. canmove < 1), no event can be made. Fighters have an "ai" statistic:
290 ai = 0: playable fighter; user selects move.
291 ai = 1: monster fighter; move randomly decided and target(s) randomly decided from hero party.
292 ai = 2: fighter afflicted with madness; move and target(s) randomly select from either party.
294 PerTurn status transactions are processed at the beginning of makeEvent.
295 PerTurnOnMove status transactions are processed when an event is generated.
297 if self.getStat("tom").getCurrent() != currentTime:
298 return None
301 etypes = cont.getBattle().getEventTypeList()
303 # Can fighter move? (asleep?, paralysed?, dead?)
305 if self.getStat("canMove").getCurrent() != 1:
306 return None
309 # Generate per-turn status transactions
310 for s in self.__status:
311 s.genPerTurn()
313 d = []
315 # Get ai of fighter
316 ai = self.getStat("ai").getCurrent()
318 # Playable fighters (ai=0) have battle menu
319 if ai == 0:
320 # Show battle menu and choose event from battle menu
321 command = cont.getBattleCommand(eparty,fparty,self)
322 d.append(command[0])
323 t = command[1]
325 # Monsters
326 elif ai == 1:
327 x = randint(0,eparty.sizeOfParty()-1)
328 while not eparty.getFighter(x).isAlive():
329 x = randint(0,eparty.sizeOfParty()-1)
331 d.append(eparty.getFighter(x))
333 x = randint(0,len(self.getEventTypes(etypes))-1)
334 t = cont.getBattle().getEventType(self.getEventTypes(etypes)[x])
335 # Madness (attack whatever!)
336 elif ai == 2:
337 x = randint(0,eparty.sizeOfParty()-1)
338 y = randint(0,fparty.sizeOfParty()-1)
339 while not eparty.getFighter(x).isAlive():
340 x = randint(0,eparty.sizeOfParty()-1)
341 while not fparty.getFighter(y).isAlive():
342 y = randint(0,fparty.sizeOfParty()-1)
343 # randint needs a range from SMALL to BIG
344 if x <= y:
345 target = randint(x,y)
346 elif x > y:
347 target = randint(y,x)
348 # Attacking friendly
349 if target == y:
350 d.append(fparty.getFighter(y))
351 # Attacking enemy
352 elif target == x:
353 d.append(eparty.getFighter(x))
355 # fighter cannot choose QUIT while mad (only for playable fighters)
356 if self.__jobType != "monster":
357 x = randint(0,len(self.getEventTypes(etypes))-2)
358 else:
359 x = randint(0,len(self.getEventTypes(etypes))-1)
360 t = cont.getBattle().getEventType(self.getEventTypes(etypes)[x])
362 return Event(self,d,[],t)
364 def isAlive(self):
365 """If hp is greater than 0... return true"""
366 return (self.getStat("hp").getCurrent() > 0)
368 def getEventTypes(self,eventTypes):
369 """Return list of events available to fighter. Currently hard-coded."""
370 etypes = []
372 if self.__jobType == "blackmage":
373 etypes = ["attack","fire1","ice1","lit1","poison","sleep","rabies","quit"]
374 elif self.__jobType == "swordsman":
375 etypes = ["attack","cure1","heal","life","fog","harm","quit"]
376 elif self.__name == "Roc":
377 etypes = ["attack","lit1"]
378 elif self.__name == "Imp":
379 etypes = ["attack"]
380 elif self.__name == "Briaru":
381 etypes = ["attack","chokeout","sleep"]
382 elif self.__name == "Wolf":
383 etypes = ["attack","rabies"]
384 elif self.__name == "Griffiend":
385 etypes = ["attack","fright"]
386 elif self.__name == "Stonos":
387 etypes = ["attack"]
388 elif self.__name == "Slithera":
389 etypes = ["attack"]
390 else:
391 etypes = ["attack"]
393 # remove the event types that don't have enough mp.
394 found = True
395 while found:
396 found = False
397 for s_etype in etypes:
398 for o_etype in eventTypes:
399 if o_etype.getName() == s_etype:
400 if o_etype.getMpCost() > self.getStat("mp").getCurrent():
401 etypes.remove(s_etype)
402 found = True
403 break
405 return etypes
408 def receiveExp(self,exp):
409 """Fighter receives experience for leveling up."""
410 # Add on exp
411 exp_s = self.getStat("exp")
412 exp_s.setCurrent(exp_s.getCurrent() + exp)
413 exp_s.setMax(exp_s.getCurrent())
415 # Tax off exp from expTillNextLevel
416 exptnl_s = self.getStat("exptnl")
417 exptnl_s.setCurrent(exptnl_s.getCurrent() - exp)
419 # Level up?
420 while exptnl_s.getCurrent() <= 0:
421 # Increase level number
422 level_s = self.getStat("level")
423 level_s.setMax(level_s.getMax() + 1)
424 level_s.setCurrent(level_s.getMax())
426 ## Increase stats...
428 # Increase hp?
429 x = randint(1,100)
430 hp = self.getStat("hp")
431 if x <= hp.getChance() * 100:
432 hp.setMax(hp.getMax() + hp.getPlus())
434 # Increase strength?
435 x = randint(1,100)
436 str = self.getStat("str")
437 if x <= str.getChance() * 100:
438 str.setMax(str.getMax() + str.getPlus())
439 str.setCurrent(str.getMax())
442 # Increase speed?
443 x = randint(1,100)
444 spd = self.getStat("spd")
445 if x <= spd.getChance() * 100:
446 spd.setMax(spd.getMax() + spd.getPlus())
447 spd.setCurrent(spd.getMax())
449 # Increase dexterity?
450 x = randint(1,100)
451 dex = self.getStat("dex")
452 if x <= dex.getChance() * 100:
453 dex.setMax(dex.getMax() + dex.getPlus())
454 dex.setCurrent(dex.getMax())
456 # Increase agility?
457 x = randint(1,100)
458 agl = self.getStat("agl")
459 if x <= agl.getChance() * 100:
460 agl.setMax(agl.getMax() + agl.getPlus())
461 agl.setCurrent(agl.getMax())
463 # Increase wisdom?
464 x = randint(1,100)
465 wis = self.getStat("wis")
466 if x <= wis.getChance() * 100:
467 wis.setMax(wis.getMax() + wis.getPlus())
468 wis.setCurrent(wis.getMax())
470 # Increase will?
471 x = randint(1,100)
472 wil = self.getStat("wil")
473 if x <= wil.getChance() * 100:
474 wil.setMax(wil.getMax() + wil.getPlus())
475 wil.setCurrent(wil.getMax())
477 # Set new exp till next level maximum
478 exptnl_s.setMax(int(exptnl_s.getMax() * (1 + exptnl_s.getGrowthRate())))
480 # Carry over any experience that didn't go towards getting the last level
481 exptnl_s.setCurrent(int(exptnl_s.getMax() + exptnl_s.getCurrent()))
483 def getElemModifier(self,elem):
484 """ Retrieves this information currently just from elemMod. If
485 the elemental name specified is found in the elemMod, and return the associated
486 modifier. The modifier is a float x where -2.0 <= x <= 2.0. If x is negative,
487 elemental effects hurt (remove hp). If x is positive, they cure (add hp). Some
488 Elementals may be innate to the Fighter, others might come from armor (armor
489 portion not yet implemented). """
491 for elemMod in self.__elemMod:
492 if elem == elemMod[0]:
493 return elemMod[1]
495 return 0.0
497 def getStatusModifier(self,stat):
498 """ Retrieves the status name specified, and return the
499 associated modifier. The modifier is a float x where 0 <= x <= 1.0. If x is 0,
500 Fighter is immune to stat. If x > 0, it represents the chance of being
501 afflicted with a stat. Some Status defenses may be innate to Fighter, others
502 might come from armor (armor portion not yet implemented). """
504 for statusMod in self.__statusMod:
505 if stat == statusMod[0]:
506 return statusMod[1]
508 return 1.0
510 def getStatus(self):
511 """Get status(es) of fighter."""
512 return self.__status
514 def setStatus(self,s):
515 """Set status(es) of fighter."""
516 self.__status = s
518 def addStatus(self,s):
519 """Add a status to status list for fighter."""
520 found = False
521 for i in self.__status:
522 if i.getName() == s:
523 found = True
524 break
526 if not found:
527 self.__status.append(s)
528 s.genEntrance()
529 else:
530 print 'ERROR: Status already in list!'
532 def removeStatus(self,s):
533 """Remove statuses from status list for fighter."""
534 found = False
535 for i in self.__status:
536 if i.getName() == s:
537 found = True
538 break
540 if found == True:
541 i.genExit()
542 self.__status.remove(i)
543 else:
544 print 'ERROR: Status was not in list!'
546 def getStat(self,statName):
547 """ Get Statistic object for this Fighter by Statistic object name """
548 for stat in self.__stats:
549 if stat.getName() == statName:
550 return stat
551 print 'ERROR: No such Fighter stat.'
553 def getAllStats(self):
554 """ Get entire statistic object list """
555 return self.__stats
557 def setAllStats(self,stats):
558 """ Set entire statistic object list """
559 self.__stats = stats