diff --git a/nwn/nwnprc/trunk/2das/cls_bfeat_etbl.2da b/nwn/nwnprc/trunk/2das/cls_bfeat_etbl.2da index 6efff0c1..d41b8e5b 100644 --- a/nwn/nwnprc/trunk/2das/cls_bfeat_etbl.2da +++ b/nwn/nwnprc/trunk/2das/cls_bfeat_etbl.2da @@ -1,63 +1,63 @@ 2DA V2.0 - Bonus -0 0 -1 0 -2 0 -3 0 -4 0 -5 0 -6 0 -7 0 -8 0 -9 0 -10 0 -11 0 -12 0 -13 1 -14 0 -15 0 -16 0 -17 1 -18 0 -19 0 -20 0 -21 1 -22 0 -23 0 -24 0 -25 1 -26 0 -27 0 -28 0 -29 1 -30 0 -31 0 -32 0 -33 1 -34 0 -35 0 -36 0 -37 1 -38 0 -39 0 -40 0 -41 1 -42 0 -43 0 -44 0 -45 1 -46 0 -47 0 -48 0 -49 1 -50 0 -51 0 -52 0 -53 1 -54 0 -55 0 -56 0 -57 1 -58 0 -59 0 + Bonus +0 0 +1 0 +2 0 +3 0 +4 0 +5 0 +6 0 +7 0 +8 0 +9 0 +10 0 +11 0 +12 1 +13 0 +14 0 +15 1 +16 0 +17 0 +18 1 +19 0 +20 0 +21 1 +22 0 +23 0 +24 1 +25 0 +26 0 +27 1 +28 0 +29 0 +30 1 +31 0 +32 0 +33 1 +34 0 +35 0 +36 1 +37 0 +38 0 +39 1 +40 0 +41 0 +42 1 +43 0 +44 0 +45 1 +46 0 +47 0 +48 1 +49 0 +50 0 +51 1 +52 0 +53 0 +54 1 +55 0 +56 0 +57 1 +58 0 +59 0 diff --git a/nwn/nwnprc/trunk/2das/cls_feat_etbl.2da b/nwn/nwnprc/trunk/2das/cls_feat_etbl.2da index 1813e270..9945c530 100644 --- a/nwn/nwnprc/trunk/2das/cls_feat_etbl.2da +++ b/nwn/nwnprc/trunk/2das/cls_feat_etbl.2da @@ -1,16 +1,302 @@ 2DA V2.0 - FeatLabel FeatIndex List GrantedOnLevel OnMenu -0 FEAT_EPIC_BLINDING_SPEED 491 0 -1 1 -1 DefArrow 8 0 -1 0 -2 TwoWeap 41 0 -1 0 -3 Ambidex 1 0 -1 0 -4 ETBL_ManeuverQuicksel 24125 3 1 1 -5 ETBL_Blade_Guide 24126 3 1 0 -6 ETBL_Eternal_Training 24127 3 1 1 -7 ETBL_Guided_Strike 24128 3 2 1 -8 ETBL_Armored_Uncanny_Dodge 24129 3 3 0 -9 ETBL_Eternal_Knowledge 24130 3 4 0 -10 ETBL_Defensive_Insight 24131 3 5 1 -11 ETBL_Tactical_Insight 24132 3 8 1 -12 ETBL_Island_In_Time 24133 3 10 1 + FeatLabel FeatIndex List GrantedOnLevel OnMenu +0 ETBL_ManeuverQuicksel 24125 3 1 1 +1 ETBL_Blade_Guide 24126 3 1 0 +2 ETBL_Eternal_Training 24127 3 1 1 +3 ETBL_Guided_Strike 24128 3 2 1 +4 ETBL_Armored_Uncanny_Dodge 24129 3 3 0 +5 ETBL_Eternal_Knowledge 24130 3 4 0 +6 ETBL_Defensive_Insight 24131 3 5 1 +7 ETBL_Tactical_Insight 24132 3 8 1 +8 ETBL_Island_In_Time 24133 3 10 1 +9 FEAT_EPIC_ETERNAL_BLADE 25081 3 11 0 +10 FEAT_EPIC_BLINDING_SPEED 491 1 -1 1 +11 FEAT_EPIC_GREAT_STRENGTH_1 814 1 -1 0 +12 FEAT_EPIC_GREAT_STRENGTH_2 815 1 -1 0 +13 FEAT_EPIC_GREAT_STRENGTH_3 816 1 -1 0 +14 FEAT_EPIC_GREAT_STRENGTH_4 817 1 -1 0 +15 FEAT_EPIC_GREAT_STRENGTH_5 818 1 -1 0 +16 FEAT_EPIC_GREAT_STRENGTH_6 819 1 -1 0 +17 FEAT_EPIC_GREAT_STRENGTH_7 820 1 -1 0 +18 FEAT_EPIC_GREAT_STRENGTH_8 821 1 -1 0 +19 FEAT_EPIC_GREAT_STRENGTH_9 822 1 -1 0 +20 FEAT_EPIC_GREAT_STRENGTH_10 823 1 -1 0 +21 FEAT_EPIC_GREAT_INTELLIGENCE_1 794 1 -1 0 +22 FEAT_EPIC_GREAT_INTELLIGENCE_2 795 1 -1 0 +23 FEAT_EPIC_GREAT_INTELLIGENCE_3 796 1 -1 0 +24 FEAT_EPIC_GREAT_INTELLIGENCE_4 797 1 -1 0 +25 FEAT_EPIC_GREAT_INTELLIGENCE_5 798 1 -1 0 +26 FEAT_EPIC_GREAT_INTELLIGENCE_6 799 1 -1 0 +27 FEAT_EPIC_GREAT_INTELLIGENCE_7 800 1 -1 0 +28 FEAT_EPIC_GREAT_INTELLIGENCE_8 801 1 -1 0 +29 FEAT_EPIC_GREAT_INTELLIGENCE_9 802 1 -1 0 +30 FEAT_EPIC_GREAT_INTELLIGENCE_10 803 1 -1 0 +31 FEAT_EPIC_ARMOR_SKIN 490 1 -1 0 +32 FEAT_EPIC_DAMAGE_REDUCTION_3 492 1 -1 0 +33 FEAT_EPIC_DAMAGE_REDUCTION_6 493 1 -1 0 +34 FEAT_EPIC_DAMAGE_REDUCTION_9 494 1 -1 0 +35 FEAT_EPIC_DEVASTATING_CRITICAL_CLUB 495 1 -1 0 +36 FEAT_EPIC_DEVASTATING_CRITICAL_DAGGER 496 1 -1 0 +37 FEAT_EPIC_DEVASTATING_CRITICAL_DART 497 1 -1 0 +38 FEAT_EPIC_DEVASTATING_CRITICAL_HEAVYCROSSBOW 498 1 -1 0 +39 FEAT_EPIC_DEVASTATING_CRITICAL_LIGHTCROSSBOW 499 1 -1 0 +40 FEAT_EPIC_DEVASTATING_CRITICAL_LIGHTMACE 500 1 -1 0 +41 FEAT_EPIC_DEVASTATING_CRITICAL_MORNINGSTAR 501 1 -1 0 +42 FEAT_EPIC_DEVASTATING_CRITICAL_QUARTERSTAFF 502 1 -1 0 +43 FEAT_EPIC_DEVASTATING_CRITICAL_SHORTSPEAR 503 1 -1 0 +44 FEAT_EPIC_DEVASTATING_CRITICAL_SICKLE 504 1 -1 0 +45 FEAT_EPIC_DEVASTATING_CRITICAL_SLING 505 1 -1 0 +46 FEAT_EPIC_DEVASTATING_CRITICAL_UNARMED 506 1 -1 0 +47 FEAT_EPIC_DEVASTATING_CRITICAL_LONGBOW 507 1 -1 0 +48 FEAT_EPIC_DEVASTATING_CRITICAL_SHORTBOW 508 1 -1 0 +49 FEAT_EPIC_DEVASTATING_CRITICAL_SHORTSWORD 509 1 -1 0 +50 FEAT_EPIC_DEVASTATING_CRITICAL_RAPIER 510 1 -1 0 +51 FEAT_EPIC_DEVASTATING_CRITICAL_SCIMITAR 511 1 -1 0 +52 FEAT_EPIC_DEVASTATING_CRITICAL_LONGSWORD 512 1 -1 0 +53 FEAT_EPIC_DEVASTATING_CRITICAL_GREATSWORD 513 1 -1 0 +54 FEAT_EPIC_DEVASTATING_CRITICAL_HANDAXE 514 1 -1 0 +55 FEAT_EPIC_DEVASTATING_CRITICAL_THROWINGAXE 515 1 -1 0 +56 FEAT_EPIC_DEVASTATING_CRITICAL_BATTLEAXE 516 1 -1 0 +57 FEAT_EPIC_DEVASTATING_CRITICAL_GREATAXE 517 1 -1 0 +58 FEAT_EPIC_DEVASTATING_CRITICAL_HALBERD 518 1 -1 0 +59 FEAT_EPIC_DEVASTATING_CRITICAL_LIGHTHAMMER 519 1 -1 0 +60 FEAT_EPIC_DEVASTATING_CRITICAL_LIGHTFLAIL 520 1 -1 0 +61 FEAT_EPIC_DEVASTATING_CRITICAL_WARHAMMER 521 1 -1 0 +62 FEAT_EPIC_DEVASTATING_CRITICAL_HEAVYFLAIL 522 1 -1 0 +63 FEAT_EPIC_DEVASTATING_CRITICAL_KAMA 523 1 -1 0 +64 FEAT_EPIC_DEVASTATING_CRITICAL_KUKRI 524 1 -1 0 +65 FEAT_EPIC_DEVASTATING_CRITICAL_SHURIKEN 525 1 -1 0 +66 FEAT_EPIC_DEVASTATING_CRITICAL_SCYTHE 526 1 -1 0 +67 FEAT_EPIC_DEVASTATING_CRITICAL_KATANA 527 1 -1 0 +68 FEAT_EPIC_DEVASTATING_CRITICAL_BASTARDSWORD 528 1 -1 0 +69 FEAT_EPIC_DEVASTATING_CRITICAL_DIREMACE 529 1 -1 0 +70 FEAT_EPIC_DEVASTATING_CRITICAL_DOUBLEAXE 530 1 -1 0 +71 FEAT_EPIC_DEVASTATING_CRITICAL_TWOBLADEDSWORD 531 1 -1 0 +72 FEAT_EPIC_DEVASTATING_CRITICAL_TRIDENT 1075 1 -1 0 +73 DEVASTATING_CRITICAL_LIGHT_LANCE 24673 1 -1 0 +74 DEVASTATING_CRITICAL_HEAVY_PICK 24674 1 -1 0 +75 DEVASTATING_CRITICAL_LIGHT_PICK 24675 1 -1 0 +76 DEVASTATING_CRITICAL_SAI 24676 1 -1 0 +77 DEVASTATING_CRITICAL_NUNCHAKU 24677 1 -1 0 +78 DEVASTATING_CRITICAL_FALCHION 24678 1 -1 0 +79 DEVASTATING_CRITICAL_SAP 24679 1 -1 0 +80 DEVASTATING_CRITICAL_KATAR 24680 1 -1 0 +81 DEVASTATING_CRITICAL_HEAVY_MACE 24681 1 -1 0 +82 DEVASTATING_CRITICAL_MAUL 24682 1 -1 0 +83 DEVASTATING_CRITICAL_DBL_SCIMITAR 24683 1 -1 0 +84 DEVASTATING_CRITICAL_GOAD 24684 1 -1 0 +85 DEVASTATING_CRITICAL_ELVEN_LIGHTBLADE 24703 1 -1 0 +86 DEVASTATING_CRITICAL_ELVEN_THINBLADE 24711 1 -1 0 +87 DEVASTATING_CRITICAL_ELVEN_COURTBLADE 24719 1 -1 0 +88 FEAT_EPIC_PROWESS 584 1 -1 0 +89 FEAT_EPIC_WEAPON_FOCUS_CLUB 619 1 -1 0 +90 FEAT_EPIC_WEAPON_FOCUS_DAGGER 620 1 -1 0 +91 FEAT_EPIC_WEAPON_FOCUS_DART 621 1 -1 0 +92 FEAT_EPIC_WEAPON_FOCUS_HEAVYCROSSBOW 622 1 -1 0 +93 FEAT_EPIC_WEAPON_FOCUS_LIGHTCROSSBOW 623 1 -1 0 +94 FEAT_EPIC_WEAPON_FOCUS_LIGHTMACE 624 1 -1 0 +95 FEAT_EPIC_WEAPON_FOCUS_MORNINGSTAR 625 1 -1 0 +96 FEAT_EPIC_WEAPON_FOCUS_QUARTERSTAFF 626 1 -1 0 +97 FEAT_EPIC_WEAPON_FOCUS_SHORTSPEAR 627 1 -1 0 +98 FEAT_EPIC_WEAPON_FOCUS_SICKLE 628 1 -1 0 +99 FEAT_EPIC_WEAPON_FOCUS_SLING 629 1 -1 0 +100 FEAT_EPIC_WEAPON_FOCUS_UNARMED 630 1 -1 0 +101 FEAT_EPIC_WEAPON_FOCUS_LONGBOW 631 1 -1 0 +102 FEAT_EPIC_WEAPON_FOCUS_SHORTBOW 632 1 -1 0 +103 FEAT_EPIC_WEAPON_FOCUS_SHORTSWORD 633 1 -1 0 +104 FEAT_EPIC_WEAPON_FOCUS_RAPIER 634 1 -1 0 +105 FEAT_EPIC_WEAPON_FOCUS_SCIMITAR 635 1 -1 0 +106 FEAT_EPIC_WEAPON_FOCUS_LONGSWORD 636 1 -1 0 +107 FEAT_EPIC_WEAPON_FOCUS_GREATSWORD 637 1 -1 0 +108 FEAT_EPIC_WEAPON_FOCUS_HANDAXE 638 1 -1 0 +109 FEAT_EPIC_WEAPON_FOCUS_THROWINGAXE 639 1 -1 0 +110 FEAT_EPIC_WEAPON_FOCUS_BATTLEAXE 640 1 -1 0 +111 FEAT_EPIC_WEAPON_FOCUS_GREATAXE 641 1 -1 0 +112 FEAT_EPIC_WEAPON_FOCUS_HALBERD 642 1 -1 0 +113 FEAT_EPIC_WEAPON_FOCUS_LIGHTHAMMER 643 1 -1 0 +114 FEAT_EPIC_WEAPON_FOCUS_LIGHTFLAIL 644 1 -1 0 +115 FEAT_EPIC_WEAPON_FOCUS_WARHAMMER 645 1 -1 0 +116 FEAT_EPIC_WEAPON_FOCUS_HEAVYFLAIL 646 1 -1 0 +117 FEAT_EPIC_WEAPON_FOCUS_KAMA 647 1 -1 0 +118 FEAT_EPIC_WEAPON_FOCUS_KUKRI 648 1 -1 0 +119 FEAT_EPIC_WEAPON_FOCUS_SHURIKEN 649 1 -1 0 +120 FEAT_EPIC_WEAPON_FOCUS_SCYTHE 650 1 -1 0 +121 FEAT_EPIC_WEAPON_FOCUS_KATANA 651 1 -1 0 +122 FEAT_EPIC_WEAPON_FOCUS_BASTARDSWORD 652 1 -1 0 +123 FEAT_EPIC_WEAPON_FOCUS_DIREMACE 653 1 -1 0 +124 FEAT_EPIC_WEAPON_FOCUS_DOUBLEAXE 654 1 -1 0 +125 FEAT_EPIC_WEAPON_FOCUS_TWOBLADEDSWORD 655 1 -1 0 +126 EPIC_WEAPON_FOCUS_LANCE 24613 1 -1 0 +127 EPIC_WEAPON_FOCUS_HEAVY_PICK 24614 1 -1 0 +128 EPIC_WEAPON_FOCUS_LIGHT_PICK 24615 1 -1 0 +129 EPIC_WEAPON_FOCUS_SAI 24616 1 -1 0 +130 EPIC_WEAPON_FOCUS_NUNCHAKU 24617 1 -1 0 +131 EPIC_WEAPON_FOCUS_FALCHION 24618 1 -1 0 +132 EPIC_WEAPON_FOCUS_SAP 24619 1 -1 0 +133 EPIC_WEAPON_FOCUS_KATAR 24620 1 -1 0 +134 EPIC_WEAPON_FOCUS_HEAVY_MACE 24621 1 -1 0 +135 EPIC_WEAPON_FOCUS_MAUL 24622 1 -1 0 +136 EPIC_WEAPON_FOCUS_DBL_SCIMITAR 24623 1 -1 0 +137 EPIC_WEAPON_FOCUS_GOAD 24624 1 -1 0 +138 EPIC_WEAPON_FOCUS_ELVEN_LIGHTBLADE 24698 1 -1 0 +139 EPIC_WEAPON_FOCUS_ELVEN_THINBLADE 24706 1 -1 0 +140 EPIC_WEAPON_FOCUS_ELVEN_COURTBLADE 24714 1 -1 0 +141 FEAT_EPIC_WEAPON_SPECIALIZATION_CLUB 657 1 -1 0 +142 FEAT_EPIC_WEAPON_SPECIALIZATION_DAGGER 658 1 -1 0 +143 FEAT_EPIC_WEAPON_SPECIALIZATION_DART 659 1 -1 0 +144 FEAT_EPIC_WEAPON_SPECIALIZATION_HEAVYCROSSBOW 660 1 -1 0 +145 FEAT_EPIC_WEAPON_SPECIALIZATION_LIGHTCROSSBOW 661 1 -1 0 +146 FEAT_EPIC_WEAPON_SPECIALIZATION_LIGHTMACE 662 1 -1 0 +147 FEAT_EPIC_WEAPON_SPECIALIZATION_MORNINGSTAR 663 1 -1 0 +148 FEAT_EPIC_WEAPON_SPECIALIZATION_QUARTERSTAFF 664 1 -1 0 +149 FEAT_EPIC_WEAPON_SPECIALIZATION_SHORTSPEAR 665 1 -1 0 +150 FEAT_EPIC_WEAPON_SPECIALIZATION_SICKLE 666 1 -1 0 +151 FEAT_EPIC_WEAPON_SPECIALIZATION_SLING 667 1 -1 0 +152 FEAT_EPIC_WEAPON_SPECIALIZATION_UNARMED 668 1 -1 0 +153 FEAT_EPIC_WEAPON_SPECIALIZATION_LONGBOW 669 1 -1 0 +154 FEAT_EPIC_WEAPON_SPECIALIZATION_SHORTBOW 670 1 -1 0 +155 FEAT_EPIC_WEAPON_SPECIALIZATION_SHORTSWORD 671 1 -1 0 +156 FEAT_EPIC_WEAPON_SPECIALIZATION_RAPIER 672 1 -1 0 +157 FEAT_EPIC_WEAPON_SPECIALIZATION_SCIMITAR 673 1 -1 0 +158 FEAT_EPIC_WEAPON_SPECIALIZATION_LONGSWORD 674 1 -1 0 +159 FEAT_EPIC_WEAPON_SPECIALIZATION_GREATSWORD 675 1 -1 0 +160 FEAT_EPIC_WEAPON_SPECIALIZATION_HANDAXE 676 1 -1 0 +161 FEAT_EPIC_WEAPON_SPECIALIZATION_THROWINGAXE 677 1 -1 0 +162 FEAT_EPIC_WEAPON_SPECIALIZATION_BATTLEAXE 678 1 -1 0 +163 FEAT_EPIC_WEAPON_SPECIALIZATION_GREATAXE 679 1 -1 0 +164 FEAT_EPIC_WEAPON_SPECIALIZATION_HALBERD 680 1 -1 0 +165 FEAT_EPIC_WEAPON_SPECIALIZATION_LIGHTHAMMER 681 1 -1 0 +166 FEAT_EPIC_WEAPON_SPECIALIZATION_LIGHTFLAIL 682 1 -1 0 +167 FEAT_EPIC_WEAPON_SPECIALIZATION_WARHAMMER 683 1 -1 0 +168 FEAT_EPIC_WEAPON_SPECIALIZATION_HEAVYFLAIL 684 1 -1 0 +169 FEAT_EPIC_WEAPON_SPECIALIZATION_KAMA 685 1 -1 0 +170 FEAT_EPIC_WEAPON_SPECIALIZATION_KUKRI 686 1 -1 0 +171 FEAT_EPIC_WEAPON_SPECIALIZATION_SHURIKEN 687 1 -1 0 +172 FEAT_EPIC_WEAPON_SPECIALIZATION_SCYTHE 688 1 -1 0 +173 FEAT_EPIC_WEAPON_SPECIALIZATION_KATANA 689 1 -1 0 +174 FEAT_EPIC_WEAPON_SPECIALIZATION_BASTARDSWORD 690 1 -1 0 +175 FEAT_EPIC_WEAPON_SPECIALIZATION_DIREMACE 691 1 -1 0 +176 FEAT_EPIC_WEAPON_SPECIALIZATION_DOUBLEAXE 692 1 -1 0 +177 FEAT_EPIC_WEAPON_SPECIALIZATION_TWOBLADEDSWORD 693 1 -1 0 +178 EPIC_WEAPON_SPECIALIZATION_LIGHT_LANCE 24637 1 -1 0 +179 EPIC_WEAPON_SPECIALIZATION_HEAVY_PICK 24638 1 -1 0 +180 EPIC_WEAPON_SPECIALIZATION_LIGHT_PICK 24639 1 -1 0 +181 EPIC_WEAPON_SPECIALIZATION_SAI 24640 1 -1 0 +182 EPIC_WEAPON_SPECIALIZATION_NUNCHAKU 24641 1 -1 0 +183 EPIC_WEAPON_SPECIALIZATION_FALCHION 24642 1 -1 0 +184 EPIC_WEAPON_SPECIALIZATION_SAP 24643 1 -1 0 +185 EPIC_WEAPON_SPECIALIZATION_KATAR 24644 1 -1 0 +186 EPIC_WEAPON_SPECIALIZATION_HEAVY_MACE 24645 1 -1 0 +187 EPIC_WEAPON_SPECIALIZATION_MAUL 24646 1 -1 0 +188 EPIC_WEAPON_SPECIALIZATION_DBL_SCIMITAR 24647 1 -1 0 +189 EPIC_WEAPON_SPECIALIZATION_GOAD 24648 1 -1 0 +190 EPIC_WEAPON_SPECIALIZATION_ELVEN_LIGHTBLADE 24700 1 -1 0 +191 EPIC_WEAPON_SPECIALIZATION_ELVEN_THINBLADE 24708 1 -1 0 +192 EPIC_WEAPON_SPECIALIZATION_ELVEN_COURTBLADE 24716 1 -1 0 +193 FEAT_EPIC_OVERWHELMING_CRITICAL_CLUB 709 1 -1 0 +194 FEAT_EPIC_OVERWHELMING_CRITICAL_DAGGER 710 1 -1 0 +195 FEAT_EPIC_OVERWHELMING_CRITICAL_DART 711 1 -1 0 +196 FEAT_EPIC_OVERWHELMING_CRITICAL_HEAVYCROSSBOW 712 1 -1 0 +197 FEAT_EPIC_OVERWHELMING_CRITICAL_LIGHTCROSSBOW 713 1 -1 0 +198 FEAT_EPIC_OVERWHELMING_CRITICAL_LIGHTMACE 714 1 -1 0 +199 FEAT_EPIC_OVERWHELMING_CRITICAL_MORNINGSTAR 715 1 -1 0 +200 FEAT_EPIC_OVERWHELMING_CRITICAL_QUARTERSTAFF 716 1 -1 0 +201 FEAT_EPIC_OVERWHELMING_CRITICAL_SHORTSPEAR 717 1 -1 0 +202 FEAT_EPIC_OVERWHELMING_CRITICAL_SICKLE 718 1 -1 0 +203 FEAT_EPIC_OVERWHELMING_CRITICAL_SLING 719 1 -1 0 +204 FEAT_EPIC_OVERWHELMING_CRITICAL_UNARMED 720 1 -1 0 +205 FEAT_EPIC_OVERWHELMING_CRITICAL_LONGBOW 721 1 -1 0 +206 FEAT_EPIC_OVERWHELMING_CRITICAL_SHORTBOW 722 1 -1 0 +207 FEAT_EPIC_OVERWHELMING_CRITICAL_SHORTSWORD 723 1 -1 0 +208 FEAT_EPIC_OVERWHELMING_CRITICAL_RAPIER 724 1 -1 0 +209 FEAT_EPIC_OVERWHELMING_CRITICAL_SCIMITAR 725 1 -1 0 +210 FEAT_EPIC_OVERWHELMING_CRITICAL_LONGSWORD 726 1 -1 0 +211 FEAT_EPIC_OVERWHELMING_CRITICAL_GREATSWORD 727 1 -1 0 +212 FEAT_EPIC_OVERWHELMING_CRITICAL_HANDAXE 728 1 -1 0 +213 FEAT_EPIC_OVERWHELMING_CRITICAL_THROWINGAXE 729 1 -1 0 +214 FEAT_EPIC_OVERWHELMING_CRITICAL_BATTLEAXE 730 1 -1 0 +215 FEAT_EPIC_OVERWHELMING_CRITICAL_GREATAXE 731 1 -1 0 +216 FEAT_EPIC_OVERWHELMING_CRITICAL_HALBERD 732 1 -1 0 +217 FEAT_EPIC_OVERWHELMING_CRITICAL_LIGHTHAMMER 733 1 -1 0 +218 FEAT_EPIC_OVERWHELMING_CRITICAL_LIGHTFLAIL 734 1 -1 0 +219 FEAT_EPIC_OVERWHELMING_CRITICAL_WARHAMMER 735 1 -1 0 +220 FEAT_EPIC_OVERWHELMING_CRITICAL_HEAVYFLAIL 736 1 -1 0 +221 FEAT_EPIC_OVERWHELMING_CRITICAL_KAMA 737 1 -1 0 +222 FEAT_EPIC_OVERWHELMING_CRITICAL_KUKRI 738 1 -1 0 +223 FEAT_EPIC_OVERWHELMING_CRITICAL_SHURIKEN 739 1 -1 0 +224 FEAT_EPIC_OVERWHELMING_CRITICAL_SCYTHE 740 1 -1 0 +225 FEAT_EPIC_OVERWHELMING_CRITICAL_KATANA 741 1 -1 0 +226 FEAT_EPIC_OVERWHELMING_CRITICAL_BASTARDSWORD 742 1 -1 0 +227 FEAT_EPIC_OVERWHELMING_CRITICAL_DIREMACE 743 1 -1 0 +228 FEAT_EPIC_OVERWHELMING_CRITICAL_DOUBLEAXE 744 1 -1 0 +229 FEAT_EPIC_OVERWHELMING_CRITICAL_TWOBLADEDSWORD 745 1 -1 0 +230 OVERWHELMING_CRITICAL_LIGHT_LANCE 24661 1 -1 0 +231 OVERWHELMING_CRITICAL_HEAVY_PICK 24662 1 -1 0 +232 OVERWHELMING_CRITICAL_LIGHT_PICK 24663 1 -1 0 +233 OVERWHELMING_CRITICAL_SAI 24664 1 -1 0 +234 OVERWHELMING_CRITICAL_NUNCHAKU 24665 1 -1 0 +235 OVERWHELMING_CRITICAL_FALCHION 24666 1 -1 0 +236 OVERWHELMING_CRITICAL_SAP 24667 1 -1 0 +237 OVERWHELMING_CRITICAL_KATAR 24668 1 -1 0 +238 OVERWHELMING_CRITICAL_HEAVY_MACE 24669 1 -1 0 +239 OVERWHELMING_CRITICAL_MAUL 24670 1 -1 0 +240 OVERWHELMING_CRITICAL_DBL_SCIMITAR 24671 1 -1 0 +241 OVERWHELMING_CRITICAL_GOAD 24672 1 -1 0 +242 OVERWHELMING_CRITICAL_ELVEN_LIGHTBLADE 24702 1 -1 0 +243 OVERWHELMING_CRITICAL_ELVEN_THINBLADE 24710 1 -1 0 +244 OVERWHELMING_CRITICAL_ELVEN_COURTBLADE 24718 1 -1 0 +245 FEAT_EPIC_TOUGHNESS_1 754 1 -1 0 +246 FEAT_EPIC_TOUGHNESS_2 755 1 -1 0 +247 FEAT_EPIC_TOUGHNESS_3 756 1 -1 0 +248 FEAT_EPIC_TOUGHNESS_4 757 1 -1 0 +249 FEAT_EPIC_TOUGHNESS_5 758 1 -1 0 +250 FEAT_EPIC_TOUGHNESS_6 759 1 -1 0 +251 FEAT_EPIC_TOUGHNESS_7 760 1 -1 0 +252 FEAT_EPIC_TOUGHNESS_8 761 1 -1 0 +253 FEAT_EPIC_TOUGHNESS_9 762 1 -1 0 +254 FEAT_EPIC_TOUGHNESS_10 763 1 -1 0 +255 FEAT_EPIC_DEVASTATING_CRITICAL_DWAXE 955 1 -1 0 +256 FEAT_EPIC_WEAPON_FOCUS_DWAXE 956 1 -1 0 +257 FEAT_EPIC_WEAPON_SPECIALIZATION_DWAXE 957 1 -1 0 +258 FEAT_EPIC_OVERWHELMING_CRITICAL_DWAXE 958 1 -1 0 +259 FEAT_EPIC_WEAPON_FOCUS_WHIP 997 1 -1 0 +260 FEAT_EPIC_DEVASTATING_CRITICAL_WHIP 996 1 -1 0 +261 FEAT_EPIC_OVERWHELMING_CRITICAL_WHIP 999 1 -1 0 +262 FEAT_EPIC_WEAPON_SPECIALIZATION_WHIP 998 1 -1 0 +263 FEAT_IMPROVED_WHIRLWIND 868 1 -1 0 +264 FEAT_EPIC_SUPERIOR_INITIATIVE 753 1 -1 0 +265 FEAT_EPIC_BLINDING_SPEED 491 1 -1 1 +266 OverWCrit_Mindblade 3615 1 -1 0 +267 DevCrit_Mindblade 3616 1 -1 0 +268 Epic_WeaponFocus_Mindblade 3618 1 -1 0 +269 Epic_WeaponSpecialization_Mindblade 3619 1 -1 0 +270 PERFECT_TWO_WEAPON_FIGHTING 4320 1 -1 0 +271 FEAT_EPIC_LEADERSHIP 4366 1 -1 0 +272 FEAT_LEGENDARY_COMMANDER 4367 1 -1 0 +273 Two-Weapon_Rend 3113 1 -1 0 +274 FEAT_EPIC_GREAT_CHARISMA_2 764 1 -1 0 +275 FEAT_EPIC_GREAT_CHARISMA_3 765 1 -1 0 +276 FEAT_EPIC_GREAT_CHARISMA_4 766 1 -1 0 +277 FEAT_EPIC_GREAT_CHARISMA_5 767 1 -1 0 +278 FEAT_EPIC_GREAT_CHARISMA_6 768 1 -1 0 +279 FEAT_EPIC_GREAT_CHARISMA_7 769 1 -1 0 +280 FEAT_EPIC_GREAT_CHARISMA_8 770 1 -1 0 +281 FEAT_EPIC_GREAT_CHARISMA_9 771 1 -1 0 +282 FEAT_EPIC_GREAT_CHARISMA_10 772 1 -1 0 +283 FEAT_EPIC_GREAT_CONSTITUTION_1 773 1 -1 0 +284 FEAT_EPIC_GREAT_CONSTITUTION_2 774 1 -1 0 +285 FEAT_EPIC_GREAT_CONSTITUTION_3 775 1 -1 0 +286 FEAT_EPIC_GREAT_CONSTITUTION_4 776 1 -1 0 +287 FEAT_EPIC_GREAT_CONSTITUTION_5 777 1 -1 0 +288 FEAT_EPIC_GREAT_CONSTITUTION_6 778 1 -1 0 +289 FEAT_EPIC_GREAT_CONSTITUTION_7 779 1 -1 0 +290 FEAT_EPIC_GREAT_CONSTITUTION_8 780 1 -1 0 +291 FEAT_EPIC_GREAT_CONSTITUTION_9 781 1 -1 0 +292 FEAT_EPIC_GREAT_CONSTITUTION_10 782 1 -1 0 +293 ExtraGrantedManeuver 4096 0 -1 0 +294 StonePower 5294 0 -1 1 +295 VitalRecovery 3636 0 -1 0 +296 DefArrow 8 0 -1 0 +297 TwoWeap 41 0 -1 0 +298 Ambidex 1 0 -1 0 diff --git a/nwn/nwnprc/trunk/2das/prc_vop_feats.2da b/nwn/nwnprc/trunk/2das/prc_vop_feats.2da index 65f9fa3f..5ea38328 100644 --- a/nwn/nwnprc/trunk/2das/prc_vop_feats.2da +++ b/nwn/nwnprc/trunk/2das/prc_vop_feats.2da @@ -3,13 +3,13 @@ Label Name FeatIndex PreReq1 PreReq2 Con Wis Cha BAB Law 0 FEAT_EXALTED_COMPANION "Exalted Companion" 4199 199 **** **** **** **** **** **** 1 FEAT_EXALTED_TURNING "Exalted Turning" 3168 294 **** **** **** **** **** **** -2 FEAT_FISTOFHEAVENS "Fist of Heavens" 26004 213 26002 **** 15 **** **** **** -3 FEAT_GIFTOFFAITH "Gift of Faith" 26007 **** **** **** 13 **** **** **** -4 FEAT_HOLYKISTRIKE "Holy Ki Strike" 26003 213 26002 **** **** 15 **** **** +2 FEAT_FISTOFHEAVENS "Fist of Heavens" 26005 213 26003 **** 15 **** **** **** +3 FEAT_GIFTOFFAITH "Gift of Faith" 26008 **** **** **** 13 **** **** **** +4 FEAT_HOLYKISTRIKE "Holy Ki Strike" 26004 213 26003 **** **** 15 **** **** 5 FEAT_HOLYRADIANCE "Holy Radiance" 3164 3165 **** **** **** 15 **** **** 6 FEAT_INTUITIVE_ATTACK "Intuitive Attack" 3166 **** **** **** **** **** 1 **** 7 FEAT_NIMBUSLIGHT "Nimbus of Light" 3165 **** **** **** **** **** 1 **** -8 FEAT_SANCTIFYKISTRIKE "Sanctify Ki Strike" 26002 213 **** **** **** 15 **** **** +8 FEAT_SANCTIFYKISTRIKE "Sanctify Ki Strike" 26003 213 **** **** **** 15 **** **** 9 FEAT_SANCTIFY_MARTIAL_CLUB "Sanctify Martial Strike (Club)" 3194 43 **** **** **** 15 **** **** 10 FEAT_SANCTIFY_MARTIAL_DAGGER "Sanctify Martial Strike (Dagger)" 3195 90 **** **** **** 15 **** **** 11 FEAT_SANCTIFY_MARTIAL_DART "Sanctify Martial Strike (Dard)" 3227 91 **** **** **** 15 **** **** @@ -24,7 +24,7 @@ 20 FEAT_SERVHEAVEN "Servant of the Heavens" 3355 **** **** **** **** **** **** **** 21 FEAT_STIGMATA Stigmata 3163 3165 **** **** **** **** **** **** 22 FEAT_RAVAGEGOLDENICE "Touch of Golden Ice" 3162 **** **** 13 **** **** **** **** -23 FEAT_VOWABSTINENCE "Vow of Abstinence" 26005 **** **** **** **** **** **** **** -24 FEAT_VOWCHASTITY "Vow of Chastity" 26006 **** **** **** **** **** **** **** +23 FEAT_VOWABSTINENCE "Vow of Abstinence" 26006 **** **** **** **** **** **** **** +24 FEAT_VOWCHASTITY "Vow of Chastity" 26007 **** **** **** **** **** **** **** 25 FEAT_VOW_OBED "Vow of Obedience" 3389 **** **** **** **** **** **** 1 26 FEAT_VOW_PURITY "Vow of Purity" 5360 **** **** **** **** **** **** **** diff --git a/nwn/nwnprc/trunk/include/inc_switch_setup.nss b/nwn/nwnprc/trunk/include/inc_switch_setup.nss index afffaae2..95e378bc 100644 --- a/nwn/nwnprc/trunk/include/inc_switch_setup.nss +++ b/nwn/nwnprc/trunk/include/inc_switch_setup.nss @@ -876,6 +876,8 @@ void CreateSwitchNameArray() array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_BIOWARE_HARM); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_BIOWARE_NEUTRALIZE_POISON); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_BIOWARE_REMOVE_DISEASE); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_BIO_UNLEARN); + array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_UNLEARN_SPELL_MAXNR); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_TIMESTOP_BIOWARE_DURATION); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_TIMESTOP_LOCAL); array_set_string(oWP, "Switch_Name", array_get_size(oWP, "Switch_Name"), PRC_TIMESTOP_NO_HOSTILE); diff --git a/nwn/nwnprc/trunk/include/prc_feat_const.nss b/nwn/nwnprc/trunk/include/prc_feat_const.nss index c961dabb..a666b3e7 100644 --- a/nwn/nwnprc/trunk/include/prc_feat_const.nss +++ b/nwn/nwnprc/trunk/include/prc_feat_const.nss @@ -1549,7 +1549,7 @@ const int FEAT_STIGMATA = 3163; const int FEAT_SERVHEAVEN = 3355; const int FEAT_RANGED_SMITE = 3356; const int FEAT_VOW_PURITY = 5360; -const int FEAT_VOWOFPOVERTY = 26001; +const int FEAT_VOWOFPOVERTY = 26002; //Vile Feat const int FEAT_LICHLOVED = 3395; @@ -1868,12 +1868,12 @@ const int FEAT_SANCTIFY_MARTIAL_SICKLE = 3169; const int FEAT_SANCTIFY_MARTIAL_MINDBLADE = 3623; const int FEAT_SANCTIFY_MARTIAL_WHIP = 3596; const int FEAT_SANCTIFY_MARTIAL_TRIDENT = 3597; -const int FEAT_SANCTIFYKISTRIKE = 26002; -const int FEAT_HOLYKISTRIKE = 26003; -const int FEAT_FISTOFHEAVENS = 26004; -const int FEAT_VOWABSTINENCE = 26005; -const int FEAT_VOWCHASTITY = 26006; -const int FEAT_GIFTOFFAITH = 26007; +const int FEAT_SANCTIFYKISTRIKE = 26003; +const int FEAT_HOLYKISTRIKE = 26004; +const int FEAT_FISTOFHEAVENS = 26005; +const int FEAT_VOWABSTINENCE = 26006; +const int FEAT_VOWCHASTITY = 26007; +const int FEAT_GIFTOFFAITH = 26008; //heartwarder const int FEAT_CHARISMA_INC1 = 3230; diff --git a/nwn/nwnprc/trunk/include/prc_inc_castlvl.nss b/nwn/nwnprc/trunk/include/prc_inc_castlvl.nss index 876ac84c..172ee03d 100644 --- a/nwn/nwnprc/trunk/include/prc_inc_castlvl.nss +++ b/nwn/nwnprc/trunk/include/prc_inc_castlvl.nss @@ -575,8 +575,8 @@ int PRCGetCasterLevel(object oCaster = OBJECT_SELF) iReturnLevel = GetLevelByClass(CLASS_TYPE_SHAPECHANGER); } - // Casting as a bard but don't have any levels in the class - if(iCastingClass == CLASS_TYPE_BARD && !GetLevelByClass(CLASS_TYPE_BARD, oCaster)) + // Casting as a bard but don't have any levels in the class //:: Double-dipping? +/* if(iCastingClass == CLASS_TYPE_BARD && !GetLevelByClass(CLASS_TYPE_BARD, oCaster)) { int nRace = GetRacialType(oCaster); @@ -584,7 +584,7 @@ int PRCGetCasterLevel(object oCaster = OBJECT_SELF) //otherwise use RHD instead of bard levels if(nRace == RACIAL_TYPE_GLOURA) iReturnLevel = GetLevelByClass(CLASS_TYPE_FEY); - } + } */ //Spell Rage ability if(GetHasSpellEffect(SPELL_SPELL_RAGE, oCaster) @@ -960,8 +960,10 @@ int GetArcanePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) } //:: End Bard Arcane PrC casting calculations - if(nCastingClass == CLASS_TYPE_BARD && nRace == RACIAL_TYPE_GLOURA && !GetLevelByClass(CLASS_TYPE_BARD, oCaster)) + if(nCastingClass == CLASS_TYPE_BARD || nCastingClass == CLASS_TYPE_BARD && nRace == RACIAL_TYPE_GLOURA && !GetLevelByClass(CLASS_TYPE_BARD, oCaster)) { + if(DEBUG) DoDebug("prc_inc_castlvl >> Found Fey RHD caster (not bard)"); + if(GetHasFeat(FEAT_ABCHAMP_SPELLCASTING_FEY, oCaster)) nArcane += GetLevelByClass(CLASS_TYPE_ABJURANT_CHAMPION, oCaster); @@ -1065,7 +1067,10 @@ int GetArcanePRCLevels(object oCaster, int nCastingClass = CLASS_TYPE_INVALID) nArcane += GetLevelByClass(CLASS_TYPE_UNSEEN_SEER, oCaster); if(GetHasFeat(FEAT_VIRTUOSO_SPELLCASTING_FEY, oCaster)) + { nArcane += GetLevelByClass(CLASS_TYPE_VIRTUOSO, oCaster); + if(DEBUG) DoDebug("prc_inc_castlvl >> Found Fey + Virtuoso PrC. Arcane caster level is "+IntToString(nArcane)+"."); + } if(GetHasFeat(FEAT_WWOC_SPELLCASTING_FEY, oCaster)) nArcane += GetLevelByClass(CLASS_TYPE_WAR_WIZARD_OF_CORMYR, oCaster); diff --git a/nwn/nwnprc/trunk/include/prc_inc_spells.nss b/nwn/nwnprc/trunk/include/prc_inc_spells.nss index 19e73d75..98e812dd 100644 --- a/nwn/nwnprc/trunk/include/prc_inc_spells.nss +++ b/nwn/nwnprc/trunk/include/prc_inc_spells.nss @@ -382,17 +382,17 @@ int GetPrCAdjustedClassLevel(int nClass, object oCaster = OBJECT_SELF) // is it arcane, divine or neither? if(GetIsArcaneClass(nClass, oCaster) && nClass != CLASS_TYPE_SUBLIME_CHORD) { - if (GetPrimaryArcaneClass(oCaster) == nClass) // adjust for any PrCs + if (GetPrimaryArcaneClass(oCaster) == nClass) // adjust for any PrCs iTemp = GetArcanePRCLevels(oCaster, nClass); } else if(GetIsDivineClass(nClass, oCaster)) { - if (GetPrimaryDivineClass(oCaster) == nClass) // adjust for any PrCs - iTemp = GetDivinePRCLevels(oCaster, nClass); + if (GetPrimaryDivineClass(oCaster) == nClass) // adjust for any PrCs + iTemp = GetDivinePRCLevels(oCaster, nClass); } else // a non-caster class or a PrC { - return 0; + return 0; } // add the caster class levels return iTemp += GetLevelByClass(nClass, oCaster); diff --git a/nwn/nwnprc/trunk/include/prc_nui_com_inc.nss b/nwn/nwnprc/trunk/include/prc_nui_com_inc.nss new file mode 100644 index 00000000..bf9bc51a --- /dev/null +++ b/nwn/nwnprc/trunk/include/prc_nui_com_inc.nss @@ -0,0 +1,530 @@ +#include "prc_nui_consts" +#include "inc_newspellbook" +#include "psi_inc_psifunc" +#include "inc_lookups" +#include "nw_inc_nui" + +// +// GetCurrentSpellLevel +// Gets the current spell level the class can achieve at the current +// caster level (ranging from 0-9) +// +// Arguments: +// nClass:int the ClassID +// nLevel:int the caster level +// +// Returns: +// int the circle the class can achieve currently +// +int GetCurrentSpellLevel(int nClass, int nLevel); + +// +// GetMaxSpellLevel +// Gets the highest possible circle the class can achieve (from 0-9) +// +// Arguments: +// nClass:int the ClassID +// +// Returns: +// int the highest circle that can be achieved +// +int GetMaxSpellLevel(int nClass); + +// +// GetMinSpellLevel +// Gets the lowest possible circle the class can achieve (from 0-9) +// +// Arguments: +// nClass:int the ClassID +// +// Returns: +// int the lowest circle that can be achieved +// +int GetMinSpellLevel(int nClass); + +// +// GetHighestLevelPossibleInClass +// Given a class Id this will determine what the max level of a class can be +// achieved +// +// Arguments: +// nClass:int the ClassID +// +// Returns: +// int the highest possible level the class can achieve +// +int GetHighestLevelPossibleInClass(int nClass); + +// +// GetClassSpellbookFile +// Gets the class 2da spellbook/ability for the given class Id +// +// Arguments: +// nClass:int the classID +// +// Returns: +// string the 2da file name for the spell/abilities of the ClassID +// +string GetClassSpellbookFile(int nClass); + +// +// GetBinderSpellToFeatDictionary +// Sets up the Binder Spell Dictionary that is used to match a binder's vestige +// to their feat. This is constructed based off the binder's known location of +// their feat and spell ranges in the base 2das respectivly. After constructing +// this it will be saved to the player locally as a cached result since we do +// not need to call this again. +// +// Argument: +// oPlayer:object the player +// +// Returns: +// json:Dictionary a dictionary of mapping between the SpellID +// and the FeatID of a vestige ability +// +json GetBinderSpellToFeatDictionary(object oPlayer=OBJECT_SELF); + +// +// GetSpellLevelIcon +// Takes the spell circle int and gets the icon appropriate for it (i.e. 0 turns +// into "ir_cantrips" +// +// Arguments: +// spellLevel:int the spell level we want the icon for +// +// Returns: +// string the spell level icon +// +string GetSpellLevelIcon(int spellLevel); + +// +// GetSpellLevelToolTip +// Gets the spell level tool tip text based on the int spell level provided (i.e. +// 0 turns into "Cantrips") +// +// Arguments: +// spellLevel:int the spell level we want the tooltip for +// +// Returns: +// string the spell level toop tip +// +string GetSpellLevelToolTip(int spellLevel); + +// +// GetSpellIcon +// Gets the spell icon based off the spellId, or featId supplied +// +// Arguments: +// nClass:int the class Id +// featId:int the featId we can use the icon for +// spellId:int the spell Id we want the icon for +// +// Returns: +// json:String the string of the icon we want. +// +json GetSpellIcon(int spellId, int featId=0, int nClass=0); +string GetSpellName(int spellId, int realSpellID=0, int featId=0, int nClass=0); + +// +// GreyOutButton +// Takes NUI Button along with it's width and height and greys it out it with a drawn +// colored rectangle to represent it's not been selected or not valid. +// +// Arguments: +// jButton:json the NUI Button +// w:float the width of the button +// h:float the height of the button +// +// Returns: +// json the NUI button greyed out +// +json GreyOutButton(json jButton, float w, float h); + +// +// CreateGreyOutRectangle +// Creates a grey out rectangle for buttons +// +// Arguments: +// w:float the width of the button +// h:float the height of the button +// +// Returns: +// json the transparant black rectangle +// +json CreateGreyOutRectangle(float w, float h); + +void CreateSpellDescriptionNUI(object oPlayer, int featID, int spellId=0, int realSpellId=0, int nClass=0); +void ClearSpellDescriptionNUI(object oPlayer=OBJECT_SELF); + +int GetCurrentSpellLevel(int nClass, int nLevel) +{ + int currentLevel = nLevel; + + // ToB doesn't have a concept of spell levels, but still match up to it + if(nClass == CLASS_TYPE_WARBLADE + || nClass == CLASS_TYPE_SWORDSAGE + || nClass == CLASS_TYPE_CRUSADER + || nClass == CLASS_TYPE_SHADOWCASTER) + { + return 9; + } + + + // Binders don't really have a concept of spell level + if (nClass == CLASS_TYPE_BINDER + || nClass == CLASS_TYPE_DRAGON_SHAMAN) // they can only reach 1st circle + return 1; + + //Shadowsmith has no concept of spell levels + if (nClass == CLASS_TYPE_SHADOWSMITH) + return 2; + + if (nClass == CLASS_TYPE_WARLOCK + || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT) + return 4; + + // Spont casters have their own function + if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS + || nClass == CLASS_TYPE_ARCHIVIST) + { + + int maxLevel = GetMaxSpellLevelForCasterLevel(nClass, currentLevel); + return maxLevel; + } + else + { + // everyone else uses this + string spellLevel2da = GetAMSKnownFileName(nClass); + + currentLevel = nLevel - 1; // Level is 1 off of the row in the 2da + + if (nClass == CLASS_TYPE_FIST_OF_ZUOKEN + || nClass == CLASS_TYPE_PSION + || nClass == CLASS_TYPE_PSYWAR + || nClass == CLASS_TYPE_WILDER + || nClass == CLASS_TYPE_PSYCHIC_ROGUE + || nClass == CLASS_TYPE_WARMIND) + currentLevel = GetManifesterLevel(OBJECT_SELF, nClass, TRUE) - 1; + + int totalLevel = Get2DARowCount(spellLevel2da); + + // in case we somehow go over bounds just don't :) + if (currentLevel >= totalLevel) + currentLevel = totalLevel - 1; + + //Psionics have MaxPowerLevel as their column name + string columnName = "MaxPowerLevel"; + + //Invokers have MaxInvocationLevel + if (nClass == CLASS_TYPE_WARLOCK + || nClass == CLASS_TYPE_DRAGON_SHAMAN + || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT) + columnName = "MaxInvocationLevel"; + + // Truenamers have 3 sets of utterances, ranging from 1-6, EvolvingMind covers the entire range + if (nClass == CLASS_TYPE_TRUENAMER) + { + columnName = "EvolvingMind"; + spellLevel2da = "cls_true_maxlvl"; //has a different 2da we want to look at + } + + if (nClass == CLASS_TYPE_BINDER) + { + columnName = "VestigeLvl"; + spellLevel2da = "cls_bind_binder"; + } + + // ToB doesn't have a concept of this, but we don't care. + + int maxLevel = StringToInt(Get2DACache(spellLevel2da, columnName, currentLevel)); + return maxLevel; + } +} + +int GetMinSpellLevel(int nClass) +{ + // again sponts have their own function + if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS + || nClass == CLASS_TYPE_ARCHIVIST) + { + return GetMinSpellLevelForCasterLevel(nClass, GetHighestLevelPossibleInClass(nClass)); + } + else + { + if (nClass == CLASS_TYPE_FIST_OF_ZUOKEN + || nClass == CLASS_TYPE_PSION + || nClass == CLASS_TYPE_PSYWAR + || nClass == CLASS_TYPE_WILDER + || nClass == CLASS_TYPE_PSYCHIC_ROGUE + || nClass == CLASS_TYPE_WARMIND + || nClass == CLASS_TYPE_WARBLADE + || nClass == CLASS_TYPE_SWORDSAGE + || nClass == CLASS_TYPE_CRUSADER + || nClass == CLASS_TYPE_WARLOCK + || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT + || nClass == CLASS_TYPE_DRAGON_SHAMAN + || nClass == CLASS_TYPE_SHADOWCASTER + || nClass == CLASS_TYPE_SHADOWSMITH + || nClass == CLASS_TYPE_BINDER) + return 1; + + return GetCurrentSpellLevel(nClass, 1); + } + +} + +int GetMaxSpellLevel(int nClass) +{ + if (nClass == CLASS_TYPE_WILDER + || nClass == CLASS_TYPE_PSION) + return 9; + if (nClass == CLASS_TYPE_PSYCHIC_ROGUE + || nClass == CLASS_TYPE_FIST_OF_ZUOKEN + || nClass == CLASS_TYPE_WARMIND) + return 5; + if (nClass == CLASS_TYPE_PSYWAR) + return 6; + + return GetCurrentSpellLevel(nClass, GetHighestLevelPossibleInClass(nClass)); +} + +int GetHighestLevelPossibleInClass(int nClass) +{ + string sFile; + + //sponts have their spells in the classes.2da + if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS + || nClass == CLASS_TYPE_ARCHIVIST) + { + sFile = Get2DACache("classes", "SpellGainTable", nClass); + } + else + { + // everyone else uses this + sFile = GetAMSKnownFileName(nClass); + + if (nClass == CLASS_TYPE_TRUENAMER) + { + sFile = "cls_true_maxlvl"; //has a different 2da we want to look at + } + + if (nClass == CLASS_TYPE_BINDER) + { + sFile = "cls_bind_binder"; + } + } + + return Get2DARowCount(sFile); +} + +string GetClassSpellbookFile(int nClass) +{ + string sFile; + // Spontaneous casters use a specific file name structure + if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS + || nClass == CLASS_TYPE_ARCHIVIST) + { + sFile = GetFileForClass(nClass); + } + // everyone else uses this structure + else + { + sFile = GetAMSDefinitionFileName(nClass); + + if (nClass == CLASS_TYPE_BINDER) + { + sFile = "vestiges"; + } + } + + return sFile; +} + +string GetSpellLevelIcon(int spellLevel) +{ + switch (spellLevel) + { + case 0: return "ir_cantrips"; + case 1: return "ir_level1"; + case 2: return "ir_level2"; + case 3: return "ir_level3"; + case 4: return "ir_level4"; + case 5: return "ir_level5"; + case 6: return "ir_level6"; + case 7: return "ir_level789"; + case 8: return "ir_level789"; + case 9: return "ir_level789"; + } + + return ""; +} + +string GetSpellLevelToolTip(int spellLevel) +{ + switch (spellLevel) + { + case 0: return "Cantrips"; + case 1: return "Level 1"; + case 2: return "Level 2"; + case 3: return "Level 3"; + case 4: return "Level 4"; + case 5: return "Level 5"; + case 6: return "Level 6"; + case 7: return "Level 7"; + case 8: return "Level 8"; + case 9: return "Level 9"; + } + + return ""; +} + + +json GetSpellIcon(int spellId,int featId=0,int nClass=0) +{ + // Binder's spells don't have the FeatID on the spells.2da, so we have to use + // the mapping we constructed to get it. + if (nClass == CLASS_TYPE_BINDER) + { + json binderDict = GetBinderSpellToFeatDictionary(); + int nFeatID = JsonGetInt(JsonObjectGet(binderDict, IntToString(spellId))); + return JsonString(Get2DACache("feat", "Icon", featId)); + } + + if (featId) + return JsonString(Get2DACache("feat", "Icon", featId)); + + int masterSpellID = StringToInt(Get2DACache("spells", "Master", spellId)); + + // if this is a sub radial spell, then we use spell's icon instead + if (masterSpellID) + return JsonString(Get2DACache("spells", "IconResRef", spellId)); + + // the FeatID holds the accurate spell icon, not the SpellID + int nFeatID = StringToInt(Get2DACache("spells", "FeatID", spellId)); + + return JsonString(Get2DACache("feat", "Icon", nFeatID)); +} + +string GetSpellName(int spellId, int realSpellID=0, int featId=0, int nClass=0) +{ + if ((nClass == CLASS_TYPE_SHADOWSMITH + || nClass == CLASS_TYPE_SHADOWCASTER) && spellId) + return GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", spellId))); + if (nClass == CLASS_TYPE_TRUENAMER && featId) + return GetStringByStrRef(StringToInt(Get2DACache("feat", "FEAT", featId))); + if (realSpellID) + return GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", realSpellID))); + if (spellId) + return GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", spellId))); + if (featId) + return GetStringByStrRef(StringToInt(Get2DACache("feat", "FEAT", featId))); + + return GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", spellId))); +} + +json GetBinderSpellToFeatDictionary(object oPlayer=OBJECT_SELF) +{ + // a dictionary of + json binderDict = GetLocalJson(oPlayer, NUI_SPELLBOOK_BINDER_DICTIONARY_CACHE_VAR); + // if this hasn't been created, create it now. + if (binderDict == JsonNull()) + binderDict = JsonObject(); + else + return binderDict; + + // the starting row for binder spells + int spellIndex = 19070; + // the starting row for binder feats + int featIndex = 9030; + //the end of the binder spells/feats + while (spellIndex <= 19156 && featIndex <= 9104) + { + // get the SpellID tied to the feat + int spellID = StringToInt(Get2DACache("feat", "SPELLID", featIndex)); + // if the spellID matches the current index, then this is the spell + // attached to the feat + if (spellID == spellIndex) + { + binderDict = JsonObjectSet(binderDict, IntToString(spellID), JsonInt(featIndex)); + + // move to next spell/feat + featIndex++; + spellIndex++; + } + // else we have reached a subdial spell + else + { + // loop through until we reach back at spellID + while (spellIndex < spellID) + { + int masterSpell = StringToInt(Get2DACache("spells", "Master", spellIndex)); + + // add the sub radial to the dict, tied to the master's FeatID + int featId = JsonGetInt(JsonObjectGet(binderDict, IntToString(masterSpell))); + binderDict = JsonObjectSet(binderDict, IntToString(spellIndex), JsonInt(featId)); + + spellIndex++; + } + + + // some feats overlap the same FeatID, can cause this to get stuck. + // if it happens then move on + if (spellIndex > spellID) + featIndex++; + } + } + + // cache the result + SetLocalJson(oPlayer, NUI_SPELLBOOK_BINDER_DICTIONARY_CACHE_VAR, binderDict); + return binderDict; +} + +json GreyOutButton(json jButton, float w, float h) +{ + json retValue = jButton; + + json jBorders = JsonArray(); + jBorders = JsonArrayInsert(jBorders, CreateGreyOutRectangle(w, h)); + + return NuiDrawList(jButton, JsonBool(FALSE), jBorders); +} + +json CreateGreyOutRectangle(float w, float h) +{ + // set the points of the button shape + json jPoints = JsonArray(); + jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); + jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); + + jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); + jPoints = JsonArrayInsert(jPoints, JsonFloat(h)); + + jPoints = JsonArrayInsert(jPoints, JsonFloat(w)); + jPoints = JsonArrayInsert(jPoints, JsonFloat(h)); + + jPoints = JsonArrayInsert(jPoints, JsonFloat(w)); + jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); + + jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); + jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); + + return NuiDrawListPolyLine(JsonBool(TRUE), NuiColor(0, 0, 0, 127), JsonBool(TRUE), JsonFloat(2.0), jPoints); +} + +void CreateSpellDescriptionNUI(object oPlayer, int featID, int spellId=0, int realSpellId=0, int nClass=0) +{ + SetLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_FEATID_VAR, featID); + SetLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_SPELLID_VAR, spellId); + SetLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_REAL_SPELLID_VAR, realSpellId); + SetLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_CLASSID_VAR, nClass); + ExecuteScript("prc_nui_dsc_view", oPlayer); +} + +void ClearSpellDescriptionNUI(object oPlayer=OBJECT_SELF) +{ + DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_FEATID_VAR); + DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_SPELLID_VAR); + DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_REAL_SPELLID_VAR); + DeleteLocalInt(oPlayer, NUI_SPELL_DESCRIPTION_CLASSID_VAR); +} + diff --git a/nwn/nwnprc/trunk/include/prc_nui_consts.nss b/nwn/nwnprc/trunk/include/prc_nui_consts.nss index 0cb0efa2..e1e9c5ca 100644 --- a/nwn/nwnprc/trunk/include/prc_nui_consts.nss +++ b/nwn/nwnprc/trunk/include/prc_nui_consts.nss @@ -110,4 +110,49 @@ const string NUI_PRC_PA_TEXT_BIND = "nui_prc_pa_text_bind"; // Left Button Enabled Bind for Power Attack NUI const string NUI_PRC_PA_LEFT_BUTTON_ENABLED_BIND = "leftButtonEnabled"; // Right Button Enabled Bind for Power Attack NUI -const string NUI_PRC_PA_RIGHT_BUTTON_ENABLED_BIND = "rightButtonEnabled"; \ No newline at end of file +const string NUI_PRC_PA_RIGHT_BUTTON_ENABLED_BIND = "rightButtonEnabled"; + +////////////////////////////////////////////////// +// // +// NUI Level Up // +// // +////////////////////////////////////////////////// + +const string NUI_LEVEL_UP_WINDOW_ID = "prcLevelUpNui"; + +const string NUI_LEVEL_UP_SPELL_CIRCLE_BUTTON_BASEID = "NuiLevelUpCircleButton_"; +const string NUI_LEVEL_UP_SPELL_BUTTON_BASEID = "NuiLevelUpSpellButton_"; +const string NUI_LEVEL_UP_SPELL_DISABLED_BUTTON_BASEID = "NuiLevelUpDisabledSpellButton_"; +const string NUI_LEVEL_UP_SPELL_CHOSEN_BUTTON_BASEID = "NuiLevelUpChosenSpellButton_"; +const string NUI_LEVEL_UP_SPELL_CHOSEN_DISABLED_BUTTON_BASEID = "NuiLevelUpDisabledChosenSpellButton_"; +const string NUI_LEVEL_UP_DONE_BUTTON = "NuiLevelUpDoneButton"; +const string NUI_LEVEL_UP_RESET_BUTTON = "NuiLevelUpResetButton"; + +const string NUI_LEVEL_UP_SELECTED_CLASS_VAR = "NUILevelUpSelectedClass"; +const string NUI_LEVEL_UP_SELECTED_CIRCLE_VAR = "NUILevelUpSelectedCircle"; +const string NUI_LEVEL_UP_KNOWN_SPELLS_VAR = "NUILevelUpKnownSpells"; +const string NUI_LEVEL_UP_CHOSEN_SPELLS_VAR = "NUILevelUpChosenSpells"; +const string NUI_LEVEL_UP_EXPANDED_KNOW_LIST_VAR = "NUILevelUpExpKnowList"; +const string NUI_LEVEL_UP_POWER_LIST_VAR = "NUILevelUpPowerList"; +const string NUI_LEVEL_UP_DISCIPLINE_INFO_VAR = "GetDisciplineInfoObjectCache_"; +const string NUI_LEVEL_UP_SPELLID_LIST_VAR = "NUILevelUpSpellIDList_"; +const string NUI_LEVEL_UP_REMAINING_CHOICES_CACHE_VAR = "NUIRemainingChoicesCache"; +const string NUI_LEVEL_UP_RELEARN_LIST_VAR = "NUILevelUpRelearnList"; +const string NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR = "NUILevelUpArchivistNewSpellsList"; + +const string NUI_LEVEL_UP_EXPANDED_CHOICES_VAR = "NUIExpandedChoices"; +const string NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR = "NUIEpicExpandedChoices"; + +const int NUI_LEVEL_UP_MANEUVER_PREREQ_LIMIT = 6; + +const string NUI_LEVEL_UP_MANEUVER_TOTAL = "ManeuverTotal"; +const string NUI_LEVEL_UP_STANCE_TOTAL = "StanceTotal"; + +const string NUI_LEVEL_UP_SPELLBOOK_OBJECT_CACHE_VAR = "GetSpellListObjectCache_"; +const string NUI_LEVEL_UP_KNOWN_INVOCATIONS_CACHE_VAR = "GetInvokerKnownListObjectCache_"; + +const string NUI_SPELL_DESCRIPTION_FEATID_VAR = "NUISpellDescriptionFeatID"; +const string NUI_SPELL_DESCRIPTION_CLASSID_VAR = "NUISpellDescriptionClassID"; +const string NUI_SPELL_DESCRIPTION_SPELLID_VAR = "NUISpellDescriptionSpellID"; +const string NUI_SPELL_DESCRIPTION_REAL_SPELLID_VAR = "NUISpellDescriptionRealSpellID"; + diff --git a/nwn/nwnprc/trunk/include/prc_nui_lv_inc.nss b/nwn/nwnprc/trunk/include/prc_nui_lv_inc.nss new file mode 100644 index 00000000..184467ba --- /dev/null +++ b/nwn/nwnprc/trunk/include/prc_nui_lv_inc.nss @@ -0,0 +1,3336 @@ +//:://///////////////////////////////////////////// +//:: PRC Level Up NUI +//:: prc_nui_lv_inc +//::////////////////////////////////////////////// +/* + This is the logic for the Level Up NUI, holding all the functions needed for + the NUI to operate properly and allow leveling up in different classes. +*/ +//::////////////////////////////////////////////// +//:: Created By: Rakiov +//:: Created On: 20.06.2005 +//::////////////////////////////////////////////// + +#include "prc_nui_com_inc" +#include "tob_inc_tobfunc" +#include "tob_inc_moveknwn" +#include "inv_inc_invfunc" +#include "shd_inc_mystknwn" +#include "shd_inc_shdfunc" +#include "true_inc_truknwn" +#include "true_inc_trufunc" + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Spont Casters / Base /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +// +// GetSpellListObject +// Gets the JSON Object representation of a class's spellbook 2da. This function +// will cache it's result to the object given to it to avoid further calculations +// and will not clear itself since it does not change. +// +// Arguments: +// nClass:int the ClassID +// oPC:Object the player +// +// Returns: +// Json:Dictionary a dictionary of each circle's spellbook Ids. +// +json GetSpellListObject(int nClass, object oPC=OBJECT_SELF); + +// +// GetKnownSpellListObject +// Gets the JSON Object representation of a player's known spell list. This function +// will temporarily cache it's result to the object given to avoid further calculations. +// However this should be cleared after done using the level up screen or reset. +// +// Arguments: +// nClass:int the ClassID +// oPC:Object the player +// +// Returns: +// Json:Dictionary a dictionary of each circle's known spellbook Ids. +// +json GetKnownSpellListObject(int nClass, object oPC=OBJECT_SELF); + +// +// GetKnownSpellListObject +// Gets the JSON Object representation of a player's chosen spell list. This function +// will temporarily cache it's result to the object given to avoid further calculations. +// However this should be cleared after done using the level up screen or reset. +// +// Arguments: +// nClass:int the ClassID +// oPC:Object the player +// +// Returns: +// Json:Dictionary a dictionary of each circle's chosen spellbook Ids. +// +json GetChosenSpellListObject(int nClass, object oPC=OBJECT_SELF); + +// +// ShouldAddSpellToSpellButtons +// Given a classId and a spellbookId, if the player knows the spell already we +// should not add the spell, otherwise we should +// +// Arguments: +// nClass:int Class ID +// spellbookId:int the spell book ID +// oPC:object the player +// +// Returns: +// int:Boolean TRUE if spell should be added, FALSE otherwise +// +int ShouldAddSpellToSpellButtons(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// OpenNUILevelUpWindow +// Opens the Level Up NUI window for the provided class +// +// Arguments: +// nClass:int the ClassID +// +void OpenNUILevelUpWindow(int nClass, object oPC=OBJECT_SELF); + +// +// CloseNUILevelUpWindow +// Closes the NUI Level Up Window if its open +// +void CloseNUILevelUpWindow(object oPC=OBJECT_SELF); + +// +// GetTrueClassType +// Gets the true class Id for a provided class Id, mostly for RHD and for +// ToB prestige classes +// +// Arguments: +// nClass:int classId +// +// Returns: +// int the true classId based off nClass +// +int GetTrueClassType(int nClass, object oPC=OBJECT_SELF); + +// +// GetRemainingSpellChoices +// Gets the remaining spell choices for a class at the given circle by checking its +// chosen spells and comparing it against the total spells allowed. This value +// is cached on the player and cleared everytime the window is refreshed/closed +// +// Arguments: +// nClass:int the class id +// circleLevel:int the circle being checked +// +// Returns: +// int the amount of choices left at the circle +// +int GetRemainingSpellChoices(int nClass, int circleLevel, object oPC=OBJECT_SELF); + +// +// ShouldSpellButtonBeEnabled +// Checks whether a spell button should be enabled either because all choices have +// been made, replacing spells isn't allowed, or for various other reasons +// +// Arguments: +// nClass:int class id +// circleLevel:int the chosen circle +// spellbookId:int the chosen spell +// +// Returns: +// int:Boolean TRUE if spell button should be enabled, FALSE otherwise +// +int ShouldSpellButtonBeEnabled(int nClass, int circleLevel, int spellbookId, object oPC=OBJECT_SELF); + +// +// AddSpellToChosenList +// Adds spell to the chosen spells list +// +// Arguments: +// nClass:int the classId +// spellbookId:int the spellbook Id +// spellCircle:int the current circle of the spell +// +void AddSpellToChosenList(int nClass, int spellbookId, int spellCircle, object oPC=OBJECT_SELF); + +// +// RemoveSpellFromChosenList +// Removes a spell from the chosen spell list +// +// Arguments: +// nClass:int the class id +// spellbookId:int the spellbook Id +// spellCircle:int the circle of the spell +// +void RemoveSpellFromChosenList(int nClass, int spellbookId, int spellCircle, object oPC=OBJECT_SELF); + +// +// LearnSpells +// gives the player the spells they want to learn based off of the chosen spell +// list in a stored variable +// +// Arguments: +// nClass:int the classId +// +void LearnSpells(int nClass, object oPC=OBJECT_SELF); + +// +// RemoveSpells +// removes spells from the player that they may know currently but aren't selected +// based off lists in stored variables +// +// Arguments: +// nClass:int the classId +// +void RemoveSpells(int nClass, object oPC=OBJECT_SELF); + +// +// FinishLevelUp +// Finishes level up NUI by removing spells, learning spells, clearing cache, then closing the NUI +// +// Arguments: +// nClass:int the class id +// +void FinishLevelUp(int nClass, object oPC=OBJECT_SELF); + +// +// ClearLevelUpNUICaches +// Clears the cache (stored local variables) for the level up NUI so it is +// ready to be used for a new level up +// +// Arguments: +// nClass:int class id +// oPC:object the player object this is stored under +// +void ClearLevelUpNUICaches(int nClass, object oPC=OBJECT_SELF); + +// +// SpellIsWithinObject +// checks whether a spell is within a JSON Object structure used by the remaining +// spells object and known spells object, following this structure +// { +// "circleLevel:int": [ 1,2,3...,spellId], +// ... +// } +// +// Arguments +// nClass:int classId +// spellbookId:int the spellbook Id +// circleLevel:int the chosen circle of the spell +// spellList;JsonObject the spell list object being checked +// +// Returns: +// int:Boolean TRUE if it is in the object, FALSE otherwise +// +int SpellIsWithinObject(int nClass, int spellbookId, int circleLevel, json spellList, object oPC=OBJECT_SELF); + +// +// IsLevelUpNUIOpen +// Checks if the Level Up NUI is open for the player or not +// +// Arguments: +// oPC:object the player object +// +// Returns: +// int:Boolean TRUE if it is, FALSE otherwise +// +int IsLevelUpNUIOpen(object oPC=OBJECT_SELF); + +// +// IsClassAllowedToUseLevelUpNUI +// Is the provided class allowed to use the level up NUI +// +// Arguments: +// nClass:int class id +// +// Returns: +// int:Boolean TRUE if it can, FALSE otherwise +// +int IsClassAllowedToUseLevelUpNUI(int nClass); + +// +// EnabledChosenButton +// determines if a chosen spell button should be enabled or not. It may not due to +// class restrictions, replacing is not enabled, or other reason +// +// Arguments: +// nClass:int the class id +// spellbookId: the spellbook Id +// circleLevel: the spell's circle +// +// Returns: +// int:Boolean TRUE if it should be enabled, FALSE otherwise +// +int EnableChosenButton(int nClass, int spellbookId, int circleLevel, object oPC=OBJECT_SELF); + +// +// ResetChoices +// Action for the Level Up NUI's 'Reset' button, resets choices by clearing the cache of +// the user so their choices are forgotten and they can start over. +// +// Arguments: +// oPC:object the player object +// +void ResetChoices(object oPC=OBJECT_SELF); + +// +// RemoveSpellKnown +// Removes a spell from a player based off class id. This is for classes that +// aren't spont casters where we have to go in and adjust persistant arrays +// to say if a spell is known or not. +// +// Arguments: +// nClass:int class id +// spellbookId:int the spellbook Id +// oPC:object the player object +// nList:int the list we are removing the spell from (extra invocations or expanded knowledge) +// +void RemoveSpellKnown(int nClass, int spellbookId, object oPC=OBJECT_SELF, int nList=0); + +// +// GetSpellIDsKnown +// Gets the SpellIDs list of the given class and list and returns it as a JsonObject following this structure +// { +// "spellId:int": TRUE, +// ... +// } +// +// This is to keep lookups at O(1) processing time. This value is cached and is +// cleared when the player finishes level up +// +// Arguments: +// nClass:int class id +// oPC:object the player object +// nList:int the list we are checking if provided (extra invocations or expanded knowledge) +// +// Returns: +// JsonObject the list of spell ids the class knows in JsonObject format +// +json GetSpellIDsKnown(int nClass, object oPC=OBJECT_SELF, int nList=0); + +// +// ReasonForDisabledSpell +// Provides the reason for why a spell choice is disabled +// +// Arguments: +// nClass:int the class id +// spellbookId:int the spellbook Id +// +// Returns: +// string the reason for the disabled button, empty string otherwise +// +string ReasonForDisabledSpell(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// ReasonForDisabledChosen +// Provides the reason for why a chosen spell button is disabled +// +// Arguments: +// nClass:int the class id +// spellbookId:int the spellbook Id +// +// Returns: +// string the reason for the disabled button, empty string otherwise +// +string ReasonForDisabledChosen(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// GetExpandedChoicesList +// Gets the expanded choices list for a class (the list of expanded knowledge or +// extra invocations). It follows this structure +// +// { +// "spellId:int": TRUE, +// ... +// } +// This is cached to reduce process times and is cleared everytime the window is refreshed/closed +// +// Arguments: +// nClass:int the class id +// +// Returns: +// JsonObject the object representation of the expanded choices +// +json GetExpandedChoicesList(int nClass, object oPC=OBJECT_SELF); + +// +// GetExpandedChoicesList +// Gets the epic expanded choices list for a class (the list of expanded knowledge or +// extra invocations). It follows this structure +// +// { +// "spellId:int": TRUE, +// ... +// } +// This is cached to reduce process times and is cleared everytime the window is refreshed/closed +// +// Arguments: +// nClass:int the class id +// +// Returns: +// JsonObject the object representation of the expanded choices +// +json GetEpicExpandedChoicesList(int nClass, object oPC=OBJECT_SELF); + +// +// GetRemainingExpandedChoices +// Gets the remaining expanded choices for a class based off list, comparing the +// total number of choices allowed and the total number chosen +// +// Arguments: +// nClass: class id +// nList: the list we are checking (extra invocations/expanded knowledge) +// +// Returns: +// int the amount of choices left +// +int GetRemainingExpandedChoices(int nClass, int nList, object oPC=OBJECT_SELF); +// +// IsSpellInExpandedChoices +// tells if a spell is in the expanded choices list or not +// +// Arguments: +// nClass:int class id +// nList:int the list we are checking (extra invocations/expanded knowledge) +// spellId:int the spell id (not the spellbook id) +// +// Returns +// int:Boolean TRUE if it is a expanded choice, FALSE otherwise +// +int IsSpellInExpandedChoices(int nClass, int nList, int spellId, object oPC=OBJECT_SELF); + +// +// GetChosenReplaceListObject +// The chosen list of spells we wish to replace for PnP replacing if Bioware replacing +// is disabled. This is cached and is cleared when the player is finished leveling +// or resets their choices +// +// Arguments: +// oPC:object the player +// +// Returns: +// json the list of spells chosen to replace +// +json GetChosenReplaceListObject(object oPC=OBJECT_SELF); + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Psionics /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +// +// IsExpKnowledgePower +// checks if a spell is a expanded knowledge spell +// +// Arguments: +// nClass:int class id +// spellbookId:int the spellbook Id +// +// Returns: +// int:Boolean TRUE if the spell is a expanded knowledge spell, FALSE otherwise +// +int IsExpKnowledgePower(int nClass, int spellbookId); + +// +// GetExpKnowledgePowerListRequired +// Tells what list the spell should be added to based on if it was added to the +// expanded choices or epic expanded choices list +// +// Arguments: +// nClass:int the class id +// spellbookId:int the spellbook Id +// +// Returns: +// int -1 for the expanded knowledge list, -2 for the epic expanded knowledge +// list, 0 if just add it to the normal class list +// +int GetExpKnowledgePowerListRequired(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// GetCurrentPowerList +// Gets the current chosen powers list. This is cached and is cleared when the +// player either finishs leveling up or resets. +// +// Arguments: +// oPC:object the player object +// +// Returns: +// JsonArray the list of chosen powers wanting to learn +// +json GetCurrentPowerList(object oPC=OBJECT_SELF); + +// +// ShouldAddPower +// Tells if the power should be added to the list of choices or not. It may not +// be added because its an expanded knowledge choice and you have no more expanded +// knowledge slots, or it may be a restricted spell like psions list +// +// Arguments: +// nClass:int the class id +// spellbookId:int the spellbook id +// +// Returns: +// int:Boolean TRUE if it should be added, FALSE otherwise +// +int ShouldAddPower(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// LearnPowers +// learns the list of chosen powers for the player based off their chosen power list +// +// Arguments: +// nClass:int class id +// oPC:object the player object where stored variables are +// +void LearnPowers(int nClass, object oPC=OBJECT_SELF); + +// +// GetMaxPowerLevelForClass +// gets the max power level for the player based off their level and the class's +// known 2da +// +// Arguments: +// nClass:int the class id +// oPC:object the player +// +// Returns: +// int the max power level (circle) the player can achieve on that class +// +int GetMaxPowerLevelForClass(int nClass, object oPC=OBJECT_SELF); + +// +// GetRemainingPowerChoices +// Gets the remaining power choices the character has at the given chosen circle/power level +// +// Arguments: +// nClass:int class id +// chosenCircle:int the chosen circle/power level +// oPC:object the player +// extra:int should we add the expanded knowledge choices or not +// +// Returns: +// int the number of choices left at the given circle +// +int GetRemainingPowerChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE); + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Initiators /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +// +// GetDisciplineInfoObject +// Gets the disciplien info for the given class, telling what the chosen spells +// disicpline is, what type of maneuever it is, the different totals, and prerequisites. +// This is cached and is cleared when the window is refreshed/closed +// +// Argument: +// nClass:int class id +// +// Returns: +// JsonObject the object representation of the chosen spells discipline info +// +json GetDisciplineInfoObject(int nClass, object oPC=OBJECT_SELF); + +// +// HasPreRequisitesForManeuver +// Does the player have the prerequisites for the given spell based off their chosen +// spell list +// +// Arguments: +// nClass:int the class id +// spellbookId:int the spellbook id +// oPC:object the player object with stored variables +// +// Returns: +// int:Boolean, TRUE if you have the prerequisites, FALSE otherwise +// +int HasPreRequisitesForManeuver(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// GetMaxInitiatorCircle +// gets the max circle/level a player can obtain with the given class +// +// Arguments: +// nClass:int the class id +// oPC:object the player +// +// Returns: +// int the highest circle the player can achieve with the class +// +int GetMaxInitiatorCircle(int nClass, object oPC=OBJECT_SELF); + +// +// GetRemainingManeuverChoices +// Gets remaining maneuever choices for the player +// +// Arguments: +// nClass:int class id +// oPC:object the player +// +// Returns: +// int the remaining maneuevers choices +// +int GetRemainingManeuverChoices(int nClass, object oPC=OBJECT_SELF); + +// +// GetRemainingStanceChoices +// Gets remaining stance choices for the player +// +// Arguments: +// nClass:int class id +// oPC:object the player +// +// Returns: +// int the remaining stance choices +// +int GetRemainingStanceChoices(int nClass, object oPC=OBJECT_SELF); + +// +// IsRequiredForOtherManeuvers +// Checks the given prerequisite number and the chosen spells to see if removing it +// will cause it to fail the requirement for other maneuevers +// +// Arguments: +// nClass:int the class id +// prereq:int the chosen spells prerequisite number of maneuevers needed +// discipline:string the chosen spells discipline +// +// Returns: +// int:Boolean TRUE if it is required, FALSE otherwise +// +int IsRequiredForOtherManeuvers(int nClass, int prereq, string discipline, object oPC=OBJECT_SELF); + +// +// IsAllowedDiscipline +// checks to see if the given spell is a allowed discipline for a class +// +// Arguments: +// nClass:int class id +// spellbookId:int the spellbook id +// +// Returns: +// int:boolean TRUE if it is allowed, FALSE otherwise +// +int IsAllowedDiscipline(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +// +// AddSpellDisciplineInfo +// Adds the maneuver's discipline info to the class's discpline object +// +// Arguments: +// sFile:string the class's spell 2da +// spellbookId:int the spellbook Id +// classDisc:JsonObject the class discipline object we are adding to +// +// Returns: +// json:Object the classDisc with the given spells information added +// +json AddSpellDisciplineInfo(string sFile, int spellbookId, json classDisc); + +// +// IsRequiredForToBPRCClass +// tells if a given maneuver is needed to satisfy a PRC's prerequsitie +// +// Arguments: +// nClass:int class id +// spellbookId:int the spellbook id +// +// Returns: +// int:Boolean TRUE if the maneuver is required for a PRC, FALSE otherwise. +// +int IsRequiredForToBPRCClass(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Invokers /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +// +// GetInvokerKnownListObject +// gets the invokers known invocations list in object format, needed to tell how many +// of each invocation level does a person know at a given level. This is cached on the +// player and not cleared since it never changes. +// +// Arguments: +// nClass:int class id +// +// Returns: +// json:Object the list of invocations known in json format +// +json GetInvokerKnownListObject(int nClass, object oPC=OBJECT_SELF); + +// +// GetRemainingInvocationChoices +// Gets the remaining invocation choices left +// +// Arguments: +// nClass:int class id +// chosenCircle:int the chosen circle we are checking +// oPC:Object the player +// extra:int should we count the number of extra invocations we have left +// +// Returns: +// int the amount of choices left at the given circle +// +int GetRemainingInvocationChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE); + +// +// IsExtraChoiceInvocation +// tells if a given spell is a extra invocation choice +// +// Arguments: +// nClass:int class id +// spellbookId:int the spellbook id +// +// Returns: +// int;Boolean TRUE if it is a extra choice, FALSE otherwise +// +int IsExtraChoiceInvocation(int nClass, int spellbookId, object oPC=OBJECT_SELF); + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Truenamer /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +// +// GetRemainingTruenameChoices +// gets the remaining truename choices left at the given lexicon type +// +// Arguments: +// nClass:int class id +// nType:int the lexicon +// +// Returns: +// int the amount of truename choices left for the given lexicon +// +int GetRemainingTruenameChoices(int nClass, int nType, object oPC=OBJECT_SELF); + +// +// GetLexiconCircleKnownAtLevel +// gets the known circle level for a given lexicon +// +// Arguments: +// nLevel:int the level to check +// nType:int the lexicon we are checking +// +// Returns: +// int the highest circle we can achieve +// +int GetLexiconCircleKnownAtLevel(int nLevel, int nType); + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Archivist /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +json GetArchivistNewSpellsList(object oPC=OBJECT_SELF); + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Spont Casters / Base /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +int IsLevelUpNUIOpen(object oPC=OBJECT_SELF) +{ + int nPreviousToken = NuiFindWindow(oPC, NUI_LEVEL_UP_WINDOW_ID); + if (nPreviousToken != 0) + { + return TRUE; + } + + return FALSE; +} + +int IsClassAllowedToUseLevelUpNUI(int nClass) +{ + + if (GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS) + return TRUE; + + if (nClass == CLASS_TYPE_PSYWAR + || nClass == CLASS_TYPE_PSYCHIC_ROGUE + || nClass == CLASS_TYPE_PSION + || nClass == CLASS_TYPE_FIST_OF_ZUOKEN + || nClass == CLASS_TYPE_WILDER + || nClass == CLASS_TYPE_WARMIND) + return TRUE; + + if (nClass == CLASS_TYPE_WARBLADE + || nClass == CLASS_TYPE_SWORDSAGE + || nClass == CLASS_TYPE_CRUSADER) + return TRUE; + + if (nClass == CLASS_TYPE_WARLOCK + || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT + || nClass == CLASS_TYPE_DRAGON_SHAMAN) + return TRUE; + + if (nClass == CLASS_TYPE_SHADOWCASTER + || nClass == CLASS_TYPE_SHADOWSMITH) + return TRUE; + + if (nClass == CLASS_TYPE_TRUENAMER) + return TRUE; + + if (nClass == CLASS_TYPE_ARCHIVIST) + return TRUE; + + return FALSE; +} + +int SpellIsWithinObject(int nClass, int spellbookId, int circleLevel, json spellList, object oPC=OBJECT_SELF) +{ + // check to see if the spell circle isn't empty + json currentList = JsonObjectGet(spellList, IntToString(circleLevel)); + if (currentList == JsonNull()) + return FALSE; + + int totalSpells = JsonGetLength(currentList); + + // then loop through the spell list and find the spell. + int i; + for (i = 0; i < totalSpells; i++) + { + int currentSpell = JsonGetInt(JsonArrayGet(currentList, i)); + if (currentSpell == spellbookId) + return TRUE; + } + + return FALSE; +} + +int AllSpellsAreChosen(int nClass, object oPC=OBJECT_SELF) +{ + // we need the max number of circles a class has. + json spellList = GetSpellListObject(nClass, oPC); + json spellCircles = JsonObjectKeys(spellList); + int totalCircles = JsonGetLength(spellCircles); + + int i; + for (i = 0; i < totalCircles; i++) + { + // loop through each circle and check if you have any remaining choices left + // if you do or you have a deficit then you need to remove or add something + // until you get 0 + int spellCircle = StringToInt(JsonGetString(JsonArrayGet(spellCircles, i))); + int remainingChoices = GetRemainingSpellChoices(nClass, spellCircle, oPC); + if (remainingChoices < 0 || remainingChoices > 0) + return FALSE; + } + + return TRUE; +} + +void AddSpellToChosenList(int nClass, int spellbookId, int spellCircle, object oPC=OBJECT_SELF) +{ + if (GetIsInvocationClass(nClass)) + { + // get the remaining invocation choices left without extra feats + // if it is 0 then we are adding the chosen invocation to the extra lists + int totalInvocations = GetRemainingInvocationChoices(nClass, spellCircle, oPC, FALSE); + if (totalInvocations == 0) + { + string sFile = GetClassSpellbookFile(nClass); + if (GetRemainingExpandedChoices(nClass, INVOCATION_LIST_EXTRA, oPC)) + { + json expList = GetExpandedChoicesList(nClass, oPC); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + expList = JsonObjectSet(expList, spellId, JsonBool(TRUE)); + SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expList); + } + else if (GetRemainingExpandedChoices(nClass, INVOCATION_LIST_EXTRA_EPIC, oPC)) + { + json expList = GetEpicExpandedChoicesList(nClass, oPC); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + expList = JsonObjectSet(expList, spellId, JsonBool(TRUE)); + SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expList); + } + } + } + if (GetIsPsionicClass(nClass)) + { + // if the power is a expanded knowledge than we immediatly add it to the + // extra list, otherwise check to make sure we have made all choices in our + // base list first before adding it to the extra list. + if (IsExpKnowledgePower(nClass, spellbookId) + || GetRemainingPowerChoices(nClass, spellCircle, oPC, FALSE) == 0) + { + string sFile = GetClassSpellbookFile(nClass); + if (GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC)) + { + json expList = GetExpandedChoicesList(nClass, oPC); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + expList = JsonObjectSet(expList, spellId, JsonBool(TRUE)); + SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expList); + } + else if (GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC)) + { + json expList = GetEpicExpandedChoicesList(nClass, oPC); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + expList = JsonObjectSet(expList, spellId, JsonBool(TRUE)); + SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expList); + } + } + + // add the power to the current power list. + json currPowerList = GetCurrentPowerList(oPC); + currPowerList = JsonArrayInsert(currPowerList, JsonInt(spellbookId)); + SetLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR, currPowerList); + } + + if (nClass == CLASS_TYPE_ARCHIVIST) + { + json newSpells = GetArchivistNewSpellsList(oPC); + newSpells = JsonArrayInsert(newSpells, JsonInt(spellbookId)); + SetLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR, newSpells); + } + + // base logic for spont casters, add the spell to the ChosenSpells JSON object + // by adding it to it's appropriate circle. + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + json spellsAtCircle = JsonObjectGet(chosenSpells, IntToString(spellCircle)); + if (spellsAtCircle == JsonNull()) + spellsAtCircle = JsonArray(); + spellsAtCircle = JsonArrayInsert(spellsAtCircle, JsonInt(spellbookId)); + chosenSpells = JsonObjectSet(chosenSpells, IntToString(spellCircle), spellsAtCircle); + SetLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR, chosenSpells); + + // if we are not using bioware unlearning logic, then we need to limit the + // amount of spells we can replace. + if (!GetPRCSwitch(PRC_BIO_UNLEARN)) + { + json unlearnList = GetChosenReplaceListObject(oPC); + // if the spell belongs to the unlearn list, then remove it to make room + // for a new spell. + if (JsonObjectGet(unlearnList, IntToString(spellbookId)) != JsonNull()) + { + unlearnList = JsonObjectDel(unlearnList, IntToString(spellbookId)); + SetLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR, unlearnList); + } + } +} + +void RemoveSpellFromChosenList(int nClass, int spellbookId, int spellCircle, object oPC=OBJECT_SELF) +{ + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + json spellsAtCircle = JsonObjectGet(chosenSpells, IntToString(spellCircle)); + if (spellsAtCircle == JsonNull()) + spellsAtCircle = JsonArray(); + + int totalSpells = JsonGetLength(spellsAtCircle); + + // find the spell at the circle in the chosen list and remove it. + int i; + for (i = 0; i < totalSpells; i++) + { + if (spellbookId == JsonGetInt(JsonArrayGet(spellsAtCircle, i))) + { + spellsAtCircle = JsonArrayDel(spellsAtCircle, i); + break; + } + } + + chosenSpells = JsonObjectSet(chosenSpells, IntToString(spellCircle), spellsAtCircle); + SetLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR, chosenSpells); + + // if we re not using bioware unlearn logic we need to limit how many spells + // can be replaced + if (!GetPRCSwitch(PRC_BIO_UNLEARN)) + { + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json spellListAtCircle = JsonObjectGet(knownSpells, IntToString(spellCircle)); + int totalSpells = JsonGetLength(spellListAtCircle); + + // with the list of known spells, check the selected circle and see if the + // current spell belongs in the already known spell list. + for (i = 0; i < totalSpells; i++) + { + int chosenSpell = JsonGetInt(JsonArrayGet(spellListAtCircle, i)); + if (chosenSpell == spellbookId) + { + // if it does we need to add the spell to the unlearn JSON object to track what spells + // are being replaced. + json unlearnList = GetChosenReplaceListObject(oPC); + unlearnList = JsonObjectSet(unlearnList, IntToString(spellbookId), JsonBool(TRUE)); + SetLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR, unlearnList); + break; + } + } + } + + if (GetIsPsionicClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + + // for psionics we need to check if the removed spell was a expanded knowledge choice + // or not. The id of the list is -1 or -2. + int i; + for (i == -1; i >= -2; i--) + { + json expList = (i == -1) ? GetExpandedChoicesList(nClass, oPC) : + GetEpicExpandedChoicesList(nClass, oPC); + + //if the spell belongs in the expanded knowledge list, then we need + // to remove it. + if (JsonObjectGet(expList, spellId) != JsonNull()) + { + expList = JsonObjectDel(expList, spellId); + if (i == POWER_LIST_EXP_KNOWLEDGE) + SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expList); + else + SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expList); + } + } + + // then we need to remove the power from the selected powers list. + json currPowerChoices = GetCurrentPowerList(oPC); + int totalPowers = JsonGetLength(currPowerChoices); + + for (i = 0; i < totalPowers; i++) + { + if (spellbookId == JsonGetInt(JsonArrayGet(currPowerChoices, i))) + { + currPowerChoices = JsonArrayDel(currPowerChoices, i); + break; + } + } + + SetLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR, currPowerChoices); + } + if (GetIsInvocationClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + + // for invocations we need to check if the spell was added to the extra + // invocations list, the list ids are the invalid class id, and -2 + int i; + for (i = 0; i <= 1; i++) + { + json expList = (i == 0) ? GetExpandedChoicesList(nClass, oPC) : + GetEpicExpandedChoicesList(nClass, oPC); + + // if the spell was found, remove it. + if (JsonObjectGet(expList, spellId) != JsonNull()) + { + expList = JsonObjectDel(expList, spellId); + if (i == 0) + SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expList); + else + SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expList); + } + } + } + if (nClass == CLASS_TYPE_ARCHIVIST) + { + json newSpells = GetArchivistNewSpellsList(oPC); + int totalNew = JsonGetLength(newSpells); + + int i; + for (i = 0; i < totalNew; i++) + { + int newSpellbookId = JsonGetInt(JsonArrayGet(newSpells, i)); + if (newSpellbookId == spellbookId) + { + newSpells = JsonArrayDel(newSpells, i); + SetLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR, newSpells); + break; + } + } + } +} + +void OpenNUILevelUpWindow(int nClass, object oPC=OBJECT_SELF) +{ + CloseNUILevelUpWindow(oPC); + // set the NUI to the given classId + int currentClass = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + // we need to clear the cache if it was used before to avoid weird behaviors + ClearLevelUpNUICaches(currentClass, oPC); + // sometimes we are given a different classId instead of the base, we need to + // figure out what the true base class is (mostly true for RHD) + int chosenClass = GetTrueClassType(nClass, oPC); + SetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR, chosenClass); + + ExecuteScript("prc_nui_lv_view", oPC); +} + +int GetTrueClassType(int nClass, object oPC=OBJECT_SELF) +{ + if (nClass == CLASS_TYPE_JADE_PHOENIX_MAGE + || nClass == CLASS_TYPE_MASTER_OF_NINE + || nClass == CLASS_TYPE_DEEPSTONE_SENTINEL + || nClass == CLASS_TYPE_BLOODCLAW_MASTER + || nClass == CLASS_TYPE_RUBY_VINDICATOR + || nClass == CLASS_TYPE_ETERNAL_BLADE + || nClass == CLASS_TYPE_SHADOW_SUN_NINJA) + { + int trueClass = GetPrimaryBladeMagicClass(oPC); + return trueClass; + } + + if ((nClass == CLASS_TYPE_SHAPECHANGER + && GetRacialType(oPC) == RACIAL_TYPE_ARANEA) + || (nClass == CLASS_TYPE_OUTSIDER + && GetRacialType(oPC) == RACIAL_TYPE_RAKSHASA) + || (nClass == CLASS_TYPE_ABERRATION + && GetRacialType(oPC) == RACIAL_TYPE_DRIDER) + || (nClass == CLASS_TYPE_MONSTROUS + && GetRacialType(oPC) == RACIAL_TYPE_ARKAMOI) + || (nClass == CLASS_TYPE_MONSTROUS + && GetRacialType(oPC) == RACIAL_TYPE_HOBGOBLIN_WARSOUL) + || (nClass == CLASS_TYPE_MONSTROUS + && GetRacialType(oPC) == RACIAL_TYPE_REDSPAWN_ARCANISS) + || (nClass == CLASS_TYPE_MONSTROUS + && GetRacialType(oPC) == RACIAL_TYPE_MARRUTACT)) + return CLASS_TYPE_SORCERER; + if (nClass == CLASS_TYPE_FEY + && GetRacialType(oPC) == RACIAL_TYPE_GLOURA) + return CLASS_TYPE_BARD; + + return nClass; +} + +void CloseNUILevelUpWindow(object oPC=OBJECT_SELF) +{ + int currentClass = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + // if we are refreshing the NUI but not finished we need to clear some caching done + // to save computation time as they will need to be reprocessed. + DeleteLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(currentClass)); + SetLocalInt(oPC, NUI_LEVEL_UP_REMAINING_CHOICES_CACHE_VAR, -20); + int nPreviousToken = NuiFindWindow(oPC, NUI_LEVEL_UP_WINDOW_ID); + if (nPreviousToken != 0) + { + NuiDestroy(oPC, nPreviousToken); + } +} + +int ShouldSpellButtonBeEnabled(int nClass, int circleLevel, int spellbookId, object oPC=OBJECT_SELF) +{ + // logic for psionics + if (GetIsPsionicClass(nClass)) + { + int maxLevel = GetMaxPowerLevelForClass(nClass, oPC); + if (circleLevel > maxLevel) + return FALSE; + + // if its an expanded knowledge choice and we have already made all our + // exp knowledge choices then it needs to be disabled. + if (IsExpKnowledgePower(nClass, spellbookId)) + { + int remainingExp = GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC) + + GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC); + if (!remainingExp) + return FALSE; + } + } + + if (GetIsShadowMagicClass(nClass)) + { + // mysteries are weird, the circles are sectioned by 1-3, 4-6, 7-9 + // if you do not have at least 2 choices from a circle you can't progress up + // so you can have access to circles 1,2,4,7,8 + int nType = 1; + if (circleLevel >= 4 && circleLevel <= 6) + nType = 2; + if (circleLevel >= 7 && circleLevel <= 9) + nType = 3; + int maxPossibleCircle = GetMaxMysteryLevelLearnable(oPC, nClass, nType); + if (circleLevel > maxPossibleCircle) + return FALSE; + } + + if (GetIsTruenamingClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + int lexicon = StringToInt(Get2DACache(sFile, "Lexicon", spellbookId)); + // each lexicon learns at different rates + int maxCircle = GetLexiconCircleKnownAtLevel(GetLevelByClass(nClass, oPC), lexicon); + if (circleLevel > maxCircle) + return FALSE; + + if (GetRemainingTruenameChoices(nClass, lexicon, oPC)) + return TRUE; + return FALSE; + } + + // logic for ToB + if (GetIsBladeMagicClass(nClass)) + { + if (circleLevel > GetMaxInitiatorCircle(nClass, oPC)) + return FALSE; + + // if you do not have the prerequisite amount of maneuevers to learn + // the maneuever, then you can't learn it. + if (!HasPreRequisitesForManeuver(nClass, spellbookId, oPC)) + return FALSE; + + // maneuvers and stances have their own seperate limits + string sFile = GetClassSpellbookFile(nClass); + int type = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + if (type == MANEUVER_TYPE_BOOST + || type == MANEUVER_TYPE_COUNTER + || type == MANEUVER_TYPE_STRIKE + || type == MANEUVER_TYPE_MANEUVER) + { + int remainingMan = GetRemainingManeuverChoices(nClass, oPC); + if (remainingMan) + return TRUE; + return FALSE; + } + if (type == MANEUVER_TYPE_STANCE) + { + int remainingStance = GetRemainingStanceChoices(nClass, oPC); + if (remainingStance) + return TRUE; + return FALSE; + } + } + + if (nClass == CLASS_TYPE_ARCHIVIST) + { + int maxLevel = GetMaxSpellLevelForCasterLevel(nClass, GetCasterLevelByClass(nClass, oPC)); + if (circleLevel > maxLevel) + return FALSE; + } + + // default logic + // determine remaining Spell/Power choices left for player, if there is any + // remaining, enable the buttons. + if (GetRemainingSpellChoices(nClass, circleLevel, oPC)) + return TRUE; + + return FALSE; +} + +int ShouldAddSpellToSpellButtons(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + string sFile = GetClassSpellbookFile(nClass); + + string spellLevel = Get2DACache(sFile, "Level", spellbookId); + json chosenSpellsAtCircle = JsonObjectGet(chosenSpells, spellLevel); + + int chosenSpellCount = JsonGetLength(chosenSpellsAtCircle); + + // if the spell is in the chosen list, then don't add it to the available list + int i; + for (i = 0; i < chosenSpellCount; i++) + { + int chosenSpellId = JsonGetInt(JsonArrayGet(chosenSpellsAtCircle, i)); + if (chosenSpellId == spellbookId) + return FALSE; + } + + if (GetIsBladeMagicClass(nClass)) + return IsAllowedDiscipline(nClass, spellbookId, oPC); + + // if a psionic class we need to see if the power is a expanded knowledge + // choice and if we should show it or not + if (GetIsPsionicClass(nClass)) + return ShouldAddPower(nClass, spellbookId, oPC); + + // for these set of classes we need to only allow 'advanced learning' + // spells to be added + if (nClass == CLASS_TYPE_BEGUILER + || nClass == CLASS_TYPE_DREAD_NECROMANCER + || nClass == CLASS_TYPE_WARMAGE) + { + int advancedLearning = StringToInt(Get2DACache(sFile, "AL", spellbookId)); + if (advancedLearning) + return TRUE; + return FALSE; + } + + if (nClass == CLASS_TYPE_ARCHIVIST) + { + int nLevel = GetLevelByClass(nClass, oPC); + if ((StringToInt(spellLevel) == 0) && (nLevel == 1)) + return FALSE; + int advancedLearning = StringToInt(Get2DACache(sFile, "AL", spellbookId)); + if (advancedLearning) + return FALSE; + } + + return TRUE; +} + +json GetChosenSpellListObject(int nClass, object oPC=OBJECT_SELF) +{ + json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR); + // if this isn't set yet then we the chosen currently is the known spells + if (retValue == JsonNull()) + { + retValue = GetKnownSpellListObject(nClass, oPC); + SetLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR, retValue); + } + + return retValue; +} + +json GetKnownSpellListObject(int nClass, object oPC=OBJECT_SELF) +{ + json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_SPELLS_VAR); + if (retValue == JsonNull()) + retValue = JsonObject(); + else + return retValue; + + string sFile = GetClassSpellbookFile(nClass); + int totalSpells = Get2DARowCount(sFile); + + if (nClass == CLASS_TYPE_ARCHIVIST) + { + int i; + for (i = 0; i < 10; i++) + { + string sSpellbook = GetSpellsKnown_Array(nClass, i); + + int nSize = persistant_array_get_size(oPC, sSpellbook); + + int j; + for (j = 0; j < nSize; j++) + { + int knownSpellbookID = persistant_array_get_int(oPC, sSpellbook, j); + // we store things in a JSON Object where the spell circle + // is the key to a JsonArray of spellbookIds. + json spellList = JsonObjectGet(retValue, IntToString(i)); + if (spellList == JsonNull()) + spellList = JsonArray(); + spellList = JsonArrayInsert(spellList, JsonInt(knownSpellbookID)); + retValue = JsonObjectSet(retValue, IntToString(i), spellList); + } + } + } + else + { + // loop through all the spells in the class's 2da + int i; + for (i = 0; i < totalSpells; i++) + { + int featId = StringToInt(Get2DACache(sFile, "FeatID", i)); + // if you have the feat, you know the spell + if (featId && GetHasFeat(featId, oPC, TRUE)) + { + string spellLevel = Get2DACache(sFile, "Level", i); + int nSpellLevel = StringToInt(spellLevel); + // some spells have **** as their level, so make sure we have + // parsed it correctly + if (IntToString(nSpellLevel) == spellLevel) + { + // we store things in a JSON Object where the spell circle + // is the key to a JsonArray of spellbookIds. + json spellList = JsonObjectGet(retValue, spellLevel); + if (spellList == JsonNull()) + spellList = JsonArray(); + spellList = JsonArrayInsert(spellList, JsonInt(i)); + retValue = JsonObjectSet(retValue, spellLevel, spellList); + } + } + } + } + + SetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_SPELLS_VAR, retValue); + return retValue; +} + +json GetSpellListObject(int nClass, object oPC=OBJECT_SELF) +{ + json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_SPELLBOOK_OBJECT_CACHE_VAR + IntToString(nClass)); + if (retValue == JsonNull()) + retValue = JsonObject(); + else + return retValue; + + string sFile = GetClassSpellbookFile(nClass); + int totalSpells = Get2DARowCount(sFile); + + // loop through all the spells in the 2da and convert it to a JSON Object representation + int i; + for (i = 0; i < totalSpells; i++) + { + string spellLevel = Get2DACache(sFile, "Level", i); + int nSpellLevel = StringToInt(spellLevel); + // some spells in the list have **** as spell level. We need to ignore them + if (IntToString(nSpellLevel) == spellLevel) + { + if (nClass == CLASS_TYPE_ARCHIVIST) + { + int reqFeat = StringToInt(Get2DACache(sFile, "ReqFeat", i)); + if (!reqFeat) + { + json spellList = JsonObjectGet(retValue, spellLevel); + if (spellList == JsonNull()) + spellList = JsonArray(); + spellList = JsonArrayInsert(spellList, JsonInt(i)); + retValue = JsonObjectSet(retValue, spellLevel, spellList); + } + } + else + { + json spellList = JsonObjectGet(retValue, spellLevel); + if (spellList == JsonNull()) + spellList = JsonArray(); + spellList = JsonArrayInsert(spellList, JsonInt(i)); + retValue = JsonObjectSet(retValue, spellLevel, spellList); + } + } + } + + SetLocalJson(oPC, NUI_LEVEL_UP_SPELLBOOK_OBJECT_CACHE_VAR + IntToString(nClass), retValue); + return retValue; +} + +int GetRemainingSpellChoices(int nClass, int circleLevel, object oPC=OBJECT_SELF) +{ + int chosenCircle = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR); + int remainingChoices = 0; + + // we only want to cache on the current circle. + if (chosenCircle == circleLevel) + { + remainingChoices = GetLocalInt(oPC, NUI_LEVEL_UP_REMAINING_CHOICES_CACHE_VAR); + // -20 is the chosen number to say there is no cache set since 0 is + // a valid option + if (remainingChoices != -20) + return remainingChoices; + } + + // logic for psionics + if (GetIsPsionicClass(nClass)) + remainingChoices = GetRemainingPowerChoices(nClass, circleLevel, oPC); + + // logic for ToB + if (GetIsBladeMagicClass(nClass)) + remainingChoices = (GetRemainingManeuverChoices(nClass, oPC) + + GetRemainingStanceChoices(nClass, oPC)); + + // logic for Invokers + if (GetIsInvocationClass(nClass)) + remainingChoices = GetRemainingInvocationChoices(nClass, circleLevel, oPC); + + // logic for mysteries + if (GetIsShadowMagicClass(nClass)) + { + int totalChosen = 0; + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + json circles = JsonObjectKeys(chosenSpells); + int totalCircles = JsonGetLength(circles); + + int i; + for (i = 0; i < totalCircles; i++) + { + // loop through each circle and add its total spells together since + // we don't care about where you spend your spells, only the amount + string currentCircle = JsonGetString(JsonArrayGet(circles, i)); + json spellList = JsonObjectGet(chosenSpells, currentCircle); + if (spellList != JsonNull()) + totalChosen += JsonGetLength(spellList); + } + + int maxKnown = GetMaxMysteryCount(oPC, nClass); + remainingChoices = (maxKnown - totalChosen); + } + + if (GetIsTruenamingClass(nClass)) + remainingChoices = GetRemainingTruenameChoices(nClass, -1, oPC); + + if (nClass == CLASS_TYPE_ARCHIVIST) + { + int nLevel = GetLevelByClass(CLASS_TYPE_ARCHIVIST, oPC); + int spellsAvailable; + if (nLevel == 1) + spellsAvailable = (3 + GetAbilityModifier(ABILITY_INTELLIGENCE, oPC)); + else + spellsAvailable = 2; + + json newSpells = GetArchivistNewSpellsList(oPC); + int totalNewSpells = JsonGetLength(newSpells); + remainingChoices = (spellsAvailable - totalNewSpells); + } + if (GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS) + { + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + int totalSpellsKnown = 0; + int casterLevel = GetCasterLevelByClass(nClass, oPC); + + // these specific classes only learn at specific rates + int advancedLearning = 0; + // beguiler learns every 4th level starting on 3 + if (nClass == CLASS_TYPE_BEGUILER) + advancedLearning = ((casterLevel+1)/4); + // dread learns every 4th level + if (nClass == CLASS_TYPE_DREAD_NECROMANCER) + advancedLearning = (casterLevel/4); + // warmage is a bastard child that choses when it learns a spell whenever + // it decides it feels like it wants to + if (nClass == CLASS_TYPE_WARMAGE) + { + if (casterLevel >= 3) // 1 choice + advancedLearning++; + if (casterLevel >= 6) // 2 choice + advancedLearning++; + if (casterLevel >= 11) // 3 choice + advancedLearning++; + if (casterLevel >= 16) // 4 choice + advancedLearning++; + if (casterLevel >= 24) // 5 choice + advancedLearning++; + if (casterLevel >= 28) // 6 choice + advancedLearning++; + if (casterLevel >= 32) // 7 choice + advancedLearning++; + if (casterLevel >= 36) // 8 choice + advancedLearning++; + if (casterLevel >= 40) // 9 choice + advancedLearning++; + } + + if (advancedLearning) + { + int maxSpellLevel = GetMaxSpellLevelForCasterLevel(nClass, casterLevel); + // can't learn what you can't achieve + if (circleLevel > maxSpellLevel) + remainingChoices = 0; + else + { + int chosenSpellsAmount = 0; + + json circles = JsonObjectKeys(chosenSpells); + int totalCircles = JsonGetLength(circles); + string sFile = GetClassSpellbookFile(nClass); + + int i; + for (i = 0; i <= totalCircles; i++) + { + string currentCircle = JsonGetString(JsonArrayGet(circles, i)); + json spellList = JsonObjectGet(chosenSpells, currentCircle); + if ((spellList != JsonNull())) + { + // loop through the spells of a given circle and count how + // many advanced learning spells you know + int numOfSpells = JsonGetLength(spellList); + int j; + for (j = 0; j < numOfSpells; j++) + { + int nSpellbookID = JsonGetInt(JsonArrayGet(spellList, j)); + int isAL = StringToInt(Get2DACache(sFile, "AL", nSpellbookID)); + if (isAL) + chosenSpellsAmount++; + } + } + } + + remainingChoices = (advancedLearning - chosenSpellsAmount); + } + } + else + { + // default logic for spont casters + totalSpellsKnown = GetSpellKnownMaxCount(casterLevel, circleLevel, nClass, oPC); + // Favoured Soul has more 0 choices than there are spells for some reason + if (nClass == CLASS_TYPE_FAVOURED_SOUL && circleLevel == 0 && totalSpellsKnown > 6) + totalSpellsKnown = 6; + + // logic for spont casters + json selectedCircle = JsonObjectGet(chosenSpells, IntToString(circleLevel)); + if (selectedCircle == JsonNull()) + return totalSpellsKnown; + + int selectedSpellCount = JsonGetLength(selectedCircle); + remainingChoices = (totalSpellsKnown - selectedSpellCount); + } + } + + if (chosenCircle == circleLevel) + SetLocalInt(oPC, NUI_LEVEL_UP_REMAINING_CHOICES_CACHE_VAR, remainingChoices); + return remainingChoices; +} + +void FinishLevelUp(int nClass, object oPC=OBJECT_SELF) +{ + RemoveSpells(nClass, oPC); + LearnSpells(nClass, oPC); + if (nClass == CLASS_TYPE_ARCHIVIST) + { + int nLevel = GetLevelByClass(nClass, oPC); + SetPersistantLocalInt(oPC, "LastSpellGainLevel", nLevel); + } + ClearLevelUpNUICaches(nClass, oPC); +} + +void ClearLevelUpNUICaches(int nClass, object oPC=OBJECT_SELF) +{ + // clear the chosen spells you made + DeleteLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR); + // clear the known spells you have + DeleteLocalJson(oPC, NUI_LEVEL_UP_KNOWN_SPELLS_VAR); + SetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR, -1); + DeleteLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + // clear the psionics selected choices + DeleteLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR); + // clear the expanded choices for psionics and invokers + DeleteLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR); + // clear the PnP replace list + DeleteLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR); + // for invocation and psionics we grab the list of known extra spells and cache it + // so we need to clear those caches + if (GetIsInvocationClass(nClass)) + { + DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_0"); + DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(INVOCATION_LIST_EXTRA)); + DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(INVOCATION_LIST_EXTRA_EPIC)); + } + if (GetIsPsionicClass(nClass)) + { + DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_0"); + DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(POWER_LIST_EXP_KNOWLEDGE)); + DeleteLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(POWER_LIST_EPIC_EXP_KNOWLEDGE)); + } + // for ToB we need to clear all the discipline info for determining PrC choice validity + if (GetIsBladeMagicClass(nClass)) + { + DeleteLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(CLASS_TYPE_SWORDSAGE)); + DeleteLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(CLASS_TYPE_WARBLADE)); + DeleteLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(CLASS_TYPE_CRUSADER)); + } +} + +void RemoveSpells(int nClass, object oPC=OBJECT_SELF) +{ + // we don't remove on psionic classes and archivist + if (GetIsPsionicClass(nClass) || nClass == CLASS_TYPE_ARCHIVIST) + return; + + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + json spellCircles = JsonObjectKeys(knownSpells); + int totalCircles = JsonGetLength(spellCircles); + + // loop through all the known spells circles + int i; + for (i = 0; i < totalCircles; i++) + { + string sSpellLevel = JsonGetString(JsonArrayGet(spellCircles, i)); + int nSpellLevel = StringToInt(sSpellLevel); + + json chosenCircle = JsonObjectGet(knownSpells, sSpellLevel); + int totalSpells = JsonGetLength(chosenCircle); + + // loop through the spell list at the given circle + int y; + for (y = 0; y < totalSpells; y++) + { + int nSpellbookID = JsonGetInt(JsonArrayGet(chosenCircle, y)); + // if the spell is not a chosen spell, then it was removed + if (!SpellIsWithinObject(nClass, nSpellbookID, nSpellLevel, chosenSpells, oPC)) + { + if (GetIsInvocationClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + string spellId = Get2DACache(sFile, "SpellID", nSpellbookID); + int chosenList = 0; + // check to see if its a extra invocation choice and set it's chosen list + if (GetHasFeat(FEAT_EXTRA_INVOCATION_I, oPC)) + { + json expList = GetSpellIDsKnown(nClass, oPC, INVOCATION_LIST_EXTRA); + if (JsonObjectGet(expList, spellId) != JsonNull()) + chosenList = INVOCATION_LIST_EXTRA; + } + if (GetHasFeat(FEAT_EPIC_EXTRA_INVOCATION_I, oPC)) + { + json expList = GetSpellIDsKnown(nClass, oPC, INVOCATION_LIST_EXTRA_EPIC); + if (JsonObjectGet(expList, spellId) != JsonNull()) + chosenList = INVOCATION_LIST_EXTRA_EPIC; + } + RemoveSpellKnown(nClass, nSpellbookID, oPC, chosenList); + } + if (GetIsBladeMagicClass(nClass) || GetIsShadowMagicClass(nClass)) + RemoveSpellKnown(nClass, nSpellbookID, oPC); + + if (GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS) + { + string sFile = GetClassSpellbookFile(nClass); + string sSpellBook = GetSpellsKnown_Array(nClass); + // remove the spell from the spellbook + array_extract_int(oPC, sSpellBook, nSpellbookID); + // wipe the spell from the player + int ipFeatID = StringToInt(Get2DACache(sFile, "IPFeatID", nSpellbookID)); + WipeSpellFromHide(ipFeatID, oPC); + } + } + } + } +} + +void LearnSpells(int nClass, object oPC=OBJECT_SELF) +{ + if (GetIsPsionicClass(nClass)) + { + LearnPowers(nClass, oPC); + return; + } + + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json spellCircles = JsonObjectKeys(chosenSpells); + int totalCircles = JsonGetLength(spellCircles); + + // loop through chosen spells circles + int i; + for (i = 0; i < totalCircles; i++) + { + string sSpellLevel = JsonGetString(JsonArrayGet(spellCircles, i)); + int nSpellLevel = StringToInt(sSpellLevel); + + json chosenCircle = JsonObjectGet(chosenSpells, sSpellLevel); + int totalSpells = JsonGetLength(chosenCircle); + + // loop through the spell list at the circle + int y; + for (y = 0; y < totalSpells; y++) + { + int nSpellbookID = JsonGetInt(JsonArrayGet(chosenCircle, y)); + // if the spell is not in the known spell list then it was newly added + if (!SpellIsWithinObject(nClass, nSpellbookID, nSpellLevel, knownSpells, oPC)) + { + if (GetIsTruenamingClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + // find out what lexicon it belongs to and add it to that + int lexicon = StringToInt(Get2DACache(sFile, "Lexicon", nSpellbookID)); + AddUtteranceKnown(oPC, nClass, nSpellbookID, lexicon, TRUE, GetHitDice(oPC)); + } + + if (GetIsShadowMagicClass(nClass)) + AddMysteryKnown(oPC, nClass, nSpellbookID, TRUE, GetHitDice(oPC)); + + if (GetIsInvocationClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + string spellId = Get2DACache(sFile, "SpellID", nSpellbookID); + int chosenList = nClass; + json expList = GetExpandedChoicesList(nClass, oPC); + // if the invocation belongs to the extra or epic extra list + // then we need to provide those list ids instead. + if (JsonObjectGet(expList, spellId) != JsonNull()) + chosenList = INVOCATION_LIST_EXTRA; + expList = GetEpicExpandedChoicesList(nClass, oPC); + if (JsonObjectGet(expList, spellId) != JsonNull()) + chosenList = INVOCATION_LIST_EXTRA_EPIC; + AddInvocationKnown(oPC, chosenList, nSpellbookID, TRUE, GetHitDice(oPC)); + } + + if (GetIsBladeMagicClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + int maneuverType = StringToInt(Get2DACache(sFile, "Type", nSpellbookID)); + // we save our moves either to stance or maneuever + if (maneuverType != MANEUVER_TYPE_STANCE) + maneuverType = MANEUVER_TYPE_MANEUVER; + + AddManeuverKnown(oPC, nClass, nSpellbookID, maneuverType, TRUE, GetHitDice(oPC)); + } + int nSpellbookType = GetSpellbookTypeForClass(nClass); + if (nSpellbookType == SPELLBOOK_TYPE_SPONTANEOUS + || nClass == CLASS_TYPE_ARCHIVIST) + { + // these classes have their own syste, + if (nClass == CLASS_TYPE_BEGUILER + || nClass == CLASS_TYPE_DREAD_NECROMANCER + || nClass == CLASS_TYPE_WARMAGE) + { + int casterLevel = GetCasterLevelByClass(nClass, oPC); + // this is taken from prc_s_spellgain as it is coupled with the + // dynamic dialogue system + int advancedLearning = 0; + // beguilers learn every 4th level starting on 3rd + if (nClass == CLASS_TYPE_BEGUILER) + advancedLearning = ((casterLevel+1)/4); + // dread learns every 4th level + if (nClass == CLASS_TYPE_DREAD_NECROMANCER) + advancedLearning = (casterLevel/4); + if (nClass == CLASS_TYPE_WARMAGE) + { + if (casterLevel >= 3) + advancedLearning++; + } + + if (advancedLearning) + { + // incremenet the total advanced learning known + int nAdvLearn = GetPersistantLocalInt(oPC, "AdvancedLearning_"+IntToString(nClass)); + nAdvLearn++; + SetPersistantLocalInt(oPC, "AdvancedLearning_"+IntToString(nClass), nAdvLearn); + } + } + + // get location of persistant storage on the hide + string sSpellbook = GetSpellsKnown_Array(nClass, nSpellLevel); + //object oToken = GetHideToken(oPC); + + // Create spells known persistant array if it is missing + int nSize = persistant_array_get_size(oPC, sSpellbook); + if (nSize < 0) + { + persistant_array_create(oPC, sSpellbook); + nSize = 0; + } + + // Mark the spell as known (e.g. add it to the end of oPCs spellbook) + persistant_array_set_int(oPC, sSpellbook, nSize, nSpellbookID); + + if (nSpellbookType == SPELLBOOK_TYPE_SPONTANEOUS) + { + // add spell + string sFile = GetClassSpellbookFile(nClass); + string sArrayName = "NewSpellbookMem_" + IntToString(nClass); + int featId = StringToInt(Get2DACache(sFile, "FeatID", nSpellbookID)); + int ipFeatID = StringToInt(Get2DACache(sFile, "IPFeatID", nSpellbookID)); + AddSpellUse(oPC, nSpellbookID, nClass, sFile, sArrayName, nSpellbookType, GetPCSkin(oPC), featId, ipFeatID); + } + } + } + } + } +} + +int EnableChosenButton(int nClass, int spellbookId, int circleLevel, object oPC=OBJECT_SELF) +{ + if (GetIsPsionicClass(nClass) + || GetIsShadowMagicClass(nClass) + || GetIsTruenamingClass(nClass) + || nClass == CLASS_TYPE_DREAD_NECROMANCER + || nClass == CLASS_TYPE_BEGUILER + || nClass == CLASS_TYPE_WARMAGE + || nClass == CLASS_TYPE_ARCHIVIST) + { + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json currentCircle = JsonObjectGet(knownSpells, IntToString(circleLevel)); + int totalSpells = JsonGetLength(currentCircle); + int i; + for (i = 0; i < totalSpells; i++) + { + // if spell belongs to known spells, then disable, we don't allow + // replacing for these classes. + int currentSpellbookId = JsonGetInt(JsonArrayGet(currentCircle, i)); + if (currentSpellbookId == spellbookId) + return FALSE; + } + + } + + if (GetIsBladeMagicClass(nClass)) + { + string sFile = GetClassSpellbookFile(nClass); + int prereqs = StringToInt(Get2DACache(sFile, "Prereqs", spellbookId)); + string discipline = Get2DACache(sFile, "Discipline", spellbookId); + // if the maneuver is required for others to exist, t hen disable it + if (IsRequiredForOtherManeuvers(nClass, prereqs, discipline, oPC)) + return FALSE; + // if it is required for a PRC to exist, then disable it. + if (IsRequiredForToBPRCClass(nClass, spellbookId, oPC)) + return FALSE; + } + + if (GetIsInvocationClass(nClass)) + { + // dragon Shamans can't replace + if (nClass == CLASS_TYPE_DRAGON_SHAMAN) + { + json invokKnown = GetSpellIDsKnown(nClass, oPC); + string sFile = GetClassSpellbookFile(nClass); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + json chosenSpell = JsonObjectGet(invokKnown, spellId); + if (chosenSpell != JsonNull()) + return FALSE; + } + } + + // If we do not use the bioware unlearn system, we follow PnP + if (!GetPRCSwitch(PRC_BIO_UNLEARN)) + { + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json currentCircle = JsonObjectGet(knownSpells, IntToString(circleLevel)); + int totalSpells = JsonGetLength(currentCircle); + int i; + for (i = 0; i < totalSpells; i++) + { + int currentSpellbookId = JsonGetInt(JsonArrayGet(currentCircle, i)); + // if the spell belongs to the known spell list, then we need to determine + if (currentSpellbookId == spellbookId) + { + json unlearnList = GetChosenReplaceListObject(oPC); + int totalUnlearned = JsonGetLength(unlearnList); + int totalAllowed = GetPRCSwitch(PRC_UNLEARN_SPELL_MAXNR); + // we default to 1 if no max number of unlearns is set + if (!totalAllowed) + totalAllowed = 1; + // we cannot replace a spell if we have more than or equal to the + // amount of relearns allowed, therefore disable. + return totalUnlearned < totalAllowed; + } + } + + } + + return TRUE; +} + +void ResetChoices(object oPC=OBJECT_SELF) +{ + // reset choices made so far + DeleteLocalJson(oPC, NUI_LEVEL_UP_CHOSEN_SPELLS_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR); + DeleteLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR); +} + +void RemoveSpellKnown(int nClass, int spellbookId, object oPC=OBJECT_SELF, int nList=0) +{ + string sBase; + string levelArrayBaseId; + string generalArrayBaseId; + string totalCountId; + + string sFile = GetClassSpellbookFile(nClass); + int chosenList = (nList != 0) ? nList : nClass; + int spellID = StringToInt(Get2DACache(sFile, "SpellID", spellbookId)); + + // if statements are to change the location of the spellbook we are grabbing + if (GetIsShadowMagicClass(nClass)) + { + sBase = _MYSTERY_LIST_NAME_BASE + IntToString(chosenList); + levelArrayBaseId = _MYSTERY_LIST_LEVEL_ARRAY; + generalArrayBaseId = _MYSTERY_LIST_GENERAL_ARRAY; + totalCountId = _MYSTERY_LIST_TOTAL_KNOWN; + } + + if (GetIsInvocationClass(nClass)) + { + sBase = _INVOCATION_LIST_NAME_BASE + IntToString(chosenList); + levelArrayBaseId = _INVOCATION_LIST_LEVEL_ARRAY; + generalArrayBaseId = _INVOCATION_LIST_GENERAL_ARRAY; + totalCountId = _INVOCATION_LIST_TOTAL_KNOWN; + } + + if (GetIsBladeMagicClass(nClass)) + { + int maneuverType = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + if (maneuverType != MANEUVER_TYPE_STANCE) maneuverType = MANEUVER_TYPE_MANEUVER; + sBase = _MANEUVER_LIST_NAME_BASE + IntToString(chosenList) + IntToString(maneuverType); + levelArrayBaseId = _MANEUVER_LIST_LEVEL_ARRAY; + generalArrayBaseId = _MANEUVER_LIST_GENERAL_ARRAY; + totalCountId = _MANEUVER_LIST_TOTAL_KNOWN; + } + + if (GetIsTruenamingClass(nClass)) + { + string lexicon = Get2DACache(sFile, "Lexicon", spellbookId); + sBase = _UTTERANCE_LIST_NAME_BASE + IntToString(chosenList) + lexicon; + levelArrayBaseId = _UTTERANCE_LIST_LEVEL_ARRAY; + generalArrayBaseId = _UTTERANCE_LIST_GENERAL_ARRAY; + totalCountId = _UTTERANCE_LIST_TOTAL_KNOWN; + } + + string sTestArray; + + int found = FALSE; + + int i; + for (i = 1; i <= GetHitDice(oPC); i++) + { + sTestArray = sBase + levelArrayBaseId + IntToString(i); + if (persistant_array_exists(oPC, sTestArray)) + { + // if we found the spell, then we remove it. + if (persistant_array_extract_int(oPC, sTestArray, spellID) >= 0) + { + found = TRUE; + break; + } + } + } + + if (!found) + { + // if not found we check the general list where spells aren't set to a level. + sTestArray = sBase + generalArrayBaseId; + if (persistant_array_exists(oPC, sTestArray)) + { + //if we could not find the spell here, something went wrong + if (persistant_array_extract_int(oPC, sTestArray, spellID) < 0) + { + SendMessageToPC(oPC, "Could not find spellID " + IntToString(spellID) + " in the class's spellbook!"); + return; + } + } + } + + // decrement the amount of spells known. + SetPersistantLocalInt(oPC, sBase + totalCountId, + GetPersistantLocalInt(oPC, sBase + totalCountId) - 1 + ); + + // if ToB we need to decrement the specific discipline as well. + if (GetIsBladeMagicClass(nClass)) + { + int maneuverType = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + if (maneuverType == MANEUVER_TYPE_BOOST + || maneuverType == MANEUVER_TYPE_COUNTER + || maneuverType == MANEUVER_TYPE_STRIKE + || maneuverType == MANEUVER_TYPE_MANEUVER) + maneuverType = MANEUVER_TYPE_MANEUVER; + string sDisciplineArray = _MANEUVER_LIST_DISCIPLINE + IntToString(maneuverType) + "_" + Get2DACache(sFile, "Discipline", spellbookId); + SetPersistantLocalInt(oPC, sDisciplineArray, + GetPersistantLocalInt(oPC, sDisciplineArray) - 1); + } + + // remove spell from player + int ipFeatID = StringToInt(Get2DACache(sFile, "IPFeatID", spellbookId)); + itemproperty ipFeat = PRCItemPropertyBonusFeat(ipFeatID); + object oSkin = GetPCSkin(oPC); + RemoveItemProperty(oSkin, ipFeat); + CheckAndRemoveFeat(oSkin, ipFeat); +} + +json GetSpellIDsKnown(int nClass, object oPC=OBJECT_SELF, int nList=0) +{ + json spellIds = GetLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(nList)); + if (spellIds == JsonNull()) + spellIds = JsonObject(); + else + return spellIds; + + string sBase; + string levelArrayBaseId; + string generalArrayBaseId; + // if we are given a listId then use that instead, used for extra choices and + // expanded knowledge + int chosenList = (nList != 0) ? nList : nClass; + // these if checks are for setting class specific ids + if (nClass == CLASS_TYPE_DRAGON_SHAMAN + || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT + || nClass == CLASS_TYPE_WARLOCK) + { + sBase = _INVOCATION_LIST_NAME_BASE + IntToString(chosenList); + levelArrayBaseId = _INVOCATION_LIST_LEVEL_ARRAY; + generalArrayBaseId = _INVOCATION_LIST_GENERAL_ARRAY; + } + if (GetIsPsionicClass(nClass)) + { + sBase = _POWER_LIST_NAME_BASE + IntToString(chosenList); + levelArrayBaseId = _POWER_LIST_LEVEL_ARRAY; + generalArrayBaseId = _POWER_LIST_GENERAL_ARRAY; + } + + // go through the level list and translate the spellIds into a JSON Object + // structure for easier access. + int i; + for (i = 1; i <= GetHitDice(oPC); i++) + { + string sTestArray = sBase + levelArrayBaseId + IntToString(i); + if (persistant_array_exists(oPC, sTestArray)) + { + int nSize = persistant_array_get_size(oPC, sTestArray); + + int j; + for (j = 0; j < nSize; j++) + { + spellIds = JsonObjectSet(spellIds, IntToString(persistant_array_get_int(oPC, sTestArray, j)), JsonBool(TRUE)); + } + } + } + + // go through the general list and translate the spellIds into a JSON Object + // structure for easier access. + string sTestArray = sBase + generalArrayBaseId; + if (persistant_array_exists(oPC, sTestArray)) + { + int nSize = persistant_array_get_size(oPC, sTestArray); + + int j; + for (j = 0; j < nSize; j++) + { + spellIds = JsonObjectSet(spellIds, IntToString(persistant_array_get_int(oPC, sTestArray, j)), JsonBool(TRUE)); + } + } + + SetLocalJson(oPC, NUI_LEVEL_UP_SPELLID_LIST_VAR + IntToString(nClass) + "_" + IntToString(nList), spellIds); + return spellIds; +} + +string ReasonForDisabledSpell(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + int circleLevel = StringToInt(Get2DACache(sFile, "Level", spellbookId)); + + // logic for psionics + if (GetIsPsionicClass(nClass)) + { + int maxLevel = GetMaxPowerLevelForClass(nClass, oPC); + if (circleLevel > maxLevel) + return "You are unable to learn at this level currently."; + + // if its an expanded knowledge choice and we have already made all our + // exp knowledge choices then it needs to be disabled. + if (IsExpKnowledgePower(nClass, spellbookId)) + { + int remainingExp = GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC) + + GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC); + if (!remainingExp) + return "You have no more expanded knowledge choices left."; + } + } + + if (GetIsShadowMagicClass(nClass)) + { + // mysteries are weird, the circles are sectioned by 1-3, 4-6, 7-9 + // if you do not have at least 2 choices from a circle you can't progress up + // so you can have access to circles 1,2,4,7,8 + int nType = 1; + if (circleLevel >= 4 && circleLevel <= 6) + nType = 2; + if (circleLevel >= 7 && circleLevel <= 9) + nType = 3; + int maxPossibleCircle = GetMaxMysteryLevelLearnable(oPC, nClass, nType); + if (circleLevel > maxPossibleCircle) + return "You are unable to learn at this level currently."; + } + + if (GetIsTruenamingClass(nClass)) + { + int lexicon = StringToInt(Get2DACache(sFile, "Lexicon", spellbookId)); + // each lexicon learns at different rates + int maxCircle = GetLexiconCircleKnownAtLevel(GetLevelByClass(nClass, oPC), lexicon); + if (circleLevel > maxCircle) + return "You are unable to learn at this level currently."; + + if (GetRemainingTruenameChoices(nClass, lexicon, oPC)) + return ""; + return "You have made all your truenaming choices."; + } + + // logic for ToB + if (GetIsBladeMagicClass(nClass)) + { + if (circleLevel > GetMaxInitiatorCircle(nClass, oPC)) + return "You are unable to learn at this level currently."; + + // if you do not have the prerequisite amount of maneuevers to learn + // the maneuever, then you can't learn it. + if (!HasPreRequisitesForManeuver(nClass, spellbookId, oPC)) + return "You do not have the prerequisites for this maneuver."; + + // maneuvers and stances have their own seperate limits + int type = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + if (type == MANEUVER_TYPE_BOOST + || type == MANEUVER_TYPE_COUNTER + || type == MANEUVER_TYPE_STRIKE + || type == MANEUVER_TYPE_MANEUVER) + { + int remainingMan = GetRemainingManeuverChoices(nClass, oPC); + if (remainingMan) + return ""; + return "You have made all your maneuver choices."; + } + if (type == MANEUVER_TYPE_STANCE) + { + int remainingStance = GetRemainingStanceChoices(nClass, oPC); + if (remainingStance) + return ""; + return "You have made all your stance choices."; + } + } + + // default logic + // determine remaining Spell/Power choices left for player, if there is any + // remaining, enable the buttons. + if (GetRemainingSpellChoices(nClass, circleLevel, oPC)) + return ""; + + return "You have made all your spell choices."; +} + +string ReasonForDisabledChosen(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + int circleLevel = StringToInt(Get2DACache(sFile, "Level", spellbookId)); + + + if (GetIsPsionicClass(nClass) + || GetIsShadowMagicClass(nClass) + || GetIsTruenamingClass(nClass) + || nClass == CLASS_TYPE_DREAD_NECROMANCER + || nClass == CLASS_TYPE_BEGUILER + || nClass == CLASS_TYPE_WARMAGE) + { + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json currentCircle = JsonObjectGet(knownSpells, IntToString(circleLevel)); + int totalSpells = JsonGetLength(currentCircle); + int i; + for (i = 0; i < totalSpells; i++) + { + // if spell belongs to known spells, then disable, we don't allow + // replacing for these classes. + int currentSpellbookId = JsonGetInt(JsonArrayGet(currentCircle, i)); + if (currentSpellbookId == spellbookId) + return "You cannot replace spells as this class."; + } + + } + + if (GetIsBladeMagicClass(nClass)) + { + int prereqs = StringToInt(Get2DACache(sFile, "Prereqs", spellbookId)); + string discipline = Get2DACache(sFile, "Discipline", spellbookId); + // if the maneuver is required for others to exist, t hen disable it + if (IsRequiredForOtherManeuvers(nClass, prereqs, discipline, oPC)) + return "This maneuver is required for another maneuver."; + // if it is required for a PRC to exist, then disable it. + if (IsRequiredForToBPRCClass(nClass, spellbookId, oPC)) + return "This maneuver is reuquired for a PRC class."; + } + + if (GetIsInvocationClass(nClass)) + { + // dragon Shamans can't replace + if (nClass == CLASS_TYPE_DRAGON_SHAMAN) + { + json invokKnown = GetSpellIDsKnown(nClass, oPC); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + json chosenSpell = JsonObjectGet(invokKnown, spellId); + if (chosenSpell != JsonNull()) + return "You cannot replace invocations as this class."; + } + } + + // If we do not use the bioware unlearn system, we follow PnP + if (!GetPRCSwitch(PRC_BIO_UNLEARN)) + { + json knownSpells = GetKnownSpellListObject(nClass, oPC); + json currentCircle = JsonObjectGet(knownSpells, IntToString(circleLevel)); + int totalSpells = JsonGetLength(currentCircle); + int i; + for (i = 0; i < totalSpells; i++) + { + int currentSpellbookId = JsonGetInt(JsonArrayGet(currentCircle, i)); + // if the spell belongs to the known spell list, then we need to determine + if (currentSpellbookId == spellbookId) + { + json unlearnList = GetChosenReplaceListObject(oPC); + int totalUnlearned = JsonGetLength(unlearnList); + int totalAllowed = GetPRCSwitch(PRC_UNLEARN_SPELL_MAXNR); + // we default to 1 if no max number of unlearns is set + if (!totalAllowed) + totalAllowed = 1; + // we cannot replace a spell if we have more than or equal to the + // amount of relearns allowed, therefore disable. + if (totalUnlearned < totalAllowed) + return ""; + return "You can only replace " + IntToString(totalAllowed) + " spells during level up."; + } + } + + } + + return ""; +} + +json GetExpandedChoicesList(int nClass, object oPC=OBJECT_SELF) +{ + json expandedChoices = GetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR); + if (expandedChoices != JsonNull()) + return expandedChoices; + + if (GetIsPsionicClass(nClass)) + expandedChoices = GetSpellIDsKnown(nClass, oPC, POWER_LIST_EXP_KNOWLEDGE); + else + expandedChoices = GetSpellIDsKnown(nClass, oPC, INVOCATION_LIST_EXTRA); + + SetLocalJson(oPC, NUI_LEVEL_UP_EXPANDED_CHOICES_VAR, expandedChoices); + return expandedChoices; +} + +int GetRemainingExpandedChoices(int nClass, int nList, object oPC=OBJECT_SELF) +{ + int remainingChoices = 0; + + json expandedList = (nList == INVOCATION_LIST_EXTRA || nList == POWER_LIST_EXP_KNOWLEDGE ) ? GetExpandedChoicesList(nClass, oPC) + : GetEpicExpandedChoicesList(nClass, oPC); + int expChoicesCount = JsonGetLength(JsonObjectKeys(expandedList)); + int maxExpChoices = (GetIsPsionicClass(nClass)) ? GetMaxPowerCount(oPC, nList) + : GetMaxInvocationCount(oPC, nList); + remainingChoices += (maxExpChoices - expChoicesCount); + + return remainingChoices; +} + +json GetEpicExpandedChoicesList(int nClass, object oPC=OBJECT_SELF) +{ + json expandedChoices = GetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR); + if (expandedChoices != JsonNull()) + return expandedChoices; + + if (GetIsPsionicClass(nClass)) + expandedChoices = GetSpellIDsKnown(nClass, oPC, POWER_LIST_EPIC_EXP_KNOWLEDGE); + else + expandedChoices = GetSpellIDsKnown(nClass, oPC, INVOCATION_LIST_EXTRA_EPIC); + + SetLocalJson(oPC, NUI_LEVEL_UP_EPIC_EXPANDED_CHOICES_VAR, expandedChoices); + return expandedChoices; +} + +int IsSpellInExpandedChoices(int nClass, int nList, int spellId, object oPC=OBJECT_SELF) +{ + json expList = (nList == POWER_LIST_EXP_KNOWLEDGE || nList == INVOCATION_LIST_EXTRA) ? GetExpandedChoicesList(nClass, oPC) + : GetEpicExpandedChoicesList(nClass, oPC); + + return (JsonObjectGet(expList, IntToString(spellId)) != JsonNull()); +} + +json GetChosenReplaceListObject(object oPC=OBJECT_SELF) +{ + json replaceList = GetLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR); + if (replaceList == JsonNull()) + replaceList = JsonObject(); + else + return replaceList; + + SetLocalJson(oPC, NUI_LEVEL_UP_RELEARN_LIST_VAR, replaceList); + return replaceList; +} + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Psionics /// +/// /// +//////////////////////////////////////////////////////////////////////////// + + +int IsExpKnowledgePower(int nClass, int spellbookId) +{ + string sFile = GetClassSpellbookFile(nClass); + int isExp = StringToInt(Get2DACache(sFile, "Exp", spellbookId)); + return isExp; +} + +json GetCurrentPowerList(object oPC=OBJECT_SELF) +{ + json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR); + if (retValue == JsonNull()) + retValue = JsonArray(); + else + return retValue; + + SetLocalJson(oPC, NUI_LEVEL_UP_POWER_LIST_VAR, retValue); + return retValue; +} + +int ShouldAddPower(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + int featId = StringToInt(Get2DACache(sFile, "FeatID", spellbookId)); + int isExp = StringToInt(Get2DACache(sFile, "Exp", spellbookId)); + // if you don't have the prereqs for a power then don't add it. Specific for + // psions + if (!CheckPowerPrereqs(featId, oPC)) + return FALSE; + // if the power is a expanded knowledge power + if (isExp) + { + // and we have a expanded knowledge choice left to make then show + // the button + int addPower = FALSE; + int maxLevel = GetMaxPowerLevelForClass(nClass, oPC); + int currentCircle = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR); + + int choicesLeft = GetRemainingExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, oPC); + if (choicesLeft && (currentCircle <= (maxLevel-1))) + addPower = TRUE; + choicesLeft = GetRemainingExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, oPC); + if (choicesLeft) + addPower = TRUE; + // otherwise don't show the button. + return addPower; + } + + return TRUE; +} + +void LearnPowers(int nClass, object oPC=OBJECT_SELF) +{ + // add normal powers + json powerList = GetCurrentPowerList(oPC); + int totalPowers = JsonGetLength(powerList); + int i; + for (i = 0; i < totalPowers; i++) + { + int nSpellbookID = JsonGetInt(JsonArrayGet(powerList, i)); + // get the expanded knowledge list we are adding to if any + int expKnow = GetExpKnowledgePowerListRequired(nClass, nSpellbookID, oPC); + AddPowerKnown(oPC, nClass, nSpellbookID, TRUE, GetManifesterLevel(oPC, nClass, TRUE), expKnow); + } +} + +int GetExpKnowledgePowerListRequired(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + + int i; + // expanded knowledge is -1, epic epxanded knowledge is -2 + for (i = -1; i >= -2; i--) + { + int spellId = StringToInt(Get2DACache(sFile, "SpellID", spellbookId)); + if (IsSpellInExpandedChoices(nClass, i, spellId, oPC)) + return i; + } + + return 0; +} + +int GetMaxPowerLevelForClass(int nClass, object oPC=OBJECT_SELF) +{ + string sFile = GetAMSKnownFileName(nClass); + int nLevel = GetManifesterLevel(oPC, nClass, TRUE); + // index is level - 1 since it starts at 0. + int maxLevel = StringToInt(Get2DACache(sFile, "MaxPowerLevel", nLevel-1)); + return maxLevel; +} + +int GetRemainingPowerChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE) +{ + int remaining = 0; + + int maxLevel = GetMaxPowerLevelForClass(nClass, oPC); + if (chosenCircle > maxLevel) + return 0; + + json choices = GetCurrentPowerList(oPC); + int totalChoices = JsonGetLength(choices); + int allowedChoices = GetMaxPowerCount(oPC, nClass); + int alreadyChosen = GetPowerCount(oPC, nClass); + string sFile = GetClassSpellbookFile(nClass); + + int i = 0; + for (i = 0; i < totalChoices; i++) + { + int spellbookId = JsonGetInt(JsonArrayGet(choices, i)); + int spellId = StringToInt(Get2DACache(sFile, "SpellID", spellbookId)); + //if the power is a expanded knowledge choice, don't count it + if (!IsSpellInExpandedChoices(nClass, POWER_LIST_EXP_KNOWLEDGE, spellId, oPC) + && !IsSpellInExpandedChoices(nClass, POWER_LIST_EPIC_EXP_KNOWLEDGE, spellId, oPC)) + remaining++; + } + remaining = (allowedChoices - remaining - alreadyChosen); + + // if this is true we count expanded knowledge choices + if (extra) + { + if (GetHasFeat(FEAT_EXPANDED_KNOWLEDGE_1, oPC) && (chosenCircle <= (maxLevel-1))) + { + int totalExp = GetMaxPowerCount(oPC, POWER_LIST_EXP_KNOWLEDGE); + json expChoices = GetExpandedChoicesList(nClass, oPC); + int choicesCount = JsonGetLength(JsonObjectKeys(expChoices)); + remaining += (totalExp - choicesCount); + } + if (GetHasFeat(FEAT_EPIC_EXPANDED_KNOWLEDGE_1, oPC)) + { + int totalExp = GetMaxPowerCount(oPC, POWER_LIST_EPIC_EXP_KNOWLEDGE); + json expChoices = GetEpicExpandedChoicesList(nClass, oPC); + int choicesCount = JsonGetLength(JsonObjectKeys(expChoices)); + remaining += (totalExp - choicesCount); + } + } + + return remaining; +} + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Initiators /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +json GetDisciplineInfoObject(int nClass, object oPC=OBJECT_SELF) +{ + json disciplineInfo = GetLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(nClass)); + if (disciplineInfo == JsonNull()) + disciplineInfo = JsonObject(); + else + return disciplineInfo; + + int chosenClass = GetLocalInt(oPC, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + string sFile = GetClassSpellbookFile(nClass); + + //if this is not the chosen class then we do not have a chosen spell list + // need to go through the class's 2da and check if you know the spell or not. + if (nClass != chosenClass) + { + int totalSpells = Get2DARowCount(sFile); + + int i; + for (i = 0; i < totalSpells; i++) + { + int featId = StringToInt(Get2DACache(sFile, "FeatID", i)); + if (featId && GetHasFeat(featId, oPC, TRUE)) + disciplineInfo = AddSpellDisciplineInfo(sFile, i, disciplineInfo); + } + } + else + { + json chosenMans = GetChosenSpellListObject(nClass, oPC); + + json circles = JsonObjectKeys(chosenMans); + int totalCircles = JsonGetLength(circles); + + int i; + for (i = 0; i < totalCircles; i++) + { + string currentCircle = JsonGetString(JsonArrayGet(circles, i)); + json currentList = JsonObjectGet(chosenMans, currentCircle); + int totalSpells = JsonGetLength(currentList); + + int y; + for (y = 0; y < totalSpells; y++) + { + int spellbookId = JsonGetInt(JsonArrayGet(currentList, y)); + disciplineInfo = AddSpellDisciplineInfo(sFile, spellbookId, disciplineInfo); + } + } + } + + SetLocalJson(oPC, NUI_LEVEL_UP_DISCIPLINE_INFO_VAR + IntToString(nClass), disciplineInfo); + return disciplineInfo; +} + +int IsRequiredForOtherManeuvers(int nClass, int prereq, string discipline, object oPC=OBJECT_SELF) +{ + json discInfo = GetDisciplineInfoObject(nClass, oPC); + + int total = 0; + + // loop through each prereq level and add up it's totals (ie how many maneuevrs + // do we know with 0,1,2...,n prereqs. + int i; + for (i = 0; i <= prereq; i++) + { + + json currDisc = JsonObjectGet(discInfo, discipline); + string discKey = ("Prereq_" + IntToString(i)); + int currentDiscPrereq = JsonGetInt(JsonObjectGet(currDisc, discKey)); + total += currentDiscPrereq; + } + + // then from above the given prereq check if we have any prereq maneuevers taken + for (i = (prereq+1); i < NUI_LEVEL_UP_MANEUVER_PREREQ_LIMIT; i++) + { + json currDisc = JsonObjectGet(discInfo, discipline); + string discKey = ("Prereq_" + IntToString(i)); + json discPrereq = JsonObjectGet(currDisc, discKey); + if (discPrereq != JsonNull()) + { + // if we do take the total and subtract by one, if it is lower than + // than the prereq needed, it is required + if (total - 1 < i) + return TRUE; + + // otherwise add how many we have and move up and keep trying. + int currentDiscPrereq = JsonGetInt(discPrereq); + total += currentDiscPrereq; + } + } + + return FALSE; +} + +int HasPreRequisitesForManeuver(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + int prereqs = StringToInt(Get2DACache(sFile, "Prereqs", spellbookId)); + if (!prereqs) + return TRUE; + string discipline = Get2DACache(sFile, "Discipline", spellbookId); + json discInfo = GetDisciplineInfoObject(nClass, oPC); + json chosenDisc = JsonObjectGet(discInfo, discipline); + if (chosenDisc != JsonNull()) + { + int nManCount = JsonGetInt(JsonObjectGet(chosenDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + if (nManCount >= prereqs) + return TRUE; + } + + return FALSE; +} + +int GetMaxInitiatorCircle(int nClass, object oPC=OBJECT_SELF) +{ + int initiatorLevel = GetInitiatorLevel(oPC, nClass); + // initiators learn by ceiling(classLevel) + int highestCircle = (initiatorLevel + 1) / 2; + if (highestCircle > 9) + return 9; + return highestCircle; +} + +int GetRemainingManeuverChoices(int nClass, object oPC=OBJECT_SELF) +{ + json discInfo = GetDisciplineInfoObject(nClass, oPC); + + json jManAmount = JsonObjectGet(discInfo, NUI_LEVEL_UP_MANEUVER_TOTAL); + int nManAmount = 0; + if (jManAmount != JsonNull()) + nManAmount = JsonGetInt(jManAmount); + + int maxAmount = GetMaxManeuverCount(oPC, nClass, MANEUVER_TYPE_MANEUVER); + + return maxAmount - nManAmount; +} + +int GetRemainingStanceChoices(int nClass, object oPC=OBJECT_SELF) +{ + json discInfo = GetDisciplineInfoObject(nClass, oPC); + + json jStanceAmount = JsonObjectGet(discInfo, NUI_LEVEL_UP_STANCE_TOTAL); + int nStanceAmount = 0; + if (jStanceAmount != JsonNull()) + nStanceAmount = JsonGetInt(jStanceAmount); + + int maxAmount = GetMaxManeuverCount(oPC, nClass, MANEUVER_TYPE_STANCE); + + return maxAmount - nStanceAmount; +} + +int IsAllowedDiscipline(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + // logic carried over from private function in discipline inc functions + // uses bitwise matching to tell if the discipline is allowed or not + string sFile = GetClassSpellbookFile(nClass); + int discipline = StringToInt(Get2DACache(sFile, "Discipline", spellbookId)); + + int nOverride = GetPersistantLocalInt(oPC, "AllowedDisciplines"); + if(nOverride == 0) + { + switch(nClass) + { + case CLASS_TYPE_CRUSADER: nOverride = 322; break;//DISCIPLINE_DEVOTED_SPIRIT + DISCIPLINE_STONE_DRAGON + DISCIPLINE_WHITE_RAVEN + case CLASS_TYPE_SWORDSAGE: nOverride = 245; break;//DISCIPLINE_DESERT_WIND + DISCIPLINE_DIAMOND_MIND + DISCIPLINE_SETTING_SUN + DISCIPLINE_SHADOW_HAND + DISCIPLINE_STONE_DRAGON + DISCIPLINE_TIGER_CLAW + case CLASS_TYPE_WARBLADE: nOverride = 460; break;//DISCIPLINE_DIAMOND_MIND + DISCIPLINE_IRON_HEART + DISCIPLINE_STONE_DRAGON + DISCIPLINE_TIGER_CLAW + DISCIPLINE_WHITE_RAVEN + } + } + return nOverride & discipline; +} + +int IsRequiredForToBPRCClass(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + int currentClassPos = 1; + // loop through all the classes and look for a PRC + while (currentClassPos) + { + int currentClass = GetClassByPosition(currentClassPos, oPC); + // if we reached a non existant class, we reached the end. + if (currentClass != CLASS_TYPE_INVALID) + { + string sFile = GetClassSpellbookFile(nClass); + int discipline = StringToInt(Get2DACache(sFile, "Discipline", spellbookId)); + + // check if the class is a ToB PRC Class and if the current spell's + // discipline is used for it. + int isUsed = FALSE; + if (currentClass == CLASS_TYPE_DEEPSTONE_SENTINEL + && (discipline == DISCIPLINE_STONE_DRAGON)) + isUsed = TRUE; + if (currentClass == CLASS_TYPE_BLOODCLAW_MASTER + && (discipline == DISCIPLINE_TIGER_CLAW)) + isUsed = TRUE; + if (currentClass == CLASS_TYPE_RUBY_VINDICATOR + && (discipline == DISCIPLINE_DEVOTED_SPIRIT)) + isUsed = TRUE; + if (currentClass == CLASS_TYPE_JADE_PHOENIX_MAGE) + isUsed = TRUE; + if (currentClass == CLASS_TYPE_MASTER_OF_NINE) + isUsed = TRUE; + if (currentClass == CLASS_TYPE_ETERNAL_BLADE + && (discipline == DISCIPLINE_DEVOTED_SPIRIT + || discipline == DISCIPLINE_DIAMOND_MIND)) + isUsed = TRUE; + if (currentClass == CLASS_TYPE_SHADOW_SUN_NINJA + && (discipline == DISCIPLINE_SETTING_SUN + || discipline == DISCIPLINE_SHADOW_HAND)) + isUsed = TRUE; + + // if any of the above was true than we need to check if this spell + // is required for a PRC + if (isUsed) + { + // get the discipline info for all BladeMagic classes if we have them. + json discInfo = GetDisciplineInfoObject(nClass, oPC); + json classDisc2Info = JsonObject(); + json classDisc3Info = JsonObject(); + if (nClass == CLASS_TYPE_SWORDSAGE) + { + if (GetLevelByClass(CLASS_TYPE_WARBLADE, oPC)) + classDisc2Info = GetDisciplineInfoObject(CLASS_TYPE_WARBLADE, oPC); + if (GetLevelByClass(CLASS_TYPE_CRUSADER, oPC)) + classDisc3Info = GetDisciplineInfoObject(CLASS_TYPE_CRUSADER, oPC); + } + if (nClass == CLASS_TYPE_CRUSADER) + { + if (GetLevelByClass(CLASS_TYPE_WARBLADE, oPC)) + classDisc2Info = GetDisciplineInfoObject(CLASS_TYPE_WARBLADE, oPC); + if (GetLevelByClass(CLASS_TYPE_SWORDSAGE, oPC)) + classDisc3Info = GetDisciplineInfoObject(CLASS_TYPE_SWORDSAGE, oPC); + } + if (nClass == CLASS_TYPE_WARBLADE) + { + if (GetLevelByClass(CLASS_TYPE_CRUSADER, oPC)) + classDisc2Info = GetDisciplineInfoObject(CLASS_TYPE_CRUSADER, oPC); + if (GetLevelByClass(CLASS_TYPE_SWORDSAGE, oPC)) + classDisc3Info = GetDisciplineInfoObject(CLASS_TYPE_SWORDSAGE, oPC); + } + + // Time to begin checking the PRCs + // this should follow the same logic as here + // https://gitea.raptio.us/Jaysyn/PRC8/src/commit/797442d3da7c9c8e1fcf585b97e2ff1cbe56045b/nwn/nwnprc/trunk/scripts/prc_prereq.nss#L991 + + // Check Deepstone Sentinel + if (currentClass == CLASS_TYPE_DEEPSTONE_SENTINEL) + { + // we need to look for 2 Stone Dragon Maneuvers and 1 Stone Dragon + // Stance. So add up the other MagicBlade classes and see if it is satisfied. + int stoneDMan, stoneDStance = 0; + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stoneDMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + stoneDStance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + if (stoneDMan >= 2 && stoneDStance >= 1) + return FALSE; + } + + currentDisc = JsonObjectGet(classDisc3Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stoneDMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + stoneDStance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + if (stoneDMan >= 2 && stoneDStance >= 1) + return FALSE; + } + // if it still isn't satisfied than the current class is required + // for it to exist. Check to see if it is safe to remove the maneuever + currentDisc = JsonObjectGet(discInfo, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stoneDMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + stoneDStance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + int type = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + //if the current maneuver is a stance, check to see if it is safe to remove + if (type == MANEUVER_TYPE_STANCE) + { + if (stoneDStance - 1 >= 1) + return FALSE; + } + else + { + // if it is not a stance we can just check the maneuevers in general + if (stoneDMan - 1 >= 2) + return FALSE; + } + + // this maneuver is required and should not be removed. + return TRUE; + } + } + + // Check Bloodclaw Master + if (currentClass == CLASS_TYPE_BLOODCLAW_MASTER) + { + // bloodclaw needs 3 Tiger Claw maneuevers + int tigerCMan = 0; + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + if (tigerCMan >= 3) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc3Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + if (tigerCMan >= 3) + return FALSE; + } + currentDisc = JsonObjectGet(discInfo, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + tigerCMan += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + if (tigerCMan-1 >= 3) + return FALSE; + return TRUE; + } + } + + if (currentClass == CLASS_TYPE_RUBY_VINDICATOR) + { + // Ruby Vindicator needs 1 stance and 1 maneuever from Devoted Spirit + int stance = 0; + int maneuver = 0; + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + if (stance >= 1 && maneuver >= 1) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc3Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + if (stance >= 1 && maneuver >= 1) + return FALSE; + } + currentDisc = JsonObjectGet(discInfo, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + int type = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + if (type == MANEUVER_TYPE_STANCE) + { + if (stance - 1 >= 1) + return FALSE; + return TRUE; + } + if (maneuver - 1 >= 1) + return FALSE; + return TRUE; + } + } + + if (currentClass == CLASS_TYPE_JADE_PHOENIX_MAGE) + { + // Jade Phoenix needs 1 stance and 2 maneuvers of any type + int stance = 0; + int maneuver = 0; + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + if (stance >= 1 && maneuver >= 2) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc3Info, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + if (stance >= 1 && maneuver >= 2) + return FALSE; + } + currentDisc = JsonObjectGet(discInfo, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + stance += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + maneuver += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + int type = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + if (type == MANEUVER_TYPE_STANCE) + { + if ((stance - 1 >= 1) && (maneuver -1 >= 2)) + return FALSE; + return TRUE; + } + if (maneuver - 1 >= 2) + return FALSE; + return TRUE; + } + } + + if (currentClass == CLASS_TYPE_MASTER_OF_NINE) + { + // master of nine needs 1 maneuever from 6 different disciplines + + int totalDiscCount = 0; + int currentClassAndDiscUsed = 0; + int i; + // loop through each possible discipline + for (i = 0; i <= 256; i++) + { + int found = 0; + // only disciplines that exist are stored, and only those + // that are used are stored, so we can loop through and + // find what disciplines we do or don't know. + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(i)); + if (currentDisc != JsonNull()) + found = 1; + + if (!found) + { + json currentDisc = JsonObjectGet(classDisc3Info, IntToString(i)); + if (currentDisc != JsonNull()) + found = 1; + } + + if (!found) + { + json currentDisc = JsonObjectGet(discInfo, IntToString(i)); + if (currentDisc != JsonNull()) + { + if (i == discipline) + currentClassAndDiscUsed = 1; + found = 1; + } + } + + totalDiscCount += found; + } + // if we have more maneuevers than 6, it is not required + if (totalDiscCount > 6) + return FALSE; + // however if we have 6 and this discipline was grabbed we need to make sure it is safe to remove + if (currentClassAndDiscUsed) + { + // if we were to remove this discipline and it is 5 or less total disciplines we have now + // it is important + if (totalDiscCount - 1 >= 6) + return FALSE; + json currentDisc = JsonObjectGet(discInfo, IntToString(discipline)); + int stance = JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + int maneuver = JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + // if we were to remove this discipline and are left with no more than + // this was important and it can't be removed + if ((stance + maneuver - 1) >= 1) + return FALSE; + return TRUE; + } + return FALSE; + } + + if (currentClass == CLASS_TYPE_ETERNAL_BLADE) + { + //Eternal blade 2 Devoted Spirits OR 2 Diamond Mind + int nTotal = 0; + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_DEVOTED_SPIRIT)); + if (currentDisc != JsonNull()) + { + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nTotal >= 2) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_DIAMOND_MIND)); + if (currentDisc != JsonNull()) + { + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nTotal >= 2) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc3Info, IntToString(DISCIPLINE_DEVOTED_SPIRIT)); + if (currentDisc != JsonNull()) + { + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nTotal >= 2) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc3Info, IntToString(DISCIPLINE_DIAMOND_MIND)); + if (currentDisc != JsonNull()) + { + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nTotal >= 2) + return FALSE; + } + currentDisc = JsonObjectGet(discInfo, IntToString(discipline)); + if (currentDisc != JsonNull()) + { + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + nTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + if ((nTotal - 1) >= 2) + return FALSE; + return TRUE; + } + } + + if (currentClass == CLASS_TYPE_SHADOW_SUN_NINJA) + { + // Shadow Sun Ninja needs 1 lvl2 Setting Sun OR Shadow Hand maneuever + // 1 Setting Sun maneuver AND 1 Shadow Hand maneuver + int nLvl2 = 0; + int shadowHTotal; + int settingSTotal; + + json currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_SHADOW_HAND)); + if (currentDisc != JsonNull()) + { + shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2)) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_SETTING_SUN)); + if (currentDisc != JsonNull()) + { + settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2)) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc3Info, IntToString(DISCIPLINE_SHADOW_HAND)); + if (currentDisc != JsonNull()) + { + shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2)) + return FALSE; + } + currentDisc = JsonObjectGet(classDisc2Info, IntToString(DISCIPLINE_SETTING_SUN)); + if (currentDisc != JsonNull()) + { + settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER))); + // stances here count as a maneuver so we need to count 2 + // to account for that + if (nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2)) + return FALSE; + } + currentDisc = JsonObjectGet(discInfo, IntToString(DISCIPLINE_SHADOW_HAND)); + if (currentDisc != JsonNull()) + { + shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + shadowHTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER))); + } + currentDisc = JsonObjectGet(discInfo, IntToString(DISCIPLINE_SETTING_SUN)); + if (currentDisc != JsonNull()) + { + settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_STANCE))); + settingSTotal += JsonGetInt(JsonObjectGet(currentDisc, IntToString(MANEUVER_TYPE_MANEUVER))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_STANCE))); + nLvl2 += JsonGetInt(JsonObjectGet(currentDisc, "Level2_" + IntToString(MANEUVER_TYPE_MANEUVER))); + } + int level = StringToInt(Get2DACache(sFile, "Level", spellbookId)); + if (level == 2) + nLvl2 -= 1; + if (discipline == DISCIPLINE_SHADOW_HAND) + shadowHTotal -= 1; + else + settingSTotal -= 1; + + return !(nLvl2 && shadowHTotal && settingSTotal && (shadowHTotal >= 2 || settingSTotal >= 2)); + } + } + + currentClassPos += 1; + } + else + currentClassPos = 0; + } + + return FALSE; +} + +json AddSpellDisciplineInfo(string sFile, int spellbookId, json classDisc) +{ + json classDiscCopy = classDisc; + int discipline = StringToInt(Get2DACache(sFile, "Discipline", spellbookId)); + int type = StringToInt(Get2DACache(sFile, "Type", spellbookId)); + int level = StringToInt(Get2DACache(sFile, "Level", spellbookId)); + int prereq = StringToInt(Get2DACache(sFile, "Prereqs", spellbookId)); + + json jDisc = JsonObjectGet(classDisc, IntToString(discipline)); + if (jDisc == JsonNull()) + jDisc = JsonObject(); + + string levelKey = "Level" + IntToString(level) + "_" + IntToString(type); + int nTypeTotal = (JsonGetInt(JsonObjectGet(jDisc, levelKey)) + 1); + jDisc = JsonObjectSet(jDisc, levelKey, JsonInt(nTypeTotal)); + + nTypeTotal = (JsonGetInt(JsonObjectGet(jDisc, IntToString(type))) + 1); + jDisc = JsonObjectSet(jDisc, IntToString(type), JsonInt(nTypeTotal)); + + if (type != MANEUVER_TYPE_MANEUVER + && type != MANEUVER_TYPE_STANCE) + { + levelKey = "Level" + IntToString(level) + "_" + IntToString(MANEUVER_TYPE_MANEUVER); + nTypeTotal = (JsonGetInt(JsonObjectGet(jDisc, levelKey)) + 1); + jDisc = JsonObjectSet(jDisc, levelKey, JsonInt(nTypeTotal)); + + nTypeTotal = (JsonGetInt(JsonObjectGet(jDisc, IntToString(MANEUVER_TYPE_MANEUVER))) + 1); + jDisc = JsonObjectSet(jDisc, IntToString(MANEUVER_TYPE_MANEUVER), JsonInt(nTypeTotal)); + } + + string prereqKey = "Prereq_" + IntToString(prereq); + int nPrereqTotal = (JsonGetInt(JsonObjectGet(jDisc, prereqKey)) + 1); + jDisc = JsonObjectSet(jDisc, prereqKey, JsonInt(nPrereqTotal)); + + if (type == MANEUVER_TYPE_STANCE) + { + nTypeTotal = (JsonGetInt(JsonObjectGet(classDisc, NUI_LEVEL_UP_STANCE_TOTAL)) + 1); + classDiscCopy = JsonObjectSet(classDiscCopy, NUI_LEVEL_UP_STANCE_TOTAL, JsonInt(nTypeTotal)); + } + else + { + nTypeTotal = (JsonGetInt(JsonObjectGet(classDisc, NUI_LEVEL_UP_MANEUVER_TOTAL)) + 1); + classDiscCopy = JsonObjectSet(classDiscCopy, NUI_LEVEL_UP_MANEUVER_TOTAL, JsonInt(nTypeTotal)); + } + + return JsonObjectSet(classDiscCopy, IntToString(discipline), jDisc); +} + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Invokers /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +json GetInvokerKnownListObject(int nClass, object oPC=OBJECT_SELF) +{ + json knownObject = GetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_INVOCATIONS_CACHE_VAR + IntToString(nClass)); + if (knownObject == JsonNull()) + knownObject = JsonObject(); + else + return knownObject; + + string sFile = GetAMSKnownFileName(nClass); + int totalRows = Get2DARowCount(sFile); + int maxInvocLevel = StringToInt(Get2DACache(sFile, "MaxInvocationLevel", totalRows-1)); + json previousInvocList = JsonObject(); + + int i; + for (i = 1; i <= maxInvocLevel; i++) + { + previousInvocList = JsonObjectSet(previousInvocList, IntToString(i), JsonInt(0)); + } + + for (i = 0; i < totalRows; i++) + { + int maxInvocation = StringToInt(Get2DACache(sFile, "MaxInvocationLevel", i)); + int invocationKnown = StringToInt(Get2DACache(sFile, "InvocationKnown", i)); + json invocList = previousInvocList; + + int previousInvocTotal = 0; + if (i > 0) + previousInvocTotal = StringToInt(Get2DACache(sFile, "InvocationKnown", i-1)); + int previousInvocAmount = JsonGetInt(JsonObjectGet(previousInvocList, IntToString(maxInvocation))); + int currentInvocationAmount = (invocationKnown - previousInvocTotal + previousInvocAmount); + + invocList = JsonObjectSet(invocList, IntToString(maxInvocation), JsonInt(currentInvocationAmount)); + knownObject = JsonObjectSet(knownObject, IntToString(i+1), invocList); + previousInvocList = invocList; + } + + SetLocalJson(oPC, NUI_LEVEL_UP_KNOWN_INVOCATIONS_CACHE_VAR + IntToString(nClass), knownObject); + return knownObject; +} + +int GetRemainingInvocationChoices(int nClass, int chosenCircle, object oPC=OBJECT_SELF, int extra=TRUE) +{ + int remaining = 0; + int nLevel = GetInvokerLevel(oPC, nClass); + + json knownObject = GetInvokerKnownListObject(nClass, oPC); + json chosenInv = GetChosenSpellListObject(nClass, oPC); + json currentLevelKnown = JsonObjectGet(knownObject, IntToString(nLevel)); + + int totalCircles = JsonGetLength(JsonObjectKeys(currentLevelKnown)); + + // logic goes we are given a set amount of invocations at each circle. We can + // take from a circle above us, but not below us. So we need to make sure + // we have a legal amount of choices + int i; + for (i = 1; i <= totalCircles; i++) + { + int currentChosen = 0; + json chosenSpells = JsonObjectGet(chosenInv, IntToString(i)); + if (chosenSpells != JsonNull()) + { + int totalChosen = JsonGetLength(chosenSpells); + int j; + for (j = 0; j < totalChosen; j++) + { + int spellbookId = JsonGetInt(JsonArrayGet(chosenSpells, j)); + // only count non extra invocation choices + if (!IsExtraChoiceInvocation(nClass, spellbookId, oPC)) + currentChosen += 1; + } + } + + int allowedAtCircle = JsonGetInt(JsonObjectGet(currentLevelKnown, IntToString(i))); + + remaining = (allowedAtCircle - currentChosen + remaining); + // if the circle is below the chosen circle and we have a positive remaining, + // we set it to 0 because we cannot use lower circle spells on higher circle. + // however if thge value is negative then we carry it over because we + // have a deficit and need to account for it by using the spells of the + // next circle. + if (i < chosenCircle && remaining > 0) + remaining = 0; + } + + + // count extra and epic invocation choices + if (extra) + { + string sFile = GetAMSKnownFileName(nClass); + int maxCircle = StringToInt(Get2DACache(sFile, "MaxInvocationLevel", nLevel-1)); + + if (GetHasFeat(FEAT_EXTRA_INVOCATION_I, oPC) && (chosenCircle <= (maxCircle-1))) + { + int totalExp = GetMaxInvocationCount(oPC, INVOCATION_LIST_EXTRA); + json expChoices = GetExpandedChoicesList(nClass, oPC); + int choicesCount = JsonGetLength(JsonObjectKeys(expChoices)); + remaining += (totalExp - choicesCount); + } + if (GetHasFeat(FEAT_EPIC_EXTRA_INVOCATION_I, oPC)) + { + int totalExp = GetMaxInvocationCount(oPC, INVOCATION_LIST_EXTRA_EPIC); + json expChoices = GetEpicExpandedChoicesList(nClass, oPC); + int choicesCount = JsonGetLength(JsonObjectKeys(expChoices)); + remaining += (totalExp - choicesCount); + } + } + + return remaining; +} + +int IsExtraChoiceInvocation(int nClass, int spellbookId, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + string spellId = Get2DACache(sFile, "SpellID", spellbookId); + json extraChoices = GetExpandedChoicesList(nClass, oPC); + json chosenSpell = JsonObjectGet(extraChoices, spellId); + if (chosenSpell != JsonNull()) + return TRUE; + + extraChoices = GetEpicExpandedChoicesList(nClass, oPC); + chosenSpell = JsonObjectGet(extraChoices, spellId); + if (chosenSpell != JsonNull()) + return TRUE; + + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Truenamer /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +int GetRemainingTruenameChoices(int nClass, int nType, object oPC=OBJECT_SELF) +{ + string sFile = GetClassSpellbookFile(nClass); + json chosenSpells = GetChosenSpellListObject(nClass, oPC); + json circles = JsonObjectKeys(chosenSpells); + int totalCircles = JsonGetLength(circles); + + int remainingChoices = 0; + + int i; + for (i = 0; i < totalCircles; i++) + { + json spellList = JsonObjectGet(chosenSpells, JsonGetString(JsonArrayGet(circles, i))); + if (spellList != JsonNull()) + { + int totalChoices = JsonGetLength(spellList); + + int j; + for (j = 0; j < totalChoices; j++) + { + int spellbookId = JsonGetInt(JsonArrayGet(spellList, j)); + int lexicon = StringToInt(Get2DACache(sFile, "Lexicon", spellbookId)); + // -1 means count all lexicons + if (nType == -1 || lexicon == nType) + remainingChoices += 1; + } + } + } + + int maxChoices; + // if -1 we count all lexicons to get total remaining + if (nType == -1) + maxChoices = (GetMaxUtteranceCount(oPC, nClass, LEXICON_CRAFTED_TOOL) + + GetMaxUtteranceCount(oPC, nClass, LEXICON_EVOLVING_MIND) + + GetMaxUtteranceCount(oPC, nClass, LEXICON_PERFECTED_MAP)); + else + maxChoices = GetMaxUtteranceCount(oPC, nClass, nType); + return (maxChoices - remainingChoices); +} + +int GetLexiconCircleKnownAtLevel(int nLevel, int nType) +{ + + string sFile = "cls_true_maxlvl"; + + string columnName; + if (nType == LEXICON_EVOLVING_MIND) + columnName = "EvolvingMind"; + else if (nType == LEXICON_CRAFTED_TOOL) + columnName = "CraftedTool"; + else + columnName = "PerfectedMap"; + + return StringToInt(Get2DACache(sFile, columnName, nLevel-1)); +} + +//////////////////////////////////////////////////////////////////////////// +/// /// +/// Archivist /// +/// /// +//////////////////////////////////////////////////////////////////////////// + +json GetArchivistNewSpellsList(object oPC=OBJECT_SELF) +{ + json retValue = GetLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR); + if (retValue == JsonNull()) + retValue = JsonArray(); + else + return retValue; + + SetLocalJson(oPC, NUI_LEVEL_UP_ARCHIVIST_NEW_SPELLS_LIST_VAR, retValue); + return retValue; +} diff --git a/nwn/nwnprc/trunk/include/prc_nui_sb_inc.nss b/nwn/nwnprc/trunk/include/prc_nui_sb_inc.nss index a1a768e2..409044fd 100644 --- a/nwn/nwnprc/trunk/include/prc_nui_sb_inc.nss +++ b/nwn/nwnprc/trunk/include/prc_nui_sb_inc.nss @@ -10,10 +10,8 @@ //:: Created By: Rakiov //:: Created On: 24.05.2005 //::////////////////////////////////////////////// -#include "inc_newspellbook" -#include "psi_inc_psifunc" -#include "inc_lookups" -#include "prc_nui_consts" + +#include "prc_nui_com_inc" // // GetSpellListForCircle @@ -43,69 +41,6 @@ json GetSpellListForCircle(object oPlayer, int nClass, int circle); // json GetSupportedNUISpellbookClasses(object oPlayer); -// -// GetCurrentSpellLevel -// Gets the current spell level the class can achieve at the current -// caster level (ranging from 0-9) -// -// Arguments: -// nClass:int the ClassID -// nLevel:int the caster level -// -// Returns: -// int the circle the class can achieve currently -// -int GetCurrentSpellLevel(int nClass, int nLevel); - -// -// GetMaxSpellLevel -// Gets the highest possible circle the class can achieve (from 0-9) -// -// Arguments: -// nClass:int the ClassID -// -// Returns: -// int the highest circle that can be achieved -// -int GetMaxSpellLevel(int nClass); - -// -// GetMinSpellLevel -// Gets the lowest possible circle the class can achieve (from 0-9) -// -// Arguments: -// nClass:int the ClassID -// -// Returns: -// int the lowest circle that can be achieved -// -int GetMinSpellLevel(int nClass); - -// -// GetHighestLevelPossibleInClass -// Given a class Id this will determine what the max level of a class can be -// achieved -// -// Arguments: -// nClass:int the ClassID -// -// Returns: -// int the highest possible level the class can achieve -// -int GetHighestLevelPossibleInClass(int nClass); - -// -// GetClassSpellbookFile -// Gets the class 2da spellbook/ability for the given class Id -// -// Arguments: -// nClass:int the classID -// -// Returns: -// string the 2da file name for the spell/abilities of the ClassID -// -string GetClassSpellbookFile(int nClass); - // // IsSpellKnown // Returns whether the player with the given class, spell file, and spellbook id @@ -234,23 +169,6 @@ json GetMetaMysteryFeatList(); // int GetTrueClassIfRHD(object oPlayer, int nClass); -// -// GetBinderSpellToFeatDictionary -// Sets up the Binder Spell Dictionary that is used to match a binder's vestige -// to their feat. This is constructed based off the binder's known location of -// their feat and spell ranges in the base 2das respectivly. After constructing -// this it will be saved to the player locally as a cached result since we do -// not need to call this again. -// -// Argument: -// oPlayer:object the player -// -// Returns: -// json:Dictionary a dictionary of mapping between the SpellID -// and the FeatID of a vestige ability -// -json GetBinderSpellToFeatDictionary(object oPlayer=OBJECT_SELF); - // // ShouldAddSpell // Given a spellId and a class, determines if the spell should be added to the @@ -318,6 +236,18 @@ json GetInvokerEssenceSpellList(int nClass, object oPlayer=OBJECT_SELF); // int JsonArrayContainsInt(json list, int item); +// +// IsSpellbookNUIOpen +// Checks to see if the Spellbook NUI is open on a given player. +// +// Arguments: +// oPC:object the player +// +// Returns: +// int:Boolean TRUE if window is open, FALSE otherwise +// +int IsSpellbookNUIOpen(object oPC); + json GetSpellListForCircle(object oPlayer, int nClass, int circle) { json retValue = JsonArray(); @@ -397,86 +327,6 @@ int ShouldAddSpell(int nClass, int spellId, object oPlayer=OBJECT_SELF) return TRUE; } -json GetBinderSpellToFeatDictionary(object oPlayer=OBJECT_SELF) -{ - // a dictionary of - json binderDict = GetLocalJson(oPlayer, NUI_SPELLBOOK_BINDER_DICTIONARY_CACHE_VAR); - // if this hasn't been created, create it now. - if (binderDict == JsonNull()) - binderDict = JsonObject(); - else - return binderDict; - - // the starting row for binder spells - int spellIndex = 19070; - // the starting row for binder feats - int featIndex = 9030; - //the end of the binder spells/feats - while (spellIndex <= 19156 && featIndex <= 9104) - { - // get the SpellID tied to the feat - int spellID = StringToInt(Get2DACache("feat", "SPELLID", featIndex)); - // if the spellID matches the current index, then this is the spell - // attached to the feat - if (spellID == spellIndex) - { - binderDict = JsonObjectSet(binderDict, IntToString(spellID), JsonInt(featIndex)); - - // move to next spell/feat - featIndex++; - spellIndex++; - } - // else we have reached a subdial spell - else - { - // loop through until we reach back at spellID - while (spellIndex < spellID) - { - int masterSpell = StringToInt(Get2DACache("spells", "Master", spellIndex)); - - // add the sub radial to the dict, tied to the master's FeatID - int featId = JsonGetInt(JsonObjectGet(binderDict, IntToString(masterSpell))); - binderDict = JsonObjectSet(binderDict, IntToString(spellIndex), JsonInt(featId)); - - spellIndex++; - } - - - // some feats overlap the same FeatID, can cause this to get stuck. - // if it happens then move on - if (spellIndex > spellID) - featIndex++; - } - } - - // cache the result - SetLocalJson(oPlayer, NUI_SPELLBOOK_BINDER_DICTIONARY_CACHE_VAR, binderDict); - return binderDict; -} - -string GetClassSpellbookFile(int nClass) -{ - string sFile; - // Spontaneous casters use a specific file name structure - if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS - || nClass == CLASS_TYPE_ARCHIVIST) - { - sFile = GetFileForClass(nClass); - } - // everyone else uses this structure - else - { - sFile = GetAMSDefinitionFileName(nClass); - - if (nClass == CLASS_TYPE_BINDER) - { - sFile = "vestiges"; - } - } - - return sFile; -} - json GetSupportedNUISpellbookClasses(object oPlayer) { json retValue = JsonArray(); @@ -526,167 +376,6 @@ int IsSpellKnown(object oPlayer, int nClass, int spellId) return FALSE; } -int GetCurrentSpellLevel(int nClass, int nLevel) -{ - int currentLevel = nLevel; - - // ToB doesn't have a concept of spell levels, but still match up to it - if(nClass == CLASS_TYPE_WARBLADE - || nClass == CLASS_TYPE_SWORDSAGE - || nClass == CLASS_TYPE_CRUSADER - || nClass == CLASS_TYPE_SHADOWCASTER) - { - return 9; - } - - - // Binders don't really have a concept of spell level - if (nClass == CLASS_TYPE_BINDER - || nClass == CLASS_TYPE_DRAGON_SHAMAN) // they can only reach 1st circle - return 1; - - //Shadowsmith has no concept of spell levels - if (nClass == CLASS_TYPE_SHADOWSMITH) - return 2; - - if (nClass == CLASS_TYPE_WARLOCK - || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT) - return 4; - - // Spont casters have their own function - if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS - || nClass == CLASS_TYPE_ARCHIVIST) - { - - int maxLevel = GetMaxSpellLevelForCasterLevel(nClass, currentLevel); - return maxLevel; - } - else - { - // everyone else uses this - string spellLevel2da = GetAMSKnownFileName(nClass); - - currentLevel = nLevel - 1; // Level is 1 off of the row in the 2da - - if (nClass == CLASS_TYPE_FIST_OF_ZUOKEN - || nClass == CLASS_TYPE_PSION - || nClass == CLASS_TYPE_PSYWAR - || nClass == CLASS_TYPE_WILDER - || nClass == CLASS_TYPE_PSYCHIC_ROGUE - || nClass == CLASS_TYPE_WARMIND) - currentLevel = GetManifesterLevel(OBJECT_SELF, nClass, TRUE) - 1; - - int totalLevel = Get2DARowCount(spellLevel2da); - - // in case we somehow go over bounds just don't :) - if (currentLevel >= totalLevel) - currentLevel = totalLevel - 1; - - //Psionics have MaxPowerLevel as their column name - string columnName = "MaxPowerLevel"; - - //Invokers have MaxInvocationLevel - if (nClass == CLASS_TYPE_WARLOCK - || nClass == CLASS_TYPE_DRAGON_SHAMAN - || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT) - columnName = "MaxInvocationLevel"; - - // Truenamers have 3 sets of utterances, ranging from 1-6, EvolvingMind covers the entire range - if (nClass == CLASS_TYPE_TRUENAMER) - { - columnName = "EvolvingMind"; - spellLevel2da = "cls_true_maxlvl"; //has a different 2da we want to look at - } - - if (nClass == CLASS_TYPE_BINDER) - { - columnName = "VestigeLvl"; - spellLevel2da = "cls_bind_binder"; - } - - // ToB doesn't have a concept of this, but we don't care. - - int maxLevel = StringToInt(Get2DACache(spellLevel2da, columnName, currentLevel)); - return maxLevel; - } -} - -int GetMinSpellLevel(int nClass) -{ - // again sponts have their own function - if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS - || nClass == CLASS_TYPE_ARCHIVIST) - { - return GetMinSpellLevelForCasterLevel(nClass, GetHighestLevelPossibleInClass(nClass)); - } - else - { - if (nClass == CLASS_TYPE_FIST_OF_ZUOKEN - || nClass == CLASS_TYPE_PSION - || nClass == CLASS_TYPE_PSYWAR - || nClass == CLASS_TYPE_WILDER - || nClass == CLASS_TYPE_PSYCHIC_ROGUE - || nClass == CLASS_TYPE_WARMIND - || nClass == CLASS_TYPE_WARBLADE - || nClass == CLASS_TYPE_SWORDSAGE - || nClass == CLASS_TYPE_CRUSADER - || nClass == CLASS_TYPE_WARLOCK - || nClass == CLASS_TYPE_DRAGONFIRE_ADEPT - || nClass == CLASS_TYPE_DRAGON_SHAMAN - || nClass == CLASS_TYPE_SHADOWCASTER - || nClass == CLASS_TYPE_SHADOWSMITH - || nClass == CLASS_TYPE_BINDER) - return 1; - - return GetCurrentSpellLevel(nClass, 1); - } - -} - -int GetMaxSpellLevel(int nClass) -{ - if (nClass == CLASS_TYPE_WILDER - || nClass == CLASS_TYPE_PSION) - return 9; - if (nClass == CLASS_TYPE_PSYCHIC_ROGUE - || nClass == CLASS_TYPE_FIST_OF_ZUOKEN - || nClass == CLASS_TYPE_WARMIND) - return 5; - if (nClass == CLASS_TYPE_PSYWAR) - return 6; - - return GetCurrentSpellLevel(nClass, GetHighestLevelPossibleInClass(nClass)); -} - -int GetHighestLevelPossibleInClass(int nClass) -{ - string sFile; - - //sponts have their spells in the classes.2da - if(GetSpellbookTypeForClass(nClass) == SPELLBOOK_TYPE_SPONTANEOUS - || nClass == CLASS_TYPE_ARCHIVIST) - { - sFile = Get2DACache("classes", "SpellGainTable", nClass); - } - else - { - // everyone else uses this - sFile = GetAMSKnownFileName(nClass); - - if (nClass == CLASS_TYPE_TRUENAMER) - { - sFile = "cls_true_maxlvl"; //has a different 2da we want to look at - } - - if (nClass == CLASS_TYPE_BINDER) - { - sFile = "cls_bind_binder"; - } - } - - return Get2DARowCount(sFile); -} - int IsClassAllowedToUseNUISpellbook(object oPlayer, int nClass) { // This controls who can use the Spellbook NUI, if for some reason you don't @@ -1145,4 +834,15 @@ int JsonArrayContainsInt(json list, int item) } return FALSE; -} \ No newline at end of file +} + +int IsSpellbookNUIOpen(object oPC) +{ + int nPreviousToken = NuiFindWindow(oPC, PRC_SPELLBOOK_NUI_WINDOW_ID); + if (nPreviousToken != 0) + { + return TRUE; + } + + return FALSE; +} diff --git a/nwn/nwnprc/trunk/include/shd_inc_mystknwn.nss b/nwn/nwnprc/trunk/include/shd_inc_mystknwn.nss index bc6ecc5b..b3354850 100644 --- a/nwn/nwnprc/trunk/include/shd_inc_mystknwn.nss +++ b/nwn/nwnprc/trunk/include/shd_inc_mystknwn.nss @@ -41,6 +41,7 @@ const string _MYSTERY_LIST_MISC_ARRAY = "_MysteriesKnownMiscArray"; const string _MYSTERY_LIST_LEVEL_ARRAY = "_MysteriesKnownLevelArray_"; const string _MYSTERY_LIST_GENERAL_ARRAY = "_MysteriesKnownGeneralArray"; +#include "shd_inc_shdfunc" ////////////////////////////////////////////////// /* Function prototypes */ diff --git a/nwn/nwnprc/trunk/include/x2_inc_spellhook.nss b/nwn/nwnprc/trunk/include/x2_inc_spellhook.nss index 27857899..c631c0e5 100644 --- a/nwn/nwnprc/trunk/include/x2_inc_spellhook.nss +++ b/nwn/nwnprc/trunk/include/x2_inc_spellhook.nss @@ -1441,8 +1441,9 @@ int CheckSecondaryPrC(object oPC = OBJECT_SELF) if (GetHasFeat(FEAT_WILDMAGE_SPELLCASTING_BARD)) return TRUE; if (GetHasFeat(FEAT_WWOC_SPELLCASTING_BARD)) return TRUE; } - else if (bBeguiler) + if (bBeguiler) { + if(DEBUG) DoDebug("x2_inc_spellhook: CheckSecondaryPrC >>> Entering Beguiler", oPC); if (GetHasFeat(FEAT_ABCHAMP_SPELLCASTING_BEGUILER)) return TRUE; if (GetHasFeat(FEAT_AOTS_SPELLCASTING_BEGUILER)) return TRUE; if (GetHasFeat(FEAT_ALCHEM_SPELLCASTING_BEGUILER)) return TRUE; @@ -1492,8 +1493,9 @@ int CheckSecondaryPrC(object oPC = OBJECT_SELF) } - else if (bDuskblade) + if (bDuskblade) { + if(DEBUG) DoDebug("x2_inc_spellhook: CheckSecondaryPrC >>> Entering Dusblade", oPC); if (GetHasFeat(FEAT_ABCHAMP_SPELLCASTING_DUSKBLADE)) return TRUE; if (GetHasFeat(FEAT_AOTS_SPELLCASTING_DUSKBLADE)) return TRUE; if (GetHasFeat(FEAT_ALCHEM_SPELLCASTING_DUSKBLADE)) return TRUE; @@ -1540,8 +1542,9 @@ int CheckSecondaryPrC(object oPC = OBJECT_SELF) } - else if (bSorcerer) + if (bSorcerer) { + if(DEBUG) DoDebug("x2_inc_spellhook: CheckSecondaryPrC >>> Entering Sorcerer", oPC); if (GetHasFeat(FEAT_ABERRATION_SPELLCASTING_DRIDER)) return TRUE; if (GetHasFeat(FEAT_MONSTROUS_SPELLCASTING_ARKAMOI)) return TRUE; if (GetHasFeat(FEAT_MONSTROUS_SPELLCASTING_MARRUTACT)) return TRUE; @@ -1599,8 +1602,9 @@ int CheckSecondaryPrC(object oPC = OBJECT_SELF) if (GetHasFeat(FEAT_WILDMAGE_SPELLCASTING_SORCERER)) return TRUE; if (GetHasFeat(FEAT_WWOC_SPELLCASTING_SORCERER)) return TRUE; } - else if (bWarmage) + if (bWarmage) { + if(DEBUG) DoDebug("x2_inc_spellhook: CheckSecondaryPrC >>> Entering Warmage", oPC); if (GetHasFeat(FEAT_AOTS_SPELLCASTING_WARMAGE)) return TRUE; if (GetHasFeat(FEAT_ALCHEM_SPELLCASTING_WARMAGE)) return TRUE; if (GetHasFeat(FEAT_ANIMA_SPELLCASTING_WARMAGE)) return TRUE; @@ -1662,14 +1666,71 @@ int BardSorcPrCCheck(object oCaster, int nCastingClass, object oSpellCastItem) return TRUE; } - //check its a sorc spell + //check its a sorcerer spell if(nCastingClass == CLASS_TYPE_SORCERER) { - if (CheckSecondaryPrC(oCaster) == TRUE) + if(DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> nCastingClass is Sorcerer.", oCaster); + //no need to check further if new spellbooks are disabled + if(GetPRCSwitch(PRC_SORC_DISALLOW_NEWSPELLBOOK)) { - if (DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> Sorcerer w/RHD found.", oCaster); + if (DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> PRC_SORC_DISALLOW_NEWSPELLBOOK.", oCaster); return TRUE; } + //check they have sorcerer levels + if(!GetLevelByClass(CLASS_TYPE_SORCERER, oCaster)) + { + if(DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> Not a sorcerer.", oCaster); + return TRUE; + } + //check if they are casting via new spellbook + if(GetLocalInt(oCaster, "NSB_Class") != CLASS_TYPE_SORCERER && GetLevelByClass(CLASS_TYPE_ULTIMATE_MAGUS, oCaster)) + { + if(DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> UltMagus using new spellbook.", oCaster); + return FALSE; + } + //check if they are casting via new spellbook + if(GetLocalInt(oCaster, "NSB_Class") == CLASS_TYPE_SORCERER) + { + if(DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> Using new spellbook.", oCaster); + return TRUE; + } + if(GetLevelByClass(CLASS_TYPE_SUBLIME_CHORD, oCaster) > 0 && CheckSecondaryPrC(oCaster) == TRUE) + { + if (DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> Sublime Chord w/RHD found.", oCaster); + FloatingTextStringOnCreature("You must use the new spellbook on the class radial.", oCaster, FALSE); + return FALSE; + } + if (CheckSecondaryPrC(oCaster) == TRUE) + { + if (DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> Sorcerer w/RHD found.", oCaster); + FloatingTextStringOnCreature("You must use the new spellbook on the class radial.", oCaster, FALSE); + return FALSE; + } + //check they have arcane PrC or Draconic Arcane Grace/Breath + if(!(GetArcanePRCLevels(oCaster, nCastingClass) - GetLevelByClass(CLASS_TYPE_SUBLIME_CHORD, oCaster)) + && !(GetHasFeat(FEAT_DRACONIC_GRACE, oCaster) || GetHasFeat(FEAT_DRACONIC_BREATH, oCaster))) + { + if(DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> First Sublime Chord check.", oCaster); + return TRUE; + } + + //check they have sorcerer in first arcane slot + //if(GetPrimaryArcaneClass() != CLASS_TYPE_SORCERER) + if(GetPrCAdjustedCasterLevelByType(TYPE_ARCANE, oCaster, TRUE) != GetPrCAdjustedCasterLevelByType(CLASS_TYPE_SORCERER, oCaster, TRUE)) + { + if(DEBUG) DoDebug("x2_inc_spellhook: BardSorcPrCCheck >>> GetPrCAdjustedCasterLevelByType.", oCaster); + return TRUE; + } + //at this point, they must be using the bioware spellbook + //from a class that adds to bard + FloatingTextStringOnCreature("You must use the new spellbook on the class radial.", oCaster, FALSE); + return FALSE; + } + + +/* //check its a sorc spell + if(nCastingClass == CLASS_TYPE_SORCERER) + { //no need to check further if new spellbooks are disabled if(GetPRCSwitch(PRC_SORC_DISALLOW_NEWSPELLBOOK)) return TRUE; @@ -1708,7 +1769,7 @@ int BardSorcPrCCheck(object oCaster, int nCastingClass, object oSpellCastItem) //from a class that adds to sorc FloatingTextStringOnCreature("You must use the new spellbook on the class radial.", oCaster, FALSE); return FALSE; - } + } */ //check its a bard spell if(nCastingClass == CLASS_TYPE_BARD) diff --git a/nwn/nwnprc/trunk/newspellbook/prc_amagsys_gain.nss b/nwn/nwnprc/trunk/newspellbook/prc_amagsys_gain.nss index e7e810f0..bf54bf19 100644 --- a/nwn/nwnprc/trunk/newspellbook/prc_amagsys_gain.nss +++ b/nwn/nwnprc/trunk/newspellbook/prc_amagsys_gain.nss @@ -31,6 +31,7 @@ #include "tob_inc_tobfunc" #include "shd_inc_shdfunc" #include "inv_inc_invfunc" +#include "prc_nui_lv_inc" ////////////////////////////////////////////////// /* Function prototypes */ @@ -77,7 +78,7 @@ void main() void CheckSpellbooks(object oPC) { - + if(GetLevelByClass(CLASS_TYPE_SUBLIME_CHORD, oPC) > 0) { CheckMissingSpells(oPC, CLASS_TYPE_SUBLIME_CHORD, 4, 9); @@ -87,7 +88,7 @@ void CheckSpellbooks(object oPC) CheckMissingSpells(oPC, CLASS_TYPE_BARD, 0, 3); } if(GetHasFeat(FEAT_SUBLIME_CHORD_SPELLCASTING_SORCERER)) - { + { CheckMissingSpells(oPC, CLASS_TYPE_SORCERER, 0, 3); } if(GetHasFeat(FEAT_SUBLIME_CHORD_SPELLCASTING_WARMAGE, oPC)) @@ -98,7 +99,7 @@ void CheckSpellbooks(object oPC) { CheckMissingSpells(oPC, CLASS_TYPE_DUSKBLADE, 0, 3); } - if(GetHasFeat(FEAT_SUBLIME_CHORD_SPELLCASTING_BEGUILER, oPC)) + if(GetHasFeat(FEAT_SUBLIME_CHORD_SPELLCASTING_BEGUILER, oPC)) { CheckMissingSpells(oPC, CLASS_TYPE_BEGUILER, 0, 3); } @@ -134,9 +135,9 @@ void CheckSpellbooks(object oPC) /* void CheckSpellbooks(object oPC) { - if(GetIsRHDSorcerer(oPC) && CheckMissingSpells(oPC, CLASS_TYPE_SORCERER, 0, 9)) + if(GetIsRHDSorcerer(oPC) && CheckMissingSpells(oPC, CLASS_TYPE_SORCERER, 0, 9)) return; - if(GetIsRHDBard(oPC) && CheckMissingSpells(oPC, CLASS_TYPE_BARD, 0, 6)) + if(GetIsRHDBard(oPC) && CheckMissingSpells(oPC, CLASS_TYPE_BARD, 0, 6)) return; if(!GetPRCSwitch(PRC_BARD_DISALLOW_NEWSPELLBOOK) && CheckMissingSpells(oPC, CLASS_TYPE_BARD, 0, 6)) return; @@ -159,7 +160,7 @@ void CheckSpellbooks(object oPC) if(CheckMissingSpells(oPC, CLASS_TYPE_JUSTICEWW, 1, 4)) return; if(CheckMissingSpells(oPC, CLASS_TYPE_KNIGHT_WEAVE, 1, 6)) - return; + return; // if(CheckMissingSpells(oPC, CLASS_TYPE_WITCH, 0, 9)) // return; if(CheckMissingSpells(oPC, CLASS_TYPE_SUBLIME_CHORD, 4, 9)) @@ -191,7 +192,7 @@ void CheckPsionics(object oPC) if(CheckMissingPowers(oPC, CLASS_TYPE_PSYWAR)) return; if(CheckMissingPowers(oPC, CLASS_TYPE_PSYCHIC_ROGUE)) - return; + return; if(CheckMissingPowers(oPC, CLASS_TYPE_FIST_OF_ZUOKEN)) return; if(CheckMissingPowers(oPC, CLASS_TYPE_WARMIND)) @@ -278,10 +279,15 @@ int CheckMissingPowers(object oPC, int nClass) if(nCurrentPowers < nMaxPowers) { + if (nClass <= 0) + nClass = GetPrimaryPsionicClass(oPC); + if (!IsLevelUpNUIOpen(oPC)) + OpenNUILevelUpWindow(nClass, oPC); + /* // Mark the class for which the PC is to gain powers and start the conversation SetLocalInt(oPC, "nClass", nClass); StartDynamicConversation("psi_powconv", oPC, DYNCONV_EXIT_NOT_ALLOWED, FALSE, TRUE, oPC); - + */ return TRUE; } return FALSE; @@ -304,9 +310,15 @@ int CheckMissingInvocations(object oPC, int nClass) if(nCurrentInvocations < nMaxInvocations) { + if (nClass == CLASS_TYPE_INVALID || nClass == -2) + nClass = GetPrimaryInvocationClass(oPC); + if (!IsLevelUpNUIOpen(oPC)) + OpenNUILevelUpWindow(nClass, oPC); + /* // Mark the class for which the PC is to gain invocations and start the conversation SetLocalInt(oPC, "nClass", nClass); StartDynamicConversation("inv_invokeconv", oPC, DYNCONV_EXIT_NOT_ALLOWED, FALSE, TRUE, oPC); + */ return TRUE; } @@ -358,49 +370,49 @@ void AddSpellsForLevel(int nClass, int nLevel) int CheckMissingSpells(object oPC, int nClass, int nMinLevel, int nMaxLevel) { int nLevel; - + //:: Rakshasa cast as sorcerers if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_RAKSHASA) nLevel = GetSpellslotLevel(nClass, oPC); //GetLevelByClass(CLASS_TYPE_OUTSIDER, oPC); -//:: Aranea cast as sorcerers - else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_ARANEA) +//:: Aranea cast as sorcerers + else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_ARANEA) nLevel = GetSpellslotLevel(nClass, oPC); //GetLevelByClass(CLASS_TYPE_SHAPECHANGER, oPC); -//::Arkamoi cast as sorcerers - else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_ARKAMOI) +//::Arkamoi cast as sorcerers + else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_ARKAMOI) nLevel = GetSpellslotLevel(nClass, oPC); //GetLevelByClass(CLASS_TYPE_MONSTROUS, oPC); - -//::Hobgoblin Warsouls cast as sorcerers - else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_HOBGOBLIN_WARSOUL) + +//::Hobgoblin Warsouls cast as sorcerers + else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_HOBGOBLIN_WARSOUL) nLevel = GetSpellslotLevel(nClass, oPC); //GetLevelByClass(CLASS_TYPE_MONSTROUS, oPC); - -//:: Driders cast as sorcerers - else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_DRIDER) + +//:: Driders cast as sorcerers + else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_DRIDER) nLevel = GetSpellslotLevel(nClass, oPC); //GetLevelByClass(CLASS_TYPE_ABERRATION, oPC); - + //:: Marrutact cast as 6/7 sorcerers - else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_MARRUTACT) + else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_MARRUTACT) nLevel = GetSpellslotLevel(nClass, oPC); //GetLevelByClass(CLASS_TYPE_MONSTROUS, oPC); - + //:: Redspawn Arcaniss cast as 3/4 sorcerers - else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_REDSPAWN_ARCANISS) + else if(nClass == CLASS_TYPE_SORCERER && !GetLevelByClass(CLASS_TYPE_SORCERER, oPC) && GetRacialType(oPC) == RACIAL_TYPE_REDSPAWN_ARCANISS) nLevel = GetSpellslotLevel(nClass, oPC); //GetLevelByClass(CLASS_TYPE_MONSTROUS, oPC); - -//:: Gloura cast as bards - else if(nClass == CLASS_TYPE_BARD && !GetLevelByClass(CLASS_TYPE_BARD, oPC) && GetRacialType(oPC) == RACIAL_TYPE_GLOURA) + +//:: Gloura cast as bards + else if(nClass == CLASS_TYPE_BARD && !GetLevelByClass(CLASS_TYPE_BARD, oPC) && GetRacialType(oPC) == RACIAL_TYPE_GLOURA) nLevel = GetSpellslotLevel(nClass, oPC); //GetLevelByClass(CLASS_TYPE_MONSTROUS, oPC); - - else + + else nLevel = nClass == CLASS_TYPE_SUBLIME_CHORD ? GetLevelByClass(nClass, oPC) : GetSpellslotLevel(nClass, oPC); if (DEBUG) DoDebug("CheckMissingSpells 1 Class: " + IntToString(nClass)); if (DEBUG) DoDebug("CheckMissingSpells 1 Level: " + IntToString(nLevel)); if(!nLevel) - return FALSE; - - if(nClass == CLASS_TYPE_BARD || nClass == CLASS_TYPE_SORCERER) + return FALSE; + + if(nClass == CLASS_TYPE_BARD || nClass == CLASS_TYPE_SORCERER) { if((GetLevelByClass(nClass, oPC) == nLevel) //no PrC && !(GetHasFeat(FEAT_DRACONIC_GRACE, oPC) || GetHasFeat(FEAT_DRACONIC_BREATH, oPC))) //no Draconic feats that apply @@ -411,36 +423,31 @@ int CheckMissingSpells(object oPC, int nClass, int nMinLevel, int nMaxLevel) int nLastGainLevel = GetPersistantLocalInt(oPC, "LastSpellGainLevel"); nLevel = GetLevelByClass(CLASS_TYPE_ARCHIVIST, oPC); + + //add cleric spells known for level 0 + if(persistant_array_get_size(oPC, "Spellbook_Known_"+IntToString(CLASS_TYPE_ARCHIVIST)+"_0") < 5) // TODO: replace with GetSpellKnownCurrentCount + { + ActionDoCommand(AddSpellsForLevel(CLASS_TYPE_ARCHIVIST, 0)); + } if(nLastGainLevel < nLevel) { - if(nLevel == 1) - { - //count the number of available at 1st level spells - int nSpellsAvailable = 3 + GetAbilityModifier(ABILITY_INTELLIGENCE, oPC); - SetLocalInt(oPC, "LrnLvlUp", nSpellsAvailable); - } - else if(nLevel > 1) - //add additional 2 spells form cleric list - SetLocalInt(oPC, "LrnLvlUp", 2); - + if (!IsLevelUpNUIOpen(oPC)) + OpenNUILevelUpWindow(nClass, oPC); + /* SetLocalInt(oPC, "SpellGainClass", CLASS_TYPE_ARCHIVIST); SetLocalInt(oPC, "SpellbookMinSpelllevel", nMinLevel); StartDynamicConversation("prc_s_spellgain", oPC, DYNCONV_EXIT_NOT_ALLOWED, TRUE, FALSE, oPC); + */ return TRUE; } - //add cleric spells known for level 0 - else if(persistant_array_get_size(oPC, "Spellbook_Known_"+IntToString(CLASS_TYPE_ARCHIVIST)+"_0") < 5) // TODO: replace with GetSpellKnownCurrentCount - { - ActionDoCommand(AddSpellsForLevel(CLASS_TYPE_ARCHIVIST, 0)); - } else return FALSE; } - + if (DEBUG) DoDebug("CheckMissingSpells 2 Class: " + IntToString(nClass)); if (DEBUG) DoDebug("CheckMissingSpells 2 Level: " + IntToString(nLevel)); - + int i; for(i = nMinLevel; i <= nMaxLevel; i++) { @@ -458,12 +465,15 @@ int CheckMissingSpells(object oPC, int nClass, int nMinLevel, int nMaxLevel) } else { + if (!IsLevelUpNUIOpen(oPC)) + OpenNUILevelUpWindow(nClass, oPC); + /* // Mark the class for which the PC is to gain powers and start the conversation SetLocalInt(oPC, "SpellGainClass", nClass); SetLocalInt(oPC, "SpellbookMinSpelllevel", nMinLevel); SetLocalInt(oPC, "SpellbookMaxSpelllevel", nMaxLevel); StartDynamicConversation("prc_s_spellgain", oPC, DYNCONV_EXIT_NOT_ALLOWED, FALSE, TRUE, oPC); - + */ return TRUE; } } @@ -474,20 +484,28 @@ int CheckMissingSpells(object oPC, int nClass, int nMinLevel, int nMaxLevel) int nALSpells = GetPersistantLocalInt(oPC, "AdvancedLearning_"+IntToString(nClass)); if(nClass == CLASS_TYPE_BEGUILER && nALSpells < (nLevel+1)/4)//one every 4 levels starting at 3. { + if (!IsLevelUpNUIOpen(oPC)) + OpenNUILevelUpWindow(nClass, oPC); + /* // Mark the class for which the PC is to gain powers and start the conversation SetLocalInt(oPC, "SpellGainClass", CLASS_TYPE_BEGUILER); SetLocalInt(oPC, "SpellbookMinSpelllevel", nMinLevel); SetLocalInt(oPC, "AdvancedLearning", 1); StartDynamicConversation("prc_s_spellgain", oPC, DYNCONV_EXIT_NOT_ALLOWED, FALSE, TRUE, oPC); + */ return TRUE; } else if(nClass == CLASS_TYPE_DREAD_NECROMANCER && nALSpells < nLevel/4)//one every 4 levels { + if (!IsLevelUpNUIOpen(oPC)) + OpenNUILevelUpWindow(nClass, oPC); + /* // Mark the class for which the PC is to gain powers and start the conversation SetLocalInt(oPC, "SpellGainClass", CLASS_TYPE_DREAD_NECROMANCER); SetLocalInt(oPC, "SpellbookMinSpelllevel", nMinLevel); SetLocalInt(oPC, "AdvancedLearning", 1); StartDynamicConversation("prc_s_spellgain", oPC, DYNCONV_EXIT_NOT_ALLOWED, FALSE, TRUE, oPC); + */ return TRUE; } else if(nClass == CLASS_TYPE_WARMAGE) @@ -502,11 +520,15 @@ int CheckMissingSpells(object oPC, int nClass, int nMinLevel, int nMaxLevel) (nLevel >= 6 && nLevel < 11 && nALSpells < 2) || (nLevel >= 3 && nLevel < 6 && nALSpells < 1)) { + if (!IsLevelUpNUIOpen(oPC)) + OpenNUILevelUpWindow(nClass, oPC); + /* // Mark the class for which the PC is to gain powers and start the conversation SetLocalInt(oPC, "SpellGainClass", CLASS_TYPE_WARMAGE); SetLocalInt(oPC, "SpellbookMinSpelllevel", nMinLevel); SetLocalInt(oPC, "AdvancedLearning", 1); StartDynamicConversation("prc_s_spellgain", oPC, DYNCONV_EXIT_NOT_ALLOWED, FALSE, TRUE, oPC); + */ return TRUE; } } @@ -535,9 +557,13 @@ int CheckMissingUtterances(object oPC, int nClass, int nLexicon) if(nCurrentUtterances < nMaxUtterances) { + if (!IsLevelUpNUIOpen(oPC)) + OpenNUILevelUpWindow(nClass, oPC); + /* // Mark the class for which the PC is to gain Utterances and start the conversation SetLocalInt(oPC, "nClass", nClass); StartDynamicConversation("true_utterconv", oPC, DYNCONV_EXIT_NOT_ALLOWED, FALSE, TRUE, oPC); + */ return TRUE; } @@ -557,9 +583,13 @@ int CheckMissingManeuvers(object oPC, int nClass) if(nCurrentManeuvers < nMaxManeuvers || nCurrentStances < nMaxStances) { + if (!IsLevelUpNUIOpen(oPC)) + OpenNUILevelUpWindow(nClass, oPC); + /* // Mark the class for which the PC is to gain powers and start the conversation SetLocalInt(oPC, "nClass", nClass); StartDynamicConversation("tob_moveconv", oPC, DYNCONV_EXIT_NOT_ALLOWED, FALSE, TRUE, oPC); + */ return TRUE; } @@ -577,10 +607,13 @@ int CheckMissingMysteries(object oPC, int nClass) if(nCurrentMysteries < nMaxMysteries) { + if (!IsLevelUpNUIOpen(oPC)) + OpenNUILevelUpWindow(nClass, oPC); + /* // Mark the class for which the PC is to gain powers and start the conversation SetLocalInt(oPC, "nClass", nClass); StartDynamicConversation("shd_mystconv", oPC, DYNCONV_EXIT_NOT_ALLOWED, FALSE, TRUE, oPC); - + */ return TRUE; } return FALSE; @@ -646,4 +679,4 @@ void AMSCompatibilityCheck(object oPC) } } } -} \ No newline at end of file +} diff --git a/nwn/nwnprc/trunk/newspellbook/tob_etbl_tacins.nss b/nwn/nwnprc/trunk/newspellbook/tob_etbl_tacins.nss index f1d9c0b6..9c90fe8e 100644 --- a/nwn/nwnprc/trunk/newspellbook/tob_etbl_tacins.nss +++ b/nwn/nwnprc/trunk/newspellbook/tob_etbl_tacins.nss @@ -7,7 +7,7 @@ 10 MAR 09 by GC */ /** @file - Although you may be young by the elve's reckoning, + Although you may be young by the elves' reckoning, your blade guide lends you the experience and wisdom of one who has fought battles across countless fields. @@ -53,7 +53,7 @@ void main() effect eAC; int nInt = GetAbilityModifier(ABILITY_INTELLIGENCE, oInitiator); if(nInt >= 1) eAC = EffectACDecrease(nInt); - PerformAttackRound(oTarget, oInitiator, eAC, 6.0, 0, 0, 0, FALSE, "", "", FALSE, FALSE, TRUE); + PerformAttackRound(oTarget, oInitiator, eAC, 6.0, 0, 0, 0, FALSE, "Tactical Insight: Hit!", "Tactical Insight: Miss!", FALSE, FALSE, TRUE); // Expend ability SetLocalInt(oInitiator, "ETBL_Tactical_Insight_Expended", TRUE); diff --git a/nwn/nwnprc/trunk/newspellbook/tob_eternalblade.nss b/nwn/nwnprc/trunk/newspellbook/tob_eternalblade.nss index 6f3ba980..4f15f8bd 100644 --- a/nwn/nwnprc/trunk/newspellbook/tob_eternalblade.nss +++ b/nwn/nwnprc/trunk/newspellbook/tob_eternalblade.nss @@ -79,6 +79,7 @@ void RemoveUncannyDodge(object oInitiator) DeleteLocalInt(oInitiator, "ETBL_AUD_Applied"); } } + void BladeGuide(object oInitiator, object oItem) { if (GetBaseItemType(oItem) == BASE_ITEM_ARMOR) @@ -176,11 +177,12 @@ void main() oItem = GetItemInSlot(INVENTORY_SLOT_CHEST, oInitiator); } - int nClass = CLASS_TYPE_ETERNAL_BLADE; - int nLevel = GetLevelByClass(CLASS_TYPE_ETERNAL_BLADE, oInitiator); - int nMoveTotal = GetKnownManeuversModifier(oInitiator, nClass, MANEUVER_TYPE_MANEUVER); - int nStncTotal = GetKnownManeuversModifier(oInitiator, nClass, MANEUVER_TYPE_STANCE); - int nRdyTotal = GetReadiedManeuversModifier(oInitiator, nClass); + int nClass = CLASS_TYPE_ETERNAL_BLADE; + int nLevel = GetLevelByClass(CLASS_TYPE_ETERNAL_BLADE, oInitiator); + int nBladeClass = GetPrimaryBladeMagicClass(oInitiator); + int nMoveTotal = GetKnownManeuversModifier(oInitiator, nBladeClass, MANEUVER_TYPE_MANEUVER); + int nStncTotal = GetKnownManeuversModifier(oInitiator, nBladeClass, MANEUVER_TYPE_STANCE); + int nRdyTotal = GetReadiedManeuversModifier(oInitiator, nBladeClass); if(DEBUG) DoDebug("tob_eternalblade running, event: " + IntToString(nEvent)); if(DEBUG) DoDebug("tob_eternalblade nMoveTotal: " + IntToString(nMoveTotal)); @@ -203,57 +205,109 @@ void main() // It's not pretty, but it works if (nLevel >= 1 && !GetPersistantLocalInt(oInitiator, "ToBEternalBlade1")) { - if(DEBUG) DoDebug("tob_eternalblade: Adding Maneuver 1"); - if(DEBUG) DoDebug("tob_eternalblade SetKnownManeuversModifier 1: " + IntToString(++nMoveTotal)); - DelayCommand(0.0f, SetKnownManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), ++nMoveTotal, MANEUVER_TYPE_MANEUVER)); + nMoveTotal += 1; + if(DEBUG) DoDebug("tob_eternalblade: Adding Maneuver 1"); + if(DEBUG) DoDebug("tob_eternalblade SetKnownManeuversModifier 1: " + IntToString(nMoveTotal)); + //DelayCommand(0.0f, SetKnownManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), nMoveTotal, MANEUVER_TYPE_MANEUVER)); + SetKnownManeuversModifier(oInitiator, nBladeClass, nMoveTotal, MANEUVER_TYPE_MANEUVER); SetPersistantLocalInt(oInitiator, "ToBEternalBlade1", TRUE); - SetPersistantLocalInt(oInitiator, "AllowedDisciplines", 270);//DISCIPLINE_DEVOTED_SPIRIT + DISCIPLINE_DIAMOND_MIND + DISCIPLINE_IRON_HEART + DISCIPLINE_WHITE_RAVEN - } + SetPersistantLocalInt(oInitiator, "AllowedDisciplines", DISCIPLINE_DEVOTED_SPIRIT + + DISCIPLINE_DIAMOND_MIND + + DISCIPLINE_IRON_HEART + + DISCIPLINE_WHITE_RAVEN); //:: (270) + + if (DEBUG) DoDebug("Granted Maneuver at Level 1: total now " + IntToString(nMoveTotal)); + if (DEBUG) DoDebug(IntToString(nStncTotal) +" stances known"); + if (DEBUG) DoDebug("Readied maneuvers now: " + IntToString(nRdyTotal)); + } if (nLevel >= 3 && !GetPersistantLocalInt(oInitiator, "ToBEternalBlade3")) { - if(DEBUG) DoDebug("tob_eternalblade: Adding Maneuver 3"); - if(DEBUG) DoDebug("tob_eternalblade SetKnownManeuversModifier 3: " + IntToString(++nMoveTotal)); - SetReadiedManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), ++nRdyTotal); - DelayCommand(0.0f, SetKnownManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), ++nMoveTotal, MANEUVER_TYPE_MANEUVER)); + nMoveTotal += 1; + nRdyTotal += 1; + if(DEBUG) DoDebug("tob_eternalblade: Adding Maneuver 3"); + if(DEBUG) DoDebug("tob_eternalblade SetKnownManeuversModifier 3: " + IntToString(nMoveTotal)); + SetReadiedManeuversModifier(oInitiator, nBladeClass, nRdyTotal); + //DelayCommand(0.0f, SetKnownManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), nMoveTotal, MANEUVER_TYPE_MANEUVER)); + SetKnownManeuversModifier(oInitiator, nBladeClass, nMoveTotal, MANEUVER_TYPE_MANEUVER); SetPersistantLocalInt(oInitiator, "ToBEternalBlade3", TRUE); - SetPersistantLocalInt(oInitiator, "AllowedDisciplines", 270);//DISCIPLINE_DEVOTED_SPIRIT + DISCIPLINE_DIAMOND_MIND + DISCIPLINE_IRON_HEART + DISCIPLINE_WHITE_RAVEN + SetPersistantLocalInt(oInitiator, "AllowedDisciplines", DISCIPLINE_DEVOTED_SPIRIT + + DISCIPLINE_DIAMOND_MIND + + DISCIPLINE_IRON_HEART + + DISCIPLINE_WHITE_RAVEN); //:: (270) + + if (DEBUG) DoDebug("Granted Maneuver at Level 3: total now " + IntToString(nMoveTotal)); + if (DEBUG) DoDebug(IntToString(nStncTotal) +" stances known"); + if (DEBUG) DoDebug("Readied maneuvers now: " + IntToString(nRdyTotal)); } if (nLevel >= 5 && !GetPersistantLocalInt(oInitiator, "ToBEternalBlade5")) { - if(DEBUG) DoDebug("tob_eternalblade: Adding Maneuver 5"); + nStncTotal += 1; + nMoveTotal += 1; + if(DEBUG) DoDebug("tob_eternalblade: Adding Maneuver 5"); if(DEBUG) DoDebug("tob_eternalblade SetKnownManeuversModifier 5: " + IntToString(++nMoveTotal)); - DelayCommand(0.0f, SetKnownManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), ++nStncTotal, MANEUVER_TYPE_STANCE)); - DelayCommand(0.0f, SetKnownManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), ++nMoveTotal, MANEUVER_TYPE_MANEUVER)); + //DelayCommand(0.0f, SetKnownManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), nStncTotal, MANEUVER_TYPE_STANCE)); + //DelayCommand(0.0f, SetKnownManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), nMoveTotal, MANEUVER_TYPE_MANEUVER)); + SetKnownManeuversModifier(oInitiator, nBladeClass, nStncTotal, MANEUVER_TYPE_STANCE); + SetKnownManeuversModifier(oInitiator, nBladeClass, nMoveTotal, MANEUVER_TYPE_MANEUVER); SetPersistantLocalInt(oInitiator, "ToBEternalBlade5", TRUE); - SetPersistantLocalInt(oInitiator, "AllowedDisciplines", 270);//DISCIPLINE_DEVOTED_SPIRIT + DISCIPLINE_DIAMOND_MIND + DISCIPLINE_IRON_HEART + DISCIPLINE_WHITE_RAVEN + SetPersistantLocalInt(oInitiator, "AllowedDisciplines", DISCIPLINE_DEVOTED_SPIRIT + + DISCIPLINE_DIAMOND_MIND + + DISCIPLINE_IRON_HEART + + DISCIPLINE_WHITE_RAVEN); //:: (270) + + if (DEBUG) DoDebug("Granted Maneuver at Level 5: total now " + IntToString(nMoveTotal)); + if (DEBUG) DoDebug(IntToString(nStncTotal) +" stances known"); + if (DEBUG) DoDebug("Readied maneuvers now: " + IntToString(nRdyTotal)); } if (nLevel >= 6 && !GetPersistantLocalInt(oInitiator, "ToBEternalBlade6")) { - if(DEBUG) DoDebug("tob_eternalblade: Adding Maneuver 6"); - SetReadiedManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), ++nRdyTotal); + nRdyTotal += 1; + if(DEBUG) DoDebug("tob_eternalblade: Adding Maneuver 6"); + SetReadiedManeuversModifier(oInitiator, nBladeClass, nRdyTotal); SetPersistantLocalInt(oInitiator, "ToBEternalBlade6", TRUE); + + if (DEBUG) DoDebug("Granted Maneuver at Level 6: total now " + IntToString(nMoveTotal)); + if (DEBUG) DoDebug(IntToString(nStncTotal) +" stances known"); + if (DEBUG) DoDebug("Readied maneuvers now: " + IntToString(nRdyTotal)); } if (nLevel >= 7 && !GetPersistantLocalInt(oInitiator, "ToBEternalBlade7")) { - if(DEBUG) DoDebug("tob_eternalblade: Adding Maneuver 7"); - if(DEBUG) DoDebug("tob_eternalblade SetKnownManeuversModifier 7: " + IntToString(++nMoveTotal)); - DelayCommand(0.0f, SetKnownManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), ++nMoveTotal, MANEUVER_TYPE_MANEUVER)); + nMoveTotal += 1; + if(DEBUG) DoDebug("tob_eternalblade: Adding Maneuver 7"); + if(DEBUG) DoDebug("tob_eternalblade SetKnownManeuversModifier 7: " + IntToString(nMoveTotal)); + DelayCommand(0.0f, SetKnownManeuversModifier(oInitiator, nBladeClass, nMoveTotal, MANEUVER_TYPE_MANEUVER)); SetPersistantLocalInt(oInitiator, "ToBEternalBlade7", TRUE); - SetPersistantLocalInt(oInitiator, "AllowedDisciplines", 270);//DISCIPLINE_DEVOTED_SPIRIT + DISCIPLINE_DIAMOND_MIND + DISCIPLINE_IRON_HEART + DISCIPLINE_WHITE_RAVEN + SetPersistantLocalInt(oInitiator, "AllowedDisciplines", DISCIPLINE_DEVOTED_SPIRIT + + DISCIPLINE_DIAMOND_MIND + + DISCIPLINE_IRON_HEART + + DISCIPLINE_WHITE_RAVEN); //:: (270) + + if (DEBUG) DoDebug("Granted Maneuver at Level 7: total now " + IntToString(nMoveTotal)); + if (DEBUG) DoDebug(IntToString(nStncTotal) +" stances known"); + if (DEBUG) DoDebug("Readied maneuvers now: " + IntToString(nRdyTotal)); } if (nLevel >= 9 && !GetPersistantLocalInt(oInitiator, "ToBEternalBlade9")) { - if(DEBUG) DoDebug("tob_eternalblade: Adding Maneuver 9"); - if(DEBUG) DoDebug("tob_eternalblade SetKnownManeuversModifier 9: " + IntToString(++nMoveTotal)); - SetReadiedManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), ++nRdyTotal); - DelayCommand(0.0f, SetKnownManeuversModifier(oInitiator, GetPrimaryBladeMagicClass(oInitiator), ++nMoveTotal, MANEUVER_TYPE_MANEUVER)); + nMoveTotal += 1; + nRdyTotal += 1; + if(DEBUG) DoDebug("tob_eternalblade: Adding Maneuver 9"); + if(DEBUG) DoDebug("tob_eternalblade SetKnownManeuversModifier 9: " + IntToString(nMoveTotal)); + SetReadiedManeuversModifier(oInitiator, nBladeClass, nRdyTotal); + DelayCommand(0.0f, SetKnownManeuversModifier(oInitiator, nBladeClass, nMoveTotal, MANEUVER_TYPE_MANEUVER)); SetPersistantLocalInt(oInitiator, "ToBEternalBlade9", TRUE); - SetPersistantLocalInt(oInitiator, "AllowedDisciplines", 270);//DISCIPLINE_DEVOTED_SPIRIT + DISCIPLINE_DIAMOND_MIND + DISCIPLINE_IRON_HEART + DISCIPLINE_WHITE_RAVEN + SetPersistantLocalInt(oInitiator, "AllowedDisciplines", DISCIPLINE_DEVOTED_SPIRIT + + DISCIPLINE_DIAMOND_MIND + + DISCIPLINE_IRON_HEART + + DISCIPLINE_WHITE_RAVEN); //:: (270) + + if (DEBUG) DoDebug("Granted Maneuver at Level 9: total now " + IntToString(nMoveTotal)); + if (DEBUG) DoDebug(IntToString(nStncTotal) +" stances known"); + if (DEBUG) DoDebug("Readied maneuvers now: " + IntToString(nRdyTotal)); } // Hook to OnLevelDown to remove the maneuver slots granted here diff --git a/nwn/nwnprc/trunk/newspellbook/tob_rubyknight.nss b/nwn/nwnprc/trunk/newspellbook/tob_rubyknight.nss index 279f9177..741f62b0 100644 --- a/nwn/nwnprc/trunk/newspellbook/tob_rubyknight.nss +++ b/nwn/nwnprc/trunk/newspellbook/tob_rubyknight.nss @@ -28,9 +28,9 @@ void main() int nStncTotal = GetKnownManeuversModifier(oInitiator, nClass, MANEUVER_TYPE_STANCE); int nRdyTotal = GetReadiedManeuversModifier(oInitiator, nClass); -DoDebug("nMoveTotal = "+IntToString(nMoveTotal)); -DoDebug("nStncTotal = "+IntToString(nStncTotal)); -DoDebug("nRdyTotal = "+IntToString(nRdyTotal)); + if (DEBUG) DoDebug("nMoveTotal = "+IntToString(nMoveTotal)); + if (DEBUG) DoDebug("nStncTotal = "+IntToString(nStncTotal)); + if (DEBUG) DoDebug("nRdyTotal = "+IntToString(nRdyTotal)); int nRubyLvl = GetLevelByClass(CLASS_TYPE_RUBY_VINDICATOR, oInitiator); int nRubyBonusMove = nRubyLvl / 2; @@ -38,9 +38,9 @@ DoDebug("nRdyTotal = "+IntToString(nRdyTotal)); if (nRubyLvl >= 6) nRubyBonusStance = 2; int nRubyBonusReady = nRubyLvl > 8 ? 2 : nRubyLvl > 4 ? 1 : 0; int nMod; -DoDebug("nRubyBonusMove = "+IntToString(nRubyBonusMove)); -DoDebug("nRubyBonusStance = "+IntToString(nRubyBonusStance)); -DoDebug("nRubyBonusReady = "+IntToString(nRubyBonusReady)); + if (DEBUG) DoDebug("nRubyBonusMove = "+IntToString(nRubyBonusMove)); + if (DEBUG) DoDebug("nRubyBonusStance = "+IntToString(nRubyBonusStance)); + if (DEBUG) DoDebug("nRubyBonusReady = "+IntToString(nRubyBonusReady)); // We aren't being called from any event, instead from EvalPRCFeats if(nEvent == FALSE) diff --git a/nwn/nwnprc/trunk/scripts/ft_vowpoverty_ft.nss b/nwn/nwnprc/trunk/scripts/ft_vowpoverty_ft.nss index f13cfa03..e64d7c76 100644 --- a/nwn/nwnprc/trunk/scripts/ft_vowpoverty_ft.nss +++ b/nwn/nwnprc/trunk/scripts/ft_vowpoverty_ft.nss @@ -85,11 +85,11 @@ void main() if(nPreReq1>0 && !GetHasFeat(nPreReq1, oPC)) nAllPreReq = 0; if(nPreReq1==213 && GetLevelByClass(CLASS_TYPE_MONK, oPC) >= 1) nAllPreReq = 1; //for Ki Strike and monks if(nPreReq2>0 && !GetHasFeat(nPreReq2, oPC)) nAllPreReq = 0; - if(nCon>0 && !GetAbilityScore(oPC,ABILITY_CONSTITUTION) >= nCon) nAllPreReq = 0; - if(nWis>0 && !GetAbilityScore(oPC,ABILITY_CONSTITUTION) >= nWis) nAllPreReq = 0; - if(nCha>0 && !GetAbilityScore(oPC,ABILITY_CONSTITUTION) >= nCha) nAllPreReq = 0; - if(nBAB>0 && !GetBaseAttackBonus(oPC) >= nBAB) nAllPreReq = 0; - if(nLaw>0 && !GetAlignmentLawChaos(oPC) == ALIGNMENT_LAWFUL) nAllPreReq = 0; + if(nCon>0 && GetAbilityScore(oPC,ABILITY_CONSTITUTION, TRUE) < nCon) nAllPreReq = 0; + if(nWis>0 && GetAbilityScore(oPC,ABILITY_WISDOM, TRUE) < nWis) nAllPreReq = 0; + if(nCha>0 && GetAbilityScore(oPC,ABILITY_CHARISMA, TRUE) < nCha) nAllPreReq = 0; + if(nBAB>0 && GetBaseAttackBonus(oPC) < nBAB) nAllPreReq = 0; + if(nLaw>0 && !(GetAlignmentLawChaos(oPC) == ALIGNMENT_LAWFUL)) nAllPreReq = 0; if (!GetHasFeat(nFeat, oPC) && !GetLocalInt(oPC, "VoPFeat"+IntToString(nFeat)) && nAllPreReq == 1) AddChoice(sName, nFeat, oPC); } @@ -122,6 +122,9 @@ void main() { SetPersistantLocalInt(oPC, "VoPFeat"+IntToString(nLevel),1); //Register the choice was made + // Store the feat ID for recalling onClientEnter + SetPersistantLocalInt(oPC, "VoPFeatID" + IntToString(nChoice), 1); + //If it was not cancelled, give chosen feat to PC (as EffectBonusFeat) if(nChoice > 0) { diff --git a/nwn/nwnprc/trunk/scripts/prc_nui_dsc_evnt.nss b/nwn/nwnprc/trunk/scripts/prc_nui_dsc_evnt.nss new file mode 100644 index 00000000..115c7edb --- /dev/null +++ b/nwn/nwnprc/trunk/scripts/prc_nui_dsc_evnt.nss @@ -0,0 +1,27 @@ +//:://///////////////////////////////////////////// +//:: PRC Spell Description NUI Events +//:: prc_nui_dsc_evnt +//::////////////////////////////////////////////// +/* + This is the event logic used for the spell description nui +*/ +//::////////////////////////////////////////////// +//:: Created By: Rakiov +//:: Created On: 20.06.2005 +//::////////////////////////////////////////////// + +#include "prc_nui_consts" + +void main() +{ + object oPlayer = NuiGetEventPlayer(); + int nToken = NuiGetEventWindow(); + string sEvent = NuiGetEventType(); + string sElement = NuiGetEventElement(); + string sWindowId = NuiGetWindowId(oPlayer, nToken); + + // if the OK button was clicked then we close the window. + if (sElement == NUI_SPELL_DESCRIPTION_OK_BUTTON) + DelayCommand(0.1f, NuiDestroy(oPlayer, nToken)); +} + diff --git a/nwn/nwnprc/trunk/scripts/prc_nui_dsc_view.nss b/nwn/nwnprc/trunk/scripts/prc_nui_dsc_view.nss new file mode 100644 index 00000000..64186d13 --- /dev/null +++ b/nwn/nwnprc/trunk/scripts/prc_nui_dsc_view.nss @@ -0,0 +1,90 @@ +//:://///////////////////////////////////////////// +//:: PRC Spellbook Description NUI +//:: prc_nui_dsc_view +//::////////////////////////////////////////////// +/* + This is the view for the Spell Description NUI +*/ +//::////////////////////////////////////////////// +//:: Created By: Rakiov +//:: Created On: 29.05.2005 +//::////////////////////////////////////////////// + +#include "nw_inc_nui" +#include "prc_nui_consts" +#include "inc_2dacache" +#include "prc_nui_com_inc" + +void main() +{ + int featID = GetLocalInt(OBJECT_SELF, NUI_SPELL_DESCRIPTION_FEATID_VAR); + int spellId = GetLocalInt(OBJECT_SELF, NUI_SPELL_DESCRIPTION_SPELLID_VAR); + int realSpellId = GetLocalInt(OBJECT_SELF, NUI_SPELL_DESCRIPTION_REAL_SPELLID_VAR); + int nClass = GetLocalInt(OBJECT_SELF, NUI_SPELL_DESCRIPTION_CLASSID_VAR); + + // look for existing window and destroy + int nPreviousToken = NuiFindWindow(OBJECT_SELF, NUI_SPELL_DESCRIPTION_WINDOW_ID); + if(nPreviousToken != 0) + { + NuiDestroy(OBJECT_SELF, nPreviousToken); + } + + // in order of accuracy for names it goes RealSpellID > SpellID > FeatID + string spellName = GetSpellName(spellId, realSpellId, featID, nClass); + // Descriptions and Icons are accuratly stored on the feat + string spellDesc = GetStringByStrRef(StringToInt(Get2DACache("feat", "DESCRIPTION", featID))); + string spellIcon = Get2DACache("feat", "ICON", featID); + + json jRoot = JsonArray(); + json jGroup = JsonArray(); + + json jRow = JsonArray(); + + json jImage = NuiImage(JsonString(spellIcon), JsonInt(NUI_ASPECT_EXACT), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_TOP)); + jImage = NuiWidth(jImage, 32.0f); + jRow = JsonArrayInsert(jRow, jImage); + jRow = NuiCol(jRow); + jGroup = JsonArrayInsert(jGroup, jRow); + + jRow = JsonArray(); + json jText = NuiText(JsonString(spellDesc), FALSE, NUI_SCROLLBARS_AUTO); + jRow = JsonArrayInsert(jRow, jText); + jRow = NuiCol(jRow); + jGroup = JsonArrayInsert(jGroup, jRow); + + jGroup = NuiRow(jGroup); + jGroup = NuiGroup(jGroup, TRUE, NUI_SCROLLBARS_NONE); + jRoot = JsonArrayInsert(jRoot, jGroup); + + jRow = JsonArray(); + jRow = JsonArrayInsert(jRow, NuiSpacer()); + json jButton = NuiId(NuiButton(JsonString("OK")), NUI_SPELL_DESCRIPTION_OK_BUTTON); + jButton = NuiWidth(jButton, 175.0f); + jButton = NuiHeight(jButton, 48.0f); + jRow = JsonArrayInsert(jRow, jButton); + jRow = NuiRow(jRow); + + jRoot = JsonArrayInsert(jRoot, jRow); + jRoot = NuiCol(jRoot); + + + // This is the main window with jRoot as the main pane. It includes titles and parameters (more on those later) + json nui = NuiWindow(jRoot, JsonString(spellName), NuiBind("geometry"), NuiBind("resizable"), JsonBool(FALSE), NuiBind("closable"), NuiBind("transparent"), NuiBind("border")); + + // finally create it and it'll return us a non-zero token. + int nToken = NuiCreate(OBJECT_SELF, nui, NUI_SPELL_DESCRIPTION_WINDOW_ID); + + // get the geometry of the window in case we opened this before and have a + // preference for location + json geometry = NuiRect(-1.0f,-1.0f, 426.0f, 446.0f); + + // Set the binds to their default values + NuiSetBind(OBJECT_SELF, nToken, "geometry", geometry); + NuiSetBind(OBJECT_SELF, nToken, "resizable", JsonBool(FALSE)); + NuiSetBind(OBJECT_SELF, nToken, "closable", JsonBool(FALSE)); + NuiSetBind(OBJECT_SELF, nToken, "transparent", JsonBool(FALSE)); + NuiSetBind(OBJECT_SELF, nToken, "border", JsonBool(TRUE)); + + ClearSpellDescriptionNUI(); +} + diff --git a/nwn/nwnprc/trunk/scripts/prc_nui_lv_event.nss b/nwn/nwnprc/trunk/scripts/prc_nui_lv_event.nss new file mode 100644 index 00000000..e86920a8 --- /dev/null +++ b/nwn/nwnprc/trunk/scripts/prc_nui_lv_event.nss @@ -0,0 +1,158 @@ +//:://///////////////////////////////////////////// +//:: PRC Level Up NUI Events +//:: prc_nui_lv_event +//::////////////////////////////////////////////// +/* + This is the logic that handles the events for the Level Up NUI +*/ +//::////////////////////////////////////////////// +//:: Created By: Rakiov +//:: Created On: 20.06.2005 +//::////////////////////////////////////////////// + +#include "prc_nui_lv_inc" + +void SetupAndCallSpellDescriptionNUI(object oPlayer, int spellbookId, int classId) +{ + string sFile = GetClassSpellbookFile(classId); + int featId = StringToInt(Get2DACache(sFile, "FeatID", spellbookId)); + int spellId = StringToInt(Get2DACache(sFile, "SpellID", spellbookId)); + int realSpellId = StringToInt(Get2DACache(sFile, "RealSpellID", spellbookId)); + + CreateSpellDescriptionNUI(oPlayer, featId, spellId, realSpellId, classId); +} + +void main() +{ + object oPlayer = NuiGetEventPlayer(); + int nToken = NuiGetEventWindow(); + string sEvent = NuiGetEventType(); + string sElement = NuiGetEventElement(); + string sWindowId = NuiGetWindowId(oPlayer, nToken); + + // Not a mouseup event, nothing to do. + if (sEvent != "mouseup") + { + return; + } + + // we clicked a spell circle, set the selected circle to the new one and refresh the window + if (FindSubString(sElement, NUI_LEVEL_UP_SPELL_CIRCLE_BUTTON_BASEID) >= 0) + { + int selectedCircle = StringToInt(RegExpReplace(NUI_LEVEL_UP_SPELL_CIRCLE_BUTTON_BASEID, sElement, "")); + SetLocalInt(oPlayer, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR, selectedCircle); + CloseNUILevelUpWindow(oPlayer); + ExecuteScript("prc_nui_lv_view", oPlayer); + return; + } + + json jPayload = NuiGetEventPayload(); + int nButton = JsonGetInt(JsonObjectGet(jPayload, "mouse_btn")); + + if (FindSubString(sElement, NUI_LEVEL_UP_SPELL_DISABLED_BUTTON_BASEID) >= 0) + { + int spellbookId = StringToInt(RegExpReplace(NUI_LEVEL_UP_SPELL_DISABLED_BUTTON_BASEID, sElement, "")); + int classId = GetLocalInt(oPlayer, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + + // we right clicked a disabled button, tell what the spell is + if (nButton == NUI_PAYLOAD_BUTTON_RIGHT_CLICK) + { + SetupAndCallSpellDescriptionNUI(oPlayer, spellbookId, classId); + return; + } + // we left clicked a disabled button, tell why it was disables + if (nButton == NUI_PAYLOAD_BUTTON_LEFT_CLICK) + { + SendMessageToPC(oPlayer, ReasonForDisabledSpell(classId, spellbookId, oPlayer)); + return; + } + } + + if (FindSubString(sElement, NUI_LEVEL_UP_SPELL_CHOSEN_DISABLED_BUTTON_BASEID) >= 0) + { + int spellbookId = StringToInt(RegExpReplace(NUI_LEVEL_UP_SPELL_CHOSEN_DISABLED_BUTTON_BASEID, sElement, "")); + int classId = GetLocalInt(oPlayer, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + + // we right clicked a disabled button, tell what the spell is + if (nButton == NUI_PAYLOAD_BUTTON_RIGHT_CLICK) + { + SetupAndCallSpellDescriptionNUI(oPlayer, spellbookId, classId); + return; + } + // we left clicked a disabled button, tell why it was disabled + if (nButton == NUI_PAYLOAD_BUTTON_LEFT_CLICK) + { + SendMessageToPC(oPlayer, ReasonForDisabledChosen(classId, spellbookId, oPlayer)); + return; + } + } + + if (FindSubString(sElement, NUI_LEVEL_UP_SPELL_BUTTON_BASEID) >= 0) + { + int spellbookId = StringToInt(RegExpReplace(NUI_LEVEL_UP_SPELL_BUTTON_BASEID, sElement, "")); + int classId = GetLocalInt(oPlayer, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + + // If right click, open the Spell Description NUI + if (nButton == NUI_PAYLOAD_BUTTON_RIGHT_CLICK) + { + SetupAndCallSpellDescriptionNUI(oPlayer, spellbookId, classId); + return; + } + // we are adding the spell to the chosen spell list then refreshing the window + if (nButton == NUI_PAYLOAD_BUTTON_LEFT_CLICK) + { + int chosenCircle = GetLocalInt(oPlayer, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR); + + AddSpellToChosenList(classId, spellbookId, chosenCircle, oPlayer); + CloseNUILevelUpWindow(oPlayer); + ExecuteScript("prc_nui_lv_view", oPlayer); + } + return; + } + + if (FindSubString(sElement, NUI_LEVEL_UP_SPELL_CHOSEN_BUTTON_BASEID) >= 0) + { + int spellbookId = StringToInt(RegExpReplace(NUI_LEVEL_UP_SPELL_CHOSEN_BUTTON_BASEID, sElement, "")); + int classId = GetLocalInt(oPlayer, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + + + json jPayload = NuiGetEventPayload(); + int nButton = JsonGetInt(JsonObjectGet(jPayload, "mouse_btn")); + + // If right click, open the Spell Description NUI + if (nButton == NUI_PAYLOAD_BUTTON_RIGHT_CLICK) + { + SetupAndCallSpellDescriptionNUI(oPlayer, spellbookId, classId); + return; + } + // we are removing a chosen spell from the chosen list then refreshing the window + if (nButton == NUI_PAYLOAD_BUTTON_LEFT_CLICK) + { + int chosenCircle = GetLocalInt(oPlayer, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR); + + RemoveSpellFromChosenList(classId, spellbookId, chosenCircle, oPlayer); + CloseNUILevelUpWindow(oPlayer); + ExecuteScript("prc_nui_lv_view", oPlayer); + } + return; + } + // we are resetting our choices + if (sElement == NUI_LEVEL_UP_RESET_BUTTON) + { + ResetChoices(oPlayer); + CloseNUILevelUpWindow(oPlayer); + ExecuteScript("prc_nui_lv_view", oPlayer); + return; + } + // finalize all our choices + if (sElement == NUI_LEVEL_UP_DONE_BUTTON) + { + int classId = GetLocalInt(oPlayer, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + FinishLevelUp(classId, oPlayer); + CloseNUILevelUpWindow(oPlayer); + ExecuteScript("prc_amagsys_gain", oPlayer); + DelayCommand(0.2f, ExecuteScript("prc_nui_sb_view", oPlayer)); + return; + } +} + diff --git a/nwn/nwnprc/trunk/scripts/prc_nui_lv_view.nss b/nwn/nwnprc/trunk/scripts/prc_nui_lv_view.nss new file mode 100644 index 00000000..3f906723 --- /dev/null +++ b/nwn/nwnprc/trunk/scripts/prc_nui_lv_view.nss @@ -0,0 +1,353 @@ +//:://///////////////////////////////////////////// +//:: PRC Level Up NUI View +//:: prc_nui_lv_view +//::////////////////////////////////////////////// +/* + This is the logic that handles the view for the Level Up NUI +*/ +//::////////////////////////////////////////////// +//:: Created By: Rakiov +//:: Created On: 20.06.2005 +//::////////////////////////////////////////////// + +#include "nw_inc_nui" +#include "prc_nui_consts" +#include "prc_inc_function" +#include "prc_nui_lv_inc" + +// +// CreateCircleButtons +// Creates the spell circle buttons for the given class Id +// +// Arguments: +// nClass:int the ClassID +// +// Returns: +// json the NUI spell circle buttons +// +json CreateCircleButtons(int nClass) +{ + json jRow = JsonArray(); + // get the spell list for the class + json spellList = GetSpellListObject(nClass); + + // get the keys which are circles + json circleList = JsonObjectKeys(spellList); + int totalCircles = JsonGetLength(circleList); + int selectedCircle = GetLocalInt(OBJECT_SELF, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR); + + int i; + for (i = 0; i < 10; i++) + { + json jCircle; + // some classes have less than 10 circles, build buttons for circles + // we have, leave the rest blank + if (i < totalCircles) + { + int spellLevel = StringToInt(JsonGetString(JsonArrayGet(circleList, i))); + // in case this isn't set, and we start at a spot higher than 0. + // OR we switched classes and the selected circle is higher than possible + // we set the value + if ((selectedCircle == -1) + && (GetRemainingSpellChoices(nClass, spellLevel) || (i+1 == totalCircles))) + { + SetLocalInt(OBJECT_SELF, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR, spellLevel); + selectedCircle = spellLevel; + } + + jCircle = NuiId(NuiButtonImage(JsonString(GetSpellLevelIcon(spellLevel))), NUI_LEVEL_UP_SPELL_CIRCLE_BUTTON_BASEID + IntToString(spellLevel)); + jCircle = NuiTooltip(jCircle, JsonString(GetSpellLevelToolTip(spellLevel))); + + if (selectedCircle != spellLevel) + jCircle = GreyOutButton(jCircle, 36.0f, 36.0f); + } + else + jCircle = NuiText(JsonString(""), TRUE, NUI_SCROLLBARS_NONE); + jCircle = NuiWidth(jCircle, 36.0f); + jCircle = NuiHeight(jCircle, 36.0f); + jCircle = NuiMargin(jCircle, 0.0f); + jRow = JsonArrayInsert(jRow, jCircle); + } + + jRow = NuiRow(jRow); + jRow = NuiMargin(jRow, 0.0f); + return jRow; +} + +json CreateSpellButtons(int nClass, int selectedCircle) +{ + json jRow = JsonArray(); + + json spellList = GetSpellListObject(nClass); + json currentList = JsonObjectGet(spellList, IntToString(selectedCircle)); + int totalSpells = JsonGetLength(currentList); + + int i; + for (i = 0; i < totalSpells; i++) + { + int spellbookId = JsonGetInt(JsonArrayGet(currentList, i)); + if (ShouldAddSpellToSpellButtons(nClass, spellbookId)) + { + string sFile = GetClassSpellbookFile(nClass); + string sRealSpellID = Get2DACache(sFile, "RealSpellID", spellbookId); + int nRealSpellID = StringToInt(sRealSpellID); + int spellId = StringToInt(Get2DACache(sFile, "SpellID", spellbookId)); + int featId = StringToInt(Get2DACache(sFile, "FeatID", spellbookId)); + + string spellName = GetSpellName(spellId, nRealSpellID, featId, nClass); + + int enabled = ShouldSpellButtonBeEnabled(nClass, selectedCircle, spellbookId); + json jSpellButton; + if (enabled) + jSpellButton = NuiId(NuiButton(JsonString("")), NUI_LEVEL_UP_SPELL_BUTTON_BASEID + IntToString(spellbookId)); + else + jSpellButton = NuiId(NuiButton(JsonString("")), NUI_LEVEL_UP_SPELL_DISABLED_BUTTON_BASEID + IntToString(spellbookId)); + + json jIcons = JsonArray(); + json jIconLeft = NuiDrawListImage(JsonBool(TRUE), GetSpellIcon(spellId, featId), NuiRect(5.0f,5.0f,32.0f,32.0f), JsonInt(NUI_ASPECT_FIT), JsonInt(NUI_HALIGN_CENTER), JsonInt(NUI_VALIGN_MIDDLE), NUI_DRAW_LIST_ITEM_ORDER_AFTER); + json jIconRight = NuiDrawListImage(JsonBool(TRUE), JsonString("nui_shld_right"), NuiRect(299.0f,8.0f,26.0f,26.0f), JsonInt(NUI_ASPECT_FIT), JsonInt(NUI_HALIGN_CENTER), JsonInt(NUI_VALIGN_MIDDLE), NUI_DRAW_LIST_ITEM_ORDER_AFTER); + json jText = NuiDrawListText(JsonBool(TRUE), NuiColor(255, 255, 255), NuiRect(40.0f, 12.0f, 290.0f, 32.0f), JsonString(spellName)); + jIcons = JsonArrayInsert(jIcons, jIconLeft); + jIcons = JsonArrayInsert(jIcons, jIconRight); + jIcons = JsonArrayInsert(jIcons, jText); + if (!enabled) + jIcons = JsonArrayInsert(jIcons, CreateGreyOutRectangle(335.0f, 42.0f)); + jSpellButton = NuiDrawList(jSpellButton, JsonBool(FALSE), jIcons); + jSpellButton = NuiWidth(jSpellButton, 335.0f); + jSpellButton = NuiHeight(jSpellButton, 42.0f); + jSpellButton = NuiMargin(jSpellButton, 0.0f); + + jRow = JsonArrayInsert(jRow, jSpellButton); + } + } + jRow = NuiCol(jRow); + jRow = NuiGroup(jRow, TRUE, NUI_SCROLLBARS_Y); + jRow = NuiWidth(jRow, 360.0f); + jRow = NuiHeight(jRow, 360.0f); + jRow = NuiMargin(jRow, 0.0f); + + return jRow; +} + +json CreateChosenButtons(int nClass, int selectedCircle) +{ + json jRow = JsonArray(); + + json selectedList = GetChosenSpellListObject(nClass); + json currentList = JsonObjectGet(selectedList, IntToString(selectedCircle)); + if (currentList != JsonNull()) + { + int totalSpells = JsonGetLength(currentList); + + int i; + for (i = 0; i < totalSpells; i++) + { + int spellbookId = JsonGetInt(JsonArrayGet(currentList, i)); + + string sFile = GetClassSpellbookFile(nClass); + string sRealSpellID = Get2DACache(sFile, "RealSpellID", spellbookId); + int nRealSpellID = StringToInt(sRealSpellID); + int spellId = StringToInt(Get2DACache(sFile, "SpellID", spellbookId)); + int featId = StringToInt(Get2DACache(sFile, "FeatID", spellbookId)); + + int enabled = EnableChosenButton(nClass, spellbookId, selectedCircle); + + string spellName = GetSpellName(spellId, nRealSpellID, featId, nClass); + + json jSpellButton; + if (enabled) + jSpellButton = NuiId(NuiButton(JsonString("")), NUI_LEVEL_UP_SPELL_CHOSEN_BUTTON_BASEID + IntToString(spellbookId)); + else + jSpellButton = NuiId(NuiButton(JsonString("")), NUI_LEVEL_UP_SPELL_CHOSEN_DISABLED_BUTTON_BASEID + IntToString(spellbookId)); + + json jIcons = JsonArray(); + json jIconLeft = NuiDrawListImage(JsonBool(TRUE), GetSpellIcon(spellId, featId), NuiRect(5.0f,5.0f,32.0f,32.0f), JsonInt(NUI_ASPECT_FIT), JsonInt(NUI_HALIGN_CENTER), JsonInt(NUI_VALIGN_MIDDLE), NUI_DRAW_LIST_ITEM_ORDER_AFTER); + json jIconRight = NuiDrawListImage(JsonBool(TRUE), JsonString("nui_shld_left"), NuiRect(299.0f,8.0f,26.0f,26.0f), JsonInt(NUI_ASPECT_FIT), JsonInt(NUI_HALIGN_CENTER), JsonInt(NUI_VALIGN_MIDDLE), NUI_DRAW_LIST_ITEM_ORDER_AFTER); + json jText = NuiDrawListText(JsonBool(TRUE), NuiColor(255, 255, 255), NuiRect(40.0f, 12.0f, 290.0f, 32.0f), JsonString(spellName)); + jIcons = JsonArrayInsert(jIcons, jIconLeft); + jIcons = JsonArrayInsert(jIcons, jIconRight); + jIcons = JsonArrayInsert(jIcons, jText); + if (!enabled) + jIcons = JsonArrayInsert(jIcons, CreateGreyOutRectangle(335.0f, 42.0f)); + jSpellButton = NuiDrawList(jSpellButton, JsonBool(FALSE), jIcons); + jSpellButton = NuiWidth(jSpellButton, 335.0f); + jSpellButton = NuiHeight(jSpellButton, 42.0f); + jSpellButton = NuiMargin(jSpellButton, 0.0f); + + jRow = JsonArrayInsert(jRow, jSpellButton); + } + } + + jRow = NuiCol(jRow); + jRow = NuiGroup(jRow, TRUE, NUI_SCROLLBARS_Y); + jRow = NuiWidth(jRow, 360.0f); + jRow = NuiHeight(jRow, 360.0f); + jRow = NuiMargin(jRow, 0.0f); + + return jRow; +} + +string GetRemainingText(int nClass, int selectedCircle) +{ + if (GetIsBladeMagicClass(nClass)) + { + string remainingMan = IntToString(GetRemainingManeuverChoices(nClass)); + string remainingStance = IntToString(GetRemainingStanceChoices(nClass)); + + return remainingMan + " Remaining Maneuevers / " + remainingStance + " Remaining Stances"; + } + + if (GetIsTruenamingClass(nClass)) + return IntToString(GetRemainingTruenameChoices(nClass, LEXICON_EVOLVING_MIND)) + " EM / " + + IntToString(GetRemainingTruenameChoices(nClass, LEXICON_CRAFTED_TOOL)) + " CT / " + + IntToString(GetRemainingTruenameChoices(nClass, LEXICON_PERFECTED_MAP)) + " PM"; + + int remainingSpellChoices = GetRemainingSpellChoices(nClass, selectedCircle, OBJECT_SELF); + + if (GetIsInvocationClass(nClass)) + return IntToString(remainingSpellChoices) + " Remaining Invocations"; + if (GetIsShadowMagicClass(nClass)) + return IntToString(remainingSpellChoices) + " Remaining Mysteries"; + if (GetIsPsionicClass(nClass)) + return IntToString(remainingSpellChoices) + " Remaining Powers"; + return IntToString(remainingSpellChoices) + " Remaining Spells"; +} + +void main() +{ + int nClass = GetLocalInt(OBJECT_SELF, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + if (!IsClassAllowedToUseLevelUpNUI(nClass)) + { + string name = GetStringByStrRef(StringToInt(Get2DACache("classes", "Name", nClass))); + SendMessageToPC(OBJECT_SELF, "The class " + name + " is not allowed to use the Level Up NUI!"); + DeleteLocalInt(OBJECT_SELF, NUI_LEVEL_UP_SELECTED_CLASS_VAR); + return; + } + + // First we look for any previous windows, if found (ie, non-zero) we destory them so we can start fresh. + if (IsLevelUpNUIOpen()) + { + // this is already rendered + return; + } + + + /////////////// + // LEFT SIDE // + /////////////// + + json jRoot = JsonArray(); + json jLeftSide = JsonArray(); + json jRightSide = JsonArray(); + + // Title + + json jRow = JsonArray(); + json jTitle = NuiText(JsonString("Available/Known"), TRUE, NUI_SCROLLBARS_NONE); + jTitle = NuiHeight(jTitle, 26.0f); + jTitle = NuiWidth(jTitle, 360.0f); + jTitle = NuiMargin(jTitle, 0.0f); + jRow = JsonArrayInsert(jRow, jTitle); + jRow = NuiRow(jRow); + jRow = NuiMargin(jRow, 0.0f); + jLeftSide = JsonArrayInsert(jLeftSide, jRow); + + // Circles + + jRow = CreateCircleButtons(nClass); + jLeftSide = JsonArrayInsert(jLeftSide, jRow); + + int selectedCircle = GetLocalInt(OBJECT_SELF, NUI_LEVEL_UP_SELECTED_CIRCLE_VAR); + + // Spells + + jRow = CreateSpellButtons(nClass, selectedCircle); + jLeftSide = JsonArrayInsert(jLeftSide, jRow); + jLeftSide = NuiCol(jLeftSide); + + //////////////// + // RIGHT SIDE // + //////////////// + + // Title + + jRow = JsonArray(); + + jTitle = NuiText(JsonString("Selected/Prepared"), TRUE, NUI_SCROLLBARS_NONE); + jTitle = NuiHeight(jTitle, 26.0f); + jTitle = NuiWidth(jTitle, 360.0f); + jTitle = NuiMargin(jTitle, 0.0f); + jRow = JsonArrayInsert(jRow, jTitle); + jRow = NuiRow(jRow); + jRow = NuiMargin(jRow, 0.0f); + jRightSide = JsonArrayInsert(jRightSide, jRow); + + // Remaining Choice + + jRow = JsonArray(); + json jAmountLeft = NuiText(JsonString(GetRemainingText(nClass, selectedCircle)), TRUE, NUI_SCROLLBARS_NONE); + jAmountLeft = NuiHeight(jAmountLeft, 36.0f); + jAmountLeft = NuiWidth(jAmountLeft, 360.0f); + jAmountLeft = NuiMargin(jAmountLeft, 0.0f); + jRow = JsonArrayInsert(jRow, jAmountLeft); + jRow = NuiRow(jRow); + jRow = NuiMargin(jRow, 0.0f); + jRightSide = JsonArrayInsert(jRightSide, jRow); + + // Selected Choices + + jRow = CreateChosenButtons(nClass, selectedCircle); + jRightSide = JsonArrayInsert(jRightSide, jRow); + jRightSide = NuiCol(jRightSide); + + // Make each side + + json tempRow = JsonArray(); + tempRow = JsonArrayInsert(tempRow, jLeftSide); + tempRow = JsonArrayInsert(tempRow, jRightSide); + tempRow = NuiRow(tempRow); + + jRoot = JsonArrayInsert(jRoot, tempRow); + + // Done button + + jRow = JsonArray(); + json jResetButton = NuiId(NuiButton(JsonString("Reset")), NUI_LEVEL_UP_RESET_BUTTON); + jResetButton = NuiWidth(jResetButton, 118.0f); + jResetButton = NuiHeight(jResetButton, 50.0f); + json jDoneButton = NuiId(NuiButton(JsonString("Done")), NUI_LEVEL_UP_DONE_BUTTON); + jDoneButton = NuiWidth(jDoneButton, 118.0f); + jDoneButton = NuiHeight(jDoneButton, 50.0f); + if (!AllSpellsAreChosen(nClass, OBJECT_SELF)) + jDoneButton = NuiEnabled(jDoneButton, JsonBool(FALSE)); + jRow = JsonArrayInsert(jRow, NuiSpacer()); + jRow = JsonArrayInsert(jRow, jResetButton); + jRow = JsonArrayInsert(jRow, jDoneButton); + jRow = NuiRow(jRow); + jRoot = JsonArrayInsert(jRoot, jRow); + + jRoot = NuiCol(jRoot); + + string className = GetStringByStrRef(StringToInt(Get2DACache("classes", "Name", nClass))); + string title = className + " Level Up"; + + // This is the main window with jRoot as the main pane. It includes titles and parameters (more on those later) + json nui = NuiWindow(jRoot, JsonString(title), NuiBind("geometry"), NuiBind("resizable"), JsonBool(FALSE), NuiBind("closable"), NuiBind("transparent"), NuiBind("border")); + + // finally create it and it'll return us a non-zero token. + int nToken = NuiCreate(OBJECT_SELF, nui, NUI_LEVEL_UP_WINDOW_ID); + + // get the geometry of the window in case we opened this before and have a + // preference for location + json geometry = NuiRect(-1.0f,-1.0f, 748.0f, 535.0f); + + // Set the binds to their default values + NuiSetBind(OBJECT_SELF, nToken, "geometry", geometry); + NuiSetBind(OBJECT_SELF, nToken, "resizable", JsonBool(FALSE)); + NuiSetBind(OBJECT_SELF, nToken, "closable", JsonBool(FALSE)); + NuiSetBind(OBJECT_SELF, nToken, "transparent", JsonBool(FALSE)); + NuiSetBind(OBJECT_SELF, nToken, "border", JsonBool(TRUE)); +} + + + diff --git a/nwn/nwnprc/trunk/scripts/prc_nui_sc_event.nss b/nwn/nwnprc/trunk/scripts/prc_nui_sb_event.nss similarity index 95% rename from nwn/nwnprc/trunk/scripts/prc_nui_sc_event.nss rename to nwn/nwnprc/trunk/scripts/prc_nui_sb_event.nss index a3746184..52abbe84 100644 --- a/nwn/nwnprc/trunk/scripts/prc_nui_sc_event.nss +++ b/nwn/nwnprc/trunk/scripts/prc_nui_sb_event.nss @@ -1,6 +1,6 @@ //:://///////////////////////////////////////////// //:: PRC Spellbook NUI Events -//:: prc_nui_sc_event +//:: prc_nui_sb_event //::////////////////////////////////////////////// /* This is the event script for the PRC Spellbook NUI that handles button presses @@ -13,7 +13,6 @@ #include "prc_nui_consts" #include "prc_nui_sb_inc" -#include "prc_nui_sbd_inc" // // SetWindowGeometry @@ -75,18 +74,9 @@ void main() string sElement = NuiGetEventElement(); string sWindowId = NuiGetWindowId(oPlayer, nToken); - // if this was the Spell Description NUI and the OK button was clicked - // then we close the window. - if (sWindowId == NUI_SPELL_DESCRIPTION_WINDOW_ID) - { - if (sElement == NUI_SPELL_DESCRIPTION_OK_BUTTON) - NuiDestroy(oPlayer, nToken); - return; - } - - // if the window is closed, save the geometry - if (sEvent == "close") + if (sEvent == "watch" && sElement == "geometry") { + // Save the geometry SetWindowGeometry(oPlayer, nToken); return; } @@ -97,9 +87,6 @@ void main() return; } - // Save the geometry first - SetWindowGeometry(oPlayer, nToken); - int spellId; int featId; int realSpellId; @@ -192,7 +179,8 @@ void main() // If right click, open the Spell Description NUI if (nButton == NUI_PAYLOAD_BUTTON_RIGHT_CLICK) { - CreateSpellDescriptionNUI(oPlayer, featId, spellId, realSpellId); + int classId = GetLocalInt(oPlayer, PRC_SPELLBOOK_SELECTED_CLASSID_VAR); + CreateSpellDescriptionNUI(oPlayer, featId, spellId, realSpellId, classId); DeleteLocalInt(oPlayer, NUI_SPELLBOOK_SELECTED_SUBSPELL_SPELLID_VAR); return; } @@ -209,7 +197,7 @@ void main() if (sRange == "P") { SetLocalInt(oPlayer, NUI_SPELLBOOK_ON_TARGET_IS_PERSONAL_FEAT, 1); - ExecuteScript("prc_nui_sc_trggr", oPlayer); + ExecuteScript("prc_nui_sb_trggr", oPlayer); } // otherwise enter targetting mode else @@ -228,7 +216,7 @@ void main() SetEnterTargetingModeData(oPlayer, iShape, fSizeX, fSizeY, nFlags, fRange); EnterTargetingMode(oPlayer, iTargetType); - } + } } } diff --git a/nwn/nwnprc/trunk/scripts/prc_nui_sc_trggr.nss b/nwn/nwnprc/trunk/scripts/prc_nui_sb_trggr.nss similarity index 100% rename from nwn/nwnprc/trunk/scripts/prc_nui_sc_trggr.nss rename to nwn/nwnprc/trunk/scripts/prc_nui_sb_trggr.nss diff --git a/nwn/nwnprc/trunk/scripts/prc_nui_sb_view.nss b/nwn/nwnprc/trunk/scripts/prc_nui_sb_view.nss index 8135a305..6a68789e 100644 --- a/nwn/nwnprc/trunk/scripts/prc_nui_sb_view.nss +++ b/nwn/nwnprc/trunk/scripts/prc_nui_sb_view.nss @@ -10,7 +10,6 @@ //:: Created On: 24.05.2005 //::////////////////////////////////////////////// -#include "nw_inc_nui" //#include "nw_inc_nui_insp" #include "prc_nui_sb_inc" #include "prc_nui_consts" @@ -80,60 +79,6 @@ json CreateMetaMagicFeatButtons(int nClass); // json CreateMetaFeatButtonRow(json spellList); -// -// GetSpellLevelIcon -// Takes the spell circle int and gets the icon appropriate for it (i.e. 0 turns -// into "ir_cantrips" -// -// Arguments: -// spellLevel:int the spell level we want the icon for -// -// Returns: -// string the spell level icon -// -string GetSpellLevelIcon(int spellLevel); - -// -// GetSpellLevelToolTip -// Gets the spell level tool tip text based on the int spell level provided (i.e. -// 0 turns into "Cantrips") -// -// Arguments: -// spellLevel:int the spell level we want the tooltip for -// -// Returns: -// string the spell level toop tip -// -string GetSpellLevelToolTip(int spellLevel); - -// -// GetSpellIcon -// Gets the spell icon based off the spellId by using the FeatID instead -// -// Arguments: -// nClass:int the class Id -// spellId:int the spell Id we want the icon for -// -// Returns: -// json:String the string of the icon we want. -// -json GetSpellIcon(int spellId, int nClass=0); - -// -// HighlightButton -// Takes NUI Button along with it's width and height and heighlights it with a drawn -// colored rectangle to represent it's been selected. -// -// Arguments: -// jButton:json the NUI Button -// w:float the width of the button -// h:float the height of the button -// -// Returns: -// json the NUI button highlighted -// -json HighlightButton(json jButton, float w, float h); - void main() { // look for existing window and destroy @@ -200,7 +145,7 @@ void main() // Default to put this near the middle and let the person adjust its location if (geometry == JsonNull()) { - geometry = NuiRect(893.0f,346.0f, 489.0f, 351.0f); + geometry = NuiRect(-1.0f,-1.0f, 489.0f, 351.0f); } else { @@ -216,6 +161,8 @@ void main() NuiSetBind(OBJECT_SELF, nToken, "closable", JsonBool(TRUE)); NuiSetBind(OBJECT_SELF, nToken, "transparent", JsonBool(TRUE)); NuiSetBind(OBJECT_SELF, nToken, "border", JsonBool(FALSE)); + + NuiSetBindWatch(OBJECT_SELF, nToken, "geometry", TRUE); } json CreateSpellBookClassButtons() @@ -243,8 +190,8 @@ json CreateSpellBookClassButtons() float height = 32.0f; // Get the class icon from the classes.2da json jClassButton = NuiId(NuiButtonImage(JsonString(Get2DACache("classes", "Icon", classId))), PRC_SPELLBOOK_NUI_CLASS_BUTTON_BASEID + IntToString(classId)); - if (classId == selectedClassId) - jClassButton = HighlightButton(jClassButton, width, height); + if (classId != selectedClassId) + jClassButton = GreyOutButton(jClassButton, width, height); jClassButton = NuiWidth(jClassButton, width); jClassButton = NuiHeight(jClassButton, height); // Get the class name from the classes.2da and set it to the tooltip @@ -272,8 +219,6 @@ json CreateSpellbookCircleButtons(int nClass) if (minSpellLevel >= 0) { - // get what is the highest circle the class can cast at - int currentMaxSpellLevel = GetCurrentSpellLevel(nClass, casterLevel); // Get what the max circle the class can reach at is int totalMaxSpellLevel = GetMaxSpellLevel(nClass); @@ -288,9 +233,9 @@ json CreateSpellbookCircleButtons(int nClass) // conversily if it is higher than the max the class has (possibly due to // switching classes) then set it to that. - if (currentCircle > currentMaxSpellLevel) + if (currentCircle > totalMaxSpellLevel) { - currentCircle = currentMaxSpellLevel; + currentCircle = totalMaxSpellLevel; SetLocalInt(OBJECT_SELF, PRC_SPELLBOOK_SELECTED_CIRCLE_VAR, currentCircle); } @@ -306,13 +251,10 @@ json CreateSpellbookCircleButtons(int nClass) // if the current circle is selected or if the person can't cast at // that circle yet then disable the button. - if (i > currentMaxSpellLevel) - enabled = JsonBool(FALSE); - else - enabled = JsonBool(TRUE); - jButton = NuiEnabled(jButton, enabled); - if (i == currentCircle) - jButton = HighlightButton(jButton, width, height); + + if (i != currentCircle) + jButton = GreyOutButton(jButton, width, height); + jRow = JsonArrayInsert(jRow, jButton); } @@ -340,22 +282,27 @@ json CreateSpellbookSpellButtons(int nClass, int circle) for (i = 0; i < JsonGetLength(spellListAtCircle); i++) { int spellbookId = JsonGetInt(JsonArrayGet(spellListAtCircle, i)); + int featId; int spellId; // Binders don't have a spellbook, so spellbookId is actually SpellID if (nClass == CLASS_TYPE_BINDER) + { spellId = spellbookId; + featId = StringToInt(Get2DACache("spells", "FeatID", spellId)); + } else + { spellId = StringToInt(Get2DACache(sFile, "SpellID", spellbookId)); + featId = StringToInt(Get2DACache(sFile, "FeatID", spellbookId)); + } - json jSpellButton = NuiId(NuiButtonImage(GetSpellIcon(spellId, nClass)), PRC_SPELLBOOK_NUI_SPELL_BUTTON_BASEID + IntToString(spellbookId)); + json jSpellButton = NuiId(NuiButtonImage(GetSpellIcon(spellId, featId, nClass)), PRC_SPELLBOOK_NUI_SPELL_BUTTON_BASEID + IntToString(spellbookId)); jSpellButton = NuiWidth(jSpellButton, 38.0f); jSpellButton = NuiHeight(jSpellButton, 38.0f); // the RealSpellID has the accurate descriptions for the spells/abilities int realSpellId = StringToInt(Get2DACache(sFile, "RealSpellID", spellbookId)); - if (!realSpellId) - realSpellId = spellId; - jSpellButton = NuiTooltip(jSpellButton, JsonString(GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", realSpellId))))); + jSpellButton = NuiTooltip(jSpellButton, JsonString(GetSpellName(spellId, realSpellId, featId, nClass))); // if the row limit has been reached, make a new row tempRow = JsonArrayInsert(tempRow, jSpellButton); @@ -465,12 +412,23 @@ json CreateMetaFeatButtonRow(json spellList) else featId = StringToInt(Get2DACache("spells", "FeatID", spellId)); + int selectedFeatId = featId; + if (featId == FEAT_EXTEND_SPELL_ABILITY) + selectedFeatId = FEAT_EXTEND_SPELL; + if (featId == FEAT_EMPOWER_SPELL_ABILITY) + selectedFeatId = FEAT_EMPOWER_SPELL; + if (featId == FEAT_MAXIMIZE_SPELL_ABILITY) + selectedFeatId = FEAT_MAXIMIZE_SPELL; + if (featId == FEAT_QUICKEN_SPELL_ABILITY) + selectedFeatId = FEAT_QUICKEN_SPELL; + if (featId == FEAT_STILL_SPELL_ABILITY) + selectedFeatId = FEAT_STILL_SPELL; - if (GetHasFeat(featId, OBJECT_SELF, TRUE)) + if (GetHasFeat(selectedFeatId, OBJECT_SELF, TRUE)) { string featName = GetStringByStrRef(StringToInt(Get2DACache("spells", "Name", spellId))); - json jMetaButton = NuiId(NuiButtonImage(GetSpellIcon(spellId)), PRC_SPELLBOOK_NUI_META_BUTTON_BASEID + IntToString(spellId)); + json jMetaButton = NuiId(NuiButtonImage(GetSpellIcon(spellId, featId)), PRC_SPELLBOOK_NUI_META_BUTTON_BASEID + IntToString(spellId)); jMetaButton = NuiWidth(jMetaButton, 32.0f); jMetaButton = NuiHeight(jMetaButton, 32.0f); jMetaButton = NuiTooltip(jMetaButton, JsonString(featName)); @@ -482,91 +440,3 @@ json CreateMetaFeatButtonRow(json spellList) return jRow; } -json GetSpellIcon(int spellId,int nClass=0) -{ - // Binder's spells don't have the FeatID on the spells.2da, so we have to use - // the mapping we constructed to get it. - if (nClass == CLASS_TYPE_BINDER) - { - json binderDict = GetBinderSpellToFeatDictionary(); - int featId = JsonGetInt(JsonObjectGet(binderDict, IntToString(spellId))); - return JsonString(Get2DACache("feat", "Icon", featId)); - } - - int masterSpellID = StringToInt(Get2DACache("spells", "Master", spellId)); - - // if this is a sub radial spell, then we use spell's icon instead - if (masterSpellID) - return JsonString(Get2DACache("spells", "IconResRef", spellId)); - - // the FeatID holds the accurate spell icon, not the SpellID - int featId = StringToInt(Get2DACache("spells", "FeatID", spellId)); - - return JsonString(Get2DACache("feat", "Icon", featId)); -} - -json HighlightButton(json jButton, float w, float h) -{ - json retValue = jButton; - - json jBorders = JsonArray(); - - // set the points of the button shape - json jPoints = JsonArray(); - jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); - jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); - - jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); - jPoints = JsonArrayInsert(jPoints, JsonFloat(h)); - - jPoints = JsonArrayInsert(jPoints, JsonFloat(w)); - jPoints = JsonArrayInsert(jPoints, JsonFloat(h)); - - jPoints = JsonArrayInsert(jPoints, JsonFloat(w)); - jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); - - jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); - jPoints = JsonArrayInsert(jPoints, JsonFloat(0.0)); - - jBorders = JsonArrayInsert(jBorders, NuiDrawListPolyLine(JsonBool(TRUE), NuiColor(71, 140, 32), JsonBool(FALSE), JsonFloat(2.0), jPoints)); - - return NuiDrawList(jButton, JsonBool(FALSE), jBorders); -} - -string GetSpellLevelIcon(int spellLevel) -{ - switch (spellLevel) - { - case 0: return "ir_cantrips"; - case 1: return "ir_level1"; - case 2: return "ir_level2"; - case 3: return "ir_level3"; - case 4: return "ir_level4"; - case 5: return "ir_level5"; - case 6: return "ir_level6"; - case 7: return "ir_level789"; - case 8: return "ir_level789"; - case 9: return "ir_level789"; - } - - return ""; -} - -string GetSpellLevelToolTip(int spellLevel) -{ - switch (spellLevel) - { - case 0: return "Cantrips"; - case 1: return "Level 1"; - case 2: return "Level 2"; - case 3: return "Level 3"; - case 4: return "Level 4"; - case 5: return "Level 5"; - case 6: return "Level 6"; - case 7: return "Level 7"; - case 8: return "Level 8"; - case 9: return "Level 9"; - } - - return ""; -} \ No newline at end of file diff --git a/nwn/nwnprc/trunk/scripts/prc_onenter.nss b/nwn/nwnprc/trunk/scripts/prc_onenter.nss index f2f0421b..14e57de9 100644 --- a/nwn/nwnprc/trunk/scripts/prc_onenter.nss +++ b/nwn/nwnprc/trunk/scripts/prc_onenter.nss @@ -217,6 +217,11 @@ void main() // through the .2da's, the entering player already meets. ExecuteScript("prc_prereq", oPC); ExecuteScript("prc_psi_ppoints", oPC); + if (GetHasFeat(FEAT_VOWOFPOVERTY, oPC) == TRUE) + { + ExecuteScript("prc_vop_feats_oe", oPC); + } + ResetTouchOfVitality(oPC); DelayCommand(0.15, DeleteLocalInt(oPC,"ONENTER")); diff --git a/nwn/nwnprc/trunk/scripts/prc_onplayerchat.nss b/nwn/nwnprc/trunk/scripts/prc_onplayerchat.nss index 6cb75e3f..5bacfb18 100644 --- a/nwn/nwnprc/trunk/scripts/prc_onplayerchat.nss +++ b/nwn/nwnprc/trunk/scripts/prc_onplayerchat.nss @@ -3,9 +3,9 @@ //:: prc_onplayerchat //::////////////////////////////////////////////// /* - A OnChat script that parses what is said and - uses any commands or NUI associated with - commands. + A OnChat script that parses what is said and + uses any commands or NUI associated with + commands. */ //::////////////////////////////////////////////// //:: Updated By: Rakiov @@ -28,6 +28,7 @@ OR: #include "prc_string_inc" #include "prc_nui_sb_inc" #include "prc_nui_consts" +#include "prc_nui_lv_inc" const string CHAT_COMMAND_INDICATOR_1 = "~~"; const string CHAT_COMMAND_INDICATOR_2 = ".."; @@ -135,19 +136,31 @@ void main() // clear message from chat SetPCChatMessage(); + return; } // If the first word is /sb then we open the Spellbook NUI if(firstWord == "/sb") { - ExecuteScript("prc_nui_sc_view", oPC); + ExecuteScript("prc_nui_sb_view", oPC); // clear message from chat SetPCChatMessage(); + return; + } + if (firstWord == "/lvl") + { + if (JsonGetLength(sCommandSplit) >= 2) + { + int classPos = StringToInt(JsonGetString(JsonArrayGet(sCommandSplit, 1))); + int nClass = GetClassByPosition(classPos, oPC); + OpenNUILevelUpWindow(nClass, oPC); + SetPCChatMessage(); + return; + } } } - - // Execute scripts hooked to this event for the player triggering it ExecuteAllScriptsHookedToEvent(oPC, EVENT_ONPLAYERCHAT); -} \ No newline at end of file +} + diff --git a/nwn/nwnprc/trunk/scripts/prc_onplayernui.nss b/nwn/nwnprc/trunk/scripts/prc_onplayernui.nss index dcf8a7c3..c324b222 100644 --- a/nwn/nwnprc/trunk/scripts/prc_onplayernui.nss +++ b/nwn/nwnprc/trunk/scripts/prc_onplayernui.nss @@ -4,8 +4,8 @@ //::////////////////////////////////////////////// /* This handles any NUI events and sends them to - appropriate NUI Event handler depending on the - window Id + appropriate NUI Event handler depending on the + window Id */ //::////////////////////////////////////////////// //:: Created By: Rakiov @@ -22,16 +22,18 @@ void main() // Open the Power Attack NUI if(sWindowId == NUI_PRC_POWER_ATTACK_WINDOW) - { ExecuteScript("prc_nui_pa_event"); - } // Open the Spellbook NUI - if(sWindowId == PRC_SPELLBOOK_NUI_WINDOW_ID - || sWindowId == NUI_SPELL_DESCRIPTION_WINDOW_ID) - { - ExecuteScript("prc_nui_sc_event"); - } + if(sWindowId == PRC_SPELLBOOK_NUI_WINDOW_ID) + ExecuteScript("prc_nui_sb_event"); + + if (sWindowId == NUI_SPELL_DESCRIPTION_WINDOW_ID) + ExecuteScript("prc_nui_dsc_evnt"); + + if (sWindowId == NUI_LEVEL_UP_WINDOW_ID) + ExecuteScript("prc_nui_lv_event"); return; } + diff --git a/nwn/nwnprc/trunk/scripts/prc_onplaytarget.nss b/nwn/nwnprc/trunk/scripts/prc_onplaytarget.nss index 886789aa..ce74a2c4 100644 --- a/nwn/nwnprc/trunk/scripts/prc_onplaytarget.nss +++ b/nwn/nwnprc/trunk/scripts/prc_onplaytarget.nss @@ -3,9 +3,9 @@ //:: prc_onplaytarget //::////////////////////////////////////////////// /* - This is the OnTarget event used set up spell - attacks with the selected spell from the PRC - Spellbook NUI + This is the OnTarget event used set up spell + attacks with the selected spell from the PRC + Spellbook NUI */ //::////////////////////////////////////////////// //:: Updated By: Rakiov @@ -20,7 +20,7 @@ void DoJump(object oPC, location lTarget, int bDoKnockdown); // DoSpellbookAction // This is a OnTarget event action handling the use of the NUI Spellbook's spell. // All this should do is take the manual targeting information and send it to the -// prc_nui_sc_trggr to handle the use of the spell. +// prc_nui_sb_trggr to handle the use of the spell. // // Arguments: // oPC:object the player executing the action @@ -85,7 +85,7 @@ void DoSpellbookAction(object oPC, object oTarget, location lTarget) SetLocalLocation(oPC, "TARGETING_POSITION", lTarget); } - ExecuteScript("prc_nui_sc_trggr", oPC); + ExecuteScript("prc_nui_sb_trggr", oPC); ClearEventVariables(oPC); } diff --git a/nwn/nwnprc/trunk/scripts/prc_prereq.nss b/nwn/nwnprc/trunk/scripts/prc_prereq.nss index 121a98d1..269378d0 100644 --- a/nwn/nwnprc/trunk/scripts/prc_prereq.nss +++ b/nwn/nwnprc/trunk/scripts/prc_prereq.nss @@ -1054,15 +1054,57 @@ void TomeOfBattle(object oPC = OBJECT_SELF) //Master of Nine SetLocalInt(oPC, "PRC_PrereqMoNine", 1); // Needs 6 maneuvers, so check the persistent locals - int i, nCount, nSkills; + int nCount = 0; + int nTotal = 0; + + int nDesert = _CheckPrereqsByDiscipline(oPC, DISCIPLINE_DESERT_WIND); + if (nDesert > 0) nCount += 1; + + int nDevoted = _CheckPrereqsByDiscipline(oPC, DISCIPLINE_DEVOTED_SPIRIT); + if (nDevoted > 0) nCount += 1; + + int nDiamond = _CheckPrereqsByDiscipline(oPC, DISCIPLINE_DIAMOND_MIND); + if (nDiamond > 0) nCount += 1; + + int nTiger = _CheckPrereqsByDiscipline(oPC, DISCIPLINE_TIGER_CLAW); + if (nTiger > 0) nCount += 1; + + int nShadow = _CheckPrereqsByDiscipline(oPC, DISCIPLINE_SHADOW_HAND); + if (nShadow > 0) nCount += 1; + + int nStone = _CheckPrereqsByDiscipline(oPC, DISCIPLINE_STONE_DRAGON); + if (nStone > 0) nCount += 1; + + int nSun = _CheckPrereqsByDiscipline(oPC, DISCIPLINE_SETTING_SUN); + if (nSun > 0) nCount += 1; + + int nRaven = _CheckPrereqsByDiscipline(oPC, DISCIPLINE_WHITE_RAVEN); + if (nRaven > 0) nCount += 1; + + nTotal = nDevoted + nDiamond + nTiger + nShadow + nStone + nSun + nRaven + nDesert; + + if (DEBUG) DoDebug("You have "+IntToString(nDevoted)+" Devoted Spirit Maneuvers"); + if (DEBUG) DoDebug("You have "+IntToString(nDiamond)+" Diamond Mind Maneuvers"); + if (DEBUG) DoDebug("You have "+IntToString(nTiger)+" Tiger Claw Maneuvers"); + if (DEBUG) DoDebug("You have "+IntToString(nShadow)+" Shadow Hand Maneuvers"); + if (DEBUG) DoDebug("You have "+IntToString(nStone)+" Stone Dragon Maneuvers"); + if (DEBUG) DoDebug("You have "+IntToString(nSun)+" Setting Sun Maneuvers"); + if (DEBUG) DoDebug("You have "+IntToString(nRaven)+" White Raven Maneuvers"); + if (DEBUG) DoDebug("You have "+IntToString(nDesert)+" Desert Wind Maneuvers"); + + if (DEBUG) DoDebug("You have "+IntToString(nTotal)+" total Maneuvers from "+IntToString(nCount)+" Disciplines."); + + // Needs 6 maneuvers, so check the persistent locals +/* int i, nCount, nSkills; for(i = 1; i <= 256; i <<= 1) { // Loop over all disciplines, and total up how many he knows if(_CheckPrereqsByDiscipline(oPC, i)) nCount++; - } + } */ - // Base ranks only + int nSkills = 0; + // Base ranks only if(GetSkillRank(SKILL_TUMBLE, oPC, TRUE) >= 10) nSkills += 1; if(GetSkillRank(SKILL_INTIMIDATE, oPC, TRUE) >= 10) nSkills += 1; if(GetSkillRank(SKILL_CONCENTRATION, oPC, TRUE) >= 10) nSkills += 1; @@ -1071,6 +1113,8 @@ void TomeOfBattle(object oPC = OBJECT_SELF) if(GetSkillRank(SKILL_HIDE, oPC, TRUE) >= 10) nSkills += 1; if(GetSkillRank(SKILL_JUMP, oPC, TRUE) >= 10) nSkills += 1; if(GetSkillRank(SKILL_PERSUADE, oPC, TRUE) >= 10) nSkills += 1; + + if (DEBUG) DoDebug("You have "+IntToString(nSkills)+" total Skills over 10 ranks."); if(nCount >= 6 && nSkills >= 4) SetLocalInt(oPC, "PRC_PrereqMoNine", 0); diff --git a/nwn/nwnprc/trunk/scripts/prc_vop_feats_oe.nss b/nwn/nwnprc/trunk/scripts/prc_vop_feats_oe.nss new file mode 100644 index 00000000..3ef15695 --- /dev/null +++ b/nwn/nwnprc/trunk/scripts/prc_vop_feats_oe.nss @@ -0,0 +1,85 @@ +//::////////////////////////////////////////////// +//:: Restore VoP Bonus Feats OnClientEnter +//:: Uses prc_vop_feats.2da +//::////////////////////////////////////////////// + +#include "prc_inc_function" +#include "inc_debug" + +void main() +{ + object oPC = OBJECT_SELF; + if (!GetIsPC(oPC) || GetIsDM(oPC)) return; + + int nRow = 0; + int nFeat; + string sFeatTag; + + int nRows = Get2DARowCount("prc_vop_feats"); + if (DEBUG) DoDebug("[VoPRestore] Checking " + IntToString(nRows) + " exalted feats..."); + + while (nRow < nRows) + { + string sIndex = Get2DAString("prc_vop_feats", "FeatIndex", nRow); + + if (sIndex != "****" && sIndex != "") + { + nFeat = StringToInt(sIndex); + sFeatTag = "VoPFeatID" + IntToString(nFeat); + + if (GetPersistantLocalInt(oPC, sFeatTag) == 1) + { + if (DEBUG) DoDebug("[VoPRestore] Restoring feat ID " + IntToString(nFeat)); + + effect eFeat = EffectBonusFeat(nFeat); + eFeat = UnyieldingEffect(eFeat); + eFeat = TagEffect(eFeat, "VoPFeat" + IntToString(nFeat)); + + ApplyEffectToObject(DURATION_TYPE_PERMANENT, eFeat, oPC); + } + else + { + // Only uncomment this line if you want to see *every* non-match + // SendMessageToPC(oPC, "[VoPRestore] Not set: " + sFeatTag); + } + } + else + { + if (DEBUG) DoDebug("[VoPRestore] Invalid FeatIndex at row " + IntToString(nRow)); + } + + nRow++; + } + + if (DEBUG) DoDebug("[VoPRestore] Done."); +} + + +/* void main() +{ + object oPC = GetEnteringObject(); + if (!GetIsPC(oPC) || GetIsDM(oPC)) return; + + int nRow = 0; + int nFeat; + string sFeatTag; + + int nRows = Get2DARowCount("prc_vop_feats"); + while (nRow < nRows) + { + // Read the feat index from the 2DA + nFeat = StringToInt(Get2DACache("prc_vop_feats", "FeatIndex", nRow)); + sFeatTag = "VoPFeat" + IntToString(nFeat); + + // If the PC has a persistent variable set for this feat, reapply the effect + if (GetPersistantLocalInt(oPC, sFeatTag) == 1) + { + effect eFeat = EffectBonusFeat(nFeat); + eFeat = UnyieldingEffect(eFeat); + eFeat = TagEffect(eFeat, sFeatTag); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, eFeat, oPC); + } + + nRow++; + } +} */ \ No newline at end of file diff --git a/nwn/nwnprc/trunk/tlk/prc8_consortium.tlk b/nwn/nwnprc/trunk/tlk/prc8_consortium.tlk index 12eb7312..0e2f4d05 100644 Binary files a/nwn/nwnprc/trunk/tlk/prc8_consortium.tlk and b/nwn/nwnprc/trunk/tlk/prc8_consortium.tlk differ diff --git a/nwn/nwnprc/trunk/tlk/prc8_consortium.tlk.xml b/nwn/nwnprc/trunk/tlk/prc8_consortium.tlk.xml index c2e7a1b6..c7d65b65 100644 --- a/nwn/nwnprc/trunk/tlk/prc8_consortium.tlk.xml +++ b/nwn/nwnprc/trunk/tlk/prc8_consortium.tlk.xml @@ -35992,6 +35992,7 @@ This is a supernatural maneuver. Fire Riposte Desert Wind (Counter) Level: Swordsage 2 +Prerequisite: One Desert Wind maneuver Initiation Action: 1 Immediate Action Range: Personal. Target: You. @@ -36344,7 +36345,7 @@ This makes you flat-footed for the rest of the round. Doom Charge Devoted Spirit (Strike) [Evil] Level: Crusader 5 -Prerequisite: One Devoted Spirit maneuver +Prerequisite: One Devoted Spirit maneuver, evil alignment Initiation Action: 1 Full-round action Range: Melee Target: One creature @@ -36401,7 +36402,7 @@ Any creature you threaten takes a -4 penalty on attacks against allies. Law Bearer Devoted Spirit (Strike) [Law] Level: Crusader 5 -Prerequisite: One Devoted Spirit maneuver +Prerequisite: One Devoted Spirit maneuver, lawful alignment Initiation Action: 1 Full-round action Range: Melee Target: One creature @@ -36424,7 +36425,7 @@ Whenever you successfully strike a creature, you or your ally within 30 feet hea Radiant Charge Devoted Spirit (Strike) [Good] Level: Crusader 5 -Prerequisite: One Devoted Spirit maneuver +Prerequisite: One Devoted Spirit maneuver, good alignment Initiation Action: 1 Full-round action Range: Melee Target: One creature @@ -36469,7 +36470,7 @@ Your grant your ally a +4 shield bonus against the next attack made against him. Shield Counter Devoted Spirit (Counter) Level: Crusader 2 -Prerequisite: 2 Devoted Spirit maneuvers +Prerequisite: Two Devoted Spirit maneuvers Initiation Action: 1 Swift Action Range: Melee Attack Target: One Creature @@ -36503,7 +36504,7 @@ Any creature who attempts to move near you provokes an AoO. Tide Of Chaos Devoted Spirit (Strike) [Chaos] Level: Crusader 5 -Prerequisite: One Devoted Spirit maneuver +Prerequisite: One Devoted Spirit maneuver, chaotic alignment Initiation Action: 1 Full-round action Range: Melee Target: One creature @@ -37101,6 +37102,15 @@ A creature strikes, but you turn the blow straight back at it. Make an opposed attack roll against the targeted creature. If you succeed, he damages himself rather than you. Ghostly Defense + Setting Sun (Stance) +Level: Swordsage 8 +Prerequisite: Three Setting Sun maneuvers +Initiation Action: 1 swift action +Range: Personal +Target : You +Duration: Stance + +You are a ghost in the mist, capable of turning a foe against her allies. By gauging your opponent?s fighting stance, timing your dodge correctly, and placing yourself in the right position, you can lead her into attacking one of her allies rather than you. When your foe strikes, you duck out of the way. As she follows through with her attack, she inadvertently attacks an ally. Giant Killing Style Setting Sun (Stance) Level: Swordsage 3 @@ -37159,6 +37169,7 @@ Make an opposed attack roll against the targeted creature. If you succeed, he da Shifting Defense Setting Sun (Stance) Level: Swordsage 5 +Prerequisite: Two Setting Sun maneuvers Initiation Action: 1 Swift Action Range: Personal Target: You @@ -37240,6 +37251,7 @@ You gain +2d6 Sneak Attack. Balance On The Sky Shadow Hand (Stance) Level: Swordsage 8 +Prerequisite: Three Shadow Hand maneuvers. Initiation Action: 1 Swift Action Range: Personal. Target: You. @@ -37321,6 +37333,7 @@ This maneuver functions only against a flat-footed opponent. As part of this man Drain Vitality Shadow Hand (Strike) Level: Swordsage 2 +Prerequisite: One Shadow Hand maneuver Initiation Action: 1 Standard Action Range: Melee Attack Target: One Creatures @@ -37414,6 +37427,7 @@ This is a supernatural maneuver. One With Shadow Shadow Hand (Counter) Level: Swordsage 8 +Prerequisite: Three Shadow Hand maneuvers Initiation Action: 1 Immediate Action Range: Personal Target: You @@ -37443,7 +37457,6 @@ This maneuver functions as the shadow jaunt maneuver, except that it can be init Shadow Garrote Shadow Hand (Strike) Level: Swordsage 3 -Prerequisite: One Shadow Hand maneuver Initiation Action: 1 Standard Action Range: 60' Target: One Living Creature @@ -37616,6 +37629,7 @@ As part of this maneuver, you make a melee attack. This attack deals an extra 4d Crushing Weight Of The Mountain Stone Dragon (Stance) Level: Crusader 3, Swordsage 3, Warblade 3 +Prerequisite: One Stone Dragon maneuver Initiation Action: 1 Swift Action Range: Personal Target: You @@ -37756,6 +37770,7 @@ You gain DR 5/adamantium (+5) for 1 round. Stone Dragon's Fury Stone Dragon (Strike) Level: Crusader 3, Swordsage 3, Warblade 3 +Prerequisite: One Stone Dragon maneuver Initiation Action: 1 Standard Action Range: Melee Attack Target: One Creature @@ -37788,6 +37803,7 @@ You gain a +2 bonus on Strength checks, and a +2 AC bonus against creatures larg White Raven Tactics White Raven (Boost) Level: Crusader 3, Warblade 3 +Prerequisite: One White Raven maneuver Initiation Action: 1 Swift Action Range: 10 ft. Target: One ally @@ -37832,6 +37848,7 @@ While you are in this stance, you focus your efforts on preventing any devastati Blood In The Water Tiger Claw (Stance) Level: Swordsage 1, Warblade 1 +Prerequisite: One Tiger Claw maneuver Initiation Action: 1 Swift Action Range: Personal. Target: You. @@ -37843,6 +37860,7 @@ Whenever you successfully critical hit a creature, you gain a +1 Attack and Dama Claw At The Moon Tiger Claw (Strike) Level: Swordsage 2, Warblade 2 +Prerequisite: Two Tiger Claw maneuvers Initiation Action: 1 Standard Action Range: Melee Attack Target: One Creature @@ -37889,6 +37907,7 @@ If your Jump check fails, you can make a singe attack normally. The maneuver is Flesh Ripper Tiger Claw (Strike) Level: Swordsage 3, Warblade 3 +Prerequisite: Two Tiger Claw maneuvers Initiation Action: 1 Standard Action Range: Melee Attack Target: One Creature @@ -37950,6 +37969,7 @@ As part of this maneuver, you make a single melee attack. If this attack hits, i Hunter's Sense Tiger Claw (Stance) Level: Swordsage 1, Warblade 1 +Prerequisite: One Tiger Claw maneuver Initiation Action: 1 Swift Action Range: Personal. Target: You. @@ -38028,6 +38048,7 @@ You make a flurry of deadly attacks. After initiating this boost, you can make t Soaring Raptor Strike Tiger Claw (Strike) Level: Swordsage 3, Warblade 3 +Prerequisite: One Tiger Claw maneuver Initiation Action: 1 Standard Action Range: Melee Attack Target: One Creature @@ -38039,6 +38060,7 @@ This must be used on a creature larger than you are. Sudden Leap Tiger Claw (Boost) Level: Swordsage 1, Warblade 1 +Prerequisite: One Tiger Claw maneuver Initiation Action: 1 Swift Action Range: Personal. Target: You. @@ -38097,6 +38119,7 @@ While you are in this stance, you gain a 5 foot bonus to movement for every succ Wolverine Stance Tiger Claw (Stance) Level: Swordsage 3, Warblade 3 +Prerequisite: One Tiger Claw maneuver Initiation Action: 1 Swift Action Range: Personal. Target: You. @@ -38108,6 +38131,7 @@ You may use any weapon in a grapple, and do not take the -4 penalty to attack. I Battle Leader's Charge White Raven (Strike) Level: Crusader 2, Warblade 2 +Prerequisite: One White Raven maneuver Initiation Action: 1 Full-round Action Range: Melee Attack Target: One Creature @@ -38185,6 +38209,7 @@ If your strike hits, your target suffers a -4 penalty to AC. Lion's Roar White Raven (Boost) Level: Crusader 3, Warblade 3 +Prerequisite: One White Raven maneuver Initiation Action: 1 Swift Action Range: 60' Target: You @@ -38242,6 +38267,7 @@ If you strike your foe, all allies in melee range make a single attack against t Tactical Strike White Raven (Strike) Level: Crusader 2, Warblade 2 +Prerequisite: One White Raven maneuver Initiation Action: 1 Standard Action Range: Melee Attack Target: One Creature @@ -38252,6 +38278,7 @@ Make a single melee attack. If you successfully hit the creature, all allies gai Tactics Of The Wolf White Raven (Stance) Level: Crusader 3, Warblade 3 +Prerequisite: One White Raven maneuver Initiation Action: 1 Swift Action Range: 10ft. Area: 10 ft radius centred on you. diff --git a/nwn/nwnprc/trunk/tlk/prc_consortium.tlk b/nwn/nwnprc/trunk/tlk/prc_consortium.tlk index a4e37a68..0e2f4d05 100644 Binary files a/nwn/nwnprc/trunk/tlk/prc_consortium.tlk and b/nwn/nwnprc/trunk/tlk/prc_consortium.tlk differ