factored out the EFFv2 saving into EFFImporter
[gemrb.git] / gemrb / GUIScripts / GUICommon.py
blob69e05f370b4da69ddaef79a13026a6e124be0af1
1 # -*-python-*-
2 # GemRB - Infinity Engine Emulator
3 # Copyright (C) 2003 The GemRB Project
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program 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 this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 # GUICommon.py - common functions for GUIScripts of all game types
22 import GemRB
23 import GUIClasses
24 import CommonTables
25 from ie_restype import RES_CHU, RES_2DA, RES_WMP, RES_ARE
26 from ie_spells import LS_MEMO, LSR_KNOWN, LSR_LEVEL, LSR_STAT
27 from GUIDefines import *
28 from ie_stats import *
29 from ie_action import ACT_QSLOT1, ACT_QSLOT2, ACT_QSLOT3, ACT_QSLOT4, ACT_QSLOT5
30 from ie_slots import SLOT_ALL
32 OtherWindowFn = None
33 NextWindowFn = None
35 CommonTables.Load ()
37 def CloseOtherWindow (NewWindowFn):
38 global OtherWindowFn,NextWindowFn
40 GemRB.LeaveContainer()
41 if OtherWindowFn and OtherWindowFn != NewWindowFn:
42 # allow detection of 'next window'
43 NextWindowFn = NewWindowFn
44 # switching from a window to something else, call old function
45 OtherWindowFn ()
46 OtherWindowFn = NewWindowFn
47 return 0
48 elif OtherWindowFn:
49 # something is calling us with its own function, so
50 # it is closing down, return true
51 OtherWindowFn = None
52 return 1
53 else:
54 # new window, no need to do setup
55 OtherWindowFn = NewWindowFn
56 NextWindowFn = None
57 return 0
59 def GetWindowPack():
60 width = GemRB.GetSystemVariable (SV_WIDTH)
61 height = GemRB.GetSystemVariable (SV_HEIGHT)
63 if GemRB.GameType == "pst":
64 default = "GUIWORLD"
65 else:
66 default = "GUIW"
68 # use a custom gui if there is one
69 gui = "CGUI" + str(width)[:2] + str(height)[:2]
70 if GemRB.HasResource (gui, RES_CHU, 1):
71 return gui
73 gui = None
74 if width == 640:
75 gui = default
76 elif width == 800:
77 gui = "GUIW08"
78 elif width == 1024:
79 gui = "GUIW10"
80 elif width == 1280:
81 gui = "GUIW12"
82 if gui:
83 if GemRB.HasResource (gui, RES_CHU, 1):
84 return gui
86 # fallback to the smallest resolution
87 return default
89 def RestPress ():
90 GemRB.RestParty(0,0,0)
91 return
93 def SelectFormation ():
94 GemRB.GameSetFormation (GemRB.GetVar ("Formation"))
95 return
97 def OpenFloatMenuWindow ():
98 if GameIsPST():
99 import FloatMenuWindow
100 FloatMenuWindow.OpenFloatMenuWindow()
101 else:
102 GemRB.GameControlSetTargetMode (TARGET_MODE_NONE)
104 def GetActorPaperDoll (actor):
105 anim_id = GemRB.GetPlayerStat (actor, IE_ANIMATION_ID)
106 level = GemRB.GetPlayerStat (actor, IE_ARMOR_TYPE)
107 row = "0x%04X" %anim_id
108 which = "LEVEL%d" %(level+1)
109 doll = CommonTables.Pdolls.GetValue (row, which)
110 if doll == "*":
111 print "GetActorPaperDoll: Missing paper doll for animation", row, which
112 return doll
114 def SelectAllOnPress ():
115 GemRB.GameSelectPC (0, 1)
117 def GearsClicked ():
118 #GemRB.SetPlayerStat(GemRB.GameGetFirstSelectedPC (),44,249990)
119 GemRB.GamePause (2, 0)
121 def GetMageSpells (Kit, Alignment, Level):
122 MageSpells = []
123 SpellType = 99
124 Table = GemRB.LoadTable ("aligns")
125 v = Table.FindValue (3, Alignment)
126 Usability = Kit | Table.GetValue(v, 5)
128 SpellsTable = GemRB.LoadTable ("spells")
129 for i in range(SpellsTable.GetValue ("MAGE", str(Level), 1) ):
130 SpellName = "SPWI%d%02d"%(Level,i+1)
131 ms = GemRB.GetSpell (SpellName, 1)
132 if ms == None:
133 continue
135 if Usability & ms['SpellExclusion']:
136 SpellType = 0
137 else:
138 SpellType = 1
139 if Kit & (1 << ms['SpellSchool']+5): # of matching specialist school
140 SpellType = 2
141 # Wild mage spells are of normal schools, so we have to find them
142 # separately. Generalists can learn any spell but the wild ones, so
143 # we check if the mage is wild and if a generalist wouldn't be able
144 # to learn the spell.
145 if Kit == 0x8000 and (0x4000 & ms['SpellExclusion']):
146 SpellType = 2
147 MageSpells.append ([SpellName, SpellType])
149 return MageSpells
151 def GetLearnableMageSpells (Kit, Alignment, Level):
152 Learnable = []
154 for Spell in GetMageSpells (Kit, Alignment, Level):
155 if Spell[1]:
156 Learnable.append (Spell[0])
157 return Learnable
159 def GetLearnablePriestSpells (Class, Alignment, Level):
160 Learnable =[]
162 Table=GemRB.LoadTable("aligns")
163 v = Table.FindValue(3, Alignment)
164 #usability is the bitset we look for
165 Usability=Table.GetValue(v, 5)
167 SpellsTable = GemRB.LoadTable ("spells")
168 for i in range(SpellsTable.GetValue ("PRIEST", str (Level), 1) ):
169 SpellName = "SPPR%d%02d"%(Level,i+1)
170 ms = GemRB.GetSpell(SpellName, 1)
171 if ms == None:
172 continue
173 if Class & ms['SpellDivine']:
174 continue
175 if Usability & ms['SpellExclusion']:
176 continue
177 Learnable.append (SpellName)
178 return Learnable
180 def SetupSpellLevels (pc, TableName, Type, Level):
181 #don't die on a missing reference
182 #FIXME: try to do this in a non-hard way?
183 if not GemRB.HasResource (TableName, RES_2DA):
184 if TableName == "MXSPLDRU":
185 SetupSpellLevels (pc, "MXSPLPRS", Type, Level)
186 return
188 Table = GemRB.LoadTable (TableName)
189 for i in range(Table.GetColumnCount ()):
190 # do a string lookup since some tables don't have entries for all levels
191 value = Table.GetValue (str(Level), str(i+1), 1)
192 # specialist mages get an extra spell if they already know that level
193 # FIXME: get a general routine to find specialists
194 school = GemRB.GetVar("MAGESCHOOL")
195 if Type == IE_SPELL_TYPE_WIZARD and school != 0:
196 if value > 0:
197 value += 1
198 GemRB.SetMemorizableSpellsCount (pc, value, Type, i)
199 return
201 def UnsetupSpellLevels (pc, TableName, Type, Level):
202 #don't die on a missing reference
203 #FIXME: try to do this in a non-hard way?
204 if not GemRB.HasResource (TableName, RES_2DA):
205 if TableName == "MXSPLDRU":
206 UnsetupSpellLevels (pc, "MXSPLPRS", Type, Level)
207 return
209 Table = GemRB.LoadTable (TableName)
210 for i in range(Table.GetColumnCount ()):
211 GemRB.SetMemorizableSpellsCount (pc, 0, Type, i)
212 return
214 def SetColorStat (Actor, Stat, Value):
215 t = Value & 0xFF
216 t |= t << 8
217 t |= t << 16
218 GemRB.SetPlayerStat (Actor, Stat, t)
219 return
221 def CheckStat100 (Actor, Stat, Diff):
222 mystat = GemRB.GetPlayerStat (Actor, Stat)
223 goal = GemRB.Roll (1,100, Diff)
224 if mystat>=goal:
225 return True
226 return False
228 def CheckStat20 (Actor, Stat, Diff):
229 mystat = GemRB.GetPlayerStat (Actor, Stat)
230 goal = GemRB.Roll (1,20, Diff)
231 if mystat>=goal:
232 return True
233 return False
235 def GameIsPST ():
236 return GemRB.GameType == "pst"
238 def GameIsIWD ():
239 return GemRB.GameType == "iwd"
241 def GameIsHOW ():
242 return GemRB.GameType == "how"
244 def GameIsIWD1 ():
245 return GemRB.GameType == "iwd" or GemRB.GameType == "how"
247 def GameIsIWD2 ():
248 return GemRB.GameType == "iwd2"
250 def GameIsBG1 ():
251 return GemRB.GameType == "bg1"
253 def GameIsBG2 ():
254 return GemRB.GameType == "bg2"
256 def GameIsTOB ():
257 return GemRB.HasResource ("worldm25", RES_WMP) and GemRB.GetVar("oldgame") == 0
259 def HasTOB ():
260 return GemRB.HasResource ("worldm25", RES_WMP)
262 def HasHOW ():
263 return GemRB.HasResource ("expmap", RES_WMP)
265 def HasTOTL ():
266 return GemRB.HasResource ("ar9700", RES_ARE)
268 def GetIWDSpellButtonCount ():
269 if HasHOW():
270 return 24
271 else:
272 return 20
274 def SetGamedaysAndHourToken ():
275 currentTime = GemRB.GetGameTime()
276 days = currentTime / 7200
277 hours = (currentTime % 7200) / 300
278 GemRB.SetToken ('GAMEDAYS', str (days))
279 GemRB.SetToken ('HOUR', str (hours))
281 # Returns -1 if not found; otherwise, the index of the spell
282 def HasSpell (Actor, SpellType, Level, Ref):
283 # loop through each spell in the spell level and check for a matching ref
284 for i in range (GemRB.GetKnownSpellsCount (Actor, SpellType, Level)):
285 Spell = GemRB.GetKnownSpell(Actor, SpellType, Level, i)
286 if Spell["SpellResRef"].upper() == Ref.upper(): # ensure case is the same
287 return i
289 # not found
290 return -1
292 # Adds class/kit abilities
293 def AddClassAbilities (pc, table, Level=1, LevelDiff=1, align=-1):
294 TmpTable = GemRB.LoadTable (table)
296 # gotta stay positive
297 if Level-LevelDiff < 0:
298 return
300 # we're doing alignment additions
301 if align == -1:
302 iMin = 0
303 iMax = TmpTable.GetRowCount ()
304 else:
305 # alignment is expected to be the row required
306 iMin = align
307 iMax = align+1
309 # make sure we don't go out too far
310 jMin = Level-LevelDiff
311 jMax = Level
312 if jMax > TmpTable.GetColumnCount ():
313 jMax = TmpTable.GetColumnCount ()
315 for i in range(iMin, iMax):
316 # apply each spell from each new class
317 for j in range (jMin, jMax):
318 ab = TmpTable.GetValue (i, j, 0)
319 if ab and ab != "****":
320 # seems all SPINs act like GA_*
321 if ab[:4] == "SPIN":
322 ab = "GA_" + ab
324 # apply spell (AP_) or gain spell (GA_)
325 if ab[:2] == "AP":
326 GemRB.ApplySpell (pc, ab[3:])
327 elif ab[:2] == "GA":
328 SpellIndex = HasSpell (pc, IE_SPELL_TYPE_INNATE, 0, ab[3:])
329 if SpellIndex == -1:
330 GemRB.LearnSpell (pc, ab[3:], LS_MEMO)
331 else:
332 # make room for one more memorization
333 max_mem_cnt = GemRB.GetMemorizableSpellsCount (pc, IE_SPELL_TYPE_INNATE, 0, 0)
334 GemRB.SetMemorizableSpellsCount (pc, max_mem_cnt+1, IE_SPELL_TYPE_INNATE, 0)
335 # memorize another spell instance
336 GemRB.MemorizeSpell (pc, IE_SPELL_TYPE_INNATE, 0, SpellIndex)
337 else:
338 print "ERROR, unknown class ability (type): ", ab
340 # remove all class abilities up to the given level
341 # for dual-classing mainly
342 def RemoveClassAbilities (pc, table, Level):
343 TmpTable = GemRB.LoadTable (table)
345 # gotta stay positive
346 if Level < 0:
347 return
349 # make sure we don't go out too far
350 jMax = Level
351 if jMax > TmpTable.GetColumnCount ():
352 jMax = TmpTable.GetColumnCount ()
354 for i in range(TmpTable.GetRowCount ()):
355 for j in range (jMax):
356 ab = TmpTable.GetValue (i, j, 0)
357 if ab and ab != "****":
358 # get the index
359 SpellIndex = HasSpell (pc, IE_SPELL_TYPE_INNATE, 0, ab[3:])
361 # seems all SPINs act like GA_*
362 if ab[:4] == "SPIN":
363 ab = "GA_" + ab
365 # apply spell (AP_) or gain spell (GA_)?
366 if ab[:2] == "AP":
367 GemRB.RemoveEffects (pc, ab[3:])
368 elif ab[:2] == "GA":
369 if SpellIndex >= 0:
370 # TODO: get the correct counts to avoid removing an innate ability
371 # given by more than one thing?
372 # RemoveSpell will unmemorize them all too
373 GemRB.RemoveSpell (pc, IE_SPELL_TYPE_INNATE, 0, SpellIndex)
374 else:
375 print "ERROR, unknown class ability (type): ", ab
377 def CannotLearnSlotSpell ():
378 pc = GemRB.GameGetSelectedPCSingle ()
380 # disqualify sorcerors immediately
381 if GemRB.GetPlayerStat (pc, IE_CLASS) == 19:
382 return LSR_STAT
384 slot_item = GemRB.GetSlotItem (pc, GemRB.GetVar ("ItemButton"))
385 spell_ref = GemRB.GetItem (slot_item['ItemResRef'], pc)['Spell']
386 spell = GemRB.GetSpell (spell_ref)
388 # maybe she already knows this spell
389 if HasSpell (pc, IE_SPELL_TYPE_WIZARD, spell['SpellLevel']-1, spell_ref) != -1:
390 return LSR_KNOWN
392 # level check (needs enough intelligence for this level of spell)
393 dumbness = GemRB.GetPlayerStat (pc, IE_INT)
394 if spell['SpellLevel'] > GemRB.GetAbilityBonus (IE_INT, 1, dumbness):
395 return LSR_LEVEL
397 return 0
399 def UpdateInventorySlot (pc, Button, Slot, Type, Equipped=False):
400 Button.SetFont ("NUMBER")
401 Button.SetBorder (0, 0,0,0,0, 128,128,255,64, 0,1)
402 Button.SetBorder (1, 2,2,2,2, 32,32,255,0, 0,0)
403 Button.SetBorder (2, 0,0,0,0, 255,128,128,64, 0,1)
404 Button.SetFlags (IE_GUI_BUTTON_ALIGN_RIGHT | IE_GUI_BUTTON_ALIGN_TOP | IE_GUI_BUTTON_PICTURE, OP_OR)
405 Button.SetText ("")
407 if Slot == None:
408 Button.SetFlags (IE_GUI_BUTTON_PICTURE, OP_NAND)
409 if Type == "inventory":
410 Button.SetTooltip (12013) # Personal Item
411 elif Type == "ground":
412 Button.SetTooltip (12011) # Ground Item
413 else:
414 Button.SetTooltip ("")
415 Button.EnableBorder (0, 0)
416 Button.EnableBorder (1, 0)
417 Button.EnableBorder (2, 0)
418 else:
419 item = GemRB.GetItem (Slot['ItemResRef'])
420 identified = Slot["Flags"] & IE_INV_ITEM_IDENTIFIED
421 magical = Slot["Flags"] & IE_INV_ITEM_MAGICAL
423 # StackAmount holds the *maximum* item count in the stack while Usages0 holds the actual
424 if item["StackAmount"] > 1:
425 Button.SetText (str (Slot["Usages0"]))
426 else:
427 Button.SetText ("")
429 # auto-identify mundane items; the actual indentification will happen on transfer
430 if not identified and item["LoreToID"] == 0:
431 identified = True
433 if not identified or item["ItemNameIdentified"] == -1:
434 Button.SetTooltip (item["ItemName"])
435 Button.EnableBorder (0, 1)
436 Button.EnableBorder (1, 0)
437 else:
438 Button.SetTooltip (item["ItemNameIdentified"])
439 Button.EnableBorder (0, 0)
440 if magical:
441 Button.EnableBorder (1, 1)
442 else:
443 Button.EnableBorder (1, 0)
445 if GemRB.CanUseItemType (SLOT_ALL, Slot['ItemResRef'], pc, Equipped):
446 Button.EnableBorder (2, 0)
447 else:
448 Button.EnableBorder (2, 1)
450 Button.SetItemIcon (Slot['ItemResRef'], 0)
452 return
454 def LearnPriestSpells (pc, level, mask):
455 """Learns all the priest spells through the given spell level.
457 Mask distinguishes clerical and druidic spells."""
458 if level > 7: # make sure we don't have too high a level
459 level = 7
461 # go through each level
462 alignment = GemRB.GetPlayerStat (pc, IE_ALIGNMENT)
463 for i in range (level):
464 learnable = GetLearnablePriestSpells (mask, alignment, i+1)
466 for spell in learnable:
467 # if the spell isn't learned, learn it
468 if HasSpell (pc, IE_SPELL_TYPE_PRIEST, i, spell) < 0:
469 GemRB.LearnSpell (pc, spell)
470 return
472 # PST uses a button, IWD2 two types, the rest are the same with two labels
473 def SetEncumbranceLabels (Window, ControlID, Control2ID, pc, invert_colors = False):
474 """Displays the encumbrance as a ratio of current to maximum."""
476 # Getting the character's strength
477 sstr = GemRB.GetPlayerStat (pc, IE_STR)
478 ext_str = GemRB.GetPlayerStat (pc, IE_STREXTRA)
480 # encumbrance
481 max_encumb = CommonTables.StrMod.GetValue (sstr, 3) + CommonTables.StrModEx.GetValue (ext_str, 3)
482 encumbrance = GemRB.GetPlayerStat (pc, IE_ENCUMBRANCE)
484 Control = Window.GetControl (ControlID)
485 if GameIsPST():
486 # FIXME: there should be a space before LB symbol (':')
487 Control.SetText (str (encumbrance) + ":\n\n\n\n" + str (max_encumb) + ":")
488 elif GameIsIWD2() and not Control2ID:
489 Control.SetText (str (encumbrance) + "/" + str(max_encumb) + GemRB.GetString(39537))
490 else:
491 Control.SetText (str (encumbrance) + ":")
492 if not Control2ID: # shouldn't happen
493 print "Missing second control parameter to SetEncumbranceLabels!"
494 return
495 Control2 = Window.GetControl (Control2ID)
496 Control2.SetText (str (max_encumb) + ":")
498 ratio = (0.0 + encumbrance) / max_encumb
499 if ratio > 1.0:
500 if invert_colors:
501 Control.SetTextColor (255, 0, 0, True)
502 else:
503 Control.SetTextColor (255, 0, 0)
504 elif ratio > 0.8:
505 if invert_colors:
506 Control.SetTextColor (255, 255, 0, True)
507 else:
508 Control.SetTextColor (255, 255, 0)
509 else:
510 if invert_colors:
511 Control.SetTextColor (255, 255, 255, True)
512 else:
513 Control.SetTextColor (255, 255, 255)
515 if Control2ID:
516 Control2.SetTextColor (255, 0, 0)
518 return
520 def GetActorClassTitle (actor):
521 """Returns the string representation of the actors class."""
523 ClassTitle = GemRB.GetPlayerStat (actor, IE_TITLE1)
525 if ClassTitle == 0:
526 Class = GemRB.GetPlayerStat (actor, IE_CLASS)
527 ClassIndex = CommonTables.Classes.FindValue ( 5, Class )
528 KitIndex = GetKitIndex (actor)
529 Multi = CommonTables.Classes.GetValue (ClassIndex, 4)
530 Dual = IsDualClassed (actor, 1)
532 if Multi and Dual[0] == 0: # true multi class
533 ClassTitle = CommonTables.Classes.GetValue (ClassIndex, 2)
534 ClassTitle = GemRB.GetString (ClassTitle)
535 else:
536 if Dual[0]: # dual class
537 # first (previous) kit or class of the dual class
538 if Dual[0] == 1:
539 ClassTitle = CommonTables.KitList.GetValue (Dual[1], 2)
540 elif Dual[0] == 2:
541 ClassTitle = CommonTables.Classes.GetValue (Dual[1], 2)
542 ClassTitle = GemRB.GetString (ClassTitle) + " / "
543 ClassTitle += GemRB.GetString (CommonTables.Classes.GetValue (Dual[2], 2))
544 else: # ordinary class or kit
545 if KitIndex:
546 ClassTitle = CommonTables.KitList.GetValue (KitIndex, 2)
547 else:
548 ClassTitle = CommonTables.Classes.GetValue (ClassIndex, 2)
549 ClassTitle = GemRB.GetString (ClassTitle)
550 else:
551 ClassTitle = GemRB.GetString (ClassTitle)
553 #GetActorClassTitle returns string now...
554 #if ClassTitle == "*":
555 # return 0
557 return ClassTitle
560 def GetKitIndex (actor):
561 """Return the index of the actors kit from KITLIST.2da.
563 Returns 0 if the class is not kitted."""
565 Class = GemRB.GetPlayerStat (actor, IE_CLASS)
566 Kit = GemRB.GetPlayerStat (actor, IE_KIT)
567 KitIndex = 0
569 if Kit & 0xc000 == 0x4000:
570 KitIndex = Kit & 0xfff
572 # carefully looking for kit by the usability flag
573 # since the barbarian kit id clashes with the no-kit value
574 if KitIndex == 0 and Kit != 0x4000:
575 KitIndex = CommonTables.KitList.FindValue (6, Kit)
576 if KitIndex == -1:
577 KitIndex = 0
579 return KitIndex
581 def IsDualClassed(actor, verbose):
582 """Returns an array containing the dual class information.
584 Return[0] is 0 if not dualclassed, 1 if the old class is a kit, 2 otherwise.
585 Return[1] contains either the kit or class index of the old class.
586 Return[2] contains the class index of the new class.
587 If verbose is false, only Return[0] contains useable data."""
589 if GameIsIWD2():
590 return (0,-1,-1)
592 DualedFrom = GemRB.GetPlayerStat (actor, IE_MC_FLAGS) & MC_WAS_ANY_CLASS
594 if verbose:
595 Class = GemRB.GetPlayerStat (actor, IE_CLASS)
596 ClassIndex = CommonTables.Classes.FindValue (5, Class)
597 Multi = CommonTables.Classes.GetValue (ClassIndex, 4)
598 DualInfo = []
599 KitIndex = GetKitIndex (actor)
601 if DualedFrom > 0: # first (previous) class of the dual class
602 MCColumn = CommonTables.Classes.GetColumnIndex ("MC_WAS_ID")
603 FirstClassIndex = CommonTables.Classes.FindValue (MCColumn, DualedFrom)
604 if KitIndex:
605 DualInfo.append (1)
606 DualInfo.append (KitIndex)
607 else:
608 DualInfo.append (2)
609 DualInfo.append (FirstClassIndex)
611 # use the first class of the multiclass bunch that isn't the same as the first class
612 Mask = 1
613 for i in range (1,16):
614 if Multi & Mask:
615 ClassIndex = CommonTables.Classes.FindValue (5, i)
616 if ClassIndex == FirstClassIndex:
617 Mask = 1 << i
618 continue
619 DualInfo.append (ClassIndex)
620 break
621 Mask = 1 << i
622 if len(DualInfo) != 3:
623 print "WARNING: Invalid dualclass combination, treating as a single class!"
624 print DualedFrom, Class, Multi, KitIndex, DualInfo
625 return (0,-1,-1)
627 return DualInfo
628 else:
629 return (0,-1,-1)
630 else:
631 if DualedFrom > 0:
632 return (1,-1,-1)
633 else:
634 return (0,-1,-1)
636 def IsDualSwap (actor):
637 """Returns true if the dualed classes are reverse of expection.
639 This can happen, because the engine gives dualclass characters the same ID as
640 their multiclass counterpart (eg. FIGHTER_MAGE = 3). Logic would dictate that
641 the new and old class levels would be stored in IE_LEVEL and IE_LEVEL2,
642 respectively; however, if one duals from a fighter to a mage in the above
643 example, the levels would actually be in reverse of expectation."""
645 Dual = IsDualClassed (actor, 1)
647 # not dual classed
648 if Dual[0] == 0:
649 return 0
651 # split the full class name into its individual parts
652 # i.e FIGHTER_MAGE becomes [FIGHTER, MAGE]
653 Class = GemRB.GetPlayerStat (actor, IE_CLASS)
654 Class = CommonTables.Classes.FindValue (5, Class)
655 Class = CommonTables.Classes.GetRowName (Class)
656 Class = Class.split("_")
658 # get our old class name
659 if Dual[0] == 2:
660 BaseClass = CommonTables.Classes.GetRowName (Dual[1])
661 else:
662 BaseClass = GetKitIndex (actor)
663 BaseClass = CommonTables.KitList.GetValue (BaseClass, 7)
664 BaseClass = CommonTables.Classes.FindValue (5, BaseClass)
665 BaseClass = CommonTables.Classes.GetRowName (BaseClass)
667 # if our old class is the first class, we need to swap
668 if Class[0] == BaseClass:
669 return 1
671 return 0
673 def IsMultiClassed (actor, verbose):
674 """Returns a tuple containing the multiclass information.
676 Return[0] contains the total number of classes.
677 Return[1-3] contain the ID of their respective classes.
678 If verbose is false, only Return[0] has useable data."""
680 # change this if it will ever be needed
681 if GameIsIWD2():
682 return (0,-1,-1,-1)
684 # get our base class
685 ClassIndex = CommonTables.Classes.FindValue (5, GemRB.GetPlayerStat (actor, IE_CLASS))
686 IsMulti = CommonTables.Classes.GetValue (ClassIndex, 4) # 0 if not multi'd
687 IsDual = IsDualClassed (actor, 0)
689 # dual-class char's look like multi-class chars
690 if (IsMulti == 0) or (IsDual[0] > 0):
691 return (0,-1,-1,-1)
692 elif verbose == 0:
693 return (IsMulti,-1,-1,-1)
695 # get all our classes (leave space for our number of classes in the return array)
696 Classes = [0]*3
697 NumClasses = 0
698 Mask = 1 # we're looking at multiples of 2
699 ClassNames = CommonTables.Classes.GetRowName(ClassIndex).split("_")
701 # loop through each class and test it as a mask
702 # TODO: make 16 dynamic? -- allows for custom classes (not just kits)
703 for i in range (1, 16):
704 if IsMulti&Mask: # it's part of this class
705 #we need to place the classes in the array based on their order in the name,
706 #NOT the order they are detected in
707 CurrentName = CommonTables.Classes.GetRowName (CommonTables.Classes.FindValue (5, i));
708 for j in range(len(ClassNames)):
709 if ClassNames[j] == CurrentName:
710 Classes[j] = i # mask is (i-1)^2 where i is class id
711 NumClasses = NumClasses+1
712 Mask = 1 << i # shift to the next multiple of 2 for testing
714 # in case we couldn't figure out to which classes the multi belonged
715 if NumClasses < 2:
716 return (0,-1,-1,-1)
718 # return the tuple
719 return (NumClasses, Classes[0], Classes[1], Classes[2])
721 def RemoveKnownSpells (pc, type, level1=1, level2=1, noslots=0, kit=0):
722 """Removes all known spells of a given type between two spell levels.
724 If noslots is true, all memorization counts are set to 0.
725 Kit is used to identify the priest spell mask of the spells to be removed;
726 this is only used when removing spells in a dualclass."""
728 # choose the correct limit based upon class type
729 if type == IE_SPELL_TYPE_WIZARD:
730 limit = 9
731 elif type == IE_SPELL_TYPE_PRIEST:
732 limit = 7
734 # make sure that we get the original kit, if we have one
735 if kit:
736 originalkit = GetKitIndex (pc)
738 if originalkit: # kitted; find the class value
739 originalkit = CommonTables.KitList.GetValue (originalkit, 7)
740 else: # just get the class value
741 originalkit = GemRB.GetPlayerStat (pc, IE_CLASS)
743 # this is is specifically for dual-classes and will not work to remove only one
744 # spell type from a ranger/cleric multi-class
745 if CommonTables.ClassSkills.GetValue (originalkit, 0, 0) != "*": # knows druid spells
746 originalkit = 0x8000
747 elif CommonTables.ClassSkills.GetValue (originalkit, 1, 0) != "*": # knows cleric spells
748 originalkit = 0x4000
749 else: # don't know any other spells
750 originalkit = 0
752 # don't know how this would happen, but better to be safe
753 if originalkit == kit:
754 originalkit = 0
755 elif type == IE_SPELL_TYPE_INNATE:
756 limit = 1
757 else: # can't do anything if an improper spell type is sent
758 return 0
760 # make sure we're within parameters
761 if level1 < 1 or level2 > limit or level1 > level2:
762 return 0
764 # remove all spells for each level
765 for level in range (level1-1, level2):
766 # we need the count because we remove each spell in reverse order
767 count = GemRB.GetKnownSpellsCount (pc, type, level)
768 mod = count-1
770 for spell in range (count):
771 # see if we need to check for kit
772 if type == IE_SPELL_TYPE_PRIEST and kit:
773 # get the spell's ref data
774 ref = GemRB.GetKnownSpell (pc, type, level, mod-spell)
775 ref = GemRB.GetSpell (ref['SpellResRef'], 1)
777 # we have to look at the originalkit as well specifically for ranger/cleric dual-classes
778 # we wouldn't want to remove all cleric spells and druid spells if we lost our cleric class
779 # only the cleric ones
780 if kit&ref['SpellDivine'] or (originalkit and not originalkit&ref['SpellDivine']):
781 continue
783 # remove the spell
784 GemRB.RemoveSpell (pc, type, level, mod-spell)
786 # remove memorization counts if desired
787 if noslots:
788 GemRB.SetMemorizableSpellsCount (pc, 0, type, level)
790 # return success
791 return 1
793 def CanDualClass(actor):
794 # human
795 if GemRB.GetPlayerStat (actor, IE_RACE) != 1:
796 return 1
798 # already dualclassed
799 Dual = IsDualClassed (actor,0)
800 if Dual[0] > 0:
801 return 1
803 DualClassTable = GemRB.LoadTable ("dualclas")
804 CurrentStatTable = GemRB.LoadTable ("abdcscrq")
805 Class = GemRB.GetPlayerStat (actor, IE_CLASS)
806 ClassIndex = CommonTables.Classes.FindValue (5, Class)
807 ClassName = CommonTables.Classes.GetRowName (ClassIndex)
808 KitIndex = GetKitIndex (actor)
809 if KitIndex == 0:
810 ClassTitle = ClassName
811 else:
812 ClassTitle = CommonTables.KitList.GetValue (KitIndex, 0)
813 Row = DualClassTable.GetRowIndex (ClassTitle)
815 # a lookup table for the DualClassTable columns
816 classes = [ "FIGHTER", "CLERIC", "MAGE", "THIEF", "DRUID", "RANGER" ]
817 matches = []
818 Sum = 0
819 for col in range (0, DualClassTable.GetColumnCount ()):
820 value = DualClassTable.GetValue (Row, col)
821 Sum += value
822 if value == 1:
823 matches.append (classes[col])
825 # cannot dc if all the columns of the DualClassTable are 0
826 if Sum == 0:
827 print "CannotDualClass: all the columns of the DualClassTable are 0"
828 return 1
830 # if the only choice for dc is already the same as the actors base class
831 if Sum == 1 and ClassName in matches and KitIndex == 0:
832 print "CannotDualClass: the only choice for dc is already the same as the actors base class"
833 return 1
835 AlignmentTable = GemRB.LoadTable ("alignmnt")
836 AlignsTable = GemRB.LoadTable ("aligns")
837 Alignment = GemRB.GetPlayerStat (actor, IE_ALIGNMENT)
838 AlignmentColName = AlignsTable.FindValue (3, Alignment)
839 AlignmentColName = AlignsTable.GetValue (AlignmentColName, 4)
840 Sum = 0
841 for classy in matches:
842 Sum += AlignmentTable.GetValue (classy, AlignmentColName)
844 # cannot dc if all the available classes forbid the chars alignment
845 if Sum == 0:
846 print "CannotDualClass: all the available classes forbid the chars alignment"
847 return 1
849 # check current class' stat limitations
850 ClassStatIndex = CurrentStatTable.GetRowIndex (ClassTitle)
851 for stat in range (6):
852 minimum = CurrentStatTable.GetValue (ClassStatIndex, stat)
853 name = CurrentStatTable.GetColumnName (stat)
854 if GemRB.GetPlayerStat (actor, eval ("IE_" + name[4:])) < minimum:
855 print "CannotDualClass: current class' stat limitations are too big"
856 return 1
858 # check new class' stat limitations - make sure there are any good class choices
859 TargetStatTable = GemRB.LoadTable ("abdcdsrq")
860 for match in matches:
861 ClassStatIndex = TargetStatTable.GetRowIndex (match)
862 for stat in range (6):
863 minimum = TargetStatTable.GetValue (ClassStatIndex, stat)
864 name = TargetStatTable.GetColumnName (stat)
865 if GemRB.GetPlayerStat (actor, eval ("IE_" + name[4:])) < minimum:
866 matches.remove (match)
867 break
868 if len(matches) == 0:
869 print "CannotDualClass: no good new class choices"
870 return 1
872 # must be at least level 2
873 if GemRB.GetPlayerStat (actor, IE_LEVEL) == 1:
874 print "CannotDualClass: level 1"
875 return 1
876 return 0
878 def SetupDamageInfo (pc, Button):
879 hp = GemRB.GetPlayerStat (pc, IE_HITPOINTS)
880 hp_max = GemRB.GetPlayerStat (pc, IE_MAXHITPOINTS)
881 state = GemRB.GetPlayerStat (pc, IE_STATE_ID)
883 if hp_max < 1:
884 ratio = 0.0
885 else:
886 ratio = (hp+0.0) / hp_max
888 if hp < 1 or (state & STATE_DEAD):
889 Button.SetOverlay (0, 64,64,64,200, 64,64,64,200)
890 else:
891 Button.SetOverlay (ratio, 140,0,0,205, 128,0,0,200)
892 ratio_str = "\n%d/%d" %(hp, hp_max)
893 Button.SetTooltip (GemRB.GetPlayerName (pc, 1) + ratio_str)
895 return ratio_str
898 GameWindow = GUIClasses.GWindow(0)
899 GameControl = GUIClasses.GControl(0,0)