diff --git a/cdtweaks/languages/english/knockdowm.tra b/cdtweaks/languages/english/knockdowm.tra new file mode 100644 index 0000000..8a4b548 --- /dev/null +++ b/cdtweaks/languages/english/knockdowm.tra @@ -0,0 +1,16 @@ +@0 = "Knockdown" + +@1 = "Knockdown + +A character with this feat can attempt to knock his opponents to the ground. The character makes an attack roll at -4, and if successful the defender makes a Save vs. Death. If failed, the defender is knocked to a prone position." + +@50 = "Prone" + +@100 = "The selected target is out of range" +@101 = "The selected target is already prone" +@102 = "The selected target is too large" +@103 = "This feat cannot be used while wielding a ranged weapon" + +@104 = "The character cannot perform more than one action per round" + +@105 = "Unaffected by effects from Knockdown" \ No newline at end of file diff --git a/cdtweaks/languages/english/weidu.tra b/cdtweaks/languages/english/weidu.tra index 06daf4e..3750744 100644 --- a/cdtweaks/languages/english/weidu.tra +++ b/cdtweaks/languages/english/weidu.tra @@ -802,3 +802,13 @@ Use Baldur.lua options: a7_interval_ini @504000 = ~Allow Yeslick to Use Axes~ @505000 = ~Ensure Shar-Teel Doesn't Die in the Original Challenge~ + +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +///// \\\\\ +///// NWN-ish feats collection \\\\\ +///// \\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ + +@600010 = "Knockdown class feat for Fighters and Monks [Luke]" diff --git a/cdtweaks/languages/italian/knockdown.tra b/cdtweaks/languages/italian/knockdown.tra new file mode 100644 index 0000000..ff54744 --- /dev/null +++ b/cdtweaks/languages/italian/knockdown.tra @@ -0,0 +1,16 @@ +@0 = "Buttare a Terra" + +@1 = "Buttare a Terra + +Un personaggio con questo talento può tentare di far cadere a terra i suoi avversari. Il personaggio effettua un attacco con una penalità di -4 ai tiri per colpire e, se ha successo, il difensore effettua un tiro-salvezza contro Morte. Se fallisce, il difensore viene buttato a terra." + +@50 = "Steso a Terra" + +@100 = "Il bersaglio selezionato è fuori portata" +@101 = "Il bersaglio selezionato è già a terra" +@102 = "Il bersaglio selezionato è troppo grande" +@103 = "Questo talento non può essere usato mentre si brandisce un'arma a distanza" + +@104 = "Il personaggio non può compiere più di un'azione per round" + +@105 = "Non soggetto agli effetti di Buttare a Terra" \ No newline at end of file diff --git a/cdtweaks/languages/italian/weidu.tra b/cdtweaks/languages/italian/weidu.tra index 738ea95..0b8be3f 100644 --- a/cdtweaks/languages/italian/weidu.tra +++ b/cdtweaks/languages/italian/weidu.tra @@ -718,3 +718,13 @@ Usa opzioni di Baldur.lua: a7_interval_ini @504000 = ~Permettere a Yeslick di usare le asce~ @505000 = ~Assicura che Shar-Teel non muoia nella sfida iniziale~ + +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +///// \\\\\ +///// Raccolta di talenti in stile NWN \\\\\ +///// \\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ + +@600010 = "Aggiungi talento di classe Buttare a Terra per i Guerrieri e i Monaci [Luke]" diff --git a/cdtweaks/lib/comp_6010.tpa b/cdtweaks/lib/comp_6010.tpa new file mode 100644 index 0000000..77749ea --- /dev/null +++ b/cdtweaks/lib/comp_6010.tpa @@ -0,0 +1,17 @@ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +///// \\\\\ +///// Knockdown class feat for Fighters and Monks \\\\\ +///// \\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ + +WITH_SCOPE BEGIN + INCLUDE "cdtweaks\luke\misc.tph" + INCLUDE "cdtweaks\ardanis\functions.tph" + // + INCLUDE "cdtweaks\lib\knockdown.tph" + WITH_TRA "cdtweaks\languages\english\knockdown.tra" "cdtweaks\languages\%LANGUAGE%\knockdown.tra" BEGIN + LAF "KNOCKDOWN" END + END +END \ No newline at end of file diff --git a/cdtweaks/lib/knockdown.tph b/cdtweaks/lib/knockdown.tph new file mode 100644 index 0000000..1cd561d --- /dev/null +++ b/cdtweaks/lib/knockdown.tph @@ -0,0 +1,77 @@ +DEFINE_ACTION_FUNCTION "KNOCKDOWN" +BEGIN + LAF "GT_ADD_SPELL" + INT_VAR + "level" = 1 + "preferredSlot" = 44 + STR_VAR + "idsName" = "INNATE_KNOCKDOWN" + RET + "INNATE_KNOCKDOWN" = "resName" + END + // + //LAF "ADD_EXTENDED_STAT" INT_VAR "max" = 25 STR_VAR "identifier" = "GT_IGNORE_ACTION_ADD_SPRITE_STARTED_ACTION_LISTENER" END + // + WITH_SCOPE BEGIN + ACTION_TO_LOWER "INNATE_KNOCKDOWN" + // Knockdown (spl file) + CREATE "spl" "%INNATE_KNOCKDOWN%" + COPY_EXISTING "%INNATE_KNOCKDOWN%.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" = 30 STR_VAR "icon" = "%DEST_RES%B" END + // + LPF "ADD_SPELL_EFFECT" INT_VAR "opcode" = 402 "target" = 2 STR_VAR "resource" = "%DEST_RES%" END // Invoke lua + BUT_ONLY + // EFF + CREATE "eff" "%INNATE_KNOCKDOWN%" + COPY_EXISTING "%INNATE_KNOCKDOWN%.eff" "override" + WRITE_LONG 0x10 146 // Cast spell + WRITE_LONG 0x14 2 // Projectile target + WRITE_LONG 0x20 1 // Mode: instant/ignore level + WRITE_SHORT 0x2C 100 // prob1 + WRITE_ASCII 0x30 "%DEST_RES%" #8 // spl file + BUT_ONLY + // icons + LAF "ADD_STATDESC_ENTRY" INT_VAR "description" = RESOLVE_STR_REF (@50) STR_VAR "bam_file" = "GTPRONE" RET "feedback_icon" = "index" END + COPY "cdtweaks\luke\bam\innate\knockdown\spl_icon.bam" "override\%INNATE_KNOCKDOWN%b.bam" "cdtweaks\luke\bam\gtprone.bam" "override" + // + CREATE "eff" "gtprone" + COPY_EXISTING "gtprone.eff" "override" + WRITE_LONG 0x10 39 // Sleep + WRITE_LONG 0x14 2 // Projectile target + WRITE_LONG 0x20 1 // p2: wake on damage? no + WRITE_SHORT 0x2C 100 // prob1 + WRITE_LONG 0x48 "%feedback_icon%" // icon + BUT_ONLY + END + // lua + WITH_SCOPE BEGIN + OUTER_SET "feedback_strref_already_prone" = RESOLVE_STR_REF (@101) + OUTER_SET "feedback_strref_too_large" = RESOLVE_STR_REF (@102) + OUTER_SET "feedback_strref_melee_only" = RESOLVE_STR_REF (@103) + OUTER_SET "feedback_strref_aura_free" = RESOLVE_STR_REF (@104) + // + WITH_SCOPE BEGIN + ACTION_TO_LOWER "INNATE_KNOCKDOWN" + COPY "cdtweaks\luke\bam\innate\knockdown\portrait_icon.bam" "override\%INNATE_KNOCKDOWN%d.bam" + END + LAF "ADD_STATDESC_ENTRY" INT_VAR "description" = RESOLVE_STR_REF (@0) STR_VAR "bam_file" = "%INNATE_KNOCKDOWN%D" RET "feedback_icon_can_knockdown" = "index" END + // + LAF "APPEND_LUA_FUNCTION" STR_VAR "description" = "Innate Abilities" "sourceFileSpec" = "cdtweaks\luke\lua\innate\knockdown.lua" "destRes" = "m_gtspin" END + END + // + LAF "APPEND_LUA_FUNCTION" STR_VAR "description" = "Lua Tools" "sourceFileSpec" = "cdtweaks\luke\lua\tools\key_exists.lua" "destRes" = "m_gttool" END + LAF "APPEND_LUA_FUNCTION" STR_VAR "description" = "Utility Functions / Listeners" "sourceFileSpec" = "cdtweaks\luke\lua\utility\effect_check.lua" "destRes" = "m_gtutil" 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/gtprone.bam b/cdtweaks/luke/bam/gtprone.bam new file mode 100644 index 0000000..f64ae14 Binary files /dev/null and b/cdtweaks/luke/bam/gtprone.bam differ diff --git a/cdtweaks/luke/bam/innate/knockdown/portrait_icon.bam b/cdtweaks/luke/bam/innate/knockdown/portrait_icon.bam new file mode 100644 index 0000000..b35d38d Binary files /dev/null and b/cdtweaks/luke/bam/innate/knockdown/portrait_icon.bam differ diff --git a/cdtweaks/luke/bam/innate/knockdown/spl_icon.bam b/cdtweaks/luke/bam/innate/knockdown/spl_icon.bam new file mode 100644 index 0000000..841b7cd Binary files /dev/null and b/cdtweaks/luke/bam/innate/knockdown/spl_icon.bam differ diff --git a/cdtweaks/luke/lua/innate/knockdown.lua b/cdtweaks/luke/lua/innate/knockdown.lua new file mode 100644 index 0000000..7327f3d --- /dev/null +++ b/cdtweaks/luke/lua/innate/knockdown.lua @@ -0,0 +1,292 @@ +--[[ ++------------------------------------------------------------+ +| cdtweaks, NWN-ish Knockdown ability for Fighters and Monks | ++------------------------------------------------------------+ +--]] + +-- NWN-ish Knockdown ability. Creatures already on the ground / levitating / etc. should be immune to this feat -- + +local cdtweaks_ImmuneToKnockdown = { + {"WEAPON"}, -- GENERAL.IDS + {"DRAGON", "BEHOLDER", "ANKHEG", "SLIME", "DEMILICH", "WILL-O-WISP", "SPECTRAL_UNDEAD", "SHADOW", "SPECTRE", "WRAITH", "MIST", "GENIE", "ELEMENTAL", "SALAMANDER"}, -- RACE.IDS + {"WIZARD_EYE", "SPECTRAL_TROLL", "SPIDER_WRAITH"}, -- CLASS.IDS + -- ANIMATE.IDS + { + "DOOM_GUARD", "DOOM_GUARD_LARGER", + "SNAKE", "BLOB_MIST_CREATURE", "MIST_CREATURE", "HAKEASHAR", "NISHRUU", "SNAKE_WATER", "DANCING_SWORD", + "SHADOW_SMALL", "SHADOW_LARGE", "WATER_WEIRD" + }, +} + +-- NWN-ish Knockdown ability (main) -- + +function %INNATE_KNOCKDOWN%(CGameEffect, CGameSprite) + local sourceSprite = EEex_GameObject_Get(CGameEffect.m_sourceId) -- CGameSprite + -- Get personal space + local sourcePersonalSpace = sourceSprite.m_animation.m_animation.m_personalSpace + local targetPersonalSpace = CGameSprite.m_animation.m_animation.m_personalSpace + -- + local class = GT_Resource_SymbolToIDS["class"] + -- + local targetGeneralStr = GT_Resource_IDSToSymbol["general"][CGameSprite.m_typeAI.m_General] + local targetRaceStr = GT_Resource_IDSToSymbol["race"][CGameSprite.m_typeAI.m_Race] + local targetClassStr = GT_Resource_IDSToSymbol["class"][CGameSprite.m_typeAI.m_Class] + local targetAnimateStr = GT_Resource_IDSToSymbol["animate"][CGameSprite.m_animation.m_animation.m_animationID] + -- + local targetIDS = {targetGeneralStr, targetRaceStr, targetClassStr, targetAnimateStr} + -- MAIN -- + -- immunity check + local found = false + do + for index, symbolList in ipairs(cdtweaks_ImmuneToKnockdown) do + for _, symbol in ipairs(symbolList) do + if targetIDS[index] == symbol then + found = true + break + end + end + end + end + -- + if not found then + if (sourcePersonalSpace - targetPersonalSpace) >= -1 then + -- SLOT_FIST is always equipped, even if not in use... As a result, we want to apply op39 via op182... + local fistResRef = {} + if CGameSprite.m_typeAI.m_Class == class["MONK"] then + local monkfist = GT_Resource_2DA["monkfist"] + for lvl = 1, 50 do + if GT_LuaTool_KeyExists(GT_Resource_2DA, "monkfist", tostring(lvl), "RESREF") then + fistResRef[monkfist[tostring(lvl)]["RESREF"]] = true + end + end + else + local items = CGameSprite.m_equipment.m_items -- Array + local item = items:get(10) -- CItem + if item then + fistResRef[item.pRes.resref:get()] = true -- should be "FIST.ITM" + end + end + -- set ``savebonus`` + local savebonus = 0 + if (sourcePersonalSpace - targetPersonalSpace) > 0 then + savebonus = -4 + elseif (sourcePersonalSpace - targetPersonalSpace) < 0 then + savebonus = 4 + end + -- + local effectCodes = {} + for resref, _ in pairs(fistResRef) do + table.insert(effectCodes, {["op"] = 182, ["res"] = resref, ["res2"] = "GTPRONE"}) -- apply EFF while FIST/MFIST[1-8] is equipped (i.e. always). We need this to bypass op101 and make op39 uncurable (i.e. immune to op2) + end + table.insert(effectCodes, {["op"] = 206, ["p1"] = %feedback_strref_already_prone%, ["res"] = "%INNATE_KNOCKDOWN%B"}) -- protection from spell + -- + for _, attributes in ipairs(effectCodes) do + CGameSprite:applyEffect({ + ["effectID"] = attributes["op"] or EEex_Error("opcode number not specified"), + ["effectAmount"] = attributes["p1"] or 0, + ["duration"] = 6, + ["savingThrow"] = 0x4, -- save vs. death + ["saveMod"] = savebonus, + ["m_res2"] = attributes["res2"] or "", + ["m_sourceRes"] = "%INNATE_KNOCKDOWN%B", + ["m_sourceType"] = 1, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + end + else + CGameSprite:applyEffect({ + ["effectID"] = 139, -- display string + ["effectAmount"] = %feedback_strref_too_large%, + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + end + else + CGameSprite:applyEffect({ + ["effectID"] = 324, -- immunity to resource and message + ["res"] = CGameEffect.m_sourceRes:get(), + ["sourceID"] = CGameEffect.m_sourceId, + ["sourceTarget"] = CGameEffect.m_sourceTarget, + }) + end +end + +-- NWN-ish Knockdown ability. Make sure one and only one attack roll is performed -- + +EEex_Opcode_AddListsResolvedListener(function(sprite) + -- Sanity check + if not EEex_GameObject_IsSprite(sprite) then + return + end + -- + local isWeaponRanged = EEex_Trigger_ParseConditionalString("IsWeaponRanged(Myself)") + -- + if sprite:getLocalInt("cdtweaksKnockdown") == 1 then + if GT_Utility_EffectCheck(sprite, {["op"] = 0xF8, ["res"] = "%INNATE_KNOCKDOWN%"}) then + if sprite.m_startedSwing == 1 and sprite:getLocalInt("gtKnockdownSwing") == 0 and not isWeaponRanged:evalConditionalAsAIBase(sprite) then + sprite:setLocalInt("gtKnockdownSwing", 1) + elseif (sprite.m_startedSwing == 0 and sprite:getLocalInt("gtKnockdownSwing") == 1) or isWeaponRanged:evalConditionalAsAIBase(sprite) then + sprite:setLocalInt("gtKnockdownSwing", 0) + -- + sprite.m_curAction.m_actionID = 0 -- nuke current action + -- + EEex_GameObject_ApplyEffect(sprite, + { + ["effectID"] = 321, -- remove effects by resource + ["res"] = "%INNATE_KNOCKDOWN%", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + -- + if isWeaponRanged:evalConditionalAsAIBase(sprite) then + sprite:applyEffect({ + ["effectID"] = 139, -- display string + ["effectAmount"] = %feedback_strref_melee_only%, + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + end + end + end + end + -- + isWeaponRanged:free() +end) + +-- NWN-ish Knockdown ability. Morph the spell action into an attack action -- + +EEex_Action_AddSpriteStartedActionListener(function(sprite, action) + local ea = GT_Resource_SymbolToIDS["ea"] + -- + if sprite:getLocalInt("cdtweaksKnockdown") == 1 then + if action.m_actionID == 31 and action.m_string1.m_pchData:get() == "%INNATE_KNOCKDOWN%" then + if EEex_Sprite_GetCastTimer(sprite) == -1 then + -- + local effectCodes = { + {["op"] = 321, ["res"] = "%INNATE_KNOCKDOWN%"}, -- remove effects by resource + {["op"] = 284, ["p1"] = -4}, -- melee thac0 bonus + {["op"] = 142, ["p2"] = %feedback_icon_can_knockdown%}, -- feedback icon + {["op"] = 248, ["res"] = "%INNATE_KNOCKDOWN%"}, -- melee hit effect + } + -- + for _, attributes in ipairs(effectCodes) do + sprite:applyEffect({ + ["effectID"] = attributes["op"] or EEex_Error("opcode number not specified"), + ["effectAmount"] = attributes["p1"] or 0, + ["dwFlags"] = attributes["p2"] or 0, + ["res"] = attributes["res"] or "", + ["durationType"] = 1, + ["m_sourceRes"] = "%INNATE_KNOCKDOWN%", + ["m_sourceType"] = 1, + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + end + -- + sprite:setLocalInt("gtKnockdownSwing", 0) + -- + if sprite.m_typeAI.m_EnemyAlly < ea["GOODCUTOFF"] then + action.m_actionID = 3 -- Attack() + else + action.m_actionID = 134 -- AttackReevaluate() + action.m_specificID = 100 -- ReevaluationPeriod + end + -- + sprite.m_castCounter = 0 + else + action.m_actionID = 0 -- nuke current action + -- + EEex_GameObject_ApplyEffect(sprite, + { + ["effectID"] = 321, -- remove effects by resource + ["res"] = "%INNATE_KNOCKDOWN%", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + EEex_GameObject_ApplyEffect(sprite, + { + ["effectID"] = 139, -- display string + ["effectAmount"] = %feedback_strref_aura_free%, + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + end + else + EEex_GameObject_ApplyEffect(sprite, + { + ["effectID"] = 321, -- remove effects by resource + ["res"] = "%INNATE_KNOCKDOWN%", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + end + end +end) + +-- cdtweaks, NWN-ish Knockdown 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("cdtweaksKnockdown", 1) + -- + local effectCodes = { + {["op"] = 172}, -- remove spell + {["op"] = 171}, -- give spell + } + -- + for _, attributes in ipairs(effectCodes) do + sprite:applyEffect({ + ["effectID"] = attributes["op"] or -1, + ["res"] = "%INNATE_KNOCKDOWN%", + ["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 == "MONK" or spriteClassStr == "FIGHTER" or spriteClassStr == "FIGHTER_MAGE_THIEF" or spriteClassStr == "FIGHTER_MAGE_CLERIC" + or (spriteClassStr == "FIGHTER_MAGE" and (EEex_IsBitUnset(spriteFlags, 0x3) or spriteLevel2 > spriteLevel1)) + or (spriteClassStr == "FIGHTER_CLERIC" and (EEex_IsBitUnset(spriteFlags, 0x3) or spriteLevel2 > spriteLevel1)) + or (spriteClassStr == "FIGHTER_THIEF" and (EEex_IsBitUnset(spriteFlags, 0x3) or spriteLevel2 > spriteLevel1)) + or (spriteClassStr == "FIGHTER_DRUID" and (EEex_IsBitUnset(spriteFlags, 0x3) or spriteLevel2 > spriteLevel1)) + -- + if sprite:getLocalInt("cdtweaksKnockdown") == 0 then + if gainAbility then + gain() + end + else + if gainAbility then + -- do nothing + else + -- Mark the creature as 'feat removed' + sprite:setLocalInt("cdtweaksKnockdown", 0) + -- + sprite:applyEffect({ + ["effectID"] = 172, -- remove spell + ["res"] = "%INNATE_KNOCKDOWN%", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + sprite:applyEffect({ + ["effectID"] = 321, -- remove effects by resource + ["res"] = "%INNATE_KNOCKDOWN%", + ["sourceID"] = sprite.m_id, + ["sourceTarget"] = sprite.m_id, + }) + end + end +end) diff --git a/cdtweaks/luke/lua/m_gttbls.lua b/cdtweaks/luke/lua/m_gttbls.lua index 6861a6c..bde99f9 100644 --- a/cdtweaks/luke/lua/m_gttbls.lua +++ b/cdtweaks/luke/lua/m_gttbls.lua @@ -7,7 +7,7 @@ GT_Resource_SymbolToIDS = {} EEex_GameState_AddInitializedListener(function() -- 2DA EEex_Utility_NewScope(function() - local resources = { "STRMOD", "STRMODEX", "DEXMOD", "STYLBONU", "SNEAKATT" } + local resources = { "STRMOD", "STRMODEX", "DEXMOD", "STYLBONU", "SNEAKATT", "MONKFIST" } -- for _, v in ipairs(resources) do local data = EEex_Resource_Load2DA(v) diff --git a/cdtweaks/luke/lua/tools/key_exists.lua b/cdtweaks/luke/lua/tools/key_exists.lua new file mode 100644 index 0000000..05f04d3 --- /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 f539c3e..0ba8ef7 100644 --- a/cdtweaks/luke/misc.tph +++ b/cdtweaks/luke/misc.tph @@ -413,4 +413,234 @@ BEGIN COPY_EXISTING "%destRes%.lua" "override" APPEND_FILE_EVALUATE TEXT "%sourceFileSpec%" BUT_ONLY UNLESS "%fileContent%" +END + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/* + +Custom ADD_SPELL + +*/ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +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 !(FILE_EXISTS_IN_GAME "%prefix%%level%%preferredSlot%.spl") 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 !(FILE_EXISTS_IN_GAME "%prefix%%level%%i%.spl") 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 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/* + +*/ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +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 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/* + +*/ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +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 84c933f..6637d17 100644 --- a/cdtweaks/readme-cdtweaks.html +++ b/cdtweaks/readme-cdtweaks.html @@ -1461,6 +1461,42 @@

