Serial Map & Mission Swaps for Resettable Objectives

Talk about your new mod or map here

Moderators: jelco, bert_the_turtle

brice
level2
level2
Posts: 167
Joined: Fri Feb 23, 2007 6:04 am

Serial Map & Mission Swaps for Resettable Objectives

Postby brice » Mon May 21, 2007 9:59 am

Hi again, I have been working on a level which asks the player to set an 8-bit combination in order to move to the next level. I've used info from xander, TGF, trickfred and others, but I've come across a problem & found a solution which I haven't seen mentioned before. Probably only a handful of modders will be interested in this...

The 8-bit combo could be implemented with ControlTowers or SafeAreas or any combo of Online and Offline buildings. A game.txt Event can detect the correct pattern using combinations of BuildingOnline and BuildingOffline. But what to do about level resets? The cleanest approach would be to have a script initialize all the code buildings to Offline after each reset, but this can't be done. Instant engineers are problematic since they can only turn a building Offline if there is no competition from opposing team engineers... and they either have to be disposed of, and hidden, or their presence explained. If instant Red reset engineers are left around the CTs, then the player's engineers won't be able to bring the ControlTowers Online -- only the CT team will change, and then the CT's Online & team status will get out of sync, causing problems. So sometimes the only practical solution is to swap maps after each reset, and duplicate the game.txt Events for each map's group of building IDs. Nothing new here yet.

Swapping map files requires a "jump gate" level if the hack is to be transparent to the player. So if we are going to allow the player to reset the 8-bit code level maybe 5 times, we need to be able to update the jump level 5 times, once after each reset of the playable level. This can be done with stacked ScriptTriggers in another hidden level which only gets accessed once after each reset of the playable level. This is getting complicated, and I'll diagram it and give code listings below. But there is a BIG problem that bites when you have scripts jumping from level to level.

If a script (A) does EnterLocation into a level which has an Always ScriptTrigger, the Always script (B) can get executed before the calling EnterLocation script completely finishes. This causes all sorts of problems, but the biggest problem is if script B immediately does an ExitLocation. Then the mission file of the current location gets saved improperly. Any dynamic ScriptTriggers which have been used will NOT be retired & removed from the saved mission file. Also, instant units may not get instantiated in time to be saved in the mission file. They will be lost until the next reset, or forever if the timings are always unfavorable.

It doesn't matter if the EnterLocation is the very last command of script A -- script A is still "active" in some internal sense after the target level is first entered. It is the overlapping of scripts A & B which causes the problems. And since the dynamic ScriptTriggers are not getting retired as expected, it is easy to get trapped into infinite loops A <--> B which show up only as confusing infinite blackouts. You can find these loops by inserting a Wait 1.0 in each of your scripts.

The solution to this problem is to insert a short Wait before the ExitLocation in script B. This allows script A to fully complete inside the current location. The length of the Wait can be 0.01 for hidden levels which contain nothing but a stack of ScriptTriggers... up to 0.35 or more for levels that instantiate lots of units. For the setup I'm going to describe, the chained scripts are called only after level resets, so there will be a known amount of instant units, and the Wait can be tuned to get them all onboard and hopefully stay within the black fade-out / fade-in times. EDIT: see below (end) for a workaround to the camera delay.

The following setup shows 5 resets of the level called "target" (T). All versions of the target level share the common mission: mission_target. The target levels are hidden and only accessable through the level "jump" (J). The jump missions contain stacks of ScriptTriggers which jump to the appropriate versions of the target map. The target mission contains a single dynamic ScriptTrigger which fires only on first entry, and then retires until the next reset of a target level. This reset script jumps to the hidden level "serial" (S) which also contains a stack of ScriptTriggers. Unlike the jump stacks, the scripts here are all different. They set the mission for the jump level and then jump back to the appropriate target level. They are used up in serial order.

So the first level entry from the world map goes like this: J --> Tn --> S --> Tn, where n starts at 1 (or "a" in this example). Without a reset, subsequent level entries are more direct: J --> Tn. And the n is updated each time S is visited. It's hard to describe cleanly, but it works. Below is a listing of all the filenames involved, with the appropriate scripts grouped next to the mission files that call them. There are mission swaps for the jump map, and map swaps for the target mission, but the serial map & mission never change. Also listed are the relevant sections of game.txt, and the map & mission files, plus the script files.

Thanks to xander, TGF, trickfred, et al. or I never would have figured this out.

-brice

Code: Select all

