diff --git a/cdtweaks/languages/english/disarm.tra b/cdtweaks/languages/english/disarm.tra new file mode 100644 index 00000000..a3ee28d6 --- /dev/null +++ b/cdtweaks/languages/english/disarm.tra @@ -0,0 +1,13 @@ +@0 = "Disarm" + +@1 = "Disarm + +The character can attempt to disarm an opponent in melee combat. The combatant with the larger weapon gains a +2 bonus per size category of difference. If the opponent fails a Save vs. Breath, then the weapon flies from the opponent's hands." + +//@100 = "The selected target is out of range" +@101 = "Disarm Canceled: Inventory full" +@102 = "Disarm: Resisted" +@103 = "Disarm: Hit" +@104 = "This feat cannot be used while wielding a ranged weapon" +@105 = "The targeted creature cannot be disarmed" +//@106 = "Unaffected by effects from Disarm" \ No newline at end of file diff --git a/cdtweaks/languages/english/weidu.tra b/cdtweaks/languages/english/weidu.tra index 109f0e85..1d66d19f 100644 --- a/cdtweaks/languages/english/weidu.tra +++ b/cdtweaks/languages/english/weidu.tra @@ -808,6 +808,8 @@ Use Baldur.lua options: a7_interval_ini /////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ /////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +@600000 = "Disarm class feat for Rogues [Luke (EEex)]" + @602000 = "Spellcaster Skill/Counterspell Class Talent [Luke (EEex)]" @603000 = "Spontaneous Casting for Clerics [Luke]" diff --git a/cdtweaks/languages/italian/disarm.tra b/cdtweaks/languages/italian/disarm.tra new file mode 100644 index 00000000..038db712 --- /dev/null +++ b/cdtweaks/languages/italian/disarm.tra @@ -0,0 +1,13 @@ +@0 = "Disarmare" + +@1 = "Disarmare + +Il personaggio può tentare di disarmare un avversario durante un combattimento in mischia. Il combattente con l'arma più grande ottiene un bonus di +2 per ogni categoria di taglia di differenza. Se l'avversario fallisce un tiro-salvezza contro Soffio, l'arma gli vola via dalle mani." + +//@100 = "Il bersaglio selezionato è fuori portata" +@101 = "Disarmare Annullato: Inventario pieno" +@102 = "Disarmare: Resistito" +@103 = "Disarmare: Colpito" +@104 = "Questa abilità non può essere usata mentre si brandisce un'arma a distanza" +@105 = "La creatura selezionata non può essere disarmata" +//@106 = "Non soggetto agli effetti di Disarmare" \ No newline at end of file diff --git a/cdtweaks/languages/italian/weidu.tra b/cdtweaks/languages/italian/weidu.tra index b44fb689..4df70b70 100644 --- a/cdtweaks/languages/italian/weidu.tra +++ b/cdtweaks/languages/italian/weidu.tra @@ -36,6 +36,7 @@ o rimpiazzata da - un'altra facente parte di uno dei mods installati.~ @27 = ~E' necessario utilizzare Modmerge prima che i mod possano essere installati su questo gioco. Controllare il readme per ulteriori informazioni e per un link per scaricare Modmerge.~ @28 = ~Non sono disponibili abbastanza slot per lo stato dell'incantesimo o di tipo secondario per installare questo componente.~ @29 = "Questo componente richiede EEex (https://github.com/Bubb13/EEex)." +@30 = "Talenti in stile NWN" // questo componente non è pronto, puoi saltare la traduzione di questo blocco //@100 = ~Programma di installazione automatizzato~ @@ -735,6 +736,8 @@ Usa opzioni di Baldur.lua: a7_interval_ini /////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ /////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +@600000 = "Aggiungi talento di classe Disarmare per i Ladri [Luke (EEex)]" + @602000 = "Aggiungi talento di classe Sapienza Magica / Controincantesimo per gli incantatori [Luke (EEex)]" @603000 = "Aggiungi talento di classe Lancio Spontaneo per i Chierici [Luke]" diff --git a/cdtweaks/lib/comp_6000.tpa b/cdtweaks/lib/comp_6000.tpa new file mode 100644 index 00000000..927b7eab --- /dev/null +++ b/cdtweaks/lib/comp_6000.tpa @@ -0,0 +1,17 @@ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +///// \\\\\ +///// Disarm class feat for Rogues \\\\\ +///// \\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ + +WITH_SCOPE BEGIN + INCLUDE "cdtweaks\luke\misc.tph" + INCLUDE "cdtweaks\ardanis\functions.tph" + // + INCLUDE "cdtweaks\lib\disarm.tph" + WITH_TRA "cdtweaks\languages\english\disarm.tra" "cdtweaks\languages\%LANGUAGE%\disarm.tra" BEGIN + LAF "DISARM" END + END +END \ No newline at end of file diff --git a/cdtweaks/lib/disarm.tph b/cdtweaks/lib/disarm.tph new file mode 100644 index 00000000..52c12070 --- /dev/null +++ b/cdtweaks/lib/disarm.tph @@ -0,0 +1,49 @@ +DEFINE_ACTION_FUNCTION "DISARM" +BEGIN + LAF "GT_ADD_SPELL" + INT_VAR + "level" = 4 + "preferredSlot" = 44 + "type" = 4 + STR_VAR + "idsName" = "THIEF_DISARM" + RET + "THIEF_DISARM" = "resName" + END + // + WITH_SCOPE BEGIN + ACTION_TO_LOWER "THIEF_DISARM" + // Disarm (main spl file) + CREATE "spl" "%THIEF_DISARM%" + COPY_EXISTING "%THIEF_DISARM%.spl" "override" + WRITE_LONG NAME1 RESOLVE_STR_REF (@0) + WRITE_LONG NAME2 "-1" + WRITE_LONG UNIDENTIFIED_DESC RESOLVE_STR_REF (@1) + WRITE_LONG DESC "-1" + WRITE_LONG 0x18 (BIT14 BOR BIT25) // ignore dead/wild magic, castable when silenced + WRITE_SHORT 0x1C 4 // innate + WRITE_LONG 0x34 1 // level + WRITE_ASCII 0x3A "%DEST_RES%B" #8 // icon + // + LPF "ADD_SPELL_HEADER" INT_VAR "range" = 0 STR_VAR "icon" = "%DEST_RES%B" END + // + LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 138 "target" = 1 "parameter2" = 0 END // SEQ_ATTACK + BUT_ONLY + // icon + COPY "cdtweaks\luke\bam\class\disarm.bam" "override\%THIEF_DISARM%b.bam" + END + // lua + WITH_SCOPE BEGIN + OUTER_SET "feedback_strref_inventory_full" = RESOLVE_STR_REF (@101) + OUTER_SET "feedback_strref_resisted" = RESOLVE_STR_REF (@102) + OUTER_SET "feedback_strref_hit" = RESOLVE_STR_REF (@103) + OUTER_SET "feedback_strref_melee_only" = RESOLVE_STR_REF (@104) + OUTER_SET "feedback_strref_cannot_be_disarmed" = RESOLVE_STR_REF (@105) + // + LAF "APPEND_LUA_FUNCTION" STR_VAR "description" = "Class/Kit Abilities" "sourceFileSpec" = "cdtweaks\luke\lua\class\disarm.lua" "destRes" = "m_gtspcl" END + END + // + ACTION_IF !(FILE_EXISTS_IN_GAME "m_gttbls.lua") BEGIN + COPY "cdtweaks\luke\lua\m_gttbls.lua" "override" + END +END \ No newline at end of file diff --git a/cdtweaks/luke/bam/class/disarm.bam b/cdtweaks/luke/bam/class/disarm.bam new file mode 100644 index 00000000..34706e40 Binary files /dev/null and b/cdtweaks/luke/bam/class/disarm.bam differ diff --git a/cdtweaks/luke/lua/class/disarm.lua b/cdtweaks/luke/lua/class/disarm.lua new file mode 100644 index 00000000..3d9ad370 --- /dev/null +++ b/cdtweaks/luke/lua/class/disarm.lua @@ -0,0 +1,361 @@ +--[[ ++---------------------------------------------+ +| cdtweaks, NWN-ish Disarm ability for Rogues | ++---------------------------------------------+ +--]] + +-- NWN-ish Disarm ability. Small / Medium / Large weapons -- + +local cdtweaks_Disarm_WeaponSize = { + ["small"] = {"", "CL", "DD", "F2", "M2", "MC", "SL", "SS"}, + ["medium"] = {"AX", "BS", "CB", "FS", "MS", "S1", "SC", "WH"}, + ["large"] = {"BW", "F0", "F1", "F3", "FL", "GS", "HB", "Q2", "Q3", "Q4", "QS", "S0", "S2", "S3", "SP"}, +} + +local function cdtweaks_Disarm_CheckWeaponSize(animationType) + for size, animationTypeList in pairs(cdtweaks_Disarm_WeaponSize) do + for _, value in ipairs(animationTypeList) do + if value == animationType then + return size + end + end + end + return "none" -- should not happen +end + +-- NWN-ish Disarm ability (main) -- + +function %THIEF_DISARM%(CGameEffect, CGameSprite) + local sourceSprite = EEex_GameObject_Get(CGameEffect.m_sourceId) -- CGameSprite + -- + local inventoryFull = EEex_Trigger_ParseConditionalString("InventoryFull(Myself)") + -- Get source's currently selected weapon + local sourceEquipment = sourceSprite.m_equipment + local sourceSelectedWeapon = sourceEquipment.m_items:get(sourceEquipment.m_selectedWeapon) -- CItem + -- + local sourceSelectedWeaponHeader = sourceSelectedWeapon.pRes.pHeader -- Item_Header_st + -- Get target's currently selected weapon + local targetEquipment = CGameSprite.m_equipment + local targetSelectedWeapon = targetEquipment.m_items:get(targetEquipment.m_selectedWeapon) -- CItem + -- Get launcher if needed + local targetSelectedWeapon = CGameSprite:getLauncher(targetSelectedWeapon:getAbility(targetEquipment.m_selectedWeaponAbility)) or targetSelectedWeapon -- CItem + -- + local targetSelectedWeaponResRef = targetSelectedWeapon.pRes.resref:get() + local targetSelectedWeaponHeader = targetSelectedWeapon.pRes.pHeader -- Item_Header_st + -- + local targetActiveStats = EEex_Sprite_GetActiveStats(CGameSprite) + -- MAIN -- + -- Check if inventory is full + if not inventoryFull:evalConditionalAsAIBase(sourceSprite) then + -- check if NONDROPABLE + if EEex_IsBitUnset(targetSelectedWeapon.m_flags, 0x3) then + -- check if DROPPABLE + if EEex_IsBitSet(targetSelectedWeaponHeader.itemFlags, 0x2) then + -- check if CURSED + if EEex_IsBitUnset(targetSelectedWeaponHeader.itemFlags, 0x4) then + -- + local sourceAnimationType = EEex_CastUD(sourceSelectedWeaponHeader.animationType, "CResRef"):get() + local targetAnimationType = EEex_CastUD(targetSelectedWeaponHeader.animationType, "CResRef"):get() + -- sanity check (only darts are supposed to have a null animation) + if (targetAnimationType ~= "") or (targetSelectedWeaponHeader.itemType == 24) then + -- set ``savebonus`` + local savebonus = 0 + -- + local sourceWeaponSize = cdtweaks_Disarm_CheckWeaponSize(sourceAnimationType) + local targetWeaponSize = cdtweaks_Disarm_CheckWeaponSize(targetAnimationType) + -- + if (sourceWeaponSize == "small" and targetWeaponSize == "medium") or (sourceWeaponSize == "medium" and targetWeaponSize == "large") then + savebonus = 2 + elseif (sourceWeaponSize == "medium" and targetWeaponSize == "small") or (sourceWeaponSize == "large" and targetWeaponSize == "medium") then + savebonus = -2 + elseif sourceWeaponSize == "small" and targetWeaponSize == "large" then + savebonus = 4 + elseif sourceWeaponSize == "large" and targetWeaponSize == "small" then + savebonus = -4 + end + -- + local targetSaveVSBreath = targetActiveStats.m_nSaveVSBreath + local adjustedRoll = CGameSprite.m_saveVSBreathRoll + savebonus + -- + if adjustedRoll >= targetSaveVSBreath then + CGameSprite:applyEffect({ + ["effectID"] = 139, -- display string + ["effectAmount"] = %feedback_strref_resisted%, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + else + CGameSprite:applyEffect({ + ["effectID"] = 139, -- display string + ["effectAmount"] = %feedback_strref_hit%, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + -- + sourceSprite:applyEffect({ + ["effectID"] = 122, -- create inventory item + ["durationType"] = 1, + ["effectAmount"] = targetSelectedWeapon.m_useCount1, + ["m_effectAmount2"] = targetSelectedWeapon.m_useCount2, + ["m_effectAmount3"] = targetSelectedWeapon.m_useCount3, + ["res"] = targetSelectedWeaponResRef, + ["sourceID"] = sourceSprite.m_id, + ["sourceTarget"] = sourceSprite.m_id, + }) + -- restore ``CItem`` flags + do + local sourceItems = sourceEquipment.m_items -- Array + for i = 18, 33 do -- inventory slots + local item = sourceItems:get(i) -- CItem + if item then + local resref = item.pRes.resref:get() + if resref == targetSelectedWeaponResRef then + if item.m_flags == 0 then + if item.m_useCount1 == targetSelectedWeapon.m_useCount1 then + if item.m_useCount2 == targetSelectedWeapon.m_useCount2 then + if item.m_useCount3 == targetSelectedWeapon.m_useCount3 then + item.m_flags = targetSelectedWeapon.m_flags + break + end + end + end + end + end + end + end + end + -- + CGameSprite:applyEffect({ + ["effectID"] = 112, -- remove item + ["res"] = targetSelectedWeaponResRef, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + -- make sure to unequip ammo (apparently, if you disarm a launcher, the corresponding ammo is still equipped) + do + local targetItems = CGameSprite.m_equipment.m_items -- Array + for i = 11, 13 do -- ammo slots + local item = targetItems:get(i) -- CItem + if item then + local resref = item.pRes.resref:get() + -- + local responseString = EEex_Action_ParseResponseString(string.format('XEquipItem("%s",Myself,%d,UNEQUIP)', resref, i)) + responseString:executeResponseAsAIBaseInstantly(CGameSprite) + -- + responseString:free() + end + end + end + end + else + CGameSprite:applyEffect({ + ["effectID"] = 139, -- display string + ["effectAmount"] = %feedback_strref_cannot_be_disarmed%, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + end + else + CGameSprite:applyEffect({ + ["effectID"] = 139, -- display string + ["effectAmount"] = %feedback_strref_cannot_be_disarmed%, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + end + else + CGameSprite:applyEffect({ + ["effectID"] = 139, -- display string + ["effectAmount"] = %feedback_strref_cannot_be_disarmed%, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + end + else + CGameSprite:applyEffect({ + ["effectID"] = 139, -- display string + ["effectAmount"] = %feedback_strref_cannot_be_disarmed%, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + end + else + sourceSprite:applyEffect({ + ["effectID"] = 139, -- display string + ["effectAmount"] = %feedback_strref_inventory_full%, + ["sourceID"] = sourceSprite.m_id, + ["sourceTarget"] = sourceSprite.m_id, + }) + end + -- + inventoryFull:free() +end + +-- Make it castable at will. Prevent spell disruption. Check if melee weapon equipped -- + +EEex_Sprite_AddQuickListsCheckedListener(function(sprite, resref, changeAmount) + local curAction = sprite.m_curAction + local spriteAux = EEex_GetUDAux(sprite) + + if not (curAction.m_actionID == 31 and resref == "%THIEF_DISARM%" and changeAmount < 0) then + return + end + + -- nuke current action + curAction.m_actionID = 0 + + local spellHeader = EEex_Resource_Demand(resref, "SPL") + local spellLevelMemListArray = sprite.m_memorizedSpellsInnate + local memList = spellLevelMemListArray:getReference(spellHeader.spellLevel - 1) -- !!!count starts from 0!!! + + -- restore memorization bit + EEex_Utility_IterateCPtrList(memList, function(memInstance) + local memInstanceResref = memInstance.m_spellId:get() + if memInstanceResref == resref then + local memFlags = memInstance.m_flags + if EEex_IsBitUnset(memFlags, 0x0) then + memInstance.m_flags = EEex_SetBit(memFlags, 0x0) + end + end + end) + + -- make sure the creature is equipped with a melee weapon + local isWeaponRanged = EEex_Trigger_ParseConditionalString("IsWeaponRanged(Myself)") + if not isWeaponRanged:evalConditionalAsAIBase(sprite) then + -- store target id + spriteAux["gtDisarmTargetID"] = curAction.m_acteeID.m_Instance + -- initialize the attack frame counter + sprite.m_attackFrame = 0 + -- recast the ability as "ForceSpell()" + local targetSprite = EEex_GameObject_Get(curAction.m_acteeID.m_Instance) + targetSprite:applyEffect({ + ["effectID"] = 146, -- Cast spell + ["res"] = resref, + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = targetSprite.m_id, + }) + else + sprite:applyEffect({ + ["effectID"] = 139, -- Display string + ["effectAmount"] = %feedback_strref_melee_only%, + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + end + + isWeaponRanged:free() +end) + +-- Cast the "real" spl (ability) when the attack frame counter is 6 -- + +EEex_Opcode_AddListsResolvedListener(function(sprite) + -- Sanity check + if not EEex_GameObject_IsSprite(sprite) then + return + end + -- + local spriteAux = EEex_GetUDAux(sprite) + -- + local isWeaponRanged = EEex_Trigger_ParseConditionalString("IsWeaponRanged(Myself)") + -- + if sprite:getLocalInt("gtThiefDisarm") == 1 then + if not isWeaponRanged:evalConditionalAsAIBase(sprite) then + if sprite.m_nSequence == 0 and sprite.m_attackFrame == 6 then -- SetSequence(SEQ_ATTACK) + if spriteAux["gtDisarmTargetID"] then + -- retrieve / forget target sprite + local targetSprite = EEex_GameObject_Get(spriteAux["gtDisarmTargetID"]) + spriteAux["gtDisarmTargetID"] = nil + -- + targetSprite:applyEffect({ + ["effectID"] = 138, -- set animation + ["dwFlags"] = 4, -- SEQ_DAMAGE + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = targetSprite.m_id, + }) + targetSprite:applyEffect({ + ["effectID"] = 402, -- invoke lua + ["res"] = "%THIEF_DISARM%", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = targetSprite.m_id, + }) + end + end + end + end + -- + isWeaponRanged:free() +end) + +-- Forget about ``spriteAux["gtDisarmTargetID"]`` if the player manually interrupts the action -- + +EEex_Action_AddSpriteStartedActionListener(function(sprite, action) + local spriteAux = EEex_GetUDAux(sprite) + -- + if sprite:getLocalInt("gtThiefDisarm") == 1 then + if not (action.m_actionID == 113 and action.m_string1.m_pchData:get() == "%THIEF_DISARM%") then + if spriteAux["gtDisarmTargetID"] ~= nil then + spriteAux["gtDisarmTargetID"] = nil + end + end + end +end) + +-- cdtweaks, NWN-ish Disarm ability. Gain ability -- + +EEex_Opcode_AddListsResolvedListener(function(sprite) + -- Sanity check + if not EEex_GameObject_IsSprite(sprite) then + return + end + -- internal function that grants the ability + local gain = function() + -- Mark the creature as 'feat granted' + sprite:setLocalInt("gtThiefDisarm", 1) + -- + local effectCodes = { + {["op"] = 172}, -- remove spell + {["op"] = 171}, -- give spell + } + -- + for _, attributes in ipairs(effectCodes) do + sprite:applyEffect({ + ["effectID"] = attributes["op"] or EEex_Error("opcode number not specified"), + ["res"] = "%THIEF_DISARM%", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + end + end + -- Check creature's class + local spriteClassStr = GT_Resource_IDSToSymbol["class"][sprite.m_typeAI.m_Class] + -- + local spriteFlags = sprite.m_baseStats.m_flags + -- since ``EEex_Opcode_AddListsResolvedListener`` is running after the effect lists have been evaluated, ``m_bonusStats`` has already been added to ``m_derivedStats`` by the engine + local spriteLevel1 = sprite.m_derivedStats.m_nLevel1 + local spriteLevel2 = sprite.m_derivedStats.m_nLevel2 + -- + local gainAbility = spriteClassStr == "THIEF" or spriteClassStr == "FIGHTER_MAGE_THIEF" + or (spriteClassStr == "MAGE_THIEF" and (EEex_IsBitUnset(spriteFlags, 0x6) or spriteLevel1 > spriteLevel2)) + or (spriteClassStr == "CLERIC_THIEF" and (EEex_IsBitUnset(spriteFlags, 0x6) or spriteLevel1 > spriteLevel2)) + or (spriteClassStr == "FIGHTER_THIEF" and (EEex_IsBitUnset(spriteFlags, 0x6) or spriteLevel1 > spriteLevel2)) + -- + if sprite:getLocalInt("gtThiefDisarm") == 0 then + if gainAbility then + gain() + end + else + if gainAbility then + -- do nothing + else + -- Mark the creature as 'feat removed' + sprite:setLocalInt("gtThiefDisarm", 0) + -- + sprite:applyEffect({ + ["effectID"] = 172, -- remove spell + ["res"] = "%THIEF_DISARM%", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + end + end +end) diff --git a/cdtweaks/luke/lua/tools/key_exists.lua b/cdtweaks/luke/lua/tools/key_exists.lua new file mode 100644 index 00000000..05f04d36 --- /dev/null +++ b/cdtweaks/luke/lua/tools/key_exists.lua @@ -0,0 +1,15 @@ +-- Tool: Check if a key exists in a table (should work for any kind of table, from simple to nested tables...) -- + +function GT_LuaTool_KeyExists(tbl, ...) -- NB.: ``...`` is called ``vararg`` (variable argument). It allows the function to accept a variable number of arguments. This is useful when we don't know in advance how many arguments will be passed to the function + local keys = {...} + local current = tbl + + for _, key in ipairs(keys) do + if type(current) ~= "table" or current[key] == nil then + return false + end + current = current[key] + end + + return true +end diff --git a/cdtweaks/luke/misc.tph b/cdtweaks/luke/misc.tph index f539c3ea..ba9b1fab 100644 --- a/cdtweaks/luke/misc.tph +++ b/cdtweaks/luke/misc.tph @@ -413,4 +413,231 @@ BEGIN COPY_EXISTING "%destRes%.lua" "override" APPEND_FILE_EVALUATE TEXT "%sourceFileSpec%" BUT_ONLY UNLESS "%fileContent%" +END + +/* ++-----------------------------------+ +| Custom ADD_SPELL | ++-----------------------------------+ +| Adds a new entry to "gtspell.ids" | +| - "gtwi***" for wizard spells | +| - "gtpr***" for priest spells | +| - "gtin***" for innate spells | +| - "gtcl***" for class/kit spells | ++-----------------------------------+ +*/ + +DEFINE_DIMORPHIC_FUNCTION "GT_ADD_SPELL" +INT_VAR + "type" = 3 // GTINXXX + "level" = 0 + "sort" = 1 // boolean + "preferredSlot" = 0 +STR_VAR + "idsName" = "INNATE_NO_NAME" +RET + "resName" +BEGIN + OUTER_TEXT_SPRINT "resName" "" + // sanity check + ACTION_IF ("%preferredSlot%" >= 0 AND "%preferredSlot%" <= 99) BEGIN + ACTION_IF ("%preferredSlot%" < 10) BEGIN + OUTER_TEXT_SPRINT "preferredSlot" "0%preferredSlot%" + END + END ELSE BEGIN + OUTER_TEXT_SPRINT "preferredSlot" "00" + END + // + ACTION_IF !(FILE_EXISTS_IN_GAME "gtspell.ids") BEGIN + COPY_EXISTING "spell.ids" "override\gtspell.ids" + DELETE_BYTES 0x0 BUFFER_LENGTH + BUT_ONLY + END + // + ACTION_IF (IDS_OF_SYMBOL ("GTSPELL" "%idsName%") == "-1") BEGIN + ACTION_MATCH "%type%" WITH + 1 BEGIN + OUTER_SET "min" = 0 + OUTER_SET "max" = 99 + OUTER_TEXT_SPRINT "prefix" "gtpr" + END + 2 BEGIN + OUTER_SET "min" = 0 + OUTER_SET "max" = 99 + OUTER_TEXT_SPRINT "prefix" "gtwi" + END + 3 BEGIN + OUTER_SET "min" = 0 + OUTER_SET "max" = 99 + OUTER_TEXT_SPRINT "prefix" "gtin" + END + 4 BEGIN + OUTER_SET "min" = 0 + OUTER_SET "max" = 99 + OUTER_TEXT_SPRINT "prefix" "gtcl" + END + DEFAULT + FAIL "GT_ADD_SPELL: invalid spell type (%type%)" + END + // + ACTION_IF !(RESOURCE_CONTAINS "gtspell.ids" "^%type%%level%%preferredSlot%[ %TAB%]+") BEGIN + APPEND "GTSPELL.IDS" "%type%%level%%preferredSlot% %idsName%" + OUTER_TEXT_SPRINT "resName" "%prefix%%level%%preferredSlot%" + END ELSE BEGIN + OUTER_FOR ("i" = "%min%" ; "%i%" <= "%max%" ; "i" += 1) BEGIN + ACTION_IF (STRING_LENGTH "%i%" == 1) BEGIN + OUTER_TEXT_SPRINT "i" "0%i%" + END + ACTION_IF !(RESOURCE_CONTAINS "gtspell.ids" "^%type%%level%%i%[ %TAB%]+") BEGIN + APPEND "GTSPELL.IDS" "%type%%level%%i% %idsName%" + OUTER_TEXT_SPRINT "resName" "%prefix%%level%%i%" + OUTER_SET "i" = "%max%" // kill FOR-loop + END + END + END + // + ACTION_IF "%sort%" BEGIN + LAF "SORT_IDS_FILE" STR_VAR "idsFile" = "gtspell" END + END + END ELSE BEGIN + LAF "GT_RES_NUM_OF_SPELL_NAME" STR_VAR "spell_name" = "%idsName%" RET "resName" = "spell_res" END + END +END + +// auxiliary function // + +DEFINE_DIMORPHIC_FUNCTION "GT_RES_NUM_OF_SPELL_NAME" +STR_VAR + "spell_name" = "" +RET + "spell_num" + "spell_res" +BEGIN + OUTER_TEXT_SPRINT "spell_res" "" + OUTER_SET "spell_num" = "-1" + // + COPY_EXISTING - "gtspell.ids" "override" + COUNT_2DA_COLS "cols" + READ_2DA_ENTRIES_NOW "read_gtspell" "%cols%" + FOR ("i" = 0 ; "%i%" < "%read_gtspell%" ; "i" += 1) BEGIN + READ_2DA_ENTRY_FORMER "read_gtspell" "%i%" 1 "current_spell_name" + PATCH_IF ("%current_spell_name%" STR_EQ "%spell_name%") BEGIN + READ_2DA_ENTRY_FORMER "read_gtspell" "%i%" 0 "spell_num" + INNER_PATCH "%spell_num%" BEGIN + READ_ASCII 0x0 "type" (1) + READ_ASCII 0x1 "spell_id" (3) + PATCH_MATCH "%type%" WITH + 1 BEGIN + TEXT_SPRINT "spell_res" "gtpr%spell_id%" + END + 2 BEGIN + TEXT_SPRINT "spell_res" "gtwi%spell_id%" + END + 3 BEGIN + TEXT_SPRINT "spell_res" "gtin%spell_id%" + END + 4 BEGIN + TEXT_SPRINT "spell_res" "gtcl%spell_id%" + END + DEFAULT + PATCH_FAIL "Should not happen" + END + END + SET "i" = "%read_gtspell%" // kill FOR-loop + END + END + BUT_ONLY_IF_IT_CHANGES +END + +// auxiliary function // + +DEFINE_DIMORPHIC_FUNCTION "GT_NAME_NUM_OF_SPELL_RES" +STR_VAR + "spell_res" = "" +RET + "spell_num" + "spell_name" +BEGIN + OUTER_TEXT_SPRINT "spell_name" "" + OUTER_SET "spell_num" = "-1" + // + COPY_EXISTING - "gtspell.ids" "override" + COUNT_2DA_COLS "cols" + READ_2DA_ENTRIES_NOW "read_gtspell" "%cols%" + FOR ("i" = 0 ; "%i%" < "%read_gtspell%" ; "i" += 1) BEGIN + READ_2DA_ENTRY_FORMER "read_gtspell" "%i%" 0 "current_spell_num" + INNER_PATCH "%spell_res%" BEGIN + READ_ASCII 0x2 "type" (2) + READ_ASCII 0x4 "spell_id" (3) + PATCH_MATCH "%type%" WITH + "pr" BEGIN + TEXT_SPRINT "spell_num" "1%spell_id%" + END + "wi" BEGIN + TEXT_SPRINT "spell_num" "2%spell_id%" + END + "in" BEGIN + TEXT_SPRINT "spell_num" "3%spell_id%" + END + "cl" BEGIN + TEXT_SPRINT "spell_num" "4%spell_id%" + END + DEFAULT + PATCH_FAIL "Should not happen" + END + END + PATCH_IF ("%current_spell_num%" == "%spell_num%") BEGIN + READ_2DA_ENTRY_FORMER "read_gtspell" "%i%" 1 "spell_name" + SET "i" = "%read_gtspell%" // kill FOR-loop + END + END + BUT_ONLY_IF_IT_CHANGES +END + +/* ++------------------------------------------------------------------------------------------------------+ +| SORT_IDS_FILE | ++------------------------------------------------------------------------------------------------------+ +| Sort "%idsFile%" in ascending numerical order | +| - Do not use without a real reason (it may take up to several seconds... probably not a big deal...) | ++------------------------------------------------------------------------------------------------------+ +*/ + +DEFINE_DIMORPHIC_FUNCTION "SORT_IDS_FILE" +STR_VAR + "idsFile" = "" +BEGIN + ACTION_CLEAR_ARRAY "ids_sorted" + // + COPY_EXISTING "%idsFile%.ids" "override" + COUNT_2DA_COLS "cols" + READ_2DA_ENTRIES_NOW "sort_ids_file" "%cols%" + // Header + READ_2DA_ENTRY_FORMER "sort_ids_file" 0 0 "value" + PATCH_IF ("%value%" STR_EQ "IDS") BEGIN + SET "#_entries" = "%sort_ids_file%" - 1 + END ELSE BEGIN + SET "#_entries" = "%sort_ids_file%" + END + // Body + FOR ("row" = 0 ; "%row%" < "%sort_ids_file%" ; "row" += 1) BEGIN + READ_2DA_ENTRY_FORMER "sort_ids_file" "%row%" 0 "value" + PATCH_IF ("%value%" STRING_COMPARE_CASE "IDS") BEGIN + READ_2DA_ENTRY_FORMER "sort_ids_file" "%row%" 1 "identifier" + DEFINE_ASSOCIATIVE_ARRAY "ids_sorted" BEGIN + "%value%" , "%row%" => "%identifier%" // preserve duplicate entries + END + END + END + // Main + DELETE_BYTES 0x0 BUFFER_LENGTH + INSERT_2DA_ROW 0 0 "IDS V1.0" + INSERT_2DA_ROW 1 0 "%#_entries%" + SORT_ARRAY_INDICES "ids_sorted" NUMERICALLY + SET "row" = 2 + PHP_EACH "ids_sorted" AS "value" => "identifier" BEGIN + INSERT_2DA_ROW "%row%" 0 "%value% %identifier%" + SET "row" += 1 + END + BUT_ONLY_IF_IT_CHANGES END \ No newline at end of file diff --git a/cdtweaks/readme-cdtweaks.html b/cdtweaks/readme-cdtweaks.html index aebf1d25..f6d2104a 100644 --- a/cdtweaks/readme-cdtweaks.html +++ b/cdtweaks/readme-cdtweaks.html @@ -113,7 +113,9 @@

