Jump to content
CamDawg

Toss your semi-useful WeiDU macros here

Recommended Posts

It seems like everyone has a few macros that are fairly useful, but not quite useful enough to go bug Wisp for inclusion in WeiDU itself. I'd like to suggest a mega-thread where we can all dump anything that might be useful to others. This first post will be updated descripts and links to the (hopefully thousands, millions of) macros that get posted below.

Post 2, Ardanis -

  • ADD_SPELL_HEADER, ADD_ITEM_HEADER - Used to add abilities/headers to spells and items
  • CREATE_EFFECT - Create effects whole cloth
  • CREATE_SPELL - Create spells
  • update_item_descriptions_to_bgee - Updates vanilla item descriptions to EE format, i.e. stripping Usable By section, so that you don't have to provide two strings for vanilla and EE descriptions.

Post 3, CamDawg -

  • cd_extend_bg_area_script - Used to easily extend area scripts when writing a BG/BGEE/BGT/Tutu mod - use the updated version in post 53
  • spell_to_innate - Used to convert arcane or divine spells into innate abilties
  • kit.ids fixer - Not really a macro, but needed to fix kit.ids for non-EE, non-Fixpacked BG2 games
  • cd_equip_item - finds the specified item in the creature file and equips it to the appropriate slot
  • cd_equip_weapon - finds the first valid weapon and equips it
  • cd_equip_weapon_specific - equips the specified weapon
  • cd_no_pickpocket - Moves an item that should not be pickpocketed into an inventory slot that is impossible to pickpocket from.
  • CD_EXTEND-O-MATIC - Used to arbitrarily create new spell headers out to the specified minimum level.
  • CD_MISSING_SPELL_HEADERS - Used to 'backfill' missing spell headers

Post 4, Ardanis -

  • tooltips - Adding new tooltips for the items in a few quick strokes

Post 5, Angel

  • add_area_actor - A wrapper for fj_area_struct that simplifies even further the process of adding an actor to an area.
  • remove_all_area_actors - Removes all actors from an area; useful for repurposing an area.
  • add_simple_trap - Another wrapper for fj_area_struct that allow for quickly creating a trap in an area.
  • add_area_actors_from_2da - Uses a table to mass-add actors to an area.

Post 6, Angel

  • add_energy_drain - Adds level drain to items or spells in engines that support energy drain, and pseudo-energy drain for those that don't
  • add_magic_missile_immunity - Adds magic missile immunity to an item or spell

Post 8, Angel

  • prevent_spell_effect_stacking - Prevents a spell from stacking with itself

Post 9, subtledoctor

  • add_hla, remove_hla, replce_hla - Macros for non-destructively editing HLA tables

Post 10, Angel

  • replace_cre_script - find and replace a creature script

Post 11, k4thos

  • ADD_MAP_ICONS_EE - BAM v2 worldmap icon file patching (for EE games that use PVRZ based map icons)
  • ADD_WORLDMAP_TBL - Adds multiple areas and links to the worldmap and saves using BP-BGT Worldmap TBL files convention

Post 12, Angel

  • make_magical - Add magical flag, lore, and enchantment level to item

Post 16, argent77

  • TO_HEX_NUMBER - A decimal to hexadecimal number converter
  • FIND_FREE_ANIM_SLOT - This function attempts to find a free creature animation slot in a given range

Post 27, Gwendolyne

  • GW_FIND_DLG_RESPONSE_STRING - a homemade function used to extend existing dlg

Post 35, K4thos

  • 2DA_MISSING_COLS - Patch and action function that fixes missing column entries in 2da files.

Post 36, Angel

  • make_summon - turn a creature into a summoned creature

Post 39, Angel

  • erase_journal_entries_on_bg2_transition - Finds and scripts deletion of BG journal entries during BGT's transition to BG2

Post 46, Angel

  • make_illusion - a variant of make_summon (post 36) that makes the monster illusionary

Post 49, K4thos

  • JOINABLE_NPC_ARRAY - Action macro that generates JOINABLE_NPC_ARRAY table which can be used to patch joinable NPC CRE files (more reliable method than checking CRE BIO offset)

Post 50, Sam.

  • ps_toolset - An entire suite of functions for manipulating and patching tilesets

Post 51, argent77

  • a7_auto_apply_spl_effect - a patch function that can be used to add multiple effects to items or spells without much effort

Post 52, CamDawg

  • CD_DOUBLE_DAMAGE - Combines separate damage opcodes to utilize EE's save-for-half damage flag

Post 53, CamDawg

  • cd_extend_bg_area_script - Used to easily extend area scripts when writing a BG/BGEE/BGT/Tutu mod, now supporting tra files

Post 56, Aquadrizzt

  • qd_multiclass - allows you to assign class features to multiclass kits in the 2.0+ engine

Post 61, c4_Angel

  • C4_FIND_STAT_SLOT is an action function, checks all creature/item/spell files in game which have effect using opcode #318 (vanilla game) or opcode #328 (EE game), also values in STATS.IDS or SPLSTATE.IDS, find an unused one from 501 to 32767, and append a line with an user defined "identifier"(requred). Return "available_stat" for further usage.
  • C4_ADD_UNIQUE_MARK is a patch function, calls C4_FIND_STAT_SLOT, and uses function CLONE_EFFECT to add an effect to creature/item/spell file with an user defined "identifier"(also requred), other parameters of the "match_" part are same with CLONE_EFFECT.

Post 68, CamDawg

  • cd_new_summon_table (EE) - an action function, will add a new reference for you and then return the value for use in your spells
  • cd_new_portrait_icon (EE) - another action function, will find the next available entry and add your strref and bam file to the list.

Post 69, argent77

  • SORT_ARRAY - This macro can be used to sort arrays of numbers or strings in ascending or descending order.

Post 72, K4thos

  • REPLACE_MULTILINE - Patch function that replaces set or all occurrences of the given regexp pattern in the file with the given string.

Post 73, CamDawg

  • cd_clone_spell - Used to clone spells, updating any self-referencing protections on both the source and cloned spells. Can optionally convert spells to innates in the process.
Edited by CamDawg

Share this post


Link to post

ADD_SPELL_HEADER

ADD_ITEM_HEADER

CREATE_EFFECT

Also CREATE_SPELL, but it's probably not a thing many would care about.

 

Parameters should be self-explanatory. Can also copy an existing one.

I had this for items as well, but I don't think I've got the code anywhere in close vicinity.

EDIT Here it is https://github.com/Gibberlings3/ItemRevisions/blob/master/item_rev/lib/macros.tpa

 

 

 

 

 

 

 

 

DEFINE_PATCH_FUNCTION ~ADD_SPELL_HEADER~
  INT_VAR
    type=1
    location=4
    target=1
    target_count=0
    range=0
    required_level=1
    speed=0
    projectile=1


    copy_header=0
    insert_point=~-1~
  STR_VAR
    icon=~~
  RET
    insert_point
BEGIN
  LPF ~FJ_SPL_ITM_REINDEX~ END
  hs=0x28


  READ_LONG 0x64 ho
  READ_SHORT 0x68 hc
  READ_LONG 0x6a eo
  insert_point = (insert_point>hc || insert_point<0) ? hc : insert_point
  copy_header = (copy_header<0) ? 0 : copy_header


  PATCH_IF copy_header>hc BEGIN
    PATCH_WARN ~Unable to copy %copy_header%th header, %SOURCE_FILE% contains only %hc% headers!~
  END ELSE BEGIN
    INSERT_BYTES ho+insert_point*hs hs
    hc+=1
    eo+=hs
    PATCH_IF copy_header BEGIN
      READ_SHORT ho+(copy_header - 1)*hs+0x1e ec
      READ_SHORT ho+(copy_header - 1)*hs+0x20 ei
      READ_ASCII eo+ei*0x30 effs (ec*0x30)
      READ_ASCII ho+(copy_header - 1)*hs copy (hs)
      WRITE_ASCIIE ho+insert_point*hs ~%copy%~ (hs)
    END
    WRITE_SHORT 0x68 hc
    WRITE_LONG 0x6a eo


    READ_SHORT 0x70 ei // technically, it is a counter
    FOR (i=ho;i<ho+hc*hs;i+=hs) BEGIN
      READ_SHORT i+0x1e ec
      WRITE_SHORT i+0x20 ei
      ei+=ec
    END


    PATCH_IF copy_header BEGIN
      READ_SHORT ho+insert_point*hs+0x1e ec
      READ_SHORT ho+insert_point*hs+0x20 ei
      INSERT_BYTES eo+ei*0x30 ec*0x30
      WRITE_ASCIIE eo+ei*0x30 ~%effs%~ (ec*0x30)
    END ELSE BEGIN
      off=ho+insert_point*hs
      WRITE_BYTE off type
      WRITE_BYTE off+0x2 location
      WRITE_ASCIIE off+0x4 ~%icon%~ (8)
      WRITE_BYTE off+0xc target
      WRITE_BYTE off+0xd target_count
      WRITE_SHORT off+0xe range
      WRITE_SHORT off+0x10 required_level
      WRITE_LONG off+0x12 speed
      WRITE_SHORT off+0x26 projectile
    END
  END
