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 ########################################################
20 from equiption
import *
24 from statistic
import *
27 from utilities
import *
28 from struct
import pack
31 """ This class represents a playable or non-playable fighter. """
34 """ Name of the fighter. Let's keep it down to 15 characters. """
37 """ Name of the Fighter's job type. """
40 """ Whether the user gets to decide a fighter's course of action or not. """
43 """ Time that the Fighter gets to move next. """
46 """ List of stats for a fighter, i.e. HP, MP, EXP ... """
49 """ All armor and weapons on the fighter's person. """
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."""
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). """
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.
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
80 # If there was a fighter tree specified
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'
91 jTree
= libxml2
.parseFile('./data/job/'+j
)
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
)
102 statTree
= fTree
.xpathEval("//stat")
103 for statElem
in statTree
:
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:
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")
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")))
141 # for the sake of more legible code
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
153 #print "Right" + right
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)
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')
179 self
.__equiption
= Equiption(head
.strip('\0'), body
.strip('\0'), left
.strip('\0'), right
.strip('\0'))
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
))
219 self
.__statusMod
= []
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))
230 if self
.__jobType
== 'monster':
231 self
.__stats
.append(Statistic("ai",1,1,False,0,0,0))
233 self
.__stats
.append(Statistic("ai",0,0,False,0,0,0))
235 # initialize statuses list
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())
244 """Get fighter's name."""
247 def setName(self
, n
):
248 """Change fighter's name."""
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
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()
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
):
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
:
302 etypes
= cont
.battle
.getEventTypeList()
304 # Can fighter move? (asleep?, paralysed?, dead?)
306 if self
.getStat("canMove").getCurrent() != 1:
310 # Generate per-turn status transactions
311 for s
in self
.__status
:
317 ai
= self
.getStat("ai").getCurrent()
319 # Playable fighters (ai=0) have battle menu
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()
326 oEvent
.setExecutor(self
)
328 #d.append(command[0])
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!)
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
352 target
= randint(x
,y
)
354 target
= randint(y
,x
)
357 d
.append(fparty
.getFighter(y
))
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)
366 x
= randint(0,len(self
.getEventTypes(etypes
))-1)
367 t
= cont
.battle
.getEventType(self
.getEventTypes(etypes
)[x
])
369 return Event(self
,d
,[],t
)
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."""
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":
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":
395 elif self
.__name
== "Slithera":
400 # remove the event types that don't have enough mp.
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
)
415 def receiveExp(self
,exp
):
416 """Fighter receives experience for leveling up."""
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
)
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())
437 hp
= self
.getStat("hp")
438 if x
<= hp
.getChance() * 100:
439 hp
.setMax(hp
.getMax() + hp
.getPlus())
443 str = self
.getStat("str")
444 if x
<= str.getChance() * 100:
445 str.setMax(str.getMax() + str.getPlus())
446 str.setCurrent(str.getMax())
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?
458 dex
= self
.getStat("dex")
459 if x
<= dex
.getChance() * 100:
460 dex
.setMax(dex
.getMax() + dex
.getPlus())
461 dex
.setCurrent(dex
.getMax())
465 agl
= self
.getStat("agl")
466 if x
<= agl
.getChance() * 100:
467 agl
.setMax(agl
.getMax() + agl
.getPlus())
468 agl
.setCurrent(agl
.getMax())
472 wis
= self
.getStat("wis")
473 if x
<= wis
.getChance() * 100:
474 wis
.setMax(wis
.getMax() + wis
.getPlus())
475 wis
.setCurrent(wis
.getMax())
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]:
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]:
518 """Get status(es) of fighter."""
521 def setStatus(self
,s
):
522 """Set status(es) of fighter."""
525 def addStatus(self
,s
):
526 """Add a status to status list for fighter."""
528 for i
in self
.__status
:
534 self
.__status
.append(s
)
537 print 'ERROR: Status already in list!'
539 def removeStatus(self
,s
):
540 """Remove statuses from status list for fighter."""
542 for i
in self
.__status
:
549 self
.__status
.remove(i
)
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
:
558 print 'ERROR: No such Fighter stat.'
560 def getAllStats(self
):
561 """ Get entire statistic object list """
564 def setAllStats(self
,stats
):
565 """ Set entire statistic object list """