argent77 Posted August 26, 2021 Share Posted August 26, 2021 14 minutes ago, jastey said: Could you or someone else give an example how I would use RESOLVE_STR_REF here, especially if it's supposed to use a line from another mod. Would it be something like this? RESOLVE_STR_REF(@107) USING ~modname/translations/%s/blabla.tra~ I don't think RESOLVE_STR_REF supports the USING directive. However, you could wrap the WeiDU action in a WITH_TRA block. WITH_TRA ~modname/translations/%s/blabla.tra~ BEGIN OUTER_SET strref2 = RESOLVE_STR_REF(@107) END Quote Link to comment
CamDawg Posted September 10, 2021 Author Share Posted September 10, 2021 (edited) In light of @argent77's information about being able to expand random treasure tables, I've written a function to add rows to the random treasure table. Spoiler /////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ /////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ ///// \\\\\ ///// cd_add_random_treasure, v2021.09.23 \\\\\ ///// \\\\\ /////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ /////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\ DEFINE_ACTION_FUNCTION cd_add_random_treasure STR_VAR cd_random_table = ~~ cd_random_item = ~~ RET cd_random_item_return BEGIN OUTER_SPRINT cd_random_item_return ~~ ACTION_IF (("%cd_random_table%" STRING_COMPARE_CASE "" = 0) OR ("%cd_random_item%" STRING_COMPARE_CASE "" = 0)) BEGIN // sanity check PRINT ~Parameters not specified, nothing added.~ END ELSE BEGIN OUTER_INNER_PATCH_SAVE cd_random_table ~%cd_random_table%~ BEGIN // some basic cleanup first REPLACE_TEXTUALLY ~%TAB%~ ~ ~ REPLACE_TEXTUALLY ~ +~ ~ ~ END ACTION_IF FILE_EXISTS_IN_GAME ~rndtres.2da~ BEGIN // for iwd or the EEs OUTER_SPRINT cd_random_item_return ~%cd_random_item%~ ACTION_IF !GAME_IS ~iwd how totlm~ BEGIN // need to pad out entry if EEs COPY_EXISTING ~rndtres.2da~ ~override~ COUNT_2DA_COLS columns BUT_ONLY OUTER_INNER_PATCH_SAVE cd_random_table ~%cd_random_table%~ BEGIN COUNT_REGEXP_INSTANCES ~ +[^ ]~ cd_random_padding FOR (cd_random_index = cd_random_padding ; cd_random_index < (columns - 2) ; ++cd_random_index) BEGIN // -2: C_R_E doesn't count first entry; columns is +1 because it includes item REPLACE_TEXTUALLY ~$~ ~ *~ END END END APPEND ~rndtres.2da~ ~%cd_random_item% %cd_random_table%~ END ELSE BEGIN // for bg2-style tables // pad out to 20 entries OUTER_INNER_PATCH_SAVE cd_random_table ~%cd_random_table%~ BEGIN COUNT_REGEXP_INSTANCES ~ +[^ ]~ cd_random_padding END OUTER_WHILE cd_random_padding < 19 BEGIN OUTER_INNER_PATCH_SAVE cd_random_table ~%cd_random_table%~ BEGIN REPLACE_TEXTUALLY ~$~ ~ %cd_random_table%~ COUNT_REGEXP_INSTANCES ~ +[^ ]~ cd_random_padding END END OUTER_INNER_PATCH_SAVE cd_random_table ~%cd_random_table%~ BEGIN REPLACE_TEXTUALLY ~^\([^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+\).+~ ~\1~ END COPY_EXISTING ~rndtreas.2da~ ~override~ COUNT_2DA_ROWS 20 cd_rndtreas_rows BUT_ONLY ACTION_IF cd_rndtreas_rows >= 42 BEGIN PRINT ~Random treasure table already full, nothing added.~ END ELSE ACTION_IF cd_rndtreas_rows < 9 BEGIN OUTER_SET cd_random_symbol = cd_rndtreas_rows + 1 APPEND ~rndtreas.2da~ ~%cd_random_item% %cd_random_table%~ OUTER_SPRINT cd_random_item_return ~rndtre0%cd_random_symbol%~ END ELSE BEGIN ACTION_IF cd_rndtreas_rows < 16 BEGIN // if in 'dead symbol' zone, pad out to 17 to get back into letters OUTER_FOR (index = cd_rndtreas_rows ; index < 16 ; ++index) BEGIN APPEND ~rndtreas.2da~ ~cdnull%index% 001 001 001 001 001 001 001 001 001 001 001 001 001 001 001 001 001 001 001~ END OUTER_SET cd_rndtreas_rows = 16 END APPEND ~rndtreas.2da~ ~%cd_random_item% %cd_random_table%~ OUTER_SET cd_random_symbol = 49 + cd_rndtreas_rows <<<<<<<< ./cd_rndtreas.txt rndtre00 >>>>>>>> COPY ~./cd_rndtreas.txt~ ~./cd_rndtreas.txt~ WRITE_BYTE 0x07 ~%cd_random_symbol%~ READ_ASCII 0x00 cd_random_item_return (8) END END // end rndtres vs. rndtreas END // end sanity check END // end function The parameters are cd_random_table (string) - A space-separated list of items you want to be in the new row on the table. cd_random_item (string) - The name of the item to be used in the row's header cd_random_item_return (string) - A returned variable to be used in assigning the new random treasure. cd_random_table does not need to be a complete list; the function will pad it as necessary for the detected game. On Icewind Dale, Icewind Dale 2, or the EEs, the function will add entries to rndtres.2da since it supports unlimited entries. In such cases, cd_random_item_return will be cd_random_item. On the EEs, the function will pad cd_random_table with asterisks to fill the row; this is not needed on the IWD series. For BG and BG2 the function will use rndtreas.2da, which does have a size limitation. If the table is already full, the function will display a warning and return cd_random_item_return as null. As a sample use case, let's say we wanted to IWDify enemy weapons by randomizing them. (Just imagine, what a great idea for a mod!) Spoiler LAF cd_add_random_treasure STR_VAR cd_random_table = ~hamm01 ax1h01 sw1h01 sw1h04 sw1h07 blun01 blun02 blun04~ cd_random_item = ~cdmelee~ RET cd_random_item_return END ACTION_IF ("%cd_random_item_return%" STRING_COMPARE_CASE "" = 0) BEGIN // in case oBG/oBG2 table is full COPY_EXISTING ~orc01.cre~ ~override~ REPLACE_CRE_ITEM ~%cd_random_item_return%~ #0 #0 #0 ~NONE~ ~WEAPON2~ EQUIP END This would append, depending on the platform, the following: Spoiler // appended to rndtres.2da on the EEs, returns cdmelee cdmelee hamm01 ax1h01 sw1h01 sw1h04 sw1h07 blun01 blun02 blun04 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // appended to rndtres.2da on oIWD/oIWD2, returns cdmelee cdmelee hamm01 ax1h01 sw1h01 sw1h04 sw1h07 blun01 blun02 blun04 // appended to rndtreas.2da on oBG/oBG2, returns first open slot in rndtre01 - rndtre09 or rndtre0A - rndtre0Z cdmelee hamm01 ax1h01 sw1h01 sw1h04 sw1h07 blun01 blun02 blun04 hamm01 ax1h01 sw1h01 sw1h04 sw1h07 blun01 blun02 blun04 hamm01 ax1h01 sw1h01 Note that the EE and IWD/IWD2 appends are identical, except the EEs require the row to be padded out to the end of the table. On oBG/oBG2, the list is simply repeated until it hits the required 19 entries. edit: the original version, v2021.09.09, did not handle listings with quantities (e.g. arow01*20) at all. The new version, v2021.09.23, does. Edited September 24, 2021 by CamDawg v2021.09.23 Quote Link to comment
CamDawg Posted September 10, 2021 Author Share Posted September 10, 2021 The links in the first post have been fixed, and updated through the preceding post. Sorry I fell so far behind. Quote Link to comment
Luke Posted September 25, 2021 Share Posted September 25, 2021 On 1/19/2019 at 10:08 PM, Ardanis said: Same issue as Luke's - it [FJ_SPL_ITM_REINDEX]'s trying to run reindex, and it fails without file extension... To tell the truth, there's another issue. You might want to replace SOURCE_SIZE (here and here) with BUFFER_LENGTH (i.e., current file size), otherwise patches like this one will fail COPY_EXISTING "spwi112.spl" "override" LPF "DELETE_SPELL_HEADER" INT_VAR "header_type" = "-1" // All END LPF "ADD_SPELL_HEADER" INT_VAR "type" = 2 END BUT_ONLY_IF_IT_CHANGES Quote Link to comment
Grammarsalad Posted October 4, 2021 Share Posted October 4, 2021 (edited) - ADD_ICONS I use this one a lot. It adds portrait and spell icons to spells and can create a corresponding scroll (though you need to provide a path to some scroll file). I'll be updating this last part to just create a scroll from some existing scroll when I get around to it https://github.com/Grammarsalad/Macros_n_Functions/blob/main/lib/add_icons.tpa - TEXT_REPLACE This one just replaces one bit of text in a spell description with another bit of text. https://github.com/Grammarsalad/Macros_n_Functions/blob/main/lib/replace_text.tpa Edited October 4, 2021 by Grammarsalad Added text_replace Quote Link to comment
Lauriel Posted October 5, 2021 Share Posted October 5, 2021 (edited) I did a function that adds a door. It's not generic, but it's a start. It should help folks get over some hurdles at least. It's a lot of code...feel free to rip into it. Everything can always be better. Spoiler //////////////////////////////////////////////////// // ADD A DOOR TO AN AREA AND ITS ASSOCIATED FILES // // PVRz are compressed graphic files // // TIS are a series of indices pointing to // // sections (tiles) in the PVRz files // // An area can have two TIS/WED files associated // // with it. One for day, one for night. // // If the area has a day and night version, then // // this will need to be called twice - once // // for each TIS/WED // // // // THIS IS NOT A GENERIC FUNCTION // // IT WAS BUILT FOR A VERY SPECIFIC CASE // // MUCH WORK WOULD NEED TO BE DONE TO MAKE IT // // FIT ALL USE CASES // //////////////////////////////////////////////////// // To get the x/y of the open and closed tiles (g_closed_... and g_open_...) use: // COPY_EXISTING ~<your source tis file>.tis~ ~override~ // LPF ps_tileset_info INT_VAR Verbose = 2 Log = 1 END // Log verbose output of PVRz-based tileset to file // BUT_ONLY // Then open up the log file it generates, search for your tile #s and it'll list and the X/Ys needed to use in his other functions // I could probably automate that process...but I don't feel much inclined to do so DEFINE_ACTION_FUNCTION ADD_DOOR INT_VAR is_day = 0 g_closed_x1 = 0 // X,Y coordinates of the graphic within the source PVRz file (use ps_tileset_info to determine) g_closed_x2 = 0 g_closed_y1 = 0 g_closed_y2 = 0 g_open_x1 = 0 g_open_x2 = 0 g_open_y1 = 0 g_open_y2 = 0 t_open_pos1 = 0 // The tile # in the target TIS to be replaced (closed are added to the end of the file, not replaced) t_open_pos2 = 0 t_open_pos3 = 0 t_open_pos4 = 0 v_closed_x0 = 0 // X/Y pairs (vertices) of the actual door object - only allowing 4 per door state v_closed_y0 = 0 // Must be given in clockwise order starting with the one furthest to the right v_closed_x1 = 0 // If 2 tie for rightmost - use the lower of the two v_closed_y1 = 0 v_closed_x2 = 0 v_closed_y2 = 0 v_closed_x3 = 0 v_closed_y3 = 0 v_open_x0 = 0 v_open_y0 = 0 v_open_x1 = 0 v_open_y1 = 0 v_open_x2 = 0 v_open_y2 = 0 v_open_x3 = 0 v_open_y3 = 0 STR_VAR source_open_pvrz = "" source_closed_pvrz = "" target_tis = "" target_wed = "" target_are = "" door_name = "" door_id = "" RET open_pvrz closed_pvrz BEGIN // There are 2 versions of the door, opened and closed // Copy their respective PVRZ (source graphic) files to the override folder using // names that can be associated with the target TIS file (if necessary) LAF COPY_PVRZ_FILE_TO_OVERRIDE STR_VAR tis_file = EVAL ~%target_tis%~ source_pvrz = EVAL ~%source_open_pvrz%~ RET open_pvrz = pvrz_file open_pvrz_page = pvrz_suffix END ACTION_IF (~%source_closed_pvrz%~ STRING_EQUAL ~%source_open_pvrz%~) = 1 BEGIN // Open and closed day sources are the same file OUTER_SPRINT closed_pvrz ~%open_pvrz%~ OUTER_SPRINT closed_pvrz_page ~%open_pvrz_page%~ END ELSE BEGIN LAF COPY_PVRZ_FILE_TO_OVERRIDE STR_VAR tis_file = EVAL ~%target_tis%~ source_pvrz = EVAL ~%source_closed_pvrz%~ RET closed_pvrz = pvrz_file closed_pvrz_page = pvrz_suffix END END // Update the target TIS file COPY_EXISTING ~%target_tis%.tis~ ~override~ // Will need to know where the closed tiles were added in order to update the WED READ_LONG 0x0008 new_closed_tile // The count of the current tiles will be the index to the new one PATCH_IF (STRING_LENGTH ~%closed_pvrz%~ > 1) BEGIN // Add closed door tiles to end of TIS first LPF ps_tileset_add_tiles INT_VAR MaxCount = 1 PVRz_Page = "%closed_pvrz_page%" PVRz_X = "%g_closed_x1%" PVRz_Y = "%g_closed_y1%" STR_VAR Method = "Push" RET Count END LPF ps_tileset_add_tiles INT_VAR MaxCount = 1 PVRz_Page = "%closed_pvrz_page%" PVRz_X = "%g_closed_x2%" PVRz_Y = "%g_closed_y1%" STR_VAR Method = "Push" RET Count END LPF ps_tileset_add_tiles INT_VAR MaxCount = 1 PVRz_Page = "%closed_pvrz_page%" PVRz_X = "%g_closed_x1%" PVRz_Y = "%g_closed_y2%" STR_VAR Method = "Push" RET Count END LPF ps_tileset_add_tiles INT_VAR MaxCount = 1 PVRz_Page = "%closed_pvrz_page%" PVRz_X = "%g_closed_x2%" PVRz_Y = "%g_closed_y2%" STR_VAR Method = "Push" RET Count END END PATCH_IF (STRING_LENGTH ~%open_pvrz%~ > 1) BEGIN // These replace tiles currently in the file LPF ps_tileset_add_tiles INT_VAR MaxCount = 1 Pos = "%t_open_pos1%" PVRz_Page = "%open_pvrz_page%" PVRz_X = "%g_open_x1%" PVRz_Y = "%g_open_y1%" STR_VAR Method = "Replace" RET Count END LPF ps_tileset_add_tiles INT_VAR MaxCount = 1 Pos = "%t_open_pos2%" PVRz_Page = "%open_pvrz_page%" PVRz_X = "%g_open_x2%" PVRz_Y = "%g_open_y1%" STR_VAR Method = "Replace" RET Count END LPF ps_tileset_add_tiles INT_VAR MaxCount = 1 Pos = "%t_open_pos3%" PVRz_Page = "%open_pvrz_page%" PVRz_X = "%g_open_x1%" PVRz_Y = "%g_open_y2%" STR_VAR Method = "Replace" RET Count END LPF ps_tileset_add_tiles INT_VAR MaxCount = 1 Pos = "%t_open_pos4%" PVRz_Page = "%open_pvrz_page%" PVRz_X = "%g_open_x2%" PVRz_Y = "%g_open_y2%" STR_VAR Method = "Replace" RET Count END END BUT_ONLY_IF_IT_CHANGES // WED file changes COPY_EXISTING ~%target_wed%.WED~ ~override~ LPF ADD_WED_DOOR INT_VAR v_open_x0 = %v_open_x0% // X/Y pairs (vertices) of the actual door object - only allowing 4 per door state v_open_y0 = %v_open_y0% // Must be given in clockwise order starting with the one furthest to the right v_open_x1 = %v_open_x1% // Start with the lowest if two are tied for the rightmost v_open_y1 = %v_open_y1% v_open_x2 = %v_open_x2% v_open_y2 = %v_open_y2% v_open_x3 = %v_open_x3% v_open_y3 = %v_open_y3% v_closed_x0 = %v_closed_x0% v_closed_y0 = %v_closed_y0% v_closed_x1 = %v_closed_x1% v_closed_y1 = %v_closed_y1% v_closed_x2 = %v_closed_x2% v_closed_y2 = %v_closed_y2% v_closed_x3 = %v_closed_x3% v_closed_y3 = %v_closed_y3% t_open_pos1 = %t_open_pos1% // The tile # in the target TIS that was replaced t_open_pos2 = %t_open_pos2% t_open_pos3 = %t_open_pos3% t_open_pos4 = %t_open_pos4% t_closed_pos1 = %new_closed_tile% // The tile # of the first new closed graphic that was added to the target TIS ... rest are sequential STR_VAR door_id = EVAL ~%door_id%~ // Called name in NI, but it's the ID used in the ARE file END BUT_ONLY_IF_IT_CHANGES // I'm just putting it in to connect it to the WED // Any details like scripts, flags, travel regions, keys, etc will have to be done elsewhere ACTION_IF is_day = 1 BEGIN COPY_EXISTING ~%target_are%.ARE~ ~override~ // Set defaults SET door_cursor = 30 // Calculate the min/max x/y for both open/closed LPF GET_BOUNDING_BOX INT_VAR v_x0 = %v_open_x0% v_y0 = %v_open_y0% v_x1 = %v_open_x1% v_y1 = %v_open_y1% v_x2 = %v_open_x2% v_y2 = %v_open_y2% v_x3 = %v_open_x3% v_y3 = %v_open_y3% RET min_open_x = min_x max_open_x = max_x min_open_y = min_y max_open_y = max_y END LPF GET_BOUNDING_BOX INT_VAR v_x0 = %v_closed_x0% v_y0 = %v_closed_y0% v_x1 = %v_closed_x1% v_y1 = %v_closed_y1% v_x2 = %v_closed_x2% v_y2 = %v_closed_y2% v_x3 = %v_closed_x3% v_y3 = %v_closed_y3% RET min_closed_x = min_x max_closed_x = max_x min_closed_y = min_y max_closed_y = max_y END LPF fj_are_structure INT_VAR fj_open_box_left = min_open_x fj_open_box_top = min_open_y fj_open_box_right = max_open_x fj_open_box_bottom = max_open_y fj_closed_box_left = min_closed_x fj_closed_box_top = min_closed_y fj_closed_box_right = max_closed_x fj_closed_box_bottom = max_closed_y fj_cursor_idx = door_cursor fj_door_open_vert_0 = v_open_x0 + (v_open_y0 << 16) fj_door_open_vert_1 = v_open_x1 + (v_open_y1 << 16) fj_door_open_vert_2 = v_open_x2 + (v_open_y2 << 16) fj_door_open_vert_3 = v_open_x3 + (v_open_y3 << 16) fj_door_closed_vert_0 = v_closed_x0 + (v_closed_y0 << 16) fj_door_closed_vert_1 = v_closed_x1 + (v_closed_y1 << 16) fj_door_closed_vert_2 = v_closed_x2 + (v_closed_y2 << 16) fj_door_closed_vert_3 = v_closed_x3 + (v_closed_y3 << 16) STR_VAR fj_structure_type = ~door~ fj_name = EVAL ~%door_name%~ fj_door_wed_id = EVAL ~%door_id%~ END BUT_ONLY_IF_IT_CHANGES END END //////////////////////////////////////////////////// // Gets the name of the PVRz file and copies it // // to the override folder if it's new // // Returns the name of the PVRz file as well // // as it's associated parts (prefix and suffix) // // The PVRz file name is the TIS file name // // with the 2nd letter of the file name dropped // // and then a sequential number added as a suffix // //////////////////////////////////////////////////// DEFINE_ACTION_FUNCTION COPY_PVRZ_FILE_TO_OVERRIDE STR_VAR tis_file = ~~ // target tis file name without the extension source_pvrz = ~~ // source of the graphic without the extension RET pvrz_file pvrz_prefix pvrz_suffix BEGIN // Set default return values OUTER_SPRINT pvrz_file ~~ OUTER_SPRINT pvrz_prefix ~~ OUTER_SPRINT pvrz_suffix ~~ ACTION_IF (STRING_LENGTH ~%tis_file%~ > 1) BEGIN OUTER_INNER_PATCH_SAVE pvrz_prefix ~%tis_file%~ BEGIN DELETE_BYTES 1 1 END ACTION_IF ((~%source_pvrz%~ STRING_CONTAINS_REGEXP ~%pvrz_prefix%~) = 0) AND ((STRING_LENGTH ~%pvrz_prefix%~) = ((STRING_LENGTH ~%source_pvrz%~) - 2)) BEGIN // The source of the graphic is already one of the TIS file's PVRz files OUTER_INNER_PATCH_SAVE pvrz_suffix ~%source_pvrz%~ BEGIN DELETE_BYTES 0 ((STRING_LENGTH ~%source_pvrz%~) - 2) END OUTER_SPRINT pvrz_file ~%source_pvrz%~ END ELSE BEGIN // The source of the graphic will need to be copied to a new PVRz that can be associated with the TIS OUTER_SPRINT pvrz_suffix ~-1~ OUTER_SET file_ok = 0 OUTER_SET strt_idx = 0 ACTION_IF GAME_IS ~eet~ THEN BEGIN OUTER_SET strt_idx = 50 END OUTER_FOR (idx = strt_idx; idx < 99 && file_ok = 0; idx = idx + 1) BEGIN ACTION_IF idx < 10 THEN BEGIN OUTER_SPRINT pvrz_suffix ~0%idx%~ END ELSE BEGIN OUTER_SPRINT pvrz_suffix ~%idx%~ END ACTION_IF NOT FILE_EXISTS_IN_GAME ~%pvrz_prefix%%pvrz_suffix%.pvrz~ THEN BEGIN OUTER_SET file_ok = 1 OUTER_SPRINT pvrz_file ~%pvrz_prefix%%pvrz_suffix%~ COPY_EXISTING ~%source_pvrz%.PVRZ~ ~override\%pvrz_file%.pvrz~ END END END END END ///////////////////////////////////////////////////// // Determine the bounding box for polygon vertices // // Expects there to be four // ///////////////////////////////////////////////////// DEFINE_PATCH_FUNCTION GET_BOUNDING_BOX INT_VAR v_x0 = 0 v_y0 = 0 v_x1 = 0 v_y1 = 0 v_x2 = 0 v_y2 = 0 v_x3 = 0 v_y3 = 0 RET min_x max_x min_y max_y BEGIN SET min_x = v_x0 SET max_x = v_x0 SET min_y = v_y0 SET max_y = v_y0 // Calculate the min/max x/y PATCH_IF v_x1 < min_x BEGIN SET min_x = v_x1 END PATCH_IF v_x2 < min_x BEGIN SET min_x = v_x2 END PATCH_IF v_x3 < min_x BEGIN SET min_x = v_x3 END PATCH_IF v_x1 > max_x BEGIN SET max_x = v_x1 END PATCH_IF v_x2 > max_x BEGIN SET max_x = v_x2 END PATCH_IF v_x3 > max_x BEGIN SET max_x = v_x3 END PATCH_IF v_y1 < min_y BEGIN SET min_y = v_y1 END PATCH_IF v_y2 < min_y BEGIN SET min_y = v_y2 END PATCH_IF v_y3 < min_y BEGIN SET min_y = v_y3 END PATCH_IF v_y1 > max_y BEGIN SET max_y = v_y1 END PATCH_IF v_y2 > max_y BEGIN SET max_y = v_y2 END PATCH_IF v_y3 > max_y BEGIN SET max_y = v_y3 END END ////////////////////////////////////////// // ADD A DOOR TO THE WED FILE // // It'll be easier to insert bytes at // // the proper places starting from // // the bottom of the file, recalc all // // the offsets, then add objects that // // reference those offsets. // // Those sections that don't reference // // offsets can be written right away. // ////////////////////////////////////////// DEFINE_PATCH_FUNCTION ADD_WED_DOOR INT_VAR v_open_x0 = 0 // X/Y pairs (vertices) of the actual door object - only allowing 4 per door state v_open_y0 = 0 // Must be given in clockwise order starting with the one furthest to the right v_open_x1 = 0 // Start with the lowest if two are tied for the rightmost v_open_y1 = 0 v_open_x2 = 0 v_open_y2 = 0 v_open_x3 = 0 v_open_y3 = 0 v_closed_x0 = 0 v_closed_y0 = 0 v_closed_x1 = 0 v_closed_y1 = 0 v_closed_x2 = 0 v_closed_y2 = 0 v_closed_x3 = 0 v_closed_y3 = 0 t_open_pos1 = 0 // The tile # in the target TIS that was replaced (closed were added to the end of the file, not replaced) t_open_pos2 = 0 t_open_pos3 = 0 t_open_pos4 = 0 t_closed_pos1 = 0 // The tile # of the first new closed graphic that was added to the target TIS ... rest are sequential STR_VAR door_id = "" // Called name in NI, but it's the ID used in the ARE file BEGIN /////////////////////////////////////////// // Read in offsets and counts from the headers /////////////////////////////////////////// READ_LONG 0x0008 num_overlays READ_LONG 0x000c num_doors READ_LONG 0x0010 offset_overlays READ_LONG 0x0014 offset_header2 READ_LONG 0x0018 offset_doors READ_LONG 0x001c offset_door_tile_cells READ_LONG (offset_header2) num_polygons READ_LONG (offset_header2 + 0x0004) offset_polygons READ_LONG (offset_header2 + 0x0008) offset_vertices READ_LONG (offset_header2 + 0x000c) offset_wallgroups READ_LONG (offset_header2 + 0x0010) offset_polygon_indices READ_LONG (offset_overlays + 0x0010) offset_overlay_tilemap // if it's not the first overlay - this will need to change /////////////////////////////////////////// // Set up the size of objects /////////////////////////////////////////// SET size_overlay = 0x0018 SET size_door = 0x001a SET size_tilemap = 0x000a SET size_door_tile_cell = 0x0002 SET size_polygon = 0x0012 SET size_index = 0x0002 SET size_vertex = 0x0004 // X,Y ////////////////////////////////// // // // ******** VERTICES ******** // // // ////////////////////////////////// // Only allowing for 8 new vertices, 4 for the open door and 4 for the closed SET new_vertices_open = 4 SET new_vertices_closed = 4 // New vertices can just be added at the end of the file (hopefully) // We'll add the open ones first then the closed ones SET new_open_vertices_offset = SOURCE_SIZE SET new_closed_vertices_offset = new_open_vertices_offset + (new_vertices_open * size_vertex) // Insert bytes at the end of the file for our new vertices INSERT_BYTES new_open_vertices_offset (size_vertex * (new_vertices_open + new_vertices_closed)) // Add the vertices to our new vertices section // These define the door polygons WRITE_SHORT new_open_vertices_offset %v_open_x0% WRITE_SHORT (new_open_vertices_offset + 0x0002) %v_open_y0% WRITE_SHORT (new_open_vertices_offset + 0x0004) %v_open_x1% WRITE_SHORT (new_open_vertices_offset + 0x0006) %v_open_y1% WRITE_SHORT (new_open_vertices_offset + 0x0008) %v_open_x2% WRITE_SHORT (new_open_vertices_offset + 0x000a) %v_open_y2% WRITE_SHORT (new_open_vertices_offset + 0x000c) %v_open_x3% WRITE_SHORT (new_open_vertices_offset + 0x000e) %v_open_y3% WRITE_SHORT new_closed_vertices_offset %v_closed_x0% WRITE_SHORT (new_closed_vertices_offset + 0x0002) %v_closed_y0% WRITE_SHORT (new_closed_vertices_offset + 0x0004) %v_closed_x1% WRITE_SHORT (new_closed_vertices_offset + 0x0006) %v_closed_y1% WRITE_SHORT (new_closed_vertices_offset + 0x0008) %v_closed_x2% WRITE_SHORT (new_closed_vertices_offset + 0x000a) %v_closed_y2% WRITE_SHORT (new_closed_vertices_offset + 0x000c) %v_closed_x3% WRITE_SHORT (new_closed_vertices_offset + 0x000e) %v_closed_y3% //////////////////////////////////////////////////// // // // ******** POLYGON INDEX LOOKUP TABLE ******** // // // //////////////////////////////////////////////////// // I think this is only used for walls - not doors ////////////////////////////////// // // // ******** POLYGONS ******** // // // ////////////////////////////////// // Need to add polygons for both open and closed doors in the polygon section // the offset to and count of these will be added to the door object // only allowing for 1 open and 1 closed polygon at the moment SET new_polygons_open = 1 SET new_polygons_closed = 1 SET new_open_polygon_flags = 129 // Shade wall + door SET new_closed_polygon_flags = 129 // New entries will go at the bottom of the current section SET new_polygons_open_offset = offset_polygons + (num_polygons * size_polygon) SET new_polygons_closed_offset = new_polygons_open_offset + (new_polygons_open * size_polygon) // Determine the next vertex index to use READ_LONG (offset_polygons + ((num_polygons - 1) * size_polygon)) last_vertex_index READ_LONG (offset_polygons + ((num_polygons - 1) * size_polygon) + 0x0004) num_last_vertices_used SET new_vertex_index = last_vertex_index + num_last_vertices_used // Calculate the min/max x/y for both open/closed LPF GET_BOUNDING_BOX INT_VAR v_x0 = %v_open_x0% v_y0 = %v_open_y0% v_x1 = %v_open_x1% v_y1 = %v_open_y1% v_x2 = %v_open_x2% v_y2 = %v_open_y2% v_x3 = %v_open_x3% v_y3 = %v_open_y3% RET min_open_x = min_x max_open_x = max_x min_open_y = min_y max_open_y = max_y END LPF GET_BOUNDING_BOX INT_VAR v_x0 = %v_closed_x0% v_y0 = %v_closed_y0% v_x1 = %v_closed_x1% v_y1 = %v_closed_y1% v_x2 = %v_closed_x2% v_y2 = %v_closed_y2% v_x3 = %v_closed_x3% v_y3 = %v_closed_y3% RET min_closed_x = min_x max_closed_x = max_x min_closed_y = min_y max_closed_y = max_y END // Insert bytes at the end of the current section for our new polygons INSERT_BYTES new_polygons_open_offset (size_polygon * (new_polygons_open + new_polygons_closed)) // Can add polygons now since they only reference indices, not offsets WRITE_LONG new_polygons_open_offset %new_vertex_index% WRITE_LONG (new_polygons_open_offset + 0x0004) %new_vertices_open% WRITE_BYTE (new_polygons_open_offset + 0x0008) %new_open_polygon_flags% WRITE_BYTE (new_polygons_open_offset + 0x0009) 0xff WRITE_SHORT (new_polygons_open_offset + 0x000a) %min_open_x% WRITE_SHORT (new_polygons_open_offset + 0x000c) %max_open_x% WRITE_SHORT (new_polygons_open_offset + 0x000e) %min_open_y% WRITE_SHORT (new_polygons_open_offset + 0x0010) %max_open_y% WRITE_LONG new_polygons_closed_offset (%new_vertex_index% + %new_vertices_open%) WRITE_LONG (new_polygons_closed_offset + 0x0004) %new_vertices_closed% WRITE_BYTE (new_polygons_closed_offset + 0x0008) %new_closed_polygon_flags% WRITE_BYTE (new_polygons_closed_offset + 0x0009) 0xff WRITE_SHORT (new_polygons_closed_offset + 0x000a) %min_closed_x% WRITE_SHORT (new_polygons_closed_offset + 0x000c) %max_closed_x% WRITE_SHORT (new_polygons_closed_offset + 0x000e) %min_closed_y% WRITE_SHORT (new_polygons_closed_offset + 0x0010) %max_closed_y% ///////////////////////////////////// // // // ******** WALL GROUPS ******** // // // ///////////////////////////////////// // I don't think I need to do anything here ///////////////////////////////////////////////// // // // ******** TILE INDEX LOOKUP TABLE ******** // // // ///////////////////////////////////////////////// // I don't think I need to do anything with these ///////////////////////////////////////// // // // ******** DOOR TILE CELLS ******** // // // ///////////////////////////////////////// // Need to add 4 entries into this table for the 4 graphic tiles used by the open door // It'll point to tiles defined in the Tile Map Structure and will also associate them with their closed graphics SET new_door_tile_cells = 4 // Find the last tilemap index to calculate the next and determine the offset to the next SET last_door_tile_index = 0 SET last_door_tile_count = 0 PATCH_IF num_doors > 0 BEGIN SET last_door_offset = offset_doors + ((num_doors - 1) * size_door) READ_SHORT (last_door_offset + 0x000a) last_door_tile_index READ_SHORT (last_door_offset + 0x000c) last_door_tile_count END SET new_door_tile_index = (last_door_tile_index + last_door_tile_count) // Calculate where the new door tile cells are inserted SET new_door_tile_cell_offset = offset_door_tile_cells + (new_door_tile_index * size_door_tile_cell) // Insert bytes at the end of the current section for our new door tile cells INSERT_BYTES new_door_tile_cell_offset (size_door_tile_cell * new_door_tile_cells) // Door tile cells only reference indices, not offsets, so we can add them right away WRITE_SHORT new_door_tile_cell_offset %t_open_pos1% WRITE_SHORT (new_door_tile_cell_offset + size_door_tile_cell) %t_open_pos2% WRITE_SHORT (new_door_tile_cell_offset + (2 * size_door_tile_cell)) %t_open_pos3% WRITE_SHORT (new_door_tile_cell_offset + (3 * size_door_tile_cell)) %t_open_pos4% ///////////////////////////////////////////// // // // ******** TILE MAP STRUCTURES ******** // // // ///////////////////////////////////////////// // Need to find and update the tiles associated with the open door // and update their secondary values to point to the new closed door graphics WRITE_SHORT (offset_overlay_tilemap + (t_open_pos1 * size_tilemap) + 0x0004) %t_closed_pos1% WRITE_SHORT (offset_overlay_tilemap + (t_open_pos2 * size_tilemap) + 0x0004) (%t_closed_pos1% + 1) WRITE_SHORT (offset_overlay_tilemap + (t_open_pos3 * size_tilemap) + 0x0004) (%t_closed_pos1% + 2) WRITE_SHORT (offset_overlay_tilemap + (t_open_pos4 * size_tilemap) + 0x0004) (%t_closed_pos1% + 3) /////////////////////////////// // // // ******** DOORS ******** // // // /////////////////////////////// // Calculate the start of our new door object SET new_door_offset = offset_doors + (num_doors * size_door) // Only allowing for 4 door tiles (per state) at the moment // Only the open ones need to be linked to the door structure SET new_tilemap_tiles = 4 // Insert bytes at the start of our new door section INSERT_BYTES new_door_offset size_door // Write the door section after all offsets are updated ///////////////////////////////////////////// // // // ******** RECALCULATE OFFSETS ******** // // // ///////////////////////////////////////////// SET add_size = size_door SET offset_door_tile_cells = offset_door_tile_cells + add_size SET add_size = add_size + (size_door_tile_cell * new_door_tile_cells) SET offset_wallgroups = offset_wallgroups + add_size SET offset_polygons = offset_polygons + add_size SET new_polygons_open_offset = new_polygons_open_offset + add_size SET new_polygons_closed_offset = new_polygons_open_offset + (size_polygon * new_polygons_open) SET add_size = add_size + (size_polygon * (new_polygons_open + new_polygons_closed)) SET offset_polygon_indices = offset_polygon_indices + add_size SET offset_vertices = offset_vertices + add_size SET new_open_vertices_offset = new_open_vertices_offset + add_size SET new_closed_vertices_offset = new_closed_vertices_offset + add_size /////////////////////////////////////////////////////////////// // // // ******** WRITE TO SECTIONS REFERENCING OFFSETS ******** // // // /////////////////////////////////////////////////////////////// // VERTICES - already written // POLYGON INDEX LOOKUP TABLE - no changes // POLYGONS - already written // WALL GROUPS - no changes // TILE INDEX LOOKUP TABLE - no changes // DOOR TILE CELLS - already written // TILE MAP STRUCTURES - already written // DOORS WRITE_ASCIIE new_door_offset ~%door_id%~ (8) WRITE_SHORT (new_door_offset + 0x0008) 1 // Is door? WRITE_SHORT (new_door_offset + 0x000a) %new_door_tile_index% WRITE_SHORT (new_door_offset + 0x000c) %new_tilemap_tiles% WRITE_SHORT (new_door_offset + 0x000e) %new_polygons_open% WRITE_SHORT (new_door_offset + 0x0010) %new_polygons_closed% WRITE_LONG (new_door_offset + 0x0012) %new_polygons_open_offset% WRITE_LONG (new_door_offset + 0x0016) %new_polygons_closed_offset% // SECONDARY HEADER WRITE_LONG (offset_header2) (num_polygons + new_polygons_open + new_polygons_closed) WRITE_LONG (offset_header2 + 0x0004) %offset_polygons% WRITE_LONG (offset_header2 + 0x0008) %offset_vertices% WRITE_LONG (offset_header2 + 0x000c) %offset_wallgroups% WRITE_LONG (offset_header2 + 0x0010) %offset_polygon_indices% // OVERLAYS - offset to this section doesn't change // Update offsets to tilemaps and tile index lookups referenced by the overlays FOR (idx = 0; idx < num_overlays; idx = idx + 1) BEGIN READ_LONG (offset_overlays + (idx * size_overlay) + 0x0010) offset_overlay_tilemap READ_LONG (offset_overlays + (idx * size_overlay) + 0x0014) offset_overlay_tile_lookup SET add_size = size_door SET new_offset_overlay_tilemap = offset_overlay_tilemap + add_size SET add_size = add_size + (new_door_tile_cells * size_door_tile_cell) SET new_offset_overlay_tile_lookup = offset_overlay_tile_lookup + add_size WRITE_LONG (offset_overlays + (idx * size_overlay) + 0x0010) %new_offset_overlay_tilemap% WRITE_LONG (offset_overlays + (idx * size_overlay) + 0x0014) %new_offset_overlay_tile_lookup% END // HEADER // Update the header section with new number of doors and offset to door tile cell indices WRITE_LONG (0x000c) (num_doors + 1) WRITE_LONG (0x001c) %offset_door_tile_cells% END It's called like this: Spoiler // Add a door to the PC's residence in the SoD areas // Will take the graphic for the open door from the BG1 area PVRz files // There are 4 versions of every exterior door - open and closed during the day and night OUTER_SPRINT open_source_day "A020008" OUTER_SPRINT open_source_night "A0200N08" OUTER_SPRINT closed_source_day "B001000" OUTER_SPRINT closed_source_night "B0010N00" ACTION_IF GAME_IS ~eet~ THEN BEGIN OUTER_SPRINT open_source_day "B020008" OUTER_SPRINT open_source_night "B0200N08" OUTER_SPRINT closed_source_day "B001050" OUTER_SPRINT closed_source_night "B0010N50" END // Daytime door LAF ADD_DOOR INT_VAR is_day = 1 g_closed_x1 = 0 // X,Y coordinates from within the source PVRz file g_closed_x2 = 64 g_closed_y1 = 448 g_closed_y2 = 512 g_open_x1 = 128 g_open_x2 = 192 g_open_y1 = 704 g_open_y2 = 768 t_open_pos1 = 140 // The tile # in the target TIS to be replaced t_open_pos2 = 141 t_open_pos3 = 160 t_open_pos4 = 161 v_closed_x0 = 39 // X/Y pairs (vertices) of the actual door object - only allowing 4 per door state v_closed_y0 = 524 // Must be given in clockwise order starting with the one furthest to the right v_closed_x1 = 87 // If 2 tie for rightmost - use the lower of the two v_closed_y1 = 535 v_closed_x2 = 87 v_closed_y2 = 469 v_closed_x3 = 39 v_closed_y3 = 457 v_open_x0 = 16 v_open_y0 = 553 v_open_x1 = 39 v_open_y1 = 552 v_open_x2 = 39 v_open_y2 = 455 v_open_x3 = 16 v_open_y3 = 484 STR_VAR source_open_pvrz = EVAL "%open_source_day%" source_closed_pvrz = EVAL "%closed_source_day%" target_tis = ~BD0010~ target_wed = ~BD0010~ target_are = ~BD0010~ door_name = "Port0006" door_id = "door0006" RET pvrzOpenDay = open_pvrz pvrzClosedDay = closed_pvrz END // Nighttime door LAF ADD_DOOR INT_VAR is_day = 0 g_closed_x1 = 0 // X,Y coordinates from within the source PVRz file (use DLTCEP to determine) g_closed_x2 = 64 g_closed_y1 = 448 g_closed_y2 = 512 g_open_x1 = 128 g_open_x2 = 192 g_open_y1 = 704 g_open_y2 = 768 t_open_pos1 = 140 // The tile # in the target TIS to be replaced t_open_pos2 = 141 t_open_pos3 = 160 t_open_pos4 = 161 v_closed_x0 = 39 // X/Y pairs (vertices) of the actual door object - only allowing 4 per door state v_closed_y0 = 524 // Must be given in clockwise order starting with the one furthest to the right v_closed_x1 = 87 // If 2 tie for rightmost - use the lower of the two v_closed_y1 = 535 v_closed_x2 = 87 v_closed_y2 = 469 v_closed_x3 = 39 v_closed_y3 = 457 v_open_x0 = 16 v_open_y0 = 553 v_open_x1 = 39 v_open_y1 = 552 v_open_x2 = 39 v_open_y2 = 455 v_open_x3 = 16 v_open_y3 = 484 STR_VAR source_open_pvrz = EVAL "%open_source_night%" source_closed_pvrz = EVAL "%closed_source_night%" target_tis = ~BD0010N~ target_wed = ~BD0010N~ target_are = ~BD0010~ door_name = "Port0006" door_id = "door0006" RET pvrzOpenNight = open_pvrz pvrzClosedNight = closed_pvrz END Edited October 5, 2021 by Lauriel Removed redundant code and fixed a comment Quote Link to comment
Skye Posted October 13, 2021 Share Posted October 13, 2021 Ken created a patch function that adds an arbitrary polygon to an area's WED and updates all the wallgroups: Spoiler DEFINE_PATCH_FUNCTION WED_ADD_POLYGON INT_VAR poly_flags = 0b00000000 BEGIN // read the new vertex vars and convert them to array FOR (num = 0; VARIABLE_IS_SET EVAL ~new_vertex_%num%~; ++num) BEGIN SET $new_vertex(~%num%~) = EVAL ~new_vertex_%num%~ END // calculate the next vertex index from the size of the vertex block SET next_idx = ((SOURCE_SIZE - LONG_AT (LONG_AT 0x14 + 0x08)) / 4) // set the current position to the end of the file since that's where the vertex block ends SET vert_pos = SOURCE_SIZE // set the vertex count to 0 SET vert_cnt = 0 // init the bounding box coords SET min_x_coord = 0 SET min_y_coord = 0 SET max_x_coord = 0 SET max_y_coord = 0 PHP_EACH new_vertex AS i => point BEGIN INSERT_BYTES vert_pos 0x04 // add 4 bytes for the new vertex WRITE_LONG vert_pos point // write the vertex data down SET x_coord = SHORT_AT (vert_pos + 0x00) SET y_coord = SHORT_AT (vert_pos + 0x02) // update the coords for the bounding box of the polygon PATCH_IF (x_coord < min_x_coord) OR (min_x_coord = 0) BEGIN min_x_coord = x_coord END PATCH_IF (x_coord > max_x_coord) OR (max_x_coord = 0) BEGIN max_x_coord = x_coord END PATCH_IF (y_coord < min_y_coord) OR (min_y_coord = 0) BEGIN min_y_coord = y_coord END PATCH_IF (y_coord > max_y_coord) OR (max_y_coord = 0) BEGIN max_y_coord = y_coord END SET vert_pos += 0x04 // set position pointer to the next vertex SET ++vert_cnt // increase vertex count END // calculate the position of next polygon from the current offset and number SET poly_idx = LONG_AT (LONG_AT 0x14) // polygon count is also the next polygon index SET poly_pos = (LONG_AT (LONG_AT 0x14 + 0x04)) + (poly_idx * 0x12) // insert a new polygon INSERT_BYTES poly_pos 0x12 WRITE_LONG (poly_pos + 0x00) next_idx // starting vertex index WRITE_LONG (poly_pos + 0x04) vert_cnt // number of vertices WRITE_BYTE (poly_pos + 0x08) poly_flags // polygon flags WRITE_BYTE (poly_pos + 0x09) 0xff // z-index WRITE_SHORT (poly_pos + 0x0a) min_x_coord // min x coordinate of bounding box WRITE_SHORT (poly_pos + 0x0c) max_x_coord // max x coordinate of bounding box WRITE_SHORT (poly_pos + 0x0e) min_y_coord // min y coordinate of bounding box WRITE_SHORT (poly_pos + 0x10) max_y_coord // max y coordinate of bounding box // update polygon count by 1 WRITE_LONG (LONG_AT 0x14 + 0x00) THIS + 1 // update polygon index lookup table offset by size of polygon WRITE_LONG (LONG_AT 0x14 + 0x10) THIS + 0x12 // fetch the map width and height in pixels SET map_width = SHORT_AT (LONG_AT 0x10 + 0x00) * 0x40 SET map_height = SHORT_AT (LONG_AT 0x10 + 0x02) * 0x40 // calculate the number of wallgroups along each axis and total count SET x_groups = (map_width / 640) + ((map_width MODULO 640) > 0) SET y_groups = (map_height / 480) + ((map_height MODULO 480) > 0) SET wg_count = x_groups * y_groups // determine which wallgroups the polygon falls into FOR (y = (min_y_coord / 480); y <= (max_y_coord / 480); ++y) BEGIN FOR (x = (min_x_coord / 640); x <= (max_x_coord / 640); ++x) BEGIN SET wg_idx = x + (y * x_groups) SPRINT $wallgroups(~%wg_idx%~) ~%x%.%y%~ END END // ready polygon index table and wallgroups offset SET poly_idx_tbl = LONG_AT (LONG_AT 0x14 + 0x10) SET wallgroup_offset = LONG_AT (LONG_AT 0x14 + 0x0c) SET idx_tab_inc = 0 // update wallgroup(s) PHP_EACH wallgroups AS i => _ BEGIN // get offset of current wallgroup SET curr_wg_offset = wallgroup_offset + (i * 0x04) // increase the starting polygon index by amount of previous table inserts WRITE_SHORT curr_wg_offset THIS + idx_tab_inc // read index table offset + count SET start_idx = SHORT_AT (curr_wg_offset + 0x00) SET poly_cnt = SHORT_AT (curr_wg_offset + 0x02) // add polygon entry to the index table SET new_tbl_offset = poly_idx_tbl + ((start_idx + poly_cnt) * 0x02) INSERT_BYTES new_tbl_offset 0x02 WRITE_SHORT new_tbl_offset poly_idx // update the polygon count for the current wallgroup WRITE_SHORT (curr_wg_offset + 0x02) THIS + 1 FOR (wg = 0; wg < wg_count; ++wg) BEGIN // increase the starting index of all the wallgroups with the index >= than the added one SET tbl_idx = SHORT_AT (wallgroup_offset + (wg * 0x04)) PATCH_IF (tbl_idx >= (start_idx + poly_cnt)) BEGIN WRITE_SHORT (wallgroup_offset + (wg * 0x04)) THIS + 1 END END SET ++idx_tab_inc // increase currently-inserted table entries END // update vertices offset by size of polygon and changes to index table WRITE_LONG (LONG_AT 0x14 + 0x08) THIS + (0x12 + (idx_tab_inc * 0x02)) END Example usage: Spoiler COPY_EXISTING ~AR1234.wed~ ~override~ LPF WED_ADD_POLYGON INT_VAR poly_flags = 0b00001000 new_vertex_0 = 100 + (100 << 16) new_vertex_1 = 200 + (100 << 16) new_vertex_2 = 200 + (200 << 16) new_vertex_3 = 100 + (200 << 16) END Let me know if you find any issues with it so that we can fix it. Quote Link to comment
Skye Posted October 18, 2021 Share Posted October 18, 2021 This macro will spit out a "2-dimensional" array structure for a given 2DA file. Since there's actually no such thing as a 2-dimensional array in WeiDU (to the best of my knowledge), it can't be a function. It needs to return a variable number of arrays (depending on the size of the 2DA file) for it to work. Also, I'm pretty sure I've seen this before somewhere, but I was bored and Google wasn't being helpful, so here it is. Spoiler // Consume a 2DA into a 2-dimensional array structure DEFINE_PATCH_MACRO CONSUME_2DA BEGIN // array name will be the same as the file name SPRINT var_name ~%SOURCE_RES%~ // read the table size COUNT_2DA_ROWS 1 row_count COUNT_2DA_COLS col_count // read the default cell value READ_2DA_ENTRY 1 0 1 default_value // loop through each data point FOR (i = 3; i < row_count; ++i) BEGIN READ_2DA_ENTRY i 0 1 row_id // read the row id FOR (j = 1; j < col_count; ++j) BEGIN READ_2DA_ENTRY 2 (j - 1) 1 col_name // read the column name // try reading the 2da value or use the default if it's missing PATCH_TRY READ_2DA_ENTRY i j 1 cell_value WITH DEFAULT SPRINT cell_value ~%default_value%~ END // push the column value into the row array SPRINT $EVAL ~%var_name%#%row_id%~(~%col_name%~) ~%cell_value%~ END // add a reference to the row array SPRINT $EVAL ~%var_name%~(~%row_id%~) ~%var_name%#%row_id%~ END END Once a 2DA file is consumed, you can easily reference any value in the table. For example: Spoiler COPY_EXISTING ~strmod.2da~ ~override~ LPM CONSUME_2DA BUT_ONLY // Note the double $$: In actuality, $strmod is simply a reference array to each of the row arrays. // $strmod(14) will resolve into "strmod#14", which is the secondary array variable for this row. // This would be equivalent to calling $strmod#14("WEIGHT_ALLOWANCE"), but that's inconvenient. :) OUTER_SPRINT weight_allowance $$strmod(14)("WEIGHT_ALLOWANCE") PRINT ~%weight_allowance%~ As a side note: While this will fill out any empty cells with the default value, just like the engine would, it will crash or malfunction for any 2DAs in a non-standard format (e.g. missing headers, repeating row ids, etc.). I wanted this to be an associative array table, but the macro would probably be more robust if it was numeric. YMMV Quote Link to comment
DavidW Posted October 19, 2021 Share Posted October 19, 2021 14 hours ago, Skye said: Since there's actually no such thing as a 2-dimensional array in WeiDU (to the best of my knowledge), it can't be a function. WEIDU is fine with multidimensional (or variable-dimensional) arrays. You can have a function that reads a 2da and spits out a 2D WEIDU array labelled by rows and columns. This is my (probably overcomplicated) version: Basic use: COPY_EXISTING - "clastext.2da" nowhere LPF 2da_read RET_ARRAY clastext_array=array clastext_rows=rows clastext_cols=columns END Code: ////////////////////////////////////////////////////////////////////////////////////// /* document{2da_read} { Read a 2da file (or, in patch context, the current 2da file) into a 2d array. Also return an array of uppercased row headers and column headers, in the format row_label=>row_number. ('case' controls the case of the row and column headers; it's uppercase by default on genuine 2das, mixed by default otherwise) In action context, if the file doesn't exist return 0; otherwise, return 1. Also whine if it doesn't exist, unless silent=1. If the file is a 2da, and 'reflect' is set, reverse rows and columns. If it's a 2da, and 'allow_incomplete_lines' is set, don't require that all lines are complete. If it's a 2da, and "rowname_column" is set, use that column (if it's present) for the row names instead of the usual entries. If 'rowmap' and/or 'colmap' are set, they get applied to the row and column entries before the array is constructed. If 'allow_incomplete_lines' is set, we can handle incomplete lines (filling with the default). } */ ////////////////////////////////////////////////////////////////////////////////////// DEFINE_PATCH_FUNCTION "2da_read" INT_VAR silent=0//boolean reflect=0//boolean allow_incomplete_lines=0//boolean STR_VAR type=""//[2da|ids|table_header|table_no_header] rowmap=""//function colmap=""//function rowname_column="" case=""//[upper|lower|mixed] RET_ARRAY columns rows array BEGIN // initialize CLEAR_ARRAY columns CLEAR_ARRAY rows CLEAR_ARRAY array // try to infer type PATCH_IF "%type%" STR_EQ "" BEGIN PATCH_IF "%rowmap%" STR_CMP "" || "%colmap%" STR_CMP "" BEGIN SPRINT type "2da" END ELSE BEGIN READ_ASCII 0x0 sig (3) PATCH_MATCH "%sig%" WITH "2da" "ids" BEGIN SPRINT type "%sig%" END DEFAULT PATCH_MATCH "%SOURCE_EXT%" WITH "2da" "ids" BEGIN SPRINT type "%SOURCE_EXT%" END DEFAULT SPRINT type "table_header" END END END END // get column width & length COUNT_2DA_COLS colcount COUNT_2DA_ROWS colcount rowcount PATCH_MATCH "%type%" WITH "2da" BEGIN // get the default READ_2DA_ENTRY 1 0 1 default // check if it's empty COUNT_2DA_ROWS 1 empty_check is_empty=(empty_check=3) // get the col headers (& also the lookup column if appropriate) lookup_col_num=0 COUNT_2DA_COLS colcount COUNT_2DA_ROWS colcount rowcount PATCH_MATCH "%colcount%" WITH 2 BEGIN col_row=2 main_row=1 END 3 BEGIN col_row=1 main_row=0 END DEFAULT col_row=0 main_row=0 END READ_2DA_ENTRIES_NOW 2da_coldata (colcount - 1) FOR (col=1;col<colcount;++col) BEGIN READ_2DA_ENTRY_FORMER 2da_coldata col_row (col - 1) value PATCH_MATCH "%value%" WITH "%rowname_column%" BEGIN lookup_col_num=col END DEFAULT END PATCH_MATCH "%case%" WITH lower BEGIN TO_LOWER value END upper BEGIN TO_UPPER value END mixed BEGIN END DEFAULT TO_UPPER value END PATCH_IF !reflect BEGIN SET $columns("%value%")=col END ELSE BEGIN SET $rows("%value%")=col END END PATCH_IF is_empty BEGIN LPF array_map STR_VAR array=columns keymap="%colmap%" RET_ARRAY columns=array END SPRINT $rows("null") discard SPRINT $array("null") discard END ELSE BEGIN // get the rows READ_2DA_ENTRIES_NOW 2da_data colcount FOR (rownum=main_row;rownum<2da_data;++rownum) BEGIN READ_2DA_ENTRY_FORMER 2da_data rownum lookup_col_num value PATCH_MATCH "%case%" WITH lower BEGIN TO_LOWER value END upper BEGIN TO_UPPER value END mixed BEGIN END DEFAULT TO_UPPER value END PATCH_IF !reflect BEGIN SET $rows("%value%")=rownum END ELSE BEGIN SET $columns("%value%")=rownum END END // map rows and columns if needed LPF array_map STR_VAR array=columns keymap="%colmap%" RET_ARRAY columns=array END LPF array_map STR_VAR array=rows keymap="%rowmap%" RET_ARRAY rows=array END PATCH_IF allow_incomplete_lines BEGIN // get the data into a working array READ_ASCII 0x0 data (BUFFER_LENGTH) LPF data_lines STR_VAR data RET_ARRAY lines END PHP_EACH lines AS ind=>line BEGIN PATCH_IF ind>=3 BEGIN LPF array_values_from_string INT_VAR quick=1 STR_VAR string="%line%" RET_ARRAY temparray=array END LPF array_length STR_VAR array=temparray RET length END SPRINT row $temparray(0) row_int=ind - 3 +main_row PATCH_IF !reflect BEGIN SPRINT columns_or_rows columns END ELSE BEGIN SPRINT columns_or_rows rows END PHP_EACH "%columns_or_rows%" AS col=>int BEGIN PATCH_IF int<=length BEGIN SPRINT $working_array("%row_int%" "%int%") $temparray("%int%") END ELSE BEGIN SPRINT $working_array("%row_int%" "%int%") "%default%" END END END END // get it into the final array PHP_EACH rows AS row=>row_int BEGIN PHP_EACH columns AS col=>col_int BEGIN PATCH_IF !reflect BEGIN SPRINT $array("%row%" "%col%") $working_array("%row_int%" "%col_int%") END ELSE BEGIN SPRINT $array("%row%" "%col%") $working_array("%col_int%" "%row_int%") END END END END ELSE BEGIN PHP_EACH rows AS row=>row_int BEGIN PHP_EACH columns AS col=>col_int BEGIN PATCH_IF !reflect BEGIN READ_2DA_ENTRY_FORMER 2da_data row_int col_int entry END ELSE BEGIN READ_2DA_ENTRY_FORMER 2da_data col_int row_int entry END SPRINT $array("%row%" "%col%") "%entry%" END END END END END "ids" BEGIN READ_2DA_ENTRIES_NOW 2da_data 2 // set cols SET $columns("int")=0 SET $columns("sym")=1 // get main data count=0 FOR (row=0;row<2da_data;++row) BEGIN READ_2DA_ENTRY_FORMER 2da_data row 0 int READ_2DA_ENTRY_FORMER 2da_data row 1 sym PATCH_IF IS_AN_INT int BEGIN SET $array("%count%" "int")=int SPRINT $array("%count%" "sym") "%sym%" ++count END END // set rows FOR (row=0;row<count;++row) BEGIN SET $rows("%row%")=row END END "table_header" BEGIN COUNT_2DA_COLS colcount READ_2DA_ENTRIES_NOW 2da_data colcount // get columns FOR (col=0;col<colcount;++col) BEGIN READ_2DA_ENTRY_FORMER 2da_data 0 col value PATCH_MATCH "%case%" WITH lower BEGIN TO_LOWER value END upper BEGIN TO_UPPER value END DEFAULT END SET $columns("%value%")=col END // get data; set rows FOR (ind=1;ind<2da_data;++ind) BEGIN row=ind - 1 SET $rows("%row%")=row PHP_EACH columns AS col=>colnum BEGIN READ_2DA_ENTRY_FORMER 2da_data ind colnum value SPRINT $array("%row%" "%col%") "%value%" END END END "table_noheader" BEGIN COUNT_2DA_COLS colcount READ_2DA_ENTRIES_NOW 2da_data colcount // set columns FOR (col=0;col<colcount;++col) BEGIN SET $columns("%col%")=col END // get data; set rows FOR (ind=1;ind<2da_data;++ind) BEGIN row=ind - 1 SET $rows("%row%")=row PHP_EACH columns AS col=>colnum BEGIN READ_2DA_ENTRY_FORMER 2da_data ind colnum value SPRINT $array("%row%" "%col%") "%value%" END END END DEFAULT PATCH_FAIL "2da_read: unidentified read type %type%" END END Quote Link to comment
Skye Posted October 20, 2021 Share Posted October 20, 2021 I must have misunderstood how using multiple keys works. I thought $x(a b) = 1 was equivalent to $x(a) = 1, $x(b) = 1. The explanation of the array construct makes more sense now (any number of keys, single result). How does one do PHP_EACH with multiple keys though? Is that what the additional rows and columns arrays are for? Quote Link to comment
DavidW Posted October 20, 2021 Share Posted October 20, 2021 If you do PHP_EACH array AS k=>v for a multidimensional or variable-dimensional array, it cycles through all elements of the array. v is set to the value; k_0 is set to the first key, k_1 to the second key (if any), k_2 to the third (if any), and so on. k is a synonym for k_0. (This is documented in the WEIDU readme but quite obscurely - I only understood it myself recently and I've hardly ever seen it used in a mod in the wild, though I've started using it myself quite a lot.) That's often an inefficient way to cycle through a 2da that you've read in, of course; that's indeed why I read in rows and columns. If you want, say, to go through all entries in a particular column, it's easier to do something like PHP_EACH clastext_rows AS row=>discard BEGIN desc_here=$clastext_array("%row%" "descstr") [do something with row and desc_here] END Quote Link to comment
Ardanis Posted October 21, 2021 Share Posted October 21, 2021 (edited) There is also something funky with trying to use read value when some keys were missing, as WeiDU would return arrayname_key0_key1_key3_etc gibberish instead. Or something like that I ended up just ignoring values altogether and instead putting all data into keys for SoD AI, like a table that it actually was in my case. CLEAR_ARRAY ability_list DEFINE_ASSOCIATIVE_ARRAY ability_list BEGIN // ability , type , local_trigger , probability_weight_to_skip , condition , local_action , ability_timer => 0 ~WIZARD_HORROR~ , spell , ~~ , 25 , arcane , ~~ , ~~ => 0 // use Horror spell with probability = 100 / (100 + 25) ~wand02~ , item , ~~ , 50 , arcane , ~~ , ~~ => 0 // use Wand of Fear item with probability = 100 / (100 + 50) ~WIZARD_HOLD_PERSON~ , spell , ~General(%target%,HUMANOID)~ , 75 , arcane , ~~ , ~~ => 0 // extra check for humanoids, but only when actually casting it END Edited October 21, 2021 by Ardanis Quote Link to comment
kjeron Posted November 10, 2021 Share Posted November 10, 2021 DEFINE_ACTION_FUNCTION CLONE_ARRAY STR_VAR array = ~~ RET_ARRAY EVAL ~%array%~ BEGIN END LAF CLONE_ARRAY STR_VAR array = ~old~ RET_ARRAY new = old END Simple method to clone array "old" into array "new" with the new RET_ARRAY feature. Is there another, more direct way to do this? It seems like such a feature should have already existed. Quote Link to comment
CamDawg Posted November 10, 2021 Author Share Posted November 10, 2021 Miloch had recently asked for some additional array functions; you could probably add your request to the list. Quote Link to comment
Recommended Posts
Join the conversation
You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.