END

 

 

update_item_descriptions_to_bgee

Updates vanilla item descriptions to EE format, i.e. stripping Usable By section, so that you don't have to provide two strings for vanilla and EE descriptions.

https://github.com/Gibberlings3/ItemRevisions/blob/master/item_rev/lib/usability_description.tpa#L77

 

 

// make sure to add these two to your TRA files, adjust the numbers if needed
//OUTER_SPRINT usab @900000 // ~Usable[ %tab%]+[Bb]y[ %tab%]*:~
//OUTER_SPRINT unus @900001 // ~\(Not[ %tab%]+\|Un\)[Uu]sable[ %tab%]+[Bb]y[ %tab%]*:~
OUTER_SPRINT usab ~Usable[ %tab%]+[Bb]y[ %tab%]*:~
OUTER_SPRINT unus ~\(Not[ %tab%]+\|Un\)[Uu]sable[ %tab%]+[Bb]y[ %tab%]*:~
 
DEFINE_PATCH_FUNCTION ~update_item_descriptions_to_bgee~ BEGIN
  PATCH_IF (ENGINE_IS ~bgee bg2ee~) BEGIN
    FOR (index = 0x54 ; index >= 0x50 ; index -= 4) BEGIN // loop through descriptions
      READ_LONG index strref
      PATCH_IF (strref < 2147483646 && strref >= 0) BEGIN // verify description is valid
        READ_STRREF index description
        INNER_PATCH_SAVE new_desc ~%description%~ BEGIN
          REPLACE_TEXTUALLY ~\(\([%LNL%%MNL%%WNL%][ %TAB%]*\(%usab%\|%unus%\)[ %TAB%]*\)\(\([%LNL%%MNL%%WNL%].*\)*\)?\)~  ~~
        END
        SAY_EVALUATED index ~%new_desc%~
      END
    END
  END
END

 

 

Example usage 1, by item:

 

 

COPY_EXISTING ~mymod/myitem.itm~ override
  SAY_DESC ~Whatever~
  LPF update_item_descriptions_to_bgee END

 

