// adventure2 -- a Sierra-faithful sample built on JoeyLib. // // The first adventure example (examples/adventure/adventure.c) used // a simple "redraw foreground props on top of the ego" trick to get // the walk-behind-the-tree illusion. That works but is unlike how the // real Sierra engines did it. This second example reproduces the SCI // model: // // *** Three parallel screens per room *** // ------------------------------------------------------------------ // 1. VISUAL screen -- the picture the player sees. 16 colors, // 320x200, ordinary 4bpp surface. // 2. PRIORITY screen -- same dimensions; each pixel's value is a // priority band 0..15. Painted programmatically // when the room is built. Each picture object // (tree, rock, grass tuft, the floor itself) // has its pixels set to its priority. Higher // number = closer to the camera = drawn on top. // 3. CONTROL screen -- same dimensions; each pixel's value is a // "control color" 0..15 encoding what the game // logic should do at that pixel: // 0 = blocked (the actor cannot stand here) // 1 = walkable floor // 2 = trigger: east-exit zone // Sierra used the rest of the slots for other // per-pixel game state (cliff edges, door // openings, scriptable trigger regions, etc.). // // *** How the engine uses them *** // ------------------------------------------------------------------ // - When the actor is drawn, each candidate actor pixel reads the // PRIORITY screen at the same (x, y). If picture_priority > // actor_priority, the actor pixel is skipped: the picture is // in front, the actor goes behind. This is per-pixel masking, // so a single object can occlude part of the actor while another // part of the actor draws over the same object -- e.g. when an // actor steps half-behind a tree. // - The actor's priority is one value, derived from its feet Y via // a priority-band table. Down-on-screen = up-priority. The feet // position alone determines what the actor is in front of. // - When the player clicks to walk somewhere, the engine reads the // CONTROL screen pixel under the click. If it's 1 (walkable), a // BFS is run over the same control screen at a coarse cell grid. // Click on a tree trunk and the BFS still finds a path to the // nearest walkable pixel. // // *** Visualising the buffers *** // ------------------------------------------------------------------ // Press P to swap the visible screen with the priority buffer; the // palette is replaced with a rainbow ramp so each priority band is // easy to identify. Press C to switch to a control-buffer overlay // where each control code is a distinct flat color (green=walk, // red=blocked, yellow=trigger). Press N (or the same key again) to // return to the normal view. Watch the priority view as the ego walks -- // the per-pixel masking that produces the "walk behind the tree" // illusion comes straight out of those colored bands. // // Controls: // Mouse left walk to / act on whatever is under the cursor // Mouse right cycle verb (WALK / LOOK / TAKE) // SPACE cycle verb (one-button-mouse fallback) // 1 / 2 / 3 jump straight to a verb // P toggle priority-screen overlay // C toggle control-screen overlay // N return to normal view (no overlay) // ESC quit // // The forest is the only room. The point of this demo is the engine, // not the game content. Walk behind any tree; the per-pixel masking // is what makes it look right. #include #include #include /* =================================================================== * Section 1 -- Layout, priority bands, control codes * =================================================================== */ #define MSG_BAR_H 12 #define INV_BAR_H 20 #define PLAY_AREA_H (SURFACE_HEIGHT - MSG_BAR_H - INV_BAR_H) /* 168 */ #define MSG_BAR_Y PLAY_AREA_H #define INV_BAR_Y (PLAY_AREA_H + MSG_BAR_H) /* Priority bands: sky (y<70) is priority 0, ground (y=70..167) is * split into 14 bands, each PRI_BAND_HEIGHT pixels tall. A picture * pixel with priority P obscures any actor with priority < P. An * actor's priority is derived from its feet Y via the same mapping * so depth ordering "just works" as the actor walks up and down * the scene. */ #define PRI_BAND_TOP 70 #define PRI_BAND_COUNT 14 #define PRI_BAND_HEIGHT ((PLAY_AREA_H - PRI_BAND_TOP) / PRI_BAND_COUNT) #define PRI_SKY 0 #define PRI_MAX 14 /* Control colors. 4-bit values stored as the pixel value in the * control surface. Sierra used 16 slots but most demos only need a * handful. */ #define CTRL_BLOCK 0 /* unwalkable */ #define CTRL_WALK 1 /* walkable floor */ #define CTRL_TRIGGER_LOOK 2 /* "you smell the forest" trigger zone */ /* Pathfinding grid -- one cell per 4x4 pixel block, queried against * the control surface (sampled at the cell centre). */ #define WALK_CELL_PX 4 #define WALK_GRID_W (SURFACE_WIDTH / WALK_CELL_PX) /* 80 */ #define WALK_GRID_H (PLAY_AREA_H / WALK_CELL_PX) /* 42 */ /* Ego dimensions and animation. Larger sprite would be more * impressive but per-pixel priority masking scales linearly with * ego pixel count, so 16x24 = 384 pixels/frame is the comfortable * sweet spot on the slowest target (IIgs 2.8 MHz). */ #define EGO_W 16 #define EGO_H 24 #define EGO_FRAMES 3 /* idle + 2 walk frames */ /* Palettes. */ #define PAL_SCENE 0 #define PAL_DEBUG_PRI 1 #define PAL_DEBUG_CTRL 2 #define PAL_UI 3 /* Scene palette slots (same convention as adventure.c). */ #define C_TRANS 0 #define C_BLACK 1 #define C_WHITE 2 #define C_RED 3 #define C_DARK_GREEN 4 #define C_GREEN 5 #define C_BROWN 6 #define C_DARK_BROWN 7 #define C_TAN 8 #define C_GRAY 9 #define C_DARK_GRAY 10 #define C_YELLOW 11 #define C_ORANGE 12 #define C_BLUE 13 #define C_FLESH 14 #define C_PINK 15 /* UI palette slots. */ #define UI_BG 1 #define UI_INK 2 #define UI_HILITE 3 #define UI_DIM 4 typedef enum { VERB_WALK = 0, VERB_LOOK, VERB_TAKE, VERB_COUNT } VerbE; typedef enum { DIR_S = 0, DIR_E, DIR_N, DIR_W, DIR_COUNT } DirE; typedef enum { VIEW_NORMAL = 0, VIEW_PRIORITY, VIEW_CONTROL, VIEW_COUNT } DebugViewE; /* =================================================================== * Section 2 -- Globals * =================================================================== */ static jlSurfaceT *gScreen; static jlSurfaceT *gVisual; /* room background -- "picture" buffer */ static jlSurfaceT *gPriority; /* per-pixel priority codes */ static jlSurfaceT *gControl; /* per-pixel control codes */ static int16_t gEgoX, gEgoY; /* top-left of ego sprite */ static DirE gEgoDir; static uint8_t gEgoFrameIdx; static uint8_t gEgoAnimTick; static bool gEgoMoving; static int16_t gPathX[256]; static int16_t gPathY[256]; static uint16_t gPathLen; static uint16_t gPathStep; static VerbE gCurVerb; static char gMessage[80]; static uint8_t gMessageTtl; static DebugViewE gDebugView; /* =================================================================== * Section 3 -- ASCII-row sprite authoring (same idea as adventure.c) * =================================================================== */ static uint8_t pixCharToColor(char c) { switch (c) { case ' ': case '.': return C_TRANS; case '#': return C_BLACK; case 'F': return C_FLESH; case 'h': return C_BROWN; case 'H': return C_DARK_BROWN; case 'r': return C_RED; case 'b': return C_BLUE; case 'g': return C_GREEN; case 'G': return C_DARK_GREEN; case 'n': return C_BROWN; case 'N': return C_DARK_BROWN; case 'y': return C_YELLOW; case 'Y': return C_ORANGE; case 'w': return C_WHITE; case 'W': return C_GRAY; case 'T': return C_TAN; case 'k': return C_DARK_GRAY; case 'p': return C_PINK; default: return C_TRANS; } } /* Discriminate "trunk" pixels (which set CTRL_BLOCK so the actor * can't walk through them) from "foliage" pixels (which don't, * because the actor walks UNDER the canopy). Used when painting * the trees into the parallel screens. */ static bool isTrunkChar(char c) { return c == 'n' || c == 'N'; } /* ORCA-C's auto-segmenter splits at function boundaries; one TU's * code can still overflow the 65816's 64 KB code bank if it lands * in a single segment. Sprinkle ORCA-only segment directives at the * heavier sections to spread the load across multiple banks. */ #ifdef JOEYLIB_PLATFORM_IIGS segment "ADV2ART"; #endif /* =================================================================== * Section 4 -- Sprite ASCII art (ego + cursors) * =================================================================== */ #define EGO_W_PAD 16 static const char * const kEgoIdleS[24] = { " #### ", " #HHHH# ", " #HHhhHH# ", " #HFFFFH# ", " #FF##FF# ", " #FFFF# ", " #FF# ", " #rrrr# ", " #rrrrrr# ", " #rrrrrr# ", " #rrrrrrrr# ", " #rrrrrrrr# ", " #rrrrrr# ", " #brrrrb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbb# ", " #bb## ", " #b# ", " ## ", " ", " ", " ", }; static const char * const kEgoWalk1S[24] = { " #### ", " #HHHH# ", " #HHhhHH# ", " #HFFFFH# ", " #FF##FF# ", " #FFFF# ", " #FF# ", " #rrrr# ", " #rrrrrr# ", " #rrrrrr# ", " #rrrrrrrr# ", " #rrrrrrrr# ", " #rrrrrr# ", " #brrrrb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbb# ", " #bb## ", " #b# ", " ## ", " ", " ", " ", }; static const char * const kEgoWalk2S[24] = { " #### ", " #HHHH# ", " #HHhhHH# ", " #HFFFFH# ", " #FF##FF# ", " #FFFF# ", " #FF# ", " #rrrr# ", " #rrrrrr# ", " #rrrrrr# ", " #rrrrrrrr# ", " #rrrrrrrr# ", " #rrrrrr# ", " #brrrrb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbb# ", " ##bb# ", " #b# ", " ## ", " ", " ", " ", }; static const char * const kEgoIdleN[24] = { " #### ", " #HHHH# ", " #HHHHHH# ", " #HHHHHH# ", " #HHHHHH# ", " #HHHH# ", " #### ", " #rrrr# ", " #rrrrrr# ", " #rrrrrr# ", " #rrrrrrrr# ", " #rrrrrrrr# ", " #rrrrrr# ", " #brrrrb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbb# ", " ##bb# ", " #b# ", " ## ", " ", " ", " ", }; static const char * const kEgoWalk1N[24] = { " #### ", " #HHHH# ", " #HHHHHH# ", " #HHHHHH# ", " #HHHHHH# ", " #HHHH# ", " #### ", " #rrrr# ", " #rrrrrr# ", " #rrrrrr# ", " #rrrrrrrr# ", " #rrrrrrrr# ", " #rrrrrr# ", " #brrrrb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbb# ", " #bb# ", " #bb# ", " ## ", " ", " ", " ", " ", }; static const char * const kEgoWalk2N[24] = { " #### ", " #HHHH# ", " #HHHHHH# ", " #HHHHHH# ", " #HHHHHH# ", " #HHHH# ", " #### ", " #rrrr# ", " #rrrrrr# ", " #rrrrrr# ", " #rrrrrrrr# ", " #rrrrrrrr# ", " #rrrrrr# ", " #brrrrb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbb# ", " #bb# ", " #bb# ", " ## ", " ", " ", " ", " ", }; static const char * const kEgoIdleE[24] = { " #### ", " #HHHH# ", " #HHHHhh# ", " #FFFFhH# ", " #F##FFH# ", " #FFF# ", " #FF# ", " #rrrr# ", " #rrrrrr# ", " #rrrrrrrr## ", "#rrrrrrrrFF# ", " #rrrrrrrr# ", " #rrrrrr# ", " #brrrrb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbbbb# ", " #bb#bb# ", " ## ## ", " # # ", " ", " ", " ", " ", }; static const char * const kEgoWalk1E[24] = { " #### ", " #HHHH# ", " #HHHHhh# ", " #FFFFhH# ", " #F##FFH# ", " #FFF# ", " #FF# ", " #rrrr# ", " #rrrrrr# ", " #rrrrrrrr## ", "#rrrrrrrrFF# ", " #rrrrrrrr# ", " #rrrrrr# ", " #brrrrb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbb# ", " ## #b# ", " ## ", " ", " ", " ", " ", " ", }; static const char * const kEgoWalk2E[24] = { " #### ", " #HHHH# ", " #HHHHhh# ", " #FFFFhH# ", " #F##FFH# ", " #FFF# ", " #FF# ", " #rrrr# ", " #rrrrrr# ", " #rrrrrrrr## ", "#rrrrrrrrFF# ", " #rrrrrrrr# ", " #rrrrrr# ", " #brrrrb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbb# ", " #bb### ", " ## ", " ", " ", " ", " ", " ", }; static const char * const kEgoIdleW[24] = { " #### ", " #HHHH# ", " #hhHHHH# ", " #HhFFFF# ", " #HFF##F# ", " #FFF# ", " #FF# ", " #rrrr# ", " #rrrrrr# ", " ##rrrrrrrr# ", " #FFrrrrrrrr# ", " #rrrrrrrr# ", " #rrrrrr# ", " #brrrrb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbbbb# ", " #bb#bb# ", " ## ## ", " # # ", " ", " ", " ", " ", }; static const char * const kEgoWalk1W[24] = { " #### ", " #HHHH# ", " #hhHHHH# ", " #HhFFFF# ", " #HFF##F# ", " #FFF# ", " #FF# ", " #rrrr# ", " #rrrrrr# ", " ##rrrrrrrr# ", " #FFrrrrrrrr# ", " #rrrrrrrr# ", " #rrrrrr# ", " #brrrrb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbb# ", " #b# ## ", " ## ", " ", " ", " ", " ", " ", }; static const char * const kEgoWalk2W[24] = { " #### ", " #HHHH# ", " #hhHHHH# ", " #HhFFFF# ", " #HFF##F# ", " #FFF# ", " #FF# ", " #rrrr# ", " #rrrrrr# ", " ##rrrrrrrr# ", " #FFrrrrrrrr# ", " #rrrrrrrr# ", " #rrrrrr# ", " #brrrrb# ", " #bbbbbb# ", " #bbbbbb# ", " #bbbb# ", " ###bb# ", " #bb# ", " ## ", " ", " ", " ", " ", }; static const char * const * const gEgoFrames[DIR_COUNT][EGO_FRAMES] = { { kEgoIdleS, kEgoWalk1S, kEgoWalk2S }, { kEgoIdleE, kEgoWalk1E, kEgoWalk2E }, { kEgoIdleN, kEgoWalk1N, kEgoWalk2N }, { kEgoIdleW, kEgoWalk1W, kEgoWalk2W }, }; /* Cursor sprites: 16x16 each, drawn at the mouse position. */ static const char * const kCursorWalk[16] = { " ww ", " ww ", " w ww w ", " ww ww ww ", " www ww www ", "wwwwwwwwwwwwww ", " www ww www ", " ww ww ww ", " w ww w ", " ww ", " ww ", " ", " ", " ", " ", " ", }; static const char * const kCursorLook[16] = { " wwwwww ", " ww ww ", " w wwwwww w ", "w w w w ", "w w wwwww w w ", "w w w bw w w ", "w w w bbw w w ", "w w wbbww w w ", "w w wwwww w w ", "w w w w ", " w wwwwww w ", " ww ww ", " wwwwww ", " ", " ", " ", }; static const char * const kCursorTake[16] = { " wwwwww ", " wnnnnnnw ", " wnnNNNNnnw ", " wnnnnnnnnnnw ", " wnnnnnnnnnnw ", "wnnnnnnnnnnnnw ", "wnnnnnnnnnnnnw ", "wnnnnnnnnnnnnw ", " wnnnnnnnnnnw ", " wnnnnnnnnnnw ", " wnnnnnnnnw ", " wnnnnnnw ", " wwwwww ", " ", " ", " ", }; static const char * const * const gCursorFrames[VERB_COUNT] = { kCursorWalk, kCursorLook, kCursorTake }; /* =================================================================== * Section 5 -- Prop ASCII art and per-room placement * * Each "prop" gets a base Y (its anchor on the ground). The base Y * drives: * - the prop's priority value (so the actor's depth ordering works * when walking past it) * - the prop's vertical placement * * Foliage cells in a tree do NOT set the control screen to BLOCKED * (the actor walks under the canopy). Only TRUNK cells block. The * isTrunkChar() predicate above is what tells the paint pass which * pixels are which. * =================================================================== */ #define TREE_W 16 #define TREE_H 40 static const char * const kPropTree[TREE_H] = { " gggg ", " ggggGggg ", " ggGGggggGgg ", " gGggggGgggg ", " ggGgggggggGg ", " gggGGggggggg ", " gggggggGGggg ", " gGggggggGgGg ", " ggGGggggGg ", " gggGGGgggg ", " ggggGgGg ", " gggGgg ", " gggg ", " nn ", " nn ", " nN ", " Nn ", " nn ", " nN ", " Nn ", " nn ", " nN ", " Nn ", " nn ", " nN ", " Nn ", " nn ", " nN ", " Nn ", " nNn ", " NnN ", " nNn ", " nnNN ", " ", " ", " ", " ", " ", " ", " ", }; #define ROCK_W 24 #define ROCK_H 12 static const char * const kPropRock[ROCK_H] = { " ", " WWWWWW ", " WWWWWWWWWW ", " WWWWWWWWWWWWWW ", " WWkWWWWWWWWWWWWW ", " WWWWWWWWWWWWWWkW ", " WkWWWWWWWWWWWWWW ", " WWWWWWWWWWWWWWWW ", " WWWWWWWWWWWWWW ", " WWWWWWWWWW ", " WWWWWW ", " ", }; #define GRASS_W 8 #define GRASS_H 8 static const char * const kPropGrass[GRASS_H] = { " g ", " ggg g ", " gggGg ", "ggGgGgg ", "ggGgGgg ", " gGgGg ", " gGg ", " g ", }; typedef struct { int16_t baseX; /* top-left of the prop sprite on the visual screen */ int16_t baseY; int16_t w; int16_t h; const char * const *rows; bool blocksWalk; /* if false, only trunk-class chars block */ } PropDef; /* Five trees, three grass tufts, two rocks. Base Ys span the ground * area so the priority bands are all exercised. */ static const PropDef kProps[] = { /* Trees: trunk chars block, foliage chars don't. */ { 20, 82, TREE_W, TREE_H, kPropTree, false }, { 72, 98, TREE_W, TREE_H, kPropTree, false }, { 148, 74, TREE_W, TREE_H, kPropTree, false }, { 220, 92, TREE_W, TREE_H, kPropTree, false }, { 268, 110, TREE_W, TREE_H, kPropTree, false }, /* Rocks: every non-transparent pixel blocks. */ { 100, 138, ROCK_W, ROCK_H, kPropRock, true }, { 200, 150, ROCK_W, ROCK_H, kPropRock, true }, /* Grass tufts: never block (the actor walks through them). */ { 40, 152, GRASS_W, GRASS_H, kPropGrass, false }, { 180, 158, GRASS_W, GRASS_H, kPropGrass, false }, { 260, 152, GRASS_W, GRASS_H, kPropGrass, false }, }; #define PROP_COUNT ((int)(sizeof(kProps)/sizeof(kProps[0]))) /* =================================================================== * Section 6 -- Tiny 5x7 font (lifted from adventure.c for self- * containment). * =================================================================== */ #define FONT_W 5 #define FONT_H 7 static const uint8_t kFontGlyph[][FONT_H] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* 0 space */ { 0x70, 0x88, 0x98, 0xA8, 0xC8, 0x88, 0x70 }, /* 1 '0' */ { 0x20, 0x60, 0x20, 0x20, 0x20, 0x20, 0x70 }, /* 2 '1' */ { 0x70, 0x88, 0x08, 0x10, 0x20, 0x40, 0xF8 }, /* 3 '2' */ { 0x70, 0x88, 0x08, 0x30, 0x08, 0x88, 0x70 }, /* 4 '3' */ { 0x10, 0x30, 0x50, 0x90, 0xF8, 0x10, 0x10 }, /* 5 '4' */ { 0xF8, 0x80, 0xF0, 0x08, 0x08, 0x88, 0x70 }, /* 6 '5' */ { 0x30, 0x40, 0x80, 0xF0, 0x88, 0x88, 0x70 }, /* 7 '6' */ { 0xF8, 0x08, 0x10, 0x20, 0x40, 0x40, 0x40 }, /* 8 '7' */ { 0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70 }, /* 9 '8' */ { 0x70, 0x88, 0x88, 0x78, 0x08, 0x10, 0x60 }, /* 10 '9' */ { 0x70, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88 }, /* 11 'A' */ { 0xF0, 0x88, 0x88, 0xF0, 0x88, 0x88, 0xF0 }, /* 12 'B' */ { 0x70, 0x88, 0x80, 0x80, 0x80, 0x88, 0x70 }, /* 13 'C' */ { 0xF0, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF0 }, /* 14 'D' */ { 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0xF8 }, /* 15 'E' */ { 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80 }, /* 16 'F' */ { 0x70, 0x88, 0x80, 0xB8, 0x88, 0x88, 0x70 }, /* 17 'G' */ { 0x88, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88 }, /* 18 'H' */ { 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70 }, /* 19 'I' */ { 0x38, 0x10, 0x10, 0x10, 0x90, 0x90, 0x60 }, /* 20 'J' */ { 0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88 }, /* 21 'K' */ { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8 }, /* 22 'L' */ { 0x88, 0xD8, 0xA8, 0xA8, 0x88, 0x88, 0x88 }, /* 23 'M' */ { 0x88, 0xC8, 0xA8, 0x98, 0x88, 0x88, 0x88 }, /* 24 'N' */ { 0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70 }, /* 25 'O' */ { 0xF0, 0x88, 0x88, 0xF0, 0x80, 0x80, 0x80 }, /* 26 'P' */ { 0x70, 0x88, 0x88, 0x88, 0xA8, 0x90, 0x68 }, /* 27 'Q' */ { 0xF0, 0x88, 0x88, 0xF0, 0xA0, 0x90, 0x88 }, /* 28 'R' */ { 0x70, 0x88, 0x80, 0x70, 0x08, 0x88, 0x70 }, /* 29 'S' */ { 0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }, /* 30 'T' */ { 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70 }, /* 31 'U' */ { 0x88, 0x88, 0x88, 0x88, 0x88, 0x50, 0x20 }, /* 32 'V' */ { 0x88, 0x88, 0x88, 0xA8, 0xA8, 0xD8, 0x88 }, /* 33 'W' */ { 0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88 }, /* 34 'X' */ { 0x88, 0x88, 0x50, 0x20, 0x20, 0x20, 0x20 }, /* 35 'Y' */ { 0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8 }, /* 36 'Z' */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60 }, /* 37 '.' */ { 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x40 }, /* 38 ',' */ { 0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x00 }, /* 39 ':' */ { 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x20 }, /* 40 '!' */ }; static int glyphIdx(char c) { if (c == ' ') return 0; if (c >= '0' && c <= '9') return 1 + (c - '0'); if (c >= 'A' && c <= 'Z') return 11 + (c - 'A'); if (c >= 'a' && c <= 'z') return 11 + (c - 'a'); if (c == '.') return 37; if (c == ',') return 38; if (c == ':') return 39; if (c == '!') return 40; return 0; } static void drawText(int16_t x, int16_t y, const char *text, uint8_t color) { int16_t cx; int i; int idx; int16_t row; int16_t col; uint8_t bits; cx = x; for (i = 0; text[i] != '\0'; i++) { idx = glyphIdx(text[i]); for (row = 0; row < FONT_H; row++) { bits = kFontGlyph[idx][row]; for (col = 0; col < FONT_W; col++) { if (bits & (0x80 >> col)) { jlDrawPixel(gScreen, (int16_t)(cx + col), (int16_t)(y + row), color); } } } cx = (int16_t)(cx + FONT_W + 1); if (cx > SURFACE_WIDTH - FONT_W) { return; } } } static void setMessage(const char *text) { size_t n; n = strlen(text); if (n >= sizeof(gMessage)) { n = sizeof(gMessage) - 1; } memcpy(gMessage, text, n); gMessage[n] = '\0'; gMessageTtl = 180; } /* =================================================================== * Section 7 -- Palettes (scene, debug-priority rainbow, * debug-control categorical, UI) * =================================================================== */ static uint16_t rgb(uint8_t r, uint8_t g, uint8_t b) { return (uint16_t)(((r & 0x0F) << 8) | ((g & 0x0F) << 4) | (b & 0x0F)); } static void buildPalettes(void) { uint16_t scene[16]; uint16_t pri[16]; uint16_t ctl[16]; uint16_t ui[16]; uint16_t i; for (i = 0; i < 16; i++) { scene[i] = 0; pri[i] = 0; ctl[i] = 0; ui[i] = 0; } /* Scene palette: matches the C_* indices above. */ scene[C_BLACK] = rgb(0, 0, 0); scene[C_WHITE] = rgb(15, 15, 15); scene[C_RED] = rgb(13, 2, 2); scene[C_DARK_GREEN] = rgb(2, 6, 2); scene[C_GREEN] = rgb(4, 10, 3); scene[C_BROWN] = rgb(9, 6, 3); scene[C_DARK_BROWN] = rgb(5, 3, 1); scene[C_TAN] = rgb(13, 11, 7); scene[C_GRAY] = rgb(10, 10, 10); scene[C_DARK_GRAY] = rgb(5, 5, 5); scene[C_YELLOW] = rgb(15, 13, 3); scene[C_ORANGE] = rgb(14, 8, 2); scene[C_BLUE] = rgb(3, 7, 13); scene[C_FLESH] = rgb(14, 11, 8); scene[C_PINK] = rgb(15, 10, 11); /* Debug-priority palette: 0..14 rainbow ramp, plus white for 15 * (unused in our band scheme). Priority 0 = deep blue (sky/back), * priority 14 = bright red (foreground). Easy to read at a * glance. */ pri[0] = rgb(0, 0, 5); pri[1] = rgb(0, 2, 9); pri[2] = rgb(0, 5, 12); pri[3] = rgb(0, 9, 13); pri[4] = rgb(0, 12, 11); pri[5] = rgb(2, 13, 6); pri[6] = rgb(6, 14, 3); pri[7] = rgb(10, 14, 0); pri[8] = rgb(14, 13, 0); pri[9] = rgb(14, 10, 0); pri[10] = rgb(14, 7, 0); pri[11] = rgb(14, 4, 0); pri[12] = rgb(14, 2, 2); pri[13] = rgb(14, 0, 4); pri[14] = rgb(14, 0, 8); pri[15] = rgb(15, 15, 15); /* Debug-control palette: categorical. 0 = solid red (blocked), * 1 = mid green (walk), 2 = bright yellow (trigger). Others are * gray to highlight any unintended values. */ ctl[CTRL_BLOCK] = rgb(13, 1, 1); ctl[CTRL_WALK] = rgb(2, 10, 3); ctl[CTRL_TRIGGER_LOOK] = rgb(15, 14, 3); for (i = 3; i < 16; i++) { ctl[i] = rgb(6, 6, 6); } /* UI palette: bottom message + inventory bar. */ ui[UI_BG] = rgb(2, 2, 3); ui[UI_INK] = rgb(15, 15, 15); ui[UI_HILITE] = rgb(15, 13, 3); ui[UI_DIM] = rgb(7, 7, 8); jlPaletteSet(gScreen, PAL_SCENE, scene); jlPaletteSet(gScreen, PAL_DEBUG_PRI, pri); jlPaletteSet(gScreen, PAL_DEBUG_CTRL, ctl); jlPaletteSet(gScreen, PAL_UI, ui); } /* The stage's SCB selects which of the 4 palettes is active on each * scanline. Normal view uses PAL_SCENE for the play area and PAL_UI * for the message/inventory bars. Debug views swap PAL_SCENE for * the matching debug palette. */ static void programScbForView(DebugViewE view) { int16_t line; uint8_t playPal; switch (view) { case VIEW_PRIORITY: playPal = PAL_DEBUG_PRI; break; case VIEW_CONTROL: playPal = PAL_DEBUG_CTRL; break; default: playPal = PAL_SCENE; break; } for (line = 0; line < PLAY_AREA_H; line++) { jlScbSet(gScreen, (uint16_t)line, playPal); } for (line = PLAY_AREA_H; line < SURFACE_HEIGHT; line++) { jlScbSet(gScreen, (uint16_t)line, PAL_UI); } } #ifdef JOEYLIB_PLATFORM_IIGS segment "ADV2PAINT"; #endif /* =================================================================== * Section 8 -- Painting the three room screens (visual / priority / * control) * * Each pass operates on the same logical scene -- the difference is * what value gets written at each pixel. The PROPS pass writes to * all three at once because the prop's screen footprint is shared. * =================================================================== */ /* Map a screen Y to a priority band 0..14. The sky band sits above * PRI_BAND_TOP; everything below is divided into PRI_BAND_COUNT * even-height bands. Higher Y -> higher priority. */ static uint8_t priorityForY(int16_t y) { int16_t band; if (y < PRI_BAND_TOP) { return PRI_SKY; } band = (int16_t)((y - PRI_BAND_TOP) / PRI_BAND_HEIGHT); band++; if (band > PRI_MAX) { band = PRI_MAX; } return (uint8_t)band; } /* Paint the visual buffer: a normal-looking forest scene, drawn * with JoeyLib primitives only (no asset files). */ static void paintForestVisual(void) { int16_t y; int16_t i; jlSurfaceClear(gVisual, C_DARK_GREEN); /* Sky band. */ jlFillRect(gVisual, 0, 0, SURFACE_WIDTH, PRI_BAND_TOP, C_BLUE); /* Far tree-line silhouette at the horizon. */ for (i = 0; i < SURFACE_WIDTH; i += 16) { jlFillCircle(gVisual, i, (int16_t)(PRI_BAND_TOP - 6), 6, C_DARK_GREEN); } /* Ground gradient: brighter green stripes near the camera. */ for (y = PRI_BAND_TOP + 4; y < PLAY_AREA_H; y += 8) { jlDrawLine(gVisual, 0, y, SURFACE_WIDTH - 1, y, C_GREEN); } /* A vague dirt path that hints at the east-exit trigger zone * but does NOT correspond perfectly to it -- the trigger logic * is in the control buffer, the path is just visual flavor. */ jlFillRect(gVisual, SURFACE_WIDTH - 30, 130, 30, 14, C_TAN); jlFillRect(gVisual, SURFACE_WIDTH - 28, 132, 28, 10, C_BROWN); } /* Paint the priority buffer: * - Sky (y < PRI_BAND_TOP): solid PRI_SKY (0). The actor never * stands here; the sky is always behind everything. * - Ground area: filled rows of priority 1..PRI_MAX, each * PRI_BAND_HEIGHT pixels tall, increasing with Y. * * The PROPS pass below will overwrite this with each prop's * single priority value, covering the prop's exact pixel footprint. * That's how a 40-pixel-tall tree gets a uniform priority equal to * its base Y's band -- the actor can be in front of the WHOLE tree * or behind the WHOLE tree, no in-between, exactly as in SCI. */ static void paintForestPriority(void) { int16_t y; /* Sky band. */ jlFillRect(gPriority, 0, 0, SURFACE_WIDTH, PRI_BAND_TOP, PRI_SKY); /* Ground bands. */ for (y = PRI_BAND_TOP; y < PLAY_AREA_H; y++) { jlDrawLine(gPriority, 0, y, SURFACE_WIDTH - 1, y, priorityForY(y)); } } /* Paint the control buffer: * - Sky band: CTRL_BLOCK (the actor can't stand on the sky). * - Ground area: CTRL_WALK. * - Right-edge column: CTRL_TRIGGER_LOOK as a sample trigger zone * so we can demonstrate non-walk control codes. * * Props patch in their own collision pixels in the PROPS pass: tree * trunks block, foliage doesn't, rocks block entirely. */ static void paintForestControl(void) { int16_t y; jlFillRect(gControl, 0, 0, SURFACE_WIDTH, PRI_BAND_TOP, CTRL_BLOCK); jlFillRect(gControl, 0, PRI_BAND_TOP, SURFACE_WIDTH, PLAY_AREA_H - PRI_BAND_TOP, CTRL_WALK); /* East-edge trigger zone: a 14 px wide vertical strip the actor * walks into to "smell the deeper forest." Painted only over the * walkable region so the actor can step into it. */ for (y = PRI_BAND_TOP; y < PLAY_AREA_H; y++) { jlDrawLine(gControl, SURFACE_WIDTH - 14, y, SURFACE_WIDTH - 1, y, CTRL_TRIGGER_LOOK); } } /* Stamp one prop into all three screens. */ static void paintProp(const PropDef *p) { int16_t r; int16_t c; int16_t px; int16_t py; char ch; uint8_t color; uint8_t pri; pri = priorityForY((int16_t)(p->baseY + p->h - 1)); /* prop priority = priority at its bottom */ for (r = 0; r < p->h; r++) { py = (int16_t)(p->baseY + r); if (py < 0 || py >= PLAY_AREA_H) { continue; } for (c = 0; c < p->w; c++) { ch = p->rows[r][c]; color = pixCharToColor(ch); if (color == C_TRANS) { continue; } px = (int16_t)(p->baseX + c); if (px < 0 || px >= SURFACE_WIDTH) { continue; } jlDrawPixel(gVisual, px, py, color); jlDrawPixel(gPriority, px, py, pri); if (p->blocksWalk) { jlDrawPixel(gControl, px, py, CTRL_BLOCK); } else if (isTrunkChar(ch)) { /* Tree trunks block; foliage doesn't. The actor walks * under the canopy because the control screen is * untouched at those pixels. */ jlDrawPixel(gControl, px, py, CTRL_BLOCK); } } } } static void paintAllProps(void) { int i; for (i = 0; i < PROP_COUNT; i++) { paintProp(&kProps[i]); } } /* =================================================================== * Section 9 -- Per-pixel actor draw against the priority screen * * For each non-transparent pixel of the ego sprite, we sample the * priority buffer at the same screen coordinate. If the picture's * priority at that pixel exceeds the actor's priority, we leave the * picture pixel alone -- the picture is in front of the actor. * Otherwise we draw the actor pixel. * * This is the same algorithm SCI used. Per-pixel jlSamplePixel + * jlDrawPixel is slower than the compiled-sprite path JoeyLib * provides, but it's faithful to the engine being demonstrated; the * actor footprint (16x24 = 384 candidate pixels) keeps the cost * manageable even on a 2.8 MHz IIgs. * =================================================================== */ static uint8_t actorPriority(int16_t feetY) { return priorityForY(feetY); } static void drawEgoMasked(int16_t x, int16_t y, DirE dir, uint8_t frame) { const char * const *rows; int16_t r; int16_t c; int16_t px; int16_t py; uint8_t actorPri; uint8_t picPri; char ch; uint8_t color; rows = gEgoFrames[dir][frame]; actorPri = actorPriority((int16_t)(y + EGO_H - 1)); /* feet = bottom of sprite */ for (r = 0; r < EGO_H; r++) { py = (int16_t)(y + r); if (py < 0 || py >= PLAY_AREA_H) { continue; } for (c = 0; c < EGO_W_PAD; c++) { ch = rows[r][c]; color = pixCharToColor(ch); if (color == C_TRANS) { continue; } px = (int16_t)(x + c); if (px < 0 || px >= SURFACE_WIDTH) { continue; } picPri = jlSamplePixel(gPriority, px, py); if (actorPri < picPri) { /* Picture pixel is in front. Skip the actor pixel. */ continue; } jlDrawPixel(gScreen, px, py, color); } } } #ifdef JOEYLIB_PLATFORM_IIGS segment "ADV2PATH"; #endif /* =================================================================== * Section 10 -- Pathfinding via the control buffer * * The walk-mask in the original adventure example was an authored * grid. Here we just SAMPLE the control screen at each cell's center * pixel; if it reports CTRL_WALK or any trigger code, the cell is * walkable. CTRL_BLOCK cells (tree trunks, rocks, sky) are skipped. * =================================================================== */ static bool cellWalkable(int16_t cx, int16_t cy) { int16_t px; int16_t py; uint8_t v; if (cx < 0 || cx >= WALK_GRID_W || cy < 0 || cy >= WALK_GRID_H) { return false; } px = (int16_t)((cx * WALK_CELL_PX) + (WALK_CELL_PX / 2)); py = (int16_t)((cy * WALK_CELL_PX) + (WALK_CELL_PX / 2)); v = jlSamplePixel(gControl, px, py); return v != CTRL_BLOCK; } static uint8_t gBfsParent[WALK_GRID_H][WALK_GRID_W]; static int16_t gBfsQueueX[WALK_GRID_H * WALK_GRID_W]; static int16_t gBfsQueueY[WALK_GRID_H * WALK_GRID_W]; static bool findPath(int16_t sx, int16_t sy, int16_t tx, int16_t ty) { int16_t qhead; int16_t qtail; int16_t cx, cy; int16_t nx, ny; uint8_t d; int16_t scx, scy, tcx, tcy; int16_t row, col; scx = (int16_t)(sx / WALK_CELL_PX); scy = (int16_t)(sy / WALK_CELL_PX); tcx = (int16_t)(tx / WALK_CELL_PX); tcy = (int16_t)(ty / WALK_CELL_PX); /* Snap unwalkable target to nearest walkable neighbor. Keeps * "click on a tree trunk" working: the actor walks to the foot * of the tree, not into the trunk pixels. */ if (!cellWalkable(tcx, tcy)) { int16_t r; int16_t dx, dy; bool found; found = false; for (r = 1; r < 12 && !found; r++) { for (dy = -r; dy <= r && !found; dy++) { for (dx = -r; dx <= r && !found; dx++) { if (cellWalkable((int16_t)(tcx + dx), (int16_t)(tcy + dy))) { tcx = (int16_t)(tcx + dx); tcy = (int16_t)(tcy + dy); found = true; } } } } if (!found) { gPathLen = 0; return false; } } for (row = 0; row < WALK_GRID_H; row++) { for (col = 0; col < WALK_GRID_W; col++) { gBfsParent[row][col] = 0xFF; } } qhead = 0; qtail = 0; gBfsQueueX[qtail] = scx; gBfsQueueY[qtail] = scy; qtail++; gBfsParent[scy][scx] = 0xFE; while (qhead < qtail) { cx = gBfsQueueX[qhead]; cy = gBfsQueueY[qhead]; qhead++; if (cx == tcx && cy == tcy) { break; } for (d = 0; d < 4; d++) { nx = cx; ny = cy; switch (d) { case 0: ny = (int16_t)(cy - 1); break; case 1: nx = (int16_t)(cx + 1); break; case 2: ny = (int16_t)(cy + 1); break; default: nx = (int16_t)(cx - 1); break; } if (!cellWalkable(nx, ny)) { continue; } if (gBfsParent[ny][nx] != 0xFF) { continue; } gBfsParent[ny][nx] = d; gBfsQueueX[qtail] = nx; gBfsQueueY[qtail] = ny; qtail++; } } if (gBfsParent[tcy][tcx] == 0xFF) { gPathLen = 0; return false; } { int16_t pathRevX[256]; int16_t pathRevY[256]; int16_t n; int16_t i; n = 0; cx = tcx; cy = tcy; while (n < 256) { pathRevX[n] = cx; pathRevY[n] = cy; n++; d = gBfsParent[cy][cx]; if (d == 0xFE) { break; } switch (d) { case 0: cy = (int16_t)(cy + 1); break; case 1: cx = (int16_t)(cx - 1); break; case 2: cy = (int16_t)(cy - 1); break; default: cx = (int16_t)(cx + 1); break; } } gPathLen = 0; for (i = (int16_t)(n - 1); i >= 0; i--) { gPathX[gPathLen] = (int16_t)((pathRevX[i] * WALK_CELL_PX) + (WALK_CELL_PX / 2)); gPathY[gPathLen] = (int16_t)((pathRevY[i] * WALK_CELL_PX) + (WALK_CELL_PX / 2)); gPathLen++; if (gPathLen >= 256) { break; } } gPathStep = 0; } return gPathLen > 0; } /* =================================================================== * Section 11 -- Ego motion * =================================================================== */ static int16_t egoFeetX(void) { return (int16_t)(gEgoX + (EGO_W / 2)); } static int16_t egoFeetY(void) { return (int16_t)(gEgoY + EGO_H); } static void egoSetFeet(int16_t fx, int16_t fy) { gEgoX = (int16_t)(fx - (EGO_W / 2)); gEgoY = (int16_t)(fy - EGO_H); } #define EGO_WALK_SPEED 1 #define ANIM_TICKS 6 static void advanceEgo(void) { int16_t fx, fy; int16_t tx, ty; int16_t dx, dy; int16_t adx, ady; int16_t step; if (!gEgoMoving) { return; } if (gPathStep >= gPathLen) { gEgoMoving = false; return; } fx = egoFeetX(); fy = egoFeetY(); tx = gPathX[gPathStep]; ty = gPathY[gPathStep]; dx = (int16_t)(tx - fx); dy = (int16_t)(ty - fy); adx = (int16_t)(dx < 0 ? -dx : dx); ady = (int16_t)(dy < 0 ? -dy : dy); if (adx <= EGO_WALK_SPEED && ady <= EGO_WALK_SPEED) { egoSetFeet(tx, ty); gPathStep++; if (gPathStep >= gPathLen) { gEgoMoving = false; gEgoFrameIdx = 0; } return; } if (adx > ady) { step = (int16_t)(dx > 0 ? EGO_WALK_SPEED : -EGO_WALK_SPEED); fx = (int16_t)(fx + step); gEgoDir = (step > 0) ? DIR_E : DIR_W; } else { step = (int16_t)(dy > 0 ? EGO_WALK_SPEED : -EGO_WALK_SPEED); fy = (int16_t)(fy + step); gEgoDir = (step > 0) ? DIR_S : DIR_N; } egoSetFeet(fx, fy); gEgoAnimTick++; if (gEgoAnimTick >= ANIM_TICKS) { gEgoAnimTick = 0; gEgoFrameIdx++; if (gEgoFrameIdx >= EGO_FRAMES) { gEgoFrameIdx = 1; } if (gEgoFrameIdx == 0) { gEgoFrameIdx = 1; } } } /* Check the control buffer at the actor's feet and fire any * trigger that lives there. Called once per frame after motion. */ static void runFootTriggers(void) { uint8_t v; v = jlSamplePixel(gControl, egoFeetX(), (int16_t)(egoFeetY() - 1)); if (v == CTRL_TRIGGER_LOOK) { if (gMessageTtl == 0) { setMessage("You smell pine and damp earth."); } } } /* =================================================================== * Section 12 -- Verb cursor + UI * =================================================================== */ static const char *verbName(VerbE v) { switch (v) { case VERB_WALK: return "Walk"; case VERB_LOOK: return "Look"; case VERB_TAKE: return "Take"; default: return "?"; } } /* Draw the verb cursor at the mouse position. Like the actor draw, * cursors are walked from ASCII rows and plotted via jlDrawPixel. * Cursors do NOT consult the priority buffer -- the cursor is * always on top. */ static void drawCursor(int16_t mx, int16_t my, VerbE v) { const char * const *rows; int16_t r; int16_t c; int16_t px, py; uint8_t color; rows = gCursorFrames[v]; for (r = 0; r < 16; r++) { py = (int16_t)(my - 6 + r); if (py < 0 || py >= SURFACE_HEIGHT) { continue; } for (c = 0; c < 16; c++) { px = (int16_t)(mx - 6 + c); if (px < 0 || px >= SURFACE_WIDTH) { continue; } color = pixCharToColor(rows[r][c]); if (color == C_TRANS) { continue; } jlDrawPixel(gScreen, px, py, color); } } } static void drawMessageBar(void) { jlFillRect(gScreen, 0, MSG_BAR_Y, SURFACE_WIDTH, MSG_BAR_H, UI_BG); jlFillRect(gScreen, 0, MSG_BAR_Y, SURFACE_WIDTH, 1, UI_DIM); if (gMessage[0] != '\0') { drawText(4, (int16_t)(MSG_BAR_Y + 2), gMessage, UI_INK); } } static void drawInventoryBar(void) { char buf[40]; const char *vw; jlFillRect(gScreen, 0, INV_BAR_Y, SURFACE_WIDTH, INV_BAR_H, UI_BG); jlFillRect(gScreen, 0, INV_BAR_Y, SURFACE_WIDTH, 1, UI_DIM); snprintf(buf, sizeof(buf), "VERB: %s", verbName(gCurVerb)); drawText(4, (int16_t)(INV_BAR_Y + 2), buf, UI_HILITE); switch (gDebugView) { case VIEW_PRIORITY: vw = "VIEW: PRIORITY (C ctrl, N normal)"; break; case VIEW_CONTROL: vw = "VIEW: CONTROL (P pri, N normal)"; break; default: vw = "VIEW: NORMAL (P pri, C ctrl)"; break; } drawText(4, (int16_t)(INV_BAR_Y + 11), vw, UI_INK); } #ifdef JOEYLIB_PLATFORM_IIGS segment "ADV2COMP"; #endif /* =================================================================== * Section 13 -- Frame compositor * =================================================================== */ static void composeFrameNormal(int16_t mx, int16_t my) { jlSurfaceCopy(gScreen, gVisual); drawEgoMasked(gEgoX, gEgoY, gEgoDir, gEgoFrameIdx); drawMessageBar(); drawInventoryBar(); drawCursor(mx, my, gCurVerb); jlStagePresent(); } static void composeFrameDebug(int16_t mx, int16_t my) { /* The debug overlays blit the raw priority/control buffer as the * play-area picture; the palette swap (PAL_DEBUG_PRI or * PAL_DEBUG_CTRL) makes the codes visible. */ jlSurfaceCopy(gScreen, (gDebugView == VIEW_PRIORITY) ? gPriority : gControl); /* Show where the actor is by overlaying ego pixels in pure white * with priority always-on. */ { const char * const *rows; int16_t r; int16_t c; int16_t px; int16_t py; rows = gEgoFrames[gEgoDir][gEgoFrameIdx]; for (r = 0; r < EGO_H; r++) { py = (int16_t)(gEgoY + r); if (py < 0 || py >= PLAY_AREA_H) { continue; } for (c = 0; c < EGO_W_PAD; c++) { px = (int16_t)(gEgoX + c); if (px < 0 || px >= SURFACE_WIDTH) { continue; } if (pixCharToColor(rows[r][c]) == C_TRANS) { continue; } jlDrawPixel(gScreen, px, py, 15); } } } drawMessageBar(); drawInventoryBar(); drawCursor(mx, my, gCurVerb); jlStagePresent(); } /* =================================================================== * Section 14 -- Click handling * =================================================================== */ static void cycleVerb(void) { gCurVerb = (VerbE)((gCurVerb + 1) % VERB_COUNT); } static void handleClick(int16_t mx, int16_t my, bool leftClick, bool rightClick) { uint8_t ctrlHere; if (rightClick) { cycleVerb(); return; } if (!leftClick) { return; } if (my >= PLAY_AREA_H) { return; } /* Inspect the control screen under the click. Trigger codes * give the player a "look" hint without walking. */ ctrlHere = jlSamplePixel(gControl, mx, my); switch (gCurVerb) { case VERB_LOOK: if (ctrlHere == CTRL_BLOCK) { /* Could be sky, tree trunk, rock... distinguish by * looking at the priority value at the same pixel. */ uint8_t pri; pri = jlSamplePixel(gPriority, mx, my); if (pri == PRI_SKY) { setMessage("Blue sky through the canopy."); } else { uint8_t vis; vis = jlSamplePixel(gVisual, mx, my); if (vis == C_GRAY || vis == C_WHITE || vis == C_DARK_GRAY) { setMessage("A weather-rounded rock."); } else { setMessage("A tall, narrow tree."); } } } else if (ctrlHere == CTRL_TRIGGER_LOOK) { setMessage("The forest is deeper here. Pine and damp earth."); } else { setMessage("Soft, well-trodden ground."); } return; case VERB_TAKE: if (ctrlHere == CTRL_BLOCK) { setMessage("You can't pick that up."); } else { setMessage("There is nothing here to take."); } return; case VERB_WALK: default: if (findPath(egoFeetX(), egoFeetY(), mx, my)) { gEgoMoving = true; } return; } } #ifdef JOEYLIB_PLATFORM_IIGS segment ""; #endif /* =================================================================== * Section 15 -- main() * =================================================================== */ int main(void) { jlConfigT config; bool leftPressed; bool rightPressed; /* No compiled sprites used in this demo -- everything is plotted * pixel-by-pixel against the priority screen. Codegen budget can * be small. We need 4 surfaces (stage + visual + priority + * control). */ config.codegenBytes = 4UL * 1024; config.maxSurfaces = 8; config.audioBytes = 0; if (!jlInit(&config)) { fprintf(stderr, "jlInit failed: %s\n", jlLastError()); return 1; } gScreen = jlStageGet(); if (gScreen == NULL) { fprintf(stderr, "jlStageGet returned NULL\n"); jlShutdown(); return 1; } gVisual = jlSurfaceCreate(); gPriority = jlSurfaceCreate(); gControl = jlSurfaceCreate(); if (gVisual == NULL || gPriority == NULL || gControl == NULL) { fprintf(stderr, "Failed to allocate parallel screens: %s\n", jlLastError()); jlShutdown(); return 1; } buildPalettes(); gDebugView = VIEW_NORMAL; programScbForView(gDebugView); paintForestVisual(); paintForestPriority(); paintForestControl(); paintAllProps(); /* Plant the ego near the front of the scene, idle, facing south. */ egoSetFeet(SURFACE_WIDTH / 2, 150); gEgoDir = DIR_S; gEgoFrameIdx = 0; gEgoAnimTick = 0; gEgoMoving = false; gPathLen = 0; gPathStep = 0; gCurVerb = VERB_WALK; gMessage[0] = '\0'; gMessageTtl = 0; setMessage("Click to walk. P = priority view, C = control view."); for (;;) { int16_t mx, my; jlInputPoll(); if (jlKeyPressed(KEY_ESCAPE)) { break; } if (jlKeyPressed(KEY_1)) { gCurVerb = VERB_WALK; } if (jlKeyPressed(KEY_2)) { gCurVerb = VERB_LOOK; } if (jlKeyPressed(KEY_3)) { gCurVerb = VERB_TAKE; } if (jlKeyPressed(KEY_SPACE)) { cycleVerb(); } /* Debug overlay toggles. Letters chosen because the IIgs's * stock Apple Keyboard II has no function keys; P/C/N work * on every target's standard keyboard. */ if (jlKeyPressed(KEY_P)) { gDebugView = (gDebugView == VIEW_PRIORITY) ? VIEW_NORMAL : VIEW_PRIORITY; programScbForView(gDebugView); } if (jlKeyPressed(KEY_C)) { gDebugView = (gDebugView == VIEW_CONTROL) ? VIEW_NORMAL : VIEW_CONTROL; programScbForView(gDebugView); } if (jlKeyPressed(KEY_N)) { gDebugView = VIEW_NORMAL; programScbForView(gDebugView); } mx = jlMouseX(); my = jlMouseY(); if (mx < 0) { mx = 0; } if (my < 0) { my = 0; } if (mx >= SURFACE_WIDTH) { mx = SURFACE_WIDTH - 1; } if (my >= SURFACE_HEIGHT) { my = SURFACE_HEIGHT - 1; } leftPressed = jlMousePressed(MOUSE_BUTTON_LEFT); rightPressed = jlMousePressed(MOUSE_BUTTON_RIGHT); if (leftPressed || rightPressed) { handleClick(mx, my, leftPressed, rightPressed); } advanceEgo(); runFootTriggers(); if (gMessageTtl > 0) { gMessageTtl--; if (gMessageTtl == 0) { gMessage[0] = '\0'; } } if (gDebugView == VIEW_NORMAL) { composeFrameNormal(mx, my); } else { composeFrameDebug(mx, my); } } jlShutdown(); return 0; }