- Game Maker Language 84.4%
- C 5.5%
- C++ 3.7%
- Python 3.2%
- C# 1%
- Other 2.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| ext | ||
| gml | ||
| hotspots | ||
| installer | ||
| patch | ||
| tools | ||
| vendor | ||
| .gitattributes | ||
| .gitignore | ||
| Makefile | ||
| README.md | ||
| run.sh | ||
Access Delicious 1
Screen-reader accessibility mod for Cook, Serve, Delicious! (the first game; Windows, Steam
app 247020, "CSD v3.251F (SDC Edition)", data.win SHA-256 c18adf88…). A sibling of Access
Delicious for CSD3 (../access-delicious): same approach (GML injected into the GameMaker
bytecode data.win with UndertaleModTool, plus a small DLL speaking through PRISM: NVDA,
JAWS, SAPI…), but CSD1's code is entirely different, so all game handlers are new.
Development is on Windows (this machine): the game at
C:\Program Files (x86)\Steam\steamapps\common\CookServeDelicious, NVDA running.
Game logic questions: read the decompiled code in re/CodeEntries/ (make decompile; local
only, not in git), then check in the game (tools/ctl.py, tools/winshot.ps1).
How it works
gml/gml_Object_obj_csd1a_*.gml: the controller's events (Create, Begin Step, End Step). Created frominitvarsOCreate (the first room), persistent.gml/lib/*.gml: all mod functions, as methods on the controller, concatenated alphabetically into Create by the patcher. Create-time statements in a lib file may only use things defined in files that sort before it.ext/accessdelicious.c→accessdelicious.dll(mingw): PRISM speech, the log, the development TCP control server (127.0.0.1:47701).CSD1A_MUTE=1in the environment: speech is logged, not spoken (automated test runs, so they don't talk over NVDA).patch/patch.csx(UndertaleModCli script):- imports the mod code;
- appends the controller's creation to
initvarsOCreate; - around every
instance_deactivate_all(the pause screen,backgroundchangeO):obj_csd1a.before_pause()and re-activates the controller; - rewrites every
draw_text*call toobj_csd1a.dt_text*(object_index, ...)(text capture);draw_sprite(arrow<dir>S, ...)(arrow-key bindings in the key legends) todt_arrow; - rewrites every
mouse_x/mouse_yin the game's code toglobal.csd1a_mx/my(the virtual pointer).
hotspots/gen.py→gml/lib/hotspots.gml(generated, committed): the clickable spots.
The virtual pointer (the menus are mouse-only)
With keyboard and mouse, CSD1's menus have no keyboard navigation at all (only a gamepad
cursor, which GML can't fake). Every object tests rectangles in its global-click event:
if (mouse_x > A && mouse_y > B && mouse_x < C && mouse_y < D && ...) inside screen-state
conditions. hotspots/gen.py turns each object's Mouse_53 into a function that keeps the
control flow, drops the actions, and calls hs_add(rect) where a click would do something
in the current state. Temporaries the event assigns and then tests (LVkeepgoing) become locals.
At runtime (lib/pointer.gml):
- spots = those functions run on each live instance + instances with their own click event
(
hs_inst_objects: email rows and folders, food icons, bubbles, tickets); - Arrows move to the nearest spot in that direction (45° cone, then 60°); Tab / Shift+Tab go through them in reading order (loose spots, then columns top to bottom, grids row by row); Enter clicks: the game's global and instance left-press events, release 2 frames later;
- the pointer sets
global.csd1a_mx/my, so the game's hover logic (Step) sees it; moving the real mouse hands control back to it. The real cursor never moves; - labels: a per-object describer (
hs_describers, inscreens_*.gml) for state drawn as pictures, else the text whose box centre (from font, scale and alignment at draw time) is inside the spot, else text just to its right (checkboxes); - after a click, a spot whose label changed (a toggle) is re-read;
- drag and drop (Active Menu): Enter on a food picks it up (press only; the icon follows
mouse_x), Enter on a slot drops it (release after the icon has touched the slot). - Allowed everywhere except during play (arrows and Enter cook); in a day only on the results screen, special-event cards, the pause screen (the day is deactivated) and the two tutorial slides that teach the mouse.
Modules (gml/lib)
| file | covers |
|---|---|
| text.gml | text capture (text_of(obj, age), text_new, box centre per entry), grace 12 frames |
| fallback.gml | speaks new drawn text (batched, top to bottom); clean_text (# line breaks, "(SL)", "click to continue") |
| pointer.gml | the virtual pointer (above) |
| hotspots.gml | generated by hotspots/gen.py |
| screens_title.gml | title, save slots (day, money, stars, Extreme), delete/restore modes, options |
| screens_menu.gml | main menu: tutorial pages (pictures, transcribed), food icons (name, price, owned, stars), Active Menu slots, mail rows and folders, booster/detractor bubbles, toggles, Shift+S |
| screens_day.gml | spot labels in a day: corner toggles, pause button, tickets |
| tutorial.gml | opening tutorial: Enter on "click to continue" slides, keyboard hints for the mouse slides |
| orders.gml | tickets: new, leaving (under 3 s of patience), ready to serve / to prep (blue vs yellow foods), burnt, lost; results (Perfect/Average/Bad chimes); Shift+O, Shift+number, Shift+S (also special events) |
| prep.gml | the open ticket: recipe, key legend (letters and arrow sprites), Enter/Space action; placed ingredients; page turns; gauges (fryer done/burning, beer fill verdict) |
| day_events.gml | flashing words (open, closed, rush hour), the hour, inspector, VIP, robbery verdict, photo chore's food |
| settings.gml | ini settings, hotkeys, per-handler error isolation, help |
| ctl.gml | development control commands |
Keys (mod)
- Menus: arrows / Tab / Shift+Tab move, Enter clicks (Active Menu: Enter picks a food up, Enter on a slot drops it; Enter elsewhere takes it off the menu).
- Day: the game's keys (number keys open orders, letters add ingredients, Enter serves, Esc pauses). Shift+O all orders · Shift+number one order (state, seconds left, heat) · Shift+K the open order's keys · Shift+W what it wants · Shift+S time, money, buzz, results (special events: served, strikes, wave, timer).
- Anywhere: Shift+R read the screen · Shift+H help · Shift+= / Shift+- speech rate (SAPI only) · Left Ctrl stop speech.
Build, deploy, run, test
Tools on this machine: msys2 mingw64 gcc + mingw32-make (tools/make.sh puts them on PATH),
UndertaleModCli 0.9.2.0 in ~/tools/utmt, Python 3.
tools/make.sh # dist/: accessdelicious.dll, patched data.win, prism files
tools/make.sh deploy # closes the game, copies dist/ into the game folder
tools/make.sh restore # the original data.win back (orig/data.win: untouched copy, not in git)
tools/make.sh decompile # re/ from orig/data.win
CSD1A_MUTE=1 ./run.sh # relaunch the game (Steam must be running), wait for the controller
- Log:
<game folder>/accessdelicious.log(previous run.log.1). tools/ctl.py <cmd>:ping,room(also game speed: 30 fps),insts,vars <Obj|id|global> [prefix],get,gml_set,screen [obj],hs(the pointer's spots and labels),orders,order <n>,prep,status,say.tools/keys.py: presses keys in the game window without focusing it (PostMessage);down@1.4holds a key. (ctl.py keyneeds the game focused.)tools/goto.sh "<regex>" [enter]: Tab until the pointer says a matching label.tools/fresh.sh(empties save slot 1 through the game's Delete Saves: Steam Cloud restores deleted files otherwise),tools/to_menu.sh(new game, skip tutorial, Management),tools/day1.sh(…buy three foods, Active Menu, start day 1),tools/bot.py [secs](plays a day from the mod's speech).tools/winshot.ps1 [out.png]: screenshot of the game window.- Saves:
%LOCALAPPDATA%\CSDSteamBuild\Ndata<n>file.ini(+ Steam CloudCdata<n>file.ini). - Release:
tools/make.sh release VERSION=x.y→build/AccessDelicious1-x.y.zip(installer with the xdelta3 decoder, delta fromorig/data.win, DLLs). Needs anxdelta3encoder (XDELTA=).
UndertaleModTool compiler traps
Same as CSD3 (see its README), plus: no local named like a built-in (score, id); no indexing a
call result (f()[1]); a literal newline inside a string breaks the build (write \n).
Scripted edits from Python on Windows: write with newline="\n" and beware "\b" in non-raw
strings (a backspace ends up in the file).
Game facts worth knowing
- Game speed 30 fps;
action_set_alarm(steps, n)meansalarm[n] = steps; numeric object ids in the code are one higher than__global_object_depthsnames: useobject_get_name. - A ticket is its food object (
<CODE>_barO, e.g.BUGR_barO), in slotLwhichprepstationvar(keys 1-9; slot 0 has no key).Lstatusvar: 0 waiting, 1 prep stage one, 2 on the heat, 3 prep (second or only stage), 4 done/leaving. One open at a time (Gfoodbeingpreppedvar).Lcookingvar9..34 done, 35+ burnt. Blue (back to prep when cooked): burger, nachos, pasta, pancakes, lobster, baked potato; the rest are served by selecting them (yellow). - Patience:
alarm[0]=Gcustomerwaitvar(a couple of seconds), then the ticket slides left (gravity 0.005) and is lost when wholly off screen. - Prep screen text: title (280,565), wants (280,605), legend key at x 900/1100 with the name 35 px right, "Enter"/"Space" + "to serve"/"to cook" near (1115,488). Mistakes are not shown at all.
- Chores (
W_*_barO) share the slots andLstatusvar(0/3/4); prompts are drawn text. - Main menu screen:
global.Gwhichmenuvar(table inscreens_menu.gml). Food ids: 1-20, 1001-1010 (grid icons 21-30 = 1001-1010).GT_AMslot1..6var= the Active Menu. - Results screen (
GTdayfinishedvar == 2) is drawn text; its Continue is mouse-only.
Tested in play
Tutorial; career day 1 (full day, results, checklist); buying, upgrading, equipment, not enough
money; Active Menu; mail; options; key binding; catering, Iron Cook (waves), Hungry Festivities
(and unlocking with tickets), Battle Kitchen strike challenge (character select); chores:
restroom, trash, dishes, rodents, robbery, photo, texting; the inspector; rush hour; fryer foods,
beer, soda, soup, pizza, pasta, burger, ice cream. tools/ctl.py spawn <W_*_barO> puts a chore
in the first free slot (testing).
Next
- Not heard yet: VIP taster, Battle Kitchen endurance / eSports / mystery box / co-op, the character wheel, Daily Special, CookBets, dating emails' buttons.
- Pictures not read: Credits, Battle Kitchen help pages, food pin / checklist done marks.
- Beer: a timing cue better than the after-the-fact verdict (the perfect window is 5 frames).
- The last strike of a failed event is not said (the result card says it).