Serge
Members-
Posts
264 -
Joined
-
Last visited
-
Days Won
14
Content Type
Profiles
Forums
Events
Everything posted by Serge
-
For the Highland MIDI, we titled it "Phatt Island Wheel o' Fortune". I don't really think of it as a casino either - but it's historically been the name people used for that room in the game - not least because it was the "official" name during MI2's development (but worth remembering that names during development were generally kept as short as possible, partly due to the 8 character filename limitations of DOS). And since MI2 is one of the games that include the original room names in the game files, people likely found it there once upon a time. ð Monkey Island Heardle #290 ððĨðĐâŽïļâŽïļâŽïļ⎠#MonkeyIslandHeardle
-
Just another update of the Woodtick script: Script calls now use the proper types (i.e. room names rather than numbers when calling woodtick-music-control) - and inventory icons also got their proper names rather than numbers. ETA: Plus early work on sound-kludge/iMUSE
-
-
Me neither. Monkey Island Heardle #288 ððĐâŽïļâŽïļâŽïļâŽïļâŽïļ #MonkeyIslandHeardle
-
Yeah, but to manage expectations just a bit, Aric has been trying that for almost a decade. ð But yeah, I hope he succeeds at it. There's a lot of historically important stuff in there, and - outside of Aric's archive, which I'm pretty damn sure is well maintained and cared for - it's not guaranteed to last forever. A lot of stuff is still on backup tapes and floppy disks, and those get corrupted. And all of it is often very unstructured - random backups etc. Due to the various ways SCUMM built projects incrementally, and the way revisions were manually merged, developers may think they have a complete archive, although they're actually missing files or have old versions of them (of course, those old versions can, in themselves, be interesting from a history perspective). This new SCUMM Decompiler actually started with the goal of restoring a (thankfully) few corrupted and missing FLEM and BYLE files for one specific case. Fingers crossed. ð
-
Looking forward to that! So do I. ð The Woodtick script above has been updated with local variables and parameters named - a new decompiler feature. It still doesn't apply types to the arguments where the scripts are called - that's on the TODO list (meaning start-script bak woodtick-music-control 7 12 would turn into start-script bak woodtick-music-control woodtick inn) The only other major thing that's missing name and type annotation support now are the local variables (actually parameters) in verbs (ETA: oh, and labels, which I keep ignoring)
-
Yeah, SCUMM Revisited 5's decompiler was very hacky and quite unreliable. If I had the documentation and insight I have now, SCUMMRev in general would have looked very different (but probably also have been less fun to make). ð I'd like to eventually release the decompiler as open source, but right now it still has comments and annotations (like the ones used for the MI2 example above) which could be... troublemakers. ð It also decodes art to LBM (even in CMI where LBM wasn't used anymore), rooms to FLEM, and costumes to BYLE format (no support for CYST yet). But the latter obviously aren't very useful for the general public, unless the tools are somehow released one day. And I love that cannon replica! ð
-
I'll bite ðĪŠ Monkey Island Heardle #286 ððĐâŽïļâŽïļâŽïļâŽïļâŽïļ #MonkeyIslandHeardle
-
Looking good! ð Haven't seen it before - but then, haven't searched for SCUMM on github recently. ð This might or might not be helpful - the current output for MI2 Woodtick with annotations for variables, actors, objects, etc. - since that's something that's supported by both nutcracker and my decompiler, "notes can be compared" ð https://gist.github.com/Jither/077e9e90ad4d4f30127f2eddc3730fdb Can't show the original script, obviously, but can say what the decompiler is missing in that output: Note that by default, the decompiler outputs Unicode that remaps characters to what they would look like in Brief (codepage 437). Hence, bytecode 7 as seen in the dialog choices is output as a bullet. It doesn't yet recognize the type of bit-variables - e.g. housecall-happening = 0 would normally be written housecall-happening is true - fixed It doesn't yet name (or allow annotation at all) for local scripts and local variables (or script parameters) - hence, anywhere it says e.g. loc-0, in addition to not having a proper name, it also won't be able to properly type the content of that variable. For example, in woodtick-music-control, the first parameter, loc-0, is the previous room. and loc-1 is the new room. Hence, all the numbers they're compared to should also eventually be room names rather than numbers. ETA: Local scripts, variables and parameters are now named. As mentioned earlier, all the iMUSE commands are sound-kludge commands wrapped inside macros with proper names. Since the decompiler doesn't do "reverse macro expansion" yet, those look unwieldy. An alias for default in case statements is otherwise. The decompiler does allow to pick that in its settings. It doesn't yet declare local variables at the top of scripts (but your decompiler already does that ð) ETA: Now it does. I haven't annotated the possible values for e.g. graphics-mode - e.g. 19 is VGA. I also haven't annotated non-standard chores, since they'll mostly vary between costumes (and I haven't implemented a nice way to annotate chore-per-costume) For some reason, the "modern" (non-MM) version of class-of doesn't properly infer the return type (resulting in "134", rather than "LOCKED" - yes, the pegleg can be LOCKED ð) Another example of macro expansion - the set-dialog macro that I mentioned in another thread (and which is demonstrated in the VGHF interview with Ron), is this (set-dialog 1 "Then who keeps up the law and maintains order?"): foo = (120 + 1 - 1) verb foo at 0,dialog-ypos name "âĒThen who keeps up the law and maintains order?" on key dialog-key dialog-ypos += 8 foo -= 120 ++dialog-key ++dialog-lines say-screen-escape[foo] = 1 In this case, the wait-for-dialog macro is used in one case (after "Is this some sort of bribe situation?"). But not in the other (after "Then who keeps up the law...") Because using the macro in the latter case would require the animation resets for Largo and Guybrush to be written into each case. The macro doesn't allow "shared" code between choices. As mentioned in another thread, the jumps are how dialog was generally handled. Once again, the macro isn't "un-expanded". For some reason, my refactor code misses a nested if in one spot and keeps it as a jump. It may be due to the decompiler having a limit setting on various types of nested code. This because jumps in huge dialog trees can often be interpreted as a huge amount of nested loops - but shouldn't be - they were originally just jumps, and the nested loops actually make things less readable. ð The annotated names used in this output may or may not bear some resemblance to the names in the original script. The empty script-148 was actually additional code for the music, which was dropped before MI2 was released. Haven't looked closely at the Python code, but probably the best way to transform control flow without pulling too much of your hair out is to create an actual abstract syntax tree from the bytecode and transform that. ð But again, not sure if that's what you already do. One thing to be aware of when transforming flow is that jumps from one verb into another wasn't uncommon in early games. ð Not always ð - in all games before FT (as far as I recall), the indentation convention was 3 spaces, and dialog lines were indented to align exactly. As a fun addition to that: C-style multiline comments /* ... */ were also supported, but very rarely used. Another very rarely (if ever) used feature is that, in addition to decimal and hexadecimal (0x1a), it also supported binary (0b010), octal (0O137), and quaternary (i.e. base 4 - 0q103) numeric literals. ð
-
Just one more, to celebrate that I just got state annotations [ON/OFF vs OPEN/CLOSED vs HERE/GONE] mostly working - and because it shows off quite a bit of the more advanced features of early SCUMM. The Maniac Mansion boot script (including the demo): https://gist.github.com/Jither/bdb253854c6938675e9d525157e0b3fb Worth noting that the setup of the actors wasn't there in the C64 version - where the actors (and quite a few other things) were actually hardcoded. Yes, these bit array assignments: room-always-lit[44] = 1 ... should actually eventually have "44" replaced by the room name. That requires that I set up typed array items first. ð (now fixed)
-
To be sure, the meaning is never documented in actual C code for SCUMM or SPUTM. It's always just shortened to "rec" there, with "recursive" showing up in unrelated places where it fits. However, in any and all SCUMMlet documentation of the language - from around MI2 all the way to CMI, the flag is consistently referred to as being used to run the script "recursively". ð The flag (as well as the "bak" flag) was added for Last Crusade, as far as I recall - and to be fair, it may have originally been intended for a specific recursive script in that game. Even if the support for actual recursion is limited in SCUMM: Although it was just a measure for memory management and could be changed by just changing a constant and recompiling SCUMM and SPUTM, the maximum number of active scripts at a time was typically limited to 20-30 scripts - and multiple "rec" scripts would count towards that limit.
-
Yeah, I'm sure that Ron and Chip would originally have considered keeping actual globals separate from the rooms, but ended up with this for practical reasons. ð
-
A few more notes on the CMI example: The scripts that have numbers from 2000 and up are actually local scripts. Whichi is why they're defined inside the room definition (defining a script inside the room definition is how you would signify that a script was local - again, MM didn't have local scripts). There aren't any global scripts in the "treasure" room - hence, the room definition starts at the very top of the file. The strings wouldn't actually start with (e.g.) "/TRNZ365/" while writing. That would likely be added by the compiler or another tool before compiling. We all know that CMI really only had three verbs - those are verb-5, verb-6 and verb-7. Already from the very early days there was a special "verb" defining the default action if a verb didn't have one. And a fallback script that would be called if there wasn't a default action on the object. E.g. having Guybrush say "That doesn't seem to do anything". There was also a special verb for the "quick action". That one had a macro so that rather than writing something like verb get-verb { double-verb = open } for a door, you could just write quick-verb is open. Assigning an icon to an object in the inventory was also done with a special verb, once again the actual verb code was hidden inside a macro - so, icon is bucket-icon, rather than verb get-icon { icon-number = bucket-icon }. Similarly, in the CMI example, you'll see verbs like verb-216 { var-630[0] = "/PU_M048/climb through" } ... which defines the name for the "use" verb. That would likely, once again, be shortened by a macro, something like use-name is "climb through" - with a similar macro for the look verb (verb-217) and talk/eat verb (verb-218). In other words, the verb scripts were really an object-oriented-programming-like way to have "methods" on an object - not just for the actual verb actions. start-script got two flags, also there relatively early in SCUMM's evolution: bak for "background scripts" - while scripts would generally run until they were stopped, in a multi tasking way (break-here, wait-for-* etc. would allow the engine to interpret all the other scripts currently running) - that didn't apply to cut-scenes. Whenever a cut-scene was entered, all other scripts were paused - except for scripts started with bak. That would be stuff like the most common script example: The grandfather clock going "tick tock" - don't want that to stop when a cutscene is running. The other flag is rec, which stands for "recursive" but doesn't actually mean that... It's for scripts that should be allowed to run multiple copies at the same time. Not recursively, but in parallel. If you started a non-rec script that was already running, the original script would stop, and a new version of it would be started instead.
-
Well, it's not all that antiquated - it still makes sense. ð We're just dealing with larger files where some of the same principles apply - because they're large or because they need to accommodate being transferred over networks etc. So, for a video file, it might make sense on a superficial level to store the video in one block, then the audio, then subtitles, etc. But of course, all video containers - AVI, QuickTime/MP4, MKV etc. - interleave these things by some number of frames, so that the player doesn't have to read all over the files to put a frame and its sound together. Similarly, a lot of file formats might e.g. store some header information at the start and other information at the end (this goes for e.g. zip, although to be fair, that's also an "antiquated" format) - mostly because a lot of information about the file isn't actually known until you're done writing it - but you still want as much information as possible at the start, so that you can read parts of the file before you reach the end (if it's being transferred over a network, you might not even have the end when you need to start reading). OpenEXR, used for images mainly in the film industry (but also more and more outside) also has quite a few quirks that are made to avoid seeking in the file. Some parts of SCUMM show their age more - like garbage from old builds, because SCUMM by default only wrote the parts that changed since last build - meaning assets and offsets that were no longer in use might still be left in there. For example, there are a few indexes to assets from MI1 EGA in the VGA build, simply because the VGA build was branched out from the EGA build - those offsets just point into a random offset in the room, but since they aren't ever referenced in the game, it doesn't matter to the game, but does confuse tools that try to read all assets from the files. ð There are also bits of garbage leftover script at the end of some scripts, because offsets weren't changed unless needed - since that would require rewriting everything in the room following that script. Again, wouldn't matter to the game, because it would reach a "end of script" bytecode before reaching the garbage. But still, that also happens intentionally in some modern files - e.g. to avoid rewriting an entire video file just because title or whatever in the header got shorter. ð
-
Yeah, you can also see the principle in e.g. SCUMMRev: Here, each global script ends up in "SCRP". The costumes and sounds that the room script declares are in SOUN and COST. The local scripts - which are only loaded when the room is - are in the LSCR chunks, and "entry" and "exit" scripts in EXCD and ENCD. Object definitions end up in OBCD along with their verb scripts. But note that SCRP, SOUN and COST are actually "outside" the ROOM (but inside LFLF) - they're not "really" part of the room, but associated with it.
-
Nah, macros are gone (inlined into the script code) when the scripts are compiled - and currently, the decompiler doesn't reconstruct any macros, just leaves the inlined code as is. All the scripts in these very early games are global scripts (ETA: Outside the room definition - the verb scripts aren't global). Later, SCUMM would add local scripts which are automatically stopped when you exit a room. Any room can call a global script (you'll also see the example calling lots of scripts like give-package-to-weird-ed which aren't defined in this room). The guideline was (at least later) to define them in the room where they're most likely to be called (or which has also defined resources the script uses). Of course, you wouldn't want to place a single script in a room stored on disk 2 that's called from a room on disk 1. In this case, the room is "extra global" to the game - it's called from lots of places, depending on where a kid is when they die. But it does have the shared outcome of ending up outside the mansion, at the tombstones. This is a general principle in SCUMM, all the way up to CMI: There are global resources that are needed in many rooms, but they still "belong to" - and are stored - in (mostly) just one of them: The room itself, global scripts, costumes, sounds, character sets (in later games) - and to some extent objects. Although an object could only actually be visible in the room it belonged to - that's also why (when/if I get to releasing other scripts) you'd see that the hamster in Ted's cage is not actually the same hamster as in the microwave (so he really has no reason for getting so angry ð). In the end, like almost everything else, the compiler just turns these globals into a number in the scripts that reference it. And then it stores the disk and room number for each resource - and their offset within the room - in an index on each disk. That's the "00.lfl" file, or, in later games e.g. "tentacle.000". When a script somewhere needs one of these resources, it's just looks up in the index and loads the resource from the room where it's stored. The rest of the room isn't loaded, so it doesn't add any overhead, and it could theoretically have been stored anywhere on the disk. Assigning it to a room just gives you a bit of control over where on disk it's stored - a bit of micro management to reduce seeking during disk access.
-
Unfortunately not ð - as far as I recall, there are two publicly available SPUTM executables that were compiled in debug mode and with Windex support (windex being SCUMM/SPUTM's debug mode which allows debugging the game on a second, monochrome, monitor): The MI2 non-interactive demo, and the MI2 talkie prototype. Both include a lot of extra info in the EXE - function names etc. - and the Windex debuggers obviously bring a lot of insight into internal terminologies and jargon - the talkie will even output a disassembly of the current script using the original command names, with somewhat accurate command syntax, but obviously more in assembly form than in actual script form. BG has a walkthrough and screenshot of Windex in this excellent article: https://mixnmojo.com/features/sitefeatures/LucasArts-First-Words/2 ETA: "Somewhat accurate command syntax": The Windex debugger just shows the current byte code formatted into strings that - for the convenience of the SCUMMlet - reads somewhat like the original lines. Without a symbol file, much like the decompiler, it doesn't display variable names, actor names, label names, etc. And by the time a SCUMM game had been compiled: loops and conditionals have been turned into jumps ("goto"s) cutscene blocks have been turned into start/end commands break-until has been turned into a loop - and, in turn, into conditional jumps. some commands have been "unrolled" - for example, break-here 17 would turn into simply 17 break-here's in a row if ([some arithmetic]) would have the arithmetic moved out before the if statement: If statements in the final byte code only check a single value in a variable or - from DOTT and on - a value on the stack that is calculated before the if. Those calculations are themselves stack-based, so you wouldn't see a nice if (actor-x guybrush >= actor-x elaine - 15). Some commands had multiple nice ways of being called, where parts could be left out (similar to overloaded functions in several languages) - the compiler - and Windex - would turn those into a single command with various constants indicating "default" added. Windex had no knowledge of macros, so e.g. all iMUSE commands would be listed as sound-kludge with a long list obscure numeric parameters. That's some of the stuff the decompiler "undoes".
-
Just a bit of "behind the scenes". This is how the annotations for the decompiler look. The syntax is largely based on the syntax of SCUMM's actual "define" files, which are mostly just lists of names with a number assigned. "Largely based" - other than the braces (and annotations after the numeric value), which are added because the decompiler needs a bit more context than a compiler does: To the compiler, a name maps to a single numeric value. But to a decompiler, a numeric value can map to several different names - rooms, actors, objects, variables, states, classes, named variable values... and e.g. an actor number can map to several different actor names. For example, Kate and Governor Phatt have the same actor number in MI2. This would eventually be solved, by e.g. having an annotation like "governor-phatt = 5 when room is phatt-mansion" https://gist.github.com/Jither/c9b416aea9c5ac8ee9684500d5005289
-
Just testing the waters, for now. The full MM decompile might show up at some point. Not sure when I'll have time to annotate the larger games with actor names, object names, room names etc. - or add the missing features that mostly have to do with "intelligently" figuring out what aliases to use - some aren't straightforward to figure out programmatically - like indirection for state-of, where it actually needs to follow the assignments in order to figure out if the object in question might be a door or a switch or another kind of object - and then pick e.g. "CLOSED" or "OFF" or "HERE" (or R-GONE) depending on context. Also want to add an easy way to define "reverse macros" - so that command sequences that are known to be a result of macro expansion are turned back into macro calls. Oh, the thing about HERE and R-GONE (and GONE and R-HERE). The objects' states also control whether (and later what) object images are displayed. Hence why "OFF" = "HERE": The convention in SCUMM was to draw objects onto the background, with the object image actually being an image of the object removed - i.e. the actual background behind the object. Hence, the default, "off", state (0) would be "HERE", and the "on" state would be "GONE". When artists didn't follow that convention, you used R-HERE and R-GONE - with R standing for "Reversed" - and e.g. R-GONE being = HERE. In order to have script readability without changing the art. But just to demonstrate how the syntax and commands changed relatively little until CMI (even though DOTT introduced a completely rewritten compiler and interpreter) - here's a bit from CMI. Without annotations naming all the variables, actors, costumes, objects etc., it becomes a fair bit less easy to read, though. It also shows a good example of macro expansion - the bits with "Too many ambient sounds" warnings messages were clearly originally a much shorter macro call, expanded on compilation: https://gist.github.com/Jither/fa3d05100e4106a125abffb3a6760249 ETA: The same thing goes for kludge commands inthere - although the decompiler could already give them their proper names, at the time the scripts were written, they were "commands in training" and were actually named by wrapping them inside a macro (so that's what it's intended that the decompiler eventually does too - use the "reverse macro expansion" to name them). iMUSE also had its own set of "sound kludge" commands wrapped in macros. It was never promoted to first class citizen in SCUMM. Heck, its macros even lived in a file named SOUNDCRP.SCU... ð ETA 2: Other syntax-related tidbits that I didn't mention above (but there are so many that could be mentioned): start-script was mostly optional - you could just write play-cricket-sound rather than start-script play-cricket-sound, although the longer form was also allowed. is, are and == (and =) were all synonyms - as were is-not and != - the decompiler tries to choose the output form much like SCUMMlets would - if it's comparing to or assigning a number, use =, otherwise, is. The "intelligent alias" stuff would also eventually allow e.g. object names etc. to be annotated as plural, so that it can choose are when appropriate. a bit more technical: in case anyone with some parser knowledge is wondering about the infamously unconventional (for modern languages) use of the hyphen as both a valid identifier character and subtraction operator, the way it worked was simply that any hyphen between other identifier characters would be parsed as an identifier - spaces were required to make e.g. a - b a subtraction rather than an identifier (a-b). For the same reason, SCUMM only had prefix syntax for decrement (and increment) - --x - rather than x--, which would be parsed as an identifier. The hyphen wasn't valid as the first character of an identifier (e.g. -selected-number) - in that case, it would be parsed as a subtraction or negation. ETA3: Oh yes, I forgot (it's more than a year since I last actually worked on the decompiler) - automatic naming of rooms, objects etc. based on the info in the game files was off - the CMI example is a bit more readable now.
-
Thought someone might find this interesting. The broader public finally got a proper view of actual SCUMM in the Video Game History Foundation's brilliant interview with Ron a couple of years ago. But for a bit of a wider glance, here's an example of what the script for a full room would look like. This is output from a project I started on a couple of years ago, which is currently in hiatus. Based on a lot of research and archaeology. First and foremost - this is not an original script (as should be somewhat evident, if you read it). I've never seen an original script from Maniac Mansion. This is reconstructed from the game files. It is, however, 100% actual SCUMM syntax. Possibly with some minor unknown differences compared to an actual 1987 SCUMM script - the syntax is accurate to SCUMM circa 1990-1991 - that is, the SCUMM compiler for MI1 or MI2 would accept it as proper SCUMM. The actual syntax never changed much between Maniac Mansion on C64 to CMI. And the commands, other than additions, rarely changed either, at least from Maniac Mansion for PC and on. Other than that, the script is from Maniac Mansion (obviously) - the PC version. Although my "decompiler" actually handles all games from MM C64 to CMI, there were some early hacks in C64 that it doesn't quite like yet, and which makes the MM C64 output slightly less readable - most notably that objects and actors shared references, meaning it has a harder time naming objects and actors where they overlap. Stuff worth noting: A few variables, objects, scripts, and defines haven't been named by me. Yes, those names are all mine, but based on how variables etc. would typically be named in actual game scripts. I've only done these "annotations" for Maniac Mansion so far. Even the way indentation was done is reconstructed by the tool - 3 spaces were used, but lines of dialog were mostly always indented to align perfectly. Obviously one of the most interesting things - comments - cannot be reconstructed from game files. ð The decompiler doesn't yet handle cases where names have aliases. For example, "OFF", "CLOSED", "HERE" (yes) and "R-GONE" are all aliases for the same thing - SCUMMlets used whatever was appropriate in the context (or they'd be chastised by Ron or Tim ð). Here, I've manually renamed most of those. And a few additional line breaks. There's no AI involved ð, so the decompiler doesn't actually know where code could appropriately be split into sections. Some of the commands may actually be "expanded" code that was originally macros. Yes, the cases of "jump" commands jumping into different blocks were also actual common SCUMM practice. Other interesting things about this particular script: Around halfways down, the definition of the room starts. This is the standard (and required) layout in SCUMM - room scripts followed by the room definition - sounds, enter/exit scripts, and object definitions including their verb scripts. Since this is the PC version, while the play-cricket-sound script is still there, it doesn't actually do anything. The "userface" command is one of the few that I have no idea how actually looked. "wait-for-message" is one of the additions compared to the C64 version - where the equivalent SCUMM code would be "break-until (message-going is false)" - although it might already have been turned into a "wait-for-message" macro in the original game. Starting with the MM PC version, it turned into a native command. Enough talk... script play-cricket-sound { do { sleep-for 100 minutes } } script kill-a-kid { break-here cut-scene { if (cause-of-death is radioactive-steam) { say-line "Oh no! Radioactive steam! Ahheeeeeeee^" do { do-animation selected-actor turn-left break-here do-animation selected-actor turn-front break-here do-animation selected-actor turn-right break-here do-animation selected-actor turn-back break-here x = random 70 y = random 12 y += 56 walk selected-actor to x,y } until (message-going is false) who-dies is selected-actor } if (cause-of-death is drowning-in-pool) { current-room pool camera-at 41 sleep-for 2 seconds print-line "Glug! Glug! Glug! Glug!" wait-for-message sleep-for 3 seconds } if (cause-of-death is ed-seeing-hamster) { stop-script weird-ed-chases-kid wait-for-message say-line weird-ed "Wait! What IS that?" wait-for-message say-line weird-ed "It has bits of fur like my hamster's!" wait-for-message say-line weird-ed "Oh no!!! What did you do!!! Argh!!!" wait-for-message who-dies is selected-actor put-actor weird-ed at 10,52 in-room weird-eds-room } if (cause-of-death is flesh-eating-plant) { who-dies is selected-actor current-room plant-room camera-at 28 sleep-for 2 seconds print-line "YUM!!" sleep-for 3 seconds } if (cause-of-death is death-by-tentacle) { wait-for-message who-dies is selected-actor } actor who-dies costume costume-0 kid-is-busy[who-dies] = 1 foo = 253 do { if (owner-of foo is who-dies) { owner-of foo is nuked } foo -= 1 } until (foo == 0) ++number-of-dead-kids if (who-dies is dave) { dave-is-dead is true } if (number-of-dead-kids == 1) { state-of tombstone-1 is HERE class-of tombstone-1 is TOUCHABLE put-actor who-dies at 106,64 in-room mansion-exterior } if (number-of-dead-kids == 2) { state-of tombstone-2 is HERE class-of tombstone-2 is TOUCHABLE put-actor who-dies at 114,64 in-room mansion-exterior } if (number-of-dead-kids == 3) { state-of tombstone-3 is HERE class-of tombstone-3 is TOUCHABLE put-actor who-dies at 122,64 in-room mansion-exterior } foo = actor-x who-dies current-room mansion-exterior camera-at foo sleep-for 6 seconds } if (number-of-dead-kids == 3) { start-script game-over } userface 1 unfreeze-scripts cursor-on sentence inventory verbs } script open-film { if (develop-film-step is film-opened) { say-line "It's already opened." } if (develop-film-step is film-closed) { say-line "Ok, it's opened." wait-for-message do { foo is owner-of film if (foo == 0) { stop-script } bar is actor-room foo if (room-is-dark[bar] == 0) { if (state-of circuit-breakers is OFF) { if (selected-room is bar) { say-line "I think I just exposed the film." } develop-film-step is film-opened } } sleep-for 15 seconds } until (develop-film-step is-not film-closed) } } script deliver-envelope { sleep-for 1 minute do { sleep-for 30 seconds } until (selected-room is-not mansion-exterior) if (state-of envelope is OFF) { stop-script } state-of envelope is OFF class-of envelope is UNTOUCHABLE state-of mailbox-flag is OFF owner-of sealed-envelope-in-safe is nuked sleep-for 3 minutes cut-scene { current-room room-0 start-script marketeer-reacts-to-envelope-contents break-until (script-running marketeer-reacts-to-envelope-contents is false) } sleep-for 5 minutes if (contract-status > unsigned) { do { sleep-for 30 seconds } until (selected-room is-not mansion-exterior) state-of contract is R-HERE class-of contract is TOUCHABLE start-sound doorbell-sound } } script schedule-package-delivery { sleep-for 5 minutes do { sleep-for 5 seconds if (selected-room is-not mansion-exterior) { if (selected-room is-not weird-eds-room) { if (actor-room weird-ed is weird-eds-room) { jump deliver } } } } deliver: start-sound doorbell-sound state-of package is HERE class-of package is TOUCHABLE do { sleep-for 8 seconds } until (actor-room weird-ed is weird-eds-room) if (selected-room is-not mansion-exterior) { start-sound doorbell-sound } stop-script touch-stuff-in-weird-eds-room weird-ed-downstairs-reason is doorbell start-script send-weird-ed-downstairs } script read-contract { case contract-status { of signed-kid { case who-recorded-demo { of selected-actor { say-line "Wow! It's a recording contract for ME!" } of syd { say-line "It's a recording contract for Syd." } default { say-line "It's a recording contract for Razor." } } } of signed-green { say-line "It's a recording contract", "for Green Tentacle." } default { say-line "It's a book publishing contract,", "and it's worth MILLIONS!" } } } script meanwhile-in-lab-1 { sleep-for 1 minute cut-scene { override skip-meanwhile-1 state-of lab-door-right is OPEN put-actor dr-fred at 15,58 in-room lab put-actor sandy at 25,58 in-room lab do-animation dr-fred turn-right do-animation sandy turn-left current-room lab camera-at 20 sleep-for 1 second say-line dr-fred "Well, my dear. Hope you're having fun!": "Within minutes it'll all be over.": "You'll be hooked up to my machine", "getting your pretty brains sucked out." walk dr-fred to sandy within 5 break-until (chars-printed > 66) do-animation sandy turn-front do-animation sandy chore-12 wait-for-message say-line sandy "You'll never get away with this!": "Dave and his friends will rescue me!": "You and your meteor can eat slime!" break-until (chars-printed > 45) do-animation sandy turn-left wait-for-message foo = actor-x dr-fred walk dr-fred to foo,68 sleep-for 1 second say-line dr-fred "That's what she thinks!" wait-for-message walk dr-fred to lab-door-right say-line dr-fred "Heh, heh, heh." wait-for-actor dr-fred put-actor dr-fred in-the-void state-of lab-door-right is CLOSED start-sound close-door-sound walk sandy to lab-door-left wait-for-actor sandy do-animation sandy turn-back say-line sandy "Help, help, HELP!" wait-for-message sleep-for 1 second skip-meanwhile-1: print-line " " state-of lab-door-right is OFF } put-actor sandy in-the-void start-script schedule-meanwhile-in-lab-2 } script meanwhile-in-lab-2 { cut-scene { override skip-meanwhile-2 put-actor dr-fred at 50,62 in-room lab put-actor sandy at 30,62 in-room lab put-actor purple-tentacle at 40,62 in-room lab do-animation sandy turn-front current-room lab camera-at 20 sleep-for 1 second walk purple-tentacle to sandy within 3 wait-for-actor purple-tentacle do-animation sandy turn-left say-line sandy "Get away from me you purple slime geek." walk sandy to 5,50 break-here 2 walk purple-tentacle to 5,50 wait-for-actor sandy walk sandy to 25,45 say-line sandy "Don't touch me!" wait-for-actor purple-tentacle walk purple-tentacle to 21,50 wait-for-actor sandy walk dr-fred to 30,50 wait-for-actor dr-fred do-animation dr-fred turn-left say-line dr-fred "PURPLE TENTACLE!!": "Stop playing with the lab experiments.": "Bring her, the machine is ready.": "Heh, heh, heh." break-until (chars-printed > 90) walk dr-fred to 60,50 wait-for-message do-animation sandy turn-front say-line sandy "EEEEEEEEEK!!!!" do-animation sandy chore-12 wait-for-message sleep-for 1 second skip-meanwhile-2: print-line " " } put-actor sandy in-the-void } script schedule-meanwhile-in-lab-2 { sleep-for 45 minutes start-script meanwhile-in-lab-2 } room mansion-exterior { sounds { "sfx\unused-sound-5" unused-sound-5 "sfx\doorbell-sound" doorbell-sound } enter { lights is 2 stop-sound radiation-sound if (script-running win-game is false) { start-script play-cricket-sound } } exit { stop-script play-cricket-sound stop-sound cricket-sound if (state-of envelope is R-HERE) { if (envelope-stamped is true) { if (envelope-is-addressed is true) { if (state-of mailbox is CLOSED) { if (state-of mailbox-flag is R-HERE) { if (script-running deliver-envelope is false) { start-script deliver-envelope } } } } } } if (script-running script-12 is true) { start-sound radiation-sound } } object front-door-ext { name is "front door" class is LOCKED verb open { if (class-of current-noun1 is UNLOCKED) { start-script open-door state-of front-door-int-left is OPEN } else { start-script door-locked-response } } verb close { start-script close-door state-of front-door-int-left is CLOSED } verb unlock use { if (current-noun2 is key) { class-of current-noun1 is UNLOCKED do-sentence open front-door-ext } else { start-script cant-unlock-response } } verb lock { class-of current-noun1 is LOCKED do-sentence close front-door-ext } verb walk-to { if (state-of current-noun1 is OPEN) { if (entered-house is false) { entered-house is true start-script meanwhile-in-lab-1 put-actor nurse-edna at 62,56 in-room kitchen do-animation nurse-edna turn-back state-of refrigerator is OPEN start-script script-152 } come-out-door front-door-int-left in-room hall } } } object door-mat { name is "door mat" verb pull pick-up { if (state-of current-noun1 is GONE) { say-line "I'll leave it here." } else { state-of current-noun1 is GONE } } verb push { state-of current-noun1 is HERE } } object key { name is "key" class is PICKUPABLE dependent-on door-mat being GONE verb pick-up { pick-up-object current-noun1 } verb use { if (current-noun2 is front-door-ext) { class-of front-door-ext is UNLOCKED do-sentence open front-door-ext } else { say-line "It doesn't work." } } } object mailbox { name is "mailbox" verb open { state-of current-noun1 is OPEN } verb close { state-of current-noun1 is CLOSED } verb use { do-sentence use current-noun2 mailbox-open } } object mailbox-open { name is "mailbox" verb open { state-of mailbox is OPEN } verb close { state-of mailbox is CLOSED } verb use { if (current-noun2 is sealed-envelope-in-safe) { if (state-of mailbox is OPEN) { if (envelope-is-addressed is false) { say-line "There is no address on it." } else { if (envelope-stamped is false) { say-line "There's no stamp on it." } else { if (content-in-envelope == envelope-empty) { say-line "The envelope is empty." } else { owner-of sealed-envelope-in-safe is nobody class-of envelope is TOUCHABLE state-of envelope is R-HERE } } } } } } verb read { say-line "Solicitors will be eaten." } } object package { name is "package" state is GONE class is PICKUPABLE UNTOUCHABLE verb pick-up { pick-up-object current-noun1 if (got-stamps is false) { pick-up-object stamps owner-of stamps is nobody } } verb give { if (current-noun2 < end-kids) { owner-of current-noun1 is current-noun2 } if (current-noun2 is weird-ed) { start-script give-package-to-weird-ed } } verb read { say-line "To: Weird Ed" } verb open pull { if (got-stamps is false) { if (state-of current-noun1 is GONE) { owner-of stamps is selected-actor } else { pick-up-object stamps } say-line "Some uncanceled stamps came off!" got-stamps is true } else { say-line "That would be illegal." } } } object stamps { name is "stamps" class is PICKUPABLE dependent-on package being OFF verb pull pick-up { pick-up-object current-noun1 say-line "Hmm, they're uncanceled." got-stamps is true } verb use { if (current-noun2 is envelope-in-microwave or current-noun2 is sealed-envelope-in-safe) { if (envelope-unsealed == 1) { envelope-stamped is true say-line "They stick!" start-script update-envelope-name owner-of current-noun1 is nuked } else { jump wont-stick } } else { wont-stick: say-line "They won't stick." } } } object mailbox-flag { name is "flag" verb open { do-sentence open current-noun1 current-noun2 } verb close { do-sentence close current-noun1 current-noun2 } verb pull { state-of current-noun1 is GONE } verb push { state-of current-noun1 is HERE } verb use { if (state-of current-noun1 is GONE) { state-of current-noun1 is HERE } else { state-of current-noun1 is GONE } } } object bushes-left { name is "bushes" verb open pull pick-up { state-of current-noun1 is GONE } } object grating { name is "grating" class is LOCKED dependent-on bushes-left being GONE verb open { if (class-of current-noun1 is LOCKED) { if (used-hunk-o-matic[selected-actor] == 1) { say-line "Easy!" jump open-grating } else { say-line "I can't budge it. It's rusted shut." } } else { open-grating: state-of current-noun1 is OPEN state-of crawl-space-grate is OPEN start-sound open-door-sound } } verb close { state-of current-noun1 is CLOSED state-of crawl-space-grate is CLOSED } verb push pull { if (state-of current-noun1 is CLOSED) { do-sentence open grating } else { do-sentence close grating } } verb fix unlock use { if (current-noun2 is tools) { class-of current-noun1 is UNLOCKED do-sentence open grating } } verb walk-to { if (state-of current-noun1 is OPEN) { come-out-door crawl-space-grate in-room crawl-space } } } object film { name is "undeveloped film" state is R-HERE class is PICKUPABLE UNTOUCHABLE verb pick-up { pick-up-object current-noun1 } verb open { if (script-running open-film is false) { start-script open-film } } verb use { do-sentence use current-noun2 current-noun1 } verb read { if (develop-film-step < film-developed) { say-line "Kodak." } else { say-line "Looks like photographs of Ed's plans." } } verb give { if (current-noun2 < end-kids) { owner-of current-noun1 is current-noun2 } if (current-noun2 is weird-ed) { if (develop-film-step < film-developed) { say-line weird-ed "No! No! You have to develop it for me!" } else { start-script give-film-to-weird-ed } } } } object envelope { name is "envelope" class is UNTOUCHABLE dependent-on mailbox being OPEN verb read { if (envelope-is-addressed is false) { say-line "It's a blank envelope." } else { say-line "It's addressed to: 222 Skyscraper Way." } } verb pick-up { class-of envelope is UNTOUCHABLE state-of envelope is R-GONE owner-of sealed-envelope-in-safe is selected-actor } } object contract { name is "contract" class is PICKUPABLE UNTOUCHABLE dependent-on mailbox being OPEN verb pick-up { pick-up-object current-noun1 state-of current-noun1 is R-GONE } verb read { start-script read-contract } verb give { start-script give-contract } } object tombstone-1 { name is "tombstone" state is GONE class is UNTOUCHABLE verb read { say-line "And good riddance!" } } object tombstone-2 { name is "tombstone" state is GONE class is UNTOUCHABLE verb read { say-line "Another one bites the dust!" } } object tombstone-3 { name is "tombstone" state is GONE class is UNTOUCHABLE } object exit-mansion-ext-right { name is "" verb walk-to { come-out-door exit-mansion-gate-left in-room mansion-gate } } object bushes-right { name is "bushes" } object doorbell { name is "doorbell" verb push use { start-sound doorbell-sound if (var-92 == 0) { if (script-running send-weird-ed-downstairs is false) { if (actor-room weird-ed is weird-eds-room) { weird-ed-downstairs-reason is doorbell start-script send-weird-ed-downstairs } } else { start-script impatient-doorbell-ringing } } } verb read { say-line "This is the home of Dr. Fred,": "Nurse Edna, Weird Ed, Dead Cousin Ted,": "Green Tentacle and Purple Tentacle." } } }
-
Well, didn't join you TODAY, but can't wait to watch this one in particular. One bit I randomly stumbled upon because I did skip around a bit to give myself a teaser... Aric not being able to answer how the dialogue trees worked is totally fair, because indeed, the SCUMM tools, compilers etc. knew nothing about it. All of it was implemented for Last Crusade mostly (almost exclusively) using functionality that was there already in Zak McKracken and Maniac Mansion on PC - a lot of it was even there in Maniac Mansion C64, before the tools and language really became portable. The short version is: Dialogue lines are just verbs repurposed - Maniac Mansion could already clear the verbs and replace them with entirely new verbs placed anywhere on the screen (as far as I recall, that was only added after the C64 version). The intricacies of doing the setup were hidden inside a few scripts and macros (like Aric mentions), which made it look like "not verbs at all" - macros which pushed the existing verbs on a stack, cleared them, created new custom verbs, placed them on screen based on their number, and set up the actions each verb would trigger. This combined with using the existing case ... of (similar to the typical switch ... case statement) for the choices. But since macros allow literally adding to the syntax of the language, this turned into something like (EDIT: using the real syntax, since that's already visible in the VGHF interview with Ron Gilbert): start-dialog set-dialog 1 "I want to be a pirate!" set-dialog 2 "I want to be a fireman!" set-dialog 3 "I mean to kill you all!" ; "wait-for-dialog" is what replaced the "case" keyword - in practice it worked like built-in "wait" commands - ; allowed other scripts to run while waiting for the player to pick a dialogue line. wait-for-dialog { of dialog-1 { jump dialogue-about-three-trials } of dialog-2 { jump beat-it-kid } of dialog-3 { jump beat-it-kid } } dialog-about-three-trials: ; SCUMM code for this discussion beat-it-kid: ; SCUMM code for this discussion The wait-for-dialog macro, as mentioned, would wait for the player to pick a line - allowing other scripts to run in the meantime. When a verb was chosen, its number would be stored in a variable, choice. So, the end of the macro would just be case choice, leading into the following of dialog-1... lines. dialog-1, 2, 3... being constants also defined within the SCUMM scripts. So, the entire "system" was all done in SCUMM without needing to change the tooling. After that, it's basically "goto's" all the way down - and to my knowledge there was never any tooling to make it easier to follow complex dialogue trees. ETA: Of course, the jumps here could be replaced with the actual response being inside the of... clauses (and in this particular case, they were) - that's up to the SCUMMlet. But jumps back to the choices within a branch after a response was finished, or back to the initial dialog choices in the conversation, etc. - would be done using jump.
-
They're included in the ultimate talkie version, because that one uses a low-effort MIDI recreation that some LucasArts music site made 20+ years ago. ð
-
Fun and wonderfully animated. ð "Are you El Carlo?" ð
-
Yeah, it does - but it's really hard to tell how, since the original publication in the 1800s had no indication of harmony - and various versions have used the simple "implicit" harmony, or something slightly more "spicy". So, other than the standards of the 1600s (when it was supposedly written, although we don't know if the melody is newer), the harmony in MI2 might be the correct "original" one, for all we know. ð
-
Just to demonstrate that (only usable on desktop, and Firefox likely won't like it, since Firefox doesn't like WebMIDI) Here are the two versions after each other - very small notes, even on a large screen (because I never finished adding ways for it to "wrap lines"). You can remove the first or second line in the text field to see either of them a bit larger. ð https://music.jither.net/?share=Gs2WvmuS1j0nnnTKfVz3 And here they are on top of each other - the original transposed up an octave, but other than that, just added a few pauses to line it up with the MI2 version. https://music.jither.net/?share=8ZQZHIxDilTxhIBBrpQ4 (Note: The first measure is an anacrusis/"pickup measure" of 3 beats rather than a full 4 beat measure, just because I found a bug in my player that wouldn't play back if multiple staffs start with a pause). I also know this one from childhood - it's a recurring motif in a Hans Christian Andersen fairy tale. And I never considered that it would be anything but a reference to the German song - not least because the lyrics could describe the fate of someone with a gambling problem. ð