1# -*-python-*- 2# GemRB - Infinity Engine Emulator 3# Copyright (C) 2003 The GemRB Project 4# 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. 9# 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. 14# 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. 18# 19# 20# GUICommon.py - common functions for GUIScripts of all game types 21 22import GemRB 23import GameCheck 24import GUIClasses 25import collections 26import CommonTables 27from ie_restype import RES_CHU, RES_2DA, RES_BAM, RES_WAV 28from ie_spells import LS_MEMO 29from GUIDefines import * 30from ie_stats import * 31 32CommonTables.Load () 33 34def GetWindowPack(): 35 width = GemRB.GetSystemVariable (SV_WIDTH) 36 height = GemRB.GetSystemVariable (SV_HEIGHT) 37 38 if GemRB.GameType == "pst": 39 default = "GUIWORLD" 40 else: 41 default = "GUIW" 42 43 # use a custom gui if there is one 44 gui = "CGUI" + str(width)[:2] + str(height)[:2] 45 if GemRB.HasResource (gui, RES_CHU, 1): 46 return gui 47 48 # select this based on height 49 # we do this because: 50 # 1. windows are never the entire width, 51 # but the can be the entire height 52 # 2. the originals were for 4:3 screens, 53 # but modern screens are usually a wider aspect 54 # 3. not all games have all the window packs 55 if height >= 960 and GemRB.HasResource ("GUIW12", RES_CHU, 1): 56 return "GUIW12" 57 elif height >= 768 and GemRB.HasResource ("GUIW10", RES_CHU, 1): 58 return "GUIW10" 59 elif height >= 600 and GemRB.HasResource ("GUIW08", RES_CHU, 1): 60 return "GUIW08" 61 62 # fallback to the smallest resolution 63 return default 64 65def LocationPressed (): 66 AreaInfo = GemRB.GetAreaInfo() 67 TMessageTA = GemRB.GetView("MsgSys", 0) 68 if TMessageTA: 69 message = "[color=ff0000]Mouse:[/color] x={0}, y={1}\n[color=ff0000]Area:[/color] {2}\n" 70 message = message.format(AreaInfo["PositionX"], AreaInfo["PositionY"], AreaInfo["CurrentArea"]) 71 TMessageTA.Append(message) 72 else: 73 print("%s [%d.%d]\n" % (AreaInfo["CurrentArea"], AreaInfo["PositionX"], AreaInfo["PositionY"])) 74 75 return 76 77def SelectFormation (btn, val): 78 GemRB.GameSetFormation (val) 79 return 80 81def OpenFloatMenuWindow (x, y): 82 if GameCheck.IsPST(): 83 import FloatMenuWindow 84 FloatMenuWindow.OpenFloatMenuWindow(x, y) 85 else: 86 GemRB.GameControlSetTargetMode (TARGET_MODE_NONE) 87 88def GetActorPaperDoll (actor): 89 anim_id = GemRB.GetPlayerStat (actor, IE_ANIMATION_ID) 90 level = GemRB.GetPlayerStat (actor, IE_ARMOR_TYPE) 91 row = "0x%04X" %anim_id 92 which = "LEVEL%d" %(level+1) 93 doll = CommonTables.Pdolls.GetValue (row, which) 94 if doll == "*": 95 # guess a name 96 import GUICommonWindows 97 doll = GUICommonWindows.GetActorPaperDoll (actor) + "INV" 98 if not GemRB.HasResource (doll, RES_BAM): 99 print("GetActorPaperDoll: Missing paper doll for animation", row, which, doll) 100 return doll 101 102def SelectAllOnPress (): 103 GemRB.GameSelectPC (0, 1) 104 105def GearsClicked (): 106 #GemRB.SetPlayerStat(GemRB.GameGetFirstSelectedPC (),44,249990) 107 GemRB.GamePause (2, 0) 108 109def GetAbilityBonus (Actor, Stat): 110 Ability = GemRB.GetPlayerStat (Actor, Stat) 111 return Ability//2-5 112 113def SetColorStat (Actor, Stat, Value): 114 t = Value & 0xFF 115 t |= t << 8 116 t |= t << 16 117 GemRB.SetPlayerStat (Actor, Stat, t) 118 return 119 120def CheckStat100 (Actor, Stat, Diff): 121 mystat = GemRB.GetPlayerStat (Actor, Stat) 122 goal = GemRB.Roll (1,100, Diff) 123 if mystat>=goal: 124 return True 125 return False 126 127def CheckStat20 (Actor, Stat, Diff): 128 mystat = GemRB.GetPlayerStat (Actor, Stat) 129 goal = GemRB.Roll (1,20, Diff) 130 if mystat>=goal: 131 return True 132 return False 133 134def GetGUISpellButtonCount (): 135 if GameCheck.HasHOW() or GameCheck.IsBG2(): 136 return 24 137 else: 138 return 20 139 140def SetGamedaysAndHourToken (): 141 currentTime = GemRB.GetGameTime() 142 days = currentTime // 7200 143 hours = (currentTime % 7200) // 300 144 GemRB.SetToken ('GAMEDAY', str (days)) 145 GemRB.SetToken ('GAMEDAYS', str (days)) 146 GemRB.SetToken ('HOUR', str (hours)) 147 148def Gain(infostr, ability): 149 GemRB.SetToken ('SPECIALABILITYNAME', GemRB.GetString(int(ability) ) ) 150 GemRB.DisplayString (infostr, ColorWhite) # FIXME: what color should this really be 151 152# chargen version of AddClassAbilities 153def ResolveClassAbilities (pc, ClassName): 154 # apply class/kit abilities 155 IsMulti = IsMultiClassed (pc, 1) 156 Levels = [GemRB.GetPlayerStat (pc, IE_LEVEL), GemRB.GetPlayerStat (pc, IE_LEVEL2), \ 157 GemRB.GetPlayerStat (pc, IE_LEVEL3)] 158 KitIndex = GetKitIndex (pc) 159 if IsMulti[0]>1: 160 #get the class abilites for each class 161 for i in range (IsMulti[0]): 162 TmpClassName = GetClassRowName (IsMulti[i+1], "class") 163 ABTable = CommonTables.ClassSkills.GetValue (TmpClassName, "ABILITIES") 164 if ABTable != "*" and GemRB.HasResource (ABTable, RES_2DA, 1): 165 AddClassAbilities (pc, ABTable, Levels[i], Levels[i]) 166 else: 167 if KitIndex: 168 ABTable = CommonTables.KitList.GetValue (str(KitIndex), "ABILITIES") 169 else: 170 ABTable = CommonTables.ClassSkills.GetValue (ClassName, "ABILITIES") 171 if ABTable != "*" and GemRB.HasResource (ABTable, RES_2DA, 1): 172 AddClassAbilities (pc, ABTable, Levels[0], Levels[0]) 173 174# Adds class/kit abilities 175def AddClassAbilities (pc, table, Level=1, LevelDiff=1, align=-1): 176 TmpTable = GemRB.LoadTable (table) 177 import Spellbook 178 179 # gotta stay positive 180 if Level-LevelDiff < 0: 181 return 182 183 # we're doing alignment additions 184 if align == -1: 185 iMin = 0 186 iMax = TmpTable.GetRowCount () 187 else: 188 # alignment is expected to be the row required 189 iMin = align 190 iMax = align+1 191 192 # make sure we don't go out too far 193 jMin = Level-LevelDiff 194 jMax = Level 195 if jMax > TmpTable.GetColumnCount (): 196 jMax = TmpTable.GetColumnCount () 197 198 for i in range(iMin, iMax): 199 # apply each spell from each new class 200 for j in range (jMin, jMax): 201 ab = TmpTable.GetValue (i, j, GTV_STR) 202 if ab and ab != "****": 203 # seems all SPINs act like GA_* 204 if ab[:4] == "SPIN": 205 ab = "GA_" + ab 206 207 # apply spell (AP_) or gain spell (GA_) 208 if ab[:3] == "AP_": 209 GemRB.ApplySpell (pc, ab[3:]) 210 elif ab[:3] == "GA_": 211 Spellbook.LearnSpell (pc, ab[3:], IE_SPELL_TYPE_INNATE, 0, 1, LS_MEMO) 212 elif ab[:3] == "FS_": 213 Gain(26320, ab[3:]) 214 elif ab[:3] == "FA_": 215 Gain(10514, ab[3:]) 216 else: 217 GemRB.Log (LOG_ERROR, "AddClassAbilities", "Unknown class ability (type): " + ab) 218 219def MakeSpellCount (pc, spell, count): 220 have = GemRB.CountSpells (pc, spell, 1) 221 if count<=have: 222 return 223 # only used for innates, which are all level 1 224 import Spellbook 225 Spellbook.LearnSpell (pc, spell, IE_IWD2_SPELL_INNATE, 0, count-have, LS_MEMO) 226 return 227 228# remove all class abilities up to the given level 229# for dual-classing mainly 230def RemoveClassAbilities (pc, table, Level): 231 TmpTable = GemRB.LoadTable (table) 232 import Spellbook 233 234 # gotta stay positive 235 if Level < 0: 236 return 237 238 # make sure we don't go out too far 239 jMax = Level 240 if jMax > TmpTable.GetColumnCount (): 241 jMax = TmpTable.GetColumnCount () 242 243 for i in range(TmpTable.GetRowCount ()): 244 for j in range (jMax): 245 ab = TmpTable.GetValue (i, j, GTV_STR) 246 if ab and ab != "****": 247 # get the index 248 SpellIndex = Spellbook.HasSpell (pc, IE_SPELL_TYPE_INNATE, 0, ab[3:]) 249 250 # seems all SPINs act like GA_* 251 if ab[:4] == "SPIN": 252 ab = "GA_" + ab 253 254 # apply spell (AP_) or gain spell (GA_)? 255 if ab[:3] == "AP_": 256 GemRB.RemoveEffects (pc, ab[3:]) 257 elif ab[:3] == "GA_": 258 if SpellIndex >= 0: 259 # TODO: get the correct counts to avoid removing an innate ability 260 # given by more than one thing? 261 # RemoveSpell will unmemorize them all too 262 GemRB.RemoveSpell (pc, IE_SPELL_TYPE_INNATE, 0, SpellIndex) 263 elif ab[:3] != "FA_" and ab[:3] != "FS_": 264 GemRB.Log (LOG_ERROR, "RemoveClassAbilities", "Unknown class ability (type): " + ab) 265 266# PST uses a button, IWD2 two types, the rest are the same with two labels 267def SetEncumbranceLabels (Window, ControlID, Control2ID, pc): 268 """Displays the encumbrance as a ratio of current to maximum.""" 269 270 # encumbrance 271 encumbrance = GemRB.GetPlayerStat (pc, IE_ENCUMBRANCE) 272 max_encumb = GemRB.GetMaxEncumbrance (pc) 273 274 Control = Window.GetControl (ControlID) 275 if GameCheck.IsPST(): 276 # FIXME: there should be a space before LB symbol (':') - but there is no frame for it and our doesn't cut it 277 Control.SetText (str (encumbrance) + ":\n\n" + str (max_encumb) + ":") 278 elif GameCheck.IsIWD2() and not Control2ID: 279 Control.SetText (str (encumbrance) + "/" + str(max_encumb) + GemRB.GetString(39537)) 280 else: 281 Control.SetText (str (encumbrance) + ":") 282 if not Control2ID: # shouldn't happen 283 print("Missing second control parameter to SetEncumbranceLabels!") 284 return 285 Control2 = Window.GetControl (Control2ID) 286 Control2.SetText (str (max_encumb) + ":") 287 288 ratio = encumbrance / max_encumb 289 if GameCheck.IsIWD2 () or GameCheck.IsPST (): 290 if ratio > 1.0: 291 Control.SetTextColor ({'r' : 255, 'g' : 0, 'b' : 0}) 292 elif ratio > 0.8: 293 Control.SetTextColor ({'r' : 255, 'g' : 255, 'b' : 0}) 294 else: 295 Control.SetTextColor ({'r' : 255, 'g' : 255, 'b' : 255}) 296 297 if Control2ID: 298 Control2.SetTextColor ({'r' : 255, 'g' : 0, 'b' : 0}) 299 300 else: 301 if ratio > 1.0: 302 Control.SetFont ("NUMBER3"); 303 elif ratio > 0.8: 304 Control.SetFont ("NUMBER2"); 305 else: 306 Control.SetFont ("NUMBER"); 307 308 return 309 310def GetActorClassTitle (actor): 311 """Returns the string representation of the actors class.""" 312 313 ClassTitle = GemRB.GetPlayerStat (actor, IE_TITLE1) 314 315 if ClassTitle == 0: 316 ClassName = GetClassRowName (actor) 317 KitIndex = GetKitIndex (actor) 318 Multi = HasMultiClassBits (actor) 319 Dual = IsDualClassed (actor, 1) 320 MCFlags = GemRB.GetPlayerStat (actor, IE_MC_FLAGS) 321 322 if Multi and Dual[0] == 0: # true multi class 323 ClassTitle = CommonTables.Classes.GetValue (ClassName, "CAP_REF", GTV_REF) 324 else: 325 if Dual[0]: # dual class 326 # first (previous) kit or class of the dual class 327 if Dual[0] == 1: 328 ClassTitle = CommonTables.KitList.GetValue (Dual[1], 2) 329 else: 330 ClassTitle = CommonTables.Classes.GetValue (GetClassRowName(Dual[1], "index"), "CAP_REF") 331 if ClassTitle != "*": 332 ClassTitle = GemRB.GetString (ClassTitle) 333 ClassTitle += " / " 334 if Dual[0] == 3: 335 ClassTitle += CommonTables.KitList.GetValue (Dual[2], 2, GTV_REF) 336 else: 337 ClassTitle += CommonTables.Classes.GetValue (GetClassRowName(Dual[2], "index"), "CAP_REF", GTV_REF) 338 elif MCFlags & (MC_FALLEN_PALADIN|MC_FALLEN_RANGER): # fallen 339 ClassTitle = 10369 340 if MCFlags & MC_FALLEN_PALADIN: 341 ClassTitle = 10371 342 ClassTitle = GemRB.GetString (ClassTitle) 343 else: # ordinary class or kit 344 if KitIndex: 345 ClassTitle = CommonTables.KitList.GetValue (KitIndex, 2) 346 else: 347 ClassTitle = CommonTables.Classes.GetValue (ClassName, "CAP_REF") 348 if ClassTitle != "*": 349 ClassTitle = GemRB.GetString (ClassTitle) 350 else: 351 ClassTitle = GemRB.GetString (ClassTitle) 352 353 #GetActorClassTitle returns string now... 354 #if ClassTitle == "*": 355 # return 0 356 357 return ClassTitle 358 359 360def GetKitIndex (actor): 361 """Return the index of the actors kit from KITLIST.2da. 362 363 Returns 0 if the class is not kitted.""" 364 365 Kit = GemRB.GetPlayerStat (actor, IE_KIT) 366 KitIndex = 0 367 368 if Kit & 0xc000 == 0x4000: 369 KitIndex = Kit & 0xfff 370 371 # carefully looking for kit by the usability flag 372 # since the barbarian kit id clashes with the no-kit value 373 if KitIndex == 0 and Kit != 0x4000: 374 KitIndex = CommonTables.KitList.FindValue (6, Kit) 375 if KitIndex == -1: 376 KitIndex = 0 377 378 return KitIndex 379 380# fetches the rowname of the passed actor's (base) class from classes.2da 381# NOTE: only the "index" method is iwd2-ready, since you can have multiple classes and kits 382def GetClassRowName(value, which=-1): 383 if which == "index": 384 ClassIndex = value 385 # if barbarians cause problems, repeat the lookup again here 386 else: 387 if which == -1: 388 Class = GemRB.GetPlayerStat (value, IE_CLASS) 389 elif which == "class": 390 Class = value 391 else: 392 raise RuntimeError("Bad type parameter for GetClassRowName: " + str(which)) 393 ClassIndex = CommonTables.Classes.FindValue ("ID", Class) 394 ClassRowName = CommonTables.Classes.GetRowName (ClassIndex) 395 return ClassRowName 396 397# checks the classes.2da table if the class is multiclass/dualclass capable (bits define the class combination) 398def HasMultiClassBits(actor): 399 MultiBits = CommonTables.Classes.GetValue (GetClassRowName(actor), "MULTI") 400 401 # we have no entries for npc creature classes, so treat them as single-classed 402 if MultiBits == "*": 403 MultiBits = 0 404 405 return MultiBits 406 407def IsDualClassed(actor, verbose): 408 """Returns an array containing the dual class information. 409 410 Return[0] is 0 if not dualclassed, 1 if the old class is a kit, 3 if the new class is a kit, 2 otherwise. 411 Return[1] contains either the kit or class index of the old class. 412 Return[2] contains the class index of the new class. 413 If verbose is false, only Return[0] contains useable data.""" 414 415 if GameCheck.IsIWD2(): 416 return (0,-1,-1) 417 418 Multi = HasMultiClassBits (actor) 419 420 if Multi == 0: 421 return (0, -1, -1) 422 423 DualedFrom = GemRB.GetPlayerStat (actor, IE_MC_FLAGS) & MC_WAS_ANY_CLASS 424 425 if verbose: 426 KitIndex = GetKitIndex (actor) 427 if KitIndex: 428 KittedClass = CommonTables.KitList.GetValue (KitIndex, 7) 429 KittedClassIndex = CommonTables.Classes.FindValue ("ID", KittedClass) 430 else: 431 KittedClassIndex = 0 432 433 if DualedFrom > 0: # first (previous) class of the dual class 434 FirstClassIndex = CommonTables.Classes.FindValue ("MC_WAS_ID", DualedFrom) 435 436 # use the first class of the multiclass bunch that isn't the same as the first class 437 for i in range (1,16): 438 Mask = 1 << (i - 1) 439 if Multi & Mask: 440 ClassIndex = CommonTables.Classes.FindValue ("ID", i) 441 if ClassIndex == FirstClassIndex: 442 continue 443 SecondClassIndex = ClassIndex 444 break 445 else: 446 GemRB.Log (LOG_WARNING, "IsDualClassed", "Invalid dualclass combination, treating as a single class!") 447 print(DualedFrom, Multi, KitIndex, FirstClassIndex) 448 return (0,-1,-1) 449 450 if KittedClassIndex == FirstClassIndex and KitIndex: 451 return (1, KitIndex, SecondClassIndex) 452 elif KittedClassIndex == SecondClassIndex: 453 return (3, FirstClassIndex, KitIndex) 454 else: 455 return (2, FirstClassIndex, SecondClassIndex) 456 else: 457 return (0,-1,-1) 458 else: 459 if DualedFrom > 0: 460 return (1,-1,-1) 461 else: 462 return (0,-1,-1) 463 464def IsDualSwap (actor, override=None): 465 """Returns true if the dualed classes are reverse of expection. 466 467 This can happen, because the engine gives dualclass characters the same ID as 468 their multiclass counterpart (eg. FIGHTER_MAGE = 3). Logic would dictate that 469 the new and old class levels would be stored in IE_LEVEL and IE_LEVEL2, 470 respectively; however, if one duals from a fighter to a mage in the above 471 example, the levels would actually be in reverse of expectation.""" 472 473 Dual = IsDualClassed (actor, 1) 474 if override: 475 CI1 = CommonTables.Classes.FindValue ("ID", override["old"]) 476 CI2 = CommonTables.Classes.FindValue ("ID", override["new"]) 477 Dual = (2, CI1, CI2) # TODO: support IsDualClassed mode 3 once a gui for it is added 478 479 # not dual classed 480 if Dual[0] == 0: 481 return 0 482 483 # split the full class name into its individual parts 484 # i.e FIGHTER_MAGE becomes [FIGHTER, MAGE] 485 Class = GetClassRowName(actor).split("_") 486 if override: 487 Class = GetClassRowName(override["mc"], "class").split("_") 488 489 # get our old class name 490 if Dual[0] > 1: 491 BaseClass = GetClassRowName(Dual[1], "index") 492 else: 493 BaseClass = CommonTables.KitList.GetValue (Dual[1], 7) 494 if BaseClass == "*": 495 # mod boilerplate 496 return 0 497 BaseClass = GetClassRowName(BaseClass, "class") 498 499 # if our old class is the first class, we need to swap 500 if Class[0] == BaseClass: 501 return 1 502 503 return 0 504 505def IsMultiClassed (actor, verbose): 506 """Returns a tuple containing the multiclass information. 507 508 Return[0] contains the total number of classes. 509 Return[1-3] contain the ID of their respective classes. 510 If verbose is false, only Return[0] has useable data.""" 511 512 # change this if it will ever be needed 513 if GameCheck.IsIWD2(): 514 return (0,-1,-1,-1) 515 516 # get our base class 517 IsMulti = HasMultiClassBits (actor) 518 IsDual = IsDualClassed (actor, 0) 519 520 # dual-class char's look like multi-class chars 521 if (IsMulti == 0) or (IsDual[0] > 0): 522 return (0,-1,-1,-1) 523 elif verbose == 0: 524 return (IsMulti,-1,-1,-1) 525 526 # get all our classes (leave space for our number of classes in the return array) 527 Classes = [0]*3 528 NumClasses = 0 529 Mask = 1 # we're looking at multiples of 2 530 ClassNames = GetClassRowName(actor).split("_") 531 532 # loop through each class and test it as a mask 533 ClassCount = CommonTables.Classes.GetRowCount() 534 for i in range (1, ClassCount): 535 if IsMulti&Mask: # it's part of this class 536 #we need to place the classes in the array based on their order in the name, 537 #NOT the order they are detected in 538 CurrentName = GetClassRowName (i, "class") 539 if CurrentName == "*": 540 # we read too far, as the upper range limit is greater than the number of "single" classes 541 break 542 for j in range(len(ClassNames)): 543 if ClassNames[j] == CurrentName: 544 Classes[j] = i # mask is (i-1)^2 where i is class id 545 NumClasses = NumClasses+1 546 Mask = 1 << i # shift to the next multiple of 2 for testing 547 548 # in case we couldn't figure out to which classes the multi belonged 549 if NumClasses < 2: 550 GemRB.Log (LOG_ERROR, "IsMultiClassed", "Couldn't figure out the individual classes of multiclass!") 551 print(ClassNames) 552 return (0,-1,-1,-1) 553 554 # return the tuple 555 return (NumClasses, Classes[0], Classes[1], Classes[2]) 556 557def IsNamelessOne (actor): 558 # A very simple test to identify the actor as TNO 559 if not GameCheck.IsPST(): 560 return False 561 562 Specific = GemRB.GetPlayerStat (actor, IE_SPECIFIC) 563 if Specific == 2: 564 return True 565 566 return False 567 568def NamelessOneClass (actor): 569 # A shortcut function to determine the identity of the nameless one 570 # and get his class if that is the case 571 if IsNamelessOne(actor): 572 return GetClassRowName(actor) 573 574 return None 575 576def CanDualClass(actor): 577 # race restriction (human) 578 RaceName = CommonTables.Races.FindValue ("ID", GemRB.GetPlayerStat (actor, IE_RACE, 1)) 579 RaceName = CommonTables.Races.GetRowName (RaceName) 580 RaceDual = CommonTables.Races.GetValue (RaceName, "CANDUAL") 581 if int(RaceDual) != 1: 582 return 1 583 584 # already dualclassed 585 Dual = IsDualClassed (actor,0) 586 if Dual[0] > 0: 587 return 1 588 589 DualClassTable = GemRB.LoadTable ("dualclas") 590 CurrentStatTable = GemRB.LoadTable ("abdcscrq") 591 ClassName = GetClassRowName(actor) 592 KitIndex = GetKitIndex (actor) 593 if KitIndex == 0: 594 ClassTitle = ClassName 595 else: 596 ClassTitle = CommonTables.KitList.GetValue (KitIndex, 0) 597 Row = DualClassTable.GetRowIndex (ClassTitle) 598 599 # create a lookup table for the DualClassTable columns 600 classes = [] 601 for col in range(DualClassTable.GetColumnCount()): 602 classes.append(DualClassTable.GetColumnName(col)) 603 604 matches = [] 605 Sum = 0 606 for col in range (0, DualClassTable.GetColumnCount ()): 607 value = DualClassTable.GetValue (Row, col) 608 Sum += value 609 if value == 1: 610 matches.append (classes[col]) 611 612 # cannot dc if all the columns of the DualClassTable are 0 613 if Sum == 0: 614 print("CannotDualClass: all the columns of the DualClassTable are 0") 615 return 1 616 617 # if the only choice for dc is already the same as the actors base class 618 if Sum == 1 and ClassName in matches and KitIndex == 0: 619 print("CannotDualClass: the only choice for dc is already the same as the actors base class") 620 return 1 621 622 AlignmentTable = GemRB.LoadTable ("alignmnt") 623 Alignment = GemRB.GetPlayerStat (actor, IE_ALIGNMENT) 624 AlignmentColName = CommonTables.Aligns.FindValue (3, Alignment) 625 AlignmentColName = CommonTables.Aligns.GetValue (AlignmentColName, 4) 626 if AlignmentColName == -1: 627 print("CannotDualClass: extraordinary character alignment") 628 return 1 629 Sum = 0 630 for classy in matches: 631 Sum += AlignmentTable.GetValue (classy, AlignmentColName) 632 633 # cannot dc if all the available classes forbid the chars alignment 634 if Sum == 0: 635 print("CannotDualClass: all the available classes forbid the chars alignment") 636 return 1 637 638 # check current class' stat limitations 639 ClassStatIndex = CurrentStatTable.GetRowIndex (ClassTitle) 640 for stat in range (6): 641 minimum = CurrentStatTable.GetValue (ClassStatIndex, stat) 642 name = CurrentStatTable.GetColumnName (stat) 643 if GemRB.GetPlayerStat (actor, SafeStatEval ("IE_" + name[4:]), 1) < minimum: 644 print("CannotDualClass: current class' stat limitations are too big") 645 return 1 646 647 # check new class' stat limitations - make sure there are any good class choices 648 TargetStatTable = GemRB.LoadTable ("abdcdsrq") 649 bad = 0 650 for match in matches: 651 ClassStatIndex = TargetStatTable.GetRowIndex (match) 652 for stat in range (6): 653 minimum = TargetStatTable.GetValue (ClassStatIndex, stat) 654 name = TargetStatTable.GetColumnName (stat) 655 if GemRB.GetPlayerStat (actor, SafeStatEval ("IE_" + name[4:]), 1) < minimum: 656 bad += 1 657 break 658 if len(matches) == bad: 659 print("CannotDualClass: no good new class choices") 660 return 1 661 662 # must be at least level 2 663 if GemRB.GetPlayerStat (actor, IE_LEVEL) == 1: 664 print("CannotDualClass: level 1") 665 return 1 666 return 0 667 668def IsWarrior (actor): 669 IsWarrior = CommonTables.ClassSkills.GetValue (GetClassRowName(actor), "NO_PROF") 670 671 # warriors get only a -2 penalty for wielding weapons they are not proficient with 672 # FIXME: make the check more robust, someone may change the value! 673 IsWarrior = (IsWarrior == -2) 674 675 Dual = IsDualClassed (actor, 0) 676 if Dual[0] > 0: 677 DualedFrom = GemRB.GetPlayerStat (actor, IE_MC_FLAGS) & MC_WAS_ANY_CLASS 678 FirstClassIndex = CommonTables.Classes.FindValue ("MC_WAS_ID", DualedFrom) 679 FirstClassName = CommonTables.Classes.GetRowName (FirstClassIndex) 680 OldIsWarrior = CommonTables.ClassSkills.GetValue (FirstClassName, "NO_PROF") 681 # there are no warrior to warrior dualclasses, so if the previous class was one, the current one certainly isn't 682 if OldIsWarrior == -2: 683 return 0 684 # but there are also non-warrior to non-warrior dualclasses, so just use the new class check 685 686 return IsWarrior 687 688def SetupDamageInfo (pc, Button, Window): 689 hp = GemRB.GetPlayerStat (pc, IE_HITPOINTS) 690 hp_max = GemRB.GetPlayerStat (pc, IE_MAXHITPOINTS) 691 state = GemRB.GetPlayerStat (pc, IE_STATE_ID) 692 693 if hp_max < 1 or hp == "?": 694 ratio = 0.0 695 else: 696 ratio = hp / float(hp_max) 697 698 if hp < 1 or (state & STATE_DEAD): 699 c = {'r' : 64, 'g' : 64, 'b' : 64, 'a' : 255} 700 Button.SetOverlay (0, c, c) 701 702 if ratio == 1.0: 703 band = 0 704 color = {'r' : 255, 'g' : 255, 'b' : 255} # white 705 elif ratio >= 0.75: 706 band = 1 707 color = {'r' : 0, 'g' : 255, 'b' : 0} # green 708 elif ratio >= 0.50: 709 band = 2 710 color = {'r' : 255, 'g' : 255, 'b' : 0} # yellow 711 elif ratio >= 0.25: 712 band = 3 713 color = {'r' : 255, 'g' : 128, 'b' : 0} # orange 714 else: 715 band = 4 716 color = {'r' : 255, 'g' : 0, 'b' : 0} # red 717 718 if GemRB.GetVar("Old Portrait Health") or not GameCheck.IsIWD2(): 719 # draw the blood overlay 720 if hp >= 1 and not (state & STATE_DEAD): 721 c1 = {'r' : 0x70, 'g' : 0, 'b' : 0, 'a' : 0xff} 722 c2 = {'r' : 0xf7, 'g' : 0, 'b' : 0, 'a' : 0xff} 723 Button.SetOverlay (ratio, c1, c2) 724 else: 725 # scale the hp bar under the portraits and recolor it 726 # GUIHITPT has 5 frames with different severity colors 727 # luckily their ids follow a nice pattern 728 hpBar = Window.GetControl (pc-1 + 50) 729 hpBar.SetBAM ("GUIHITPT", band, 0) 730 hpBar.SetPictureClipping (ratio) 731 hpBar.SetFlags (IE_GUI_BUTTON_NO_IMAGE, OP_OR) 732 733 ratio_str = "" 734 if hp != "?": 735 ratio_str = "\n%d/%d" %(hp, hp_max) 736 Button.SetTooltip (GemRB.GetPlayerName (pc, 1) + ratio_str) 737 738 return ratio_str, color 739 740def SetCurrentDateTokens (stat, plural=False): 741 # NOTE: currentTime is in seconds, joinTime is in seconds * 15 742 # (script updates). In each case, there are 60 seconds 743 # in a minute, 24 hours in a day, but ONLY 5 minutes in an hour!! 744 # Hence currentTime (and joinTime after div by 15) has 745 # 7200 secs a day (60 * 5 * 24) 746 currentTime = GemRB.GetGameTime () 747 joinTime = stat['JoinDate'] - stat['AwayTime'] 748 749 party_time = currentTime - (joinTime // 15) 750 days = party_time // 7200 751 hours = (party_time % 7200) // 300 752 753 GemRB.SetToken ('GAMEDAYS', str (days)) 754 GemRB.SetToken ('HOUR', str (hours)) 755 if plural: 756 return 757 758 # construct <GAMEDAYS> days ~and~ ~<HOUR> hours~ 759 if days == 1: 760 time = GemRB.GetString (10698) 761 else: 762 time = GemRB.GetString (10697) 763 time += " " + GemRB.GetString (10699) + " " 764 if days == 0: 765 # only display hours 766 time = "" 767 768 if hours == 1: 769 time += GemRB.GetString (10701) 770 else: 771 time += GemRB.GetString (10700) 772 773 return time 774 775def SetSaveDir(): 776 if GameCheck.IsIWD1() or GameCheck.IsIWD2(): 777 GemRB.SetToken ("SaveDir", "mpsave") 778 elif GameCheck.IsBG1() and GemRB.GetVar ("PlayMode") == 1: 779 GemRB.SetToken ("SaveDir", "mpsave") 780 else: 781 GemRB.SetToken ("SaveDir", "save") 782 783# gray out window or mark it as visible depending on the actor's state 784# Always greys it out for actors that are: dead, berserking 785# The third parameter is another check which must be 0 to maintain window visibility 786def AdjustWindowVisibility (Window, pc, additionalCheck): 787 if not additionalCheck and GemRB.ValidTarget (pc, GA_SELECT|GA_NO_DEAD): 788 Window.SetDisabled (False) 789 else: 790 Window.SetDisabled (True) 791 return 792 793def UsingTouchInput (): 794 return GemRB.GetSystemVariable (SV_TOUCH) 795 796# return ceil(n/d) 797# 798def ceildiv (n, d): 799 if d == 0: 800 raise ZeroDivisionError("ceildiv by zero") 801 elif d < 0: 802 return (n + d + 1) // d 803 else: 804 return (n + d - 1) // d 805 806# a placeholder for unimplemented and hardcoded key actions 807def ResolveKey(): 808 return 809 810# eval that only accepts alphanumerics and "_" 811# used for converting constructed stat names to their values, eg. IE_STR to 36 812def SafeStatEval (expression): 813 # if we ever import string: string.ascii_letters + "_" + string.digits 814 alnum = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' 815 for chr in expression: 816 if chr not in alnum: 817 raise ValueError("Invalid input! Bad data encountered, check the GemRB install's integrity!") 818 819 return eval(expression) 820 821GameWindow = GUIClasses.GWindow(ID=0, SCRIPT_GROUP="GAMEWIN") 822GameControl = GUIClasses.GView(ID=0, SCRIPT_GROUP="GC") 823 824def DisplayAC (pc, window, labelID): 825 AC = GemRB.GetPlayerStat (pc, IE_ARMORCLASS) + GetACStyleBonus (pc) 826 Label = window.GetControl (labelID) 827 Label.SetText (str (AC)) 828 Label.SetTooltip (17183) 829 830def GetACStyleBonus (pc): 831 stars = GemRB.GetPlayerStat(pc, IE_PROFICIENCYSINGLEWEAPON) & 0x7 832 if not stars: 833 return 0 834 835 WStyleTable = GemRB.LoadTable ("wssingle", 1) 836 if not WStyleTable: 837 return 0 838 # are we actually single-wielding? 839 cdet = GemRB.GetCombatDetails (pc, 0) 840 if cdet["Style"] % 1000 != IE_PROFICIENCYSINGLEWEAPON: 841 return 0 842 return WStyleTable.GetValue (str(stars), "AC") 843 844def AddDefaultVoiceSet (VoiceList, Voices): 845 if GameCheck.IsBG1 () or GameCheck.IsBG2 (): 846 Options = collections.OrderedDict(enumerate(Voices)) 847 Options[-1] = "default" 848 Options = collections.OrderedDict(sorted(Options.items())) 849 VoiceList.SetOptions (list(Options.values())) 850 return True 851 return False 852 853def OverrideDefaultVoiceSet (Gender, CharSound): 854 # handle "default" gendered voice 855 if CharSound == "default" and not GemRB.HasResource ("defaulta", RES_WAV): 856 if GameCheck.IsBG1 (): 857 Gender2Sound = [ "", "mainm", "mainf" ] 858 CharSound = Gender2Sound[Gender] 859 elif GameCheck.IsBG2 (): 860 Gender2Sound = [ "", "male005", "female4" ] 861 CharSound = Gender2Sound[Gender] 862 return CharSound 863 864class _stdioWrapper(object): 865 def __init__(self, log_level): 866 self.log_level = log_level 867 self.buffer = "" 868 def write(self, message): 869 self.buffer += str(message) 870 if self.buffer.endswith("\n"): 871 out = self.buffer.rstrip("\n") 872 if out: 873 GemRB.Log(self.log_level, "Python", out) 874 self.buffer = "" 875 def flush(self): 876 pass 877 878def _wrapStdio(): 879 import sys 880 sys.stdout = _stdioWrapper(LOG_MESSAGE) 881 sys.stderr = _stdioWrapper(LOG_ERROR) 882 883_wrapStdio() 884