Contents

+

The Tweaks Anthology has many components, which can be installed completely independently of one another. For convenience, they are grouped into six broad categories: Cosmetic Changes, Content Changes, Rule Changes, Convenience Tweaks and/or Cheats, Joinable NPC Tweaks, and NWN-Style Feats. At installation time, players can select or ignore entire categories to streamline the installation process.

+

Italics running underneath each component indicate availability for various games. Unless specified, a component will work on a game with or without its expansions unless noted, e.g. a component labeled BG2 would work on Baldur's Gate II with or without the Throne of Bhaal expansion. If the expansion is required or forbidden, the note will either be BG2 (requires ToB) or BG2 (without ToB) respectively. Components that are not available for your game will be skipped automatically by the installer.

@@ -1397,13 +1399,45 @@

Joinable

NWN-Style Feats

-

Components in this category are inspired from NWN and are aimed at providing new class/kit abilities. All components in this section require EEex and work on BGEE, BG2EE, IWDEE, and EET.

+

Components in this category are inspired from NWN and are aimed at providing new class/kit abilities. All components in this section require EEex and work on BGEE, BG2EE, IWDEE, and EET.

+ +

Disarm class feat for Rogues [Luke]
+ EEex

+

This component aims at implementing the NWN feat Disarm.
+ The character can attempt to disarm an opponent in melee combat. Attempting a disarm applies a -6 penalty to the character's attack roll, and the combatant with the larger weapon gains a +2 bonus per size category of difference. A successful hit deals normal damage, and if the opponent fails a Save vs. Breath, then the weapon flies from the opponent's hands.
+ Notes:

