Full credit goes to Spherical Ice, all I did was create the guide and update a tiny bit of code
Original Source: Spherical Ice's pokecommunity post
Just a simple port of the Ruins of Alph sliding puzzles from HG/SS. All four puzzles (Kabuto, Omanyte, Aerodactyl and Ho-Oh) are included with initial layouts as close as possible to how they appear in HG/SS (with some tiles moved due to the loss of the top and bottom rows of tiles for the smaller GBA screen size).
How To Use
pull from Spherical Ice's branch or follow the below guide:
git remote add sphericalice https://github.com/sphericalice/pokeemerald
git pull sphericalice sliding_puzzles
if pulling from Spherical Ice's branch, use the below "Manual Changes" sections only to manually change/update the code to compile in up-to-date pokeemerald
Pulling From Branch
After pulling from Spherical Ice's branch, make the following manual changes
spritesheet_rules.mk
-- IMPORTANT: only make this change if you have an older version of pokeemerald and still use spriteskeet_rules.mk
-- if using an upto date version that uses INCGFX for sprites, make the alternate change for file src/data/sliding_puzzles.h
add to the top of the file after the first few entries:
SLIDINGPUZZLESDIR := graphics/sliding_puzzle/puzzles
$(SLIDINGPUZZLESDIR)/kabuto/tiles.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 4 -mheight 4
$(SLIDINGPUZZLESDIR)/omanyte/tiles.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 4 -mheight 4
$(SLIDINGPUZZLESDIR)/aerodactyl/tiles.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 4 -mheight 4
$(SLIDINGPUZZLESDIR)/ho_oh/tiles.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 4 -mheight 4
[root] ld_script.ld
If pulling from Spherical Ice's branch, you may get an error saying to "commit or stash changes" to this file (This seems to be normal with changes to this file, so don't worry). Make the two changes:
add at line 344, before } > ROM =0
src/sliding_puzzle.o(.text);
add at now line 708, before } > ROM =0
src/sliding_puzzle.o(.rodata);
include/constants/flags.h
define 4 new flags
FLAG_SLIDING_PUZZLE_KABUTO_SOLVED
FLAG_SLIDING_PUZZLE_OMANYTE_SOLVED
FLAG_SLIDING_PUZZLE_AERODACTYL_SOLVED
FLAG_SLIDING_PUZZLE_HO_OH_SOLVED
src/data/sliding_puzzles.h
--IMPORTANT: only make this change if you use an up-to-date version of pokeemerald that uses INCGFX for sprites and no longer uses spritesheet_rules.mk
change the first 4 lines from:
static const u32 sKabutoPuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/kabuto/tiles.4bpp.lz");
static const u32 sOmanytePuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/omanyte/tiles.4bpp.lz");
static const u32 sAerodactylPuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/aerodactyl/tiles.4bpp.lz");
static const u32 sHoOhPuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/ho_oh/tiles.4bpp.lz");
to
static const u32 sKabutoPuzzle_Gfx[] = INCGFX_U32("graphics/sliding_puzzle/puzzles/kabuto/tiles.png", ".4bpp.lz", "-mwidth 4 -mheight 4");
static const u32 sOmanytePuzzle_Gfx[] = INCGFX_U32("graphics/sliding_puzzle/puzzles/omanyte/tiles.png", ".4bpp.lz", "-mwidth 4 -mheight 4");
static const u32 sAerodactylPuzzle_Gfx[] = INCGFX_U32("graphics/sliding_puzzle/puzzles/aerodactyl/tiles.png", ".4bpp.lz", "-mwidth 4 -mheight 4");
static const u32 sHoOhPuzzle_Gfx[] = INCGFX_U32("graphics/sliding_puzzle/puzzles/ho_oh/tiles.png", ".4bpp.lz", "-mwidth 4 -mheight 4");
[map]/scripts.inc
add the below code to your map scripts.inc or wherever you keep your added code
in porymap, however you want to access the puzzle, set these as the script:
SlidingPuzzle_EventScript_Kabuto_Start
SlidingPuzzle_EventScript_Omanyte_Start
SlidingPuzzle_EventScript_Aerodactyl_Start
SlidingPuzzle_EventScript_Ho_oh_Start
//Sliding Puzzles Start
SlidingPuzzle_EventScript_Kabuto_Start::
goto_if_set FLAG_SLIDING_PUZZLE_KABUTO_SOLVED, SlidingPuzzle_EventScript_Kabuto_Solved
fadescreen FADE_TO_BLACK
setvar VAR_0x8004, SLIDING_PUZZLE_KABUTO
setvar VAR_0x8005, FALSE
special DoSlidingPuzzle
waitstate
goto_if_eq VAR_RESULT, TRUE, SlidingPuzzle_EventScript_Kabuto_SetFlag
release
end
SlidingPuzzle_EventScript_Kabuto_SetFlag:
setflag FLAG_SLIDING_PUZZLE_KABUTO_SOLVED
release
end
SlidingPuzzle_EventScript_Kabuto_Solved:
fadescreen FADE_TO_BLACK
setvar VAR_0x8004, SLIDING_PUZZLE_KABUTO
setvar VAR_0x8005, TRUE
special DoSlidingPuzzle
waitstate
release
end
SlidingPuzzle_EventScript_Omanyte_Start::
goto_if_set FLAG_SLIDING_PUZZLE_OMANYTE_SOLVED, SlidingPuzzle_EventScript_Omanyte_Solved
fadescreen FADE_TO_BLACK
setvar VAR_0x8004, SLIDING_PUZZLE_OMANYTE
setvar VAR_0x8005, FALSE
special DoSlidingPuzzle
waitstate
goto_if_eq VAR_RESULT, TRUE, SlidingPuzzle_EventScript_Omanyte_SetFlag
release
end
SlidingPuzzle_EventScript_Omanyte_SetFlag:
setflag FLAG_SLIDING_PUZZLE_OMANYTE_SOLVED
release
end
SlidingPuzzle_EventScript_Omanyte_Solved:
fadescreen FADE_TO_BLACK
setvar VAR_0x8004, SLIDING_PUZZLE_OMANYTE
setvar VAR_0x8005, TRUE
special DoSlidingPuzzle
waitstate
release
end
SlidingPuzzle_EventScript_Aerodactyl_Start::
goto_if_set FLAG_SLIDING_PUZZLE_AERODACTYL_SOLVED, SlidingPuzzle_EventScript_Aerodactyl_Solved
fadescreen FADE_TO_BLACK
setvar VAR_0x8004, SLIDING_PUZZLE_AERODACTYL
setvar VAR_0x8005, FALSE
special DoSlidingPuzzle
waitstate
goto_if_eq VAR_RESULT, TRUE, SlidingPuzzle_EventScript_Aerodactyl_SetFlag
release
end
SlidingPuzzle_EventScript_Aerodactyl_SetFlag:
setflag FLAG_SLIDING_PUZZLE_AERODACTYL_SOLVED
release
end
SlidingPuzzle_EventScript_Aerodactyl_Solved:
fadescreen FADE_TO_BLACK
setvar VAR_0x8004, SLIDING_PUZZLE_AERODACTYL
setvar VAR_0x8005, TRUE
special DoSlidingPuzzle
waitstate
release
end
SlidingPuzzle_EventScript_Ho_oh_Start::
goto_if_set FLAG_SLIDING_PUZZLE_HO_OH_SOLVED, SlidingPuzzle_EventScript_Ho_oh_Solved
fadescreen FADE_TO_BLACK
setvar VAR_0x8004, SLIDING_PUZZLE_HO_OH
setvar VAR_0x8005, FALSE
special DoSlidingPuzzle
waitstate
goto_if_eq VAR_RESULT, TRUE, SlidingPuzzle_EventScript_Ho_oh_SetFlag
release
end
SlidingPuzzle_EventScript_Ho_oh_SetFlag:
setflag FLAG_SLIDING_PUZZLE_HO_OH_SOLVED
release
end
SlidingPuzzle_EventScript_Ho_oh_Solved:
fadescreen FADE_TO_BLACK
setvar VAR_0x8004, SLIDING_PUZZLE_HO_OH
setvar VAR_0x8005, TRUE
special DoSlidingPuzzle
waitstate
release
end
Adding The Code To Your Hack Manually
-- if adding this code manually, first download this zip sliding_puzzle.zip, extract and place folder sliding_puzzle in "[root]/graphics" folder (so the paths should end up [root]/graphics/sliding_puzzle/)
Make the changes using the above sections first, then add the below
data/event_scripts.s
add this line somewhere in the file (I added this near the top of the file in alphabetical order), this makes the new code be added to your hack when compiling.
#include "constants/sliding_puzzles.h"
data/specials.inc
add this line at the end of the file
def_special DoSlidingPuzzle
include/constants/sliding_puzzles.h
create a new file called "sliding_puzzles.h" at this location and add the following
#ifndef GUARD_SLIDING_PUZZLES_CONSTANTS_H
#define GUARD_SLIDING_PUZZLES_CONSTANTS_H
#define SLIDING_PUZZLE_KABUTO 0
#define SLIDING_PUZZLE_OMANYTE 1
#define SLIDING_PUZZLE_AERODACTYL 2
#define SLIDING_PUZZLE_HO_OH 3
#define SLIDING_PUZZLE_SOLVED 4
#define SLIDING_PUZZLE_COUNT 5
#endif // GUARD_SLIDING_PUZZLES_CONSTANTS_H
include/sliding_puzzle.h
create a new file called "sliding_puzzle.h" at this location and add the following
#ifndef GUARD_SLIDING_PUZZLE_H
#define GUARD_SLIDING_PUZZLE_H
#include "constants/sliding_puzzles.h"
enum
{
ROTATE_NONE,
ROTATE_ANTICLOCKWISE,
ROTATE_CLOCKWISE,
};
enum
{
ORIENTATION_0,
ORIENTATION_90,
ORIENTATION_180,
ORIENTATION_270,
ORIENTATION_MAX,
};
#define IMMOVABLE_TILE ORIENTATION_MAX
#define NUM_SLIDING_PUZZLE_COLS 6
#define NUM_SLIDING_PUZZLE_ROWS 4
#define FIRST_SLIDING_PUZZLE_COL 0
#define FINAL_SLIDING_PUZZLE_COL (NUM_SLIDING_PUZZLE_COLS - 1)
#define FIRST_SLIDING_PUZZLE_ROW 0
#define FINAL_SLIDING_PUZZLE_ROW (NUM_SLIDING_PUZZLE_ROWS - 1)
struct SlidingPuzzle
{
u8 tiles[NUM_SLIDING_PUZZLE_ROWS][NUM_SLIDING_PUZZLE_COLS];
u8 puzzleId;
u8 cursorSpriteId;
u8 heldTile;
bool8 solved;
};
#endif // GUARD_SLIDING_PUZZLE_H
src/data/sliding_puzzles.h
create a new file called "sliding_puzzles.h" at this location and add the following
--IMPORTANT: depending if you have an older or an up-to-date version of pokeemerald, only make the change to INCGFX if you no longer use spritesheet_rules.mk
static const u32 sKabutoPuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/kabuto/tiles.4bpp.lz");
static const u32 sOmanytePuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/omanyte/tiles.4bpp.lz");
static const u32 sAerodactylPuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/aerodactyl/tiles.4bpp.lz");
static const u32 sHoOhPuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/puzzles/ho_oh/tiles.4bpp.lz");
static const struct CompressedSpriteSheet sSpriteSheet_Tiles[SLIDING_PUZZLE_COUNT] =
{
[SLIDING_PUZZLE_KABUTO] = {sKabutoPuzzle_Gfx, 0x400 * 16, GFXTAG_TILES},
[SLIDING_PUZZLE_OMANYTE] = {sOmanytePuzzle_Gfx, 0x400 * 16, GFXTAG_TILES},
[SLIDING_PUZZLE_AERODACTYL] = {sAerodactylPuzzle_Gfx, 0x400 * 16, GFXTAG_TILES},
[SLIDING_PUZZLE_HO_OH] = {sHoOhPuzzle_Gfx, 0x400 * 16, GFXTAG_TILES},
[SLIDING_PUZZLE_SOLVED] = {},
};
static const u8 sPuzzleLayouts[SLIDING_PUZZLE_COUNT][NUM_SLIDING_PUZZLE_ROWS][NUM_SLIDING_PUZZLE_COLS] =
{
[SLIDING_PUZZLE_KABUTO] =
{
{ 6, 1,__, 3, 4,__},
{__, 5,__, 7, 8,11},
{__,__,10,__,12,__},
{ 2,13,14,15,16, 9},
},
[SLIDING_PUZZLE_OMANYTE] =
{
{ 9,__, 2, 3, 4,11},
{__, 5, 6,12,__,14},
{ 8,__,__,__,__,13},
{ 1, 7,__,15,16,10},
},
[SLIDING_PUZZLE_AERODACTYL] =
{
{14,__,__, 3, 4,__},
{__,__,10, 7, 8,16},
{ 2, 9,__,11,12, 5},
{ 1,13,__,15,__, 6},
},
[SLIDING_PUZZLE_HO_OH] =
{
{15,__,14, 3, 4, 1},
{13,__, 6, 7,__,10},
{ 2,__,__,__,12, 9},
{ 8,__, 5,__,16,11},
},
[SLIDING_PUZZLE_SOLVED] =
{
{__, 1, 2, 3, 4,__},
{__, 5, 6, 7, 8,__},
{__, 9,10,11,12,__},
{__,13,14,15,16,__},
},
};
static const u8 sTileOrientations[SLIDING_PUZZLE_COUNT][NUM_SLIDING_PUZZLE_ROWS][NUM_SLIDING_PUZZLE_COLS] =
{
[SLIDING_PUZZLE_KABUTO] =
{
{ORIENTATION_90, IMMOVABLE_TILE, ORIENTATION_0, IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_0},
{ORIENTATION_0, IMMOVABLE_TILE, ORIENTATION_0, IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_180},
{ORIENTATION_0, ORIENTATION_0, IMMOVABLE_TILE, ORIENTATION_0, IMMOVABLE_TILE, ORIENTATION_0},
{ORIENTATION_270, IMMOVABLE_TILE, IMMOVABLE_TILE, IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_90},
},
[SLIDING_PUZZLE_OMANYTE] =
{
{ORIENTATION_0, ORIENTATION_0, IMMOVABLE_TILE, IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_90},
{ORIENTATION_0, IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_90, ORIENTATION_0, ORIENTATION_180},
{ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_270},
{ORIENTATION_270, ORIENTATION_180, ORIENTATION_0, IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_0},
},
[SLIDING_PUZZLE_AERODACTYL] =
{
{ORIENTATION_180, ORIENTATION_0, ORIENTATION_0, IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_0},
{ORIENTATION_0, ORIENTATION_0, ORIENTATION_270, IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_270},
{ORIENTATION_270, IMMOVABLE_TILE, ORIENTATION_0, IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_90},
{ORIENTATION_180, IMMOVABLE_TILE, ORIENTATION_0, IMMOVABLE_TILE, ORIENTATION_0, ORIENTATION_180},
},
[SLIDING_PUZZLE_HO_OH] =
{
{ORIENTATION_180, ORIENTATION_180, ORIENTATION_90, IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_270},
{ORIENTATION_180, ORIENTATION_0, IMMOVABLE_TILE, IMMOVABLE_TILE, ORIENTATION_0, ORIENTATION_270},
{ORIENTATION_270, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, IMMOVABLE_TILE, ORIENTATION_0},
{ORIENTATION_90, ORIENTATION_0, ORIENTATION_180, ORIENTATION_0, IMMOVABLE_TILE, ORIENTATION_270},
},
[SLIDING_PUZZLE_SOLVED] =
{
{ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0},
{ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0},
{ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0},
{ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0, ORIENTATION_0},
},
};
[root] spritesheet_rules.mk
-- IMPORTANT: only keep this change if you have an older version of pokeemerald and still use spriteskeet_rules.mk
-- if using an upto date version that uses INCGFX for sprites, make the alternate change for file src/data/sliding_puzzles.h
[root] sym_ewram.txt
add to the end of the file
.include "src/sliding_puzzle.o"
src/sliding_puzzle.c
create a new file called "sliding_puzzle.c" at this location and add the following
#include "global.h"
#include "sliding_puzzle.h"
#include "bg.h"
#include "decompress.h"
#include "event_data.h"
#include "gpu_regs.h"
#include "graphics.h"
#include "main.h"
#include "malloc.h"
#include "menu.h"
#include "overworld.h"
#include "palette.h"
#include "scanline_effect.h"
#include "sound.h"
#include "strings.h"
#include "task.h"
#include "text.h"
#include "text_window.h"
#include "window.h"
#include "constants/rgb.h"
#include "constants/songs.h"
static void CB2_LoadSlidingPuzzle(void);
static void CB2_SlidingPuzzle(void);
static void VBlankCB_SlidingPuzzle(void);
static void Task_SlidingPuzzle_WaitFadeIn(u8);
static void Task_SlidingPuzzle_HandleInput(u8);
static void Task_SlidingPuzzle_Glow(u8);
static void Task_SlidingPuzzle_Solved(u8);
static void Task_SlidingPuzzle_Exit(u8);
static void DrawInstructionsBar(u8);
static void CreateCursorSprite(void);
static void CreateTileSprites(void);
static void MoveCursor(s8, s8);
static void MoveCursorSprite(s8, s8, u8);
static bool32 CursorIsOnTile(void);
static bool32 CursorIsOnImmovableTile(void);
static void SelectTile(void);
static void PlaceTile(void);
static void RotateTile(u8);
static void CheckForSolution(void);
static void ExitSlidingPuzzle(u8);
static void SpriteCB_Cursor(struct Sprite *);
static EWRAM_DATA struct SlidingPuzzle *sSlidingPuzzle = NULL;
static const u32 sSlidingPuzzle_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/bg.4bpp.lz");
static const u32 sSlidingPuzzle_Tilemap[] = INCBIN_U32("graphics/sliding_puzzle/map.bin.lz");
static const u16 sSlidingPuzzle_Pal[] = INCBIN_U16("graphics/sliding_puzzle/bg.gbapal");
enum
{
WIN_INSTRUCTIONS_BAR,
WIN_DUMMY,
};
static const struct WindowTemplate sWindowTemplates[] =
{
[WIN_INSTRUCTIONS_BAR] =
{
.bg = 0,
.tilemapLeft = 0,
.tilemapTop = 0,
.width = 30,
.height = 2,
.paletteNum = 13,
.baseBlock = 1,
},
[WIN_DUMMY] = DUMMY_WIN_TEMPLATE,
};
static const struct BgTemplate sBgTemplates[] =
{
{
.bg = 0,
.charBaseIndex = 1,
.mapBaseIndex = 29,
.priority = 3,
},
{
.bg = 1,
.charBaseIndex = 0,
.mapBaseIndex = 7,
.priority = 3,
},
};
static const u16 sColumnXCoords[NUM_SLIDING_PUZZLE_COLS] =
{
32, 72, 104, 136, 168, 208,
};
static const u16 sRowYCoords[NUM_SLIDING_PUZZLE_ROWS] =
{
40,
72,
104,
136,
};
static const u32 sCursor_Gfx[] = INCBIN_U32("graphics/sliding_puzzle/cursor.4bpp.lz");
static const u16 sCursorTiles_Pal[] = INCBIN_U16("graphics/sliding_puzzle/cursor_tiles.gbapal");
#define TAG_CURSOR 244
#define GFXTAG_TILES 245
static const struct SpritePalette sSpritePalettes_CursorTiles[] =
{
{
.data = sCursorTiles_Pal,
.tag = TAG_CURSOR
},
{},
};
static const struct CompressedSpriteSheet sSpriteSheet_Cursor =
{
.data = sCursor_Gfx,
.size = 0x400,
.tag = TAG_CURSOR,
};
static const struct OamData sOamData_Cursor =
{
.y = 0,
.affineMode = ST_OAM_AFFINE_OFF,
.objMode = ST_OAM_OBJ_NORMAL,
.mosaic = 0,
.bpp = ST_OAM_4BPP,
.shape = SPRITE_SHAPE(32x32),
.x = 0,
.matrixNum = 0,
.size = SPRITE_SIZE(32x32),
.tileNum = 0,
.priority = 2,
.paletteNum = 0,
.affineParam = 0,
};
static const struct SpriteTemplate sSpriteTemplate_Cursor =
{
.tileTag = TAG_CURSOR,
.paletteTag = TAG_CURSOR,
.oam = &sOamData_Cursor,
.anims = gDummySpriteAnimTable,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCB_Cursor,
};
static const struct OamData sOamData_Tiles =
{
.y = 0,
.affineMode = ST_OAM_AFFINE_NORMAL,
.objMode = ST_OAM_OBJ_NORMAL,
.mosaic = 0,
.bpp = ST_OAM_4BPP,
.shape = SPRITE_SHAPE(32x32),
.x = 0,
.matrixNum = 0,
.size = SPRITE_SIZE(32x32),
.tileNum = 0,
.priority = 2,
.paletteNum = 0,
.affineParam = 0,
};
static const union AnimCmd sAnim_Tile0[] =
{
ANIMCMD_FRAME(0 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile1[] =
{
ANIMCMD_FRAME(1 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile2[] =
{
ANIMCMD_FRAME(2 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile3[] =
{
ANIMCMD_FRAME(3 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile4[] =
{
ANIMCMD_FRAME(4 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile5[] =
{
ANIMCMD_FRAME(5 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile6[] =
{
ANIMCMD_FRAME(6 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile7[] =
{
ANIMCMD_FRAME(7 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile8[] =
{
ANIMCMD_FRAME(8 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile9[] =
{
ANIMCMD_FRAME(9 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile10[] =
{
ANIMCMD_FRAME(10 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile11[] =
{
ANIMCMD_FRAME(11 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile12[] =
{
ANIMCMD_FRAME(12 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile13[] =
{
ANIMCMD_FRAME(13 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile14[] =
{
ANIMCMD_FRAME(14 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd sAnim_Tile15[] =
{
ANIMCMD_FRAME(15 * 16, 30),
ANIMCMD_END,
};
static const union AnimCmd *const sAnims_Tiles[] =
{
sAnim_Tile0,
sAnim_Tile1,
sAnim_Tile2,
sAnim_Tile3,
sAnim_Tile4,
sAnim_Tile5,
sAnim_Tile6,
sAnim_Tile7,
sAnim_Tile8,
sAnim_Tile9,
sAnim_Tile10,
sAnim_Tile11,
sAnim_Tile12,
sAnim_Tile13,
sAnim_Tile14,
sAnim_Tile15,
};
static const union AffineAnimCmd sSpriteAffineAnim_Rotated0[] =
{
AFFINEANIMCMD_FRAME(0x100, 0x100, 0, 0),
AFFINEANIMCMD_JUMP(0),
};
static const union AffineAnimCmd sSpriteAffineAnim_Rotated90[] =
{
AFFINEANIMCMD_FRAME(0x100, 0x100, -64, 0),
AFFINEANIMCMD_JUMP(0),
};
static const union AffineAnimCmd sSpriteAffineAnim_Rotated180[] =
{
AFFINEANIMCMD_FRAME(0x100, 0x100, -128, 0),
AFFINEANIMCMD_JUMP(0),
};
static const union AffineAnimCmd sSpriteAffineAnim_Rotated270[] =
{
AFFINEANIMCMD_FRAME(0x100, 0x100, 64, 0),
AFFINEANIMCMD_JUMP(0),
};
static const union AffineAnimCmd sSpriteAffineAnim_RotatingClockwise0to90[] =
{
AFFINEANIMCMD_FRAME(0x100, 0x100, 0, 0),
AFFINEANIMCMD_FRAME(0x0, 0x0, -8, 8),
AFFINEANIMCMD_END,
};
static const union AffineAnimCmd sSpriteAffineAnim_RotatingClockwise90to180[] =
{
AFFINEANIMCMD_FRAME(0x100, 0x100, -64, 0),
AFFINEANIMCMD_FRAME(0x0, 0x0, -8, 8),
AFFINEANIMCMD_END,
};
static const union AffineAnimCmd sSpriteAffineAnim_RotatingClockwise180to270[] =
{
AFFINEANIMCMD_FRAME(0x100, 0x100, -128, 0),
AFFINEANIMCMD_FRAME(0x0, 0x0, -8, 8),
AFFINEANIMCMD_END,
};
static const union AffineAnimCmd sSpriteAffineAnim_RotatingClockwise270to360[] =
{
AFFINEANIMCMD_FRAME(0x100, 0x100, 64, 0),
AFFINEANIMCMD_FRAME(0x0, 0x0, -8, 8),
AFFINEANIMCMD_END,
};
static const union AffineAnimCmd sSpriteAffineAnim_RotatingAnticlockwise360to270[] =
{
AFFINEANIMCMD_FRAME(0x100, 0x100, 0, 0),
AFFINEANIMCMD_FRAME(0x0, 0x0, 8, 8),
AFFINEANIMCMD_END,
};
static const union AffineAnimCmd sSpriteAffineAnim_RotatingAnticlockwise270to180[] =
{
AFFINEANIMCMD_FRAME(0x100, 0x100, 64, 0),
AFFINEANIMCMD_FRAME(0x0, 0x0, 8, 8),
AFFINEANIMCMD_END,
};
static const union AffineAnimCmd sSpriteAffineAnim_RotatingAnticlockwise180to90[] =
{
AFFINEANIMCMD_FRAME(0x100, 0x100, -128, 0),
AFFINEANIMCMD_FRAME(0x0, 0x0, 8, 8),
AFFINEANIMCMD_END,
};
static const union AffineAnimCmd sSpriteAffineAnim_RotatingAnticlockwise90to0[] =
{
AFFINEANIMCMD_FRAME(0x100, 0x100, -64, 0),
AFFINEANIMCMD_FRAME(0x0, 0x0, 8, 8),
AFFINEANIMCMD_END,
};
static const union AffineAnimCmd *const sSpriteAffineAnimTable_RotateTile[] =
{
sSpriteAffineAnim_Rotated0,
sSpriteAffineAnim_Rotated90,
sSpriteAffineAnim_Rotated180,
sSpriteAffineAnim_Rotated270,
sSpriteAffineAnim_RotatingAnticlockwise360to270,
sSpriteAffineAnim_RotatingAnticlockwise90to0,
sSpriteAffineAnim_RotatingAnticlockwise180to90,
sSpriteAffineAnim_RotatingAnticlockwise270to180,
sSpriteAffineAnim_RotatingClockwise0to90,
sSpriteAffineAnim_RotatingClockwise90to180,
sSpriteAffineAnim_RotatingClockwise180to270,
sSpriteAffineAnim_RotatingClockwise270to360,
};
static const struct SpriteTemplate sSpriteTemplate_Tiles =
{
.tileTag = GFXTAG_TILES,
.paletteTag = TAG_CURSOR,
.oam = &sOamData_Tiles,
.anims = sAnims_Tiles,
.images = NULL,
.affineAnims = sSpriteAffineAnimTable_RotateTile,
.callback = SpriteCallbackDummy,
};
#define __ 0
#include "data/sliding_puzzles.h"
enum
{
INSTRUCTION_NO_SELECTION,
INSTRUCTION_PICK_UP,
INSTRUCTION_PLACE,
INSTRUCTION_SWAP,
INSTRUCTION_ROTATE,
INSTRUCTION_CONTINUE,
};
static const u8 sText_MoveQuit[] = _("{DPAD_NONE} Move {B_BUTTON} Quit");
static const u8 sText_MovePickUpQuit[] = _("{DPAD_NONE} Move {A_BUTTON} Pick Up {B_BUTTON} Quit");
static const u8 sText_MovePlaceRotateQuit[] = _("{DPAD_NONE} Move {A_BUTTON} Place {L_BUTTON}{R_BUTTON} Rotate {B_BUTTON} Quit");
static const u8 sText_MoveSwapRotateQuit[] = _("{DPAD_NONE} Move {A_BUTTON} Swap {L_BUTTON}{R_BUTTON} Rotate {B_BUTTON} Quit");
static const u8 sText_MoveRotateQuit[] = _("{DPAD_NONE} Move {L_BUTTON}{R_BUTTON} Rotate {B_BUTTON} Quit");
static const u8 sText_Continue[] = _("{A_BUTTON}{B_BUTTON} Continue");
static const u8 *const sInstructions[] =
{
[INSTRUCTION_NO_SELECTION] = sText_MoveQuit,
[INSTRUCTION_PICK_UP] = sText_MovePickUpQuit,
[INSTRUCTION_PLACE] = sText_MovePlaceRotateQuit,
[INSTRUCTION_SWAP] = sText_MoveSwapRotateQuit,
[INSTRUCTION_ROTATE] = sText_MoveRotateQuit,
[INSTRUCTION_CONTINUE] = sText_Continue,
};
void DoSlidingPuzzle(void)
{
SetMainCallback2(CB2_LoadSlidingPuzzle);
gMain.savedCallback = CB2_ReturnToFieldContinueScriptPlayMapMusic;
}
static void CB2_LoadSlidingPuzzle(void)
{
switch (gMain.state)
{
case 0:
SetVBlankCallback(NULL);
SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_ON | DISPCNT_BG_ALL_ON | DISPCNT_OBJ_1D_MAP);
SetGpuReg(REG_OFFSET_BLDCNT, 0);
SetGpuReg(REG_OFFSET_BLDALPHA, 0);
SetGpuReg(REG_OFFSET_BLDY, 0);
SetGpuReg(REG_OFFSET_BG3CNT, 0);
SetGpuReg(REG_OFFSET_BG2CNT, 0);
SetGpuReg(REG_OFFSET_BG1CNT, 0);
SetGpuReg(REG_OFFSET_BG0CNT, 0);
SetGpuReg(REG_OFFSET_BG3HOFS, 0);
SetGpuReg(REG_OFFSET_BG3VOFS, 0);
SetGpuReg(REG_OFFSET_BG2HOFS, 0);
SetGpuReg(REG_OFFSET_BG2VOFS, 0);
SetGpuReg(REG_OFFSET_BG1HOFS, 0);
SetGpuReg(REG_OFFSET_BG1VOFS, 0);
SetGpuReg(REG_OFFSET_BG0HOFS, 0);
SetGpuReg(REG_OFFSET_BG0VOFS, 0);
break;
case 1:
ScanlineEffect_Stop();
ResetTasks();
ResetSpriteData();
FreeAllSpritePalettes();
ResetPaletteFade();
CpuFill32(0, (void *)VRAM, VRAM_SIZE);
ResetBgsAndClearDma3BusyFlags(0);
break;
case 2:
InitBgsFromTemplates(0, sBgTemplates, ARRAY_COUNT(sBgTemplates));
InitWindows(sWindowTemplates);
break;
case 3:
sSlidingPuzzle = AllocZeroed(sizeof(*sSlidingPuzzle));
sSlidingPuzzle->heldTile = __;
sSlidingPuzzle->puzzleId = gSpecialVar_0x8004;
sSlidingPuzzle->solved = gSpecialVar_0x8005;
break;
case 4:
LoadPalette(GetTextWindowPalette(2), 0xD0, 32);
if (sSlidingPuzzle->solved)
DrawInstructionsBar(INSTRUCTION_CONTINUE);
else
DrawInstructionsBar(INSTRUCTION_NO_SELECTION);
ShowBg(0);
break;
case 5:
LoadPalette(sSlidingPuzzle_Pal, 0, 32);
LZ77UnCompVram(sSlidingPuzzle_Gfx, (void *)(BG_CHAR_ADDR(0)));
LZ77UnCompVram(sSlidingPuzzle_Tilemap, (void *)(BG_SCREEN_ADDR(7)));
ShowBg(1);
break;
case 6:
LoadSpritePalettes(sSpritePalettes_CursorTiles);
BlendPalettes(PALETTES_ALL, 16, RGB_BLACK);
break;
case 7:
LoadCompressedSpriteSheet(&sSpriteSheet_Cursor);
CreateCursorSprite();
break;
case 8:
LoadCompressedSpriteSheet(&sSpriteSheet_Tiles[sSlidingPuzzle->puzzleId]);
CreateTileSprites();
break;
default:
BeginNormalPaletteFade(PALETTES_ALL, 0, 16, 0, RGB_BLACK);
CreateTask(Task_SlidingPuzzle_WaitFadeIn, 0);
SetVBlankCallback(VBlankCB_SlidingPuzzle);
SetMainCallback2(CB2_SlidingPuzzle);
return;
}
gMain.state++;
}
static void CB2_SlidingPuzzle(void)
{
RunTasks();
AnimateSprites();
BuildOamBuffer();
DoScheduledBgTilemapCopiesToVram();
UpdatePaletteFade();
}
static void VBlankCB_SlidingPuzzle(void)
{
LoadOam();
ProcessSpriteCopyRequests();
TransferPlttBuffer();
}
static void Task_SlidingPuzzle_WaitFadeIn(u8 taskId)
{
if (!gPaletteFade.active)
{
if (!sSlidingPuzzle->solved)
gTasks[taskId].func = Task_SlidingPuzzle_HandleInput;
else
gTasks[taskId].func = Task_SlidingPuzzle_Solved;
}
}
#define sTimer data[0]
#define sAnimating data[1]
#define sRow data[2]
#define sCol data[3]
#define sTileId data[4]
#define sOrientation data[5]
static void Task_SlidingPuzzle_HandleInput(u8 taskId)
{
// Prevent input if the puzzle has been solved
if (sSlidingPuzzle->solved)
{
gTasks[taskId].func = Task_SlidingPuzzle_Glow;
return;
}
// Prevent input while the tile is rotating
if (sSlidingPuzzle->heldTile != __)
{
struct Sprite *sprite = &gSprites[sSlidingPuzzle->heldTile];
if (sprite->sAnimating && !sprite->affineAnimEnded)
return;
}
if (JOY_NEW(B_BUTTON))
ExitSlidingPuzzle(taskId);
else if (JOY_NEW(DPAD_LEFT))
MoveCursor(-1, 0);
else if (JOY_NEW(DPAD_RIGHT))
MoveCursor( 1, 0);
else if (JOY_NEW(DPAD_UP))
MoveCursor( 0, -1);
else if (JOY_NEW(DPAD_DOWN))
MoveCursor( 0, 1);
if (sSlidingPuzzle->heldTile == __)
{
// Not holding a tile
if (CursorIsOnTile() && !CursorIsOnImmovableTile())
{
DrawInstructionsBar(INSTRUCTION_PICK_UP);
if (JOY_NEW(A_BUTTON))
SelectTile();
}
else
{
DrawInstructionsBar(INSTRUCTION_NO_SELECTION);
}
}
else
{
// Currently holding a tile
if (CursorIsOnTile() && !CursorIsOnImmovableTile())
{
DrawInstructionsBar(INSTRUCTION_SWAP);
if (JOY_NEW(A_BUTTON))
SelectTile();
}
else if (CursorIsOnImmovableTile())
{
DrawInstructionsBar(INSTRUCTION_ROTATE);
}
else
{
DrawInstructionsBar(INSTRUCTION_PLACE);
if (JOY_NEW(A_BUTTON))
PlaceTile();
}
if (JOY_NEW(L_BUTTON))
RotateTile(ROTATE_ANTICLOCKWISE);
else if (JOY_NEW(R_BUTTON))
RotateTile(ROTATE_CLOCKWISE);
}
}
#define tState data[0]
#define tTimer data[1]
#define tGlow data[2]
static void Task_SlidingPuzzle_Glow(u8 taskId)
{
u16 color;
s16* data = gTasks[taskId].data;
switch (tState)
{
case 0:
DrawInstructionsBar(INSTRUCTION_CONTINUE);
tState++;
break;
case 1:
DestroySprite(&gSprites[sSlidingPuzzle->cursorSpriteId]);
PlayFanfare(MUS_LEVEL_UP);
tGlow = 22;
tState++;
break;
case 2:
tTimer++;
if ((tTimer % 4) == 0)
{
if (++tGlow == 31)
tState++;
color = RGB(tGlow, tGlow, tGlow);
LoadPalette(&color, 0x103, sizeof(color));
}
break;
case 3:
tTimer++;
if ((tTimer % 4) == 0)
{
tGlow--;
if (tGlow > 22)
{
color = RGB(tGlow, tGlow, tGlow);
}
else
{
color = RGB(28, 22, 13);
tState++;
}
LoadPalette(&color, 0x103, sizeof(color));
}
break;
default:
if (IsFanfareTaskInactive())
gTasks[taskId].func = Task_SlidingPuzzle_Solved;
break;
}
}
#undef tState
#undef tTimer
#undef tGlow
static void Task_SlidingPuzzle_Solved(u8 taskId)
{
if (JOY_NEW(A_BUTTON) || JOY_NEW(B_BUTTON))
ExitSlidingPuzzle(taskId);
}
static void Task_SlidingPuzzle_Exit(u8 taskId)
{
if (!gPaletteFade.active)
{
gSpecialVar_Result = sSlidingPuzzle->solved;
Free(sSlidingPuzzle);
FreeAllWindowBuffers();
SetMainCallback2(gMain.savedCallback);
}
}
static void DrawInstructionsBar(u8 stringId)
{
const u8 color[3] = { TEXT_DYNAMIC_COLOR_6, TEXT_COLOR_WHITE, TEXT_COLOR_DARK_GRAY };
FillWindowPixelBuffer(WIN_INSTRUCTIONS_BAR, PIXEL_FILL(15));
AddTextPrinterParameterized3(WIN_INSTRUCTIONS_BAR, FONT_SMALL, 2, 1, color, 0, sInstructions[stringId]);
PutWindowTilemap(WIN_INSTRUCTIONS_BAR);
CopyWindowToVram(WIN_INSTRUCTIONS_BAR, COPYWIN_FULL);
ScheduleBgCopyTilemapToVram(0);
}
static void CreateCursorSprite(void)
{
u8 spriteId = SPRITE_NONE;
if (!sSlidingPuzzle->solved)
{
spriteId = CreateSprite(&sSpriteTemplate_Cursor,
sColumnXCoords[0],
sRowYCoords[0],
1);
gSprites[spriteId].sTimer = 0;
gSprites[spriteId].sAnimating = TRUE;
gSprites[spriteId].sRow = 0;
gSprites[spriteId].sCol = 0;
}
sSlidingPuzzle->cursorSpriteId = spriteId;
}
static void CreateTileSprites(void)
{
u8 row, col, puzzleId, tile;
for (row = 0; row < NUM_SLIDING_PUZZLE_ROWS; ++row)
{
for (col = 0; col < NUM_SLIDING_PUZZLE_COLS; ++col)
{
if (sSlidingPuzzle->solved)
puzzleId = SLIDING_PUZZLE_SOLVED;
else
puzzleId = sSlidingPuzzle->puzzleId;
tile = sPuzzleLayouts[puzzleId][row][col];
if (tile != __)
{
u8 spriteId = CreateSprite(&sSpriteTemplate_Tiles,
sColumnXCoords[col],
sRowYCoords[row],
2);
struct Sprite *sprite = &gSprites[spriteId];
sprite->sAnimating = FALSE;
sprite->sRow = row;
sprite->sCol = col;
sprite->sTileId = tile;
sprite->sOrientation = sTileOrientations[puzzleId][row][col];
if (sprite->sOrientation >= ORIENTATION_MAX)
sprite->sOrientation = ORIENTATION_0;
StartSpriteAnim(sprite, tile - 1);
StartSpriteAffineAnim(sprite, sprite->sOrientation);
tile = spriteId;
}
sSlidingPuzzle->tiles[row][col] = tile;
}
}
}
static void MoveCursor(s8 deltaX, s8 deltaY)
{
MoveCursorSprite(deltaX, deltaY, sSlidingPuzzle->cursorSpriteId);
if (sSlidingPuzzle->heldTile != __)
{
MoveCursorSprite(deltaX, deltaY, sSlidingPuzzle->heldTile);
PlaySE(SE_BALL_TRAY_ENTER);
}
}
static void MoveCursorSprite(s8 deltaX, s8 deltaY, u8 spriteId)
{
struct Sprite *sprite = &gSprites[spriteId];
s8 row = sprite->sRow + deltaY;
s8 col = sprite->sCol + deltaX;
if (row < FIRST_SLIDING_PUZZLE_ROW)
row = FINAL_SLIDING_PUZZLE_ROW;
else if (row > FINAL_SLIDING_PUZZLE_ROW)
row = FIRST_SLIDING_PUZZLE_ROW;
if (col < FIRST_SLIDING_PUZZLE_COL)
col = FINAL_SLIDING_PUZZLE_COL;
else if (col > FINAL_SLIDING_PUZZLE_COL)
col = FIRST_SLIDING_PUZZLE_COL;
sprite->sCol = col;
sprite->sRow = row;
sprite->x = sColumnXCoords[sprite->sCol];
sprite->y = sRowYCoords[sprite->sRow];
sprite->invisible = FALSE;
sprite->sTimer = 0;
}
static bool32 CursorIsOnTile(void)
{
struct Sprite *cursor = &gSprites[sSlidingPuzzle->cursorSpriteId];
if (sSlidingPuzzle->tiles[cursor->sRow][cursor->sCol] != __)
return TRUE;
return FALSE;
}
static bool32 CursorIsOnImmovableTile(void)
{
struct Sprite *cursor = &gSprites[sSlidingPuzzle->cursorSpriteId];
if (sTileOrientations[sSlidingPuzzle->puzzleId][cursor->sRow][cursor->sCol] == IMMOVABLE_TILE)
return TRUE;
return FALSE;
}
static void SelectTile(void)
{
struct Sprite *cursor = &gSprites[sSlidingPuzzle->cursorSpriteId];
u8 tile = sSlidingPuzzle->heldTile;
if (tile != __)
gSprites[tile].subpriority = 2;
sSlidingPuzzle->heldTile = sSlidingPuzzle->tiles[cursor->sRow][cursor->sCol];
gSprites[sSlidingPuzzle->heldTile].subpriority = 0;
sSlidingPuzzle->tiles[cursor->sRow][cursor->sCol] = tile;
cursor->sAnimating = FALSE;
PlaySE(SE_SELECT);
CheckForSolution();
}
static void PlaceTile(void)
{
struct Sprite *cursor = &gSprites[sSlidingPuzzle->cursorSpriteId];
sSlidingPuzzle->tiles[cursor->sRow][cursor->sCol] = sSlidingPuzzle->heldTile;
gSprites[sSlidingPuzzle->heldTile].subpriority = 2;
sSlidingPuzzle->heldTile = __;
cursor->sAnimating = TRUE;
PlaySE(SE_SELECT);
CheckForSolution();
}
static void RotateTile(u8 rotDir)
{
struct Sprite *sprite = &gSprites[sSlidingPuzzle->heldTile];
u8 affineAnimation;
if (rotDir == ROTATE_ANTICLOCKWISE)
{
affineAnimation = sprite->sOrientation + 4;
if (sprite->sOrientation)
sprite->sOrientation--;
else
sprite->sOrientation = ORIENTATION_270;
}
else if (rotDir == ROTATE_CLOCKWISE)
{
affineAnimation = sprite->sOrientation + 8;
sprite->sOrientation++;
sprite->sOrientation = sprite->sOrientation % ORIENTATION_MAX;
}
StartSpriteAffineAnim(sprite, affineAnimation);
sprite->sAnimating = TRUE;
}
static void CheckForSolution(void)
{
u8 row, col, tile;
for (row = 0; row < NUM_SLIDING_PUZZLE_ROWS; row++)
{
for (col = 0; col < NUM_SLIDING_PUZZLE_COLS; col++)
{
struct Sprite *tile = &gSprites[sSlidingPuzzle->tiles[row][col]];
u8 tileId = sPuzzleLayouts[SLIDING_PUZZLE_SOLVED][row][col];
if (tile->sTileId != tileId || tile->sOrientation != ORIENTATION_0)
{
sSlidingPuzzle->solved = FALSE;
return;
}
}
}
sSlidingPuzzle->solved = TRUE;
}
static void ExitSlidingPuzzle(u8 taskId)
{
BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
gTasks[taskId].func = Task_SlidingPuzzle_Exit;
}
static void SpriteCB_Cursor(struct Sprite *sprite)
{
if (++sprite->sTimer % 16 == 0)
{
sprite->sTimer = 0;
if (sprite->sAnimating)
sprite->invisible ^= TRUE;
else
sprite->invisible = FALSE;
}
}
#undef sTimer
#undef sAnimating
#undef sRow
#undef sCol
#undef sTileId
#undef sOrientation
#undef __
End of guide
From Spherical Ice's pokecommunity post:
fadescreen FADE_TO_BLACK
setvar VAR_0x8004, PUZZLE_KABUTO
setvar VAR_0x8005, FALSE // or TRUE if you want to display the solved puzzle
special DoSlidingPuzzle
waitstate
goto_if_eq VAR_RESULT, TRUE, EventScript_PuzzleSolved
The layouts for puzzles are in src/data/sliding_puzzles.h. To add new puzzles, you'll need to define the new puzzle's ID in include/constants/sliding_puzzles.h, add relevant entries to sPuzzleLayouts and sTileOrientations in src/data/sliding_puzzles.h, add your new puzzle's graphics in graphics/sliding_puzzle/puzzles/ and define the graphics at the top of src/data/sliding_puzzles.h as pointed to in sSpriteSheet_Tiles. I don't really have time to write up a full tutorial on adding new puzzles but hopefully the code is self-documenting enough for someone with C experience to understand.
pokefirered branch: https://github.com/Jaizu/pokefirered/tree/sliding_puzzles
by Jaizu
The special sets VAR_RESULT to TRUE if the player solves the puzzle, but this is obviously volatile so I recommend whatever happens in your script checks that VAR_RESULT is TRUE and sets a flag to remember if so. Then, if the player interacts with the script again, check if that flag is set and set VAR_0x8005 to TRUE if it is to display the solved puzzle. I didnt incorporate flag checking directly into the special as some people may want to reuse individual puzzles in several places and it's pretty simple to just use normal script flag checking to control this logic flow.