PC Games

Orb
Lasagne Monsters
Three Guys Apocalypse
Water Closet
Blob Wars : Attrition
The Legend of Edgar
TBFTSS: The Pandoran War
Three Guys
Blob Wars : Blob and Conquer
Blob Wars : Metal Blob Solid
Project: Starfighter
TANX Squadron

Tutorials

2D shoot 'em up
2D top-down shooter
2D platform game
Sprite atlas tutorial
Working with TTF fonts
2D adventure game
Widget tutorial
2D shoot 'em up sequel
2D run and gun
Roguelike
Medals (Achievements)
2D turn-based strategy game
2D isometric game
2D map editor
2D mission-based shoot 'em up
2D Santa game
2D split screen game
2D quest game
SDL 1 tutorials (outdated)

Latest Updates

SDL2 Quest game tutorial
Wed, 7th May 2025

SDL2 Versus game tutorial
Wed, 20th March 2024

Download keys for SDL2 tutorials on itch.io
Sat, 16th March 2024

The Legend of Edgar 1.37
Mon, 1st January 2024

SDL2 Santa game tutorial 🎅
Thu, 23rd November 2023

All Updates »

Tags

android (3)
battle-for-the-solar-system (10)
blob-wars (10)
brexit (1)
code (6)
edgar (9)
games (44)
lasagne-monsters (1)
making-of (5)
match3 (1)
numberblocksonline (1)
orb (2)
site (1)
tanx (4)
three-guys (3)
three-guys-apocalypse (3)
tutorials (18)
water-closet (4)

Books


A North-South Divide

For over a hundred years, messenger Duncan has wandered the world, searching for the missing pieces of an amulet that will rid him of his curse; a curse that has burdened him with an extreme intolerance of the cold, an unnaturally long life, and the despair of watching all he knew and loved become lost to the ravages of time. But now, Duncan is close to the end of his long quest.

Click here to learn more and read an extract!

« Back to tutorial listing

— Simple 2D quest game —
Part 16: Dispose quest

Note: this tutorial assumes knowledge of C, as well as prior tutorials.

Introduction

The final quest type that we're going to implement is to dispose of an item. As mentioned in the previous part, this is like a fetch quest, but in reverse! This quest is also easy to create, since we already have most of the essential code pieces needed to support it.

Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./quest16 to run the code. As before, you can move around with the WASD control scheme. Play the game as normal. Every now again, a Resident will request that you bury an item on an island somewhere. Head to the island, and search around until you find an X on the ground. Walk into it to dispose of the item (it will be removed from your inventory). With that done, return to the Resident to complete the quest (the quest log can help you locate the town that you need to return to). When you're finished, close the window to exit.

Inspecting the code

As already stated, this will be a very simple quest to put together, as we already have most of everything we need. We'll be adding one new entity, but otherwise the bulk of the work will be done in quests.c.

So, starting with generateQuest:


void generateQuest(Entity *requester)
{
	// snipped

	addQuestStep(q, formattedString("INTERACT %d", q->requester->id));
	addQuestStep(q, "START_DISPOSE_QUEST");

	// snipped
}

As we're only supporting dispose quests in this part, we've set the first command as START_DISPOSE_QUEST.

We then update executeQuestSteps, to support this new command:


static void executeQuestSteps(Quest *q)
{
	int  done;
	char command[MAX_NAME_LENGTH];

	done = 0;

	do
	{
		q->currentStep = q->currentStep->next;

		if (q->currentStep != NULL)
		{
			// snipped

			else if (strcmp(command, "START_DISPOSE_QUEST") == 0)
			{
				doStartDisposeQuest(q);
			}

			// snipped

			else
			{
				SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_CRITICAL, "Unrecognised command '%s'", command);
				exit(1);
			}
		}
		else
		{
			done = 1;
		}
	} while (!done);
}

When we encounter the START_DISPOSE_QUEST command, we're calling doStartDisposeQuest:


static void doStartDisposeQuest(Quest *q)
{
	Entity *item;
	Entity *location;
	Map    *oldMap;
	Island *is;
	int     x, y, ok, n;

	item = initEntity("Item");
	((Item *)item->data)->quest = q;

	item->touch(item, game.map->player);

	oldMap = game.map;

	game.map = &game.overworld.map;

	location = initEntity("Location");

	do
	{
		x = rand() % game.map->width;
		y = rand() % game.map->height;

		n = game.map->data[x][y] / TILE_TYPE_RANGE;

		ok = n > MT_SHALLOWS && n < MT_FOREST && getEntityAt(x, y) == NULL;
	} while (!ok);

	location->x = x;
	location->y = y;

	is = getIslandAt(x, y);

	game.map = oldMap;

	sprintf(q->title, "Dispose of %s", item->name);
	sprintf(q->description, "Bury on island of %s", is->name);
	q->island = is;

	addQuestStep(q, formattedString("INTERACT %d", location->id));
	addQuestStep(q, formattedString("REMOVE_INVENTORY_ITEM %d", item->id));
	addQuestStep(q, formattedString("UPDATE_DESCRIPTION Return to %s", q->requester->name));
	addQuestStep(q, formattedString("PULSE_ENTITY %d 1", q->requester->id));
	addQuestStep(q, formattedString("SET_TOWN %d", ((Resident *)q->requester->data)->town->id));
	addQuestStep(q, formattedString("INTERACT %d", q->requester->id));
	addQuestStep(q, formattedString("PULSE_ENTITY %d 0", q->requester->id));
	addQuestStep(q, formattedString("MSG_BOX %s;You've done it? Great! I hope no one saw you.", q->requester->name));
	addQuestStep(q, formattedString("MSG_BOX %s;You ... um ... might want to make yourself scarce for a few days.", q->requester->name));
	addQuestStep(q, "COMPLETE_QUEST");

	doActivateQuest(q);

	addMessageBox(q->requester->name, "Oh, hello there! If you've got a moment, I have a favour to ask.");
	addMessageBox(q->requester->name, expiringFormattedString("This %s needs disposing of. The island of %s is probably the place to get rid of it. I'd do it myself, but I've got a sore back.", item->name, q->island->name));
	addMessageBox(q->requester->name, "X will mark the spot. Make sure you bury it nice and deep.");
}

You're right in thinking that this looks kind of like a mix of the delivery and fetch quests. To begin with, we're creating an Item, and placing it in the player's inventory (by invoking its `touch`). Next, we're creating a new entity called Location (we'll get to this at the end). This entity is created in the Overworld (hence the Map context switching), and randomly placed on land. With the Location placed (at `x` and `y`), we call getIslandAt to find out which island is the target of our quest, and set quest's `island` to it. Again, this is why we want to ensure we have a valid island in getIslandAt. With all that done, we set up the quest steps we want to follow, and add some message boxes for the requester. Note how the first step is an INTERACT command, using the id of the Location we created. This will mean our quest progression will be paused until we touch the Location.

So, in summary, we're giving the player an Item, creating a Location in the overworld, and integrating it into our quest steps.

An example of a fully complete dispose script from this part might look like this:

INTERACT 99
START_DISPOSE_QUEST
INTERACT 176
REMOVE_INVENTORY_ITEM 175
UPDATE_DESCRIPTION Return to Terry Silver
PULSE_ENTITY 99 1
SET_TOWN 5
INTERACT 99
PULSE_ENTITY 99 0
MSG_BOX Terry Silver;You've done it? Great! I hope no one saw you.
MSG_BOX Terry Silver;You ... um ... might want to make yourself scarce for a few days.
COMPLETE_QUEST

Translated, line by line, this script would mean:

- Wait for the player to interact with entity #99.
- Setup the dispose quest.
- Wait for the player to interact with entity #176.
- Remove item #175 from the player's inventory
- Update the quest description, with the text "Return to Terry Silver".
- Make entity #99 pulse
- Set the Quest's town to the one with id #5
- Wait for the player to interact with entity #99
- Make entity #99 stop pulsing
- Add a message box, for Terry Silver, saying "You've done it? Great! I hope no one saw you."
- Add a message box, for Terry Silver, saying "You ... um ... might want to make yourself scarce for a few days."
- Complete the quest

That should all make complete sense, as it is what we'd expect of having to get rid of something for someone.

That's all there is to quests.c! That couldn't have gotten easier.

We now need to just look at the new Location entity we've created. This is handled in location.c, where you'll find just two functions: initLocation and `touch`. We'll start with initLocation:


void initLocation(Entity *e)
{
	e->type = ET_LOCATION;
	e->solid = 0;
	e->texture = getAtlasImage("gfx/entities/location.png", 1);

	e->touch = touch;
}

Absolutely nothing special here - we're creating a non-solid entity, with a texture, and a `touch` function. The `touch` function is the most important part:


static void touch(Entity *self, Entity *other)
{
	if (other == game.map->player && doQuestInteraction(self))
	{
		self->dead = 1;
	}
}

When the player touches the location, we'll call doQuestInteraction, to trigger any quest steps that are waiting on an interaction with this location. If so, we'll mark the Location as `dead`, to remove it from our game. If we wanted a little more control over this sort of thing, we could add a quest step to remove the entity, so it doesn't always happen. In our game, however, we don't want our Locations hanging around once the item has been buried, since it has served its purpose.

And that's it, that's all we had to do to create a dispose quest. As you can see, creating quests in our game is simple once we have everything set up (and creating such a quest system itself is actually not too tricky, once you reflect on it).

Our game is now more or less done. What we shall do in the final part is add in some sound and music, ensure our quests are randomly picked, and tidy a few things up.

Purchase

The source code for all parts of this tutorial (including assets) is available for purchase, as part of the SDL2 tutorials bundle:

From itch.io

Mobile site