# game.txt
Locations_StartDefinition
    # Id  Avail                   mapFile                    missionFile
    # ==================================================================
      70    1                   map_jump.txt          mission_jump2a.txt
     
      80    0                 map_serial.txt          mission_serial.txt
     
      90    0               map_target_a.txt          mission_target.txt
      91    0               map_target_b.txt          mission_target.txt
      92    0               map_target_c.txt          mission_target.txt
      93    0               map_target_d.txt          mission_target.txt
      94    0               map_target_d.txt          mission_target.txt
Locations_EndDefinition

Code: Select all

# files grouped together by map, mission, and scripts called
map_jump.txt
mission_jump2a.txt
   scripts/jump2target_a.txt
mission_jump2b.txt
   scripts/jump2target_b.txt
mission_jump2c.txt
   scripts/jump2target_c.txt
mission_jump2d.txt
   scripts/jump2target_d.txt
mission_jump2e.txt
   scripts/jump2target_e.txt

map_target_a.txt
map_target_b.txt
map_target_c.txt
map_target_d.txt
map_target_e.txt
mission_target.txt
   scripts/jump2serial.txt

map_serial.txt
mission_serial.txt
   scripts/setjump2a.txt
   scripts/setjump2b.txt
   scripts/setjump2c.txt
   scripts/setjump2d.txt
   scripts/setjump2e.txt

Code: Select all

# mission_jump2{a,b,c,d,e} call scripts jump2target_{a,b,c,d,e}.txt
Buildings_StartDefinition
    # Type          id      x       z   tm   rx    rz   isGlobal
    # ====================================================================
    ScriptTrigger  1   894.15  937.09  2   1.0  0.0  0  -1  100.00 jump2target_a.txt always
    ScriptTrigger  2   894.15  937.09  2   1.0  0.0  0  -1  100.00 jump2target_a.txt always
    ScriptTrigger  3   894.15  937.09  2   1.0  0.0  0  -1  100.00 jump2target_a.txt always
    ScriptTrigger  4   894.15  937.09  2   1.0  0.0  0  -1  100.00 jump2target_a.txt always
    ScriptTrigger  5   894.15  937.09  2   1.0  0.0  0  -1  100.00 jump2target_a.txt always
    ...
    (ScriptTriggers stacked deep enough so player never uses them all up between resets)
Buildings_EndDefinition

Code: Select all

# script jump2target_a.txt -- scripts jump2target_{b,c,d,e} are similar.
ExitLocation
EnterLocation target_a

Code: Select all

# mission_serial has stacked ScriptTriggers, each one calling a different script, in serial order.
Buildings_StartDefinition
    # Type          id      x       z   tm   rx    rz   isGlobal
    # ====================================================================
    ScriptTrigger   0   1040.74 1048.42  2   1.00  0.00   0  -1  100.00 setjump2a.txt always
    ScriptTrigger   1   1040.74 1048.42  2   1.00  0.00   0  -1  100.00 setjump2b.txt always
    ScriptTrigger   2   1040.74 1048.42  2   1.00  0.00   0  -1  100.00 setjump2c.txt always
    ScriptTrigger   3   1040.74 1048.42  2   1.00  0.00   0  -1  100.00 setjump2d.txt always
    ScriptTrigger   4   1040.74 1048.42  2   1.00  0.00   0  -1  100.00 setjump2e.txt always
Buildings_EndDefinition

Code: Select all

# script setjump2a.txt -- scripts setjump2{b,c,d,e} are similar.
Wait 0.01 # !!CRITICAL!! WAIT BEFORE EXIT LOCATION
SetMission jump mission_jump2a.txt
ExitLocation
EnterLocation target_a

Code: Select all

# mission_target is shared by all map_target_{a,b,c,d,e}.
# ScriptTrigger fires only once and then is retired until level is reset.
Buildings_StartDefinition
    # Type          id      x       z   tm   rx    rz   isGlobal
    # ====================================================================
    ScriptTrigger  990   894.15  937.09  2   1.00  0.00   0  -1  100.00 jump2serial.txt always
Buildings_EndDefinition

Code: Select all

# script jump2serial.txt

Wait 0.35  # !!CRITICAL!! WAIT BEFORE EXIT LOCATION
# longer than setjumps so instant units get instantiated and saved.

# MUST allow the calling EnterLocation script to completely finish
# before exiting this location via an Always ScriptTrigger script --
# otherwise the used dynamic ScriptTriggers won't be retired for this
# location. The current mission will be saved without dynamic changes.

ExitLocation
EnterLocation serial