+
    +
  • Use: selected, unlimited uses per day. Only available to Rogues (starting from level 1).
  • +
  • When the weapon flies from the opponent's hands, the attacker automatically picks it up (provided its Inventory is not full).
  • +
  • This feat cannot be used while wielding a ranged weapon.
  • +
  • + As far as weapon sizes are concerned: +
      +
    • Small: unarmed fists, clubs, maces, slings, darts, daggers, short swords.
    • +
    • Medium: battle axes, throwing axes, long swords, shortbows, crossbows, morningstars, scimitars, war hammers.
    • +
    • Large: bastard swords, katanas, halberds, staves, two-handed swords, spears, flails.
    • +
    • + Some examples: +
        +
      • if the attacker is wielding a dagger and the defender is wielding a katana, the saving throw penalty is -4 (i.e., it's harder to succeed).
      • +
      • if the attacker is wielding a katana and the defender is wielding a dagger, the saving throw penalty is +4 (i.e., it's easier to succeed).
      • +
      • if the attacker is wielding a dagger and the defender is wielding a shortbow, the saving throw penalty is -2 (i.e., it's harder to succeed).
      • +
      • if the attacker is wielding a scimitar and the defender is wielding a dagger, the saving throw penalty is +2 (i.e., it's easier to succeed).
      • +
      • if the attacker is wielding a katana and the defender is wielding a katana, the saving throw penalty is 0 (i.e., neutral).
      • +
      +
    • +
    +
  • +
  • If the opponent is dual-wielding, only the main-hand weapon can be disarmed.
  • +
  • Not all creatures can be disarmed. Natural weapons cannot be disarmed, nor can cursed / magically created weapons.
  • +

Spellcaster Skill/Counterspell Class Talent [Luke]
BGEE, BG2EE, IWDEE, EET (requires EEex)

This component aims at implementing the NWN feat Spellcraft/Counterspell. diff --git a/cdtweaks/setup-cdtweaks.tp2 b/cdtweaks/setup-cdtweaks.tp2 index 55d5ad5f..011473bf 100644 --- a/cdtweaks/setup-cdtweaks.tp2 +++ b/cdtweaks/setup-cdtweaks.tp2 @@ -4888,6 +4888,21 @@ REQUIRE_PREDICATE GAME_IS ~bgee bg2ee eet~ @25 REQUIRE_PREDICATE MOD_IS_INSTALLED "EEex.tp2" 0 @29 LABEL ~cd_tweaks_dorns_sword~ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +///// \\\\\ +///// Disarm class feat for Rogues \\\\\ +///// \\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ + +BEGIN @600000 DESIGNATED 6000 +GROUP @30 +REQUIRE_PREDICATE GAME_IS ~bgee bg2ee eet iwdee~ @25 +REQUIRE_PREDICATE MOD_IS_INSTALLED ~EEex.tp2~ 0 @29 +REQUIRE_PREDICATE FILE_EXISTS ~cdtweaks/languages/%LANGUAGE%/disarm.tra~ @7 +LABEL ~cd_tweaks_nwn_disarm~ + /////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\///// /////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\///// ///// \\\\\///// @@ -5230,3 +5245,4 @@ GROUP @30 REQUIRE_PREDICATE GAME_IS ~bgee bg2ee eet iwdee~ @25 REQUIRE_PREDICATE MOD_IS_INSTALLED "EEex.tp2" 0 @29 LABEL ~cd_tweaks_nwn_uncanny_dodge~ +