Joinable +

NWN-ish Feats Collection

+ +
+
+
+

Components in this category are inspired from NWN and are aimed at providing new class/kit abilities.

+ +

Knockdown class feat for Fighters and Monks [Luke]
+ EEex

+

This component aims at implementing the NWN feat Knockdown.
+ A character with this feat can attempt to knock his opponents to the ground. The character makes an attack roll at -4, and if successful the defender makes a Save vs. Death. If failed, the defender is knocked to a prone position.
+ Notes:

+
    +
  • Use: selected, unlimited uses per day. Only available to Fighters and Monks (starting from level 1).
  • +
  • The prone state lasts for one round.
  • +
  • This feat cannot be used while wielding a ranged weapon.
  • +
  • + A character can only make a knockdown attempt on opponents that are one size category larger than himself, or smaller. +
      +
    • As a rule of thumb, look at the defender's circle size: if smaller, equal or just a little bit bigger than the one of the attacker, then it is fine; otherwise, the creature is too large to be knocked down.
    • +
    • + On top of it: +
        +
      • if the attacker is larger than the defender, the saving throw penalty is -4 (i.e., it's easier to succeed).
      • +
      • Conversely, the saving throw penalty is +4 (i.e., it's harder to succeed).
      • +
      +
    • +
    +
  • +
  • Creatures already on the ground (f.i. snakes, slimes, salamanders ...), magically levitating creatures (beholders, demiliches, ...) and certain special creatures (f.i. spectral undead, elementals, dragons ...) cannot be knocked down.
  • +
+
+ +

Contact Information

diff --git a/cdtweaks/setup-cdtweaks.tp2 b/cdtweaks/setup-cdtweaks.tp2 index 5e97fb6..0aa53f2 100644 --- a/cdtweaks/setup-cdtweaks.tp2 +++ b/cdtweaks/setup-cdtweaks.tp2 @@ -4903,3 +4903,26 @@ GROUP @0 REQUIRE_PREDICATE GAME_IS ~bgee bg2ee eet~ @25 REQUIRE_PREDICATE MOD_IS_INSTALLED "EEex.tp2" 0 @29 LABEL ~cd_tweaks_dorns_sword~ + +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +///// \\\\\ +///// NWN-ish feats collection \\\\\ +///// \\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ + +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +///// \\\\\ +///// Knockdown class feat for Fighters and Monks \\\\\ +///// \\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ +/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ + +BEGIN @600010 DESIGNATED 6010 +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%/knockdown.tra~ @7 +LABEL ~cd_tweaks_nwn_knockdown~ \ No newline at end of file