Merge branch 'events'.
[gemrb.git] / gemrb / GUIScripts / GUICommon.py
blob1a33731f352db7ad649cdbaa19904ce684c60cc9
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 from ie_restype import RES_CHU, RES_2DA, RES_WMP, RES_ARE
25 from ie_spells import LS_MEMO, LSR_KNOWN, LSR_LEVEL, LSR_STAT
26 from GUIDefines import *
27 from ie_stats import *
28 from ie_action import ACT_QSLOT1, ACT_QSLOT2, ACT_QSLOT3, ACT_QSLOT4, ACT_QSLOT5
29 from ie_slots import SLOT_ANY
31 OtherWindowFn = None
33 # only used in SetEncumbranceLabels, but that is called very often
34 StrModTable = StrModExTable = None
35 ClassTable = KitListTable = ClassSkillsTable = RaceTable = NextLevelTable = None
36 AppearanceAvatarTable = None
38 def CloseOtherWindow (NewWindowFn):
39 global OtherWindowFn
41 GemRB.LeaveContainer()
42 if OtherWindowFn and OtherWindowFn != NewWindowFn:
43 OtherWindowFn ()
44 OtherWindowFn = NewWindowFn
45 return 0
46 elif OtherWindowFn:
47 OtherWindowFn = None
48 return 1
49 else:
50 OtherWindowFn = NewWindowFn
51 return 0
53 def GetWindowPack():
54 width = GemRB.GetSystemVariable (SV_WIDTH)
55 height = GemRB.GetSystemVariable (SV_HEIGHT)
57 if GemRB.GameType == "pst":
58 default = "GUIWORLD"
59 else:
60 default = "GUIW"
62 # use a custom gui if there is one
63 gui = "CGUI" + str(width)[:2] + str(height)[:2]
64 if GemRB.HasResource (gui, RES_CHU, 1):
65 return gui
67 gui = None
68 if width == 640:
69 gui = default
70 elif width == 800:
71 gui = "GUIW08"
72 elif width == 1024:
73 gui = "GUIW10"
74 elif width == 1280:
75 gui = "GUIW12"
76 if gui:
77 if GemRB.HasResource (gui, RES_CHU, 1):
78 return gui
80 # fallback to the smallest resolution
81 return default
83 def RestPress ():
84 GemRB.RestParty(0,0,0)
85 return
87 def SelectFormation ():
88 GemRB.GameSetFormation (GemRB.GetVar ("Formation"))
89 return
91 def ActionStopPressed ():
92 for i in range (PARTY_SIZE):
93 if GemRB.GameIsPCSelected (i + 1):
94 GemRB.ClearActions (i + 1)
95 return
97 def OpenFloatMenuWindow ():
98 GemRB.GameControlSetTargetMode (TARGET_MODE_NONE)
100 def ActionTalkPressed ():
101 GemRB.GameControlSetTargetMode (TARGET_MODE_TALK,GA_NO_DEAD|GA_NO_ENEMY|GA_NO_HIDDEN)
103 def ActionAttackPressed ():
104 GemRB.GameControlSetTargetMode (TARGET_MODE_ATTACK,GA_NO_DEAD|GA_NO_SELF|GA_NO_HIDDEN)
106 def ActionDefendPressed ():
107 GemRB.GameControlSetTargetMode (TARGET_MODE_DEFEND,GA_NO_SELF|GA_NO_ENEMY|GA_NO_HIDDEN)
109 def ActionThievingPressed ():
110 GemRB.GameControlSetTargetMode (TARGET_MODE_PICK, GA_NO_DEAD|GA_NO_SELF|GA_NO_ENEMY|GA_NO_HIDDEN)
112 def ActionQItemPressed (action):
113 """Uses the given quick item."""
114 pc = GemRB.GameGetFirstSelectedPC ()
115 # quick slot
116 GemRB.UseItem (pc, -2, action)
117 return
119 def ActionQItem1Pressed ():
120 ActionQItemPressed (ACT_QSLOT1)
121 return
123 def ActionQItem2Pressed ():
124 ActionQItemPressed (ACT_QSLOT2)
125 return
127 def ActionQItem3Pressed ():
128 ActionQItemPressed (ACT_QSLOT3)
129 return
131 def ActionQItem4Pressed ():
132 ActionQItemPressed (ACT_QSLOT4)
133 return
135 def ActionQItem5Pressed ():
136 ActionQItemPressed (ACT_QSLOT5)
137 return
139 def GetActorPaperDoll (actor):
140 anim_id = GemRB.GetPlayerStat (actor, IE_ANIMATION_ID)
141 level = GemRB.GetPlayerStat (actor, IE_ARMOR_TYPE)
142 row = "0x%04X" %anim_id
143 which = "LEVEL%d" %(level+1)
144 return AppearanceAvatarTable.GetValue (row, which)
146 def SelectAllOnPress ():
147 GemRB.GameSelectPC (0, 1)
149 def GearsClicked ():
150 #GemRB.SetPlayerStat(GemRB.GameGetFirstSelectedPC (),44,249990)
151 GemRB.GamePause (2, 0)
153 def GetMageSpells (Kit, Alignment, Level):
154 MageSpells = []
155 SpellType = 99
156 Table = GemRB.LoadTable ("aligns")
157 v = Table.FindValue (3, Alignment)
158 Usability = Kit | Table.GetValue(v, 5)
160 SpellsTable = GemRB.LoadTable ("spells")
161 for i in range(SpellsTable.GetValue ("MAGE", str(Level), 1) ):
162 SpellName = "SPWI%d%02d"%(Level,i+1)
163 ms = GemRB.GetSpell (SpellName, 1)
164 if ms == None:
165 continue
167 if Usability & ms['SpellExclusion']:
168 SpellType = 0
169 else:
170 SpellType = 1
171 if Kit & (1 << ms['SpellSchool']+5): # of matching specialist school
172 SpellType = 2
173 # Wild mage spells are of normal schools, so we have to find them
174 # separately. Generalists can learn any spell but the wild ones, so
175 # we check if the mage is wild and if a generalist wouldn't be able
176 # to learn the spell.
177 if Kit == 0x8000 and (0x4000 & ms['SpellExclusion']):
178 SpellType = 2
179 MageSpells.append ([SpellName, SpellType])
181 return MageSpells
183 def GetLearnableMageSpells (Kit, Alignment, Level):
184 Learnable = []
186 for Spell in GetMageSpells (Kit, Alignment, Level):
187 if Spell[1]:
188 Learnable.append (Spell[0])
189 return Learnable
191 def GetLearnablePriestSpells (Class, Alignment, Level):
192 Learnable =[]
194 Table=GemRB.LoadTable("aligns")
195 v = Table.FindValue(3, Alignment)
196 #usability is the bitset we look for
197 Usability=Table.GetValue(v, 5)
199 SpellsTable = GemRB.LoadTable ("spells")
200 for i in range(SpellsTable.GetValue ("PRIEST", str (Level), 1) ):
201 SpellName = "SPPR%d%02d"%(Level,i+1)
202 ms = GemRB.GetSpell(SpellName, 1)
203 if ms == None:
204 continue
205 if Class & ms['SpellDivine']:
206 continue
207 if Usability & ms['SpellExclusion']:
208 continue
209 Learnable.append (SpellName)
210 return Learnable
212 def SetupSpellLevels (pc, TableName, Type, Level):
213 #don't die on a missing reference
214 #FIXME: try to do this in a non-hard way?
215 if not GemRB.HasResource (TableName, RES_2DA):
216 if TableName == "MXSPLDRU":
217 SetupSpellLevels (pc, "MXSPLPRS", Type, Level)
218 return
220 Table = GemRB.LoadTable (TableName)
221 for i in range(Table.GetColumnCount ()):
222 # do a string lookup since some tables don't have entries for all levels
223 value = Table.GetValue (str(Level), str(i+1), 1)
224 # specialist mages get an extra spell if they already know that level
225 # FIXME: get a general routine to find specialists
226 school = GemRB.GetVar("MAGESCHOOL")
227 if Type == IE_SPELL_TYPE_WIZARD and school != 0:
228 if value > 0:
229 value += 1
230 GemRB.SetMemorizableSpellsCount (pc, value, Type, i)
231 return
233 def UnsetupSpellLevels (pc, TableName, Type, Level):
234 #don't die on a missing reference
235 #FIXME: try to do this in a non-hard way?
236 if not GemRB.HasResource (TableName, RES_2DA):
237 if TableName == "MXSPLDRU":
238 UnsetupSpellLevels (pc, "MXSPLPRS", Type, Level)
239 return
241 Table = GemRB.LoadTable (TableName)
242 for i in range(Table.GetColumnCount ()):
243 GemRB.SetMemorizableSpellsCount (pc, 0, Type, i)
244 return
246 def SetColorStat (Actor, Stat, Value):
247 t = Value & 0xFF
248 t |= t << 8
249 t |= t << 16
250 GemRB.SetPlayerStat (Actor, Stat, t)
251 return
253 def CheckStat100 (Actor, Stat, Diff):
254 mystat = GemRB.GetPlayerStat (Actor, Stat)
255 goal = GemRB.Roll (1,100, Diff)
256 if mystat>=goal:
257 return True
258 return False
260 def CheckStat20 (Actor, Stat, Diff):
261 mystat = GemRB.GetPlayerStat (Actor, Stat)
262 goal = GemRB.Roll (1,20, Diff)
263 if mystat>=goal:
264 return True
265 return False
267 def GameIsPST ():
268 return GemRB.GameType == "pst"
270 def GameIsIWD ():
271 return GemRB.GameType == "iwd"
273 def GameIsHOW ():
274 return GemRB.GameType == "how"
276 def GameIsIWD1 ():
277 return GemRB.GameType == "iwd" or GemRB.GameType == "how"
279 def GameIsIWD2 ():
280 return GemRB.GameType == "iwd2"
282 def GameIsBG1 ():
283 return GemRB.GameType == "bg1"
285 def GameIsBG2 ():
286 return GemRB.GameType == "bg2"
288 def GameIsTOB ():
289 return GemRB.HasResource ("worldm25", RES_WMP) and GemRB.GetVar("oldgame") == 0
291 def HasTOB ():
292 return GemRB.HasResource ("worldm25", RES_WMP)
294 def HasHOW ():
295 return GemRB.HasResource ("expmap", RES_WMP)
297 def HasTOTL ():
298 return GemRB.HasResource ("ar9700", RES_ARE)
300 def GetIWDSpellButtonCount ():
301 if HasHOW():
302 return 24
303 else:
304 return 20
306 def SetGamedaysAndHourToken ():
307 currentTime = GemRB.GetGameTime()
308 days = currentTime / 7200
309 hours = (currentTime % 7200) / 300
310 GemRB.SetToken ('GAMEDAYS', str (days))
311 GemRB.SetToken ('HOUR', str (hours))
313 # Returns -1 if not found; otherwise, the index of the spell
314 def HasSpell (Actor, SpellType, Level, Ref):
315 # loop through each spell in the spell level and check for a matching ref
316 for i in range (GemRB.GetKnownSpellsCount (Actor, SpellType, Level)):
317 Spell = GemRB.GetKnownSpell(Actor, SpellType, Level, i)
318 if Spell["SpellResRef"].upper() == Ref.upper(): # ensure case is the same
319 return i
321 # not found
322 return -1
324 # Adds class/kit abilities
325 def AddClassAbilities (pc, table, Level=1, LevelDiff=1, align=-1):
326 TmpTable = GemRB.LoadTable (table)
328 # gotta stay positive
329 if Level-LevelDiff < 0:
330 return
332 # we're doing alignment additions
333 if align == -1:
334 iMin = 0
335 iMax = TmpTable.GetRowCount ()
336 else:
337 # alignment is expected to be the row required
338 iMin = align
339 iMax = align+1
341 # make sure we don't go out too far
342 jMin = Level-LevelDiff
343 jMax = Level
344 if jMax > TmpTable.GetColumnCount ():
345 jMax = TmpTable.GetColumnCount ()
347 for i in range(iMin, iMax):
348 # apply each spell from each new class
349 for j in range (jMin, jMax):
350 ab = TmpTable.GetValue (i, j, 0)
351 if ab and ab != "****":
352 # seems all SPINs act like GA_*
353 if ab[:4] == "SPIN":
354 ab = "GA_" + ab
356 # apply spell (AP_) or gain spell (GA_)
357 if ab[:2] == "AP":
358 GemRB.ApplySpell (pc, ab[3:])
359 elif ab[:2] == "GA":
360 SpellIndex = HasSpell (pc, IE_SPELL_TYPE_INNATE, 0, ab[3:])
361 if SpellIndex < 0: # don't know it yet
362 GemRB.LearnSpell (pc, ab[3:], LS_MEMO)
363 else: # memorize another one
364 GemRB.MemorizeSpell (pc, IE_SPELL_TYPE_INNATE, 0, SpellIndex)
365 else:
366 print "ERROR, unknown class ability (type): ", ab
368 # remove all class abilities up to a give level
369 # for dual-classing mainly
370 def RemoveClassAbilities (pc, table, Level):
371 TmpTable = GemRB.LoadTable (table)
373 # gotta stay positive
374 if Level < 0:
375 return
377 # make sure we don't go out too far
378 jMax = Level
379 if jMax > TmpTable.GetColumnCount ():
380 jMax = TmpTable.GetColumnCount ()
382 for i in range(TmpTable.GetRowCount ()):
383 for j in range (jMax):
384 ab = TmpTable.GetValue (i, j, 0)
385 if ab and ab != "****":
386 # get the index
387 SpellIndex = HasSpell (pc, IE_SPELL_TYPE_INNATE, 0, ab[3:])
389 # seems all SPINs act like GA_*
390 if ab[:4] == "SPIN":
391 ab = "GA_" + ab
393 # apply spell (AP_) or gain spell (GA_)?
394 if ab[:2] == "AP":
395 # TODO: implement
396 GemRB.RemoveEffects (pc, ab[3:])
397 elif ab[:2] == "GA":
398 if SpellIndex >= 0:
399 # TODO: get the correct counts to avoid removing an innate ability
400 # given by more than one thing?
401 GemRB.UnmemorizeSpell (pc, IE_SPELL_TYPE_INNATE, 0, SpellIndex)
402 GemRB.RemoveSpell (pc, IE_SPELL_TYPE_INNATE, 0, SpellIndex)
403 else:
404 print "ERROR, unknown class ability (type): ", ab
406 def CannotLearnSlotSpell ():
407 pc = GemRB.GameGetSelectedPCSingle ()
409 # disqualify sorcerors immediately
410 if GemRB.GetPlayerStat (pc, IE_CLASS) == 19:
411 return LSR_STAT
413 slot_item = GemRB.GetSlotItem (pc, GemRB.GetVar ("ItemButton"))
414 spell_ref = GemRB.GetItem (slot_item['ItemResRef'], pc)['Spell']
415 spell = GemRB.GetSpell (spell_ref)
417 # maybe she already knows this spell
418 if HasSpell (pc, IE_SPELL_TYPE_WIZARD, spell['SpellLevel']-1, spell_ref) != -1:
419 return LSR_KNOWN
421 # level check (needs enough intelligence for this level of spell)
422 dumbness = GemRB.GetPlayerStat (pc, IE_INT)
423 if spell['SpellLevel'] > GemRB.GetAbilityBonus (IE_INT, 1, dumbness):
424 return LSR_LEVEL
426 return 0
428 def UpdateInventorySlot (pc, Button, Slot, Type):
429 Button.SetFont ("NUMBER")
430 Button.SetBorder (0, 0,0,0,0, 128,128,255,64, 0,1)
431 Button.SetBorder (1, 2,2,2,2, 32,32,255,0, 0,0)
432 Button.SetBorder (2, 0,0,0,0, 255,128,128,64, 0,1)
433 Button.SetFlags (IE_GUI_BUTTON_ALIGN_RIGHT | IE_GUI_BUTTON_ALIGN_TOP | IE_GUI_BUTTON_PICTURE, OP_OR)
434 Button.SetText ("")
436 if Slot == None:
437 Button.SetFlags (IE_GUI_BUTTON_PICTURE, OP_NAND)
438 if Type == "inventory":
439 Button.SetTooltip (12013) # Personal Item
440 elif Type == "ground":
441 Button.SetTooltip (12011) # Ground Item
442 else:
443 Button.SetTooltip ("")
444 Button.EnableBorder (0, 0)
445 Button.EnableBorder (1, 0)
446 Button.EnableBorder (2, 0)
447 else:
448 item = GemRB.GetItem (Slot['ItemResRef'])
449 identified = Slot["Flags"] & IE_INV_ITEM_IDENTIFIED
450 magical = Slot["Flags"] & IE_INV_ITEM_MAGICAL
452 # TODO: figure out this mess
453 # if item["StackAmount"] > 1:
454 # Button.SetText (str (item["StackAmount"])) # wrong for potions, correct for arrows
455 # if Slot["Usages0"] > 1:
456 # Button.SetText (str (Slot["Usages0"])) # this has the correct value for potions, but not for gems (0)
457 if item["StackAmount"] > 1:
458 Button.SetText (str (Slot["Usages0"]))
459 else:
460 Button.SetText ("")
462 if not identified or item["ItemNameIdentified"] == -1:
463 Button.SetTooltip (item["ItemName"])
464 Button.EnableBorder (0, 1)
465 Button.EnableBorder (1, 0)
466 else:
467 Button.SetTooltip (item["ItemNameIdentified"])
468 Button.EnableBorder (0, 0)
469 if magical:
470 Button.EnableBorder (1, 1)
471 else:
472 Button.EnableBorder (1, 0)
474 if GemRB.CanUseItemType (SLOT_ANY, Slot['ItemResRef'], pc):
475 Button.EnableBorder (2, 0)
476 else:
477 Button.EnableBorder (2, 1)
479 Button.SetItemIcon (Slot['ItemResRef'], 0)
481 return
483 def LearnPriestSpells (pc, level, mask):
484 """Learns all the priest spells through the given spell level.
486 Mask distinguishes clerical and druidic spells."""
487 if level > 7: # make sure we don't have too high a level
488 level = 7
490 # go through each level
491 alignment = GemRB.GetPlayerStat (pc, IE_ALIGNMENT)
492 for i in range (level):
493 learnable = GetLearnablePriestSpells (mask, alignment, i+1)
495 for spell in learnable:
496 # if the spell isn't learned, learn it
497 if HasSpell (pc, IE_SPELL_TYPE_PRIEST, i, spell) < 0:
498 GemRB.LearnSpell (pc, spell)
499 return
501 # PST uses a button, IWD2 two types, the rest are the same with two labels
502 def SetEncumbranceLabels (Window, ControlID, Control2ID, pc):
503 """Displays the encumbrance as a ratio of current to maximum."""
505 # Getting the character's strength
506 sstr = GemRB.GetPlayerStat (pc, IE_STR)
507 ext_str = GemRB.GetPlayerStat (pc, IE_STREXTRA)
509 # encumbrance
510 max_encumb = StrModTable.GetValue (sstr, 3) + StrModExTable.GetValue (ext_str, 3)
511 encumbrance = GemRB.GetPlayerStat (pc, IE_ENCUMBRANCE)
513 Control = Window.GetControl (ControlID)
514 if GameIsPST():
515 # FIXME: there should be a space before LB symbol (':')
516 Control.SetText (str (encumbrance) + ":\n\n\n\n" + str (max_encumb) + ":")
517 elif GameIsIWD2() and not Control2ID:
518 Control.SetText (str (encumbrance) + "/" + str(max_encumb) + GemRB.GetString(39537))
519 else:
520 Control.SetText (str (encumbrance) + ":")
521 if not Control2ID: # shouldn't happen
522 print "Missing second control parameter to SetEncumbranceLabels!"
523 return
524 Control2 = Window.GetControl (Control2ID)
525 Control2.SetText (str (max_encumb) + ":")
527 ratio = (0.0 + encumbrance) / max_encumb
528 if ratio > 1.0:
529 Control.SetTextColor (255, 0, 0)
530 elif ratio > 0.8:
531 Control.SetTextColor (255, 255, 0)
532 else:
533 Control.SetTextColor (255, 255, 255)
535 if Control2ID:
536 Control2.SetTextColor (255, 0, 0)
538 return
540 def GetActorClassTitle (actor):
541 """Returns the string representation of the actors class."""
543 ClassTitle = GemRB.GetPlayerStat (actor, IE_TITLE1)
545 if ClassTitle == 0:
546 Class = GemRB.GetPlayerStat (actor, IE_CLASS)
547 ClassIndex = ClassTable.FindValue ( 5, Class )
548 KitIndex = GetKitIndex (actor)
549 Multi = ClassTable.GetValue (ClassIndex, 4)
550 Dual = IsDualClassed (actor, 1)
552 if Multi and Dual[0] == 0: # true multi class
553 ClassTitle = ClassTable.GetValue (ClassIndex, 2)
554 ClassTitle = GemRB.GetString (ClassTitle)
555 else:
556 if Dual[0]: # dual class
557 # first (previous) kit or class of the dual class
558 if Dual[0] == 1:
559 ClassTitle = KitListTable.GetValue (Dual[1], 2)
560 elif Dual[0] == 2:
561 ClassTitle = ClassTable.GetValue (Dual[1], 2)
562 ClassTitle = GemRB.GetString (ClassTitle) + " / "
563 ClassTitle += GemRB.GetString (ClassTable.GetValue (Dual[2], 2))
564 else: # ordinary class or kit
565 if KitIndex:
566 ClassTitle = KitListTable.GetValue (KitIndex, 2)
567 else:
568 ClassTitle = ClassTable.GetValue (ClassIndex, 2)
569 ClassTitle = GemRB.GetString (ClassTitle)
570 else:
571 ClassTitle = GemRB.GetString (ClassTitle)
573 #GetActorClassTitle returns string now...
574 #if ClassTitle == "*":
575 # return 0
577 return ClassTitle
580 def GetKitIndex (actor):
581 """Return the index of the actors kit from KITLIST.2da.
583 Returns 0 if the class is not kitted."""
585 Class = GemRB.GetPlayerStat (actor, IE_CLASS)
586 Kit = GemRB.GetPlayerStat (actor, IE_KIT)
587 KitIndex = 0
589 if Kit & 0xc000 == 0x4000:
590 KitIndex = Kit & 0xfff
592 # carefully looking for kit by the usability flag
593 # since the barbarian kit id clashes with the no-kit value
594 if KitIndex == 0 and Kit != 0x4000:
595 KitIndex = KitListTable.FindValue (6, Kit)
596 if KitIndex == -1:
597 KitIndex = 0
599 return KitIndex
601 def IsDualClassed(actor, verbose):
602 """Returns an array containing the dual class information.
604 Return[0] is 0 if not dualclassed, 1 if the old class is a kit, 2 otherwise.
605 Return[1] contains either the kit or class index of the old class.
606 Return[2] contains the class index of the new class.
607 If verbose is false, only Return[0] contains useable data."""
609 Dual = GemRB.GetPlayerStat (actor, IE_MC_FLAGS)
610 Dual = Dual & ~(MC_EXPORTABLE|MC_PLOT_CRITICAL|MC_BEENINPARTY|MC_HIDDEN)
612 if verbose:
613 Class = GemRB.GetPlayerStat (actor, IE_CLASS)
614 ClassIndex = ClassTable.FindValue (5, Class)
615 Multi = ClassTable.GetValue (ClassIndex, 4)
616 DualInfo = []
617 KitIndex = GetKitIndex (actor)
619 if (Dual & MC_WAS_ANY_CLASS) > 0: # first (previous) class of the dual class
620 MCColumn = ClassTable.GetColumnIndex ("MC_WAS_ID")
621 FirstClassIndex = ClassTable.FindValue (MCColumn, Dual & MC_WAS_ANY_CLASS)
622 if KitIndex:
623 DualInfo.append (1)
624 DualInfo.append (KitIndex)
625 else:
626 DualInfo.append (2)
627 DualInfo.append (FirstClassIndex)
629 # use the first class of the multiclass bunch that isn't the same as the first class
630 Mask = 1
631 for i in range (1,16):
632 if Multi & Mask:
633 ClassIndex = ClassTable.FindValue (5, i)
634 if ClassIndex == FirstClassIndex:
635 Mask = 1 << i
636 continue
637 DualInfo.append (ClassIndex)
638 break
639 Mask = 1 << i
640 return DualInfo
641 else:
642 return (0,-1,-1)
643 else:
644 if (Dual & MC_WAS_ANY_CLASS) > 0:
645 return (1,-1,-1)
646 else:
647 return (0,-1,-1)
649 def IsDualSwap (actor):
650 """Returns true if the dualed classes are reverse of expection.
652 This can happen, because the engine gives dualclass characters the same ID as
653 their multiclass counterpart (eg. FIGHTER_MAGE = 3). Logic would dictate that
654 the new and old class levels would be stored in IE_LEVEL and IE_LEVEL2,
655 respectively; however, if one duals from a fighter to a mage in the above
656 example, the levels would actually be in reverse of expectation."""
658 Dual = IsDualClassed (actor, 1)
660 # not dual classed
661 if Dual[0] == 0:
662 return 0
664 # split the full class name into its individual parts
665 # i.e FIGHTER_MAGE becomes [FIGHTER, MAGE]
666 Class = GemRB.GetPlayerStat (actor, IE_CLASS)
667 Class = ClassTable.FindValue (5, Class)
668 Class = ClassTable.GetRowName (Class)
669 Class = Class.split("_")
671 # get our old class name
672 if Dual[0] == 2:
673 BaseClass = ClassTable.GetRowName (Dual[1])
674 else:
675 BaseClass = GetKitIndex (actor)
676 BaseClass = KitListTable.GetValue (BaseClass, 7)
677 BaseClass = ClassTable.FindValue (5, BaseClass)
678 BaseClass = ClassTable.GetRowName (BaseClass)
680 # if our old class is the first class, we need to swap
681 if Class[0] == BaseClass:
682 return 1
684 return 0
686 def IsMultiClassed (actor, verbose):
687 """Returns a tuple containing the multiclass information.
689 Return[0] contains the total number of classes.
690 Return[1-3] contain the ID of their respective classes.
691 If verbose is false, only Return[0] has useable data."""
693 # get our base class
694 ClassIndex = ClassTable.FindValue (5, GemRB.GetPlayerStat (actor, IE_CLASS))
695 IsMulti = ClassTable.GetValue (ClassIndex, 4) # 0 if not multi'd
696 IsDual = IsDualClassed (actor, 0)
698 # dual-class char's look like multi-class chars
699 if (IsMulti == 0) or (IsDual[0] > 0):
700 return (0,-1,-1,-1)
701 elif verbose == 0:
702 return (IsMulti,-1,-1,-1)
704 # get all our classes (leave space for our number of classes in the return array)
705 Classes = [0]*3
706 NumClasses = 0
707 Mask = 1 # we're looking at multiples of 2
708 ClassNames = ClassTable.GetRowName(ClassIndex).split("_")
710 # loop through each class and test it as a mask
711 # TODO: make 16 dynamic? -- allows for custom classes (not just kits)
712 for i in range (1, 16):
713 if IsMulti&Mask: # it's part of this class
714 #we need to place the classes in the array based on their order in the name,
715 #NOT the order they are detected in
716 CurrentName = ClassTable.GetRowName (ClassTable.FindValue (5, i));
717 for j in range(len(ClassNames)):
718 if ClassNames[j] == CurrentName:
719 Classes[j] = i # mask is (i-1)^2 where i is class id
720 NumClasses = NumClasses+1
721 Mask = 1 << i # shift to the next multiple of 2 for testing
723 # in case we couldn't figure out to which classes the multi belonged
724 if NumClasses < 2:
725 return (0,-1,-1,-1)
727 # return the tuple
728 return (NumClasses, Classes[0], Classes[1], Classes[2])
730 def RemoveKnownSpells (pc, type, level1=1, level2=1, noslots=0, kit=0):
731 """Removes all known spells of a given type between two spell levels.
733 If noslots is true, all memorization counts are set to 0.
734 Kit is used to identify the priest spell mask of the spells to be removed;
735 this is only used when removing spells in a dualclass."""
737 # choose the correct limit based upon class type
738 if type == IE_SPELL_TYPE_WIZARD:
739 limit = 9
740 elif type == IE_SPELL_TYPE_PRIEST:
741 limit = 7
743 # make sure that we get the original kit, if we have one
744 if kit:
745 originalkit = GetKitIndex (pc)
747 if originalkit: # kitted; find the class value
748 originalkit = KitListTable.GetValue (originalkit, 7)
749 else: # just get the class value
750 originalkit = GemRB.GetPlayerStat (pc, IE_CLASS)
752 # this is is specifically for dual-classes and will not work to remove only one
753 # spell type from a ranger/cleric multi-class
754 if ClassSkillsTable.GetValue (originalkit, 0, 0) != "*": # knows druid spells
755 originalkit = 0x8000
756 elif ClassSkillsTable.GetValue (originalkit, 1, 0) != "*": # knows cleric spells
757 originalkit = 0x4000
758 else: # don't know any other spells
759 originalkit = 0
761 # don't know how this would happen, but better to be safe
762 if originalkit == kit:
763 originalkit = 0
764 elif type == IE_SPELL_TYPE_INNATE:
765 limit = 1
766 else: # can't do anything if an improper spell type is sent
767 return 0
769 # make sure we're within parameters
770 if level1 < 1 or level2 > limit or level1 > level2:
771 return 0
773 # remove all spells for each level
774 for level in range (level1-1, level2):
775 # we need the count because we remove each spell in reverse order
776 count = GemRB.GetKnownSpellsCount (pc, type, level)
777 mod = count-1
779 for spell in range (count):
780 # see if we need to check for kit
781 if type == IE_SPELL_TYPE_PRIEST and kit:
782 # get the spell's ref data
783 ref = GemRB.GetKnownSpell (pc, type, level, mod-spell)
784 ref = GemRB.GetSpell (ref['SpellResRef'], 1)
786 # we have to look at the originalkit as well specifically for ranger/cleric dual-classes
787 # we wouldn't want to remove all cleric spells and druid spells if we lost our cleric class
788 # only the cleric ones
789 if kit&ref['SpellDivine'] or (originalkit and not originalkit&ref['SpellDivine']):
790 continue
792 # remove the spell
793 GemRB.RemoveSpell (pc, type, level, mod-spell)
795 # remove memorization counts if desired
796 if noslots:
797 GemRB.SetMemorizableSpellsCount (pc, 0, type, level)
799 # return success
800 return 1
802 def CanDualClass(actor):
803 # human
804 if GemRB.GetPlayerStat (actor, IE_RACE) != 1:
805 return 1
807 # already dualclassed
808 Dual = IsDualClassed (actor,0)
809 if Dual[0] > 0:
810 return 1
812 DualClassTable = GemRB.LoadTable ("dualclas")
813 CurrentStatTable = GemRB.LoadTable ("abdcscrq")
814 Class = GemRB.GetPlayerStat (actor, IE_CLASS)
815 ClassIndex = ClassTable.FindValue (5, Class)
816 ClassName = ClassTable.GetRowName (ClassIndex)
817 KitIndex = GetKitIndex (actor)
818 if KitIndex == 0:
819 ClassTitle = ClassName
820 else:
821 ClassTitle = KitListTable.GetValue (KitIndex, 0)
822 Row = DualClassTable.GetRowIndex (ClassTitle)
824 # a lookup table for the DualClassTable columns
825 classes = [ "FIGHTER", "CLERIC", "MAGE", "THIEF", "DRUID", "RANGER" ]
826 matches = []
827 Sum = 0
828 for col in range (0, DualClassTable.GetColumnCount ()):
829 value = DualClassTable.GetValue (Row, col)
830 Sum += value
831 if value == 1:
832 matches.append (classes[col])
834 # cannot dc if all the columns of the DualClassTable are 0
835 if Sum == 0:
836 print "CannotDualClass: all the columns of the DualClassTable are 0"
837 return 1
839 # if the only choice for dc is already the same as the actors base class
840 if Sum == 1 and ClassName in matches and KitIndex == 0:
841 print "CannotDualClass: the only choice for dc is already the same as the actors base class"
842 return 1
844 AlignmentTable = GemRB.LoadTable ("alignmnt")
845 AlignsTable = GemRB.LoadTable ("aligns")
846 Alignment = GemRB.GetPlayerStat (actor, IE_ALIGNMENT)
847 AlignmentColName = AlignsTable.FindValue (3, Alignment)
848 AlignmentColName = AlignsTable.GetValue (AlignmentColName, 4)
849 Sum = 0
850 for classy in matches:
851 Sum += AlignmentTable.GetValue (classy, AlignmentColName)
853 # cannot dc if all the available classes forbid the chars alignment
854 if Sum == 0:
855 print "CannotDualClass: all the available classes forbid the chars alignment"
856 return 1
858 # check current class' stat limitations
859 ClassStatIndex = CurrentStatTable.GetRowIndex (ClassTitle)
860 for stat in range (6):
861 minimum = CurrentStatTable.GetValue (ClassStatIndex, stat)
862 name = CurrentStatTable.GetColumnName (stat)
863 if GemRB.GetPlayerStat (actor, eval ("IE_" + name[4:])) < minimum:
864 print "CannotDualClass: current class' stat limitations are too big"
865 return 1
867 # check new class' stat limitations - make sure there are any good class choices
868 TargetStatTable = GemRB.LoadTable ("abdcdsrq")
869 for match in matches:
870 ClassStatIndex = TargetStatTable.GetRowIndex (match)
871 for stat in range (6):
872 minimum = TargetStatTable.GetValue (ClassStatIndex, stat)
873 name = TargetStatTable.GetColumnName (stat)
874 if GemRB.GetPlayerStat (actor, eval ("IE_" + name[4:])) < minimum:
875 matches.remove (match)
876 break
877 if len(matches) == 0:
878 print "CannotDualClass: no good new class choices"
879 return 1
881 # must be at least level 2
882 if GemRB.GetPlayerStat (actor, IE_LEVEL) == 1:
883 print "CannotDualClass: level 1"
884 return 1
885 return 0
887 def LoadCommonTables():
888 global ClassTable, KitListTable, ClassSkillsTable, RaceTable, NextLevelTable
889 global AppearanceAvatarTable, StrModExTable, StrModTable
891 print # so the following output isn't appended to an existing line
892 if not ClassTable:
893 ClassTable = GemRB.LoadTable ("classes")
894 if not KitListTable and GemRB.HasResource("kitlist", RES_2DA):
895 KitListTable = GemRB.LoadTable ("kitlist")
896 if not ClassSkillsTable:
897 ClassSkillsTable = GemRB.LoadTable ("clskills")
898 if not RaceTable:
899 RaceTable = GemRB.LoadTable ("races")
900 if not NextLevelTable:
901 NextLevelTable = GemRB.LoadTable ("xplevel")
902 if not AppearanceAvatarTable and GemRB.HasResource("pdolls", RES_2DA):
903 AppearanceAvatarTable = GemRB.LoadTable ("pdolls")
904 if not StrModTable:
905 StrModTable = GemRB.LoadTable ("strmod")
906 StrModExTable = GemRB.LoadTable ("strmodex")
908 LoadCommonTables ()
910 GameWindow = GUIClasses.GWindow(0)
911 GameControl = GUIClasses.GControl(0,0)