! -------------------------------------------------------------------------- ! This game illustrates some of the features that mca.h supports. Like all ! good adventure games, it has both a hunger daemon and a maze, but it's not ! really meant to be playable. ! ! We start with some of the basic stuff. ! -------------------------------------------------------------------------- Constant Story "Another day"; Constant Headline "A simple sample game for mca.h"; Release 1; Serial "050711"; Constant UnknownCommandMsg = "You can choose one of the options or type one of the following commands: save, restore, restart, quit, inventory and look."; Constant InventorySize = 2; Include "mca"; ! -------------------------------------------------------------------------- ! The following two constants represent objects that the player can Acuire() ! during the game. The actual values are not important. ! -------------------------------------------------------------------------- Constant food = 1; Constant diamond = 2; Global money = 50; Global account = 0; Global hunger = 0; Global quote = 0; Attribute safe; ! -------------------------------------------------------------------------- ! This class definition illustrates how Node subclasses can be created to ! allow certain options (in this case, eating food when hungry, and ! following a thief when robbed) in many nodes. We set initmode to 1 since ! external events can cause options to change at any time. We also enlarge ! the opts array in order to get some higher option numbers. ! -------------------------------------------------------------------------- Class TestNode class Node, with opts 0 0, initmode 1, SayOpt 20 "Eat some food" 21 "Follow the wicked thief", ActOpt [ opt; switch (opt) { 20: Acquire(-food); hunger = 0; "Ah, now you aren't hungry anymore!"; 21: MoveTo(DarkAlley); } ], InitOpts [; if (Acquired(food)) OptOn(20); if (Thief has general) { OptOn(21); give Thief ~general; } ]; ! -------------------------------------------------------------------------- ! The town square represents the most basic way of setting up a node. Both ! SayOpt and ActOpt are arrays, and numopts is used instead of InitOpts. ! -------------------------------------------------------------------------- TestNode TownSquare with title "At the town square", text "This is the town square. It's loaded with people and there are lots of places to visit, including a general store, a nice little factory, and a bank.", SayOpt 0 "Go to the general store" 1 "Go to the factory" 2 "Go to the bank", ActOpt 0 Store 1 Factory 2 Bank, numopts 2; ! -------------------------------------------------------------------------- ! The general store and the factory are only slightly more interesting. ! -------------------------------------------------------------------------- TestNode Store with title "In a general store", text "You are currently standing in the general store of this fantastic city. Vast amounts of items can be bought here, provided you have vast amounts of money, though you prefer only to invest in things you really need.", SayOpt 0 "Buy some food" 10 "Leave this place", ActOpt [ opt; switch (opt) { 0: if (money<15) "You haven't got enough money to do that!"; money = money-15; Acquire(food); "You buy a small meal for 15c."; 10: MoveTo(TownSquare); } ], InitOpts [; if (hunger>=10 && ~~Acquired(food)) OptOn(0); OptOn(10); ]; TestNode Factory with title "At the factory", text "This place is really neat, and it's conveniently situated just outside the town square, but the best part is that you can earn some money here if you're a good worker.", SayOpt 0 "Earn some money" 1 "Leave this place", ActOpt [ opt; switch (opt) { 0: if (money+account>=100) "What?! More money? Come on, you're loaded already!"; money = money+10; "OK, you are now 10c wealthier!"; 1: MoveTo(TownSquare); } ], numopts 1; ! -------------------------------------------------------------------------- ! The bank has a slightly more interesting ActOpt property. It illustrates ! how we can leave the options-only interface for a while, and ask the ! player for some specific information. This is also where the game ends. ! -------------------------------------------------------------------------- TestNode Bank with title "In a bank", text [; print_ret "Ah, the bank! It looks like your money would be safe in here. You also notice a notice next to the entrance.^^ Money in account: ", account, "c"; ], SayOpt 0 "Deposit money" 1 "Withdraw money" 2 "Read the notice" 3 "Return the diamond" 4 "Leave this place", ActOpt [ opt cash; switch (opt) { 0: print "Please enter amount:^"; cash = GetNumber(0,money); if (cash==0) "You decide not to make any deposit after all."; money = money-cash; account = account+cash; "Cash deposited."; 1: print "Please enter amount:^"; cash = GetNumber(0,account); if (cash==0) "You decide that you don't need more money right now."; account = account-cash; money = money+cash; "Cash withdrawn."; 2: "~Hey all! We wish to inform you that some wicked person recently stole a very valuable diamond from our vault.~ (Maybe not that safe after all...) ~A truly groovy reward awaits anyone who helps us retrieve this gem!~"; 3: print "You wave the diamond in the air. ~Hey, is this the thing you people have been looking for?~^^ Obviously, it was. You become the hero of the day, but who cares -- tomorrow is another day anyway!^^"; MoveTo(WonEnd); 4: MoveTo(TownSquare); } ], InitOpts [; if (money>0) OptOn(0); if (account>0) OptOn(1); OptOn(2); if (Acquired(diamond)) OptOn(3); OptOn(4); ], has safe; ! -------------------------------------------------------------------------- ! Here we get to see how the initial property can be used. ! -------------------------------------------------------------------------- TestNode DarkAlley with title "In a dark alley", initial "You follow the thief as fast as you can, and after a wild chase through the town you end up in dark alley between two old buildings. The thief seems to have vanished, but you can see an open well leading down to the sewers. Suspicious...", text "You are standing in a dark alley between two old buildings. An open well leads down to the sewers.", SayOpt 0 "Enter the well" 1 "Leave this place", ActOpt [ opt; switch (opt) { 0: Sewers.number = 2; MoveTo(Sewers); 1: MoveTo(TownSquare); } ], numopts 1, has safe; ! -------------------------------------------------------------------------- ! And here's the maze. It's the main show-off in this game, illustrating ! how a complex location can be created in a single node. Note how it calls ! MoveTo(self) all the time. This is because we want its text to be ! displayed again every time the player moves around. ! ! We use the Action method to display some random messages now and then, ! since this place is otherwise rather boring to navigate. ! -------------------------------------------------------------------------- Array maze -> 2 4 14 10 4 12 14 12 14 10 3 6 11 5 12 12 9 2 3 3 3 3 3 6 12 14 12 11 5 9 7 9 1 3 6 11 0 7 12 10 3 6 12 11 3 5 12 9 2 3 7 13 10 1 5 12 14 8 5 9 3 2 1 6 14 14 13 12 10 2 7 9 6 9 5 9 6 10 5 9 3 4 13 12 12 14 9 5 14 8 5 8 4 12 12 13 8 6 13 8; TestNode Sewers with title "In the sewers", text [ n i; print "You are in the sewers beneath the streets, a maze-like construction. From here you can go "; n = maze->self.number; n = (n & 1==1)+(n & 2==2)+(n & 4==4)+(n & 8==8); if (n==1) print "nowhere but "; for(i=1:i<=8:i=i*2) { if ((maze->self.number) & i) { switch (i) { 1: print "north"; 2: print "south"; 4: print "east"; 8: print "west"; } switch (--n) { 0: print ".^"; 1: print " and "; default: print ", "; } } } if (self.number==2) "^You can also see an open manhole above."; rtrue; ], Action [; switch (random(15)) { 1: print "^A large rodent hurries to get out of the way as it hears you approaching."; if (hunger>=36) " Damn, that thing looked edible."; else new_line; 2: "^Blah. You just stepped on something disgusting."; 3: "^You hear a flushing noise from somewhere nearby."; } ], SayOpt 0 "Go north" 1 "Go south" 2 "Go east" 3 "Go west" 4 "Leave the sewers", ActOpt [ opt; if (opt==1 && self.number==97) MoveTo(ThiefsLair); else { switch (opt) { 0: self.number = self.number-10; 1: self.number = self.number+10; 2: self.number++; 3: self.number--; 4: MoveTo(DarkAlley); } if (opt<=3) MoveTo(self); } ], InitOpts [ n; n = maze->self.number; if (n & 1) OptOn(0); if (n & 2) OptOn(1); if (n & 4) OptOn(2); if (n & 8) OptOn(3); if (self.number==2) OptOn(4); ], number 0, has safe; ! -------------------------------------------------------------------------- ! If the player lives through the maze she ends up here. Note that it isn't ! possible to reach this place and still have some food left, and the game ! also makes this assumption. ! -------------------------------------------------------------------------- TestNode ThiefsLair with title "In a thief's lair", text [; print "Now this is a rather funny place. You could almost mistake it for a second-hand store."; if (~~Acquired(diamond)) print " In the middle of a pile of junk you spot the glittering of a huge diamond. Who would have guessed!"; if (self has general) print " Oh, and there's that food you lost a while back."; " The only exit leads north, back to the sewers."; ], SayOpt 0 "Snatch that diamond" 1 "Pick up that food" 2 "Go back to the sewers", ActOpt [ opt; switch (opt) { 0: Acquire(diamond); "It's pretty heavy, but you've got it."; 1: give self ~general; Acquire(food); "Great! Now you have food again."; 2: Sewers.number = 97; MoveTo(Sewers); } ], InitOpts [; if (~~Acquired(diamond)) OptOn(0); if (self has general) OptOn(1); OptOn(2); ], has safe; ! -------------------------------------------------------------------------- ! Here we have two simple daemons, the thief being the slightly more ! interesting one. It checks the current node's safe attribute to see ! whether or not we want the thief to show up there, and sets it's own ! general attribute to signal that the option to follow the thief should be ! activated. ! ! Note that the hunger daemon has a slightly lower priority, which means ! that any hunger messages show up after any messages about the thief. ! -------------------------------------------------------------------------- Daemon HungerCheck with Action [; switch (++hunger) { 10: "^You're starting to feel a bit hungry."; 20: "^Man, you could sure use some food now."; 25: "^Aargh, you're getting #uvery#r hungry now."; 30 to 35: "^You're gonna starv to death soon."; 36 to 40: "^#uDamn it! Get some food or die!!!!#r"; 41: print "^You finally give in to the hunger"; if (Acquired(food)) { Acquire(-food); hunger = 1; " and devour the food you've been carrying in one big mouthful. "; } else { print ". Having nothing else to consume, you are forced to eat yourself alive. (On the bright side, you could have tasted worse.)^^"; MoveTo(TheEnd); } } ], priority -5, has active; Daemon Thief with Action [ cash; if (curnode has safe || ThiefsLair has visited || random(100)<=80) return; if (money>10) { cash = random(10); money = money-cash; give self general; print_ret "^A wicked thief runs past you and manages to lift ", cash, "c from your pocket."; } else if (Acquired(food) && ThiefsLair hasnt general) { Acquire(-food); give ThiefsLair general; give self general; print_ret "^A wicked thief runs past you and manages to steal the food you just bought. How rude!"; } ], has active; ! -------------------------------------------------------------------------- ! All our quotes (not that many, really) are gathered here. ! -------------------------------------------------------------------------- Object WordsQuote with text [; box "All the clouds turn to words" "All the words float in sequence" "No one knows what they mean" "Everyone just ignores them"; ]; ! -------------------------------------------------------------------------- ! Here go the various entry points. The Initialise() routine doesn't do ! much. Then we have two new player commands, a system for displaying ! quotes, and finally a custom status line. ! -------------------------------------------------------------------------- [ Initialise; curnode = TownSquare; "^^Well, here you are and it's a wonderful day. What could this day have in store for you? We'll just have to see!^^"; ]; [ UnknownCommand cmd; switch (cmd) { 'inventory', 'inv', 'i//': if (curnode ofclass EndNode || inventory-->0==0) "You aren't carrying much."; print "You are carrying"; if (Acquired(diamond)) { print " a shiny diamond"; if (inventory-->0>1) print " and"; } if (Acquired(food)) { print " some tasty food"; if (hunger>=30) print " (why don't you eat it?)"; } "."; 'look', 'l//': if (curnode ofclass EndNode) "Please move along. There is nothing to see here."; else return curnode.text(); default: quote = WordsQuote; rfalse; } ]; [ AfterPrompt flag; if (quote && ~~flag) { if (quote hasnt general) { quote.text(); give quote general; } quote = 0; } ]; [ DrawStatusLine width cpos; if (curnode ofclass EndNode) { @split_window 0; return; } width = 0->33; if (width==0) width = 80; cpos = width-9; @split_window 1; @set_window 1; style reverse; font off; @set_cursor 1 1; spaces width; @set_cursor 1 2; curnode.title(); @set_cursor 1 cpos; print "Cash: ", money; @set_cursor 1 1; style roman; @set_window 0; font on; ]; ! -------------------------------------------------------------------------- ! That's it. All done. Bring on the stubs! ! -------------------------------------------------------------------------- Include "mcastubs";