Live a week in the life of "The Postal Dude"; a hapless everyman just trying to check off some chores. Buying milk, returning an overdue library book, getting Gary Coleman's autograph, what could possibly go wrong? Blast, chop and piss your way through a freakshow of American caricatures in this darkly humorous first-person adventure. Meet Krotchy: the toy mascot gone bad, visit your Uncle Dave at his besieged religious cult compound and battle sewer-dwelling Taliban when you least expect them! Endure the sphincter-clenching challenge of cannibal rednecks, corrupt cops and berserker elephants. Accompanied by Champ, the Dude's semi-loyal pitbull, battle your way through open environments populated with amazingly unpredictable AI. Utilize an arsenal of weapons ranging from a humble shovel to a uniquely hilarious rocket launcher. Collect a pack of attack dogs! Use cats as silencers! Piss and pour gasoline on anything and everyone! YOU KNOW YOU WANT TO!

Post tutorial Report RSS Coding Tutorial 8: Making your own intro menu

Tutorials using Unreal Script specific to POSTAL 2.

Posted by on - Advanced Client Side Coding

Tutorial originally released on Running With Scissor's Postal website. Was taken down more than 10 years ago. Mirrored here for archival purposes.

Making Your Own Intro Menu

Code Tutorial 8

You must be running the new Share the Pain version of POSTAL 2 for any of this to be relevant. Using these tutorials with older code may cause you problems.

These tutorials are to be completed in succession with one another. If you jump ahead to a later tutorial, I will be discussing things from earlier tutorials that you may not recognize. Following each one in order should greatly improve your learning experience.

I’m ramping up to making your very own add-on game. In order to get that working though, the player should have access to it from the game. Let’s take a little detour and look at menus for a bit to get that hooked up

This should be short. We’re going to look at making a new intro menu that let’s you play the original POSTAL 2 and then let’s you play your new game type. The hook for the main menu is in the Postal2.ini. After that we must carefully link our new menu into each class. It’s tedious, but it gets the job done.

Making the New Root

First copy Shell\Classes\ShellRootWindow.uc over to SuperPack\Classes. Rename it to something like SuperRootWindow.uc.

Extend the class from ShellRootWindow as so:

///////////////////////////////////////////////////////////////////////////////
// SuperRootWindow.uc
//
///////////////////////////////////////////////////////////////////////////////
class SuperRootWindow extends ShellRootWindow;

The following is messy but necessary. I find in all these cases it’s good to have the parent class open and the current class I’m working on open, so I can copy things over. You need to cut everything out of your SuperRootWindow class, except the following:

function MenuMode()
function ShowMenu()
state MainMenuShowing extends MenuShowing

That is, keep those three things. In all three cases make sure to include the bodies of the functions and the body of the state, complete with the BeginState in it. So you should have around 70 or more lines of code (depending on carriage returns, etc.). So it should be decent amount of code, not just 20 or 30 lines. Make sense?

The reason for all this work is to tie our new state into the code. MainMenuShowing will be renamed to NewMainMenuShowing.
First though, let’s add it to the functions. If you do a search on MainMenuShowing you’ll see it only shows up in those spots. So in the body of MenuMode change this

GotoState('MainMenuShowing');

to this:

GotoState('NewMainMenuShowing');

Do the same in the body of ShowMenu. Simply change the state from this:

GotoState('MainMenuShowing');

to this:

GotoState('NewMainMenuShowing');

Good. The last part concerns the state we’ve been changing everything. In the state definition, rename it also as follows:

state MainMenuShowing extends MenuShowing

to

state NewMainMenuShowing extends MenuShowing

The only other work we need to do is change the menu created by this state in the BeginState function. The class MenuMain will be changed to our new main menu. We need to change the code here that references that. Let’s change this line from

CreateMenu(class’MenuMain’);

To

CreateMenu(class’NewMainMenu’);

No, we haven’t made that class yet, but we’ll do that soon.

Now we’re finished with SuperRootWindow.uc!

Save that file.

Making the New Intro Menu

Let’s make that new class. Copy Shell\Classes\MenuMain.uc over to SuperPack\Classes. Rename it to NewMainMenu.uc, the same name as the class we used above in the CreateMenu function.

Extending this class is sort of bulky in the same way as SuperRootWindow. You have to pay close attention as we’ll be copying more code over and reaching back with the Super function again.

Make NewMainMenu extend MenuMain in the header. Delete all your variables and all functions except CreateMenuContents and Notify.

You should be left this:

