├── .gitignore ├── .gitmodules ├── README.md ├── extensions └── David Cornelson │ ├── FyreVM Banner Output.i7x │ ├── FyreVM Core.i7x │ ├── FyreVM Prologue.i7x │ ├── FyreVM Simple Web Hints.i7x │ ├── FyreVM Status Line.i7x │ └── FyreVM Text Styles.i7x ├── package.json ├── projects └── Cloak of Darkness.inform │ └── Source │ └── story.ni ├── public ├── Cloak of Darkness.ulx ├── favicon.ico └── index.html ├── semantic.json └── src ├── FyreVMWeb ├── FyreVMMem.ts ├── FyreVMWeb.js ├── FyreVMWeb.ts └── tsconfig.json ├── components ├── EmbeddedCommandLine.js ├── HelpStandard.js ├── HintsStandard.js ├── PagingContent.js ├── ScrollingContent.js ├── StandardMenu.js ├── StandardStatusLine.js └── StoryTitle.js ├── index.js ├── semantic.json └── templates └── standard ├── Story.jsx ├── Story.test.js └── index.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | semantic/ 4 | *.js.map 5 | *.swp 6 | .idea 7 | public/FyreVMWeb.js 8 | Settings.plist 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/glulx-typescript"] 2 | path = src/FyreVMWeb/glulx-typescript 3 | url = https://github.com/thiloplanz/glulx-typescript.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fyrevm-web 2 | 3 | FyreVM-Web is a web application platform for Inform 7 based Interactive Fiction 4 | stories. There is a standard template, standard components, and a reference to 5 | the glulx-typescript engine. The platform relies on glulx-typescript, fyrevm 6 | web manager, fyrevm memory manager, reactjs, and semantic-ui as its core 7 | components. 8 | 9 | ## Getting started 10 | 11 | ### Requirements 12 | 13 | To be installed by the user: 14 | 15 | * [Inform 7](http://inform7.com/download/) 16 | * [NodeJS/NPM](https://nodejs.org/en/download/) 17 | * TypeScript 18 | 19 | npm install -g typescript 20 | 21 | * [Git Client](https://git-scm.com/downloads) 22 | 23 | 24 | Other items will get "pulled" from various git repositories, including: 25 | 26 | * [glulx-typescript](https://github.com/thiloplanz/glulx-typescript) 27 | * [reactjs](https://facebook.github.io/react/) 28 | 29 | ### Source code installation 30 | 31 | You may wish to alter a template or create your own. In that case, you'll want 32 | to clone or fork the entire repository and make your changes. 33 | 34 | * Clone FyreVM-Web 35 | 36 | git clone -g fyrevm-web 37 | 38 | ### Build the standard template 39 | 40 | * npm install 41 | * git submodule init; git submodule update 42 | * cd src/FyreVMWeb; tsc; cd ../.. 43 | * npm run build 44 | 45 | ### Run the standard template locally 46 | 47 | * npm install 48 | * git submodule init; git submodule update 49 | * cd src/FyreVMWeb; tsc; cd ../.. 50 | * npm start 51 | 52 | ### Creating your own template 53 | 54 | * In the src/templates folder, create a new folder with your template name 55 | (e.g. threewindows) 56 | * Copy the Standard template to your new folder and modify as needed. 57 | -------------------------------------------------------------------------------- /extensions/David Cornelson/FyreVM Banner Output.i7x: -------------------------------------------------------------------------------- 1 | FyreVM Banner Output by David Cornelson begins here. 2 | 3 | Section 1 - Print the Banner when not using channels 4 | 5 | Rule for printing the banner text when not outputting channels: 6 | say "[bold type][story title][roman type][line break]"; 7 | say "[story headline] by [story author][line break]"; 8 | say "Copyright [unicode 169] [story creation year][line break]"; 9 | say "Release [release number] / Serial number [story serial number] / Inform 7 Build [I7 version number] (I6 lib/v[I6 library number] lib [I7 library number] [strict mode][debug mode])[line break]"; 10 | 11 | Section 2 - Banner channel for putting the banner somewhere else 12 | 13 | banner-channel is a channel with content name "bannerContent" and content type "text". 14 | 15 | Rule for printing the banner text when outputting channels: 16 | say "[on banner-channel][bold type][story title][roman type][line break][story headline] by [story author][line break]Copyright [unicode 169] [story creation year][line break]Release [release number] / Serial number [story serial number] / Inform 7 Build [I7 version number] (I6 lib/v[I6 library number] lib [I7 library number] [strict mode][debug mode])[line break][end]"; 17 | 18 | FyreVM Banner Output ends here. 19 | 20 | ---- DOCUMENTATION ---- 21 | 22 | The FyreVM Banner Output extension prints the standard I7 banner when not emitting channel data (in the IDE or a non quixe-channels interpreter). -------------------------------------------------------------------------------- /extensions/David Cornelson/FyreVM Core.i7x: -------------------------------------------------------------------------------- 1 | Version 1/070815 of FyreVM Core (for Glulx only) by David Cornelson begins here. 2 | 3 | Chapter 1 - FyreVM-specific constants and definitions 4 | 5 | [FyreVM defines a new opcode to handle the things that would otherwise be handled by @glk. These definitions allow us to use that opcode.] 6 | 7 | Include (- 8 | ! FY_READLINE: Takes a buffer address and size and reads a line of input 9 | ! from the player. The line length is written into the word at the start 10 | ! of the buffer, and the characters are written after (starting at offset 4). 11 | ! Writes a length of 0 if the read failed. 12 | Constant FY_READLINE = 1; 13 | ! FY_READKEY: Reads a single character of input, e.g. for pausing the game. 14 | ! Returns the Unicode character, or 0 if the read failed. 15 | Constant FY_READKEY = 2; 16 | ! FY_TOLOWER/FY_TOUPPER: Converts a character to lower or upper case, based 17 | ! on whichever encoding is used for the dictionary and input buffer. 18 | Constant FY_TOLOWER = 3; 19 | Constant FY_TOUPPER = 4; 20 | ! FY_CHANNEL: Selects an output channel. 21 | Constant FY_CHANNEL = 5; 22 | ! FY_SETVENEER: Registers a routine address or constant value with the 23 | ! interpreter's veneer acceleration system. 24 | Constant FY_SETVENEER = 6; 25 | 26 | ! **** Channel IO Layout **** 27 | ! 28 | ! Each channel constant is a 4 byte integer packed with 4 upper case letters. 29 | ! 30 | ! Required Channels for FY_CHANNEL. 31 | ! 32 | Global next_id = 0; 33 | 34 | [ GetNextId id; 35 | if (next_id >= 26*26) { 36 | print "*** GetNextId: out of channel identifiers ***^"; 37 | next_id = 0; 38 | } 39 | id = ('Z' * $1000000) + ('Z' * $10000) + (('A' + next_id / 26) * $100) + ('A' + next_id % 26); 40 | next_id++; 41 | return id; 42 | ]; 43 | 44 | [ SET_CHANNEL channel_number; 45 | if (is_fyrevm) FyreCall(FY_CHANNEL, channel_number); 46 | ]; 47 | 48 | ! These are for internal use... 49 | Constant FYC_MAIN = ('M' * $1000000) + ('A' * $10000) + ('I' * $100) + 'N'; ! MAIN 50 | Constant FYC_PROMPT = ('P' * $1000000) + ('R' * $10000) + ('P' * $100) + 'T'; ! PRPT 51 | Constant FYC_LOCATION = ('L' * $1000000) + ('O' * $10000) + ('C' * $100) + 'N'; ! LOCN 52 | Constant FYC_SCORE = ('S' * $1000000) + ('C' * $10000) + ('O' * $100) + 'R'; ! SCOR 53 | Constant FYC_TIME = ('T' * $1000000) + ('I' * $10000) + ('M' * $100) + 'E'; ! TIME 54 | Constant FYC_DEATH = ('D' * $1000000) + ('E' * $10000) + ('A' * $100) + 'D'; ! DEAD 55 | Constant FYC_ENDGAME = ('E' * $1000000) + ('N' * $10000) + ('D' * $100) + 'G'; ! ENDG 56 | Constant FYC_TURN = ('T' * $1000000) + ('U' * $10000) + ('R' * $100) + 'N'; ! TURN 57 | Constant FYC_STORYINFO = ('I' * $1000000) + ('N' * $10000) + ('F' * $100) + 'O'; ! INFO 58 | Constant FYC_SCORENOTIFY = ('S' * $1000000) + ('N' * $10000) + ('O' * $100) + 'T'; ! SNOT 59 | Constant FYC_CONTENT_MANAGEMENT = ('C' * $1000000) + ('M' * $10000) + ('G' * $100) + 'T'; ! CMGT 60 | Constant FYC_IFID = ('I' * $1000000) + ('F' * $10000) + ('I' * $100) + 'D'; ! IFID 61 | 62 | ! Slots for FY_SETVENEER. 63 | Constant FYV_Z__Region = 1; 64 | Constant FYV_CP__Tab = 2; 65 | Constant FYV_OC__Cl = 3; 66 | Constant FYV_RA__Pr = 4; 67 | Constant FYV_RT__ChLDW = 5; 68 | Constant FYV_Unsigned__Compare = 6; 69 | Constant FYV_RL__Pr = 7; 70 | Constant FYV_RV__Pr = 8; 71 | Constant FYV_OP__Pr = 9; 72 | Constant FYV_RT__ChSTW = 10; 73 | Constant FYV_RT__ChLDB = 11; 74 | Constant FYV_Meta__class = 12; 75 | 76 | Constant FYV_String = 1001; 77 | Constant FYV_Routine = 1002; 78 | Constant FYV_Class = 1003; 79 | Constant FYV_Object = 1004; 80 | Constant FYV_RT__Err = 1005; 81 | Constant FYV_NUM_ATTR_BYTES = 1006; 82 | Constant FYV_classes_table = 1007; 83 | Constant FYV_INDIV_PROP_START = 1008; 84 | Constant FYV_cpv__start = 1009; 85 | Constant FYV_ofclass_err = 1010; 86 | Constant FYV_readprop_err = 1011; 87 | 88 | [ FyreCall a b c res; @"S4:4096" a b c res; return res; ]; 89 | -). 90 | 91 | [These activate FyreVM's veneer optimizations.] 92 | 93 | Include (- 94 | [ REGISTER_VENEER_R; 95 | @gestalt 4 20 is_fyrevm; ! Test if this interpreter has FyreVM channels 96 | if (~~is_fyrevm) rfalse; 97 | 98 | FyreCall(FY_SETVENEER, FYV_Z__Region, Z__Region); 99 | FyreCall(FY_SETVENEER, FYV_CP__Tab, CP__Tab); 100 | FyreCall(FY_SETVENEER, FYV_OC__Cl, OC__Cl); 101 | FyreCall(FY_SETVENEER, FYV_RA__Pr, RA__Pr); 102 | FyreCall(FY_SETVENEER, FYV_Unsigned__Compare, Unsigned__Compare); 103 | FyreCall(FY_SETVENEER, FYV_RL__Pr, RL__Pr); 104 | FyreCall(FY_SETVENEER, FYV_RV__Pr, RV__Pr); 105 | FyreCall(FY_SETVENEER, FYV_OP__Pr, OP__Pr); 106 | FyreCall(FY_SETVENEER, FYV_Meta__class, Meta__class); 107 | 108 | #ifdef STRICT_MODE; 109 | FyreCall(FY_SETVENEER, FYV_RT__ChLDW, RT__ChLDW); 110 | FyreCall(FY_SETVENEER, FYV_RT__ChSTW, RT__ChSTW); 111 | FyreCall(FY_SETVENEER, FYV_RT__ChLDB, RT__ChLDB); 112 | #endif; 113 | 114 | FyreCall(FY_SETVENEER, FYV_String, String); 115 | FyreCall(FY_SETVENEER, FYV_Routine, Routine); 116 | FyreCall(FY_SETVENEER, FYV_Class, Class); 117 | FyreCall(FY_SETVENEER, FYV_Object, Object); 118 | FyreCall(FY_SETVENEER, FYV_RT__Err, RT__Err); 119 | FyreCall(FY_SETVENEER, FYV_NUM_ATTR_BYTES, NUM_ATTR_BYTES); 120 | FyreCall(FY_SETVENEER, FYV_classes_table, #classes_table); 121 | FyreCall(FY_SETVENEER, FYV_INDIV_PROP_START, INDIV_PROP_START); 122 | FyreCall(FY_SETVENEER, FYV_cpv__start, #cpv__start); 123 | FyreCall(FY_SETVENEER, FYV_ofclass_err, "apply 'ofclass' for"); 124 | FyreCall(FY_SETVENEER, FYV_readprop_Err, "read"); 125 | ]; 126 | -). 127 | 128 | The register veneer routines rule translates into I6 as "REGISTER_VENEER_R". 129 | 130 | To decide whether FyreVM is present: (- (is_fyrevm) -). [This global variable is defined below, before VM_Initialise.] 131 | To decide whether FyreVM is not present: (- (~~is_fyrevm) -). 132 | 133 | After starting the virtual machine: follow the register veneer routines rule. 134 | 135 | [And these set up an alternative way to print text into an array, since Inform's default way of doing that requires Glk. FyreVM includes a Glk wrapper which could theoretically support that, but it's only active when the Glk output system is selected.] 136 | 137 | Include (- 138 | Global output_buffer_address; 139 | Global output_buffer_size; 140 | Global output_buffer_pos; 141 | Global output_buffer_uni; 142 | 143 | Constant MAX_OUTPUT_NESTING = 32; 144 | Array output_buffer_stack --> (MAX_OUTPUT_NESTING * 4); 145 | Global output_buffer_sp = 0; 146 | 147 | [ OpenOutputBufferUnicode buffer size; 148 | output_buffer_stack-->(output_buffer_sp++) = output_buffer_address; 149 | output_buffer_stack-->(output_buffer_sp++) = output_buffer_size; 150 | output_buffer_stack-->(output_buffer_sp++) = output_buffer_pos; 151 | output_buffer_stack-->(output_buffer_sp++) = output_buffer_uni; 152 | 153 | output_buffer_address = buffer; 154 | output_buffer_size = size; 155 | output_buffer_pos = 0; 156 | output_buffer_uni = 1; 157 | @setiosys 1 _OutputBufferProcUni; 158 | ]; 159 | 160 | [ OpenOutputBuffer buffer size; 161 | output_buffer_stack-->(output_buffer_sp++) = output_buffer_address; 162 | output_buffer_stack-->(output_buffer_sp++) = output_buffer_size; 163 | output_buffer_stack-->(output_buffer_sp++) = output_buffer_pos; 164 | output_buffer_stack-->(output_buffer_sp++) = output_buffer_uni; 165 | 166 | output_buffer_address = buffer; 167 | output_buffer_size = size; 168 | output_buffer_pos = 0; 169 | output_buffer_uni = 0; 170 | @setiosys 1 _OutputBufferProc; 171 | ]; 172 | 173 | [ CloseOutputBuffer results rv; 174 | if (results) { 175 | results-->0 = 0; 176 | results-->1 = output_buffer_pos; 177 | } 178 | rv = output_buffer_pos; 179 | ResumeOutputBuffer(); 180 | return rv; 181 | ]; 182 | 183 | [ SuspendOutputBuffer; 184 | output_buffer_stack-->(output_buffer_sp++) = output_buffer_address; 185 | output_buffer_stack-->(output_buffer_sp++) = output_buffer_size; 186 | output_buffer_stack-->(output_buffer_sp++) = output_buffer_pos; 187 | output_buffer_stack-->(output_buffer_sp++) = output_buffer_uni; 188 | 189 | @setiosys 20 0; 190 | ]; 191 | 192 | [ ResumeOutputBuffer; 193 | output_buffer_uni = output_buffer_stack-->(--output_buffer_sp); 194 | output_buffer_pos = output_buffer_stack-->(--output_buffer_sp); 195 | output_buffer_size = output_buffer_stack-->(--output_buffer_sp); 196 | output_buffer_address = output_buffer_stack-->(--output_buffer_sp); 197 | 198 | if (output_buffer_sp > 0) { 199 | if (output_buffer_uni) 200 | @setiosys 1 _OutputBufferProcUni; 201 | else 202 | @setiosys 1 _OutputBufferProc; 203 | } else 204 | @setiosys 20 0; 205 | ]; 206 | [ _OutputBufferProcUni ch; 207 | if (output_buffer_pos < output_buffer_size) 208 | output_buffer_address-->output_buffer_pos = ch; 209 | output_buffer_pos++; 210 | ]; 211 | 212 | [ _OutputBufferProc ch; 213 | if (output_buffer_pos < output_buffer_size) 214 | output_buffer_address->output_buffer_pos = ch; 215 | output_buffer_pos++; 216 | ]; 217 | -). 218 | 219 | Chapter 2 - Template replacements 220 | 221 | Section 1 - Glulx segment 222 | 223 | Include (- 224 | Global is_fyrevm = 0; 225 | 226 | [ VM_PreInitialise res; 227 | @gestalt 4 20 is_fyrevm; ! Test if this interpreter has FyreVM channels 228 | if (is_fyrevm) { 229 | ! If so, we can skip all the Glk business 230 | unicode_gestalt_ok = true; 231 | @setiosys 20 0; 232 | return; 233 | } 234 | 235 | ]; 236 | 237 | [ VM_Initialise res; 238 | @gestalt 4 20 is_fyrevm; ! Test if this interpreter has FyreVM channels 239 | if (is_fyrevm) { 240 | ! If so, we can skip all the Glk business 241 | unicode_gestalt_ok = true; 242 | @setiosys 20 0; 243 | return; 244 | } 245 | 246 | @gestalt 4 2 res; ! Test if this interpreter has Glk... 247 | if (res == 0) quit; ! ...without which there would be nothing we could do 248 | 249 | unicode_gestalt_ok = false; 250 | if (glk_gestalt(gestalt_Unicode, 0)) 251 | unicode_gestalt_ok = true; 252 | 253 | ! Set the VM's I/O system to be Glk. 254 | @setiosys 2 0; 255 | 256 | ! First, we must go through all the Glk objects that exist, and see 257 | ! if we created any of them. One might think this strange, since the 258 | ! program has just started running, but remember that the player might 259 | ! have just typed "restart". 260 | 261 | GGRecoverObjects(); 262 | 263 | res = InitGlkWindow(0); 264 | if (res ~= 0) return; 265 | 266 | ! Now, gg_mainwin and gg_storywin might already be set. If not, set them. 267 | 268 | if (gg_mainwin == 0) { 269 | ! Open the story window. 270 | res = InitGlkWindow(GG_MAINWIN_ROCK); 271 | if (res == 0) { 272 | glk_stylehint_set(3, 3, 2, 0); ! left-justify style_Header 273 | gg_mainwin = glk_window_open(0, 0, 0, 3, GG_MAINWIN_ROCK); 274 | } 275 | if (gg_mainwin == 0) quit; ! If we can't even open one window, give in 276 | } 277 | else { 278 | ! There was already a story window. We should erase it. 279 | glk_window_clear(gg_mainwin); 280 | } 281 | 282 | if (gg_statuswin == 0) { 283 | res = InitGlkWindow(GG_STATUSWIN_ROCK); 284 | if (res == 0) { 285 | statuswin_cursize = statuswin_size; 286 | for (res=0: res<=10: res++) 287 | glk_stylehint_set(4, res, 9, 1); ! enable ReverseColor 288 | gg_statuswin = glk_window_open(gg_mainwin, $12, statuswin_cursize, 289 | 4, GG_STATUSWIN_ROCK); 290 | } 291 | } 292 | ! It's possible that the status window couldn't be opened, in which case 293 | ! gg_statuswin is now zero. We must allow for that later on. 294 | 295 | glk_set_window(gg_mainwin); 296 | 297 | InitGlkWindow(1); 298 | 299 | if (glk_gestalt(gestalt_Sound, 0)) { 300 | if (gg_foregroundchan == 0) 301 | gg_foregroundchan = glk_schannel_create(GG_FOREGROUNDCHAN_ROCK); 302 | if (gg_backgroundchan == 0) 303 | gg_backgroundchan = glk_schannel_create(GG_BACKGROUNDCHAN_ROCK); 304 | } 305 | 306 | glk_stylehint_set(wintype_TextBuffer, style_Emphasized, stylehint_Weight, 0); 307 | glk_stylehint_set(wintype_TextBuffer, style_Emphasized, stylehint_Oblique, 1); 308 | 309 | #ifdef FIX_RNG; 310 | @random 10000 i; 311 | i = -i-2000; 312 | print "[Random number generator seed is ", i, "]^"; 313 | @setrandom i; 314 | #endif; ! FIX_RNG 315 | ]; 316 | 317 | [ GGRecoverObjects id; 318 | ! If GGRecoverObjects() has been called, all these stored IDs are 319 | ! invalid, so we start by clearing them all out. 320 | ! (In fact, after a restoreundo, some of them may still be good. 321 | ! For simplicity, though, we assume the general case.) 322 | gg_mainwin = 0; 323 | gg_statuswin = 0; 324 | gg_quotewin = 0; 325 | gg_scriptfref = 0; 326 | gg_scriptstr = 0; 327 | gg_savestr = 0; 328 | statuswin_cursize = 0; 329 | #Ifdef DEBUG; 330 | gg_commandstr = 0; 331 | gg_command_reading = false; 332 | #Endif; ! DEBUG 333 | ! Also tell the game to clear its object references. 334 | IdentifyGlkObject(0); 335 | 336 | ! Check for FyreVM 337 | @gestalt 4 20 is_fyrevm; 338 | if (is_fyrevm) return; 339 | 340 | id = glk_stream_iterate(0, gg_arguments); 341 | while (id) { 342 | switch (gg_arguments-->0) { 343 | GG_SAVESTR_ROCK: gg_savestr = id; 344 | GG_SCRIPTSTR_ROCK: gg_scriptstr = id; 345 | #Ifdef DEBUG; 346 | GG_COMMANDWSTR_ROCK: gg_commandstr = id; 347 | gg_command_reading = false; 348 | GG_COMMANDRSTR_ROCK: gg_commandstr = id; 349 | gg_command_reading = true; 350 | #Endif; ! DEBUG 351 | default: IdentifyGlkObject(1, 1, id, gg_arguments-->0); 352 | } 353 | id = glk_stream_iterate(id, gg_arguments); 354 | } 355 | 356 | id = glk_window_iterate(0, gg_arguments); 357 | while (id) { 358 | switch (gg_arguments-->0) { 359 | GG_MAINWIN_ROCK: gg_mainwin = id; 360 | GG_STATUSWIN_ROCK: gg_statuswin = id; 361 | GG_QUOTEWIN_ROCK: gg_quotewin = id; 362 | default: IdentifyGlkObject(1, 0, id, gg_arguments-->0); 363 | } 364 | id = glk_window_iterate(id, gg_arguments); 365 | } 366 | 367 | id = glk_fileref_iterate(0, gg_arguments); 368 | while (id) { 369 | switch (gg_arguments-->0) { 370 | GG_SCRIPTFREF_ROCK: gg_scriptfref = id; 371 | default: IdentifyGlkObject(1, 2, id, gg_arguments-->0); 372 | } 373 | id = glk_fileref_iterate(id, gg_arguments); 374 | } 375 | 376 | ! Tell the game to tie up any loose ends. 377 | IdentifyGlkObject(2); 378 | ]; 379 | -) instead of "Starting Up" in "Glulx.i6t". 380 | 381 | Include (- 382 | [ VM_KeyChar win nostat done res ix jx ch; 383 | if (is_fyrevm) return FyreCall(FY_READKEY); 384 | jx = ch; ! squash compiler warnings 385 | if (win == 0) win = gg_mainwin; 386 | if (gg_commandstr ~= 0 && gg_command_reading ~= false) { 387 | done = glk_get_line_stream(gg_commandstr, gg_arguments, 31); 388 | if (done == 0) { 389 | glk_stream_close(gg_commandstr); 390 | gg_commandstr = 0; 391 | gg_command_reading = false; 392 | ! fall through to normal user input. 393 | } else { 394 | ! Trim the trailing newline 395 | if (gg_arguments->(done-1) == 10) done = done-1; 396 | res = gg_arguments->0; 397 | if (res == '\') { 398 | res = 0; 399 | for (ix=1 : ixix; 401 | if (ch >= '0' && ch <= '9') { 402 | @shiftl res 4 res; 403 | res = res + (ch-'0'); 404 | } else if (ch >= 'a' && ch <= 'f') { 405 | @shiftl res 4 res; 406 | res = res + (ch+10-'a'); 407 | } else if (ch >= 'A' && ch <= 'F') { 408 | @shiftl res 4 res; 409 | res = res + (ch+10-'A'); 410 | } 411 | } 412 | } 413 | jump KCPContinue; 414 | } 415 | } 416 | done = false; 417 | glk_request_char_event(win); 418 | while (~~done) { 419 | glk_select(gg_event); 420 | switch (gg_event-->0) { 421 | 5: ! evtype_Arrange 422 | if (nostat) { 423 | glk_cancel_char_event(win); 424 | res = $80000000; 425 | done = true; 426 | break; 427 | } 428 | DrawStatusLine(); 429 | 2: ! evtype_CharInput 430 | if (gg_event-->1 == win) { 431 | res = gg_event-->2; 432 | done = true; 433 | } 434 | } 435 | ix = HandleGlkEvent(gg_event, 1, gg_arguments); 436 | if (ix == 2) { 437 | res = gg_arguments-->0; 438 | done = true; 439 | } else if (ix == -1) done = false; 440 | } 441 | if (gg_commandstr ~= 0 && gg_command_reading == false) { 442 | if (res < 32 || res >= 256 || (res == '\' or ' ')) { 443 | glk_put_char_stream(gg_commandstr, '\'); 444 | done = 0; 445 | jx = res; 446 | for (ix=0 : ix<8 : ix++) { 447 | @ushiftr jx 28 ch; 448 | @shiftl jx 4 jx; 449 | ch = ch & $0F; 450 | if (ch ~= 0 || ix == 7) done = 1; 451 | if (done) { 452 | if (ch >= 0 && ch <= 9) ch = ch + '0'; 453 | else ch = (ch - 10) + 'A'; 454 | glk_put_char_stream(gg_commandstr, ch); 455 | } 456 | } 457 | } else { 458 | glk_put_char_stream(gg_commandstr, res); 459 | } 460 | glk_put_char_stream(gg_commandstr, 10); ! newline 461 | } 462 | .KCPContinue; 463 | return res; 464 | ]; 465 | 466 | [ VM_KeyDelay tenths key done ix; 467 | if (is_fyrevm) rfalse; ! FyreVM doesn't support timed input 468 | glk_request_char_event(gg_mainwin); 469 | glk_request_timer_events(tenths*100); 470 | while (~~done) { 471 | glk_select(gg_event); 472 | ix = HandleGlkEvent(gg_event, 1, gg_arguments); 473 | if (ix == 2) { 474 | key = gg_arguments-->0; 475 | done = true; 476 | } 477 | else if (ix >= 0 && gg_event-->0 == 1 or 2) { 478 | key = gg_event-->2; 479 | done = true; 480 | } 481 | } 482 | glk_cancel_char_event(gg_mainwin); 483 | glk_request_timer_events(0); 484 | return key; 485 | ]; 486 | 487 | [ VM_ReadKeyboard a_buffer a_table done ix; 488 | if (is_fyrevm) { 489 | FyreCall(FY_READLINE, a_buffer, INPUT_BUFFER_LEN); 490 | jump KPContinue; 491 | } 492 | if (gg_commandstr ~= 0 && gg_command_reading ~= false) { 493 | done = glk_get_line_stream(gg_commandstr, a_buffer+WORDSIZE, 494 | (INPUT_BUFFER_LEN-WORDSIZE)-1); 495 | if (done == 0) { 496 | glk_stream_close(gg_commandstr); 497 | gg_commandstr = 0; 498 | gg_command_reading = false; 499 | ! L__M(##CommandsRead, 5); would come after prompt 500 | ! fall through to normal user input. 501 | } 502 | else { 503 | ! Trim the trailing newline 504 | if ((a_buffer+WORDSIZE)->(done-1) == 10) done = done-1; 505 | a_buffer-->0 = done; 506 | VM_Style(INPUT_VMSTY); 507 | glk_put_buffer(a_buffer+WORDSIZE, done); 508 | VM_Style(NORMAL_VMSTY); 509 | print "^"; 510 | jump KPContinue; 511 | } 512 | } 513 | done = false; 514 | glk_request_line_event(gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, 0); 515 | while (~~done) { 516 | glk_select(gg_event); 517 | switch (gg_event-->0) { 518 | 5: ! evtype_Arrange 519 | DrawStatusLine(); 520 | 3: ! evtype_LineInput 521 | if (gg_event-->1 == gg_mainwin) { 522 | a_buffer-->0 = gg_event-->2; 523 | done = true; 524 | } 525 | } 526 | ix = HandleGlkEvent(gg_event, 0, a_buffer); 527 | if (ix == 2) done = true; 528 | else if (ix == -1) done = false; 529 | } 530 | if (gg_commandstr ~= 0 && gg_command_reading == false) { 531 | glk_put_buffer_stream(gg_commandstr, a_buffer+WORDSIZE, a_buffer-->0); 532 | glk_put_char_stream(gg_commandstr, 10); ! newline 533 | } 534 | .KPContinue; 535 | VM_Tokenise(a_buffer,a_table); 536 | ! It's time to close any quote window we've got going. 537 | if (gg_quotewin) { 538 | glk_window_close(gg_quotewin, 0); 539 | gg_quotewin = 0; 540 | } 541 | #ifdef ECHO_COMMANDS; 542 | print "** "; 543 | for (ix=WORDSIZE: ix<(a_buffer-->0)+WORDSIZE: ix++) print (char) a_buffer->ix; 544 | print "^"; 545 | #endif; ! ECHO_COMMANDS 546 | ]; 547 | -) instead of "Keyboard Input" in "Glulx.i6t". 548 | 549 | Include (- 550 | [ VM_Picture resource_ID; 551 | if ((~~is_fyrevm) && glk_gestalt(gestalt_Graphics, 0)) { 552 | glk_image_draw(gg_mainwin, resource_ID, imagealign_InlineCenter, 0); 553 | } else { 554 | print "[Picture number ", resource_ID, " here.]^"; 555 | } 556 | ]; 557 | 558 | [ VM_SoundEffect resource_ID; 559 | if ((~~is_fyrevm) && glk_gestalt(gestalt_Sound, 0)) { 560 | glk_schannel_play(gg_foregroundchan, resource_ID); 561 | } else { 562 | print "[Sound effect number ", resource_ID, " here.]^"; 563 | } 564 | ]; 565 | -) instead of "Audiovisual Resources" in "Glulx.i6t". 566 | 567 | [ FyreVM does not do anything about styles. This has to be managed in the game file with markup. ] 568 | 569 | Include (- 570 | [ VM_Style sty; 571 | if (~~is_fyrevm) { 572 | switch (sty) { 573 | NORMAL_VMSTY: glk_set_style(style_Normal); 574 | HEADER_VMSTY: glk_set_style(style_Header); 575 | SUBHEADER_VMSTY: glk_set_style(style_Subheader); 576 | NOTE_VMSTY: glk_set_style(style_Note); 577 | ALERT_VMSTY: glk_set_style(style_Alert); 578 | BLOCKQUOTE_VMSTY: glk_set_style(style_BlockQuote); 579 | INPUT_VMSTY: glk_set_style(style_Input); 580 | } 581 | } 582 | ]; 583 | -) instead of "Typography" in "Glulx.i6t". 584 | 585 | Include (- 586 | [ VM_UpperToLowerCase c; 587 | if (is_fyrevm) return FyreCall(FY_TOLOWER, c); 588 | return glk_char_to_lower(c); 589 | ]; 590 | [ VM_LowerToUpperCase c; 591 | if (is_fyrevm) return FyreCall(FY_TOUPPER, c); 592 | return glk_char_to_upper(c); 593 | ]; 594 | -) instead of "Character Casing" in "Glulx.i6t". 595 | 596 | Include (- 597 | ! Glulx_PrintAnything() 598 | ! Glulx_PrintAnything(0) 599 | ! Glulx_PrintAnything("string"); print (string) "string"; 600 | ! Glulx_PrintAnything('word') print (address) 'word'; 601 | ! Glulx_PrintAnything(obj) print (name) obj; 602 | ! Glulx_PrintAnything(obj, prop) obj.prop(); 603 | ! Glulx_PrintAnything(obj, prop, args...) obj.prop(args...); 604 | ! Glulx_PrintAnything(func) func(); 605 | ! Glulx_PrintAnything(func, args...) func(args...); 606 | 607 | [ Glulx_PrintAnything _vararg_count obj mclass; 608 | if (_vararg_count == 0) return; 609 | @copy sp obj; 610 | _vararg_count--; 611 | if (obj == 0) return; 612 | 613 | if (obj->0 == $60) { 614 | ! Dictionary word. Metaclass() can't catch this case, so we do it manually 615 | print (address) obj; 616 | return; 617 | } 618 | 619 | mclass = metaclass(obj); 620 | switch (mclass) { 621 | nothing: 622 | return; 623 | String: 624 | print (string) obj; 625 | return; 626 | Routine: 627 | ! Call the function with all the arguments which are already 628 | ! on the stack. 629 | @call obj _vararg_count 0; 630 | return; 631 | Object: 632 | if (_vararg_count == 0) { 633 | print (name) obj; 634 | } 635 | else { 636 | ! Push the object back onto the stack, and call the 637 | ! veneer routine that handles obj.prop() calls. 638 | @copy obj sp; 639 | _vararg_count++; 640 | @call CA__Pr _vararg_count 0; 641 | } 642 | return; 643 | } 644 | ]; 645 | 646 | [ Glulx_PrintAnyToArray _vararg_count arr arrlen str oldstr len; 647 | @copy sp arr; 648 | @copy sp arrlen; 649 | _vararg_count = _vararg_count - 2; 650 | 651 | if (is_fyrevm) { 652 | OpenOutputBuffer(arr, arrlen); 653 | } else { 654 | oldstr = glk_stream_get_current(); 655 | str = glk_stream_open_memory(arr, arrlen, 1, 0); 656 | if (str == 0) return 0; 657 | glk_stream_set_current(str); 658 | } 659 | 660 | @call Glulx_PrintAnything _vararg_count 0; 661 | 662 | if (is_fyrevm) { 663 | len = CloseOutputBuffer(0); 664 | } else { 665 | glk_stream_set_current(oldstr); 666 | @copy $ffffffff sp; 667 | @copy str sp; 668 | @glk $0044 2 0; ! stream_close 669 | @copy sp len; 670 | @copy sp 0; 671 | } 672 | return len; 673 | ]; 674 | 675 | Constant GG_ANYTOSTRING_LEN 66; 676 | Array AnyToStrArr -> GG_ANYTOSTRING_LEN+1; 677 | 678 | [ Glulx_ChangeAnyToCString _vararg_count ix len; 679 | ix = GG_ANYTOSTRING_LEN-2; 680 | @copy ix sp; 681 | ix = AnyToStrArr+1; 682 | @copy ix sp; 683 | ix = _vararg_count+2; 684 | @call Glulx_PrintAnyToArray ix len; 685 | AnyToStrArr->0 = $E0; 686 | if (len >= GG_ANYTOSTRING_LEN) 687 | len = GG_ANYTOSTRING_LEN-1; 688 | AnyToStrArr->(len+1) = 0; 689 | return AnyToStrArr; 690 | ]; 691 | -) instead of "Glulx-Only Printing Routines" in "Glulx.i6t". 692 | 693 | Include (- 694 | [ VM_ClearScreen window; 695 | if (is_fyrevm) return; ! not supported 696 | if (window == WIN_ALL or WIN_MAIN) { 697 | glk_window_clear(gg_mainwin); 698 | if (gg_quotewin) { 699 | glk_window_close(gg_quotewin, 0); 700 | gg_quotewin = 0; 701 | } 702 | } 703 | if (gg_statuswin && window == WIN_ALL or WIN_STATUS) glk_window_clear(gg_statuswin); 704 | ]; 705 | 706 | [ VM_ScreenWidth id; 707 | if (is_fyrevm) return 80; ! not supported 708 | id=gg_mainwin; 709 | if (gg_statuswin && statuswin_current) id = gg_statuswin; 710 | glk_window_get_size(id, gg_arguments, 0); 711 | return gg_arguments-->0; 712 | ]; 713 | 714 | [ VM_ScreenHeight; 715 | if (is_fyrevm) return 25; ! not supported 716 | glk_window_get_size(gg_mainwin, 0, gg_arguments); 717 | return gg_arguments-->0; 718 | ]; 719 | -) instead of "The Screen" in "Glulx.i6t". 720 | 721 | Include (- 722 | [ VM_SetWindowColours f b window doclear i fwd bwd swin; 723 | if (is_fyrevm) return; ! not supported 724 | if (clr_on && f && b) { 725 | if (window) swin = 5-window; ! 4 for TextGrid, 3 for TextBuffer 726 | 727 | fwd = MakeColourWord(f); 728 | bwd = MakeColourWord(b); 729 | for (i=0 : i<=10: i++) { 730 | if (f == CLR_DEFAULT || b == CLR_DEFAULT) { ! remove style hints 731 | glk_stylehint_clear(swin, i, 7); 732 | glk_stylehint_clear(swin, i, 8); 733 | } else { 734 | glk_stylehint_set(swin, i, 7, fwd); 735 | glk_stylehint_set(swin, i, 8, bwd); 736 | } 737 | } 738 | 739 | ! Now re-open the windows to apply the hints 740 | if (gg_statuswin) glk_window_close(gg_statuswin, 0); 741 | 742 | if (doclear || ( window ~= 1 && (clr_fg ~= f || clr_bg ~= b) ) ) { 743 | glk_window_close(gg_mainwin, 0); 744 | gg_mainwin = glk_window_open(0, 0, 0, 3, GG_MAINWIN_ROCK); 745 | if (gg_scriptstr ~= 0) 746 | glk_window_set_echo_stream(gg_mainwin, gg_scriptstr); 747 | } 748 | 749 | gg_statuswin = 750 | glk_window_open(gg_mainwin, $12, statuswin_cursize, 4, GG_STATUSWIN_ROCK); 751 | if (statuswin_current && gg_statuswin) VM_MoveCursorInStatusLine(); else VM_MainWindow(); 752 | 753 | if (window ~= 2) { 754 | clr_fgstatus = f; 755 | clr_bgstatus = b; 756 | } 757 | if (window ~= 1) { 758 | clr_fg = f; 759 | clr_bg = b; 760 | } 761 | } 762 | ]; 763 | 764 | [ VM_RestoreWindowColours; ! used after UNDO: compare I6 patch L61007 765 | if (is_fyrevm) return; ! not supported 766 | if (clr_on) { ! check colour has been used 767 | VM_SetWindowColours(clr_fg, clr_bg, 2); ! make sure both sets of variables are restored 768 | VM_SetWindowColours(clr_fgstatus, clr_bgstatus, 1, true); 769 | VM_ClearScreen(); 770 | } 771 | ]; 772 | 773 | [ MakeColourWord c; 774 | if (c > 9) return c; 775 | c = c-2; 776 | return $ff0000*(c&1) + $ff00*(c&2 ~= 0) + $ff*(c&4 ~= 0); 777 | ]; 778 | -) instead of "Window Colours" in "Glulx.i6t". 779 | 780 | Include (- 781 | [ VM_MainWindow; 782 | if (is_fyrevm) return; ! not supported 783 | glk_set_window(gg_mainwin); ! set_window 784 | statuswin_current=0; 785 | ]; 786 | -) instead of "Main Window" in "Glulx.i6t". 787 | 788 | Include (- 789 | [ VM_StatusLineHeight hgt; 790 | if (is_fyrevm) return; ! not supported 791 | if (gg_statuswin == 0) return; 792 | if (hgt == statuswin_cursize) return; 793 | glk_window_set_arrangement(glk_window_get_parent(gg_statuswin), $12, hgt, 0); 794 | statuswin_cursize = hgt; 795 | ]; 796 | 797 | [ VM_MoveCursorInStatusLine line column; 798 | if (is_fyrevm) return; ! not supported 799 | if (gg_statuswin) glk_set_window(gg_statuswin); 800 | if (line == 0) { line = 1; column = 1; } 801 | glk_window_move_cursor(gg_statuswin, column-1, line-1); 802 | statuswin_current=1; 803 | ]; 804 | -) instead of "Status Line" in "Glulx.i6t". 805 | 806 | Include (- 807 | [ Box__Routine maxwid arr ix lines lastnl parwin; 808 | if (is_fyrevm) return; ! not supported 809 | maxwid = 0; ! squash compiler warning 810 | lines = arr-->0; 811 | 812 | if (gg_quotewin == 0) { 813 | gg_arguments-->0 = lines; 814 | ix = InitGlkWindow(GG_QUOTEWIN_ROCK); 815 | if (ix == 0) 816 | gg_quotewin = 817 | glk_window_open(gg_mainwin, $12, lines, 3, GG_QUOTEWIN_ROCK); 818 | } else { 819 | parwin = glk_window_get_parent(gg_quotewin); 820 | glk_window_set_arrangement(parwin, $12, lines, 0); 821 | } 822 | 823 | lastnl = true; 824 | if (gg_quotewin) { 825 | glk_window_clear(gg_quotewin); 826 | glk_set_window(gg_quotewin); 827 | lastnl = false; 828 | } 829 | 830 | VM_Style(BLOCKQUOTE_VMSTY); 831 | for (ix=0 : ix(ix+1); 833 | if (ix < lines-1 || lastnl) new_line; 834 | } 835 | VM_Style(NORMAL_VMSTY); 836 | 837 | if (gg_quotewin) glk_set_window(gg_mainwin); 838 | ]; 839 | -) instead of "Quotation Boxes" in "Glulx.i6t". 840 | 841 | Include (- 842 | #Ifdef DEBUG; 843 | [ GlkListSub id val; 844 | if (is_fyrevm) { 845 | print "Glk is not used with this interpreter.^"; 846 | return; 847 | } 848 | id = glk_window_iterate(0, gg_arguments); 849 | while (id) { 850 | print "Window ", id, " (", gg_arguments-->0, "): "; 851 | val = glk_window_get_type(id); 852 | switch (val) { 853 | 1: print "pair"; 854 | 2: print "blank"; 855 | 3: print "textbuffer"; 856 | 4: print "textgrid"; 857 | 5: print "graphics"; 858 | default: print "unknown"; 859 | } 860 | val = glk_window_get_parent(id); 861 | if (val) print ", parent is window ", val; 862 | else print ", no parent (root)"; 863 | val = glk_window_get_stream(id); 864 | print ", stream ", val; 865 | val = glk_window_get_echo_stream(id); 866 | if (val) print ", echo stream ", val; 867 | print "^"; 868 | id = glk_window_iterate(id, gg_arguments); 869 | } 870 | id = glk_stream_iterate(0, gg_arguments); 871 | while (id) { 872 | print "Stream ", id, " (", gg_arguments-->0, ")^"; 873 | id = glk_stream_iterate(id, gg_arguments); 874 | } 875 | id = glk_fileref_iterate(0, gg_arguments); 876 | while (id) { 877 | print "Fileref ", id, " (", gg_arguments-->0, ")^"; 878 | id = glk_fileref_iterate(id, gg_arguments); 879 | } 880 | if (glk_gestalt(gestalt_Sound, 0)) { 881 | id = glk_schannel_iterate(0, gg_arguments); 882 | while (id) { 883 | print "Soundchannel ", id, " (", gg_arguments-->0, ")^"; 884 | id = glk_schannel_iterate(id, gg_arguments); 885 | } 886 | } 887 | ]; 888 | 889 | Verb meta 'glklist' 890 | * -> Glklist; 891 | #Endif; 892 | -) instead of "GlkList Command" in "Glulx.i6t". 893 | 894 | Include (- 895 | [ QUIT_THE_GAME_R; 896 | if (actor ~= player) rfalse; 897 | if (is_fyrevm) FyreCall(FY_CHANNEL, FYC_PROMPT); 898 | if ((actor == player) && (untouchable_silence == false)) 899 | QUIT_THE_GAME_RM('A'); 900 | if (is_fyrevm) FyreCall(FY_CHANNEL, FYC_MAIN); 901 | if (YesOrNo()~=0) quit; 902 | ]; 903 | -) instead of "Quit The Game Rule" in "Glulx.i6t". 904 | 905 | Include (- 906 | [ RESTART_THE_GAME_R; 907 | if (actor ~= player) rfalse; 908 | if (is_fyrevm) FyreCall(FY_CHANNEL, FYC_PROMPT); 909 | RESTART_THE_GAME_RM('A'); 910 | if (is_fyrevm) FyreCall(FY_CHANNEL, FYC_MAIN); 911 | if (YesOrNo()~=0) { 912 | @restart; 913 | RESTART_THE_GAME_RM('B'); new_line; 914 | } 915 | ]; 916 | -) instead of "Restart The Game Rule" in "Glulx.i6t". 917 | 918 | Include (- 919 | [ RESTORE_THE_GAME_R res fref; 920 | if (actor ~= player) rfalse; 921 | if (is_fyrevm) { 922 | @restore 0 res; 923 | } else { 924 | fref = glk_fileref_create_by_prompt($01, $02, 0); 925 | if (fref == 0) jump RFailed; 926 | gg_savestr = glk_stream_open_file(fref, $02, GG_SAVESTR_ROCK); 927 | glk_fileref_destroy(fref); 928 | if (gg_savestr == 0) jump RFailed; 929 | @restore gg_savestr res; 930 | glk_stream_close(gg_savestr, 0); 931 | gg_savestr = 0; 932 | } 933 | .RFailed; 934 | RESTORE_THE_GAME_RM('A'); new_line; 935 | ]; 936 | -) instead of "Restore The Game Rule" in "Glulx.i6t". 937 | 938 | Include (- 939 | [ SAVE_THE_GAME_R res fref; 940 | if (actor ~= player) rfalse; 941 | if (is_fyrevm) { 942 | @save 0 res; 943 | if (res == -1) { 944 | ! The player actually just typed "restore". We're going to print 945 | ! GL__M(##Restore,2); the Z-Code Inform library does this correctly 946 | ! now. But first, we have to recover all the Glk objects; the values 947 | ! in our global variables are all wrong. 948 | GGRecoverObjects(); 949 | RESTORE_THE_GAME_RM('B'); new_line; 950 | rtrue; 951 | } 952 | } else { 953 | fref = glk_fileref_create_by_prompt($01, $01, 0); 954 | if (fref == 0) jump SFailed; 955 | gg_savestr = glk_stream_open_file(fref, $01, GG_SAVESTR_ROCK); 956 | glk_fileref_destroy(fref); 957 | if (gg_savestr == 0) jump SFailed; 958 | @save gg_savestr res; 959 | if (res == -1) { 960 | ! The player actually just typed "restore". We first have to recover 961 | ! all the Glk objects; the values in our global variables are all wrong. 962 | GGRecoverObjects(); 963 | glk_stream_close(gg_savestr, 0); ! stream_close 964 | gg_savestr = 0; 965 | RESTORE_THE_GAME_RM('B'); new_line; 966 | rtrue; 967 | } 968 | glk_stream_close(gg_savestr, 0); ! stream_close 969 | gg_savestr = 0; 970 | } 971 | if (res == 0) { SAVE_THE_GAME_RM('B'); new_line; rtrue; } 972 | .SFailed; 973 | SAVE_THE_GAME_RM('A'); new_line; 974 | ]; 975 | -) instead of "Save The Game Rule" in "Glulx.i6t". 976 | 977 | Include (- 978 | [ SWITCH_TRANSCRIPT_ON_R; 979 | if (actor ~= player) rfalse; 980 | if (is_fyrevm) { 981 | print "Transcripting is not available with this interpreter.^"; 982 | return; 983 | } 984 | if (gg_scriptstr ~= 0) { SWITCH_TRANSCRIPT_ON_RM('A'); new_line; rtrue; } 985 | if (gg_scriptfref == 0) { 986 | gg_scriptfref = glk_fileref_create_by_prompt($102, $05, GG_SCRIPTFREF_ROCK); 987 | if (gg_scriptfref == 0) jump S1Failed; 988 | } 989 | ! stream_open_file 990 | gg_scriptstr = glk_stream_open_file(gg_scriptfref, $05, GG_SCRIPTSTR_ROCK); 991 | if (gg_scriptstr == 0) jump S1Failed; 992 | glk_window_set_echo_stream(gg_mainwin, gg_scriptstr); 993 | SWITCH_TRANSCRIPT_ON_RM('B'); new_line; 994 | VersionSub(); 995 | return; 996 | .S1Failed; 997 | SWITCH_TRANSCRIPT_ON_RM('C'); new_line; 998 | ]; 999 | -) instead of "Switch Transcript On Rule" in "Glulx.i6t". 1000 | 1001 | Include (- 1002 | [ SWITCH_TRANSCRIPT_OFF_R; 1003 | if (actor ~= player) rfalse; 1004 | if (is_fyrevm) { 1005 | print "Transcripting is not available with this interpreter.^"; 1006 | return; 1007 | } 1008 | if (gg_scriptstr == 0) { SWITCH_TRANSCRIPT_OFF_RM('A'); new_line; rtrue; } 1009 | SWITCH_TRANSCRIPT_OFF_RM('B'); new_line; 1010 | glk_stream_close(gg_scriptstr, 0); ! stream_close 1011 | gg_scriptstr = 0; 1012 | ]; 1013 | -) instead of "Switch Transcript Off Rule" in "Glulx.i6t". 1014 | 1015 | Section 2 - Printing segment 1016 | 1017 | Include (- 1018 | [ PrintPrompt i; 1019 | if (is_fyrevm) { 1020 | FyreCall(FY_CHANNEL, FYC_PROMPT); 1021 | TEXT_TY_Say( (+ command prompt +) ); 1022 | FyreCall(FY_CHANNEL, FYC_MAIN); 1023 | } else { 1024 | RunTimeProblemShow(); 1025 | ClearRTP(); 1026 | style roman; 1027 | EnsureBreakBeforePrompt(); 1028 | TEXT_TY_Say( (+ command prompt +) ); 1029 | } 1030 | ClearBoxedText(); 1031 | ClearParagraphing(14); 1032 | ]; 1033 | -) instead of "Prompt" in "Printing.i6t". 1034 | 1035 | Include (- 1036 | #Ifdef TARGET_ZCODE; 1037 | #Iftrue (#version_number == 6); 1038 | [ DrawStatusLine; Z6_DrawStatusLine(); ]; 1039 | #Endif; 1040 | #Endif; 1041 | 1042 | #Ifndef DrawStatusLine; 1043 | [ DrawStatusLine width posb; 1044 | @push say__p; @push say__pc; 1045 | if (is_fyrevm) { 1046 | BeginActivity(CONSTRUCTING_STATUS_LINE_ACT); 1047 | ClearParagraphing(); 1048 | if (ForActivity(CONSTRUCTING_STATUS_LINE_ACT) == false) { 1049 | FyreCall(FY_CHANNEL, FYC_LOCATION); 1050 | SL_Location(); 1051 | 1052 | #ifndef NO_SCORE; 1053 | FyreCall(FY_CHANNEL, FYC_SCORE); 1054 | print sline1; 1055 | #endif; 1056 | 1057 | FyreCall(FY_CHANNEL, FYC_TIME); 1058 | print the_time; 1059 | 1060 | FyreCall(FY_CHANNEL, FYC_TURN); 1061 | print turns; 1062 | } 1063 | ClearParagraphing(); 1064 | FyreCall(FY_CHANNEL, FYC_MAIN); 1065 | EndActivity(CONSTRUCTING_STATUS_LINE_ACT); 1066 | } else { 1067 | BeginActivity(CONSTRUCTING_STATUS_LINE_ACT); 1068 | VM_StatusLineHeight(1); VM_MoveCursorInStatusLine(1, 1); 1069 | width = VM_ScreenWidth(); posb = width-15; 1070 | spaces width; 1071 | ClearParagraphing(); 1072 | if (ForActivity(CONSTRUCTING_STATUS_LINE_ACT) == false) { 1073 | VM_MoveCursorInStatusLine(1, 2); 1074 | switch(metaclass(left_hand_status_line)) { 1075 | String: print (string) left_hand_status_line; 1076 | Routine: left_hand_status_line(); 1077 | } 1078 | VM_MoveCursorInStatusLine(1, posb); 1079 | switch(metaclass(right_hand_status_line)) { 1080 | String: print (string) right_hand_status_line; 1081 | Routine: right_hand_status_line(); 1082 | } 1083 | } 1084 | VM_MoveCursorInStatusLine(1, 1); VM_MainWindow(); 1085 | ClearParagraphing(); 1086 | EndActivity(CONSTRUCTING_STATUS_LINE_ACT); 1087 | } 1088 | @pull say__pc; @pull say__p; 1089 | ]; 1090 | #Endif; 1091 | -) instead of "Status Line" in "Printing.i6t". 1092 | 1093 | Section 4 - Parser segment 1094 | 1095 | Include (- 1096 | [ YesOrNo i j; 1097 | for (::) { 1098 | #Ifdef TARGET_ZCODE; 1099 | if (location == nothing || parent(player) == nothing) read buffer parse; 1100 | else read buffer parse DrawStatusLine; 1101 | j = parse->1; 1102 | #Ifnot; ! TARGET_GLULX; 1103 | if (location ~= nothing && parent(player) ~= nothing) DrawStatusLine(); 1104 | KeyboardPrimitive(buffer, parse); 1105 | j = parse-->0; 1106 | #Endif; ! TARGET_ 1107 | if (j) { ! at least one word entered 1108 | i = parse-->1; 1109 | if (i == YES1__WD or YES2__WD or YES3__WD) rtrue; 1110 | if (i == NO1__WD or NO2__WD or NO3__WD) rfalse; 1111 | } 1112 | #ifdef TARGET_GLULX; if (is_fyrevm) FyreCall(FY_CHANNEL, FYC_PROMPT); #endif; 1113 | YES_OR_NO_QUESTION_INTERNAL_RM('A'); print "> "; 1114 | #ifdef TARGET_GLULX; if (is_fyrevm) FyreCall(FY_CHANNEL, FYC_MAIN); #endif; 1115 | } 1116 | ]; 1117 | 1118 | [ YES_OR_NO_QUESTION_INTERNAL_R; ]; 1119 | 1120 | -) instead of "Yes/No Questions" in "Parser.i6t". 1121 | 1122 | Section 5 - Text segment 1123 | 1124 | Include (- 1125 | #ifnot; ! TARGET_ZCODE 1126 | [ TEXT_TY_CastPrimitive to_txt from_snippet from_value 1127 | len i stream saved_stream news buffer buffer_size memory_to_free results; 1128 | 1129 | if (to_txt == 0) BlkValueError("no destination for cast"); 1130 | 1131 | buffer_size = (TEXT_TY_BufferSize + 2)*WORDSIZE; 1132 | 1133 | RawBufferSize = TEXT_TY_BufferSize; 1134 | buffer = RawBufferAddress + TEXT_TY_CastPrimitiveNesting*buffer_size; 1135 | TEXT_TY_CastPrimitiveNesting++; 1136 | if (TEXT_TY_CastPrimitiveNesting > TEXT_TY_NoBuffers) { 1137 | buffer = VM_AllocateMemory(buffer_size); memory_to_free = buffer; 1138 | if (buffer == 0) 1139 | FlexError("ran out with too many simultaneous text conversions"); 1140 | } 1141 | 1142 | if (unicode_gestalt_ok) { 1143 | SuspendRTP(); 1144 | .RetryWithLargerBuffer; 1145 | if (is_fyrevm) { 1146 | OpenOutputBufferUnicode(buffer, RawBufferSize); 1147 | } else { 1148 | saved_stream = glk_stream_get_current(); 1149 | stream = glk_stream_open_memory_uni(buffer, RawBufferSize, filemode_Write, 0); 1150 | glk_stream_set_current(stream); 1151 | } 1152 | 1153 | @push say__p; @push say__pc; 1154 | ClearParagraphing(7); 1155 | if (from_snippet) print (PrintSnippet) from_value; 1156 | else print (PrintI6Text) from_value; 1157 | @pull say__pc; @pull say__p; 1158 | 1159 | results = buffer + buffer_size - 2*WORDSIZE; 1160 | if (is_fyrevm) { 1161 | CloseOutputBuffer(results); 1162 | } else { 1163 | glk_stream_close(stream, results); 1164 | if (saved_stream) glk_stream_set_current(saved_stream); 1165 | } 1166 | ResumeRTP(); 1167 | 1168 | len = results-->1; 1169 | if (len > RawBufferSize-1) { 1170 | ! Glulx had to truncate text output because the buffer ran out: 1171 | ! len is the number of characters which it tried to print 1172 | news = RawBufferSize; 1173 | while (news < len) news=news*2; 1174 | i = VM_AllocateMemory(news*WORDSIZE); 1175 | if (i ~= 0) { 1176 | if (memory_to_free) VM_FreeMemory(memory_to_free); 1177 | memory_to_free = i; 1178 | buffer = i; 1179 | RawBufferSize = news; 1180 | buffer_size = (RawBufferSize + 2)*WORDSIZE; 1181 | jump RetryWithLargerBuffer; 1182 | } 1183 | ! Memory allocation refused: all we can do is to truncate the text 1184 | len = RawBufferSize-1; 1185 | } 1186 | buffer-->(len) = 0; 1187 | 1188 | TEXT_TY_CastPrimitiveNesting--; 1189 | BlkValueMassCopyFromArray(to_txt, buffer, 4, len+1); 1190 | } else { 1191 | RunTimeProblem(RTP_NOGLULXUNICODE); 1192 | } 1193 | if (memory_to_free) VM_FreeMemory(memory_to_free); 1194 | ]; 1195 | #endif; 1196 | -) instead of "Glulx Version" in "Text.i6t". 1197 | 1198 | Section 5 - Death 1199 | 1200 | Include (- 1201 | [ PRINT_OBITUARY_HEADLINE_R; 1202 | #ifdef TARGET_GLULX; if (is_fyrevm) FyreCall(FY_CHANNEL, FYC_DEATH); #endif; 1203 | print "^^ "; 1204 | VM_Style(ALERT_VMSTY); 1205 | print "***"; 1206 | if (deadflag == 1) PRINT_OBITUARY_HEADLINE_RM('A'); 1207 | if (deadflag == 2) PRINT_OBITUARY_HEADLINE_RM('B'); 1208 | if (deadflag == 3) PRINT_OBITUARY_HEADLINE_RM('C'); 1209 | if (deadflag ~= 0 or 1 or 2 or 3) { 1210 | print " "; 1211 | TEXT_TY_Say(deadflag); 1212 | print " "; 1213 | } 1214 | print "***"; 1215 | VM_Style(NORMAL_VMSTY); 1216 | print "^^"; #Ifndef NO_SCORING; print "^"; #Endif; 1217 | #ifdef TARGET_GLULX; if (is_fyrevm) FyreCall(FY_CHANNEL, FYC_MAIN); #endif; 1218 | rfalse; 1219 | ]; 1220 | -) instead of "Print Obituary Headline Rule" in "OrderOfPlay.i6t". 1221 | 1222 | [ Remove newlines at beginning of story ] 1223 | 1224 | Include 1225 | (- 1226 | [ VIRTUAL_MACHINE_STARTUP_R; 1227 | CarryOutActivity(STARTING_VIRTUAL_MACHINE_ACT); 1228 | VM_Initialise(); 1229 | rfalse; 1230 | ]; 1231 | -) instead of "Virtual Machine Startup Rule" in "OrderOfPlay.i6t". 1232 | 1233 | [ Story Info Definitions ] 1234 | 1235 | To say story serial number: (- PrintSerialNumber(); -). 1236 | 1237 | Include (- 1238 | [ PrintSerialNumber i; 1239 | for (i=0 : i<6 : i++) print (char) ROM_GAMESERIAL->i; 1240 | ]; 1241 | -). 1242 | 1243 | To say I7 version number: (- print (PrintI6Text) NI_BUILD_COUNT; -). 1244 | To say I6 version number: (- print inversion; -); 1245 | To say I7 library number: (- print (PrintI6Text) LibRelease; -); 1246 | To say I6 library number: (- inversion; -); 1247 | To say strict mode: (- CheckStrictMode(); -); 1248 | 1249 | Include (- 1250 | [ CheckStrictMode; 1251 | #Ifdef STRICT_MODE; 1252 | print "S"; 1253 | #Endif; ! STRICT_MODE; 1254 | ]; 1255 | -). 1256 | 1257 | To say debug mode: (- CheckDebugMode(); -); 1258 | 1259 | Include (- 1260 | [ CheckDebugMode; 1261 | #Ifdef DEBUG; 1262 | print "D"; 1263 | #Endif; ! DEBUG; 1264 | ]; 1265 | -). 1266 | 1267 | Section 6 - Score Notification 1268 | 1269 | Include (- 1270 | [ NotifyTheScore d; 1271 | #Iftrue USE_SCORING ~= 0; 1272 | if (notify_mode == 1) { 1273 | if (is_fyrevm) { 1274 | d = score-last_score; 1275 | FyreCall(FY_CHANNEL, FYC_SCORENOTIFY); 1276 | print d; 1277 | FyreCall(FY_CHANNEL, FYC_MAIN); 1278 | } else { 1279 | DivideParagraphPoint(); 1280 | VM_Style(NOTE_VMSTY); 1281 | d = score-last_score; 1282 | if (d > 0) { ANNOUNCE_SCORE_RM('D', d); } 1283 | else if (d < 0) { ANNOUNCE_SCORE_RM('E', -d); } 1284 | new_line; 1285 | VM_Style(NORMAL_VMSTY); 1286 | } 1287 | } 1288 | #Endif; 1289 | ]; 1290 | -) instead of "Score Notification" in "Printing.i6t". 1291 | 1292 | Section 7 - IFID 1293 | 1294 | Include (- 1295 | [ print_ifid ix; 1296 | for (ix=6: ix <= UUID_ARRAY->0: ix++) print (char) UUID_ARRAY->ix; 1297 | ]; 1298 | -). 1299 | 1300 | To print the Raw IFID: (- print_ifid(); -). 1301 | 1302 | To print the IFID: 1303 | select the ifid channel; 1304 | print the Raw IFID; 1305 | select the main channel. 1306 | 1307 | When play begins when outputting channels: 1308 | print the IFID. 1309 | 1310 | Chapter 3 - Standard Rules replacements 1311 | 1312 | This is the direct the final prompt to the prompt channel rule: 1313 | select the prompt channel; 1314 | follow the print the final prompt rule; 1315 | select the main channel. 1316 | 1317 | The direct the final prompt to the prompt channel rule is listed instead of the print the final prompt rule in before handling the final question. 1318 | 1319 | The room description heading rule is not listed in the carry out looking rules. 1320 | 1321 | To decide whether outputting channels: (- (is_fyrevm) -); 1322 | 1323 | Chapter 4 - Channel Rules 1324 | 1325 | Section 0 - Dynamic new channels 1326 | 1327 | To decide which number is the next channel id: 1328 | (- GetNextId() -). 1329 | 1330 | A channel is a kind of thing. A channel has a number called id. A channel has some text called content name. A channel has some text called content type. 1331 | 1332 | main-channel is a channel with id 1296124238 and content name "mainContent" and content type "text". 1333 | prompt-channel is a channel with id 1347571796 and content name "prompt" and content type "text". 1334 | location-channel is a channel with id 1280262990 and content name "locationName" and content type "text". 1335 | score-channel is a channel with id 1396920146 and content name "score" and content type "number". 1336 | time-channel is a channel with id 1414090053 and content name "time" and content type "text". 1337 | death-channel is a channel with id 1145389380 and content name "deathContent" and content type "text". 1338 | endgame-channel is a channel with id 1162757191 and content name "endGameContent" and content type "text". 1339 | turn-channel is a channel with id 1414877774 and content name "turn" and content type "number". 1340 | storyInfo-channel is a channel with id 1229866575 and content name "storyInfo" and content type "json". 1341 | scoreNotify-channel is a channel with id 1397641044 and content name "scoreNotify" and content type "text". 1342 | contentManagement-channel is a channel with id 1129138004 and content name "contentTypes" and content type "json". 1343 | ifid-channel is a channel with id 1229343044 and content name "ifid" and content type "text". 1344 | 1345 | When play begins while outputting channels (this is the content management rule): 1346 | select the content management channel; 1347 | say "[bracket]"; 1348 | let cmrow be a number; 1349 | let cmrow be 0; 1350 | repeat with C running through channels: 1351 | if cmrow is greater than 0: 1352 | say ","; 1353 | let id be the id of C; 1354 | if id is 0: 1355 | now the id of C is the next channel id; 1356 | say "{ 'id': [id of C], 'contentType': '[content type of C]', 'contentName': '[content name of C]'}"; 1357 | now cmrow is cmrow + 1; 1358 | say "[close bracket]"; 1359 | 1360 | The content management rule is listed first in the when play begins rules. 1361 | 1362 | To say on (C - channel) -- beginning say_channel -- running on: select C. 1363 | To say end -- ending say_channel -- running on: select the main channel. 1364 | 1365 | To select (chan - channel): 1366 | let id be the id of chan; 1367 | set channelid to id. 1368 | 1369 | To set channelid to (id - number): 1370 | (- SET_CHANNEL({id}); -). 1371 | 1372 | To Select the Content Management Channel: 1373 | select contentManagement-channel. 1374 | 1375 | Section 1a - Required Channels 1376 | 1377 | To Select the Main Channel: 1378 | select main-channel. 1379 | 1380 | To Select the Prompt Channel: 1381 | select prompt-channel. 1382 | 1383 | To Change the Prompt to (T - text): 1384 | Select the Prompt Channel; 1385 | say T; 1386 | Select the Main Channel. 1387 | 1388 | To Select the Location Channel: 1389 | select location-channel. 1390 | 1391 | To Select the Score Channel: 1392 | select score-channel. 1393 | 1394 | To Select the Time Channel: 1395 | select time-channel. 1396 | 1397 | To Select the Death Channel: 1398 | select death-channel. 1399 | 1400 | To Select the Turn Channel: 1401 | select turn-channel. 1402 | 1403 | To Select the Story Info Channel: 1404 | select storyInfo-channel. 1405 | 1406 | To Select the Score Notification Channel: 1407 | select scoreNotify-channel. 1408 | 1409 | To Select the IFID Channel: 1410 | select the ifid-channel. 1411 | 1412 | Chapter 5 - Transition Requested 1413 | 1414 | To Request Transition: 1415 | (- if (is_fyrevm) FyreCall(FY_REQUEST_TRANSITION); -); 1416 | 1417 | Chapter 6 - Story Info 1418 | 1419 | When play begins while outputting channels: 1420 | write story info. 1421 | 1422 | Every turn while outputting channels: 1423 | write story info. 1424 | 1425 | To write story info: 1426 | say "[on storyInfo-channel]{ [quotation mark]storyTitle[quotation mark]: [quotation mark][story title][quotation mark], [quotation mark]storyHeadline[quotation mark]: [quotation mark][story headline][quotation mark], [quotation mark]storyAuthor[quotation mark]: [quotation mark][story author][quotation mark], [quotation mark]storyCreationYear[quotation mark]: [quotation mark][story creation year][quotation mark], [quotation mark]releaseNumber[quotation mark]: [quotation mark][release number][quotation mark], [quotation mark]serialNumber[quotation mark]: [quotation mark][story serial number][quotation mark], [quotation mark]inform7Build[quotation mark]: [quotation mark][I7 version number][quotation mark], [quotation mark]inform6Library[quotation mark]: [quotation mark][I6 library number][quotation mark], [quotation mark]inform7Library[quotation mark]: [quotation mark][I7 library number][quotation mark], [quotation mark]strictMode[quotation mark]: [quotation mark][strict mode][quotation mark], [quotation mark]debugMode[quotation mark]: [quotation mark][debug mode][quotation mark] }[end]". 1427 | 1428 | Chapter 7 - Miscellany 1429 | 1430 | Include (- 1431 | 1432 | Constant SUPPRESS_DEATH_MESSAGE = 1; 1433 | Constant SUPPRESS_DEATH_SCORE = 1; 1434 | 1435 | -) before "Language.i6t". 1436 | 1437 | FyreVM Core ends here. 1438 | 1439 | ---- DOCUMENTATION ---- 1440 | 1441 | FyreVM Core is the base extension for Glulx+Channel IO web applications. 1442 | 1443 | The Channels system provides text communication from the engine to the user interface. The first six channels (main, prompt, location, score, time, and death) are required and are adapted from the standard outputs of Inform 7. There are several default channels added for convenience. These include: title, credits, prologue, turn, hint, help, error, and version. 1444 | 1445 | The author can add their own channel with one step: 1446 | 1447 | my-channel is a channel with content name "myChannelContent" and content type "text". 1448 | 1449 | 2. To use your channel: 1450 | 1451 | say "[on my-channel]This text will appear in your new channel.[end]"; 1452 | 1453 | 3. Alternate use: 1454 | 1455 | select my-channel; 1456 | say "This text will appear in your new channel."; 1457 | select main-channel. 1458 | 1459 | Standard Channels: 1460 | 1461 | main-channel - The main channel is meant to handle the regular text window output. 1462 | 1463 | prompt-channel - The prompt channel defaults to the common ">" caret, but can be altered to be anything. 1464 | 1465 | location-channel - The location channel contains the current location name. 1466 | 1467 | score-channel - The score channel contains, if any is provided, the current score of the game. 1468 | 1469 | time-channel - The time channel contains the current number of turns or the current time. 1470 | 1471 | death-channel - The death channel contains any output that happens after the player dies. This is separated from the main text so that the UI can handle it contextually. 1472 | 1473 | turn-channel - The turn channel has the turn count, regardless if time is the primary timekeeping method. 1474 | 1475 | storyInfo-channel - The story info channel contains JSON with all of the banner and general story information. 1476 | 1477 | scoreNotify-channel - When scoring is used and the score changes, the change value (up or down) will be posted to this channel. 1478 | 1479 | -------------------------------------------------------------------------------- /extensions/David Cornelson/FyreVM Prologue.i7x: -------------------------------------------------------------------------------- 1 | FyreVM Prologue by David Cornelson begins here. 2 | 3 | prologue-channel is a channel with content name "prologueContent" and content type "text". 4 | 5 | To select the prologue channel: 6 | select prologue-channel. 7 | 8 | FyreVM Prologue ends here. 9 | 10 | ---- DOCUMENTATION ---- 11 | 12 | FyreVM Prologue adds a prologue channel giving the author a method to write prologue text. 13 | 14 | The author should add the following code to their game file: 15 | 16 | When play begins (this is the prologue channel rule): 17 | select the prologue channel; 18 | say "This is the prologue."; 19 | select the main channel. 20 | -------------------------------------------------------------------------------- /extensions/David Cornelson/FyreVM Simple Web Hints.i7x: -------------------------------------------------------------------------------- 1 | FyreVM Simple Web Hints by David Cornelson begins here. 2 | 3 | Include FyreVM Core by David Cornelson. 4 | 5 | Table of Hint Categories 6 | visible catid title 7 | -- -- -- 8 | 9 | Table of Hint Questions 10 | visible catid qid description 11 | -- -- -- -- 12 | 13 | Table of Hints 14 | catid qid hid description 15 | -- -- -- -- 16 | 17 | hint-channel is a channel with content name "hintContent" and content type "json". 18 | 19 | To select the hint channel: 20 | select hint-channel; 21 | 22 | When play begins when outputting channels: 23 | emit hints. 24 | 25 | Every turn when outputting channels: 26 | emit hints. 27 | 28 | To emit hints: 29 | select the hint channel; 30 | say "[bracket]"; 31 | let crow be 0; 32 | repeat through the Table of Hint Categories: 33 | if visible entry is 1: 34 | let ccatid be the catid entry; 35 | if crow is greater than 0: 36 | say ","; 37 | say "{ 'id':[catid entry],'title': '[title entry]', 'questions': [bracket] "; 38 | let qrow be 0; 39 | repeat through the Table of Hint Questions: 40 | if visible entry is 1 and catid entry is ccatid: [ visible and in category only ] 41 | let qqid be qid entry; 42 | if qrow is greater than 0: 43 | say ","; 44 | say "{ 'id': [qid entry], 'question': '[description entry]', 'hints': [bracket] "; 45 | let hrow be 0; 46 | repeat through the Table of Hints: 47 | if catid entry is ccatid and qid entry is qqid: 48 | if hrow is greater than 0: 49 | say ","; 50 | say "{ 'id': [hid entry], 'description': '[description entry]' }"; 51 | now hrow is hrow + 1; 52 | say "[close bracket] }"; 53 | now qrow is qrow + 1; 54 | say "[close bracket] }"; 55 | now crow is crow + 1; 56 | say "[close bracket]"; 57 | select the main channel. 58 | 59 | FyreVM Simple Web Hints ends here. 60 | 61 | ---- DOCUMENTATION ---- 62 | 63 | FyreVM Simple Web Hints is an extension that allows the author to provide Invisiclue like hints to a standard FyreVM web template. 64 | 65 | The author needs to create the following tables with their own categories, questions, and hints. Note the visible flag on categories and questions allows the author to manipulate when those items are sent to the user interface. Also note that catid and qid are important for filtering the data properly and emitting the data to a channel. The hint id (hid) isn't used here, but will be used in the user interface for ordering purposes. 66 | 67 | Example: * FyreVM Hints for Cloak of Darkness - An example of of simple web hints for the standard FyreVM template. 68 | 69 | *: "FyreVM Hints for Cloak of Darkness" 70 | 71 | Include FyreVM Core by David Cornelson. 72 | Include FyreVM Simple Web Hints by David Cornelson. 73 | 74 | The story headline is "Hints for Cloak of Darkness". 75 | The story creation year is 2016. 76 | 77 | The Cloak Room is a room. 78 | 79 | Table of Hint Categories (continued) 80 | visible catid title 81 | 1 1 "Chapter 1 - In the beginning..." 82 | 1 2 "Chapter 2 - In the light..."; 83 | 84 | Table of Hint Questions (continued) 85 | visible catid qid description 86 | 1 1 1 "What do I do with this cloak?" 87 | 1 1 2 "Why is it dark in here?" 88 | 1 2 1 "What is the message on the floor?" 89 | 90 | Table of Hints (continued) 91 | catid qid hid description 92 | 1 1 1 "Maybe it belongs somewhere." 93 | 1 1 2 "If there were only somewhere to hang it." 94 | 1 1 3 "Hang it on the hook." 95 | 1 2 1 "Do you have a light source?" 96 | 1 2 2 "The light switch is somewhere." 97 | 1 2 3 "The hook in the cloakroom is interesting." 98 | 1 2 4 "After you hang up the cloak, things will get brighter!" 99 | 2 1 1 "Don't go in the bar before hanging up the cloak." 100 | 101 | when play begins: 102 | say "This is the JSON hint data that would be sent on the hint channel in a FyreVM web application.[paragraph break]"; 103 | emit hints; 104 | say "[paragraph break]"; 105 | -------------------------------------------------------------------------------- /extensions/David Cornelson/FyreVM Status Line.i7x: -------------------------------------------------------------------------------- 1 | Version 1/022317 of FyreVM Status Line by David Cornelson begins here. 2 | 3 | Section 1 - Defining the parts of the status line 4 | 5 | The Status Line is a thing. 6 | The Status Line has some text called Location Name. 7 | The Status Line has some text called Location Addendum. 8 | The Status Line has some text called Story Time. 9 | The Status Line has some text called Story Score. 10 | The Status Line has a number called Story Turn. 11 | The Status Line has a truth state called Show Location Name. The Show Location Name of The Status Line is true. 12 | The Status Line has a truth state called Show Location Addendum. The Show Location Addendum of The Status Line is false. 13 | The Status Line has a truth state called Show Story Time. The Show Story Time of The Status Line is true. 14 | The Status Line has a truth state called Show Story Score. The Show Story Time of The Status Line is true. 15 | The Status Line has a truth state called Show Story Turn. The Show Story Time of The Status Line is true. 16 | 17 | Section 2 - Status Line channel for defining the status line details 18 | 19 | statusline-channel is a channel with content name "statusLineContent" and content type "json". 20 | 21 | To define status line: 22 | say "[on statusline-channel]{ 'showLocationName': [Show Location Name of The Status Line], 'showLocationAddendum': [Show Location Addendum of The Status Line], 'showStoryTime': [Show Story TIme of The Status Line], 'showStoryScore': [Show Story Score of The Status Line], 'showStoryTurn': [Show Story Turn of The Status Line] }[end]"; 23 | 24 | To show the location name: 25 | now show location name of the status line is true. 26 | 27 | To hide the location name: 28 | now show location name of the status line is false. 29 | 30 | To show the location addendum: 31 | now show location addendum of the status line is true. 32 | 33 | To hide the location addendum: 34 | now show location addendum of the status line is false. 35 | 36 | To show the story time: 37 | now show story time of the status line is true. 38 | 39 | To hide the story time: 40 | now show story time of the status line is false. 41 | 42 | To show the story score: 43 | now show story score of the status line is true. 44 | 45 | To hide the story score: 46 | now show story score of the status line is false. 47 | 48 | To show the story turn: 49 | now show story turn of the status line is true. 50 | 51 | To hide the story turn: 52 | now show story turn of the status line is false. 53 | 54 | When play begins when outputting channels: 55 | define status line. 56 | 57 | Every turn when outputting channels: 58 | define status line. 59 | 60 | FyreVM Status Line ends here. 61 | 62 | ---- DOCUMENTATION ---- 63 | 64 | FyreVM Status Line defines which parts of the standard status line are displayed. 65 | 66 | These parts include: 67 | - location name 68 | - location addendum 69 | - story time 70 | - story score 71 | - story turn 72 | 73 | The author can show/hide any of these with the following statements: 74 | 75 | show the location name. 76 | 77 | hide the location name. 78 | 79 | show the location addendum. 80 | 81 | hide the location addendum. 82 | 83 | show the story time. 84 | 85 | hide the story time. 86 | 87 | show the story score. 88 | 89 | hide the story score. 90 | 91 | show the story turn. 92 | 93 | hide the story turn. 94 | 95 | The statusline-channel will emit the details in JSON format to be handled by the UI template. 96 | 97 | Example: 98 | { "showLocationName": true, "showLocationAddendum": false, "showStoryTime": true, "showStoryScore": false, "showStoryTurn": false } 99 | 100 | This data will be contained in fyrevm.statusLineContent in the browser. -------------------------------------------------------------------------------- /extensions/David Cornelson/FyreVM Text Styles.i7x: -------------------------------------------------------------------------------- 1 | FyreVM Text Styles by David Cornelson begins here. 2 | 3 | ui-style is a kind of thing. 4 | 5 | [We can load up font-families with an order of font family names.] 6 | 7 | link-command is a kind of thing. a link-command has some text called command. 8 | 9 | to say ui (U - ui-style) -- beginning say_font -- running on: 10 | if outputting channels: 11 | say ""; 12 | otherwise: 13 | say ""; 14 | 15 | to say /ui -- ending say_font -- running on: 16 | if outputting channels: 17 | say ""; 18 | otherwise: 19 | say ""; 20 | 21 | to say b -- beginning say_bold -- running on: 22 | say "". 23 | 24 | to say /b -- ending say_bold -- running on: 25 | say "". 26 | 27 | to say i -- beginning say_italics -- running on: 28 | say "". 29 | 30 | to say /i -- ending say_italics -- running on: 31 | say "". 32 | 33 | to say em -- beginning say_emphasis -- running on: 34 | say "". 35 | 36 | to say /em -- ending say_emphasis -- running on: 37 | say "". 38 | 39 | to say strong -- beginning say_strong -- running on: 40 | say "". 41 | 42 | to say /strong -- ending say_strong -- running on: 43 | say "". 44 | 45 | [ Common commands ] 46 | 47 | go-north is a link-command with command "north". 48 | go-northeast is a link-command with command "northeast". 49 | go-east is a link-command with command "east". 50 | go-southeast is a link-command with command "southeast". 51 | go-south is a link-command with command "south". 52 | go-southwest is a link-command with command "southwest". 53 | go-west is a link-command with command "west". 54 | go-northwest is a link-command with command "northwest". 55 | go-up is a link-command with command "up". 56 | go-down is a link-command with command "down". 57 | go-in is a link-command with command "in". 58 | go-out is a link-command with command "out". 59 | 60 | do-inventory is a link-command with command "inventory". 61 | 62 | to say cmdlink (LC - link-command) -- beginning say_link -- running on: 63 | say ""; 64 | 65 | to say /cmdlink -- ending say_link -- running on: 66 | say ""; 67 | 68 | FyreVM Text Styles ends here. 69 | 70 | ---- DOCUMENTATION ---- 71 | 72 | FyreVM Text Styles provides standard font and text styling. There are complex styles and inline styles. 73 | 74 | Complex styles: 75 | 76 | main-style is a ui-style. the color of main-style is Black. the font family of main-style is "Poirot+One". The font-source of main-style is google. the weight of main-style is normal. 77 | mono-style is a ui-style. the color of mono-style is Blue. the font family of mono-style is "Cousine". The font-source of mono-style is google. the weight of mono-style is normal. 78 | 79 | say "[ui main-style]This text will be set in the defined main style.[/ui]"; 80 | 81 | Simple styles: 82 | 83 | go-north is a link-command with the command "go north". 84 | 85 | say "[b]This is bold text.[/b]"; 86 | say "[i]This is italics text.[/i]"; 87 | say "[em]This is empasized text.[/em]"; 88 | say "[strong]This is strong text.[/strong]"; 89 | say "[cmdlink go-north]north[/cmdlink]"; 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fyrevm-web-templates", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "babel-jest": "^18.0.0", 7 | "babel-preset-es2015": "^6.18.0", 8 | "babel-preset-react": "^6.16.0", 9 | "gulp": "^3.9.1", 10 | "jest": "^18.1.0", 11 | "react-scripts": "0.8.1", 12 | "react-test-renderer": "^15.4.2", 13 | "semantic-ui": "^2.2.7", 14 | "react": "^15.4.1", 15 | "react-dom": "^15.4.1", 16 | "semantic-ui-react": "^0.64.4" 17 | }, 18 | "optionalDependencies": { 19 | "fsevents": "*" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "jest", 25 | "eject": "react-scripts eject" 26 | }, 27 | "homepage": "http://plover.net/~dave/fyrevm-web/standard" 28 | } 29 | -------------------------------------------------------------------------------- /projects/Cloak of Darkness.inform/Source/story.ni: -------------------------------------------------------------------------------- 1 | "Cloak of Darkness" by The IF Community 2 | 3 | Include FyreVM Core by David Cornelson. 4 | Include FyreVM Banner Output by David Cornelson. 5 | Include FyreVM Prologue by David Cornelson. 6 | Include FyreVM Text Styles by David Cornelson. 7 | Include FyreVM Status Line by David Cornelson. 8 | 9 | The story headline is "A basic IF demonstration using FyreVM". 10 | The story creation year is 2016. 11 | 12 | p-style is a ui-style. 13 | 14 | When play begins when outputting channels (this is the prologue channel rule): 15 | say "[on prologue-channel][ui p-style]Hurrying through the rainswept November night, you're glad to see the bright lights of the Opera House. It's surprising that there aren't more people about but, hey, what do you expect in a cheap demo game...?[/ui][paragraph break][end]". 16 | 17 | When play begins when not outputting channels (this is the normal prologue rule): 18 | say "Hurrying through the rainswept November night, you're glad to see the bright lights of the Opera House. It's surprising that there aren't more people about but, hey, what do you expect in a cheap demo game...?[paragraph break]". 19 | 20 | The prologue channel rule is listed last in the when play begins rules. 21 | 22 | Use scoring. 23 | 24 | The maximum score is 2. 25 | 26 | [Whatever room we define first becomes the starting room of the game, 27 | in the absence of other instructions:] 28 | 29 | Foyer of the Opera House is a room. "You are standing in a spacious hall, splendidly decorated in [em]red and gold[/em], with glittering chandeliers overhead. The entrance from the street is to the north, and there are doorways [cmdlink go-south]south[/cmdlink] and [cmdlink go-west]west[/cmdlink]." 30 | 31 | Instead of going north in the Foyer, say "You've only just arrived, and besides, the weather outside seems to be getting worse." 32 | 33 | [We can add more rooms by specifying their relation to the first room. 34 | Unless we say otherwise, the connection will automatically be bidirectional, 35 | so "The Cloakroom is west of the Foyer" will also mean "The Foyer is east of the Cloakroom":] 36 | 37 | The Cloakroom is west of the Foyer. 38 | "The walls of this small room were clearly once lined with hooks, though now only one remains. The exit is a door to the [cmdlink go-east]east[/cmdlink]." 39 | 40 | In the Cloakroom is a supporter called the small brass hook. 41 | The hook is scenery. Understand "peg" as the hook. 42 | 43 | [Inform will automatically understand any words in the object definition 44 | ("small", "brass", and "hook", in this case), but we can add extra synonyms 45 | with this sort of Understand command.] 46 | 47 | The description of the hook is "It's just a small brass hook, [if something is on the hook]with [a list of things on the hook] 48 | hanging on it[otherwise]screwed to the wall[end if]." 49 | 50 | [This description is general enough that, if we were to add other hangable items 51 | to the game, they would automatically be described correctly as well.] 52 | 53 | The Bar is south of the Foyer. The printed name of the bar is "Foyer Bar". 54 | The Bar is dark. "The bar, much rougher than you'd have guessed after the opulence of the foyer to the north, is completely empty. 55 | There seems to be some sort of message scrawled in the sawdust on the floor." 56 | 57 | The scrawled message is scenery in the Bar. Understand "floor" or "sawdust" as the message. 58 | 59 | Neatness is a kind of value. The neatnesses are neat, scuffed, and trampled. 60 | The message has a neatness. The message is neat. 61 | 62 | [We could if we wished use a number to indicate how many times 63 | the player has stepped on the message, but Inform also makes it easy 64 | to add descriptive properties of this sort, so that the code remains readable 65 | even when the reader does not know what "the number of the message" might mean.] 66 | 67 | Instead of examining the message: 68 | increase score by 1; 69 | say "The message, neatly marked in the sawdust, reads..."; 70 | end the story finally. 71 | 72 | [This second rule takes precedence over the first one whenever the message is trampled. 73 | Inform automatically applies whichever rule is most specific:] 74 | 75 | Instead of examining the trampled message: 76 | say "The message has been carelessly trampled, making it difficult to read. 77 | You can just distinguish the words..."; 78 | end the story saying "You have lost". 79 | 80 | [This command advances the state of the message from neat to scuffed 81 | and from scuffed to trampled. We can define any kinds of value we like 82 | and advance or decrease them in this way:] 83 | 84 | Instead of doing something other than going in the bar when in darkness: 85 | if the neatness of the message is not trampled: 86 | if the neatness of the message is neat: 87 | now the neatness of the message is scuffed; 88 | if the neatness of the message is scuffed: 89 | now the neatness of the message is trampled; 90 | say "In the dark? You could easily disturb something." 91 | 92 | Instead of going nowhere from the bar when in darkness: 93 | now the message is trampled; 94 | say "Blundering around in the dark isn't a good idea!" 95 | 96 | [This defines an object which is worn at the start of play. 97 | Because we have said the player is wearing the item, Inform infers that it is clothing 98 | and can be taken off and put on again at will.] 99 | 100 | The player wears a velvet cloak. The cloak can be hung or unhung. 101 | Understand "dark" or "black" or "satin" as the cloak. 102 | The description of the cloak is "A handsome cloak, of velvet trimmed with satin, and slightly splattered with raindrops. 103 | Its blackness is so deep that it almost seems to suck light from the room." 104 | 105 | Carry out taking the cloak: 106 | now the bar is dark. 107 | 108 | Carry out putting the unhung cloak on something in the cloakroom: 109 | now the cloak is hung; 110 | increase score by 1. 111 | 112 | Carry out putting the cloak on something in the cloakroom: 113 | now the bar is lit. 114 | 115 | Carry out dropping the cloak in the cloakroom: 116 | now the bar is lit. 117 | 118 | Instead of dropping or putting the cloak on when the player is not in the cloakroom: 119 | say "This isn't the best place to leave a smart cloak lying around." 120 | 121 | Understand "hang [something preferably held] on [something]" as putting it on. 122 | 123 | Test me with "s / n / w / inventory / hang cloak on hook / e / s / read message". 124 | 125 | -------------------------------------------------------------------------------- /public/Cloak of Darkness.ulx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChicagoDave/fyrevmweb-react/e30173afc014856a27051cf8169d5b37e6445ecd/public/Cloak of Darkness.ulx -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChicagoDave/fyrevmweb-react/e30173afc014856a27051cf8169d5b37e6445ecd/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | FyreVM Prototype 17 | 18 | 19 |
20 | 30 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /semantic.json: -------------------------------------------------------------------------------- 1 | { 2 | "base": "semantic", 3 | "paths": { 4 | "source": { 5 | "config": "src/theme.config", 6 | "definitions": "src/definitions/", 7 | "site": "src/site/", 8 | "themes": "src/themes/" 9 | }, 10 | "output": { 11 | "packaged": "dist/", 12 | "uncompressed": "dist/components/", 13 | "compressed": "dist/components/", 14 | "themes": "dist/themes/" 15 | }, 16 | "clean": "dist/" 17 | }, 18 | "permission": false, 19 | "autoInstall": true, 20 | "rtl": false, 21 | "version": "2.2.9" 22 | } -------------------------------------------------------------------------------- /src/FyreVMWeb/FyreVMMem.ts: -------------------------------------------------------------------------------- 1 | /* 2 | FyreVMMem.ts 3 | Main Module to run FyreVM engine (glulx-typescript) in-memory. 4 | Exposes OutputReady event for web page to get data. 5 | Exposes sendCommand function for web pages to execute command. 6 | */ 7 | 8 | /// 9 | 10 | module FyreVMMem { 11 | 12 | export class Manager { 13 | 14 | FyreVMData: {}; 15 | ChannelData: FyreVM.ChannelData; 16 | SessionData: FyreVM.Quetzal; 17 | EngineState: number; 18 | 19 | private wrapper: FyreVM.EngineWrapper; 20 | private saveKey: string; 21 | private quetzalData: FyreVM.Quetzal; 22 | private contentDefinition: string[]; 23 | 24 | public LoadStory(storyFile: ArrayBuffer) { 25 | this.wrapper = FyreVM.EngineWrapper.loadFromArrayBuffer(storyFile, true); 26 | this.ProcessCommand(this.wrapper.run()); 27 | this.FyreVMData['storyFile'] = storyFile; 28 | this.FyreVMData['quetzalData'] = this.wrapper.saveGame(); 29 | return this.FyreVMData; 30 | } 31 | 32 | public ProcessCommand(result: FyreVM.EngineWrapperState) { 33 | this.ChannelData = result.channelData; 34 | this.UpdateContent(); 35 | } 36 | 37 | private GetChannelName(x:number){ 38 | return String.fromCharCode( 39 | x >> 24, 40 | (x >> 16) & 0xFF, 41 | (x >> 8) & 0xFF, 42 | x & 0xFF); 43 | } 44 | 45 | private UpdateContent() { 46 | if (this.ChannelData["CMGT"] != undefined || this.contentDefinition != undefined) { 47 | 48 | // 49 | // We only get the content definition on first turn... (may need to revisit this) 50 | // 51 | if (this.contentDefinition == undefined) { 52 | this.contentDefinition = JSON.parse(this.ChannelData["CMGT"]); 53 | } 54 | 55 | this.FyreVMData = {}; 56 | 57 | for (var channelName in this.ChannelData) { 58 | 59 | for (var ch=0; ch < this.contentDefinition.length; ch++) { 60 | var channelDef = this.contentDefinition[ch]; 61 | var chanName = this.GetChannelName(channelDef['id']); 62 | if (chanName == channelName) { 63 | 64 | switch (channelDef['contentType']) { 65 | case "text": 66 | this.FyreVMData[channelDef['contentName']] = this.ChannelData[channelName]; 67 | break; 68 | case "number": 69 | this.FyreVMData[channelDef['contentName']] = Number(this.ChannelData[channelName]); 70 | break; 71 | case "json": 72 | this.FyreVMData[channelDef['contentName']] = JSON.parse(this.ChannelData[channelName]); 73 | break; 74 | case "css": 75 | this.FyreVMData[channelDef['contentName']] = this.ChannelData[channelName]; 76 | break; 77 | } 78 | 79 | break; 80 | } 81 | } 82 | } 83 | 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/FyreVMWeb/FyreVMWeb.ts: -------------------------------------------------------------------------------- 1 | /* 2 | FyreVMWeb.ts 3 | 4 | Main Module to run FyreVM web engine (glulx-typescript). 5 | 6 | Exposes OutputReady event for web page to get data. 7 | Exposes sendCommand function for web pages to execute command. 8 | 9 | */ 10 | 11 | /// 12 | 13 | var fyrevm = {}; 14 | 15 | function isEnterKey(e) { 16 | return e.code == "Enter"; 17 | } 18 | 19 | module FyreVMWeb { 20 | 21 | export enum States { 22 | INIT, 23 | RESTORE_SESSION, 24 | NEW_SESSION, 25 | WAITING_FOR_KEY, 26 | WAITING_FOR_LINE, 27 | COMMAND, 28 | SAVE, 29 | UPDATE_SESSION 30 | } 31 | 32 | export enum StoryStatus { 33 | CONTINUE, 34 | ENDED 35 | } 36 | 37 | export class Manager { 38 | 39 | ChannelData: FyreVM.ChannelData; 40 | State: States; 41 | Status: StoryStatus; 42 | OutputReady: () => void; 43 | InputElement: HTMLInputElement; 44 | 45 | private wrapper: FyreVM.EngineWrapper; 46 | private contentDefinition: string[]; 47 | private ifid: string; 48 | 49 | private SetState(state: States) { 50 | this.State = state; 51 | 52 | switch(state) { 53 | case States.INIT: 54 | this.Init(); 55 | break; 56 | case States.NEW_SESSION: 57 | this.NewSession(); 58 | break; 59 | case States.RESTORE_SESSION: 60 | this.RestoreSession(); 61 | break; 62 | case States.WAITING_FOR_LINE: 63 | break; 64 | case States.COMMAND: 65 | this.SendCommand(this.InputElement.value); 66 | break; 67 | case States.SAVE: 68 | this.SaveGame(); 69 | break; 70 | } 71 | } 72 | 73 | public LoadStory(url: string) { 74 | if (this.InputElement == undefined) { 75 | throw "FyreVM.Manager.InputElement must be defined before loading a story."; 76 | } 77 | 78 | this.InputElement.onkeypress = (e)=> { 79 | if (this.State == States.WAITING_FOR_KEY) { 80 | this.SetState(States.COMMAND); 81 | } else if (this.State == States.WAITING_FOR_LINE) { 82 | if (e.keyCode == 13 || isEnterKey(e)) { 83 | this.SetState(States.COMMAND); 84 | } 85 | } 86 | }; 87 | 88 | var reader = new XMLHttpRequest(); 89 | reader.open('GET', url); 90 | reader.responseType = 'arraybuffer'; 91 | reader.onreadystatechange = () => { 92 | if (reader.readyState === XMLHttpRequest.DONE) { 93 | this.wrapper = FyreVM.EngineWrapper.loadFromArrayBuffer(reader.response, true); 94 | let run = this.wrapper.run(); 95 | this.ProcessResult(run); 96 | this.SetState(States.INIT); 97 | } 98 | } 99 | reader.send() 100 | } 101 | 102 | private GetSaveData() { 103 | let saveKey = this.SaveKey(); 104 | let saveData = localStorage[saveKey]; 105 | if (saveData) { saveData = JSON.parse(saveData); } 106 | return saveData; 107 | } 108 | 109 | private Init() { 110 | let saveData = this.GetSaveData(); 111 | 112 | if (!saveData) { 113 | this.SetState(States.NEW_SESSION); 114 | } else { 115 | this.SetState(States.RESTORE_SESSION); 116 | } 117 | } 118 | 119 | private NewSession() { 120 | let saveKey = this.SaveKey(); 121 | localStorage[saveKey] = JSON.stringify(this.NewSaveGame()); 122 | this.UpdateTurnData(); 123 | this.OutputReady(); 124 | this.SetState(States.WAITING_FOR_LINE); 125 | } 126 | 127 | private RestoreSession() { 128 | let result = this.wrapper.receiveLine('restore'); 129 | if (result.state != FyreVM.EngineState.waitingForLoadSaveGame) { 130 | console.error('Error restoring saved game', result); 131 | } 132 | 133 | result = this.LoadSavedGame(); 134 | 135 | if (result.state != FyreVM.EngineState.waitingForLineInput) { 136 | console.error('Error restoring saved game', result); 137 | } 138 | 139 | this.SetState(States.WAITING_FOR_LINE); 140 | this.UpdateTurnData(); 141 | this.OutputReady(); 142 | } 143 | 144 | public ProcessResult(result: FyreVM.EngineWrapperState) { 145 | if (result.channelData) { 146 | this.ChannelData = result.channelData; 147 | } 148 | this.UpdateContent(); 149 | } 150 | 151 | private SendCommand(command: string) { 152 | this.UpdateCommand(command); 153 | console.log(command); 154 | let result = this.wrapper.receiveLine(command); 155 | this.ProcessCommand(result); 156 | } 157 | 158 | public ProcessCommand(result: FyreVM.EngineWrapperState) { 159 | this.Status = FyreVMWeb.StoryStatus.CONTINUE; 160 | 161 | this.ProcessResult(result); 162 | 163 | if (this.State == States.COMMAND) { 164 | this.UpdateStory(result); 165 | this.UpdateTurnData(); 166 | this.OutputReady(); 167 | } 168 | 169 | 170 | switch (result.state) { 171 | case FyreVM.EngineState.waitingForKeyInput: 172 | if (this.State == States.COMMAND) { this.SetState(States.SAVE); } 173 | else { this.SetState(States.WAITING_FOR_KEY); } 174 | break; 175 | case FyreVM.EngineState.waitingForLineInput: 176 | if (this.State == States.COMMAND) { this.SetState(States.SAVE); } 177 | else { this.SetState(States.WAITING_FOR_LINE); } 178 | break; 179 | case FyreVM.EngineState.completed: 180 | this.Status = FyreVMWeb.StoryStatus.ENDED; 181 | break; 182 | case FyreVM.EngineState.waitingForLoadSaveGame: 183 | this.LoadSavedGame(); 184 | break; 185 | case FyreVM.EngineState.waitingForGameSavedConfirmation: 186 | this.UpdateSavedGame(result); 187 | break; 188 | } 189 | } 190 | 191 | private NewSaveGame() { 192 | let storyInfo = JSON.parse(this.ChannelData['INFO']); 193 | let ifid = this.GetIFID(); 194 | let content = `Title: ${storyInfo['storyTitle']}
195 | Headline: ${storyInfo['storyHeadline']}`; 196 | 197 | return { 198 | story: { 199 | 'ifid': ifid, 200 | 'title': storyInfo['storyTitle'], 201 | 'storyInfo': storyInfo, 202 | 'storyFile': '' 203 | }, 204 | 'sessions': [ 205 | { 206 | 'session': 1, 207 | 'turns': 1, 208 | 'content': [ 209 | { 'turn': 1, 'command': '', 'content': content } 210 | ], 211 | 'data': [ 212 | ] 213 | } 214 | ] 215 | } 216 | } 217 | 218 | private SaveGame() { 219 | this.ProcessCommand(this.wrapper.receiveLine('save')); 220 | } 221 | 222 | private LoadSavedGame() { 223 | let saveData = this.GetSaveData(); 224 | let turns = saveData['sessions'][0]['turns']; 225 | let saveGameData = saveData['sessions'][0]['data'][turns]['data']; 226 | let quetzalData = FyreVM.Quetzal.load(Base64.toByteArray(saveGameData)); 227 | return this.wrapper.receiveSavedGame(quetzalData); 228 | } 229 | 230 | private UpdateSavedGame(result) { 231 | let saveKey = this.SaveKey(); 232 | let saveDataStr = localStorage[saveKey]; 233 | let saveData = null; 234 | 235 | if (!saveDataStr) { 236 | saveData = this.NewSaveGame(); 237 | } else { 238 | saveData = JSON.parse(saveDataStr); 239 | } 240 | 241 | let turns = saveData['sessions'][0]['turns']; 242 | saveData['sessions'][0]['data'][turns] = { 243 | 'turn': turns, 'data': result.gameBeingSaved.base64Encode() 244 | }; 245 | 246 | localStorage[saveKey] = JSON.stringify(saveData); 247 | 248 | this.ProcessCommand(this.wrapper.saveGameDone(true)); 249 | } 250 | 251 | private LoadSession() { 252 | let saveKey = this.SaveKey(); 253 | let saveData = localStorage[saveKey]; 254 | saveData = JSON.parse(saveData); 255 | let session = saveData['sessions'][0]; 256 | return session; 257 | } 258 | 259 | private SaveSession(session) { 260 | let saveKey = this.SaveKey(); 261 | let saveData = localStorage[saveKey]; 262 | saveData = JSON.parse(saveData); 263 | saveData['sessions'][0] = session; 264 | localStorage[saveKey] = JSON.stringify(saveData); 265 | } 266 | 267 | private UpdateCommand(command) { 268 | let session = this.LoadSession(); 269 | 270 | session['turns']++; 271 | 272 | session['content'].push({ 273 | turn: session['turns'], 274 | command: command, 275 | content: '' 276 | }); 277 | 278 | this.SaveSession(session); 279 | } 280 | 281 | private UpdateStory(result) { 282 | if (result.channelData && result.channelData['MAIN']) { 283 | let content = result.channelData['MAIN']; 284 | let session = this.LoadSession(); 285 | let turn = session['turns']; 286 | session['content'][turn-1]['content'] = content; 287 | this.SaveSession(session); 288 | } 289 | } 290 | 291 | private UpdateTurnData() { 292 | let saveKey = this.SaveKey(); 293 | let saveData = localStorage[saveKey]; 294 | if (!saveData) { return; } 295 | saveData = JSON.parse(saveData); 296 | let content = saveData['sessions'][0]['content']; 297 | fyrevm['content'] = content; 298 | } 299 | 300 | private SaveKey() { 301 | return this.GetIFID(); 302 | } 303 | 304 | private GetIFID() { 305 | if (!this.ifid) { 306 | this.ifid = this.ChannelData['IFID'].replace(/\//g, ''); 307 | } 308 | return this.ifid; 309 | } 310 | 311 | private GetChannelName(x:number){ 312 | return String.fromCharCode( 313 | x >> 24, 314 | (x >> 16) & 0xFF, 315 | (x >> 8) & 0xFF, 316 | x & 0xFF); 317 | } 318 | 319 | private UpdateContent() { 320 | if (this.ChannelData["CMGT"] != undefined || this.contentDefinition != undefined) { 321 | 322 | // 323 | // We only get the content definition on first turn... (may need to revisit this) 324 | // 325 | if (this.contentDefinition == undefined) { 326 | this.contentDefinition = JSON.parse(this.ChannelData["CMGT"]); 327 | } 328 | 329 | for (var channelName in this.ChannelData) { 330 | 331 | for (var ch=0; ch < this.contentDefinition.length; ch++) { 332 | var channelDef = this.contentDefinition[ch]; 333 | var chanName = this.GetChannelName(channelDef['id']); 334 | if (chanName == channelName) { 335 | 336 | switch (channelDef['contentType']) { 337 | case "text": 338 | fyrevm[channelDef['contentName']] = this.ChannelData[channelName]; 339 | break; 340 | case "number": 341 | fyrevm[channelDef['contentName']] = Number(this.ChannelData[channelName]); 342 | break; 343 | case "json": 344 | fyrevm[channelDef['contentName']] = JSON.parse(this.ChannelData[channelName]); 345 | break; 346 | case "css": 347 | fyrevm[channelDef['contentName']] = this.ChannelData[channelName]; 348 | break; 349 | } 350 | 351 | break; 352 | } 353 | } 354 | } 355 | } 356 | } 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/FyreVMWeb/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "out": "../../public/FyreVMWeb.js", 7 | "outDir": "../../public/" 8 | }, 9 | "files": [ 10 | "FyreVMWeb.ts" 11 | ], 12 | "exclude": [ 13 | "glulx-typescript/example" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/components/EmbeddedCommandLine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by david.cornelson on 12/29/2016. 3 | */ 4 | -------------------------------------------------------------------------------- /src/components/HelpStandard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by david.cornelson on 12/29/2016. 3 | * 4 | * Slide out panel from right taking up 30% of screen below menu 5 | * 6 | */ 7 | -------------------------------------------------------------------------------- /src/components/HintsStandard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by david.cornelson on 12/29/2016. 3 | * 4 | * Slide out panel from right taking up 30% of screen below menu 5 | * 6 | */ 7 | -------------------------------------------------------------------------------- /src/components/PagingContent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by david.cornelson on 12/29/2016. 3 | */ 4 | -------------------------------------------------------------------------------- /src/components/ScrollingContent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by david.cornelson on 12/29/2016. 3 | * 4 | * Generate all turns into scrolling content with at the bottom and 5 | * force scroll to bottom always. 6 | * 7 | * story.locationName should be placed bold before mainContent. 8 | * 9 | */ 10 | 11 | import React, { Component } from 'react'; 12 | import { Container, Input } from 'semantic-ui-react' 13 | 14 | export default class ScrollingContent extends Component { 15 | componentDidUpdate() { 16 | // Scroll content to the bottom when there's new output 17 | var element = document.getElementsByClassName('story-scroll')[0]; 18 | element.scrollTop = element.scrollHeight; 19 | } 20 | 21 | render() { 22 | var history = []; 23 | for (var i = 0; i < this.props.content.length; i++) { 24 | var input = this.props.content[i].command; 25 | var story = this.props.content[i].content; 26 | 27 | history.push(

{input}

); 28 | 29 | // Allow HTML in the story content 30 | story = { __html: story } 31 | history.push(

); 32 | } 33 | 34 | return ( 35 |

36 | 37 | {history} 38 | 39 |
40 | 41 | 42 | 43 |
44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/StandardMenu.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Dropdown, Menu } from 'semantic-ui-react' 3 | 4 | export default class StatusLine extends Component { 5 | state = {} 6 | 7 | handleItemClick = (e, { name }) => this.setState({ activeItem: name }) 8 | 9 | render() { 10 | const { activeItem } = this.state 11 | 12 | return ( 13 | 14 | FyreVM Prototype 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Help 23 | Story Help 24 | Story Hints 25 | About 26 | 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/StandardStatusLine.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container, Grid, Header, Label } from 'semantic-ui-react' 3 | 4 | export default class StatusLine extends Component { 5 | render() { 6 | return ( 7 | 8 |
{this.props.title}
9 | 10 | 11 |
{this.props.location}
12 |
13 | 14 | {this.props.score} 15 | 16 | 17 | {this.props.turn} 18 | 19 | 20 | {this.props.time} 21 | 22 |
23 |
24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/StoryTitle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by david.cornelson on 12/29/2016. 3 | */ 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Story from './templates/standard/Story'; 4 | import './templates/standard/index.css'; 5 | import '../semantic/dist/semantic.min.css'; 6 | 7 | ReactDOM.render( 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /src/semantic.json: -------------------------------------------------------------------------------- 1 | { 2 | "base": "semantic", 3 | "paths": { 4 | "source": { 5 | "config": "src/theme.config", 6 | "definitions": "src/definitions/", 7 | "site": "src/site/", 8 | "themes": "src/themes/" 9 | }, 10 | "output": { 11 | "packaged": "dist/", 12 | "uncompressed": "dist/components/", 13 | "compressed": "dist/components/", 14 | "themes": "dist/themes/" 15 | }, 16 | "clean": "dist/" 17 | }, 18 | "permission": false, 19 | "autoInstall": true, 20 | "rtl": false, 21 | "version": "2.2.7" 22 | } -------------------------------------------------------------------------------- /src/templates/standard/Story.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Grid } from 'semantic-ui-react' 3 | import StandardMenu from '../../components/StandardMenu.js' 4 | import StatusLine from '../../components/StandardStatusLine.js' 5 | import ScrollingContent from '../../components/ScrollingContent.js' 6 | 7 | class Story extends Component { 8 | constructor(props) { 9 | super(props); 10 | const FyreVMWeb = window.FyreVMWeb; 11 | this.fyrevm = new FyreVMWeb.Manager(); 12 | 13 | window.sendCommand = (value) => { 14 | this.fyrevm.InputElement.value = value; 15 | this.fyrevm.SetState(FyreVMWeb.States.COMMAND); 16 | } 17 | 18 | this.defaults = { 19 | score: 0, 20 | turn: 1, 21 | time: 0, 22 | storyInfo: { storyTitle: '' }, 23 | locationName: '', 24 | mainContent: 'Loading story', 25 | content: [] 26 | }; 27 | this.state = this.defaults; 28 | } 29 | 30 | outputReady() { 31 | const fyrevm = window.fyrevm; 32 | if (!fyrevm.mainContent) { 33 | this.setState(this.defaults); 34 | } else { 35 | this.setState(fyrevm); 36 | } 37 | this.fyrevm.InputElement.value = ''; 38 | } 39 | 40 | componentDidMount() { 41 | this.fyrevm.InputElement = document.getElementById('commandLine').firstChild; 42 | this.fyrevm.OutputReady = () => this.outputReady(); 43 | this.fyrevm.LoadStory(process.env.PUBLIC_URL + '/Cloak of Darkness.ulx'); 44 | } 45 | 46 | render() { 47 | return ( 48 |
49 | 50 | 51 | 52 | 53 | 59 |
60 | 62 |
63 |
64 |
65 | ); 66 | } 67 | } 68 | 69 | export default Story; 70 | -------------------------------------------------------------------------------- /src/templates/standard/Story.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Story from './Story'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /src/templates/standard/index.css: -------------------------------------------------------------------------------- 1 | #story { 2 | height: 90vh; 3 | } 4 | 5 | .story-content { 6 | display: flex; 7 | flex-flow: column; 8 | height: 75vh; 9 | } 10 | 11 | .story-info { 12 | flex: 0 1 auto; 13 | } 14 | 15 | .story-scroll { 16 | flex: 1 1 auto; 17 | overflow: scroll; 18 | outline: solid 1px lightgrey; 19 | } 20 | 21 | .story-input { 22 | flex: 0 1 auto; 23 | } 24 | --------------------------------------------------------------------------------