asgard 0.2.5 has been moved to tags/trash
[asgard.git] / fighter.py
blob69e3c4020f31928ecb39d0d5ada2d186cc9a93ed
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 from time import *
30 class Fighter:
31 """ This class represents a playable or non-playable fighter. """
33 name = ""
34 """ Name of the fighter. Let's keep it down to 15 characters. """
36 jobType = ""
37 """ Name of the Fighter's job type. """
39 playable = False
40 """ Whether the user gets to decide a fighter's course of action or not. """
42 timeOfMove = 0
43 """ Time that the Fighter gets to move next. """
45 stat = []
46 """ List of stats for a fighter, i.e. HP, MP, EXP ... """
48 equiption = None
49 """ All armor and weapons on the fighter's person. """
51 status = []
52 """ Statuses that are afflicting this Fighter. Statuses are not saved
53 in the data files (at the moment). Generally, rpgs don't save statuses
54 after battle. This should be talked about in the future."""
56 elemMod = []
57 """ A tuple-list. Each tuple contains the name of the element and how
58 it modifies the base change. If the element is not in this list,
59 modifier is assumed to be 0 (no modification). """
61 statusMod = []
62 """ A tuple-list. Each tuple contains the name of the status and what
63 it's chance of being afflicted is. If the status is not in this list,
64 chance is assumed to be 100% (=1.0, always inflicts). """
67 def __init__(self,fTree=None,binary=None):
68 """ Construct a Fighter object from a job type XML tree and either a fighter type XML tree or packed binary data.
69 * If "binary" specified:
70 Uses unpack() to instantiate the Fighter object based
71 off a string of binary data. Note that it needs a job class xml tree to
72 populate job class related stat members.
74 * If "binary" is "":
75 Uses XPath queries to instantiate the Fighter object based off a given XML
76 document tree. Note that it needs a job class xml tree to populate job class
77 related stat members.
78 """
80 # If there was a fighter tree specified
81 if fTree != None:
82 # Fighter members
83 self.__name = fTree.xpathEval("//name")[0].content
84 self.__playable = (fTree.xpathEval("//playable")[0].content == "True")
85 self.__jobType = fTree.xpathEval("//job")[0].content
87 # Find correct job XML file
88 j = fTree.xpathEval("//job")[0].content + '.xml'
90 # Create Job XML Tree
91 jTree = libxml2.parseFile('./data/job/'+j)
93 # Equiption
94 head = fTree.xpathEval("//equiption/head")[0].content
95 body = fTree.xpathEval("//equiption/body")[0].content
96 left = fTree.xpathEval("//equiption/left")[0].content
97 right = fTree.xpathEval("//equiption/right")[0].content
98 self.__equiption = Equiption(head,body,left,right)
100 #Statistics
101 self.__stats = []
102 statTree = fTree.xpathEval("//stat")
103 for statElem in statTree:
105 current = 0
106 afterBattle = False
108 # The following might should be moved to the Statistics Constructor in the future
109 sname = statElem.prop("name")
111 afterBattle = (statElem.prop("afterbattle") == "True")
113 if statElem.prop("current") != None:
114 current = int(statElem.prop("current"))
116 if statElem.prop("max") != None:
117 max = int(statElem.prop("max"))
119 if afterBattle == False and statElem.prop("current") == None:
120 current = max
122 (plus,chance,growthRate) = jobStatQuery(jTree, sname)
124 stat = Statistic(sname,current,max,afterBattle,plus,chance,growthRate)
125 self.__stats.append(stat)
127 elementTree = fTree.xpathEval("//modifiers/element")
128 statusTree = fTree.xpathEval("//modifiers/status")
129 self.__elemMod = []
130 for elementNode in elementTree:
131 self.__elemMod.append((elementNode.prop("name"),elementNode.prop("mod")))
133 self.__statusMod = []
134 for statusNode in statusTree:
135 self.__statusMod.append((statusNode.prop("name"),statusNode.prop("chance")))
137 jTree.freeDoc()
138 fTree.freeDoc()
139 elif binary != None:
141 # for the sake of more legible code
142 # break it up
144 # unpack data
146 (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)
148 #These might be useful later.
149 #print "Name:" + name
150 #print "Job" + job
151 #print "Head" + head
152 #print "Body" + body
153 #print "Right" + right
154 #print "Left" + left
155 #print "Level" + str(level)
156 #print "Exp" + str(exp)
157 #print "Exptnl_current" + str(exptnl_current)
158 #print "Exptnl_max" + str(exptnl_max)
159 #print "Hp_Current" + str(hp_current)
160 #print "Hp_Max" + str(hp_max)
161 #print "Mp_Current" + str(mp_current)
162 #print "Mp_Max" + str(mp_max)
163 #print "Str" + str(strg)
164 #print "Dex" + str(dex)
165 #print "Agl" + str(agl)
166 #print "Spd" + str(spd)
167 #print "Wis" + str(wis)
168 #print "Wil" + str(wil)
170 # Meta Data
171 self.__name = name.strip('\0')
172 self.__jobType = job.strip('\0')
173 self.__playable = True
175 # Create Job XML Tree
176 jTree = libxml2.parseFile('./data/job/'+self.__jobType+'.xml')
178 # Equiption
179 self.__equiption = Equiption(head.strip('\0'), body.strip('\0'), left.strip('\0'), right.strip('\0'))
181 #Stats
182 self.__stats = []
183 (plus,chance,growthRate) = jobStatQuery(jTree, "level")
184 self.__stats.append(Statistic("level",level,level,False,plus,chance,growthRate))
186 (plus,chance,growthRate) = jobStatQuery(jTree, "exp")
187 self.__stats.append(Statistic("exp",level,level,False,plus,chance,growthRate))
189 (plus,chance,growthRate) = jobStatQuery(jTree, "exptnl")
190 self.__stats.append(Statistic("exptnl",exptnl_current,exptnl_max,True,plus,chance,growthRate))
192 (plus,chance,growthRate) = jobStatQuery(jTree, "hp")
193 self.__stats.append(Statistic("hp",hp_current,hp_max,True,plus,chance,growthRate))
195 (plus,chance,growthRate) = jobStatQuery(jTree, "mp")
196 self.__stats.append(Statistic("mp",mp_current,mp_max,True,plus,chance,growthRate))
198 (plus,chance,growthRate) = jobStatQuery(jTree, "str")
199 self.__stats.append(Statistic("str",strg,strg,False,plus,chance,growthRate))
201 (plus,chance,growthRate) = jobStatQuery(jTree, "dex")
202 self.__stats.append(Statistic("dex",dex,dex,False,plus,chance,growthRate))
204 (plus,chance,growthRate) = jobStatQuery(jTree, "agl")
205 self.__stats.append(Statistic("agl",agl,agl,False,plus,chance,growthRate))
207 (plus,chance,growthRate) = jobStatQuery(jTree, "spd")
208 self.__stats.append(Statistic("spd",spd,spd,False,plus,chance,growthRate))
210 (plus,chance,growthRate) = jobStatQuery(jTree, "wis")
211 self.__stats.append(Statistic("wis",wis,wis,False,plus,chance,growthRate))
213 (plus,chance,growthRate) = jobStatQuery(jTree, "wil")
214 self.__stats.append(Statistic("wil",wis,wis,False,plus,chance,growthRate))
216 jTree.freeDoc()
218 self.__elemMod = []
219 self.__statusMod = []
221 # tom calculation
222 spd = self.getStat("spd").getCurrent()
223 tom = round((1/float(spd)*100))
224 self.__stats.append(Statistic("tom",tom,tom,False,0,0,0))
226 # set canmove to true
227 self.__stats.append(Statistic("canMove",1,1,False,0,0,0))
229 # set fighter ai
230 if self.__jobType == 'monster':
231 self.__stats.append(Statistic("ai",1,1,False,0,0,0))
232 else:
233 self.__stats.append(Statistic("ai",0,0,False,0,0,0))
235 # initialize statuses list
236 self.__status = []
238 def toBinaryString(self):
239 """Convert Fighter data to binary string, using struct's pack method."""
240 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())
241 return binStr
243 def getName(self):
244 """Get fighter's name."""
245 return self.__name
247 def setName(self, n):
248 """Change fighter's name."""
249 self.__name = n
251 def getJobType(self):
252 """Get Fighter's job type"""
253 return self.__jobType
255 def setJobType(self,jobType):
256 """ Set Fighter's job type"""
257 self.__jobType = jobType
259 def getEquiption(self):
260 """Get equiption for fighter."""
261 return self.__equiption
263 def setEquiption(self, eq):
264 """Set equiption for fighter."""
265 self.__equiption = eq
267 def getPlayable(self):
268 """Is fighter playable?"""
269 return self.__playable
271 def setPlayable(self, plyble):
272 """Set fighter's playability."""
273 self.__playable = plyble
275 def getAttack(self):
276 """Computes value of attack, taking into account all modifiers."""
277 return self.getStat("str").getCurrent() + self.__equiption.computeAtkMod()
279 def getDefense(self):
280 """Computes value of defense, taking into account all modifiers."""
281 return self.__equiption.computeDefMod()
283 def getEvade(self):
284 """Computes value of evade, taking into account all modifiers."""
285 return self.getStat("agl").getCurrent() + self.__equiption.computeEvaMod()
288 def makeEvent(self, eparty, fparty, currentTime, cont):
289 """
290 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:
291 ai = 0: playable fighter; user selects move.
292 ai = 1: monster fighter; move randomly decided and target(s) randomly decided from hero party.
293 ai = 2: fighter afflicted with madness; move and target(s) randomly select from either party.
295 PerTurn status transactions are processed at the beginning of makeEvent.
296 PerTurnOnMove status transactions are processed when an event is generated.
298 if self.getStat("tom").getCurrent() != currentTime:
299 return None
302 etypes = cont.battle.getEventTypeList()
304 # Can fighter move? (asleep?, paralysed?, dead?)
306 if self.getStat("canMove").getCurrent() != 1:
307 return None
310 # Generate per-turn status transactions
311 for s in self.__status:
312 s.genPerTurn()
314 d = []
316 # Get ai of fighter
317 ai = self.getStat("ai").getCurrent()
319 # Playable fighters (ai=0) have battle menu
320 if ai == 0:
321 # Show battle menu and choose event from battle menu
322 cont.view.focus = cont.view.getDrawableById("commands")
323 cont.view.focus.selindex = 0
324 cont.view.drawCommands()
325 oEvent = cont.wait()
326 oEvent.setExecutor(self)
327 return oEvent
328 #d.append(command[0])
329 #t = command[1]
332 # Monsters
333 elif ai == 1:
334 x = randint(0,eparty.sizeOfParty()-1)
335 while not eparty.getFighter(x).isAlive():
336 x = randint(0,eparty.sizeOfParty()-1)
338 d.append(eparty.getFighter(x))
340 x = randint(0,len(self.getEventTypes(etypes))-1)
341 t = cont.battle.getEventType(self.getEventTypes(etypes)[x])
342 # Madness (attack whatever!)
343 elif ai == 2:
344 x = randint(0,eparty.sizeOfParty()-1)
345 y = randint(0,fparty.sizeOfParty()-1)
346 while not eparty.getFighter(x).isAlive():
347 x = randint(0,eparty.sizeOfParty()-1)
348 while not fparty.getFighter(y).isAlive():
349 y = randint(0,fparty.sizeOfParty()-1)
350 # randint needs a range from SMALL to BIG
351 if x <= y:
352 target = randint(x,y)
353 elif x > y:
354 target = randint(y,x)
355 # Attacking friendly
356 if target == y:
357 d.append(fparty.getFighter(y))
358 # Attacking enemy
359 elif target == x:
360 d.append(eparty.getFighter(x))
362 # fighter cannot choose QUIT while mad (only for playable fighters)
363 if self.__jobType != "monster":
364 x = randint(0,len(self.getEventTypes(etypes))-2)
365 else:
366 x = randint(0,len(self.getEventTypes(etypes))-1)
367 t = cont.battle.getEventType(self.getEventTypes(etypes)[x])
369 return Event(self,d,[],t)
371 def isAlive(self):
372 """If hp is greater than 0... return true"""
373 return (self.getStat("hp").getCurrent() > 0)
375 def getEventTypes(self,eventTypes):
376 """Return list of events available to fighter. Currently hard-coded."""
377 etypes = []
379 if self.__jobType == "blackmage":
380 etypes = ["attack","fire1","ice1","lit1","poison","sleep","rabies","quit"]
381 elif self.__jobType == "swordsman":
382 etypes = ["attack","cure1","heal","life","fog","harm","quit"]
383 elif self.__name == "Roc":
384 etypes = ["attack","lit1"]
385 elif self.__name == "Imp":
386 etypes = ["attack"]
387 elif self.__name == "Briaru":
388 etypes = ["attack","chokeout","sleep"]
389 elif self.__name == "Wolf":
390 etypes = ["attack","rabies"]
391 elif self.__name == "Griffiend":
392 etypes = ["attack","fright"]
393 elif self.__name == "Stonos":
394 etypes = ["attack"]
395 elif self.__name == "Slithera":
396 etypes = ["attack"]
397 else:
398 etypes = ["attack"]
400 # remove the event types that don't have enough mp.
401 found = True
402 while found:
403 found = False
404 for s_etype in etypes:
405 for o_etype in eventTypes:
406 if o_etype.getName() == s_etype:
407 if o_etype.getMpCost() > self.getStat("mp").getCurrent():
408 etypes.remove(s_etype)
409 found = True
410 break
412 return etypes
415 def receiveExp(self,exp):
416 """Fighter receives experience for leveling up."""
417 # Add on exp
418 exp_s = self.getStat("exp")
419 exp_s.setCurrent(exp_s.getCurrent() + exp)
420 exp_s.setMax(exp_s.getCurrent())
422 # Tax off exp from expTillNextLevel
423 exptnl_s = self.getStat("exptnl")
424 exptnl_s.setCurrent(exptnl_s.getCurrent() - exp)
426 # Level up?
427 while exptnl_s.getCurrent() <= 0:
428 # Increase level number
429 level_s = self.getStat("level")
430 level_s.setMax(level_s.getMax() + 1)
431 level_s.setCurrent(level_s.getMax())
433 ## Increase stats...
435 # Increase hp?
436 x = randint(1,100)
437 hp = self.getStat("hp")
438 if x <= hp.getChance() * 100:
439 hp.setMax(hp.getMax() + hp.getPlus())
441 # Increase strength?
442 x = randint(1,100)
443 str = self.getStat("str")
444 if x <= str.getChance() * 100:
445 str.setMax(str.getMax() + str.getPlus())
446 str.setCurrent(str.getMax())
449 # Increase speed?
450 x = randint(1,100)
451 spd = self.getStat("spd")
452 if x <= spd.getChance() * 100:
453 spd.setMax(spd.getMax() + spd.getPlus())
454 spd.setCurrent(spd.getMax())
456 # Increase dexterity?
457 x = randint(1,100)
458 dex = self.getStat("dex")
459 if x <= dex.getChance() * 100:
460 dex.setMax(dex.getMax() + dex.getPlus())
461 dex.setCurrent(dex.getMax())
463 # Increase agility?
464 x = randint(1,100)
465 agl = self.getStat("agl")
466 if x <= agl.getChance() * 100:
467 agl.setMax(agl.getMax() + agl.getPlus())
468 agl.setCurrent(agl.getMax())
470 # Increase wisdom?
471 x = randint(1,100)
472 wis = self.getStat("wis")
473 if x <= wis.getChance() * 100:
474 wis.setMax(wis.getMax() + wis.getPlus())
475 wis.setCurrent(wis.getMax())
477 # Increase will?
478 x = randint(1,100)
479 wil = self.getStat("wil")
480 if x <= wil.getChance() * 100:
481 wil.setMax(wil.getMax() + wil.getPlus())
482 wil.setCurrent(wil.getMax())
484 # Set new exp till next level maximum
485 exptnl_s.setMax(int(exptnl_s.getMax() * (1 + exptnl_s.getGrowthRate())))
487 # Carry over any experience that didn't go towards getting the last level
488 exptnl_s.setCurrent(int(exptnl_s.getMax() + exptnl_s.getCurrent()))
490 def getElemModifier(self,elem):
491 """ Retrieves this information currently just from elemMod. If
492 the elemental name specified is found in the elemMod, and return the associated
493 modifier. The modifier is a float x where -2.0 <= x <= 2.0. If x is negative,
494 elemental effects hurt (remove hp). If x is positive, they cure (add hp). Some
495 Elementals may be innate to the Fighter, others might come from armor (armor
496 portion not yet implemented). """
498 for elemMod in self.__elemMod:
499 if elem == elemMod[0]:
500 return elemMod[1]
502 return 0.0
504 def getStatusModifier(self,stat):
505 """ Retrieves the status name specified, and return the
506 associated modifier. The modifier is a float x where 0 <= x <= 1.0. If x is 0,
507 Fighter is immune to stat. If x > 0, it represents the chance of being
508 afflicted with a stat. Some Status defenses may be innate to Fighter, others
509 might come from armor (armor portion not yet implemented). """
511 for statusMod in self.__statusMod:
512 if stat == statusMod[0]:
513 return statusMod[1]
515 return 1.0
517 def getStatus(self):
518 """Get status(es) of fighter."""
519 return self.__status
521 def setStatus(self,s):
522 """Set status(es) of fighter."""
523 self.__status = s
525 def addStatus(self,s):
526 """Add a status to status list for fighter."""
527 found = False
528 for i in self.__status:
529 if i.getName() == s:
530 found = True
531 break
533 if not found:
534 self.__status.append(s)
535 s.genEntrance()
536 else:
537 print 'ERROR: Status already in list!'
539 def removeStatus(self,s):
540 """Remove statuses from status list for fighter."""
541 found = False
542 for i in self.__status:
543 if i.getName() == s:
544 found = True
545 break
547 if found == True:
548 i.genExit()
549 self.__status.remove(i)
550 else:
551 print 'ERROR: Status was not in list!'
553 def getStat(self,statName):
554 """ Get Statistic object for this Fighter by Statistic object name """
555 for stat in self.__stats:
556 if stat.getName() == statName:
557 return stat
558 print 'ERROR: No such Fighter stat.'
560 def getAllStats(self):
561 """ Get entire statistic object list """
562 return self.__stats
564 def setAllStats(self,stats):
565 """ Set entire statistic object list """
566 self.__stats = stats