Jump to content


Adding new areas and links to worldmap


16 replies to this topic

#1 SConrad

    Spellhold Director

  • Fixpackers
  • 257 posts
  • Gender:Male
  • Location:Melbourne, Australia

Posted 22 April 2005 - 06:29 AM

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.

Edited by SConrad, 22 April 2005 - 10:00 AM.


#2 CamDawg

    Executive Delivery Boy

  • Gibberling Poobah
  • 8698 posts
  • Gender:Not Telling

Posted 22 April 2005 - 08:19 AM

SConrad, on Apr 22 2005, 10:34 AM, said:

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.
Don't you worry about Planet Express, let me worry about blank.

#3 SimDing0

    Extremely cute and fluffy and lovely.

  • Fixpackers
  • 1319 posts

Posted 22 April 2005 - 08:28 AM

Cam, is there a way a regexp can explicitly avoid those XR areas which throw up errors?

#4 CamDawg

    Executive Delivery Boy

  • Gibberling Poobah
  • 8698 posts
  • Gender:Not Telling

Posted 22 April 2005 - 08:37 AM

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.
Don't you worry about Planet Express, let me worry about blank.

#5 SimDing0

    Extremely cute and fluffy and lovely.

  • Fixpackers
  • 1319 posts

Posted 22 April 2005 - 08:53 AM

Okay, thanks.

#6 CamDawg

    Executive Delivery Boy

  • Gibberling Poobah
  • 8698 posts
  • Gender:Not Telling

Posted 22 April 2005 - 08:59 AM

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. :undecided:

That way devSin and Idobek (he's back, yay) can swing by and do the same patch in 20 fewer lines.
Don't you worry about Planet Express, let me worry about blank.

#7 devSin

  • Fixpackers
  • 3017 posts
  • Gender:Male

Posted 22 April 2005 - 09:30 AM

Quote

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. ;)

#8 CamDawg

    Executive Delivery Boy

  • Gibberling Poobah
  • 8698 posts
  • Gender:Not Telling

Posted 22 April 2005 - 09:47 AM

Hehe. I'm beginning to think that FOR kung-fu > WHILE kung-fu. I'll need to consult Fojar to be sure, though.
Don't you worry about Planet Express, let me worry about blank.

#9 SConrad

    Spellhold Director

  • Fixpackers
  • 257 posts
  • Gender:Male
  • Location:Melbourne, Australia

Posted 22 April 2005 - 09:56 AM

CamDawg, on Apr 22 2005, 06:24 PM, said:

SConrad, on Apr 22 2005, 10:34 AM, said:

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.

CamDawg, on Apr 22 2005, 06:24 PM, said:

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.

CamDawg, on Apr 22 2005, 06:42 PM, said:

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. :p

CamDawg, on Apr 22 2005, 07:04 PM, said:

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. :undecided:

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.

devSin, on Apr 22 2005, 07:35 PM, said:

Quote

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. ;)
:) :D

#10 SConrad

    Spellhold Director

  • Fixpackers
  • 257 posts
  • Gender:Male
  • Location:Melbourne, Australia

Posted 22 April 2005 - 09:58 AM

CamDawg, on Apr 22 2005, 07:52 PM, said:

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?

#11 devSin

  • Fixpackers
  • 3017 posts
  • Gender:Male

Posted 22 April 2005 - 12:47 PM

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.

#12 devSin

  • Fixpackers
  • 3017 posts
  • Gender:Male

Posted 22 April 2005 - 04:05 PM

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

Edited by devSin, 22 April 2005 - 04:06 PM.


#13 Idobek

    Dust Bunny

  • Modders
  • 1001 posts
  • Gender:Male
  • Location:England

Posted 23 April 2005 - 01:02 AM

We have FOR now? Fun.
<SimDing0> Did you know G3 has secret forum rules?
<CamDawg> Yep. They're generally of the nature 'don't annoy Idobek.'

#14 SConrad

    Spellhold Director

  • Fixpackers
  • 257 posts
  • Gender:Male
  • Location:Melbourne, Australia

Posted 23 April 2005 - 04:12 AM

Thanks, devSin.

Might I suggest a split of the topic?

#15 devSin

  • Fixpackers
  • 3017 posts
  • Gender:Male

Posted 23 April 2005 - 07:38 AM

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.

Edited by devSin, 23 April 2005 - 07:40 AM.





Reply to this topic



  


1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users