TABLE OF CONTENTS
As people might be more likely to make SP maps if there's a more comprehensive guide, I'm going to try and do some kind of step-by-step making a SP map thing, hopefully covering all the stuff that's already been covered in the very good tutorials people have written, and maybe some other things too.
The plot's pretty irrelevant, and probably won't fit neatly into the SP campaign. You'll start on somewhere korriban-ish (maybe korriban), and there will be two levels: an outside one in which you try to get inside, and then an inside one in which you finish the level by dueling some kind of cultist or reborn.
I'll begin by doing a very simple pair of maps, and then gradually introduce more complex things to make them more interesting. This is probably not the best way to make a SP map, but might make things clearer. To follow this you should probably know how to make a box map properly and how to use the entity window.
If you think that I am saying something wrong or bad, let me know and I'll improve that bit. Any feedback or requests would be very welcome.
PART 1: TWO ROOMS ONE BOSS
I opened up Radiant and checked that I was in SP mapping mode by going to File->Project Settings (I was!).
I made a quick boxmap and put a sky texture on the walls and ceiling, and sand on the ground. I then added a door frame to lead to the next map, and saved the map as map1.map.
As the sky I chose doesn't emit light, I added a couple of entity suns. An entity sun is just a spotlight (a light entity targeted at info_null entity) with the _sun key set to 1 and the _color key set (you can set the colour by hitting K with the entity selected). The second entity sun was meant to be for "shadows", so was a little bluer, not so bright, and in a different direction.
The map always needs a spawn point, so I added an info_player_start and set its angle so that it was facing towards the door.
To finish the outside map, I needed to make the door frame actually start the next map, so I created a brush to fill the doorframe textured with textures/system/trigger and turned it into a trigger_once. I ticked the playeronly box in the entity window so that only the player reaching the door frame activates the trigger.
I then created a target_level_change with the mapname key set to map2, and targeted the trigger_once at the target_level_change.
Finally, I compiled the map by doing one of the Bsp->Q3Map2: (test) compile options (doesn't matter which), and put the produced map1.bsp into a maps folder inside my base folder.
I then created a new map and made another quick boxmap, this time with some more stone-type textures, saving the map as map2.map. Although there weren't any sources for light, I placed some light entities and made them flicker a bit by setting the style key to 1.
The map again needed a spawn point, so I added an info_player_start with the keep_prev spawn flag set so that any damage from the previous level is carried over.
For the boss, I added an NPC_Cultist_Saber with the med spawnflag set so that it used medium style, and set its angle to face the player. The credits should run when the boss is killed, so I set its NPC_target to endscriptrunner - the NPC_target is used when the NPC dies.
To run the credits, I created a target_scriptrunner with its targetname set to endscriptrunner, its delay set to 1 second (so that the cultist's death is still shown), and its usescript set to kor2/theClosingCredits (this means that the script scripts/kor2/theClosingCredits.ibi is run, which is the script used in the base campaign to show the credits).
After compiling the map again and putting the map2.bsp in the maps folder, I opened up SP, typed map map1 into the console, and quickly played the maps:
https://www.youtube.com/watch?v=0m0DQLXlLag
Although they're meant for JK2, Kengo's cutscene tutorials still apply https://web.archive.org/web/20091022065706/https://geocities.com/kengomaps/tutorials.html. In addition to the stuff in the JKHub tutorials section, lassev's site has some interesting stuff https://www.student.oulu.fi/~lvaarisk/sivut/resources.htm. I also looked at the scripting stuff from https://map-forge.net/wiki/doku.php?id=tutorials:index whilst doing this.
If you're using BehavED you *might* want to look at another scripting tutorial for a guide on how to use it.
I feel like I might be going a bit too quickly over stuff - any feedback is welcome.
I decided to add a couple of short cutscenes rather than just spawning the player straight into the maps.
First, though, the player must have got to korriban somehow, so I added a Z-95 and a small landing pad.
I could have made the Z-95 a misc_model (to get proper shadows) or a misc_model_static, but instead I decided to make it a func_static so that it would be easier to make it move in the future. The func_static consists of an origin brush (a small cube textured with textures/system/origin) and some clip brushes (textured with textures/system/physics_clip) to make the model solid. I found temporarily making a misc_model of the Z-95 was very helpful for working out where the origin brush should go and how to make the clip brushes.
After the brushes were turned into a (single) func_static, I set the model2 key to the Z-95 md3 model, and the modelangles key to 0 180 0 - this is same rotation as setting the angle key to 180 for a misc_model.
I decided to have the first cutscene just being the camera looking at the door for a few seconds. This meant writing a new ICARUS script, but a couple of things needed to be set up in the map first.
I created a ref_tag at the end of the map with the landing pad, and targeted it towards an info_null in the direction of the door. The location of the ref_tag would be used as the location of the camera, and the angles of the camera in the cutscene would be given by the direction of the line connecting the ref_tag and info_null. I set the targetname of the ref_tag to startcam.
In order to run the script that would play the cutscene, I added a target_scriptrunner, and set the usescript to map1/intro. I then targeted the info_player_start at the target_scriptrunner so that the script at scripts/map1/intro.ibi would be run as soon as the map started. With everything in the map done for now, I compiled it and put the bsp into my maps folder.
I now needed to make that script! As I use a Mac I don't use BehavED and just write the scripts in a text editor, but you should probably write things in BehavED most of the time. If you do choose to write them in a text editor, you can still open them up in BehavED and compile them though. If you're on a Mac, you'll need to use IBIze in Terminal.
I created an empty text file called intro.icarus (intro.txt may be more sensible) and placed it in a folder called map1 inside the scripts folder in my base (you might need to create scripts). I typed this stuff in:
camera ( /*@CAMERA_COMMANDS*/ ENABLE );
camera ( /*@CAMERA_COMMANDS*/ MOVE, $tag( "startcam", ORIGIN)$, 0 );
camera ( /*@CAMERA_COMMANDS*/ PAN, $tag( "startcam", ANGLES)$, < 0.000 0.000 0.000 >, 0 );
wait ( 5000 );
camera ( /*@CAMERA_COMMANDS*/ DISABLE );
saved, and then compiled the script.
The ENABLE and DISABLE commands are pretty self-explanatory - they turn camera mode on and off. The MOVE command here moves the camera to the location given by the ref_tag with targetname set to startcam immediately (the 0 at the end means that it takes 0 ms). The PAN command immediately rotates the camera so that it has the same angles as the ref_tag with targetname set to startcam. The wait command means that camera mode stays on for 5000ms, i.e. 5 seconds.
I started up the game with map map1 to check that it worked. It did, but there's the slight problem that the player isn't drawn in cutscenes.
Fortunately, there is an entity spawned by NPC_Player that has the same model and weapon as the player that you can use in cutscenes, so I opened the map up again.
I placed an
NPC_Player entity a bit behind the
info_player_start and set its
droptofloor,
cinematic and
notsolid spawnflags. I then set its
npc_targetname to
fakeplayer so that the spawned
NPC would have its
targetname and
script_targetname set to
fakeplayer, and set its
angle to match the player's.
I thought it would be interesting for the "player" to walk forwards a bit during the cutscene, so I created a
waypoint_navgoal near the
info_player_start for the
NPC to walk to, and set its
targetname to
walkspawn.
That was all the mapping needed, so I opened up intro.icarus again after doing a fresh compile of the map, and made a few changes:
camera ( /*@CAMERA_COMMANDS*/ ENABLE );
camera ( /*@CAMERA_COMMANDS*/ MOVE, $tag( "startcam", ORIGIN)$, 0 );
camera ( /*@CAMERA_COMMANDS*/ PAN, $tag( "startcam", ANGLES)$, < 0.000 0.000 0.000 >, 0 );
affect ( "fakeplayer", FLUSH )
{
set ( /*@SET_TYPES*/ "SET_ANIM_BOTH", /*@ANIM_NAMES*/ "BOTH_STAND9" );
set ( /*@SET_TYPES*/ "SET_ANIM_HOLDTIME_BOTH", 2000 );
wait ( 2000 );
set ( /*@SET_TYPES*/ "SET_WALKING", /*@BOOL_TYPES*/ "true" );
set ( /*@SET_TYPES*/ "SET_NAVGOAL", "walkspawn" );
}
wait ( 5000 );
remove ( "fakeplayer" );
camera ( /*@CAMERA_COMMANDS*/ DISABLE );
The new lines mean that the entity with
targetname set to
fakeplayer (the "player"
NPC) has its animation set to
BOTH_STAND9 for
2000ms (2 seconds). After these 2 seconds are up, the "player"
NPC then walks towards the
waypoint_navgoal with
targetname set to
walkspawn. Just before the camera is turned off,
fakeplayer is removed so that the player doesn't have to worry about a doppelganger.
(note: although there are two different wait commands, one waiting 2 seconds and the other 5 seconds, only 5 seconds pass before the camera is turned off - this is because the commands in the fakeplayer affect block are being run on fakeplayer in parallel to the other commands (which are actually being run on the target_scriptrunner) and do not interrupt them.)
I compiled the script again, and tested the changes in-game.
It was now time to add an intro cutscene to the next map. That meant a couple of changes in Radiant.
I added an NPC_Player, placing it next to the info_player_start, and set its spawnflags and npc_targetname just as before. I added the spawnflag cinematic to the NPC_Cultist_Saber, and also gave it the npc_targetname bosscultist so that I could control it from a script.
I created two ref_tag entities and targeted them at corresponding info_null entities - one from above where the info_player_spawn was facing towards the boss, and the other above where the boss was facing towards the info_player_spawn. I set their targetname keys to bosscam and playercam respectively.
Finally, in order to run the script that would play the cutscene, I added another target_scriptrunner, with usescript set to map2/intro2. I then targeted the info_player_start at the target_scriptrunner so that the script at scripts/map2/intro2.ibi would be run as soon as the map started. I did a fresh compile of the map and went back to scripting.
Here's what I wrote:
camera ( /*@CAMERA_COMMANDS*/ ENABLE ); // OLD
camera ( /*@CAMERA_COMMANDS*/ MOVE, $tag( "bosscam", ORIGIN)$, 0 ); // OLD
camera ( /*@CAMERA_COMMANDS*/ PAN, $tag( "bosscam", ANGLES)$, < 0.000 0.000 0.000 >, 0 ); // OLD
affect ( "bosscultist", FLUSH )
{
set ( /*@SET_TYPES*/ "SET_ANIM_BOTH", /*@ANIM_NAMES*/ "BOTH_STAND9" );
wait ( 1000 );
set ( "SET_SABERACTIVE", "true" );
wait ( 1500 );
set ( /*@SET_TYPES*/ "SET_ANIM_BOTH", /*@ANIM_NAMES*/ "BOTH_SHOWOFF_STRONG" );
}
affect ( "fakeplayer", FLUSH )
{
set ( /*@SET_TYPES*/ "SET_ANIM_BOTH", /*@ANIM_NAMES*/ "BOTH_STAND9" ); // OLD
set ( /*@SET_TYPES*/ "SET_ANIM_HOLDTIME_BOTH", -1 );
}
wait ( 3500 );
camera ( /*@CAMERA_COMMANDS*/ MOVE, $tag( "playercam", ORIGIN)$, 3000 );
camera ( /*@CAMERA_COMMANDS*/ PAN, $tag( "playercam", ANGLES)$, < 0.000 0.000 0.000 >, 3000 );
wait ( 3000 );
remove ( "fakeplayer" ); // OLD
camera ( /*@CAMERA_COMMANDS*/ DISABLE ); // OLD
affect ( "bosscultist", FLUSH )
{
wait ( 200 );
set ( /*@SET_TYPES*/ "SET_BEHAVIOR_STATE", /*@BSTATE_STRINGS*/ "BS_DEFAULT" ); //
set ( /*@SET_TYPES*/ "SET_LOOK_FOR_ENEMIES", "true" ); // not really necessary due to SET_ENEMY
set ( /*@SET_TYPES*/ "SET_ENEMY", "player" );
}
affect ( "player", FLUSH )
{
set ( "SET_SABERACTIVE", "true" );
}
What happens here is that the camera immediately moves to the position and angles of the ref_tag with targetname bosscam. fakeplayer has its animation set to BOTH_STAND9 for -1ms (that actually means forever, or at least until told otherwise). bosscultist is told to set its animation to BOTH_STAND9 for 1000ms, then turn on its lightsaber and after 1500ms do the taunt animation BOTH_SHOWOFF_STRONG. The script does nothing for 3500ms after issuing these commands. Note that the commands for bosscultist again run in parallel, so the time from start of script until end of wait is 3500ms and not 6000ms.
After this wait, the camera is moved to the position and angles of the ref_tag with targetname playercam. As the last number is 3000 rather than 0, this does not happen immediately, but the camera instead moves and rotates to its new position in 3000ms. The next wait command pauses the script until the camera move is finished.
Once the camera has been disabled and fakeplayer removed, the remaining commands turn on the players lightsaber, and make the cultist target the player. It is necessary to set the cultist's behaviour to BS_DEFAULT for it to fight because the cinematic spawnflag was set.
I finally compiled the script, and went back in game to check it all worked.
Sorry this one is so long.
Thanks to @
@Ramikad for already covering how to do dialogue, I've mentioned it briefly anyway for completeness.
Any feedback is useful (especially bad
)
Following lassev's tutorial, I created two (quite big) doors. For each door, I created an origin brush where the door would be rotated around, and then made the door and origin brush into a func_static. I set the f_push spawnflag on the two func_static entities so that they would be activated by force push. I set both doors' soundset key to stonedoor, and set the script_targetname keys to bigdoorl and bigdoorr respectively.
To rotate the doors a new script would be required, so I created a target_scriptrunner with usescript set to map1/bigdoors. I targeted both of the door func_static entities at the target_scriptrunner, so that when they were force pushed the target_scriptrunner would run the script at scripts/map1/bigdoors.ibi.
As the default count value of a target_scriptrunner is 1, the script is only run once, so I didn't have to worry about it accidentally being run again once the doors were open.
Now I needed to make the script, so I created bigdoors.icarus and put it in scripts/map1 in my base folder. As the door is only meant to open once and I didn't need all of the stuff from lassev's tutorial it's a pretty short script:
affect ( "bigdoorr", FLUSH )
{
rotate ( < 0 -75 0 >, 2500 );
}
affect ( "bigdoorl", FLUSH )
{
rotate ( < 0 75 0 >, 2500 );
}
It just rotates both doors to the desired angle (±75 degrees) over a period of 2500ms. I compiled the script, did a fresh compile of the map, and tested it out.
It worked, but the crosshair force corona effect still showed even when I'd opened the doors. This is fine, especially if the door is going to be reusable, but I decided to fix this. I checked how some of the force stuff was done in kor1_sample.map and went back to Radiant.
I unset the f_push spawnflag on the func_static door entities, and also removed their target using Del Key/Pair.
To actually trigger the door opening, I created another func_static entity from a brush textured with textures/system/caulk_nonsolid. I set its f_push spawnflag and targeted it at the target_scriptrunner that would open the doors. I'd noticed that in kor1_sample.map some of the force usable things were set up so that only the player could activate them - that wasn't really necessary here, but I did it just to be safe and so set the npc_targetname key of the new func_static to player. I also set the script_targetname key to dummydoor so that it would be possible to remove the dummy func_static after the player had opened the doors, and stop the force crosshair effect from showing.
That meant adding another couple of lines to bigdoors.icarus:
affect ( "bigdoorr", FLUSH )
{
rotate ( < 0 -75 0 >, 2500 );
}
affect ( "bigdoorl", FLUSH )
{
rotate ( < 0 75 0 >, 2500 );
}
affect ( "dummydoor", FLUSH )
{
remove ( "self" );
}
After compiling the script and doing a fresh compile of the map, I tested that it still worked.
(note: the remove ICARUS command only takes a targetname, *not* a script_targetname, as its argument, so remove( "dummydoor" ); wouldn't work unless the func_static had its targetname set to dummydoor. The affect command instead looks for a script_targetname. If an entity does not have a script_targetname set, then its script_targetname will be the same as its targetname)
There was still the slight problem that someone just using the map command to load map1 might not have any levels of force push, and so wouldn't be able to open the door. I changed the intro.icarus script in scripts/map1 to fix that by setting the player's force powers and spawn weapon.
That meant adding a few lines to the very top of the script:
affect ( "player", /*@AFFECT_TYPE*/ FLUSH )
{
set ( /*@SET_TYPES*/ "SET_SABER_THROW", /*@[member='Force']_LEVELS*/ "2" );
set ( /*@SET_TYPES*/ "SET_SABER_DEFENSE", /*@[member='Force']_LEVELS*/ "2" );
set ( /*@SET_TYPES*/ "SET_SABER_OFFENSE", /*@[member='SaberBlade83']_STYLES*/ "2" );
set ( /*@SET_TYPES*/ "SET_FORCE_HEAL_LEVEL", /*@[member='Force']_LEVELS*/ "1" );
set ( /*@SET_TYPES*/ "SET_FORCE_JUMP_LEVEL", /*@[member='Force']_LEVELS*/ "3" );
set ( /*@SET_TYPES*/ "SET_FORCE_SPEED_LEVEL", /*@[member='Force']_LEVELS*/ "1" );
set ( /*@SET_TYPES*/ "SET_FORCE_PUSH_LEVEL", /*@[member='Force']_LEVELS*/ "1" );
set ( /*@SET_TYPES*/ "SET_FORCE_PULL_LEVEL", /*@[member='Force']_LEVELS*/ "1" );
set ( /*@SET_TYPES*/ "SET_FORCE_MINDTRICK_LEVEL", /*@[member='Force']_LEVELS*/ "0" );
set ( /*@SET_TYPES*/ "SET_FORCE_GRIP_LEVEL", /*@[member='Force']_LEVELS*/ "0" );
set ( /*@SET_TYPES*/ "SET_FORCE_LIGHTNING_LEVEL", /*@[member='Force']_LEVELS*/ "0" );
set ( /*@SET_TYPES*/ "SET_FORCE_RAGE_LEVEL", /*@[member='Force']_LEVELS*/ "0" );
set ( /*@SET_TYPES*/ "SET_FORCE_PROTECT_LEVEL", /*@[member='Force']_LEVELS*/ "1" );
set ( /*@SET_TYPES*/ "SET_FORCE_ABSORB_LEVEL", /*@[member='Force']_LEVELS*/ "1" );
set ( /*@SET_TYPES*/ "SET_FORCE_DRAIN_LEVEL", /*@[member='Force']_LEVELS*/ "0" );
set ( /*@SET_TYPES*/ "SET_FORCE_SIGHT_LEVEL", /*@[member='Force']_LEVELS*/ "3" );
set ( /*@SET_TYPES*/ "SET_WEAPON", /*@[member='weaponx']_NAMES*/ "WP_NONE" ); // This means that the player spawns with no weapons in their hands
}
camera ( /*@CAMERA_COMMANDS*/ ENABLE );
etc...
recompiling the script, and of course testing.
Before moving onto the second level, I decided to make it so that the player's Z-95 actually landed in the cutscene at the start of map1. Fortunately I'd already made it a func_static, so I just needed to set its script_targetname to z95.
I also turned the z95 shadow plane into a func_static, consisting of an origin brush and the shadow brush, and set its script_targetname to z95_shadow.
It's probably most sensible to have your func_static placed where you want it to start from, but I'd placed it where I wanted it to finish, which meant there would be a little extra work.
So that it would be easier to write the script to move the Z-95, I placed a load of ref_tag entities. Firstly, one at the origin brush of the shadow with targetname set to shadow_end and another at the origin brush of the Z-95 with targetname set to z95_end. This would be then end destination for the Z-95 and its shadow. Next, I placed one a few hundred units above the Z-95's origin brush with targetname set to z95_hover - the Z-95 would descend and rotate from z95_hover to z95_end. Finally, I placed two more ref_tag entities offset from z95_hover and shadow_end by a few hundred units in the x direction, and gave them targetname z95_start and shadow_start respectively. The Z-95 would move between these ref_tag entities.
Since I was placing ref_tag entities, I put in another with targetname set to z95cam that would be the position of the camera view following the Z-95. That was everything needed in terms of entities, so I did a fresh compile of the map and moved onto scripting again.
Here's what I wrote in intro.icarus:
affect ( "player", /*@AFFECT_TYPE*/ FLUSH )
{
set ( /*@SET_TYPES*/ "SET_SABER_THROW", /*@[member='Force']_LEVELS*/ "2" );
set ( /*@SET_TYPES*/ "SET_SABER_DEFENSE", /*@[member='Force']_LEVELS*/ "2" );
set ( /*@SET_TYPES*/ "SET_SABER_OFFENSE", /*@[member='SaberBlade83']_STYLES*/ "2" );
set ( /*@SET_TYPES*/ "SET_FORCE_HEAL_LEVEL", /*@[member='Force']_LEVELS*/ "1" );
set ( /*@SET_TYPES*/ "SET_FORCE_JUMP_LEVEL", /*@[member='Force']_LEVELS*/ "3" );
set ( /*@SET_TYPES*/ "SET_FORCE_SPEED_LEVEL", /*@[member='Force']_LEVELS*/ "1" );
set ( /*@SET_TYPES*/ "SET_FORCE_PUSH_LEVEL", /*@[member='Force']_LEVELS*/ "1" );
set ( /*@SET_TYPES*/ "SET_FORCE_PULL_LEVEL", /*@[member='Force']_LEVELS*/ "1" );
set ( /*@SET_TYPES*/ "SET_FORCE_MINDTRICK_LEVEL", /*@[member='Force']_LEVELS*/ "0" );
set ( /*@SET_TYPES*/ "SET_FORCE_GRIP_LEVEL", /*@[member='Force']_LEVELS*/ "0" );
set ( /*@SET_TYPES*/ "SET_FORCE_LIGHTNING_LEVEL", /*@[member='Force']_LEVELS*/ "0" );
set ( /*@SET_TYPES*/ "SET_FORCE_RAGE_LEVEL", /*@[member='Force']_LEVELS*/ "0" );
set ( /*@SET_TYPES*/ "SET_FORCE_PROTECT_LEVEL", /*@[member='Force']_LEVELS*/ "1" );
set ( /*@SET_TYPES*/ "SET_FORCE_ABSORB_LEVEL", /*@[member='Force']_LEVELS*/ "1" );
set ( /*@SET_TYPES*/ "SET_FORCE_DRAIN_LEVEL", /*@[member='Force']_LEVELS*/ "0" );
set ( /*@SET_TYPES*/ "SET_FORCE_SIGHT_LEVEL", /*@[member='Force']_LEVELS*/ "3" );
set ( /*@SET_TYPES*/ "SET_WEAPON", /*@[member='weaponx']_NAMES*/ "WP_NONE" ); // This means that the player spawns with no weapons in their hands
}
camera ( /*@CAMERA_COMMANDS*/ ENABLE );
affect ( "z95", FLUSH )
{
set ( "SET_CAMERA_GROUP", "ship" );
move ( $tag( "z95_start", ORIGIN)$, < 0 180 0 >, 0 );
wait ( 100 );
move ( $tag( "z95_hover", ORIGIN)$, < 0 180 0 >, 2000 );
wait ( 2000 );
move ( $tag( "z95_end", ORIGIN)$, < 0 0 0 >, 4500 );
}
affect ( "z95_shadow", FLUSH )
{
move ( $tag( "shadow_start", ORIGIN)$, < 0 180 0 >, 0 );
wait ( 100 );
move ( $tag( "shadow_end", ORIGIN)$, < 0 180 0 >, 2000 );
wait ( 2000 );
move ( $tag( "shadow_end", ORIGIN)$, < 0 0 0 >, 4500 );
}
affect ( "fakeplayer", FLUSH )
{
set ( /*@SET_TYPES*/ "SET_INVISIBLE", /*@BOOL_TYPES*/ "true" );
}
camera ( /*@CAMERA_COMMANDS*/ MOVE, $tag( "z95cam", ORIGIN)$, 0 );
camera ( /*@CAMERA_COMMANDS*/ FOLLOW, "ship", 0, 0 );
wait ( 6000 );
camera ( /*@CAMERA_COMMANDS*/ FADE, < 0 0 0 >, 0, < 0 0 0 >, 1, 1000 );
camera ( /*@CAMERA_COMMANDS*/ FOLLOW, "NULL", 0, 0 );
wait ( 1000 );
camera ( /*@CAMERA_COMMANDS*/ FADE, < 0 0 0 >, 1, < 0 0 0 >, 0, 1000 );
camera ( /*@CAMERA_COMMANDS*/ MOVE, $tag( "startcam", ORIGIN)$, 0 );
camera ( /*@CAMERA_COMMANDS*/ PAN, $tag( "startcam", ANGLES)$, < 0.000 0.000 0.000 >, 0 );
affect ( "fakeplayer", FLUSH )
{
set ( /*@SET_TYPES*/ "SET_INVISIBLE", /*@BOOL_TYPES*/ "false" );
set ( /*@SET_TYPES*/ "SET_ANIM_BOTH", /*@ANIM_NAMES*/ "BOTH_STAND9" );
set ( /*@SET_TYPES*/ "SET_ANIM_HOLDTIME_BOTH", 2000 );
wait ( 2000 );
set ( /*@SET_TYPES*/ "SET_WALKING", /*@BOOL_TYPES*/ "true" );
set ( /*@SET_TYPES*/ "SET_NAVGOAL", "walkspawn" );
}
wait ( 5000 );
remove ( "fakeplayer" );
camera ( /*@CAMERA_COMMANDS*/ DISABLE );
It's a bit more complicated now (or at least longer), so to split it up a little:
affect ( "z95", FLUSH )
{
set ( "SET_CAMERA_GROUP", "ship" );
move ( $tag( "z95_start", ORIGIN)$, < 0 180 0 >, 0 );
wait ( 100 );
move ( $tag( "z95_hover", ORIGIN)$, < 0 180 0 >, 2000 );
wait ( 2000 );
move ( $tag( "z95_end", ORIGIN)$, < 0 0 0 >, 4500 );
}
affect ( "z95_shadow", FLUSH )
{
move ( $tag( "shadow_start", ORIGIN)$, < 0 180 0 >, 0 );
wait ( 100 );
move ( $tag( "shadow_end", ORIGIN)$, < 0 180 0 >, 2000 );
wait ( 2000 );
move ( $tag( "shadow_end", ORIGIN)$, < 0 0 0 >, 4500 );
}
First, z95 has its SET_CAMERA_GROUP set to ship. Camera groups are collections of entities that the camera can follow as they move around. The move command specifies the final origin, the final angles and the time taken to move the entity. The z95 and z95_shadow are moved to their correct start positions immediately, z95_start and shadow_start respectively, and rotated 180 degrees from their rotation in the map file. After 100ms, they start moving to their next position over a period of 2000ms - the 100ms delay is just so that they actually do get moved to the start position, and wouldn't be necessary if you'd placed the ship sensibly in the first place. Finally, z95 rotates back to its rotation in the map file and lowers to z95_end, and z95_shadow rotates back to its rotation in the map file. I didn't use the rotate command here for z95_shadow as I found that if I did it rotated the other way to z95!
affect ( "fakeplayer", FLUSH )
{
set ( /*@SET_TYPES*/ "SET_INVISIBLE", /*@BOOL_TYPES*/ "true" );
}
I didn't want fakeplayer to show up until after the Z-95 had landed, so I made fakeplayer invisible. Remember that because of how affects work, this command is executed at the same time as z95 and z95_shadow moving to their start positions.
camera ( /*@CAMERA_COMMANDS*/ MOVE, $tag( "z95cam", ORIGIN)$, 0 );
camera ( /*@CAMERA_COMMANDS*/ FOLLOW, "ship", 0, 0 );
wait ( 6000 );
This command moves the camera origin to the ref_tag I'd placed with targetname z95cam, and the FOLLOW camera command makes the camera follow any entities in camera group ship. That means that it follows z95 as it moves. There's no need for a PAN command if you're using the FOLLOW command. The wait command gives the Z-95 and its shadow time to move.
camera ( /*@CAMERA_COMMANDS*/ FADE, < 0 0 0 >, 0, < 0 0 0 >, 1, 1000 );
camera ( /*@CAMERA_COMMANDS*/ FOLLOW, "NULL", 0, 0 );
wait ( 1000 );
camera ( /*@CAMERA_COMMANDS*/ FADE, < 0 0 0 >, 1, < 0 0 0 >, 0, 1000 );
camera ( /*@CAMERA_COMMANDS*/ MOVE, $tag( "startcam", ORIGIN)$, 0 );
camera ( /*@CAMERA_COMMANDS*/ PAN, $tag( "startcam", ANGLES)$, < 0.000 0.000 0.000 >, 0 );
The first FADE camera command fades the screen to black: it changes the overlay colour from an initial RGB colour < 0 0 0 > (black) and alpha 0 (invisible!) to a final colour < 0 0 0 > (black) and alpha 1 (fully visible) over 1000ms. The RGB values are between 0 and 1. The new FOLLOW command stops the camera from following the entities camera group ship. After the first fade is complete, the camera fades back in from black at the camera location given by the ref_tag with targetname startcam.
affect ( "fakeplayer", FLUSH )
{
set ( /*@SET_TYPES*/ "SET_INVISIBLE", /*@BOOL_TYPES*/ "false" );
set ( /*@SET_TYPES*/ "SET_ANIM_BOTH", /*@ANIM_NAMES*/ "BOTH_STAND9" );
set ( /*@SET_TYPES*/ "SET_ANIM_HOLDTIME_BOTH", 2000 );
wait ( 2000 );
set ( /*@SET_TYPES*/ "SET_WALKING", /*@BOOL_TYPES*/ "true" );
set ( /*@SET_TYPES*/ "SET_NAVGOAL", "walkspawn" );
}
wait ( 5000 );
remove ( "fakeplayer" );
camera ( /*@CAMERA_COMMANDS*/ DISABLE );
The only change from last time is that fakeplayer has to be made visible again.
I compiled the script, and tested the map out again.
I thought that the room for the second level needed to be made a bit more visually interesting, so I added a lava pit round the edge. To stop the cultist from jumping in the lava too much, I placed a brush textured with textures/system/do_not_enter across the surface of the lava, and also put some as barriers around the edge of the pit. I did a fresh compile of the map, and moved onto scripting.
I wanted the cultist to actually say something as it was taunting the player, and to zoom the camera in on the cultist a little. Here's what I wrote in intro2.icarus:
affect ( "player", /*@AFFECT_TYPE*/ FLUSH )
{
set ( /*@SET_TYPES*/ "SET_WEAPON", /*@[member='weaponx']_NAMES*/ "WP_SABER" );
}
camera ( /*@CAMERA_COMMANDS*/ ENABLE );
camera ( /*@CAMERA_COMMANDS*/ MOVE, $tag( "bosscam", ORIGIN)$, 0 );
camera ( /*@CAMERA_COMMANDS*/ PAN, $tag( "bosscam", ANGLES)$, < 0.000 0.000 0.000 >, 0 );
affect ( "bosscultist", FLUSH )
{
set ( /*@SET_TYPES*/ "SET_ANIM_BOTH", /*@ANIM_NAMES*/ "BOTH_STAND9" );
wait ( 3000 );
set ( "SET_SABERACTIVE", "true" );
wait ( 500 );
sound ( /*@CHANNELS*/ CHAN_VOICE, "sound/chars/cultist1/misc/victory3.mp3" );
wait ( 1000 );
set ( /*@SET_TYPES*/ "SET_ANIM_BOTH", /*@ANIM_NAMES*/ "BOTH_SHOWOFF_STRONG" );
}
affect ( "fakeplayer", FLUSH )
{
set ( /*@SET_TYPES*/ "SET_ANIM_BOTH", /*@ANIM_NAMES*/ "BOTH_STAND9" );
set ( /*@SET_TYPES*/ "SET_ANIM_HOLDTIME_BOTH", -1 );
}
wait ( 1000 );
camera ( /*@CAMERA_COMMANDS*/ ZOOM, 40, 2000 );
wait ( 5500 );
camera ( /*@CAMERA_COMMANDS*/ ZOOM, 80, 1000 );
camera ( /*@CAMERA_COMMANDS*/ MOVE, $tag( "playercam", ORIGIN)$, 3000 );
camera ( /*@CAMERA_COMMANDS*/ PAN, $tag( "playercam", ANGLES)$, < 0.000 0.000 0.000 >, 3000 );
wait ( 3000 );
remove ( "fakeplayer" );
camera ( /*@CAMERA_COMMANDS*/ DISABLE );
affect ( "bosscultist", FLUSH )
{
wait ( 200 );
set ( /*@SET_TYPES*/ "SET_BEHAVIOR_STATE", /*@BSTATE_STRINGS*/ "BS_DEFAULT" ); //
set ( /*@SET_TYPES*/ "SET_LOOK_FOR_ENEMIES", "true" ); // not really necessary due to SET_ENEMY
set ( /*@SET_TYPES*/ "SET_ENEMY", "player" );
}
affect ( "player", FLUSH )
{
set ( "SET_SABERACTIVE", "true" );
}
It's largely the same as before - I made sure that the player started with the lightsaber so that when the saber was activated at the end of the script, everything would work properly even if the player had entered the map with a different weapon active. To highlight the mst important changes:
affect ( "bosscultist", FLUSH )
{
set ( /*@SET_TYPES*/ "SET_ANIM_BOTH", /*@ANIM_NAMES*/ "BOTH_STAND9" );
wait ( 3000 );
set ( "SET_SABERACTIVE", "true" );
wait ( 500 );
sound ( /*@CHANNELS*/ CHAN_VOICE, "sound/chars/cultist1/misc/victory3.mp3" );
wait ( 1000 );
set ( /*@SET_TYPES*/ "SET_ANIM_BOTH", /*@ANIM_NAMES*/ "BOTH_SHOWOFF_STRONG" );
}
The sound command with channel set to CHAN_VOICE means that bosscultist says sound/chars/cultist1/misc/victory3.mp3.
camera ( /*@CAMERA_COMMANDS*/ ZOOM, 40, 2000 );
wait ( 5500 );
camera ( /*@CAMERA_COMMANDS*/ ZOOM, 80, 1000 );
The ZOOM camera command changes the fov / zoom of the camera. The first command changes the fov to 40 over 2000ms. The second ZOOM command changes the fov back to 80 (which is the default) over 1000ms.
I compiled the script, and tested the map again in-game, but even though I had subtitles turned on for cinematics there weren't any!
To solve this, I created an empty text file called map2.str in the English folder in the strings folder in base (you may need to create some of these folders). For other languages you'll need to create map2.str in the corresponding language folder in strings. I edited map2.str so that it looked like:
VERSION "1"
REFERENCE VICTORY3
LANG_ENGLISH "The galaxy will be ours."
ENDMARKER
When a sound is played in a script, the game looks for the REFERENCE in mapname.str (so map2.str) corresponding to the filename of the sound without a path or extension - so for sound/chars/cultist1/misc/victory3.mp3 that means victory3. It's not case sensitive, so don't worry about the capitalisation. This is why it is sensible to give more unique names to any sound files that are going to be subtitled.
I tested the map again, and the subtitles worked.
I decided to add some of the things that make a level seem more polished, like the intro text crawl and customised credits. I didn't add these before because they involve making files that have to be loaded after the base versions, and so a pk3 is required rather than just using loose files. Once a level is replacing base files, it should probably be distributed as a mod that uses a mod folder.
First, I roughly followed lassev's forced
SP playermodel tutorial at
https://www.student.oulu.fi/~lvaarisk/tutorials/forcedplamodelv2.htm so that the map would be started from the new game menu and set the playermomdel correctly - you should read it! I made copies of
ui/newgame_first.menu and
ui/newgame.menu from
assets1.pk3, and edited them along the lines of the tutorial.
I opened them up in a text editor, scrolled to the bottom, and for each file replaced the action block for nextbutton:
action
{
play "sound/interface/button1.wav"
close all
open characterMenu
}
with this:
action
{
play "sound/interface/button1.wav"
close all
setcvar sex "m"
setcvar snd "jedi"
setcvar g_char_model "jedi"
setcvar g_char_skin_head "model_master"
setcvar g_char_skin_torso "model_master"
setcvar g_char_skin_legs "model_master"
setcvar g_char_color_red "255"
setcvar g_char_color_green "255"
setcvar g_char_color_blue "255"
setcvar g_saber "stinger"
setcvar g_saber2 ""
setcvar g_saber_color "orange"
exec "map map1"
}
This meant that the player's model would be set to jedi/master upon starting the game, the hilt set to stinger, and the saber colour to orange.
Next, I decided to add the proper Star Wars intro text crawl to the level. I located
menu/video/tc_english.tga in
assets1.pk3 - this is the file that is used for the text crawl. To replace it, I created my own
512x2048 tga image with the text crawl I'd written. From
https://www.theforce.net/fanfilms/postproduction/crawl/opening.asp it appears that the fonts used in the films (if not base Jedi Academy) are
Franklin Gothic Medium Condensed for the "episode name" and
Franklin Gothic Demi for the text (with slightly further condensed width). The main body of text should fill the entire 512px width of the image, and the text of the crawl is normally left justified. I made sure that the text was white with a transparent background, and saved the file. If you want to properly support other languages, there are also
tc_french,
tc_german and
tc_spanish files.
In order to get it to actually show up in game, I needed to make a slight change to one of the scripts. I opened up map1/intro.icarus, added a single line:
set ( /*@SET_TYPES*/ "SET_VIDEO_PLAY", "jk0101_sw" );
and compiled the script. This line means that the script will play the video file at video/jk0101_sw.roq. Other roq videos can of course be played with the same command, but video/jk0101_sw.roq is special - once the roq has played, the last frame of the video remains on the screen and is used as the background for the text crawl.
I created a pk3 file containing my new ui/newgame_first.menu, ui/newgame.menu and menu/video/tc_english.tga, making sure that I had kept the right folder structure, put it in my base folder and tested that the intro crawl worked.
When the map was loading, it was still missing briefing text, so I decided to fix this. I located strings/English/briefings.str (if you wish to support other languages find their briefings.str too) in assets0.pk3. As I'd be needing them later, I also made a copy of strings/English/credits.str and strings/English/objectives.str.
I opened briefings.str in a text editor. When the game loads a map, it looks for the REFERENCE in briefings.str corresponding to the name of the map, so I needed to add entries with REFERENCE MAP1 and MAP2. That meant adding some extra lines just before ENDMARKER:
REFERENCE MAP1
LANG_ENGLISH "There have been reports of dark side activity in the vicinity of a remote tomb on Korriban. Find a way into the tomb and investigate."
REFERENCE MAP2
LANG_ENGLISH "Put a stop to the dark side activity in the tomb."
In fact, I decided just to get rid of all of the briefings that weren't related to the maps, since my changes already broke base stuff, so my briefings.str only contained:
VERSION "1"
REFERENCE MAP1
LANG_ENGLISH "There have been reports of dark side activity in the vicinity of a remote tomb on Korriban. Find a way into the tomb and investigate."
REFERENCE MAP2
LANG_ENGLISH "Put a stop to the dark side activity in the tomb."
ENDMARKER
I saved strings/English/briefings.str, added it to my new pk3 file (with the correct folder structure) and checked that it worked.
Next I decided that the map needed objectives. It's not possible to create new objectives unless you make (very minor) changes to the game code, and I wanted to keep base compatibility for now, so that meant overwriting some of the existing ones. (But it's really not that difficult to add objectives to OpenJK and then distribute a modified version of OpenJK with your level.)
I opened up the objectives.str file, and edited the objectives with REFERENCE starting with HOTH2_. I only needed 3 objectives, but if any more are needed I can just edit those starting with HOTH3_ and so on.
REFERENCE HOTH2_OBJ1
LANG_ENGLISH "Find a way into the tomb."
REFERENCE HOTH2_OBJ2
LANG_ENGLISH "Enter the tomb."
REFERENCE HOTH2_OBJ3
LANG_ENGLISH "Defeat the cultist."
I saved the file, but in order for the objectives to actually show up I'd need to make some changes to the scripts.
I opened up intro.icarus and added a line near the start:
set ( /*@SET_TYPES*/ "SET_VIDEO_PLAY", "jk0101_sw" );
set ( /*@SET_TYPES*/ "SET_OBJECTIVE_SHOW", /*@OBJECTIVES*/ "HOTH2_OBJ1" );
affect ( "player", /*@AFFECT_TYPE*/ FLUSH )
...
I saved and compiled the script. SET_OBJECTIVE_SHOW here adds the objective HOTH2_OBJ1 to the player's datapad, so "Find a way into the tomb." is displayed with an open circle next to it.
This objective should be completed when the doors are opened, so I edited bigdoors.icarus so that it read:
affect ( "bigdoorr", FLUSH )
{
rotate ( < 0 -75 0 >, 2500 );
}
affect ( "bigdoorl", FLUSH )
{
rotate ( < 0 75 0 >, 2500 );
}
affect ( "dummydoor", FLUSH )
{
remove ( "self" );
}
set ( /*@SET_TYPES*/ "SET_OBJECTIVE_SUCCEEDED", /*@OBJECTIVES*/ "HOTH2_OBJ1" );
set ( /*@SET_TYPES*/ "SET_OBJECTIVE_SHOW", /*@OBJECTIVES*/ "HOTH2_OBJ2" );
This meant that the first objective would be shown as completed (displayed with a filled circle next to it) and then "Enter the tomb." would be displayed as a new objective.
I finally opened up intro2.icarus, and added
set ( /*@SET_TYPES*/ "SET_OBJECTIVE_SHOW", /*@OBJECTIVES*/ "HOTH2_OBJ3" );
to the start, saved, and compiled the script. It might have been sensible to also set the objective as completed once the cultist was dead, but since the credits would roll after only 1 second I didn't think this mattered too much.
I added my edited strings/English/objectives.str to my new pk3 and tested that the objectives were working properly.
The last thing to add was proper custom credits. Before editing credits.str, I needed to properly set up the credits camera, which I hadn't done before. I opened up map2.map and added a new ref_tag entity targeted at an info_null. This would give the origin and angles of the credits camera. Since I was using scripts/kor2/theClosingCredits.ibi to run the credits, this meant that I had to set the targetname of the ref_tag entity to closingcreditscam for it to work correctly. I did a fresh compile of the map and went back to the fun of string editing.
For the usual stars background, I could have made a little lit box textured with
textures/common/stars and put my
ref_tag inside that. I was "reminded" of this by an old scripting tutorial map file I found somewhere on my hard disk, I'm not too sure where the file came from or who made it though
I opened up credits.str in a text editor - it looks like this:
// Note to translators:
// If a sentence is the same in your language then please change it to "#same"
//
// eg:
// LANG_ENGLISH "HALT"
// LANG_GERMAN "#same"
//
// (This is so we can tell which strings have been signed-off as ok to be the same words for QA
// and because we do not store duplicate strings, which will then get exported again next time
// as being untranslated.)
//
VERSION "1"
CONFIG "W:\bin\stringed.cfg"
FILENOTES "all the credits"
REFERENCE RAVEN
LANG_ENGLISH "(#CARD)\nProject leads;Steve Raffel;Jon Zuk\nSenior Producer - Activision;Graham Fuchs\nProducer - LucasArts;Brett Tosti\nLead Programmer;James Monroe\nProgrammers;Keith Fuller;Michael Chang Gummelt;Bob Love;Christopher Reed;Aurelio Reis\nMultiplayer Programmer;Rich Whitehouse\nTech Programmers;Ste Cork;Gil Gribb\nLead Designer;Christopher Foster\nLevel Designers;Robert Bettenberg;Ford Dye;Scott McNutt;Justin Negrete;Stu Wiegert\nMultiplayer Designer;Mike Majernik\nLead Artist;Les Dorscheid\nArtists;Nick Choles;Joe Koberstein;Isaac B. Owens;Paul Richards;Todd Rueping;Derek Smith;Jason Smith;Andrew Trabbold\nLead Animator;Jarrod Showers\nAnimators;Richard Lico;Nick Maggiore\nSound Design;Julian Kwasneski;Kevin Schilder\nMusic Editing;Clint Bajakian\nAssociate Producers - Activision;Brelan Duff;Sam Nouriani\nAssociate Producer - LucasArts;Dan Pettit\n(#TITLE)\nCAST\n(#DOTENTRY)\nJaden Korr (Male);Phil Tanzini\nJaden Korr (Female);Jennifer Hale\nRosh Penin;Jason Marsden\nKyle Katarn, Saboteur 1;Jeff Bennett\nLuke Skywalker;Bob Bergen\nTavion;Kath Soucie\nAlora, Jedi Female;Grey DeLisle\nBoba Fett, Cultist 3, Stormtrooper 1, Rodian;Tom Kane\nWedge Antilles;Chris Cox\nChewbacca;Himself\nWeequay, Imperial Worker;Roger L. Jackson\nDasariah and Vil Kothos, Marka Ragnos;Peter Lurie\nLannik Racto, Rebel 1, Stormtrooper Officer;Nick Jamison\nProtocol Droid, Cultist 1, Hazardtrooper 1;Larry Cedar\nGran, Trandoshan;Jess Harnell\nRax Joris, Rockettrooper Officer, Stormtrooper 2, Merchant 1;Gregg Berger\nJedi 2, Reborn 3, Rockettrooper Officer, Noghri;Dee Baker\nJedi 1, Rockettrooper 1, Imperial Officer 2, Prisoner 2;Cam Clarke\nReborn 1, Cultist 2, Imperial Officer 1;Alastair Duncan\nVoice Director;Kris Zimmerman\n(#TITLE)\nRaven Software\n(#DOTENTRY)\nRaven Studio Head;Brian Raffel\nAdditional Art;Gina Garren;Matt Vainio\nAdditional Programming;Jeff Dischler;Dan Vondrak\nAdditional Multiplayer Programmers;Bryan Dube;Rick Johnson;Nathan McKenzie\nScript;Michael Chang Gummelt;Jon Zuk\nDirector of Product Development;Michael Crowns\nProject Administrator;Kenn Hoekstra\nAdministrative Assistant;Kate Steinmetz\nDuel, FFA, and CTF maps by Threewave Software;Michael 'Casey' Goodhead;Dan Gold;Dan Pitts\nPre-rendered Cinematics by Creat Studios;Natasha Kholiavko;Daniel Prousline;Avenir Sniatkov\nGTK Radiant Thanks;TTimo \nQ3Map2 Thanks;Randy 'ydnar' Reddig\nLinux Programming Thanks;Jim Drews\n(#TITLE)\nLucasArts\n(#DOTENTRY)\nAssistant Producer;Heather Logas\nLead Tester;Chane Hollander\nScript Editor;Michael Stemmle\nCompatibility Supervisor;Lynn Taylor\nCompatibility Technicians;Dan Martinez;GW Childs;Jim Davison;John Von Eichorn;John Carsey\nMultiplayer Compatibility Technician;Darryl Cobb\nMarketing;Marcella Churchill\nMarketing Coordinator;Logan Parr\nPublic Relations Director;Tom Sarris\nPublic Relations Manager;Heather Twist-Phillips\nPublic Relations Associate;Alexis Mervin\nContent Supervisor;Justin Lambros\nManager of International Production;Darren Hedges\nInternational Associate Producer;Bryan Davis\nManual Writer;Geoff Keighley\nManual and Package Design;Pyro Brand Development\nManual Editor;Brett Rector\nInternet Manager;Jim Passalacqua\nCreative Services Manager;Patty Hill\nInternational Public Relations Coordinator;Kathy Apostoli\nDirector of Sales;Meredith Cahill\nChannel Marketing Manager;Tim Moore\nChannel Marketing Specialist;Katy Walden\nSales Coordinator;Mike Maguire\nSales Analyst;Greg Robles\nDirector of Sales Operations;Jason Horstman\nProduct Support Supervisor;Jay Geraci\nHint Line Supervisor;Tabitha Tosti\nQA Supervisor;Chip Hinnenberg\nQuality Services Manager;Paul Purdy\nSpecial Thanks;Geoff Jones;Kevin Schmitt;Haden Blackman;Cory Allemeier;Dave Levison;Simon Jeffery;Randy Breen;Mary Bihr;Malcolm Johnson;Camela Martin;R.J. Berg;Seth J. Steinberg;Mark Barbolak;Matthey Fillbrandt;C.B. Studios\n(#TITLE)\nActivision, Inc.\n(#DOTENTRY)\nProduction Coordinators;Aaron Gray;Steve Holmes;Kekoa Lee-Creel\nProduction Tester;Danny Taylor\nSenior Executive Producer;Laird M. Malamed\nVice President,North American Studios;Mark Lamia\nExec. Vice President;Larry Goldberg\n(#TITLE)\nMarketing\n(#DOTENTRY)\nGeneral Manager - LucasArts Europe;Sarah Ewing\nSenior Brand Manager;Keely Brenner\nHead of European Communications;Tim Ponting\nPR Manager;Suzanne Panter\n(#TITLE)\nQuality Assurance/Customer Support\n(#DOTENTRY)\nProject Lead;Thom Denick\nSenior Project Lead;Matt McClure\nQA Manager;Marilena Rixford\nFloor Lead;Matt Nelson\nNight Crew Floor Lead;Joshua Feinman\nThird Shift Floor Lead;Bruce Campbell\nSingle Player Sprinter;Jason Newitt;Shane Sasaki\nMultiplayer Co-Coordinator;George Ngo;John Lagerholm\n(#TITLE)\nSupporting Leads\n(#DOTENTRY)\nProject Lead, Localizations;Paul Colbert\nSenior Project Lead, Localizations;Anthony Hatch Korotko\nCode Release Group;Jeff Sedivy\nNetwork Lab;Chris Keim\nCompatibility;Neil Barizo\n(#TITLE)\nCustomer Support Leads \n(#DOTENTRY)\nPhone Support;Gary Bolduc\nEmail Support;Michael Hill\nInformation and Escalation Support;Rob Lim\n(#TITLE)\nManagers\n(#DOTENTRY)\nManager, QA Night Crew;Adam Hartsfield\nManager, QA Third Shift;Jason Levine\nManager, Code Release Group;Tim Vanlaw\nManager, Customer Support;Bob McPherson\nManager, Resource Admin.;Nadine Theuzillot\n(#TITLE)\nTesters\n(#DOTENTRY)\nTest Team;Avery Bennet;Mike Castillo-Walsh;Heath Cecere;Nathaniel Chapman;Mike Cook;Jim Corbin;Henry Garcia;Jeremiah Jones;Justin Kaehler;Chris Morey;Kirk McNesby;Max Porter;Chris Puente;Jason Oertell;Jason Ralya;Walter Williams\nTest Team, Localizations;Wes Bunn; Samira Chaquorzahi;Allen Chiu;John Harvey;Baro Jung;Patrick Ortiz;Jesse Shannon;Hadar Silverman;Dennis Tong;Owen Waring;Brian Wilson;Danny Yanez\n(#TITLE)\nQA SPECIAL THANKS\n(#LINE)\nGreg Deutsch\nMichael Hand\nJim Summers\nJason Wong\nJoe Favazza\nEd Clune\nJason Potter\nGlenn Vistante\nJohn Rosser\nJason Levine\nIndra Gunawan\nMarco Scataglini\nCoach\nMike Beck\nWillie Bolton\nJennifer Vitiello\nMike Rixford\nAshley Walling\n (#TITLE)\nStar Wars music composed by John Williams. \n(#LINE)\n© Warner-Tamerlane Publishing & Bantha Music.\nAll rights administered by Warner-Tamerlane Publishing Corp.\nAll rights reserved. Used by permission.\n(#TITLE)\nADD'L SPECIAL THANKS:\n(#LINE)\nId Software\nATI\nnVidia\nCreative Labs\nLogitech\nDell\n(#TITLE)\nVERY SPECIAL THANKS:\n(#LINE)\nGeorge Lucas\n"
ENDMARKER
I needed to edit the text associated with the REFERENCE RAVEN. There are two different phases of the credits - first, there are the "cards" starting from (#CARD). Next, there are the scrolling credits starting from the first (#TITLE). The different cards in the first stage of credits are separated by "\n".
Looking closely at an individual card:
Project leads;Steve Raffel;Jon Zuk\n
Text is separated by ";" and the end of the card is signalled by "\n". The first piece of text is the title, shown in capitals. The other pieces of text associated with the card are the names.
Looking more closely at the scrolling part of the credits, the section titles appear after "(#TITLE)\n", like "CAST" or "LUCASARTS", and are shown in capitals. "(#DOTENTRY)\n" signifies the start of the actual list of people. The entries - basically the titles of the roles - are separated by "\n". For example,
Jaden Korr (Male);Phil Tanzini\n
means that the Phil Tanzini is credited under Jaden Korr (Male). The first piece of text is the role title, the remaining pieces of text separated by ";" give the names credited for that role. So
Test Team;Avery Bennet;Mike Castillo-Walsh;Heath Cecere;Nathaniel Chapman;Mike Cook;Jim Corbin;Henry Garcia;Jeremiah Jones;Justin Kaehler;Chris Morey;Kirk McNesby;Max Porter;Chris Puente;Jason Oertell;Jason Ralya;Walter Williams\n
means that a load of people are credited as being part of the Test Team.
Here's an unnecessary picture that shows how different people can be credited for one role:
I made some silly changes to check that everything worked as expected, but I didn't absolutely need to do the credits right now, so I might come back later to make real changes and do the final credits.
That's basically it for this time, a rof guide is coming up in the reasonably near future but suggestions and feedback are always welcome.
Note: If you want your map to show the fullscreen A long time ago... thing as its levelshot, you'll either need to call the first map
yavin1 or make some very minor changes to the code and distribute your modded version of
SP.
Recommended Comments
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now