SConrad Posted April 22, 2005 Posted April 22, 2005 I was asked to help out with a Spanish mod that wanted to do just this, and then Sim convinced me to put up the coding for public eye, in case someone else wanted to do it. Note that I added a check for the area from G3A. I dunno if there's any other mods adding areas to the worldmap which doesn't use AR****.are, but I'll add them if needed. I stronly recommend everyone wanting to add areas to use AR****.are, to avoid hassle. (Curse you, G3!) I can't be bothered to explain every little detail in it now. It's fairly well commented, so all the other tp2-ninjas will understand what I'm doing, and the rest can ask and I will answer. // Append mastarea.2da with the new area APPEND ~MASTAREA.2da~ ~ARSC#A  value~ COPY_EXISTING ~worldmap.wmp~ ~override~  // Read offsets and stuff.  READ_LONG  0x30 "area_num"  READ_LONG  0x34 "area_off"  READ_LONG  0x38 "link_off"  READ_LONG  0x3c "link_num"  READ_LONG  0xc "map_off"  SET "entry"    = ("%map_off%" + 0xb8)  SET "outer_check" = 0  SET "inner_check" = 0  SET "num_ent"   = 0  // New offsets  WRITE_LONG 0x30 ("%area_num%" + 1)  WRITE_LONG 0x38 ("%link_off%" + 0xf0)  WRITE_LONG 0x3c ("%link_num%" + 4)  // Add area to worldmap  INSERT_BYTES  ("%area_off%" +     (0xf0 * "%area_num%")) 0xf0   WRITE_ASCII ("%area_off%" +     (0xf0 * "%area_num%")) ~ARSC#A~ // AR-name   WRITE_ASCII ("%area_off%" + 0x08 + (0xf0 * "%area_num%")) ~ARSC#A~ // AR-name   WRITE_ASCII ("%area_off%" + 0x10 + (0xf0 * "%area_num%")) ~ARSC#A~ // AR-name   WRITE_LONG  ("%area_off%" + 0x34 + (0xf0 * "%area_num%")) 27 // Map icon   WRITE_LONG  ("%area_off%" + 0x38 + (0xf0 * "%area_num%")) 1063 // X coordinate   WRITE_LONG  ("%area_off%" + 0x3C + (0xf0 * "%area_num%")) 39 // Y coordinate   SAY     ("%area_off%" + 0x40 + (0xf0 * "%area_num%")) ~SConrad's area~ // Name of the area   SAY     ("%area_off%" + 0x44 + (0xf0 * "%area_num%")) #-1 // Description   // Now, we add four area links, all from east   WRITE_SHORT ("%area_off%" + 0x50 + (0xf0 * "%area_num%")) ("%link_num%" + 4) // First N link   WRITE_SHORT ("%area_off%" + 0x58 + (0xf0 * "%area_num%")) ("%link_num%" + 4) // First W link   WRITE_SHORT ("%area_off%" + 0x60 + (0xf0 * "%area_num%")) ("%link_num%" + 4) // First S link   WRITE_SHORT ("%area_off%" + 0x68 + (0xf0 * "%area_num%")) ("%link_num%") // First E link   WRITE_SHORT ("%area_off%" + 0x6c + (0xf0 * "%area_num%")) 4 // Number of links from E  // Add links from Umar Hills to the new area  // We'll start by fixing the offsets  WHILE ("%outer_check%" = 0) BEGIN   READ_ASCII ("%entry%" + 0x8) "area" (2)   READ_ASCII ("%entry%" + 0x8) "spec_area" (6)   WHILE (("%spec_area%" STRING_COMPARE_CASE "AR1100" = 0) AND ("%inner_check%" = 0)) BEGIN    READ_SHORT  ("%entry%" + 0x50)             "nlink"    READ_SHORT  ("%entry%" + 0x50 + 0x4)          "#nlink"    WRITE_SHORT ("%entry%" + 0x50 + 0x4)          ("%#nlink%" + 1)    READ_SHORT  ("%entry%" + 0x50 + 0x8)          "wlink"    WRITE_SHORT ("%entry%" + 0x50 + 0x8)          ("%wlink%" + 3)    READ_SHORT  ("%entry%" + 0x50 + 0x8)          "wlink"    READ_SHORT  ("%entry%" + 0x50 + 0x8 + 0x4)       "#wlink"    WRITE_SHORT ("%entry%" + 0x50 + 0x8 + 0x4)       ("%#wlink%" + 1)    READ_SHORT  ("%entry%" + 0x50 + 0x8 + 0x8)       "slink"    WRITE_SHORT ("%entry%" + 0x50 + 0x8 + 0x8)       ("%slink%" + 2)    READ_SHORT  ("%entry%" + 0x50 + 0x8 + 0x8)       "slink"    READ_SHORT  ("%entry%" + 0x50 + 0x8 + 0x8 + 0x4)    "#slink"    WRITE_SHORT ("%entry%" + 0x50 + 0x8 + 0x8 + 0x4)    ("%#slink%" + 1)    READ_SHORT  ("%entry%" + 0x50 + 0x8 + 0x8 + 0x8)    "elink"    WRITE_SHORT ("%entry%" + 0x50 + 0x8 + 0x8 + 0x8)    ("%elink%" + 1)    READ_SHORT  ("%entry%" + 0x50 + 0x8 + 0x8 + 0x8)    "elink"    READ_SHORT  ("%entry%" + 0x50 + 0x8 + 0x8 + 0x8 + 0x4) "#elink"    WRITE_SHORT ("%entry%" + 0x50 + 0x8 + 0x8 + 0x8 + 0x4) ("%#elink%" + 1)    SET "inner_check" = 1   END   PATCH_IF (("%area%" STRING_COMPARE_CASE "AR" = 0) OR ("%area%" STRING_COMPARE_CASE "G3" = 0)) BEGIN    SET "num_ent" = ("%num_ent%" + 1)   END ELSE   PATCH_IF (("%area%" STRING_COMPARE_CASE "AR" != 0) AND ("%area%" STRING_COMPARE_CASE "G3" != 0)) BEGIN    SET "outer_check" = 1   END   SET "entry" = ("%entry%" + 0xf0)  END  // Re-read offsets  READ_LONG  0x30 "area_num"  READ_LONG  0x38 "link_off"  // Add link to ARSC#A  INSERT_BYTES  ("%link_off%" - 0x01 + (0xd8 * "%nlink%")) 0xd8   WRITE_LONG  ("%link_off%"     + (0xd8 * "%nlink%")) ("%area_num%" - 1) // Add the last entry   WRITE_ASCII ("%link_off%" + 0x04 + (0xd8 * "%nlink%")) ~ARSC#E~ // Entrance   WRITE_LONG  ("%link_off%" + 0x28 + (0xd8 * "%nlink%")) 4 // Unknown   // If you want to add random encounters, travelling time, do so here  // Add link to ARSC#A  INSERT_BYTES  ("%link_off%" - 0x01 + (0xd8 * "%wlink%")) 0xd8   WRITE_LONG  ("%link_off%"     + (0xd8 * "%wlink%")) ("%area_num%" - 1) // Add the last entry   WRITE_ASCII ("%link_off%" + 0x04 + (0xd8 * "%wlink%")) ~ARSC#E~ // Entrance   WRITE_LONG  ("%link_off%" + 0x28 + (0xd8 * "%wlink%")) 4 // Unknown   // If you want to add random encounters, travelling time, do so here  // Add link to ARSC#A  INSERT_BYTES  ("%link_off%" - 0x01 + (0xd8 * "%slink%")) 0xd8   WRITE_LONG  ("%link_off%"     + (0xd8 * "%slink%")) ("%area_num%" - 1) // Add the last entry   WRITE_ASCII ("%link_off%" + 0x04 + (0xd8 * "%slink%")) ~ARSC#A~ // Entrance   WRITE_LONG  ("%link_off%" + 0x28 + (0xd8 * "%slink%")) 4 // Unknown   // If you want to add random encounters, travelling time, do so here  // Add link to ARSC#A  INSERT_BYTES  ("%link_off%" - 0x01 + (0xd8 * "%elink%")) 0xd8   WRITE_LONG  ("%link_off%"     + (0xd8 * "%elink%")) ("%area_num%" - 1) // Add the last entry   WRITE_ASCII ("%link_off%" + 0x04 + (0xd8 * "%elink%")) ~ARSC#E~ // Entrance   WRITE_LONG  ("%link_off%" + 0x28 + (0xd8 * "%elink%")) 4 // Unknown   // If you want to add random encounters, travelling time, do so here  // Check which link is largest  PATCH_IF (("%nlink%" > "%wlink%") AND ("%nlink%" > "%slink%") AND ("%nlink%" > "%elink%")) BEGIN   SET "link" = "%nlink%"  END  PATCH_IF (("%wlink%" > "%nlink%") AND ("%wlink%" > "%slink%") AND ("%wlink%" > "%elink%")) BEGIN   SET "link" = "%wlink%"  END  PATCH_IF (("%slink%" > "%wlink%") AND ("%slink%" > "%nlink%") AND ("%slink%" > "%elink%")) BEGIN   SET "link" = "%slink%"  END  PATCH_IF (("%elink%" > "%wlink%") AND ("%elink%" > "%slink%") AND ("%elink%" > "%nlink%")) BEGIN   SET "link" = "%elink%"  END   // Correct ALL other links after elink  // New variables  SET "entry"    = ("%map_off%" + 0xb8)  SET "outer_c"    = 0  // Let's WHILE a bit and search for area-links  WHILE ("%outer_c%" = 0) BEGIN   READ_ASCII ("%entry%" + 0x8) "area" (2)   PATCH_IF (("%area%" STRING_COMPARE_CASE "AR" = 0) OR ("%area%" STRING_COMPARE_CASE "G3" = 0)) BEGIN    READ_SHORT ("%entry%" + 0x50)          "nlink"    READ_SHORT ("%entry%" + 0x50 + 0x8)       "wlink"    READ_SHORT ("%entry%" + 0x50 + 0x8 + 0x8)    "slink"    READ_SHORT ("%entry%" + 0x50 + 0x8 + 0x8 + 0x8) "elink"    // And if they are larger, let's patch 'em    PATCH_IF ("%nlink%" > "%link%") BEGIN     WRITE_SHORT ("%entry%" + 0x50) ("%nlink%" + 4)    END    PATCH_IF ("%wlink%" > "%link%") BEGIN     WRITE_SHORT ("%entry%" + 0x50 + 0x8) ("%wlink%" + 4)    END    PATCH_IF ("%slink%" > "%link%") BEGIN     WRITE_SHORT ("%entry%" + 0x50 + 0x8 + 0x8) ("%slink%" + 4)    END    PATCH_IF ("%elink%" > "%link%") BEGIN     WRITE_SHORT ("%entry%" + 0x50 + 0x8 + 0x8 + 0x8) ("%elink%" + 4)    END   END ELSE   PATCH_IF (("%area%" STRING_COMPARE_CASE "AR" != 0) AND ("%area%" STRING_COMPARE_CASE "G3" != 0)) BEGIN    SET "outer_c" = 1   END   SET "entry" = ("%entry%" + 0xf0)  END  // Add four new links from the new area  // Re-read offsets  READ_LONG  0x38 "link_off"  READ_LONG  0x3c "link_num"  // New offset  WRITE_LONG 0x3c ("%link_num%" + 4)  // Add link to City Gates  INSERT_BYTES  ("%link_off%" - 0x01 + (0xd8 * "%link_num%")) 0xd8   WRITE_LONG  ("%link_off%"     + (0xd8 * "%link_num%")) 11 // City Gates   WRITE_ASCII ("%link_off%" + 0x04 + (0xd8 * "%link_num%")) ~ExitNE~ // Entrance   WRITE_LONG  ("%link_off%" + 0x28 + (0xd8 * "%link_num%")) 4 // Unknown   // If you want to add random encounters, travelling time, do so here  // Add link to Umar Hills  INSERT_BYTES  ("%link_off%" - 0x01 + (0xd8 * "%link_num%")) 0xd8   WRITE_LONG  ("%link_off%"     + (0xd8 * "%link_num%")) 7 // Umar Hills   WRITE_ASCII ("%link_off%" + 0x04 + (0xd8 * "%link_num%")) ~ExitNW~ // Entrance   WRITE_LONG  ("%link_off%" + 0x28 + (0xd8 * "%link_num%")) 4 // Unknown   // If you want to add random encounters, travelling time, do so here  // Add link to Trademeet  INSERT_BYTES  ("%link_off%" - 0x01 + (0xd8 * "%link_num%")) 0xd8   WRITE_LONG  ("%link_off%"     + (0xd8 * "%link_num%")) 14 // Trademeet   WRITE_ASCII ("%link_off%" + 0x04 + (0xd8 * "%link_num%")) ~ExitNW~ // Entrance   WRITE_LONG  ("%link_off%" + 0x28 + (0xd8 * "%link_num%")) 4 // Unknown   // If you want to add random encounters, travelling time, do so here  // Add link to de'Arnise Hold  INSERT_BYTES  ("%link_off%" - 0x01 + (0xd8 * "%link_num%")) 0xd8   WRITE_LONG  ("%link_off%"     + (0xd8 * "%link_num%")) 9 // de'Arnise   WRITE_ASCII ("%link_off%" + 0x04 + (0xd8 * "%link_num%")) ~ExitSE~ // Entrance   WRITE_LONG  ("%link_off%" + 0x28 + (0xd8 * "%link_num%")) 4 // Unknown   // If you want to add random encounters, travelling time, do so here BUT_ONLY_IF_IT_CHANGES EDIT: Updating S_C_C to != 0 instead of = 1.
CamDawg Posted April 22, 2005 Posted April 22, 2005 I stronly recommend everyone wanting to add areas to use AR****.are, to avoid hassle. Why? PATCH_IF (("%area%" STRING_COMPARE_CASE "AR" = 1) AND ("%area%" STRING_COMPARE_CASE "G3" = 1)) STRING_COMPARE_CASE is not a logical test; the results from S_C(_C) are not limited to 0 or 1. You should either check =0 for true or !=0 for false.
SimDing0 Posted April 22, 2005 Posted April 22, 2005 Cam, is there a way a regexp can explicitly avoid those XR areas which throw up errors?
CamDawg Posted April 22, 2005 Posted April 22, 2005 A regexp is straightforward enough: ~^[^Xx][^Rr].+\.are$~. (If the XR areas are smaller than a proper area file should be, a PATCH_IF predicated on SOURCE_SIZE would be even simpler.) S_C_C doesn't take regexps AFAIK, so you could (using Seb's 2-character READ_ASCII) just compare the first two characters against XR.
CamDawg Posted April 22, 2005 Posted April 22, 2005 As an aside, patch code templates are an excellent idea for the Q&A/How-to forums. Rather than constantly referring folks to Tweak tp2's I think I'll start posting code instead. That way devSin and Idobek (he's back, yay) can swing by and do the same patch in 20 fewer lines.
devSin Posted April 22, 2005 Posted April 22, 2005 That way devSin and Idobek (he's back, yay) can swing by and do the same patch in 20 fewer lines.It's all an illusion. I just remove the comments, and BAM! Such a small and efficient patch.
CamDawg Posted April 22, 2005 Posted April 22, 2005 Hehe. I'm beginning to think that FOR kung-fu > WHILE kung-fu. I'll need to consult Fojar to be sure, though.
SConrad Posted April 22, 2005 Author Posted April 22, 2005 I stronly recommend everyone wanting to add areas to use AR****.are, to avoid hassle. Why? Because if everyone starts adding their own prefixes to areas, it breaks my link-searching code. I'd have to update with all possible prefixes I can find. PATCH_IF (("%area%" STRING_COMPARE_CASE "AR" = 1) AND ("%area%" STRING_COMPARE_CASE "G3" = 1)) STRING_COMPARE_CASE is not a logical test; the results from S_C(_C) are not limited to 0 or 1. You should either check =0 for true or !=0 for false. Gracias, I didn't know. /me hurries away to update. A regexp is straightforward enough: ~^[^Xx][^Rr].+\.are$~. (If the XR areas are smaller than a proper area file should be, a PATCH_IF predicated on SOURCE_SIZE would be even simpler.) S_C_C doesn't take regexps AFAIK, so you could (using Seb's 2-character READ_ASCII) just compare the first two characters against XR. Calling it "Seb's 2-character READ_ASCII" might be a bit untrue, since I got it from you. As an aside, patch code templates are an excellent idea for the Q&A/How-to forums. Rather than constantly referring folks to Tweak tp2's I think I'll start posting code instead. That way devSin and Idobek (he's back, yay) can swing by and do the same patch in 20 fewer lines. Thanks, and yes, that's sorta what I had in mind. I think it'd be good to collect templates in here. Some sort of organisation (for instance, "All wmp-templates here" or "All area-templates here") would be fairly good to avoid templates all over the place. It also prevents people from spending time doing something someone else already did. That way devSin and Idobek (he's back, yay) can swing by and do the same patch in 20 fewer lines.It's all an illusion. I just remove the comments, and BAM! Such a small and efficient patch.
SConrad Posted April 22, 2005 Author Posted April 22, 2005 Hehe. I'm beginning to think that FOR kung-fu > WHILE kung-fu. I'll need to consult Fojar to be sure, though. Isn't that blasphemy coming from you?
devSin Posted April 22, 2005 Posted April 22, 2005 FOR loops are definitely better in WeiDU, since almost all looping stuff done these days is done with a simple counter. Keep in mind that the initial assignment in the loop can be any action that reduces to a variable assignment (so, you can do FOR (READ_...), and probably FOR (SPRINT...) and FOR (LOOKUP_IDS_SYMBOL_OF_INT...) and FOR (DESCRIBE_ITEM...)). When it comes time to update the value, you can use anything that evaluates to a WeiDU-recognized value. Taking as an example Cam's code to update the offsets in ARE files, we can reduce it to the following using the FOR loop (after realizing that the difference between almost all the offsets to update is 8 bytes, of course): FOR (index = 0x60; index < 0xc4; index = (index = 0x78) ? 0x7c : (index = 0x84) ? 0xa0 : (index = 0xc0) ? 0xbc : index + 0x08) BEGIN READ_LONG ~%index%~ ~offset~ ELSE 0x011c WRITE_LONG ~%index%~ ~%offset%~ + 0x0440 END READ_LONG 0x5c ~triggersOffset~ ELSE 0x011c READ_LONG 0x88 ~variablesOffset~ ELSE 0x011c READ_LONG 0xc4 ~mapNotesOffset~ ELSE 0x011c READ_LONG 0xc8 ~numberOfMapNotes~ ELSE 0x00 WRITE_LONG 0x5c ~%triggersOffset%~ + 0x0440 WRITE_LONG 0x88 ~%variablesOffset%~ + 0x0440 WRITE_LONG 0xc4 (~%numberOfMapNotes%~ = 0x00) ? 0x00 : ~%mapNotesOffset%~ + 0x0440 Damn shazaam! That saves a lot of space (and, despite appearances, it's also very fast). And, yes, I realize how pathetic over-stuffing the if-then-else operator is, but there's no real way around it in WeiDU. We also can't easily stuff the last three offsets into the loop since they don't fit the magic pattern.
devSin Posted April 23, 2005 Posted April 23, 2005 It's been suggested I clarify the code above, so I'll do that here. FOR (index = 0x60; index < 0xc4; index = (index = 0x78) ? 0x7c : (index = 0x84) ? 0xa0 : (index = 0xc0) ? 0xbc : index + 0x08) BEGIN  READ_LONG ~%index%~ ~offset~ ELSE 0x011c  WRITE_LONG ~%index%~ ~%offset%~ + 0x0440 END READ_LONG 0x5c ~triggersOffset~ ELSE 0x011c READ_LONG 0x88 ~variablesOffset~ ELSE 0x011c READ_LONG 0xc4 ~mapNotesOffset~ ELSE 0x011c READ_LONG 0xc8 ~numberOfMapNotes~ ELSE 0x00 WRITE_LONG 0x5c ~%triggersOffset%~ + 0x0440 WRITE_LONG 0x88 ~%variablesOffset%~ + 0x0440 WRITE_LONG 0xc4 (~%numberOfMapNotes%~ = 0x00) ? 0x00 : ~%mapNotesOffset%~ + 0x0440 Two things to note: - The above will update the offsets required when an actor is added to an area definition. The size for the actor block is 272 bytes, which is really sweet because it gives us a simply hexadecimal value (0x0110). So, it's really simply to add up to 15 actors, as multiplying the size of the actor block by the number of actors "n" will give us 0x0nn0 (so, if we had five actors, each offset would need to be incremented by 0x0550). In the code above, four actors were added to an area definition, so each offset needs to be updated by 1088 (0x0440) bytes. - The loop makes use of the "if-(expression) ? then-value : else-value" expression/operator. This allows us to simulate if-then-else branching statements, except it will evaluate to a simple value (meaning that it can be used anywhere a normal, static value can be used in WeiDU). It's also confusing as hell to most people (especially with the student programmer's staple practice of cramming too much stuff in there). Since the entire expression evaluates to a variable, it can also be used within itself to chain various conditions together (so, something like "if-(expression) ? then-value : else-(expression) ? then-value : else-value). The most important part of the code is the loop header (the body in this case is largely irrelevant). Piece-by-piece: FOR (index = 60; This sets up the value that is going to be used to control the loop. In WeiDU, this can be any variable assignment statement, like SET ~index~ = 60 (of which "index = 60" is simply a shorthand form), or READ_LONG 0x00 ~index~ ELSE 0x00 (the read value will be stored in the variable "index," and then you can work with that value as normal). The offset of the first value we'll want to update here is 0x60 (spawn points), so this is going to be our initial value. index < 0xc4; Area definitions have a large number of offsets spread over a large chunk of the file header. The first offset is defined at offset 0x54 (this will be the actors offset; since we're adding actors, we don't have to update this value (there shouldn't ever be a need to, actually, since it's the first section)). The final offset is the map notes offset at 0xc4 but, since we're doing that one explicitly, we just need to make sure we get all the offsets prior to it, so we exit the loop when the value of "index" is greater than or equal to 0xc4 (196). index = (index = 0x78) ? 0x7c : (index = 0x84) ? 0xa0 : (index = 0xc0) ? 0xbc : index + 0x08) BEGIN This is really the only important part of the loop, so we'll first reformat it for easier reading: index = // all the following will reduce to a single integral value  (index = 0x78) ? 0x7c : // if (~%index%~ = 0x78) then SET ~index~ = 0x7c else ->  (index = 0x84) ? 0xa0 : // if (~%index%~ = 0x84) then SET ~index~ = 0xa0 else ->  (index = 0xc0) ? 0xbc : // if (~%index%~ = 0xc0) then SET ~index~ = 0xbc else ->  index + 0x08 // SET index = ~%index%~ + 0x08 To really understand that, we first need to know all of the offsets we'll need to update: Location in file - Data contained at location 0x5c - Offset of information points, triggers, and exits in the ARE 0x60 - Offset of spawn points in the ARE 0x68 - Offset of entrances in the ARE 0x70 - Offset of containers in the ARE 0x78 - Offset of items in the ARE 0x7c - Offset of vertices in the ARE 0x84 - Offset of ambient sounds in the ARE 0x88 - Offset of variables offset in the ARE 0xa0 - Offset of the explored area bitmap in the ARE 0xa8 - Offset of the doors in the ARE 0xb0 - Offset of the animations in the ARE 0xb8 - Offset of the tiled objects in the ARE 0xbc - Offset of the music in the ARE 0xc0 - Offset of the rest spawns in the ARE 0xc4 - Offset of the map notes in the ARE Armed with that list, we can search for and should be able to find patterns in the offsets we need to update. As can be seen, most of the offsets to update have a difference of 8 bytes, so we can separate the list into something easier to manage: 0x60 - 0x00 + 0x08 = 0x08 0x68 - 0x08 + 0x08 = 0x10 0x70 - 0x00 + 0x08 = 0x08 0x78 - 0x08 + 0x08 = 0x10 0x7c - 0x0c + 0x08 = 0x14 0x84 - 0x04 + 0x08 = 0x0c 0xa0 - 0x00 + 0x08 = 0x08 0xa8 - 0x08 + 0x08 = 0x10 0xb0 - 0x00 + 0x08 = 0x08 0xb8 - 0x08 + 0x08 = 0x10 0xc0 - 0x00 + 0x08 = 0x08 0xbc - 0x0c + 0x08 = 0x14 0xc4 - 0x04 + 0x08 = 0x0c Now, we have a choice. We can either create a separate loop for each of the above sequences, or we can use our if-then-else expression to jump around the list as appropriate. Obviously, I prefer the latter, as it creates a single loop that needs to execute once and yet updates most everything we need (at the expense of clarity, sadly). Also note that our list has left out 0x5c, 0x88, and 0xc4 (although it's included, we don't update it inside the loop), since they don't fit into our pattern (they need to be updated separately, outside of the loop). With all that said, we simply need to follow each run through the loop to figure out what's happening. As stated, we initially set the variable "index" to 0x60 (corresponding to the initial value on our list). This gets us started at the right point, so all we have to do is track how the value changes on each pass: Run1: index = 0x60; 0x60 < 0xc4 is TRUE // Update the value at 0x60 (spawn points) PostRun1: (0x60 = 0x78) is FALSE, (0x60 = 0x84) is FALSE, (0x60 = 0xc0) is FALSE : 0x60 + 0x08  -- as seen, none of our conditions are true, so the value of index is simply incremented by 8 Run2: index = 0x68; 0x68 < 0xc4 is TRUE // Update the value at 0x68 (entrances) PostRun2: (0x68 = 0x78) is FALSE, (0x68 = 0x84) is FALSE, (0x68 = 0xc0) is FALSE : 0x68 + 0x08  -- as seen, none of our conditions are true, so the value of index is simply incremented by 8 Run3: index = 0x70; 0x70 < 0xc4 is TRUE // Update the value at 0x70 (containers) PostRun3:  (0x70 = 0x78) is FALSE, (0x70 = 0x84) is FALSE, (0x70 = 0xc0) is FALSE : 0x70 + 0x08  -- as seen, none of our conditions are true, so the value of index is simply incremented by 8 Run4: index = 0x78; 0x78 < 0xc4 is TRUE // Update the value at 0x78 (items) PostRun4: (0x78 = 0x78) is TRUE  -- the initial condition here is true, so "index = (index = 0x78) ? 0x7c" sets the value of the "index" variable to 0x7c (the rest of the expression is ignored); keep in mind that this assignment is done *after* executing the loop body (so, we've already updated the value at offset 0x78, and we're free to jump to the next value on our list) Run5: index = 0x7c; 0x7c < 0xc4 is TRUE // Update the value at 0x7c (vertices) PostRun5: (0x7c = 0x78) is FALSE, (0x7c = 0x84) is FALSE, (0x7c = 0xc0) is FALSE : 0x7c + 0x08  -- as seen, none of our conditions are true, so the value of index is simply incremented by 8 Run6: index = 0x84; 0x84 < 0xc4 is TRUE // Update the value at 0x84 (ambient sounds) PostRun6: (0x84 = 0x78) is FALSE, (0x84 = 0x84) is TRUE  -- the second condition here is true, so "index = (index = 0x78) ? 0x7c : (index = 0x84) ? 0xa0" sets the value of the "index" variable to 0xa0 (the rest of the expression is ignored); keep in mind that this assignment is done *after* executing the loop body (so, we've already updated the value at offset 0x84, and we're free to jump to the next value on our list) Run7: index = 0xa0; 0xa0 < 0xc4 is TRUE // Update the value at 0xa0 (explored area bitmask) PostRun7: (0xa0 = 0x78) is FALSE, (0xa0 = 0x84) is FALSE, (0xa0 = 0xc0) is FALSE : 0xa0 + 0x08  -- as seen, none of our conditions are true, so the value of index is simply incremented by 8 Run8: index = 0xa8; 0xa8 < 0xc4 is TRUE // Update the value at 0xa8 (doors) PostRun8: (0xa8 = 0x78) is FALSE, (0xa8 = 0x84) is FALSE, (0xa8 = 0xc0) is FALSE : 0xa8 + 0x08  -- as seen, none of our conditions are true, so the value of index is simply incremented by 8 Run9: index = 0xb0; 0xb0 < 0xc4 is TRUE // Update the value at 0xb0 (animations) PostRun9: (0xb0 = 0x78) is FALSE, (0xb0 = 0x84) is FALSE, (0xb0 = 0xc0) is FALSE : 0xb0 + 0x08  -- as seen, none of our conditions are true, so the value of index is simply incremented by 8 Run10: index = 0xb8; 0xb8 < 0xc4 is TRUE // Update the value at 0xb8 (tiled objects) PostRun10: (0xb8 = 0x78) is FALSE, (0xb8 = 0x84) is FALSE, (0xb8 = 0xc0) is FALSE : 0xb8 + 0x08  -- as seen, none of our conditions are true, so the value of index is simply incremented by 8 Run11: index = 0xc0; 0xc0 < 0xc4 is TRUE // Update the value at 0xc0 (rest spawns) PostRun11: (0xc0 = 0x78) is FALSE, (0xc0 = 0x84) is FALSE, (0xc0 = 0xc0) is TRUE  -- the third condition here is true, so "index = (index = 0x78) ? 0x7c : (index = 0x84) ? 0xb0 : (index = 0xc0) ? 0xbc" sets the value of the "index" variable to 0xbc (the rest of the expression is ignored); keep in mind that this assignment is done *after* executing the loop body (so, we've already updated the value at offset 0xc0, and we're free to jump to the next value on our list) Run12: index = 0xbc; 0xbc < 0xc4 is TRUE // Update the value at 0xbc (area music) PostRun12: (0xbc = 0x78) is FALSE, (0xbc = 0x84) is FALSE, (0xbc = 0xc0) is FALSE : 0xbc + 0x08  -- as seen, none of our conditions are true, so the value of index is simply incremented by 8 Run13: index = 0xc4; 0xc4 < 0xc4 is FALSE Now that the expression "index < 0xc4" evaluates to false, we exit the loop, after having updated a total of 12 offsets (with only four lines of code, no less). In comparison, the body of the loop is dirt-cheap: READ_LONG ~%index%~ ~offset~ ELSE 0x011c This simply reads the current value at offset "index" (since we use the actual offsets of this data, we can simply use our "counter" variable as the actual address we want to read from). This stores the value in the variable "offset." WRITE_LONG ~%index%~ ~%offset%~ + 0x0440 Since we're adding 4 actors to the area, we need to update the offset to each section of the file (items, sounds, music, etc.) by 0x0440 bytes, so we can simply write the read value (previously read into "offset") + 0x0440 at "index" (again, we're using the actual value of the places in the file we want to write to as our counter in the FOR loop, so we don't need to do any additional shaq-fu). The rest of the patch should be self-explanatory (read the offset of the triggers/variables/map notes and write that value + 0x0440). The only trick is updating the map notes offset: WRITE_LONG 0xc4 (~%numberOfMapNotes%~ = 0x00) ? 0x00 : ~%mapNotesOffset%~ + 0x0440 Since our "if-(expression) ? then-value : else-value" evaluates to a single integral value, we can even use it as a value to write (for actions like WRITE_BYTE or WRITE_LONG). The patch reads in the number of map notes listed in the area, and then evaluates the following: IF (theCurrentNumberOfMapNotes equals 0) THEN 0 ELSE theCurrentMapNotesOffset + 0x0440 Once WeiDU evaluates this, there will be a single value to be written (out of two possibilities): it will either write 0 to the map notes offset if there are no map notes, or it will write the current offset + 1088 to the map notes offset if there is at least 1 map note. Note to modders: if there are no map notes in an area definition, the map notes offset should be set to 0. I have no idea why, so don't ask. There's no way I'm going to proofread that (this peach and blue shit is pissing me off); it should be accurate and make sense; if it doesn't, ask and I'll clarify some more. EDIT: Damn shazaam! (I almost forgot that part.)
SConrad Posted April 23, 2005 Author Posted April 23, 2005 Thanks, devSin. Might I suggest a split of the topic?
devSin Posted April 23, 2005 Posted April 23, 2005 I neglected to mention that all WHILE loops can be represented as FOR loops, and vice-versa. So you really only need loop kung-fu; the representation of the loop is left to the coder: SET ~index~ = 0x60 WHILE (~%index%~ < 0xc4) BEGIN  READ_LONG ~%index%~ ~offset~ ELSE 0x011c  WRITE_LONG ~%index%~ ~%offset%~ + 0x0440  SET ~index~ = (index = 0x78) ? 0x7c : (index = 0x84) ? 0xa0 : (index = 0xc0) ? 0xbc : index + 0x08 END ... is perfectly valid, and should behave identically to the FOR implementation.
Recommended Posts
Archived
This topic is now archived and is closed to further replies.