///////////////////////////////////////////////////////////////////////////////
// NewMainMenu.uc
//
///////////////////////////////////////////////////////////////////////////////
// This class describes the main menu details and processes main menu events.
///////////////////////////////////////////////////////////////////////////////
class NewMainMenu extends MenuMain;
 
 
///////////////////////////////////////////////////////////////////////////////
// Vars, structs, consts, enums...
///////////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////////
// Create menu contents
///////////////////////////////////////////////////////////////////////////////
function CreateMenuContents()
                {
                Super.CreateMenuContents();
                AddTitleBitmap(TitleTexture);
                NewChoice     = AddChoice(NewGameText,                           "",                                                                                                                                            ItemFont, ItemAlign);
 
                if(GetGameSingle() != None && GetGameSingle().VerifySeqTime(true))
                                ShellRootWindow(Root).bVerified = true;
                // Only add this option in after you've beaten the game
                if(ShellRootWindow(Root).bVerified)
                                EnhancedChoice= AddChoice(EnhancedText,                       "",                                                                                                                                            ItemFont, ItemAlign);
                LoadChoice    = AddChoice(LoadGameText,                          OptionUnavailableInDemoHelpText,       ItemFont, ItemAlign);
                MultiChoice   = AddChoice(MultiText,                     "",                                                                                                                                            ItemFont, ItemAlign);                 Opti                             "",                                                                                                                                            ItemFont, ItemAlign);
                ExitChoice    = AddChoice(ExitGameText,                              "",                                                                                                                                            ItemFont, ItemAlign);
 
                // 01/23/03 JMI Don't allow access to load or save in demos.
                //                                                             NOTE: This only works for ShellMenuChoices--not actual controls.
                LoadChoice.bActive = !GetLevel().IsDemoBuild();
 
                // Reset this value. When MenuDifficultyPatch starts up, it's the only one that needs it
                // and it will set it when necessary.
                ShellRootWindow(Root).bFixSave=false;
                }
 
///////////////////////////////////////////////////////////////////////////////
// Handle notifications from controls
///////////////////////////////////////////////////////////////////////////////
function Notify(UWindowDialogControl C, byte E)
                {
                local String NewGameURL;
 
                Super.Notify(C, E);
                switch(E)
                                {
                                case DE_Click:
                                                if (C != None)
                                                                switch (C)
                                                                                {
                                                                                case NewChoice:
                                                                                                // Start new game
                                                                                                SetSingleplayer();
                                                                                                ShellRootWindow(Root).bVerifiedPicked=false;
                                                                                                if(!GetLevel().IsDemoBuild())
                                                                                                                // If not in the demo--allow them to pick the difficulty
                                                                                                                GotoMenu(class'MenuStart');
                                                                                                else
                                                                                                                // The difficulty is set to default for the demo.
                                                                                                                // Go to the explaination instead.
                                                                                                                GotoMenu(class'MenuImageDemoExplain');
                                                                                                break;
                                                                break;
 
                                                                                case EnhancedChoice:
                                                                                                SetSingleplayer();
                                                                                                ShellRootWindow(Root).bVerifiedPicked=true;
                                                                                                GotoMenu(class'MenuEnhanced');
                                                                                                break;
 
                                                                                case LoadChoice:
                                                                                                SetSingleplayer();
                                                                                                GotoMenu(class'MenuLoad');
                                                                                                break;
 
                                                                                case MultiChoice:
                                                                                                GotoMenu(class'MenuMulti');
                                                                                                break;
 
                                                                                case OptionsChoice:
                                                                                                GoToMenu(class'MenuOptions');
                                                                                                break;
 
                                                                                case ExitChoice:
                                                                                                GoToMenu(class'MenuQuitExitConfirmation');        // 01/21/03 JMI Now looks for confirmation.
                                                                                                break;
                                                                                }
                                }
                }
///////////////////////////////////////////////////////////////////////////////
// Default properties
///////////////////////////////////////////////////////////////////////////////
defaultproperties
                {
                }

The function CreateMenuContents adds all the listings in the main menu. The text is specified in the defaultproperties when you set the various variables. We need to add a new one for our new game type. Add these as variables at the top of the class under the class extension:

class NewMainMenu extends MenuMain;

///////////////////////////////////////////////////////////////////////////////
// Vars, structs, consts, enums...
///////////////////////////////////////////////////////////////////////////////
var ShellMenuChoice     PackChoice;
var localized string       PackText;

Those will be the new menu option. Now we have to add it to the functions we’ve kept.

First, in CreateMenuContents make sure to change the Super to extend the grandparent class again (this time it’s BaseMenuBig). So change this line:

Super().CreateMenuContents();

