Constant STORY "GComplex"; Constant HEADLINE "^GWindows Demonstration Program 8^"; Constant DEMO 8; Constant PURPOSE "This program emulates the style of the games of Legend Entertainment."; ! GWindows Demo program. ! ! This demo creates an interface similar to that used in the games of ! Legend Entertainment. ! An astute user will notice that this is not exactly identical to the Legend ! games. This is for three reasons: ! First, I didn't want to get sued. ! Second, Lots of the functionality in the Legend Games relied on the context ! within the game. Since this is just a demo of GWindows, and not of "how to ! keep track of the game context", I've left a lot of the rules for dealing with ! game-specific situations out. ! Third, Legend games could make assumptions about the screen size and proportion ! which we cannot. When done in Glulx/GWindows, there's some tweaking needed ! to make things look visually attractive. ! ! The most notable changes/omissions are: ! The compass is redesigned and is not context-sensitive ! See (A) above. ! This shape of compass just seems to look better in Glulx. ! Making the compass change to reflect available exits is fairly easy to ! do, but it's outside the scope of this demo. ! The menu windows don't have scroll bars ! GWindows does not have a scrollbar widget at this time. Since GMenus ! have their own scrolling mechanism, they aren't necessary, and would ! just add a lot of extra work ! The interface control buttons are just a menu ! A GImageMap could have been used for the buttons, but this allows me to ! demonstrate GColumnMenu ! The statusline is different/The status button works differently ! In Legend games, the status button displayed an assessment of your ! character's wellbeing in the upper right window. Since this is a demo, ! the player doesn't have a state of wellbeing. I opted instead to ! use the status button to toggle the status line. The status line is ! default inform-style, because I didn't want to bother writing a custom ! one. ! The buttons for erasing/executing player input are missing ! There wasn't any room for them. Besides, I didn't see much use for them ! in this game. ! The verb menu is very short ! It's just a demo. You can add more verbs easily. ! ! ! On interpreters that support it, this particular interface looks slightly ! better with visible window borders ! GWindows switches Constant GW_ECHO_OVERRIDE; ! Force override commands to be printed include "gcomplex.bli"; ! This is a (very dull) graphical game. ! You will need iblorb or a compatable ! blorbification system ! Include Gwindows Definitions include "gwindefs"; include "parser"; include "verblib"; ! Include GWindows base classes include "gwincls"; ! Include GWindows hooks include "gwindows"; include "grammar"; ! Include GWindows predefined widgets include "gstatus"; include "gmenu"; include "gimage"; include "gpopup"; !include "gcolmen"; include "gautomen"; include "gimap"; include "gcombine"; ! Classes for menu items ! 'menu_cmd' items execute a command based on their name, so if the option ! menu_cmd "TAKE BALL"; ! was selected, the game would act as if the player had typed >TAKE BALL ! himself class menu_cmd with select [; cmd_override=self; ]; ! 'menu_word' items insert their name into the current input line. class menu_word with select [; StreamWord(self); ]; ! 'menu_separator' is a class for blank menu items. You can click them, ! but it doesn't produce any effect class menu_separator with short_name "", select [; ]; ! 'item_btn' is a menu entry which inserts the name of a real game-world ! object into the input. We create these dynamically, one for each item ! in the room (or in the player's possession), so the list will be incomplete ! if there's more than 128 objects in a room. Of course, that many objects ! in the same room is asking for trouble, and this game doesn't have ! nearly that many objects, so we should be safe class item_btn(128) with short_name [; print (name) self.obj; rtrue;], select [; streamword(self);], obj 0; ! The verb menu's data. It consists of items which override player input, and ! items which append to player input object main_menu; menu_cmd -> "SAVE"; menu_cmd -> "RESTART"; menu_cmd -> "RESTORE"; menu_cmd -> "SCORE"; menu_cmd -> "QUIT"; menu_separator ->; ! Everything below this line is a game verb, rather than ! a meta-verb. menu_cmd -> "LOOK"; menu_word -> "EXAMINE "; menu_word -> "TAKE "; menu_word -> "DROP "; ! The map for the compass. Each item gives its position in the image ! (we give the co-ordinates in the original image -- GImageMap will take care ! of how the image has been resized on the screen) ! Maps and Menus are compatable, so we could give the compassmenu object ! to a menu window if we wanted a textual compass instead. object compassmenu with image COMPAS_PIC; menu_cmd -> "UP" with xpos 5, ypos 9, width 42, height 20; menu_cmd -> "DOWN" with xpos 5, ypos 36, width 42, height 20; menu_cmd -> "IN" with xpos 5, ypos 68, width 42, height 20; menu_cmd -> "OUT" with xpos 5, ypos 95, width 42, height 20; menu_cmd -> "NORTHWEST" with xpos 67, ypos 27, width 27, height 18; menu_cmd -> "NORTH" with xpos 99, ypos 10, width 17, height 45; menu_cmd -> "NORTHEAST" with xpos 126, ypos 25, width 28, height 17; menu_cmd -> "EAST" with xpos 125, ypos 57, width 45, height 15; menu_cmd -> "SOUTHEAST" with xpos 127, ypos 80, width 27, height 21; menu_cmd -> "SOUTH" with xpos 100, ypos 66, width 18, height 44; menu_cmd -> "SOUTHWEST" with xpos 64, ypos 80, width 31, height 22; menu_cmd -> "WEST" with xpos 55, ypos 53, width 41, height 17; ! The control menu. It has commands which change the interface style. ! These aren't menu_cmd's because we need to conserve space in the window, ! but almost all of them invoke one of the WINDOW commands. ! ! We use menu_separators to space the menu out some. ! object control_menu; object -> "HELP" with select [; cmd_override="WINDOW HELP";]; menu_separator ->; object -> "HALF" with select [; cmd_override="WINDOW HALF"; ]; menu_separator ->; ! PIC, LOOK, and INV are context-sensitive; they do different things ! depending on how the interface looks (since it doesn't make sense to turn ! off the picture window if it isn't there already) object -> "PIC" with select [; if (imagewin.split) cmd_override="WINDOW HIDE"; else cmd_override="WINDOW IMAGE"; ]; menu_separator ->; object -> "LOOK" with select [; if (lookwin.split) cmd_override="WINDOW IMAGE"; else cmd_override="WINDOW LOOK"; ! Since the contents of this menu will change, ! we ask it to redraw. give controlbutton general; ], short_name [; if (lookwin.split) print "IMAGE"; else print "LOOK"; rtrue;]; menu_separator ->; ! INV/LOC toggles the noun menu. We have to do a redraw ourselves, since ! GWindows's automatic redraws only happen at the end of a turn. object -> "INV" with select [; if (nounmenu.current_menu==invmenu) nounmenu.activate(locmenu); else nounmenu.activate(invmenu); nounmenu.redraw(); ! Since the contents of this menu will change, ! we ask it to redraw. give controlbutton general; ], short_name [; if (nounmenu.current_menu==invmenu) print "LOC"; else print "INV"; rtrue; ]; menu_separator ->; object -> "TEXT" with select [; cmd_override="WINDOW TEXT"; ]; menu_separator ->; object -> "STATUS" with select [; cmd_override="WINDOW STATUS"; ]; ! The menus for the noun window automatically regenerate their contents ! every turn. ! Three notes about these: ! First, since one of these gets called every turn, it slows the game down ! some. Rewriting the objectloops would help this. ! Second, these menus don't give a very complete listing; if the player ! was carrying a sack, the contents of the sack really should be listed, ! and there should be rules for not displaying, say, pieces of scenery. ! This could easily be accomplished using a clever loopoverscope routine, ! but it would make the game run even slower. ! Third, in the real legend games, the menus are more context-sensitive; ! when a verb has already been typed, the menu only contains items which ! make sense as a noun for that verb. Since this is only a demo of the ! interface, and we don't have many objects anyway, we'll use the simpler ! version, leaving the more complete one as an exercise to the reader. object locmenu with update [ o x; objectloop(o ofclass item_btn) item_btn.destroy(o); objectloop(o near player) { x=item_btn.create(); if (~~x) break; x.obj=o; move x to self; } rtrue; ]; object invmenu with update [o x; objectloop(o ofclass item_btn) item_btn.destroy(o); objectloop(o in player) { x=item_btn.create(); if (~~x) break; x.obj=o; move x to self; } rtrue; ]; ! Even in a complex world like this, InitGWindows stays simple [ InitGWindows; Active_UI=top; Main_GWindow=mainwin; ]; ! This is a very complex interface. Watch closely. ! ! As before, 'top' is the top-level window set ! We divide this into the "game area" (the right side, where ! the main window and status window are) and the "control area" ! where the menus and compass are WindowPair top; ! The game area consists of the "standard" interface (gametoparea) ! and the upper-left window WindowPair -> gamearea; ! 'gametoparea' is basically the top-level window from GSimple, ! but we make the stauswindow a GPopupWin so that we can hide it ! if the user clicks on 'status' WindowPair -> -> gametoparea; ! Sizing: ! Remember -- the first window you create doesn't need a size; it'll ! automatically get whatever space isn't used by other windows Textbuffer -> -> -> mainwin; ! Sizing: ! This gets an absolute size of 1 line. That space is taken from ! gametoparea. All the space left over in gametoparea goes to mainwin GStatusWin -> -> -> statuswin class GPopupWin with asplit 1, has abssplit; ! This is the window in the top left. Since the same window can't be ! both text and graphics, we actually declare two windows, and only ! pop up the text window when it's called for. WindowPair -> -> ImageArea; ! The image window. It's a popup, because you can't make a windowpair ! into a popup. Deactivating imagewin hides the entire ImageArea. ! In a real Legend game, you can click on objects in the image window ! to "do the obvious thing" to them. We won't do that for this demo, but ! it would be easy enough to use a GImageMap for this. ! If we had more than one room, you'd want to reset its image ! for each room as well. ! Sizing: ! This object's size defines the size of ImageArea, so ! it means that ImageArea gets the upper 45% of gamearea (gametoparea ! gets the rest) GImageWin -> -> -> imagewin class gpopupwin, with split 45, asplit 45, split_key gamearea, image MAIN_PIC, split_dir winmethod_Above; ! The "Look" window. When it's active, it fills the ImageArea with ! the room description ! Sizing: ! This window's size refers to the percentage of 'ImageArea' it gets ! Initially this is zero, but when it's activated, it gets 100%, ! squashing imagewin to nothing GPopupWin -> -> -> lookwin class TextBuffer, with update [; if (self.split~=0) { glk_set_window(self.winid); glk_window_clear(self.winid); ; } ], asplit 100, split_dir winmethod_Below; ! The "control area" is where all the buttons and menus go. WindowPair -> controlarea; !The area at the top of the screen is a pair containing the !"control buttons" (buttons which configure the interface) !and the compass. WindowPair -> -> buttonarea; ! The buttons are done via a "column menu". We also make it a popup -- ! its "split_key" is the top level window, so deactivating this window ! actually hides the entire control area. ! Because there isn't a class for popup-column menus, you have to use a ! GCombiner. Order matters here -- GCombiners are not for the faint of ! heart. ! ! We use GAutoMenu, which automatically works out the column width. ! We also blank out the selection marker, because ! it doesn't look good here. ! This menu still accepts keyboard input. If we wanted to disable it, ! we could override its char_event and stylehints, but that seems like ! overkill. ! ! Sizing: ! This is the first window created within the control area, so its ! size determines how much of the top-level window ! goes to the control area (the rest goes to the game area) object -> -> -> controlbutton class gcombinerAD gAutoMenu gpopupwin, with split 45, asplit 45, split_key top, sel_marker ' ', split_dir winmethod_Left; ! The compass is an image map. It will distribute mouse clicks to the ! items in the compass menu ! Sizing: ! This object's size determines how much of the buttonarea goes to ! itself. The rest goes to the control buttons GImageMap -> -> -> compassbutton with split 55, split_dir winmethod_Right; ! The bottom half of the control area consists of two menus, one for verbs, ! and one for nouns. WindowPair -> -> menuarea; !The verb menu. !Sizing: ! This is the first window created within menuarea, so its size determines ! how much of the control area is given over to the menu area. ! So, the lower 75% of the control area becomes menus (and the buttons ! get the rest) GMenu -> -> -> verbmenu with split 75, split_dir winmethod_Below; !The noun menu ! Sizing: ! This is the last window created. Its size determines how much of the ! menu area it gets (the verb menu gets the rest) ! Actually, in the legend games, the noun menu was slightly bigger than ! the verb menu, so a split value of 55 would be more historically ! accurate, but 50-50 looks better under glulx. GMenu -> -> -> nounmenu with split 50, split_dir winmethod_Right; object r1 "Test room" with description "This is a primitive room. Its only purpose is to give the user somewhere to be while viewing this GWindows Demonstration.", has light; object -> foo "foo" with name 'foo'; object -> bar "bar" with name 'bar'; object baz "baz" with name 'baz' selfobj; [ Initialise; location=r1; print "^^^Welcome to GWindows demo program ", DEMO, ". ", (string) PURPOSE, "^^"; ! Turn on all the menus. verbmenu.activate(main_menu); controlbutton.activate(control_menu); compassbutton.activate(compassmenu); nounmenu.activate(locmenu); ]; ! The window configuration verbs. [ showallsub; ! Show the whole interface. Turn on the control area and the image area controlbutton.activate(control_menu); imagewin.activate(); ! This changes the whole screen, so we redraw all of it give top general; ]; [ hideallsub; ! Switch to text-only by hiding everything else controlbutton.deactivate(); imagewin.deactivate(); give top general; ]; [ showmenusub; ! Show the left-side; leave the image area alone controlbutton.activate(control_menu); give top general; ]; [ hidemenusub; controlbutton.deactivate(); give top general; ]; [ winlooksub; ! Switch the upper left window to the look window. lookwin.activate(); ! We turn on the image window, because if it wasn't on already, ! the upper left window is closed. imagewin.activate(); ! This only affects the right side of the screen, so don't waste time ! redrawing everything give gamearea general; ]; [ winpicsub; ! Turn the upper left window on if it wasn't already, and switch it to ! show the picture lookwin.deactivate(); imagewin.activate(); give gamearea general; ]; [ win1colsub; ! Turn off the upper left window (whatever happens to be in it) imagewin.deactivate(); give gamearea general; ]; [ hidestatussub; ! Toggle the statusline if (statuswin.split) statuswin.deactivate(); else statuswin.activate(); give gamearea general; ]; [ winhelpsub; ! Tell the player how to use the WINDOW commands. "The following WINDOW commands are available: ^ LOOK - Show the room description in the upper right window ^ IMAGE - Show the room picture in the upper right window ^ TEXT - Standard textual interface ^ SHOW - Full graphical interface ^ HALF - Hide left side (menus and compass) ^ MENU - Show menus and compass ^ STATUS - Toggle display of statusline ^ HIDE - Hide upper left window ^ HELP - This message"; ]; verb meta 'window' * 'look' -> winlook * 'image' -> winpic * 'text' -> hideall * 'show' -> showall * 'half' -> hidemenu * 'menu' -> showmenu * 'status' -> hidestatus * 'help' -> winhelp * 'hide' -> win1col;