Example usage 2, batch update (caution, only really useful if you've got a simple content mod and don't do any level 80 patching:

 

 

// run this after all items have been copied into the game, i.e. near the end of tp2
ACTION_IF (ENGINE_IS ~bgee bg2ee~) BEGIN

  ACTION_FOR_EACH directory IN items itm any_other_subfolder_that_contains_installable_items BEGIN
    ACTION_BASH_FOR ~MyModFolder/%directory%~ ~.*\.itm~ BEGIN // get a list of .itm files in the mod directory
      ACTION_IF FILE_EXISTS_IN_GAME ~%BASH_FOR_FILE%~ BEGIN // if those files have been copied into override
        COPY_EXISTING ~%BASH_FOR_FILE%~ override // load those files from override, i.e. with descriptions already SAY'ed
          LPF update_item_descriptions_to_bgee END // patch the description
        BUT_ONLY
      END
    END
  END

END

 

Edited by Ardanis

Share this post


Link to post

This one I wrote from frustration of trying to track down which areas have area scripts assigned, what they are, etc. when trying to write a mod for BG/BGEE/BGT/Tutu.

 

DEFINE_ACTION_FUNCTION cd_extend_bg_area_script

  INT_VAR extend_top = 0
  STR_VAR area       = ""
          script     = ""
BEGIN
          
  // make sure we have area scripts assigned
  COPY_EXISTING ~%area%.are~ ~override~
    READ_ASCII 0x94 a_script
    PATCH_IF ("%script%" STRING_COMPARE_CASE ~~ = 0) BEGIN // if blank
      PATCH_IF GAME_IS ~tutu tutu_totsc~ BEGIN // if Tutu
        WRITE_ASCIIE 0x95 ~%SOURCE_RES%~ #7
        WRITE_ASCII  0x94 ~_ar~
      END ELSE BEGIN // bgt
        WRITE_ASCIIE 0x94 ~%SOURCE_RES%~ #8
      END
      READ_ASCII 0x94 a_script
    END
    BUT_ONLY
  
  ACTION_IF extend_top = 1 THEN BEGIN

    EXTEND_TOP ~%a_script%.bcs~ ~%script%.baf~ EVALUATE_BUFFER

  END ELSE BEGIN

    EXTEND_BOTTOM ~%a_script%.bcs~ ~%script%.baf~ EVALUATE_BUFFER
    
  END
  
END

 




Invoked like so

LAF cd_extend_bg_area_script STR_VAR area = EVAL "%Ulcaster%" script = ~SUPERSECRETMOD/baf/et_3900~ END



This is using the excellent g3_xx_cpmvars libraries to set Ulcaster to the correct area. This will basically look in the area file, find the area script, assign one if none is assigned, and then EXTEND_TOP or _BOTTOM.




The classic kit.ids fixer; include this to fix kit.ids in an un-Fixpacked or non-EE game:

 

/////                                                  \\\\\
///// kit.ids fixer                                    \\\\\
/////                                                  \\\\\

COPY_EXISTING ~kit.ids~ ~override~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(TRUECLASS[ %TAB%%LNL%%MNL%%WNL%]+\)~              ~0x4000 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(BERSERKER[ %TAB%%LNL%%MNL%%WNL%]+\)~              ~0x4001 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(WIZARDSLAYER[ %TAB%%LNL%%MNL%%WNL%]+\)~           ~0x4002 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(KENSAI[ %TAB%%LNL%%MNL%%WNL%]+\)~                 ~0x4003 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(CAVALIER[ %TAB%%LNL%%MNL%%WNL%]+\)~               ~0x4004 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(INQUISITOR[ %TAB%%LNL%%MNL%%WNL%]+\)~             ~0x4005 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(UNDEADHUNTER[ %TAB%%LNL%%MNL%%WNL%]+\)~           ~0x4006 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(MAGESCHOOL_ABJURER[ %TAB%%LNL%%MNL%%WNL%]+\)~     ~0x0040 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(MAGESCHOOL_CONJURER[ %TAB%%LNL%%MNL%%WNL%]+\)~    ~0x0080 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(MAGESCHOOL_DIVINER[ %TAB%%LNL%%MNL%%WNL%]+\)~     ~0x0100 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(MAGESCHOOL_ENCHANTER[ %TAB%%LNL%%MNL%%WNL%]+\)~   ~0x0200 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(MAGESCHOOL_ILLUSIONIST[ %TAB%%LNL%%MNL%%WNL%]+\)~ ~0x0400 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(MAGESCHOOL_INVOKER[ %TAB%%LNL%%MNL%%WNL%]+\)~     ~0x0800 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(MAGESCHOOL_NECROMANCER[ %TAB%%LNL%%MNL%%WNL%]+\)~ ~0x1000 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(MAGESCHOOL_TRANSMUTER[ %TAB%%LNL%%MNL%%WNL%]+\)~  ~0x2000 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(MAGESCHOOL_GENERALIST[ %TAB%%LNL%%MNL%%WNL%]+\)~  ~0x4000 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(FERALAN[ %TAB%%LNL%%MNL%%WNL%]+\)~                ~0x4007 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(STALKER[ %TAB%%LNL%%MNL%%WNL%]+\)~                ~0x4008 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(BEASTMASTER[ %TAB%%LNL%%MNL%%WNL%]+\)~            ~0x4009 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(ASSASIN[ %TAB%%LNL%%MNL%%WNL%]+\)~                ~0x400A \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(BOUNTYHUNTER[ %TAB%%LNL%%MNL%%WNL%]+\)~           ~0x400B \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(SWASHBUCKLER[ %TAB%%LNL%%MNL%%WNL%]+\)~           ~0x400C \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(BLADE[ %TAB%%LNL%%MNL%%WNL%]+\)~                  ~0x400D \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(JESTER[ %TAB%%LNL%%MNL%%WNL%]+\)~                 ~0x400E \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(SKALD[ %TAB%%LNL%%MNL%%WNL%]+\)~                  ~0x400F \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(GODTALOS[ %TAB%%LNL%%MNL%%WNL%]+\)~               ~0x4013 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(GODHELM[ %TAB%%LNL%%MNL%%WNL%]+\)~                ~0x4014 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(GODLATHANDER[ %TAB%%LNL%%MNL%%WNL%]+\)~           ~0x4015 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(TOTEMIC[ %TAB%%LNL%%MNL%%WNL%]+\)~                ~0x4010 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(SHAPESHIFTER[ %TAB%%LNL%%MNL%%WNL%]+\)~           ~0x4011 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(BEASTFRIEND[ %TAB%%LNL%%MNL%%WNL%]+\)~            ~0x4012 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(BARBARIAN[ %TAB%%LNL%%MNL%%WNL%]+\)~              ~0x40000000 \1~
  REPLACE_TEXTUALLY ~^.+[ %TAB%]\(WILDMAGE[ %TAB%%LNL%%MNL%%WNL%]+\)~               ~0x80000000 \1~
  BUT_ONLY

APPEND ~kit.ids~ ~0x4000 TRUECLASS~     UNLESS ~^.+[ %TAB%]TRUECLASS[ %TAB%%LNL%%MNL%%WNL%]+~
APPEND ~kit.ids~ ~0x40000000 BARBARIAN~ UNLESS ~^.+[ %TAB%]BARBARIAN[ %TAB%%LNL%%MNL%%WNL%]+~
APPEND ~kit.ids~ ~0x80000000 WILDMAGE~  UNLESS ~^.+[ %TAB%]WILDMAGE[ %TAB%%LNL%%MNL%%WNL%]+~

ACTION_FOR_EACH crefile IN
  _imoen1
  _imoen2
  _imoen4
  _imoen6
  anomen10
  anomen12
  anomen6
  anomen7
  anomen8
  anomen9
  haer10  
  haer11
  haer13
  haer15
  haer19
  imoen1  
  imoen10 
  imoen15
  imoen2
  imoen211
  imoen213
  imoen4
  imoen61
  korgan11
  korgan12
  korgan15
  korgan8  
  korgan9
  mazzy11
  mazzy12
  mazzy15
  mazzy8  
  mazzy9
  BEGIN
  
  ACTION_IF FILE_EXISTS_IN_GAME ~%crefile%.cre~ BEGIN
  
    COPY_EXISTING ~%crefile%.cre~ ~override~
      READ_LONG 0x0244 KIT_VALUE
      PATCH_IF (KIT_VALUE = 0x00) BEGIN
        WRITE_LONG 0x0244 0x40000000
      END ELSE
      PATCH_IF (KIT_VALUE = 0x20) BEGIN
        WRITE_LONG 0x0244 0x400d0000
      END ELSE
      PATCH_IF (KIT_VALUE = 0x010000) BEGIN
        WRITE_LONG 0x0244 0x40010000
      END
    BUT_ONLY

  END

END

 





I wrote a lot for Fixpack for fixing creature inventories:

 

DEFINE_PATCH_FUNCTION cd_equip_item

  // defines what we're going to check
  INT_VAR move = 0
          gpuse = 0
  STR_VAR item = "same"
          slot = "helmet"

BEGIN

  SET fruitbats = "-1"
  PATCH_IF ("%slot%" STRING_COMPARE_CASE "helmet" = 0) BEGIN
    SET start = 0
  END ELSE
  PATCH_IF ("%slot%" STRING_COMPARE_CASE "armor" = 0) BEGIN
    SET start = 1
  END ELSE
  PATCH_IF ("%slot%" STRING_COMPARE_CASE "shield" = 0) BEGIN
    SET start = 2
  END ELSE
  PATCH_IF ("%slot%" STRING_COMPARE_CASE "gloves" = 0) BEGIN
    SET start = 3
  END ELSE
  PATCH_IF ("%slot%" STRING_COMPARE_CASE "rings" = 0) BEGIN
    SET start = 4
    SET fruitbats = 5
  END ELSE
  PATCH_IF ("%slot%" STRING_COMPARE_CASE "amulet" = 0) BEGIN
    SET start = 6
  END ELSE
  PATCH_IF ("%slot%" STRING_COMPARE_CASE "jewelry" = 0) BEGIN
    SET start = 4
    SET fruitbats = 6
  END ELSE
  PATCH_IF ("%slot%" STRING_COMPARE_CASE "belt" = 0) BEGIN
    SET start = 7
  END ELSE
  PATCH_IF ("%slot%" STRING_COMPARE_CASE "boots" = 0) BEGIN
    SET start = 8
  END ELSE
  PATCH_IF ("%slot%" STRING_COMPARE_CASE "weapon" = 0) BEGIN
    SET start = 9
    SET fruitbats = 12
  END ELSE
  PATCH_IF ("%slot%" STRING_COMPARE_CASE "quiver" = 0) BEGIN
    SET start = 13
    SET fruitbats = 15
  END ELSE
  PATCH_IF ("%slot%" STRING_COMPARE_CASE "quickslot" = 0) BEGIN
    SET start = 18
    SET fruitbats = 20
  END ELSE BEGIN // inventory
    PATCH_IF gpuse = 0 BEGIN // creatures with gpuse scripts will try to use items in the first inventory slots
      SET start = 21
    END ELSE BEGIN
      SET start = 23
    END
    SET fruitbats = 0
  END
  PATCH_IF fruitbats < 0 BEGIN SET fruitbats = start END

  READ_LONG  0x2b8 slot_off ELSE 0
  READ_LONG  0x2bc itm_off ELSE 0
  READ_LONG  0x2c0 itm_num ELSE 0
  FOR (index = 0 ; index < itm_num ; ++index) BEGIN
    READ_ASCII (itm_off + (0x14 * index)) test_item
    PATCH_IF ("%item%" STRING_COMPARE_CASE "%test_item%" = 0) BEGIN
      SET proceed = 1
      FOR (index2 = 0 ; index2 < 36 ; ++index2) BEGIN // first make a loop to make sure it's not assigned somwhere already
        READ_SHORT (slot_off + (index2 * 0x02)) ref
        PATCH_IF ref = index BEGIN
          PATCH_IF move = 1 BEGIN
            WRITE_SHORT (slot_off + (index2 * 0x02)) 0xffff
          END ELSE BEGIN
            SET proceed = 0
          END
        END
      END
      PATCH_IF proceed BEGIN
        FOR (index2 = start ; index2 < 36 ; ++index2) BEGIN // first make a loop to make sure it's not assigned somwhere already
          READ_SHORT (slot_off + (index2 * 0x02)) ref
          PATCH_IF (ref = 0xffff) BEGIN // if null reference in targeted slot
            WRITE_SHORT (slot_off + (index2 * 0x02)) index // adds reference to item
            SET index2 = 36 // kills loop
          END
          PATCH_IF (index2 = fruitbats) BEGIN // if end of possible equipment slots, skip ahead to inventory
            PATCH_IF gpuse = 0 BEGIN // creatures with gpuse scripts will try to use items in the first inventory slots
              SET index2 = 20 // otherwise go to inventory slots
            END ELSE BEGIN
              SET index2 = 22 // otherwise go to inventory slots
            END
          END
        END
      END
    END
  END

END

// run this on creatures with invalid selected weapons; it'll check the weapon slots and update the equipped weapon as needed
DEFINE_PATCH_FUNCTION cd_equip_weapon BEGIN

  READ_LONG  0x2b8 slot_off ELSE 0
  READ_LONG  0x2c0 itm_num  ELSE 0
  WRITE_SHORT (slot_off + 0x4c) 0xffff // null equipped weapon - either patch below will enter a valid value, or no valid value exists
  FOR (index = 0 ; index < 4 ; ++index) BEGIN // search through weapon slots
    READ_SHORT (slot_off + 0x12 + (index * 0x02)) ref
    PATCH_IF ((ref != 0xffff) AND (ref < itm_num)) BEGIN // if valid reference in weapon slots
//    PATCH_IF (ref != 0xffff) BEGIN // if valid reference in weapon slots
      WRITE_SHORT (slot_off + 0x4c) index // equipped weapon
      SET "index" = 4 // kills loop and prevents next patch_if
    END
  END

END

// run this on creatures with invalid selected weapons; it'll check the weapon slots and update the equipped weapon as needed
DEFINE_PATCH_FUNCTION cd_equip_weapon_specific

  // defines what we're going to check
  STR_VAR item = ""

BEGIN


  READ_LONG  0x2b8 slot_off
  READ_LONG  0x2bc itm_off
  FOR (index = 0; index < 4; ++index) BEGIN // cycles through weapon slots
    READ_SHORT (slot_off + 0x12 + (index * 0x02)) slot_num
    PATCH_IF (slot_num < 37) BEGIN
      READ_ASCII (itm_off + (slot_num * 0x14)) weapon
      PATCH_IF ("%item%" STRING_COMPARE_CASE "%weapon%" = 0) BEGIN // if long sword
        WRITE_SHORT (slot_off + 0x4c) index // sets weapon slot to selected weapon
        SET index = 4
      END
    END
  END

END

// this function moves an item in a creture file into a slot where it can't be pickocketed
// also used to move items out of helmet slot for non-legit crit protection
DEFINE_PATCH_FUNCTION cd_no_pickpocket

  // defines what we're going to check
  STR_VAR item = ""

BEGIN

  READ_LONG 0x2b8 slot_off ELSE 0
  READ_LONG 0x2bc itm_off  ELSE 0
  READ_LONG 0x2c0 itm_num  ELSE 0
  SET added = 0
  FOR (index = 0 ; index < itm_num ; ++index) BEGIN
    READ_ASCII (itm_off + (0x14 * index)) file_item
    PATCH_IF ("%item%" STRING_COMPARE_CASE "%file_item%" = 0) BEGIN
      FOR (index2 = 0 ; index2 < 36 ; ++index2) BEGIN // search through item slots; first pass finds and/or removes
        READ_SHORT (slot_off + (0x02 * index2)) ref
        PATCH_IF (ref = index) BEGIN // if item in question
          PATCH_IF ((index2 = 1) OR (index2 = 3) OR ((index2 > 6) AND (index2 < 18))) BEGIN // if legit slot (1, 3, 7-17)
            SET added = 1
          END ELSE BEGIN
            WRITE_SHORT (slot_off + (0x02 * index2)) 0xffff // nulls reference
          END
        END
      END
      PATCH_IF added = 0 BEGIN // if not in a legit slot to begin with
        FOR (index2 = 1 ; index2 < 36 ; ++index2) BEGIN // search through item slots (skipping helmet at 0); first pass finds and/or removes
          PATCH_IF (index2 =  2) BEGIN SET index2 =  3 END // skip shield slot
          PATCH_IF (index2 =  4) BEGIN SET index2 =  7 END // skip rings & amulet
          PATCH_IF (index2 = 18) BEGIN SET index2 = 21 END // skip quick slots
          READ_SHORT (slot_off + (0x02 * index2)) ref
          PATCH_IF (ref = 0xffff) BEGIN // if empty, legit slot
            WRITE_SHORT (slot_off + (0x02 * index2)) index // adds reference
            SET index  = itm_num  // kill loops
            SET index2 = 36       // kill loops
          END
        END
      END
    END
  END

END

 



cd_equip_item simple finds the specified item in the creature file and equips it to the appropriate slot. cd_equip_weapon finds the first valid weapon and equips it; cd_equip_weapon_specific does the same but only with the item you specify. cd_no_pickpocket move an item that should not be pickpocketed into an inventory slot that is impossible to pickpocket from.

 


 

Fixpack also had to do a lot of fixes for spells with missing headers. Sometimes these were headers at the end of the spell (e.g. spell had headers through level 18, but was missing 19 and 20), other times they were missing low-level headers (e.g. spell had headers for 1, 10, 11, etc. but also needed 8 and 9). Enter CD_EXTEND-O-MATIC (patent pending) for the former case, and CD_MISSING_SPELL_HEADERS for the latter:

 

 

 

/////                                                  \\\\\
///// CD_EXTEND-O-MATIC, patent pending                \\\\\
/////                                                  \\\\\

// this function creates headers and does basic duration extensions to level 30; written for inwd-in-bg2 ages ago
DEFINE_PATCH_FUNCTION CD_EXTEND-O-MATIC
  INT_VAR base_dur    =  0 // constant to add to all durations
          step_dur    =  6 // how much duration to add to each consecutive header
          step_size   =  1 // how many levels between headers
          level_cap   = 20 // stop extending at level
          min_dur     =  4 // ignore effects with durations less than this (e.g. cosmetics)
          dur_special =  0 // just add step_dur to existing effects (ignore base_dur)
          min_lev_alt =  0 // if extending from level 1, use this value as its minimum level instead (e.g. fifth slevel spell with only one header, use 9 here)
BEGIN

  READ_LONG  0x64 abil_off
  READ_SHORT 0x68 abil_num
  READ_LONG  0x6a fx_off
  READ_SHORT (abil_off + 0x10 + (0x28 * (abil_num - 1))) min_lev // read level of last ability
  PATCH_IF ((min_lev = 1) AND (min_lev_alt != 0)) BEGIN SET min_lev = min_lev_alt END
  FOR (index = min_lev + step_size ; index < (level_cap + 1) ; index = index + step_size) BEGIN
    READ_ASCII (abil_off +        (0x28 * (abil_num - 1))) abil (0x28) // read entire ability
    READ_SHORT (abil_off + 0x1e + (0x28 * (abil_num - 1))) abil_fx_num
    READ_SHORT (abil_off + 0x20 + (0x28 * (abil_num - 1))) abil_fx_idx
    READ_ASCII (fx_off +        (0x30 * abil_fx_idx)) effects (abil_fx_num * 0x30) // read entire fx block
    INSERT_BYTES (fx_off +        (0x30 * (abil_fx_idx + abil_fx_num))) (abil_fx_num * 0x30) // insert bytes for new ability
    WRITE_ASCIIE (fx_off +        (0x30 * (abil_fx_idx + abil_fx_num))) "%effects%"          // write in effects block
    FOR (index2 = 0 ; index2 < abil_fx_num ; ++index2) BEGIN
      READ_LONG (fx_off + 0x0e + (0x30 * (abil_fx_idx + abil_fx_num + index2))) duration
      PATCH_IF (duration > min_dur) BEGIN // exclude instant/cosmetic efects
        PATCH_IF dur_special = 1 BEGIN
          SET new_dur = duration + step_dur
        END ELSE BEGIN
          SET new_dur = (base_dur + (index * step_dur))
        END
        WRITE_LONG (fx_off + 0x0e + (0x30 * (abil_fx_idx + abil_fx_num + index2))) new_dur // adjust durations
      END
    END
    INSERT_BYTES (abil_off +        (0x28 * abil_num)) 0x28 // insert new ability
    WRITE_ASCIIE (abil_off +        (0x28 * abil_num)) "%abil%"
    WRITE_SHORT  (abil_off + 0x10 + (0x28 * abil_num)) index
    WRITE_SHORT  (abil_off + 0x20 + (0x28 * abil_num)) (abil_fx_idx + abil_fx_num)
    SET abil_num += 1
    SET fx_off   += 0x28
  END
  WRITE_SHORT 0x68 abil_num
  WRITE_LONG  0x6a fx_off

END

/////                                                  \\\\\
///// CD_MISSING_SPELL_HEADERS                         \\\\\
/////                                                  \\\\\

// this function is useful for filling in missing spell headers between existing headers
// you'll pretty much have to run a series of ALTER_EFFECTs to fix the inserted effects, though
DEFINE_PATCH_FUNCTION CD_MISSING_SPELL_HEADERS
  INT_VAR first_missing =  0 // first missing level
          last_missing  =  0 // last missing level
BEGIN

  // sanity check
  PATCH_IF ((first_missing != 0) AND (last_missing != 0) AND (first_missing <= last_missing)) BEGIN
    // create headers first, then fix effects
    READ_LONG  0x64 abil_off
    READ_SHORT 0x68 abil_num
    READ_LONG  0x6a fx_off
    CLEAR_ARRAY cd_levels
    FOR (index = first_missing ; index < (last_missing + 1) ; ++index) BEGIN
      DEFINE_ASSOCIATIVE_ARRAY cd_levels BEGIN ~%index%~ => 0 END
    END
    SET new_abil = 0
    FOR (index = 0 ; index < abil_num ; ++index) BEGIN
      READ_SHORT  (abil_off + 0x10 + (0x28 * index)) min_lev
      PATCH_IF (min_lev < first_missing) BEGIN // last header before gap, keep overwriting as needed
        READ_SHORT (abil_off + 0x1e + (0x28 * index)) abil_fx_num
        READ_SHORT (abil_off + 0x20 + (0x28 * index)) abil_fx_idx
        READ_ASCII (abil_off + (0x28 * index))       abil_clone (0x28)
        READ_ASCII (fx_off + (0x30 * (abil_fx_idx))) fx_clone (0x30 * abil_fx_num)
        SET new_fx = abil_fx_num
        SET start_fx = (abil_fx_idx + abil_fx_num)
        SET insert = (abil_off + (0x28 * (index + 1)))
      END ELSE
      PATCH_IF (min_lev >= first_missing) AND (min_lev <= last_missing) BEGIN
        DEFINE_ASSOCIATIVE_ARRAY cd_levels BEGIN "%min_lev%" => 1 END
      END
    END
    PATCH_PHP_EACH cd_levels AS level => exist BEGIN
      PATCH_IF (exist = 0) BEGIN
        INSERT_BYTES   ((fx_off + (0x28 * new_abil)) + (0x30 * (start_fx * (new_abil + 1)))) (0x30 * new_fx)
          WRITE_ASCIIE ((fx_off + (0x28 * new_abil)) + (0x30 * (start_fx * (new_abil + 1)))) "%fx_clone%"
        INSERT_BYTES (insert + (0x28 * new_abil)) 0x28
          WRITE_ASCIIE (insert +        (0x28 * new_abil)) "%abil_clone%"
          WRITE_SHORT  (insert + 0x10 + (0x28 * new_abil)) level
          WRITE_SHORT  (insert + 0x1e + (0x28 * new_abil)) new_fx
          WRITE_SHORT  (insert + 0x20 + (0x28 * new_abil)) (start_fx * (new_abil + 1))
        SET new_abil += 1
      END
    END
    PATCH_IF (new_abil > 0) BEGIN
      SET abil_num += new_abil
      WRITE_SHORT 0x68 abil_num
      WRITE_LONG  0x6a (fx_off + (0x28 * new_abil))
      FOR (index = 0 ; index < abil_num ; ++index) BEGIN
        READ_SHORT  (abil_off + 0x10 + (0x28 * index)) min_lev
        PATCH_IF (min_lev > last_missing) BEGIN // if after new inserted effects
          WRITE_SHORT (abil_off + 0x20 + (0x28 * index)) (THIS + (new_abil * new_fx))
        END
      END
    END
  END
 
END

 

 

 

 

These will do rudimentary extensions; generally they'll nail it if the only thing that changes between headers is duration. For anything else, I usually had to follow up with a loop of ALTER_EFFECT. Fixpack has numerous examples of both in action. The EXTEND-O-MATIC in particular could probably be used to knock out a new Spell-50 mod pretty easily.

 


 

And finally, an old, old friend: spell_to_innate.

 

 

 

// change regular spell to innate

DEFINE_PATCH_MACRO ~spell_to_innate~ BEGIN



  READ_LONG  0x64 abil_off ELSE 0

  READ_SHORT 0x68 abil_num ELSE 0

  READ_ASCII (abil_off + 0x04) "bam" (8) // reads the bam filename from ability

  WRITE_SHORT  0x1c 4                    // sets spell type to innate (4)

  WRITE_LONG   0x34 1                    // sets spell level to 1 to avoid scripting issues

  WRITE_ASCIIE 0x3a "%bam%" #8           // writes the bam filename from abilities to spell icon

  FOR (index = 0 ; index < abil_num ; ++index) BEGIN

    WRITE_SHORT (abil_off + 0x02 + (0x28 * index)) 4 // changes ability icon location to innate (4)

  END



END

 

 

 

 

This is useful for converting arcane/divine spells into innate abilities, particularly for kits.

Share this post


Link to post

Add a new actor to an area. This is a simple wrapper around fj_area_struct that saves having to type a bunch of variables.

 

DEFINE_PATCH_FUNCTION add_area_actor
BEGIN
  PATCH_IF FILE_EXISTS_IN_GAME "%cre_resref%.cre"
  BEGIN
    INNER_PATCH_FILE "%cre_resref%.cre"
    BEGIN
      READ_STRREF 0x0008 cre_name
    END
  END
  ELSE
  BEGIN
    PATCH_FAIL "Adding non-existent actor %cre_resref% to area!"
  END

  LAUNCH_PATCH_FUNCTION fj_are_structure
    INT_VAR
    fj_loc_x                    = x_position
    fj_loc_y                    = y_position
    fj_dest_x                   = x_position
    fj_dest_y                   = y_position
    fj_orientation              = orientation
    STR_VAR
    fj_structure_type           = "actor"
    fj_name                     = "%cre_name%"
    fj_cre_resref               = "%cre_resref%"
  END
END

 



Clear all actors from an area. Useful if you want to give a generic house in BG1 some new purpose.

 

DEFINE_PATCH_MACRO remove_all_area_actors
BEGIN
  FOR (i = SHORT_AT 0x0058; i > 0; --i)
  BEGIN
    LAUNCH_PATCH_FUNCTION fj_are_structure
      INT_VAR
      fj_delete_mode            = i - 1
      STR_VAR
      fj_structure_type         = "actor"
    END
  END
END

 



Add a simple trap to an area. Care should be taken that the trap form a long thin rectangle, or thieves may not be able to get close enough to disarm it. Another wrappper around fj_area_struct to save typing because I'm lazy. :-)

 

DEFINE_PATCH_FUNCTION add_simple_trap
  INT_VAR
  trap_detect           = 10
  trap_remove           = 10
  STR_VAR
  trap_script           = "gtar"
BEGIN
  PATCH_IF ll_x < ul_x
  BEGIN
    SET min_x = ll_x
  END
  ELSE
  BEGIN
    SET min_x = ul_x
  END

  PATCH_IF lr_x > ur_x
  BEGIN
    SET max_x = lr_x
  END
  ELSE
  BEGIN
    SET max_x = ur_x
  END

  PATCH_IF ul_y < ur_y
  BEGIN
    SET min_y = ul_y
  END
  ELSE
  BEGIN
    SET min_y = ur_y
  END

  PATCH_IF ll_y > lr_y
  BEGIN
    SET max_y = ll_y
  END
  ELSE
  BEGIN
    SET max_y = lr_y
  END

  LAUNCH_PATCH_FUNCTION fj_are_structure
    INT_VAR
    fj_type             = 0    // Trap
    fj_box_left         = min_x
    fj_box_top          = min_y
    fj_box_right        = max_x
    fj_box_bottom       = max_y
    fj_trap_active      = 1
    fj_loc_x            = (min_x + max_x) / 2
    fj_loc_y            = (min_y + max_y) / 2
    fj_alt_x            = (min_x + max_x) / 2
    fj_alt_y            = (min_y + max_y) / 2
    fj_trap_detect      = trap_detect
    fj_trap_remove      = trap_remove
    fj_flags            = BIT3
    fj_vertex_0         = ul_x + (ul_y << 16)
    fj_vertex_1         = ur_x + (ur_y << 16)
    fj_vertex_2         = lr_x + (lr_y << 16)
    fj_vertex_3         = ll_x + (ll_y << 16)
    STR_VAR
    fj_structure_type   = "region"
    fj_name             = "%trap_name%"
    fj_reg_script       = "%trap_script%"
  END
END

 



Batch-add new actors to an area from a table. Table takes the form <resref> <xpos> <ypos> <orientation>. Uses add_area_actor().

 

DEFINE_PATCH_FUNCTION add_area_actors_from_2da
  STR_VAR
  path_to_2da           = "none"
BEGIN
  PATCH_IF FILE_EXISTS "%path_to_2da%"
  BEGIN
    INNER_PATCH_FILE "%path_to_2da%"
    BEGIN
      COUNT_2DA_COLS cols
      COUNT_2DA_ROWS cols rows
      READ_2DA_ENTRIES_NOW __actor_data cols
    END

    FOR (i = 0; i < rows; ++i)
    BEGIN
      READ_2DA_ENTRY_FORMER __actor_data i 0 cre_resref
      READ_2DA_ENTRY_FORMER __actor_data i 1 x_position
      READ_2DA_ENTRY_FORMER __actor_data i 2 y_position
      READ_2DA_ENTRY_FORMER __actor_data i 3 orientation

      LAUNCH_PATCH_FUNCTION add_area_actor
        INT_VAR
        x_position
        y_position
        orientation
        STR_VAR
        cre_resref
      END
    END
  END
  ELSE
  BEGIN
    PATCH_FAIL "add_area_actors_from_2da called with invalid path %path_to_2da%!"
  END
END

 

 

Share this post


Link to post

My version of the "extend-o-matic", written for two personal mods, one that makes healing spells scale with level and the other to make Flame Blade behave as the 3E version. I'll probably switch to CamDawg's since it is superior. This one only works with spells that have a single header and does not update them in any way.

 

EDIT: Forget mine, CamDawg's version is superior in every way. It even has a much cooler name. ^^

 

Add a level-drain effect to an item or spell, and patch in a simulate for games that don't support opcode 216 (vanilla BG and IWD). The simulate lowers THAC0 by 2 and max. HP by 5 for 8 hours. Written for a personal mod to make Wraith Spiders more like their PnP version.

 

EDIT: Turns out that in BG2, the level drain icon is set by the engine when opcode 216 is used. So, don't set it ourselves as it won't be removed.

 

 

DEFINE_PATCH_FUNCTION add_energy_drain
INT_VAR
levels_drained			= 1
BEGIN
  READ_ASCII 0x0000 signature (4)
  PATCH_MATCH "%signature%"
  WITH
  "ITM " WHEN ENGINE_IS "bg1 totsc iwd1 how totlm"
  BEGIN
    LAUNCH_PATCH_FUNCTION ADD_ITEM_EFFECT
      INT_VAR
      type			= 1	// Melee
      opcode			= 18	// HP: Maximum HP Modifier
      target			= 2	// Pre-target
      duration			= 2400
      parameter1		= ((0 - 5) * levels_drained)
    END

    LAUNCH_PATCH_FUNCTION ADD_ITEM_EFFECT
      INT_VAR
      type			= 1	// Melee
      opcode			= 54	// Stat: THAC0 Modifier
      target			= 2	// Pre-target
      duration			= 2400
      parameter1		= ((0 - 2) * levels_drained)
    END

    LAUNCH_PATCH_FUNCTION ADD_ITEM_EFFECT
      INT_VAR
      type			= 1	// Melee
      opcode			= 142	// Graphics: Display Special Effect Icon
      target			= 2	// Pre-target
      duration			= 2400
      parameter2		= 53
    END
  END

  "ITM "
  BEGIN
    LAUNCH_PATCH_FUNCTION ADD_ITEM_EFFECT
      INT_VAR
      type			= 1	// Melee
      opcode			= 216	// Spell Effect: Level Drain
      target			= 2	// Pre-target
      timing			= 1	// Permanent
      parameter1		= levels_drained
    END
  END

  "SPL " WHEN ENGINE_IS "bg1 totsc iwd1 how totlm"
  BEGIN
    LAUNCH_PATCH_FUNCTION ADD_SPELL_EFFECT
      INT_VAR
      opcode			= 18	// HP: Maximum HP Modifier
      target			= 2	// Pre-target
      duration			= 2400
      parameter1		= ((0 - 5) * levels_drained)
    END

    LAUNCH_PATCH_FUNCTION ADD_SPELL_EFFECT
      INT_VAR
      opcode			= 54	// Stat: THAC0 Modifier
      target			= 2	// Pre-target
      duration			= 2400
      parameter1		= ((0 - 2) * levels_drained)
    END

    LAUNCH_PATCH_FUNCTION ADD_SPELL_EFFECT
      INT_VAR
      opcode			= 142	// Graphics: Display Special Effect Icon
      target			= 2	// Pre-target
      duration			= 2400
      parameter2		= 53
    END
  END

  "SPL "
  BEGIN
    LAUNCH_PATCH_FUNCTION ADD_SPELL_EFFECT
      INT_VAR
      type			= 1	// Melee
      opcode			= 216	// Spell Effect: Level Drain
      target			= 2	// Pre-target
      timing			= 1	// Permanent
      parameter1		= levels_drained
    END
  END
  DEFAULT
    PATCH_FAIL "add_energy_drain() used on incompatible file!"
  END
END

 




Add magic missile immunity to an item or spell. Has to be given match_opcode to determine which effect header will be cloned.

First, have this in your ALWAYS block somewhere. This finds 'Magic Missile'-like spells in BG1, BG2 and IWD1.
('Improved Magic Missile' is from a personal mod of mine, it's a third level version that shoots up to 11 missiles. Yes, the magic missile projectile actually goes that high in the BG and IWD engines. ^^).

 

  // List of spells to be blocked by the Shield spell, like Magic Missile, Mordenkainen's Force Missiles, etc.

  ACTION_FOR_EACH spell_name IN
   "WIZARD_MAGIC_MISSILE"
   "WIZARD_IMPROVED_MAGIC_MISSILE"
   "WIZARD_MORDENKAINENS_FORCE_MISSILES"
   "GORION_MAGIC_MISSILE"
   "BEHOLDER_MAGIC_MISSILE"
   "TRAP_MAGIC_MISSILE"
   "TRAP_MAGIC_MISSILE_5"
  BEGIN
    ACTION_IF IDS_OF_SYMBOL ("spell" "%spell_name%") >= 0
    BEGIN
      LAUNCH_ACTION_FUNCTION RES_NUM_OF_SPELL_NAME
        STR_VAR
        spell_name
        RET
        spell_num
        spell_res
      END

      PRINT "Found %spell_name% as %spell_res%"

      ACTION_DEFINE_ASSOCIATIVE_ARRAY magic_missiles
      BEGIN
        "%spell_name%" => "%spell_res%"
      END
    END
  END

 



And the actual function:

 

DEFINE_PATCH_FUNCTION add_magic_missile_immunity
  INT_VAR
  match_opcode		= 0	// Stat:AC against specific
BEGIN
  PATCH_FOR_EACH projectile IN 36 67 68 69 70 71 72 73 74 75 76 77
  BEGIN
    LAUNCH_PATCH_FUNCTION CLONE_EFFECT
      INT_VAR
      match_opcode
      multi_match	= 1	// Just once, please
      silent		= 1	// Don't care about warnings
      opcode		= 83	// Protection: From Projectile
      parameter1	= 0	// Irrelevant
      parameter2	= projectile
      STR_VAR
      insert		= "last"
    END
  END

  PATCH_IF NOT ENGINE_IS "bg1 totsc"
  BEGIN
    PHP_EACH magic_missiles AS spell_name => spell_res
    BEGIN
      LAUNCH_PATCH_FUNCTION CLONE_EFFECT
      INT_VAR
        match_opcode
        multi_match	= 1	// Just once, please
        silent		= 1	// Don't care about warnings
        opcode		= 206	// Spell: Protection From Spell
	parameter1	= 0
	parameter2	= 0
        STR_VAR
        insert		= "last"
        resource	= "%spell_res%"
      END
    END
  END
END

 




Prevent a spell from stacking with itself. Does not work with vanilla BG1 since it lacks opcode 206, but it does work with IWD1. Has to be given a match_opcode to find a suitable header to clone.

 

EDIT: Use the improved version below, it plays nicer with IWD1 and EE games.

Edited by Angel

Share this post


Link to post

My version of the "extend-o-matic", written for two personal mods, one that makes healing spells scale with level and the other to make Flame Blade behave as the 3E version. I'll probably switch to CamDawg's since it is superior. This one only works with spells that have a single header and does not update them in any way.

 

Yeah, something similar was the prompting for me to make the post. I thought my BG area script extender was quite clever, shared it, and realized that others had already done it long ago and I had wasted time reinventing the wheel.

 

Prevent a spell from stacking with itself. Does not work with vanilla BG1 since it lacks opcode 206, but it does work with IWD1. Has to be given a match_opcode to find a suitable header to clone.

 

I'd like to offer two refinements here, if I may. Both IWD1 and the EEs have superior ways of handling spell stacking, namely the 'Remove effects by resource' opcode (IIRC 254 for IWD, and 321 for EEs). Put it first in the effects stack (timing mode 0, duration 0) and you'll have a vastly superior stacking solution for the engines that support it. Second, I'd set the default match_opcode to 142--Fixpack patches 20+ spells to not self-stack, and 142 works for all but three of them.

Share this post


Link to post

 

Prevent a spell from stacking with itself. Does not work with vanilla BG1 since it lacks opcode 206, but it does work with IWD1. Has to be given a match_opcode to find a suitable header to clone.

I'd like to offer two refinements here, if I may. Both IWD1 and the EEs have superior ways of handling spell stacking, namely the 'Remove effects by resource' opcode (IIRC 254 for IWD, and 321 for EEs). Put it first in the effects stack (timing mode 0, duration 0) and you'll have a vastly superior stacking solution for the engines that support it. Second, I'd set the default match_opcode to 142--Fixpack patches 20+ spells to not self-stack, and 142 works for all but three of them.

 

Heh, you certainly may! I've only recently begun to extend my meddlings to IWD1, so I have yet to learn all the subtleties of it. (Not that I claim to know them for BG, mind you!) As for the EE games, I have yet to touch them at all. It's on my to-do list, just hesitant to spend more money on games I already own. But knowing myself, I'll probably give in sooner or later. :-)

 

I checked IESDP and it looks like you got the codes exactly right, so here's the adapted version:

 

 

 

DEFINE_PATCH_FUNCTION prevent_spell_effect_stacking
  INT_VAR
  match_opcode        = 142
BEGIN
  PATCH_IF ENGINE_IS "iwd1 how totlm"
  BEGIN
    LAUNCH_PATCH_FUNCTION CLONE_EFFECT
      INT_VAR
      match_opcode
      multi_match    = 1
      silent        = 1    // Don't care about warnings
      opcode        = 254    // Remove effects by resource
      timing        = 0    // Duration
      duration        = 0
      parameter1    = 0
      parameter2    = 0
      STR_VAR
      insert        = "first"
      resource        = "%SOURCE_RES%"
    END
  END

  PATCH_IF ENGINE_IS "bgee bg2ee"
  BEGIN
    LAUNCH_PATCH_FUNCTION CLONE_EFFECT
      INT_VAR
      match_opcode
      multi_match    = 1
      silent        = 1    // Don't care about warnings
      opcode        = 321    // Remove effects by resource
      timing        = 0    // Duration
      duration        = 0
      parameter1    = 0
      parameter2    = 0
      STR_VAR
      insert        = "first"
      resource        = "%SOURCE_RES%"
    END
  END

  PATCH_IF ENGINE_IS "bg2 tob"
  BEGIN
    LAUNCH_PATCH_FUNCTION CLONE_EFFECT
      INT_VAR
      match_opcode
      multi_match    = 1
      silent        = 1    // Don't care about warnings
      opcode        = 206    // Spell Effect: Immunity Spell
      parameter1    = RESOLVE_STR_REF ("Multiple castings of this spell have no effect.")
      parameter2    = 0    // Default (IWD1)
      STR_VAR
      insert        = "last"
      resource        = "%SOURCE_RES%"
    END
  END
END

 

 

Edited by Angel

Share this post


Link to post

Not sure if this actually counts, but I made a set of functions to create and customize HLA tables in a non-destructive manner:

 

https://github.com/UnearthedArcana/Modify_HLAs

 

 

 

DEFINE_ACTION_FUNCTION add_hla
  STR_VAR
    kit_name = ~~
    2da_row = ~1~
    ability = ~*~
    icon = ~*~
    strref = ~*~
    min_lev = ~1~
    max_level = ~99~
    num_allowed = ~*~
    prerequisite = ~*~
    excluded_by = ~*~
    alignment_restrict = ~*~
BEGIN
  COPY_EXISTING ~luabbr.2da~ ~override~
    COUNT_2DA_COLS l_cols // amount of columns
    READ_2DA_ENTRIES_NOW l_rows l_cols // read all file into memory  
    FOR (l_row = 1; l_row < l_rows; ++l_row) BEGIN // iterate over rows
      READ_2DA_ENTRY_FORMER l_rows l_row 0 ~l_kit~ // read column value
      PATCH_IF ~%l_kit%~ STRING_EQUAL_CASE ~%kit_name%~ BEGIN
        SET lu_row = %l_row%
        READ_2DA_ENTRY_FORMER l_rows lu_row 1 ~l_table~ // read column value
      END
    END
  BUT_ONLY
  ACTION_IF FILE_EXISTS_IN_GAME ~lu%l_table%.2da~ BEGIN
    COPY_EXISTING ~lu%l_table%.2da~ ~override/lud5_%lu_row%.2da~
        COUNT_2DA_COLS cols // amount of columns
        COUNT_2DA_ROWS cols rows // amount of rows
        READ_2DA_ENTRIES_NOW file cols // read all file into memory  
        first_empty_row = rows // default value to amount of rows in order to skip removal if the table is full
        FOR (i = 0; i < file; ++i) BEGIN // iterate over rows
          SET empty_col_count = 0 // amount of empty columns in the row
          FOR (j = 0; j < cols; ++j) BEGIN // iterate over columns in the row
            READ_2DA_ENTRY_FORMER file i j col_value // read column value
            PATCH_IF "%col_value%" STRING_EQUAL "*" BEGIN // asterisk symbolizes empty column
              empty_col_count += 1
            END
          END
          PATCH_IF "%empty_col_count%" = ("%cols%" - 1) BEGIN // first column in every row is its number, that's why (cols - 1)
            first_empty_row = i // remember the first empty row
            i = file // skip iterating over the rest of the rows
          END
        END  
        INSERT_2DA_ROW ("%first_empty_row%") %cols% ~%2da_row% %ability% %icon% %strref% %min_lev% %max_level% %num_allowed% %prerequisite% %excluded_by% %alignment_restrict%~
        PRETTY_PRINT_2DA
    ACTION_IF NOT (~%l_table%~ STRING_EQUAL_CASE ~d5_%lu_row%~) BEGIN
      COPY_EXISTING ~LUABBR.2DA~ ~override~
        SET_2DA_ENTRY %lu_row% 1 2 ~d5_%lu_row%~
    END
  END
END


DEFINE_ACTION_FUNCTION remove_hla
  STR_VAR
    kit_name = ~~
    remove_ability = ~*~
BEGIN
  COPY_EXISTING ~luabbr.2da~ ~override~
    COUNT_2DA_COLS l_cols // amount of columns
    READ_2DA_ENTRIES_NOW l_rows l_cols // read all file into memory  
    FOR (l_row = 1; l_row < l_rows; ++l_row) BEGIN // iterate over rows
      READ_2DA_ENTRY_FORMER l_rows l_row 0 ~l_kit~ // read column value
      PATCH_IF ~%l_kit%~ STRING_EQUAL_CASE ~%kit_name%~ BEGIN
        SET lu_row = %l_row%
        READ_2DA_ENTRY_FORMER l_rows lu_row 1 ~l_table~ // read column value
      END
    END
  BUT_ONLY
  ACTION_IF FILE_EXISTS_IN_GAME ~lu%l_table%.2da~ BEGIN
    COPY_EXISTING ~lu%l_table%.2da~ ~override/lud5_%lu_row%.2da~
      COUNT_2DA_COLS cols // amount of columns
      COUNT_2DA_ROWS cols rows // amount of rows
      READ_2DA_ENTRIES_NOW file cols // read all file into memory
      SET num_deleted = 0
      FOR (i = 0; i < file; ++i) BEGIN // iterate over rows
        READ_2DA_ENTRY_FORMER file i 1 col_value // read column value
        PATCH_IF "%col_value%" STRING_EQUAL_CASE "%remove_ability%" BEGIN // match .spl to be removed
          REMOVE_2DA_ROW (i - num_deleted) cols // kill the row
          SET num_deleted += 1
        END
      END
    ACTION_IF NOT (~%l_table%~ STRING_EQUAL_CASE ~d5_%lu_row%~) BEGIN
      COPY_EXISTING ~LUABBR.2DA~ ~override~
        SET_2DA_ENTRY %lu_row% 1 2 ~d5_%lu_row%~
    END
  END
END

DEFINE_ACTION_FUNCTION replace_hla
  STR_VAR
    kit_name = ~~
    remove_ability = ~*~
    2da_row = ~1~
    ability = ~*~
    icon = ~*~
    strref = ~*~
    min_lev = ~1~
    max_level = ~99~
    num_allowed = ~*~
    prerequisite = ~*~
    excluded_by = ~*~
    alignment_restrict = ~*~
BEGIN
  COPY_EXISTING ~luabbr.2da~ ~override~
    COUNT_2DA_COLS l_cols // amount of columns
    READ_2DA_ENTRIES_NOW l_rows l_cols // read all file into memory  
    FOR (l_row = 1; l_row < l_rows; ++l_row) BEGIN // iterate over rows
      READ_2DA_ENTRY_FORMER l_rows l_row 0 ~l_kit~ // read column value
      PATCH_IF ~%l_kit%~ STRING_EQUAL_CASE ~%kit_name%~ BEGIN
        SET lu_row = %l_row%
        READ_2DA_ENTRY_FORMER l_rows lu_row 1 ~l_table~ // read column value
      END
    END
  BUT_ONLY
  ACTION_IF FILE_EXISTS_IN_GAME ~lu%l_table%.2da~ BEGIN
    COPY_EXISTING ~lu%l_table%.2da~ ~override/lud5_%lu_row%.2da~
      COUNT_2DA_COLS cols // amount of columns
      COUNT_2DA_ROWS cols rows // amount of rows
      READ_2DA_ENTRIES_NOW file cols // read all file into memory
      SET num_deleted = 0
      FOR (i = 0; i < file; ++i) BEGIN // iterate over rows
        READ_2DA_ENTRY_FORMER file i 1 col_value // read column value
        PATCH_IF "%col_value%" STRING_EQUAL_CASE "%remove_ability%" BEGIN // match .spl to be removed
          REMOVE_2DA_ROW (i - num_deleted) cols // kill the row
          SET num_deleted += 1
        END
      END
      PATCH_IF NOT num_deleted = 0 BEGIN
        FOR (i = 0; i < file; ++i) BEGIN // iterate over rows
          SET empty_col_count = 0 // amount of empty columns in the row
          FOR (j = 0; j < cols; ++j) BEGIN // iterate over columns in the row
            READ_2DA_ENTRY_FORMER file i j col_value // read column value
            PATCH_IF "%col_value%" STRING_EQUAL "*" BEGIN // asterisk symbolizes empty column
              empty_col_count += 1
            END
          END
          PATCH_IF "%empty_col_count%" = ("%cols%" - 1) BEGIN // first column in every row is its number, that's why (cols - 1)
            first_empty_row = i // remember the first empty row
            i = file // skip iterating over the rest of the rows
          END
        END  
        INSERT_2DA_ROW ("%first_empty_row%" - 1) %cols% ~%2da_row% %ability% %icon% %strref% %min_lev% %max_level% %num_allowed% %prerequisite% %excluded_by% %alignment_restrict%~
      END
      PRETTY_PRINT_2DA
    ACTION_IF NOT (~%l_table%~ STRING_EQUAL_CASE ~d5_%lu_row%~) BEGIN
      COPY_EXISTING ~LUABBR.2DA~ ~override~
        SET_2DA_ENTRY %lu_row% 1 2 ~d5_%lu_row%~
    END
  END
END

 

 

 

Use is fairly simple, you just need to know the kit name of the kit you want to modify (the name in kitlist.2da), and the variables that you want to fill into the table:

LAF remove_hla STR_VAR kit_name = ~RANGER~ remove_ability = ~GA_SPCL922~ END

LAF add_hla STR_VAR kit_name = ~RANGER~ ability = ~GA_SPCL923~ num_allowed = ~20~ END

LAF replace_hla STR_VAR kit_name = ~RANGER~ remove_ability = ~GA_SPCL900~ ability = ~GA_SPCL922~ num_allowed = ~1~ excluded_by = ~GA_SPCL922~ END

The variables simply correspond to the column headings in the LUxyz.2da HLA tables... plus the added "remove_ability" variable in the remove_hla and replace_hla functions.

 

NOTE: this will insert my personal modding prefix into the player's game, regardless of which mod/modder actually uses these functions. I think that is unavoidable; for different mods to work together with these functions the tables assigned to any given kit must not conflict with another game file (thus involving a modder prefix), and they should agree on a uniform system for assigning new, unique table names for each modified kit (thus, the same modder prefix). So it's not vanity, just convenience. The functions assign any modfied kit's HLAs to a table name constructed from "LU" plus "d5" plus the row number of the kit in LUABBR.2da.

Edited by subtledoctor

Share this post


Link to post

replace_cre_script, find and replace a creature script

 

Written out of frustration when trying to give ankhegs and winter wolves different scripts for their special attacks and finding that almost every single version of the critters had it in a different slot.

 

 

 

DEFINE_PATCH_FUNCTION replace_cre_script
  INT_VAR
  check_override    = 1
  check_class        = 1
  check_race        = 1
  check_general        = 1
  check_default        = 1
  STR_VAR
  old_script        = ""
  new_script        = ""
  RET
  found
BEGIN
  SET found = 0

  PATCH_IF check_override
  BEGIN
    READ_ASCII SCRIPT_OVERRIDE script

    PATCH_IF ("%script%" STRING_EQUAL_CASE "%old_script%")
    BEGIN
      WRITE_EVALUATED_ASCII SCRIPT_OVERRIDE "%new_script%"
      SET found = 1
    END
  END

  PATCH_IF check_class
  BEGIN
    READ_ASCII SCRIPT_CLASS script

    PATCH_IF ("%script%" STRING_EQUAL_CASE "%old_script%")
    BEGIN
      WRITE_EVALUATED_ASCII SCRIPT_CLASS "%new_script%"
      SET found = 1
    END
  END

  PATCH_IF check_race
  BEGIN
    READ_ASCII SCRIPT_RACE script

    PATCH_IF ("%script%" STRING_EQUAL_CASE "%old_script%")
    BEGIN
      WRITE_EVALUATED_ASCII SCRIPT_RACE "%new_script%"
      SET found = 1
    END
  END

  PATCH_IF check_general
  BEGIN
    READ_ASCII SCRIPT_GENERAL script

    PATCH_IF ("%script%" STRING_EQUAL_CASE "%old_script%")
    BEGIN
      WRITE_EVALUATED_ASCII SCRIPT_GENERAL "%new_script%"
      SET found = 1
    END
  END

  PATCH_IF check_default
  BEGIN
    READ_ASCII SCRIPT_DEFAULT script

    PATCH_IF ("%script%" STRING_EQUAL_CASE "%old_script%")
    BEGIN
      WRITE_EVALUATED_ASCII SCRIPT_DEFAULT "%new_script%"
      SET found = 1
    END
  END
END

 

 

Edited by Angel

Share this post


Link to post

1. Code for patching BAM v2 worldmap icons without overwriting. I've tried to keep variable names similar to mike's code for BAM v1 patching in case someone would like to merge both functions into 1 that supports both formats:

https://github.com/K4thos/IE-code-repository/blob/master/add_map_icons_ee.tpa

The code can be easily adjusted to patch any BAM v2 file.

 

2. WMP file patching using BP-BGT Worldmap style TBL files (useful when you need to add more than 1 area or many links with custom travel times). The code can also patch classic and EE saves, remap icons (in case you use add_map_icons_ee.tpa too) and do other minor things (disabled by default)

https://github.com/K4thos/IE-code-repository/blob/master/add_worldmap_tbl.tpa

 

Earlier versions of these functions have been implemented in Lava's mods that add new areas to worldmap, so they can be used as a reference.

Share this post


Link to post

Small one to quickly flag an item as magical. Written mostly because I keep forgetting what the offsets in the item file are. :-)

 

 

 

DEFINE_PATCH_FUNCTION make_magical
  INT_VAR
  enchantment   = "-1"
  lore          = "-1"
BEGIN
  WRITE_LONG    0x0018 (THIS | BIT6)

  PATCH_IF enchantment >= 0
  BEGIN
    WRITE_LONG  0x0060 enchantment
  END

  PATCH_IF lore >= 0
  BEGIN
    WRITE_SHORT 0x0042 lore
  END
END

 

 

 

Used like this, to make a magical version of the generic b1-4 "paw" that counts as a +1 weapon for things to hit:

COPY_EXISTING "b1-4.itm" "override/b1-4m1.itm"
  LAUNCH_PATCH_FUNCTION make_magical
    INT_VAR
    enchantment = 1
  END

Note that this by itself does not set to-hit and damage bonuses, use the built-in ALTER_ITEM_HEADER for that.

Edited by Angel

Share this post


Link to post

...

 

EDIT: Turns out that in BG2, the level drain icon is set by the engine when opcode 216 is used. So, don't set it ourselves as it won't be removed.

 

 

EFINE_PATCH_FUNCTION add_energy_drain
INT_VAR
levels_drained			= 1
BEGIN
  READ_ASCII 0x0000 signature (4)
  PATCH_MATCH "%signature%"
  WITH
  "ITM " WHEN ENGINE_IS "bg1 totsc iwd1 how totlm"
  BEGIN
    LAUNCH_PATCH_FUNCTION ADD_ITEM_EFFECT
      INT_VAR
      type			= 1	// Melee
      opcode			= 18	// HP: Maximum HP Modifier
      target			= 2	// Pre-target
      duration			= 2400
      parameter1		= ((0 - 5) * levels_drained)
    END

    LAUNCH_PATCH_FUNCTION ADD_ITEM_EFFECT
      INT_VAR
      type			= 1	// Melee
      opcode			= 54	// Stat: THAC0 Modifier
      target			= 2	// Pre-target
      duration			= 2400
      parameter1		= ((0 - 2) * levels_drained)
    END

    LAUNCH_PATCH_FUNCTION ADD_ITEM_EFFECT
      INT_VAR
      type			= 1	// Melee
      opcode			= 142	// Graphics: Display Special Effect Icon
      target			= 2	// Pre-target
      duration			= 2400
      parameter2		= 53
    END
  END

  "ITM "
  BEGIN
    LAUNCH_PATCH_FUNCTION ADD_ITEM_EFFECT
      INT_VAR
      type			= 1	// Melee
      opcode			= 216	// Spell Effect: Level Drain
      target			= 2	// Pre-target
      timing			= 1	// Permanent
      parameter1		= levels_drained
    END
  END

  "SPL " WHEN ENGINE_IS "bg1 totsc iwd1 how totlm"
  BEGIN
    LAUNCH_PATCH_FUNCTION ADD_SPELL_EFFECT
      INT_VAR
      opcode			= 18	// HP: Maximum HP Modifier
      target			= 2	// Pre-target
      duration			= 2400
      parameter1		= ((0 - 5) * levels_drained)
    END

    LAUNCH_PATCH_FUNCTION ADD_SPELL_EFFECT
      INT_VAR
      opcode			= 54	// Stat: THAC0 Modifier
      target			= 2	// Pre-target
      duration			= 2400
      parameter1		= ((0 - 2) * levels_drained)
    END

    LAUNCH_PATCH_FUNCTION ADD_SPELL_EFFECT
      INT_VAR
      opcode			= 142	// Graphics: Display Special Effect Icon
      target			= 2	// Pre-target
      duration			= 2400
      parameter2		= 53
    END
  END

  "SPL "
  BEGIN
    LAUNCH_PATCH_FUNCTION ADD_SPELL_EFFECT
      INT_VAR
      type			= 1	// Melee
      opcode			= 216	// Spell Effect: Level Drain
      target			= 2	// Pre-target
      timing			= 1	// Permanent
      parameter1		= levels_drained
    END
  END
  DEFAULT
    PATCH_FAIL "add_energy_drain() used on incompatible file!"
  END
END

 

 

 

...

Just a slight copy/paste typo. "EFINE_PATCH_FUNCTION add_energy_drain" needs to be changed to "DEFINE_PATCH_FUNCTION add_energy_drain"

Share this post


Link to post

Just a slight copy/paste typo. "EFINE_PATCH_FUNCTION add_energy_drain" needs to be changed to "DEFINE_PATCH_FUNCTION add_energy_drain"

Heh, probably a typo when I was editing. Fixed.

Share this post


Link to post

Heh, probably a typo when I was editing. Fixed.

Yeah, you typically won't get typos with Copy/Paste_edit scennarios, and typically in those one could also use the replace that's in Notepad to get rid of the edits problems too... but maybe that's just me.

Hmm, you could just use ~add energy drain~ instead of add_energy_drain ... ahh, whell.

Share this post


Link to post
Guest
You are commenting as a guest. If you have an account, please sign in.
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×