to

Super(BaseMenuBig).CreateMenuContents();

Next, add your new PackChoice just under the regular NewGame, and before the Enhanced game option:

NewChoice= AddChoice(NewGameText,"",ItemFont, ItemAlign);

PackChoice = AddChoice(PackText, "", ItemFont, ItemAlign);

if(GetGameSingle() != None && GetGameSingle().VerifySeqTime(true))

PackChoice can be just like NewChoice except you need to replace the choice variable and the text variable.

Now we move on to the Notify. That function handles what do when the player selects the option. We’re going to remove most of Notify that we kept. Cut out everything except the NewChoice code. We’ll mimic that. So you should have this after the cutting part:

///////////////////////////////////////////////////////////////////////////////
// Handle notifications from controls
///////////////////////////////////////////////////////////////////////////////
function Notify(UWindowDialogControl C, byte E)
                {
                local String NewGameURL;
 
                Super.Notify(C, E);
                switch(E)
                                {
                                case DE_Click:
                                                if (C != None)
                                                                switch (C)
                                                                                {
                                                                                case NewChoice:
                                                                                                // Start new game
                                                                                                SetSingleplayer();
                                                                                                ShellRootWindow(Root).bVerifiedPicked=false;
                                                                                                if(!GetLevel().IsDemoBuild())
                                                                                                                // If not in the demo--allow them to pick the difficulty
                                                                                                                GotoMenu(class'MenuStart');
                                                                                                else
                                                                                                                // The difficulty is set to default for the demo.
                                                                                                                // Go to the explaination instead.
                                                                                                                GotoMenu(class'MenuImageDemoExplain');
                                                                                                break;
}
                                                                break;
                                }
                }
Next, remove the demo code from of  NewChoice case, except for the GotoMenu(class'MenuStart'); line. So the case should now just be this:
                                                case NewChoice:
                                                                // Start new game
                                                                SetSingleplayer();
                                                                ShellRootWindow(Root).bVerifiedPicked=false;
                                                                GotoMenu(class'MenuStart');
break;

The next two things we need to do is change the NewChoice to PackChoice

And MenuStart to a new class still to be made called NewMenuStart.

We should end up with this:

                                                case PackChoice:
                                                                // Start new game
                                                                SetSingleplayer();
                                                                ShellRootWindow(Root).bVerifiedPicked=false;
                                                                GotoMenu(class'NewMenuStart');
break;

The last part is to fill out the text for the menu option in the default properties. Add something like this:

///////////////////////////////////////////////////////////////////////////////
// Default properties
///////////////////////////////////////////////////////////////////////////////
defaultproperties
          {
          PackText = “Super Pack”
          }

So, the part in italics, in the double quotes will be the text that shows up on the menu. You can call it whatever you want, though it might look bad if it’s too wide.

Save NewMainMenu.uc

Starting a Game

We need to create the NewMenuStart class we just spoke about. Again, copy Shell\Classes\MenuStart.uc into SuperPack\Classes and rename it NewMenuStart.uc.

Change the class defintion to this:

///////////////////////////////////////////////////////////////////////////////
// NewMenuStart.uc
//
///////////////////////////////////////////////////////////////////////////////
class NewMenuStart extends MenuStart;

Remove everything except for the Notify function. We need to keep everything in it, but will be modifying StartChoice.

We need to make that start our new game type properly. We don’t care about TheyHateMe and looking at the key setup, really because the people playing our mod have probably already understand the layout. Reduce StartChoice down to the following as shown:

///////////////////////////////////////////////////////////////////////////////
// Handle notifications from controls
///////////////////////////////////////////////////////////////////////////////
function Notify(UWindowDialogControl C, byte E)
                {
                local int val;
 
                Super(ShellMenuCW).Notify(C, E);
                switch(E)
                                {
                                case DE_Change:
                                                switch (C)
                                                                {
                                                                case DifficultyCombo:
                                                                                DiffChanged(bUpdate);
                                                                                break;
                                                                }
                                                break;
                                case DE_Click:
                                                switch (C)
                                                                {
                                                                case BackChoice:
                                                                                GoBack();
                                                                                break;
                                                                case StartChoice:
GetGameSingle().StartGame(true);
ShellRootWindow(Root).bLaunchedMultiplayer = false;
                                                                                break;
                                                                }
                                                break;
                                }
}

The only code removed was inside StartChoice. We kept two lines inside there.

Right now the game would start but it would be with the original POSTAL 2 game type. We’re going to rewire the way this starts, and start it with our own game type in our test level. It sounds ugly, but we actually need to specify a level to start the game. Even the official POSTAL 2 single player starts with a level specified inside script (Intro.fuk).

