Tutorial: Adding a New Inventory Item to Spelunky

by Darius Kazemi on January 20, 2010

in modding,programming,Spelunky,tutorial

Here’s another Spelunky modding tutorial I posted over on the Mossmouth Forums. This shows how you might add a third inventory element to the game, something that behaves like a bomb or a rope in terms of always having a limited number of it in your inventory. This can even be modified slightly to provide you with something like an ammo count.

Overview

So let’s say you want to add a new inventory item to Spelunky — something like bombs and ropes, but different. For the sake of this tutorial, let’s have you be able to hold up to 99 rocks in your pockets. We’ll make it so that rocks behave kind of like bombs: if you run across one, you can pick it up and it is added to your inventory when you switch away from it.

What we’re going to do in this mod is: set the appropriate global variable, figure out how to switch to the new inventory item, and set up the new HUD element so we know how many rocks we have.

Setting the Global Variable

The amount of bombs and ropes you have are stored as global variables. The game sets them in the scrClearGlobals. This script is run at the beginning of every game, and it sets every global to false/0, except for bombs, ropes, and plife (player life), which all get set to 4. So in scrClearGlobals you want to make the following change at line 109:

else
{
    global.plife = 4;
    global.bombs = 4;
    global.rope = 4;
    // ADD THIS LINE (we're starting the player out with 1 rock)
    global.rocks = 1;
}

Switching to Our Rocks

Here’s where we change the code so that pressing the “switch item” button will put a rock in our hands if we have more than 0 rocks. We also want to put in some code so that when we have the rock equipped, our global.rocks is reduced by 1 (so it works the same way bombs and ropes do).

Let’s look at the code from oPlayer1 Step, in the second Action Block, line 952:

else if (inGame and kItemPressed and not whipping)
{
    // switch items
    if (holdItem)
    {

// If we're holding a bomb and it's armed, we can't switch away.

        if (holdItem.sprite_index == sBombArmed)
        {
            // do nothing
        }

// If we're holding a bomb and it's not armed, then put it back in your
// inventory (increment the global), and then destroy it so you're not
// holding it anymore. Then, if there's more than 0 ropes, switch to
// ropes. Otherwise, switch to what you're holding (or nothing if
// you're not holding anything.)

        else if (holdItem.sprite_index == sBomb)
        {
            with holdItem
            {
                global.bombs += 1;
                instance_destroy();
            }

            if (global.rope > 0)
            {
                holdItem = instance_create(x, y, oRopeThrow);
                holdItem.held = true;
                global.rope -= 1;
                whoaTimer = whoaTimerMax;
            }
            else
            {
                scrHoldItem(pickupItemType);
            }
        }

// If we're holding a rope then put it back in your inventory
// (increment the global), and then destroy it so you're not
// holding it anymore. Then switch to what you're holding
// (or nothing if you're not holding anything.)

        else if (holdItem.sprite_index == sRopeEnd)
        {
            with holdItem
            {
                global.rope += 1;
                instance_destroy();
            }            

            scrHoldItem(pickupItemType);
        }

// If we're holding an item that's not heavy (damsel or Idol)
// and we have a bomb or a rope, then store it away as
// pickupItemType for later. If we have bombs, switch
// to bombs, if not, then switch to ropes.

        else if (not holdItem.heavy and holdItem.cost == 0)
        {
            if (global.bombs > 0 or global.rope > 0)
            {
                pickupItemType = holdItem.type;
                if (holdItem.type == "Bow" and bowArmed)
                {
                    scrFireBow();
                }
                with holdItem
                {
                    breakPieces = false;
                    instance_destroy();
                }
            }

            if (global.bombs > 0)
            {
                holdItem = instance_create(x, y, oBomb);
                if (global.hasStickyBombs) holdItem.sticky = true;
                holdItem.held = true;
                global.bombs -= 1;
                whoaTimer = whoaTimerMax;
            }
            else if (global.rope > 0)
            {
                holdItem = instance_create(x, y, oRopeThrow);
                holdItem.held = true;
                global.rope -= 1;
                whoaTimer = whoaTimerMax;
            }
        }
    }
// If you're not holding anything, switch to bombs if available.
// Otherwise switch to ropes if available.
    else
    {
        if (global.bombs > 0)
        {
            holdItem = instance_create(x, y, oBomb);
            if (global.hasStickyBombs) holdItem.sticky = true;
            holdItem.held = true;
            global.bombs -= 1;
            whoaTimer = whoaTimerMax;
        }
        else if (global.rope > 0)
        {
            holdItem = instance_create(x, y, oRopeThrow);
            holdItem.held = true;
            global.rope -= 1;
            whoaTimer = whoaTimerMax;
        }
    }
}

It’s a little complex so you should read through my comments there. Basically, we take this structure and extend it so we have the logic for switching to rock. It’s a good exercise to read through this code and figure out why I made the changes I did:

else if (inGame and kItemPressed and not whipping)
{
    // switch items
    if (holdItem)
    {
        if (holdItem.sprite_index == sBombArmed)
        {
            // do nothing
        }
        else if (holdItem.sprite_index == sBomb)
        {
            with holdItem
            {
                global.bombs += 1;
                instance_destroy();
            }

            if (global.rope > 0)
            {
                holdItem = instance_create(x, y, oRopeThrow);
                holdItem.held = true;
                global.rope -= 1;
                whoaTimer = whoaTimerMax;
            }
            else
            {
                scrHoldItem(pickupItemType);
            }
        }
        else if (holdItem.sprite_index == sRopeEnd)
        {
            with holdItem
            {
                global.rope += 1;
                instance_destroy();
            }

            if (global.rocks > 0)
            {
                holdItem = instance_create(x, y, oRock);
                holdItem.held = true;
                global.rocks -= 1;
                whoaTimer = whoaTimerMax;
            }

            else
            {
                scrHoldItem(pickupItemType);
            }
        }
        else if (holdItem.sprite_index == sRock)
        {

//           global.plife = 0;

            with holdItem
            {
                global.rocks += 1;
                instance_destroy();
            }

            if (pickupItemType == "Rock") pickupItemType = "";
            scrHoldItem(pickupItemType);
        }

        else if (not holdItem.heavy and holdItem.cost == 0)
        {
            if (global.bombs > 0 or global.rope > 0 or global.rocks > 0)
            {
                pickupItemType = holdItem.type;
                if (holdItem.type == "Bow" and bowArmed)
                {
                    scrFireBow();
                }
                with holdItem
                {
                    breakPieces = false;
                    instance_destroy();
                }
            }

            if (global.bombs > 0)
            {
                holdItem = instance_create(x, y, oBomb);
                if (global.hasStickyBombs) holdItem.sticky = true;
                holdItem.held = true;
                global.bombs -= 1;
                whoaTimer = whoaTimerMax;
            }
            else if (global.rope > 0)
            {
                holdItem = instance_create(x, y, oRopeThrow);
                holdItem.held = true;
                global.rope -= 1;
                whoaTimer = whoaTimerMax;
            }
            else if (global.rocks > 0)
            {
                holdItem = instance_create(x, y, oRock);
                holdItem.held = true;
                global.rocks -= 1;
                whoaTimer = whoaTimerMax;
            }
        }
    }
    else
    {
        if (global.bombs > 0)
        {
            holdItem = instance_create(x, y, oBomb);
            if (global.hasStickyBombs) holdItem.sticky = true;
            holdItem.held = true;
            global.bombs -= 1;
            whoaTimer = whoaTimerMax;
        }
        else if (global.rope > 0)
        {
            holdItem = instance_create(x, y, oRopeThrow);
            holdItem.held = true;
            global.rope -= 1;
            whoaTimer = whoaTimerMax;
        }
        else if (global.rocks > 0)
        {
            holdItem = instance_create(x, y, oRock);
            holdItem.held = true;
            global.rocks -= 1;
            whoaTimer = whoaTimerMax;
        }
    }
}

Adding Rocks to the UI

Next we want to add a new UI element to the top of the screen that shows how many rocks we have. To do this, open up scrDrawHUD and you’ll see this:

if (global.drawHUD and instance_exists(oPlayer1))
{
    lifeX = 8;
    bombX = 64;
    ropeX = 120;
    moneyX = 176;
    draw_set_font(global.myFont);
    draw_set_color(c_white);
    draw_sprite(sHeart, -1, lifeX, 8);
    life = global.plife;
    if (life < 0) life = 0;
    draw_text(lifeX+16, 8, life);
    if (global.hasStickyBombs) draw_sprite(sStickyBombIcon, -1, bombX, 8);
    else draw_sprite(sBombIcon, -1, bombX, 8);
    draw_text(bombX+16, 8, global.bombs);
    draw_sprite(sRopeIcon, -1, ropeX, 8);
    draw_text(ropeX+16, 8, global.rope);
    draw_sprite(sDollarSign, -1, moneyX, 8);
    draw_text(moneyX+16, 8, global.money);

What you’ll want to do is create rockX, which is the X offset of the new rock UI element. I would put it in between rope and money, because money is variable length so you want it at the far right hand side. Then you just add the draw_text() and draw_sprite() functions. I used the sprite for the rock in the example below:

if (global.drawHUD and instance_exists(oPlayer1))
{
    lifeX = 8;
    bombX = 64;
    ropeX = 120;
    rockX = 176;
    moneyX = 232;
    draw_set_font(global.myFont);
    draw_set_color(c_white);
    draw_sprite(sHeart, -1, lifeX, 8);
    life = global.plife;
    if (life < 0) life = 0;
    draw_text(lifeX+16, 8, life);
    if (global.hasStickyBombs) draw_sprite(sStickyBombIcon, -1, bombX, 8);
    else draw_sprite(sBombIcon, -1, bombX, 8);
    draw_text(bombX+16, 8, global.bombs);
    draw_sprite(sRopeIcon, -1, ropeX, 8);
    draw_text(ropeX+16, 8, global.rope);
    // I messed around with the Y value until I  got something I liked
    draw_sprite(sRock, -1, rockX, 16);
    draw_text(rockX+16, 8, global.rocks);
    draw_sprite(sDollarSign, -1, moneyX, 8);
    draw_text(moneyX+16, 8, global.money);

And we end up with this:

s7.PNG

Comments on this entry are closed.

Previous post:

Next post: