Objects

Everything is an object. Let’s start with that. Also, this is an application with a GUI, so there’s no need to do lots of crazy infinite loops to wrap everything.

Using Objects means that you can put the right function in the right place, lets say you want to have an Item respond to the drink command. You can create a subclass (subclass?!?) of the Item class and then add the didRespondToCommand method that will respond to the command drink.

Every object that you place in game has the ability to respond to commands, that means you could add slay to the Dragon class, or sleep in the Bedroom class. You just have to think about the best place to put the command.

Rooms

A lot of the time you won’t need to subclass a Room object, you can create a room easily, it’s normal to only create a subclass when you want to allow / disallow, add respond to examine to the room or add your own commands inside the room.

Rooms have a name and description that is called when you enter that room, and they also have an id so you can use getRoomByID on the WQ class.

Room* finale = [WQ getRoomByID:@"finale"];

You can add multiple objects that can be examined in a room very easily by making an nsdictionary of what you want to say vs what you want people to type. This is great for a quick examine item command that prints out some text. But if you want to do things like add Items to the players inventory or the room, you’ll have to add them as custom commands.

It’s normal to have rooms connected that you still may not have permission to access yet, say for example if you need a key or some other items to enter the room, you can use the method playerShouldEnterRoom and allow players to not enter if they do not have the required items.

To find out the current room (for example in an Item’s use method), you can use [Room current] to get the room the player is in right now.

People

People are objects that respond to say, and are added to a room using room.person they are a simplified version of the Creature class. You can make people respond to when you have entered a room that a player has gone into. This is useful for starting up a conversation, let the Person talk first.

Items

Items are objects that can be picked up and dropped, have their own commands, can be used and they show up in your inventory.

The API for whether an item is on the player is all in the Player class, with the functions hasItemByID, getItemByID and removeItemByID, you can see them in use below in the snippet below

if( [Player hasItemByID:@"bubba"] ){
  Item * bubba = [Player getItemByID:@"bubba"];
  [Player removeItemByID:@"bubba"];
  ...
}

In this case we’re asking if the player has picked up an Item with an id called bubba, if they have then get a copy of it ( for later use ) and remove the one from the inventory.

As Items can have their own commands it makes sense to put commands in them, this also means they can keep their own variables, like this drink command which keeps drinking till drinksLeft is 0.

@interface Bubba : Item {
  int drinksLeft;
}

-(void) drink;
@end

And in the implementation file

-(void) onUse{
  [self drink];
}

-(void) drink{
  if (drinksLeft > 0) {
    drinksLeft = drinksLeft - 1;
    [WQ print:@"You take a swig from Bubba, it's pretty refreshing"];
  }else{
    [WQ print:@"Oh.. Looks like Bubba is empty."]; 
  }
}

Common Item use cases

Here are some example onUse functions to help you get an idea of how to make Items do what you want.

Unlocking a door

-(void) onUse{
  // Make sure we are in the right room.
  if([Player isIn:@"cabin"]){
    
    [Room connectRoomID:@"cabin" connectWestToRoomID:@"carriage"];
    [Room current].description = @"To the west is the carriage.";
    [WQ print:@"You put the key in and turn, unlocking the door."];
  }
}

Teleporting a Player

-(void) onUse{
    [WQ print:@"You use the turnkey, knowing full well what awaits you on the other side."];
    [Player teleportToRoomWithID:@"voldemortscastle"];
}

Mixing two Items to make one

-(void) onUse {
  if( [Inventory hasItem:@"candle"] ){
    [WQ print:@"You set fire to the candle, turning it into a torch."];
    
    Torch * torch = [Torch alloc] init];
    [Inventory addItem: torch];
    
    [Inventory removeItemByID:@"candle"];
    [Inventory removeItemByID:@"lighter"];
  }else{
    [WQ print:@"You don't know what to use the Lighter with."];
  }
}

Giving Items

To do this we have to use didRespondToCommand, in this example I get the first word from the command array and check if that is give. Then I narrate, and remove the Item from their inventory.

-(BOOL)didRespondToCommand:(NSArray*)commandArray {
  NSString * command = [commandArray first];
  if ([@"give" isEqualToString:command]) {
    
    [WQ print:@"You offer the rose to the beautiful gaelic princess."];
    [WQ say:@"Princess" words:@"Aww thank you!"];
    [WQ print:@"She seems happy."];
    [Inventory removeItem:self];
    return YES;
  }
  return NO;
}
Creatures

Creatures are things you want to fight. The are a subclass of People but have additional functions like responding to commands like fight.

To use them you need to have your player have some health, and a damageRange. Usually this is added in the ready function.

Player * player = [Player sharedPlayer];
player.damageRange = NSMakeRange(1, 3);
player.maxHealth = 10;
player.health = 10;

A fightable character is an instance or subclass of a Creature, they have name, damageRange, and Max Health variables that are self explanatory.

These go inside the creature subclass .m file:

- (NSString *) name {
  return @"Massive Scarey thing";
}

-(int) maxHealth {
  return 10;
}

-(NSRange)damageRange {
  return NSMakeRange(2, 4);
}

What is less obvious is that you can do lots with what the creature says and how you attack it. There is a handy method called damageModifier which allows you to modify the damage after it’s been rolled. For example, in this case the damage is doubled if the user has picked up a keytar.

-(int)damageTakenModifier:(int)originalDamage {
  // if the player has a keytar, then double the damage
  if([Player has:@"keytar"]){
    return originalDamage * 2;
  }
  return originalDamage;
}  

The way that the battle is described is quite interesting too, you supply a collection of strings in the form of an array that lets you describe each attack given and received, this means you don’t have to have generic “you hit the hippogruff for 5 damage” messages, you can make your own up. Like below.

-(NSArray*)creatureAttackPhrases{
  return [NSArray arrayWithObjects:
          @"The giant looks at you grimly and causes %i ego damage.",
          @"The giant shakes his head and bruising your ego by %i.",
          @"The giant puts his earphone in hurting your ego by %i.",
         nil];
}

-(NSArray*)playerAttackPhrases{
  return [NSArray arrayWithObjects:
          @"You strut your stuff dealing %i ego damage.",
          @"The giant didnt know what hit him when you did a cool dace doing a whole %i damage.",
          @"You showed the giant your new shoes, he's so impressed! %i damage.",
         nil];
}

There is a lot of methods you can override in order to get your creatures to be realistic, override as many as you want.

-(void)beforeTurn;
-(void)afterTurn;
-(void)beforeFight;
-(void)afterFightLost;
-(void)afterFightWon;

Once you’re happy with your class, you just need to add the creature to a room to make everything connect up.

Quickies

Where do I put my code?

If you download one of the example applications, you’ll see they have a folder for the WibbleQuest framework as well as a game folder. In the game folder there is a usually files called [something]Game.m and [something]Game.h this is your main Game class, and where you do a lot of setting up your game.

It will come with a method already called ready which is where you can safely set up your game. Once ready is finished then the game starts but saying your Game’s Title and Description and puts the player in the first room.

Write on the screen

Depending on what style of text you’ll want to use these 6 commands on WQ

+(void) print:(NSString*)string, ...;
+(void) say:(NSString*)name words:(NSString*)words;
+(void) heading:(NSString*)string;
+(void) command:(NSString*)string;
+(void) title:(NSString*)string;
+(void) art:(NSString *)string;

For example

[WQ say:@"orta" words:@"hello there"];
[WQ print:@"looks like you picked up %i shoes", shoesCount];
[WQ heading:@"and so the story ends"];

I want to have some ASCII art (Like a map or a ‘drawing’)

Awesome, you can do that by using the art function on WQ, this will give you monospaced text, you have up to 32 characters per line (on the iPhone) and the lines are separated by dollar signs ‘$’ the final gotcha is that all backslashes ‘\’ have to be two backslashes ‘\\’ as they are normally used to do things other than draw skeleton arms.

[wq art:@""
 "12345678901234567890123456789012$"
 "     ,--.            $"
 "     ([ oo]          $"
 "      `- ^\\         $"
 "     _ I`-'          $"
 "   ,o(`-V'           $"
 "   |( `-H-'          $"
 "   |(`--A-'          $"
 "   |(`-/_\'\\        $"
 "   O `'I ``\\\\      $"
 "   (\\  I    |\\,    $"
 "   \\\\-T-'`,        "];  
 

What is the id property on all these objects for?

You use the object.id whenever you are trying to find a specific object like

[Player has:@"itemID"];

or

[WQ findRoomByID:@"roomID"]

TLDR; always add one.

Make any object do a custom command

You want the Objject to override did respond to command. In this example it looks to see if the command is play and if it is, it says something. We the return YES, otherwise it will continue to look for other commands.

-(BOOL)didRespondToCommand:(NSArray*)commandArray {
  NSString * command = [commandArray first];
  if ([@"play" isEqualToString:command]) {
    [WQ print:@"You rock out on the Keytar for a while playing some George Michaels, no one seems to pay any attention though"];
    return YES;
  }
  return NO;
}

Find out what room I’m in

You want to get access to the shared WibbleQuest object, and then call currentRoom on it

[WibbleQuest sharedWibble].currentRoom

Create a room

Room * openingRoom = [[Room alloc] init];
openingRoom.id = @"start";
openingRoom.name = @"Backstage, at the venue.";
openingRoom.description = @"There are clothes all over the floor, and you can hear people practicing through the southern door.";
[wq addRoom:openingRoom];

Make a room do something when you enter it

Make a room subclass, in it you can the override which will let you act when they leave / join

-(void)playerDidEnterRoom{}
-(void)playerDidLeaveRoom{}

If you want to control whether they can leave or join a room, you can override

-(BOOL)playerShouldEnterRoom;
-(BOOL)playerShouldLeaveRoom;

which expect a bool as to whether you can move out or in. These do not say anything so that’s left to the room to deal with informing the player why.

Move to another room

You can use teleport to room with ID, make sure to have done [wq addRoom: room] and that the room has an id and everything should be fine

[Player teleportToRoomWithID:@"magicroom"];

Make it possible to examine things in the room

You need to subclass the Room object and add the method dictionaryForExamine. For example, this one says something if you write “examine clothes” in the room.

-(NSDictionary*)dictionaryForExamine {
return [NSDictionary dictionaryWithObjectsAndKeys:
@"The clothes are sweaty, you don't really want to touch them", @"clothes",
nil];
}

Make a room deny entry until you’ve got certain items

In your Room subclass you can ban someone to enter the room by returning NO. You also have a way of not letting someone leave the room, by using playerShouldLeaveRoom.

-(BOOL)playerShouldEnterRoom {
  if ([Player has:@"keytar"] && [Player has:@"microphone"]) {
    return YES;
  }
  
  [WQ print:@"You wouldn't want to go on stage without a Mic and your Keytar! Better go find them."];
  return NO;
}

Add a Person to talk to you in a room

Create the person’s class, as a subclass of Person
Make them respond to words that are said by using

-(void)respondToSentenceArray:(NSArray*)sentence {
  if([sentence contains:@"hi", @"hello", nil] ){
    [WQ say:@"Doc" words:@"Why hello there good sir, how are you on this fine day?"];
    return;
  }
}

You can use the helpful method array contains to allow people to dig deeply into the conversation tree, or to allow them to react to multiple words with the same speech.

make a person give you something

You can just add an item to the players inventory for example like below

if([sentence contains:@"mic", @"mics", @"microphone", nil] ){
  [WQ say:@"FloatstarPX" words:@"Ah yeah, the microphones, I spotted them earlier, here you go"];
  Microphone *mic = [[Microphone alloc] init];
  WibbleQuest * wibble = [WibbleQuest sharedWibble];
  [wibble.inventory addItem: mic];
  
  return;
}

Make an item that you can pick up in a room

Create an Item subclass, add it to a room’s inventory, it can have multiple items.

Room * bathroom = [[Room alloc] init];
bathroom.name = @"Bathroom"; 
Keytar *keytar = [[Keytar alloc] init];
[bathroom addItem:keytar];  

Make an item do something when picked up or dropped

override onPickup in your class

-(void)onPickup {
  [WQ print:@"Bubba sloshes as you pick up his weighty self"];
}

-(void)onDrop {
  [WQ print:@"You politely return Bubba to the floor"];
}

Allow the player to use an object

override the method and that will be called when the player does “use [item]”

-(void)onUse{ }

Make a person say Hello the first time you enter a room

In your Person subclass override respondToPlayerForTheFirstTime and this will only be called once

-(void)respondToPlayerForTheFirstTime {
  [WQ say:@"FloatstarPX" words:@"Oh hey orta."];
}

Add a Help command from an object in your inventory

You want to make it pretty obvious what is possible with the objects you have (right?!?) so it’s possible to dynamically add and remove text to the help system based on what Item’s are inside your Inventory. This is done by adding and removing commands when you pick up, or drop the object.

-(void)onPickup {
  [WQ print:@"Bubba sloshes around as you pick him up"];
  [self addCommandToHelp:@"use bubba" withDescription:@"Take a swig from your Bubba"];
}

-(void)onDrop {
  [WQ print:@"You drop bubba onto the floor and he rolls casually onto his side."];
  [self removeCommandFromHelp:@"use bubba"];
}