First thought, let’s change the super to reach back to the grandparent class. Change this line:

Super.Notify(C, E);

to

Super(ShellMenuCW).Notify(C, E);

That gets us ready to talk more about changing levels and game types. If you recall we made the game type MegaGameSP. If we then say that the starting level for our new game (or mod) will be our test level ‘testbox.fuk’ then we’re ready to go.

We need to add in a variable in the class that will specify the level and the game type we want to play. Add this at the top like so:

///////////////////////////////////////////////////////////////////////////////
// NewMenuStart.uc
//
///////////////////////////////////////////////////////////////////////////////
class NewMenuStart extends MenuStart;

var string StartGameURL;In Unreal games, the URL specifies many things, including the level to change to and the game type to use. In the default properties add this:
///////////////////////////////////////////////////////////////////////////////
// Default properties
///////////////////////////////////////////////////////////////////////////////
defaultproperties
          {
          StartGameURL=”testbox.fuk?Game=SuperPack.MegaGameSP"
          }

The first part is the level name, then the specifier “?Game=” then the game type, complete with package name.

Now we just need our menu to use this. Right now it calls the game info to start the game. The problem is, the original POSTAL 2 game info was written to specify it’s own starting URL. Because the main menu is actually a level complete with a game info, we can’t use it’s StartGame function. If we were to simply leave that, then the game would start but it would be the original POSTAL 2, not our mod. To change this, we must write our own StartGame function. This will is rather complicated, but hopefully the comments will help. Add this function to your NewMenuStart class:

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
function StartGame()
{
          local P2Player p2p;
          local P2GameInfoSingle usegame;
 
          usegame = GetGameSingle();
          p2p = usegame.GetPlayer();
          // Have menu prep for new game
          P2RootWindow(Root).StartingGame();
 
          // Stop any active SceneManager so player will have a pawn
          usegame.StopSceneManagers();
 
          usegame.PrepIniStartVals();
          usegame.TheGameState.bEGameStart = false;
 
          // Get the difficulty ready for this game state.
          usegame.SetupDifficultyOnce();
 
          // Get rid of any things in his inventory before a new game starts
          P2Pawn(p2p.pawn).DestroyAllInventory();
 
          // Game doesn't actually start until player is sent to first day,
          // which *should* happen at the end of the intro sequence.
          usegame.TheGameState.bChangeDayPostTravel = true;
          usegame.TheGameState.NextDay = 0;
          // Actually send the player to the new level with the new game type
          usegame.SendPlayerTo(p2p, StartGameURL);
}

Save the file.

The real work is done in the very last line. That does the actual level transition. All the code before that is still necessary though to properly initialize everything needed for a new game.

Linking the Menu

The last thing to be done is to make sure the game knows about our new menu. How this time? Through Postal2.ini.

Open Postal2.ini. Search for BaseMenu.

Change this line:

BaseMenu=Shell.ShellRootWindow

to this:

BaseMenu=SuperPack.SuperRootWindow

That’s it! The main menu should now be your very own menu we’ve been coding.

Save Postal2.ini.

Wrapping Up

The last important part concerns your Postal2.ini file again. Back when you made MegaGameSP, we had to get the game to recognize your new game type, right? Well, we now are hooking the game in through the menus(as we setup above), so we need to return the *.ini to the way it was.

Open Postal2.ini.

Search on ‘DefaultGame’.

Change this line:

DefaultGame=Superpack.MegaGameSP

To

DefaultGame=GameTypes.GameSinglePlayer

Save the file.

Everything should still run fine, now you just have one less thing that’s been modified.

We’re ready to see the results.

Build SuperPack.u and run the game (SuperRootWindow initializes in the background on startup).

On the main menu (NewMainMenu), you should now see an option for ‘Super Pack’! Click on it! It should then take you to the difficulty selection screen (NewMenuStart). Find a difficulty and hit Start!

After the load you should find yourself in your test level with all your cheats you’ve added before, like the Party Bomb! Cool!

Our new game starts in testbox.fuk. This probably isn’t the name of the first level you’d like. All you need to do is change the name inside the variable in NewMenuStart to a different level. You could make a new level, or even pick one from the original POSTAL 2.

The neat thing about all this is that you no longer have to start the level from the custom screen, or through a command line in a dos window. You can now access it from the main menu in the game! Sure, you may continue to run it through a dos window for testing but this step is important for other people to play your game.

We’re now ready to start looking adding new errands and days.

Nathan Fouts


Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: