Leaderboard
Popular Content
Showing content with the highest reputation on 07/04/23 in all areas
-
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! π3 points
-
This is so interesting! Thanks a lot for sharing. Scumm Revisited 5 was really useful to me when I was developing a JS replica of the cannon mini-game in CMI. I was able to understand most of the logic by looking at the scripts. The only thing I wasn't able to properly understand was the code to handle the position and size of the cannon balls. Without proper variable names, I couldn't really make sense of it. In the end, I just used an array with all positions and sizes I measured manually. Not very elegant, but worked well.3 points
-
Awesome, thank you for this amazing analysis This is actually very similiar to what I've tried doing in my project (nutcracker) also. (it's available here in case you would like to have a look: https://github.com/BLooperZ/nutcracker#decompile-game-script) which is currently able to parse v3-v8 (+ humongous entertainment games) (v3 and v4 currently not exposed in CLI) my sources were the documents published by Aric Wilmunder and the windex-enabled demos (MI2 + Putt Putt) I still working on figuring how to correctly transform the control flow to structured (specifically case..of and if..else) also, I would like to add some more points I first learned from the short SCUMM code sample included in FT remastered (IIRC) (you might already saw this, but they weren't written here yet) - Indentation was done with TAB characters - Comments are added with semicolon Also, I haven't managed to figure out how the instructions for using colon, plus and comma in say-line until your example, so thanks again.3 points
-
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." } } }1 point
-
It's ok, it's on the third repeat of everything, so you've only missed one!1 point
-
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. π1 point
-
Monkey Island Heardle #286 ππ©β¬οΈβ¬οΈβ¬οΈβ¬οΈβ¬οΈ #MonkeyIslandHeardle https://monkey-island-heardle.glitch.me1 point
-
I became fixated on Stan haggling last year. I think the current speedrun record went with: Walk away, reject 6 extras, offer 2000, offer 5000. BUT he could have gone with: Walk away, reject 6 extras, offer 5000. Of course, skipping that one click for the 2000 offer probably doesn't save much time. You could also: Reject 5 extras, offer 2000, offer 5000, offer 5000 again. That lets you avoid the "walking away" animation. I'm not sure if that would save time or not. Disclaimer: rejecting or accepting the first extra (porthole defoggers) is irrelevant because of a bug, but I imagine it's easier/faster to just reject everything. EDIT: The insult swordfighting stuff is fascinating. If you're playing Special Edition, Carla will never use "I've got a long, sharp lesson for you to learn today". So you'll never successfully use the "And I've got a little TIP for you, get the POINT" response against her. But these guys aren't on the Special Edition.1 point