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
30 """ This class represents a playable or non-playable fighter. """
33 """ Name of the fighter. Let's keep it down to 15 characters. """
36 """ Name of the Fighter's job type. """
39 """ Whether the user gets to decide a fighter's course of action or not. """
42 """ Time that the Fighter gets to move next. """
45 """ List of stats for a fighter, i.e. HP, MP, EXP ... """
48 """ All armor and weapons on the fighter's person. """
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."""
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). """
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.
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
79 # If there was a fighter tree specified
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'
90 jTree
= libxml2
.parseFile('./data/job/'+j
)
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
)
101 statTree
= fTree
.xpathEval("//stat")
102 for statElem
in statTree
:
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:
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")
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")))
140 # for the sake of more legible code
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
152 #print "Right" + right
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)
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')
178 self
.__equiption
= Equiption(head
.strip('\0'), body
.strip('\0'), left
.strip('\0'), right
.strip('\0'))
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
))
218 self
.__statusMod
= []
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))
229 if self
.__jobType
== 'monster':
230 self
.__stats
.append(Statistic("ai",1,1,False,0,0,0))
232 self
.__stats
.append(Statistic("ai",0,0,False,0,0,0))
234 # initialize statuses list
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())
243 """Get fighter's name."""
246 def setName(self
, n
):
247 """Change fighter's name."""
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
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()
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
):
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
:
301 etypes
= cont
.getBattle().getEventTypeList()
303 # Can fighter move? (asleep?, paralysed?, dead?)
305 if self
.getStat("canMove").getCurrent() != 1:
309 # Generate per-turn status transactions
310 for s
in self
.__status
:
316 ai
= self
.getStat("ai").getCurrent()
318 # Playable fighters (ai=0) have battle menu
320 # Show battle menu and choose event from battle menu
321 command
= cont
.getBattleCommand(eparty
,fparty
,self
)
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!)
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
345 target
= randint(x
,y
)
347 target
= randint(y
,x
)
350 d
.append(fparty
.getFighter(y
))
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)
359 x
= randint(0,len(self
.getEventTypes(etypes
))-1)
360 t
= cont
.getBattle().getEventType(self
.getEventTypes(etypes
)[x
])
362 return Event(self
,d
,[],t
)
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."""
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":
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":
388 elif self
.__name
== "Slithera":
393 # remove the event types that don't have enough mp.
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
)
408 def receiveExp(self
,exp
):
409 """Fighter receives experience for leveling up."""
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
)
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())
430 hp
= self
.getStat("hp")
431 if x
<= hp
.getChance() * 100:
432 hp
.setMax(hp
.getMax() + hp
.getPlus())
436 str = self
.getStat("str")
437 if x
<= str.getChance() * 100:
438 str.setMax(str.getMax() + str.getPlus())
439 str.setCurrent(str.getMax())
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?
451 dex
= self
.getStat("dex")
452 if x
<= dex
.getChance() * 100:
453 dex
.setMax(dex
.getMax() + dex
.getPlus())
454 dex
.setCurrent(dex
.getMax())
458 agl
= self
.getStat("agl")
459 if x
<= agl
.getChance() * 100:
460 agl
.setMax(agl
.getMax() + agl
.getPlus())
461 agl
.setCurrent(agl
.getMax())
465 wis
= self
.getStat("wis")
466 if x
<= wis
.getChance() * 100:
467 wis
.setMax(wis
.getMax() + wis
.getPlus())
468 wis
.setCurrent(wis
.getMax())
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]:
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]:
511 """Get status(es) of fighter."""
514 def setStatus(self
,s
):
515 """Set status(es) of fighter."""
518 def addStatus(self
,s
):
519 """Add a status to status list for fighter."""
521 for i
in self
.__status
:
527 self
.__status
.append(s
)
530 print 'ERROR: Status already in list!'
532 def removeStatus(self
,s
):
533 """Remove statuses from status list for fighter."""
535 for i
in self
.__status
:
542 self
.__status
.remove(i
)
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
:
551 print 'ERROR: No such Fighter stat.'
553 def getAllStats(self
):
554 """ Get entire statistic object list """
557 def setAllStats(self
,stats
):
558 """ Set entire statistic object list """