Jump to content

Toss your semi-useful WeiDU macros here


Recommended Posts

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

 

Link to comment

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 by CamDawg
v2021.09.23
Link to comment
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

 

Link to comment

- 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 by Grammarsalad
Added text_replace
Link to comment

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 by Lauriel
Removed redundant code and fixed a comment
Link to comment

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. 🙂

Link to comment

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

Link to comment
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

 

Link to comment

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?

Link to comment

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

 

Link to comment

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 by Ardanis
Link to comment
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.

Link to comment

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.

Guest
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.

×
×
  • Create New...