EDIT: If the Wait delay in script jump2serial.txt (above) has to be long, and the level fades in and out annoyingly during resets, there is a workaround that keeps the screen black until the target level is ready. Move the "start" camera in mission_target so that it points into black space. Then create a second camera at the desired "realstart" location. Next add a non-dynamic ScriptTrigger to each of the target_{a,b,c,d,e} map files. The building IDs must all be greater than the ID of the jump2serial.txt ScriptTrigger in the mission_target file. The IDs set their precedence, so the jump2serial.txt will occur first, and then when the serial level's script jumps back, the camcutrealstart.txt script will execute. Since this script is in the map file it never gets retired, and so it always cuts to the correct camera whether the level is being re-entered or reset. This way the jump2serial.txt Wait delay can be set long enough to avoid all mission & script problems without having to worry about the fade-in.

Code: Select all

# map_target_{a,b,c,d,e} where ScriptTrigger IDs are 995..999 respectively
Buildings_StartDefinition
    # Type          id      x       z   tm   rx    rz   isGlobal
    # ====================================================================
    ScriptTrigger  995  894.15  937.09  2   1.0  0.0  0  -1  10.00 camcutrealstart.txt always
Buildings_EndDefinition

Code: Select all

# script camcutrealstart.txt
CamCut realstart
CamReset
brice
level2
level2
Posts: 167
Joined: Fri Feb 23, 2007 6:04 am

Postby brice » Wed May 23, 2007 1:21 pm

... just a followup. The scheme above can also be achieved for serial mission swaps -- at the expense of an extra hop between levels during resets. I'll just sketch it.

Using serial mission swaps instead of serial map swaps has two benefits. First no "jump gate" level is needed and the playable level can be entered directly. Second you can have an unlimited number of missions in a game. The total number of maps is limited to somewhere around 90 -- above that and the game will no longer load. (I don't know if this is a hard limit, or just reflects memory consumption -- my test mod consumes a *lot* of memory.) So if you have a lot of complex levels and you want them to each be resettable several times, you could pay the price of slightly longer reset cycles.

The system relies on the same trick as above: each mission contains a single dynamic ScriptTrigger which jumps to the Serial level on first entry (and after each reset). The big problem with mission swaps is that you immediately create a circular loop since the new mission will also have a new ScriptTrigger jumping back to the Serial level. The map swapping system above avoids this issue by reusing the same mission for all the maps -- there is no circle to fall into.

We can break the circles in the mission swaps by making the Serial level a little more complex. When the Serial level swaps the mission for the Target level, there will be a fresh ScriptTrigger waiting to jump back into the Serial level. We need to allow for this second hop through the Serial level, but we don't want to do a second mission swap -- that leads to the circular script loops. So we setup the ScriptTriggers in the Serial level to alternate between two different scripts. One script sets the mission and then jumps to the Target level. The other script just passes through -- a kind of "NOP". Of course, all scripts are versioned to reflect the serial numbers of the new missions.

So the mission for the Serial level will look something like this:

Code: Select all

# schematic mission file for the Serial level doing serial mission swaps
    ScriptTrigger   0   ...   nop+jump2a.txt    always
    ScriptTrigger   1   ...   swap2b+jump2b.txt    always
    ScriptTrigger   2   ...   nop+jump2b.txt    always
    ScriptTrigger   3   ...   swap2c+jump2c.txt    always
    ScriptTrigger   4   ...   nop+jump2c.txt    always
    ScriptTrigger   5   ...   swap2d+jump2d.txt    always
    ScriptTrigger   6   ...   nop+jump2d.txt    always
    ... etc.


There would be only one map file for the Target level. And since it starts the game with mission = mission_target_a.txt, the ScriptTrigger stack in the Serial mission can start with a nop. The hops for a reset look like this: Tn --> S --> Tn+1 --> S --> Tn+1. Four hops instead of the three used in the map swapping system, but this only happens when players reset their level. For level re-entries there are no hops, which is one shorter than in the map swap system. For the reset cycle you still have to be very careful about inserting appropriate Wait delays before the ExitLocations after each hop, as described above.

Hopefully a few people find this useful, and / or interesting. Although if your reaction is "WTF!?", well that's probably appropriate too.

-brice
User avatar
KingAl
level5
level5
Posts: 4138
Joined: Sun Sep 10, 2006 7:42 am

Postby KingAl » Wed May 23, 2007 1:34 pm

WTF!?

Incidentally, I commend all the work you've done towards opening Darwinia up to further modding - I'm sure it'll be very handy if I ever get enough time and motivation to try my own hand (though, being of a lazy bent, I'll likely wait until Icepick's more moddable release becomes available).
Gentlemen, you can't fight in here: this is the War Room!
Ultimate Uplink Guide
Latest Patch

Return to “Mod Projects”

Who is online

Users browsing this forum: No registered users and 3 guests